From 10826932be7861ebf5df8c66db7811c0510339f4 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 30 Mar 2026 16:25:49 +1100 Subject: [PATCH] fix(skin): responsive design fixes and improvements (#1129) --- .../src/define/audio/minimal-skin.tailwind.ts | 4 +- .../html/src/define/audio/minimal-skin.ts | 8 +- .../html/src/define/audio/skin.tailwind.ts | 103 +++++----- packages/html/src/define/audio/skin.ts | 102 +++++----- .../src/define/video/minimal-skin.tailwind.ts | 13 +- .../html/src/define/video/minimal-skin.ts | 10 +- .../html/src/define/video/skin.tailwind.ts | 170 ++++++++-------- packages/html/src/define/video/skin.ts | 152 +++++++------- .../presets/audio/minimal-skin.tailwind.tsx | 4 +- .../react/src/presets/audio/minimal-skin.tsx | 8 +- .../react/src/presets/audio/skin.tailwind.tsx | 99 +++++----- packages/react/src/presets/audio/skin.tsx | 106 +++++----- .../presets/video/minimal-skin.tailwind.tsx | 13 +- .../react/src/presets/video/minimal-skin.tsx | 10 +- .../react/src/presets/video/skin.tailwind.tsx | 180 +++++++++-------- packages/react/src/presets/video/skin.tsx | 186 +++++++++--------- packages/sandbox/templates/cdn/index.html | 2 +- .../sandbox/templates/html-audio/index.html | 2 +- .../html-background-video/index.html | 2 +- .../templates/html-dash-video/index.html | 2 +- .../templates/html-hls-video/index.html | 2 +- .../templates/html-mux-video/index.html | 2 +- .../html-simple-hls-video/index.html | 2 +- .../sandbox/templates/html-video/index.html | 2 +- .../sandbox/templates/react-audio/index.html | 2 +- .../react-background-video/index.html | 2 +- .../templates/react-dash-video/index.html | 2 +- .../templates/react-hls-video/index.html | 2 +- .../templates/react-mux-video/index.html | 2 +- .../react-simple-hls-video/index.html | 2 +- .../sandbox/templates/react-video/index.html | 2 +- packages/skins/src/default/css/audio.css | 18 +- .../default/css/components/button-group.css | 13 ++ .../components/{buttons.css => button.css} | 8 +- .../src/default/css/components/captions.css | 10 +- .../src/default/css/components/controls.css | 11 +- .../src/default/css/components/overlay.css | 3 +- .../src/default/css/components/popup.css | 5 +- .../src/default/css/components/preview.css | 4 +- .../skins/src/default/css/components/root.css | 13 +- .../src/default/css/components/slider.css | 4 +- .../src/default/css/components/surface.css | 8 - .../skins/src/default/css/components/time.css | 14 +- packages/skins/src/default/css/video.css | 77 ++++++-- .../src/default/tailwind/audio.tailwind.ts | 45 ++--- .../tailwind/components/button-group.ts | 3 + .../src/default/tailwind/components/button.ts | 4 +- .../default/tailwind/components/controls.ts | 8 +- .../default/tailwind/components/overlay.ts | 5 +- .../src/default/tailwind/components/popup.ts | 4 +- .../default/tailwind/components/preview.ts | 2 +- .../src/default/tailwind/components/root.ts | 3 +- .../src/default/tailwind/components/seek.ts | 1 - .../src/default/tailwind/components/slider.ts | 4 +- .../default/tailwind/components/surface.ts | 8 +- .../src/default/tailwind/video.tailwind.ts | 82 +++++--- packages/skins/src/minimal/css/audio.css | 36 ++-- .../minimal/css/components/button-group.css | 13 ++ .../components/{buttons.css => button.css} | 26 +-- .../src/minimal/css/components/captions.css | 10 +- .../src/minimal/css/components/controls.css | 2 + .../src/minimal/css/components/overlay.css | 5 +- .../src/minimal/css/components/popup.css | 19 +- .../src/minimal/css/components/preview.css | 5 +- .../skins/src/minimal/css/components/root.css | 13 +- .../src/minimal/css/components/slider.css | 4 +- .../skins/src/minimal/css/components/time.css | 25 ++- packages/skins/src/minimal/css/video.css | 105 ++++++++-- .../src/minimal/tailwind/audio.tailwind.ts | 36 ++-- .../src/minimal/tailwind/components/button.ts | 6 +- .../minimal/tailwind/components/controls.ts | 5 +- .../minimal/tailwind/components/overlay.ts | 5 +- .../src/minimal/tailwind/components/popup.ts | 10 +- .../minimal/tailwind/components/preview.ts | 2 +- .../src/minimal/tailwind/components/root.ts | 4 +- .../src/minimal/tailwind/components/seek.ts | 1 - .../src/minimal/tailwind/components/slider.ts | 4 +- .../src/minimal/tailwind/components/time.ts | 8 +- .../src/minimal/tailwind/video.tailwind.ts | 70 +++++-- 79 files changed, 1111 insertions(+), 858 deletions(-) create mode 100644 packages/skins/src/default/css/components/button-group.css rename packages/skins/src/default/css/components/{buttons.css => button.css} (95%) create mode 100644 packages/skins/src/default/tailwind/components/button-group.ts create mode 100644 packages/skins/src/minimal/css/components/button-group.css rename packages/skins/src/minimal/css/components/{buttons.css => button.css} (81%) diff --git a/packages/html/src/define/audio/minimal-skin.tailwind.ts b/packages/html/src/define/audio/minimal-skin.tailwind.ts index 94fddbb0..ff2a3f77 100644 --- a/packages/html/src/define/audio/minimal-skin.tailwind.ts +++ b/packages/html/src/define/audio/minimal-skin.tailwind.ts @@ -57,7 +57,7 @@ function getTemplateHTML() { - + ${renderIcon('seek', { class: cn(icon, iconFlipped) })} ${SEEK_TIME} @@ -67,7 +67,7 @@ function getTemplateHTML() { Seek backward ${SEEK_TIME} seconds - + ${renderIcon('seek', { class: icon })} ${SEEK_TIME} diff --git a/packages/html/src/define/audio/minimal-skin.ts b/packages/html/src/define/audio/minimal-skin.ts index c2760e8a..adb308f5 100644 --- a/packages/html/src/define/audio/minimal-skin.ts +++ b/packages/html/src/define/audio/minimal-skin.ts @@ -61,10 +61,10 @@ function getTemplateHTML() {
- - - - + + + + diff --git a/packages/html/src/define/audio/skin.tailwind.ts b/packages/html/src/define/audio/skin.tailwind.ts index 69377ca7..5161395f 100644 --- a/packages/html/src/define/audio/skin.tailwind.ts +++ b/packages/html/src/define/audio/skin.tailwind.ts @@ -2,6 +2,7 @@ import { ReactiveElement } from '@videojs/element'; import { renderIcon } from '@videojs/icons/render'; import { button, + buttonGroup, controls, icon, iconContainer, @@ -42,40 +43,42 @@ function getTemplateHTML() {
- - - ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} - ${renderIcon('play', { class: cn(icon, iconState.play.play) })} - ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} - - - Replay - Play - Pause +
+ + + ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} + ${renderIcon('play', { class: cn(icon, iconState.play.play) })} + ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} + + + Replay + Play + Pause + + + + + + ${renderIcon('seek', { class: cn(icon, iconFlipped) })} + ${SEEK_TIME} + + + + Seek backward ${SEEK_TIME} seconds - - - - ${renderIcon('seek', { class: cn(icon, iconFlipped) })} - ${SEEK_TIME} - - - - Seek backward ${SEEK_TIME} seconds - + + + ${renderIcon('seek', { class: icon })} + ${SEEK_TIME} + + + + Seek forward ${SEEK_TIME} seconds + +
- - - ${renderIcon('seek', { class: icon })} - ${SEEK_TIME} - - - - Seek forward ${SEEK_TIME} seconds - - - +
@@ -85,27 +88,29 @@ function getTemplateHTML() { - +
- - - Toggle playback rate - +
+ + + Toggle playback rate + - - ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} - ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} - ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} - + + ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + - - - - - - - - + + + + + + + + +
diff --git a/packages/html/src/define/audio/skin.ts b/packages/html/src/define/audio/skin.ts index 658d5c67..c0f688cb 100644 --- a/packages/html/src/define/audio/skin.ts +++ b/packages/html/src/define/audio/skin.ts @@ -27,39 +27,41 @@ function getTemplateHTML() {
- - ${renderIcon('restart', { class: 'media-icon media-icon--restart' })} - ${renderIcon('play', { class: 'media-icon media-icon--play' })} - ${renderIcon('pause', { class: 'media-icon media-icon--pause' })} - - - Replay - Play - Pause - +
+ + ${renderIcon('restart', { class: 'media-icon media-icon--restart' })} + ${renderIcon('play', { class: 'media-icon media-icon--play' })} + ${renderIcon('pause', { class: 'media-icon media-icon--pause' })} + + + Replay + Play + Pause + - - - ${renderIcon('seek', { class: 'media-icon media-icon--seek media-icon--flipped' })} - ${SEEK_TIME} - - - - Seek backward ${SEEK_TIME} seconds - + + + ${renderIcon('seek', { class: 'media-icon media-icon--seek media-icon--flipped' })} + ${SEEK_TIME} + + + + Seek backward ${SEEK_TIME} seconds + - - - ${renderIcon('seek', { class: 'media-icon media-icon--seek' })} - ${SEEK_TIME} - - - - Seek forward ${SEEK_TIME} seconds - + + + ${renderIcon('seek', { class: 'media-icon media-icon--seek' })} + ${SEEK_TIME} + + + + Seek forward ${SEEK_TIME} seconds + +
- - +
+ @@ -67,28 +69,30 @@ function getTemplateHTML() { - - + +
- - - Toggle playback rate - +
+ + + Toggle playback rate + - - ${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })} - ${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })} - ${renderIcon('volume-high', { class: 'media-icon media-icon--volume-high' })} - + + ${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })} + ${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })} + ${renderIcon('volume-high', { class: 'media-icon media-icon--volume-high' })} + - - - - - - - - + + + + + + + + +
diff --git a/packages/html/src/define/video/minimal-skin.tailwind.ts b/packages/html/src/define/video/minimal-skin.tailwind.ts index 1c8dc81c..52d77272 100644 --- a/packages/html/src/define/video/minimal-skin.tailwind.ts +++ b/packages/html/src/define/video/minimal-skin.tailwind.ts @@ -3,7 +3,8 @@ import { renderIcon } from '@videojs/icons/render/minimal'; import { bufferingIndicator, button, - buttonGroup, + buttonGroupEnd, + buttonGroupStart, controls, icon, iconContainer, @@ -61,7 +62,7 @@ function getTemplateHTML() { -
+
${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} @@ -75,7 +76,7 @@ function getTemplateHTML() { - + ${renderIcon('seek', { class: cn(icon, iconFlipped) })} ${SEEK_TIME} @@ -85,7 +86,7 @@ function getTemplateHTML() { Seek backward ${SEEK_TIME} seconds - + ${renderIcon('seek', { class: icon })} ${SEEK_TIME} @@ -114,13 +115,13 @@ function getTemplateHTML() {
- + ${renderIcon('spinner', { class: cn(icon, preview.spinner) })}
-
+
diff --git a/packages/html/src/define/video/minimal-skin.ts b/packages/html/src/define/video/minimal-skin.ts index 5bfd7a96..c03ae4e8 100644 --- a/packages/html/src/define/video/minimal-skin.ts +++ b/packages/html/src/define/video/minimal-skin.ts @@ -74,10 +74,10 @@ function getTemplateHTML() {
- - - - + + + + @@ -91,7 +91,7 @@ function getTemplateHTML() {
- + ${renderIcon('spinner', { class: 'media-preview__spinner media-icon' })}
diff --git a/packages/html/src/define/video/skin.tailwind.ts b/packages/html/src/define/video/skin.tailwind.ts index 074332ef..3c1255a7 100644 --- a/packages/html/src/define/video/skin.tailwind.ts +++ b/packages/html/src/define/video/skin.tailwind.ts @@ -3,6 +3,8 @@ import { renderIcon } from '@videojs/icons/render'; import { bufferingIndicator, button, + buttonGroupEnd, + buttonGroupStart, controls, icon, iconContainer, @@ -62,40 +64,42 @@ function getTemplateHTML() { - - - ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} - ${renderIcon('play', { class: cn(icon, iconState.play.play) })} - ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} - - - Replay - Play - Pause +
+ + + ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} + ${renderIcon('play', { class: cn(icon, iconState.play.play) })} + ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} + + + Replay + Play + Pause + + + + + + ${renderIcon('seek', { class: cn(icon, iconFlipped) })} + ${SEEK_TIME} + + + + Seek backward ${SEEK_TIME} seconds - - - - ${renderIcon('seek', { class: cn(icon, iconFlipped) })} - ${SEEK_TIME} - - - - Seek backward ${SEEK_TIME} seconds - + + + ${renderIcon('seek', { class: icon })} + ${SEEK_TIME} + + + + Seek forward ${SEEK_TIME} seconds + +
- - - ${renderIcon('seek', { class: icon })} - ${SEEK_TIME} - - - - Seek forward ${SEEK_TIME} seconds - - - +
@@ -106,65 +110,67 @@ function getTemplateHTML() {
- + ${renderIcon('spinner', { class: cn(icon, preview.spinner) })}
- +
- - - Toggle playback rate - - - - ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} - ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} - ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} - - - - - - - - - - - - - - ${renderIcon('captions-off', { class: cn(icon, iconState.captions.off) })} - ${renderIcon('captions-on', { class: cn(icon, iconState.captions.on) })} - - - Enable captions - Disable captions +
+ + + Toggle playback rate - - - - ${renderIcon('pip-enter', { class: cn(icon, iconState.pip.off) })} - ${renderIcon('pip-exit', { class: cn(icon, iconState.pip.on) })} - - - Enter picture-in-picture - Exit picture-in-picture - - + + ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + - - - ${renderIcon('fullscreen-enter', { class: cn(icon, iconState.fullscreen.enter) })} - ${renderIcon('fullscreen-exit', { class: cn(icon, iconState.fullscreen.exit) })} - - - Enter fullscreen - Exit fullscreen - - + + + + + + + + + + + + ${renderIcon('captions-off', { class: cn(icon, iconState.captions.off) })} + ${renderIcon('captions-on', { class: cn(icon, iconState.captions.on) })} + + + Enable captions + Disable captions + + + + + + ${renderIcon('pip-enter', { class: cn(icon, iconState.pip.off) })} + ${renderIcon('pip-exit', { class: cn(icon, iconState.pip.on) })} + + + Enter picture-in-picture + Exit picture-in-picture + + + + + + ${renderIcon('fullscreen-enter', { class: cn(icon, iconState.fullscreen.enter) })} + ${renderIcon('fullscreen-exit', { class: cn(icon, iconState.fullscreen.exit) })} + + + Enter fullscreen + Exit fullscreen + + +
diff --git a/packages/html/src/define/video/skin.ts b/packages/html/src/define/video/skin.ts index 380d4a46..9ae833ae 100644 --- a/packages/html/src/define/video/skin.ts +++ b/packages/html/src/define/video/skin.ts @@ -43,39 +43,41 @@ function getTemplateHTML() { - - ${renderIcon('restart', { class: 'media-icon media-icon--restart' })} - ${renderIcon('play', { class: 'media-icon media-icon--play' })} - ${renderIcon('pause', { class: 'media-icon media-icon--pause' })} - - - Replay - Play - Pause - +
+ + ${renderIcon('restart', { class: 'media-icon media-icon--restart' })} + ${renderIcon('play', { class: 'media-icon media-icon--play' })} + ${renderIcon('pause', { class: 'media-icon media-icon--pause' })} + + + Replay + Play + Pause + - - - ${renderIcon('seek', { class: 'media-icon media-icon--flipped' })} - ${SEEK_TIME} - - - - Seek backward ${SEEK_TIME} seconds - + + + ${renderIcon('seek', { class: 'media-icon media-icon--flipped' })} + ${SEEK_TIME} + + + + Seek backward ${SEEK_TIME} seconds + - - - ${renderIcon('seek', { class: 'media-icon' })} - ${SEEK_TIME} - - - - Seek forward ${SEEK_TIME} seconds - + + + ${renderIcon('seek', { class: 'media-icon' })} + ${SEEK_TIME} + + + + Seek forward ${SEEK_TIME} seconds + +
- - +
+ @@ -85,59 +87,61 @@ function getTemplateHTML() {
- + ${renderIcon('spinner', { class: 'media-preview__spinner media-icon' })}
- - + +
- - - Toggle playback rate - +
+ + + Toggle playback rate + - - ${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })} - ${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })} - ${renderIcon('volume-high', { class: 'media-icon media-icon--volume-high' })} - + + ${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })} + ${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })} + ${renderIcon('volume-high', { class: 'media-icon media-icon--volume-high' })} + - - - - - - - - + + + + + + + + - - ${renderIcon('captions-off', { class: 'media-icon media-icon--captions-off' })} - ${renderIcon('captions-on', { class: 'media-icon media-icon--captions-on' })} - - - Enable captions - Disable captions - + + ${renderIcon('captions-off', { class: 'media-icon media-icon--captions-off' })} + ${renderIcon('captions-on', { class: 'media-icon media-icon--captions-on' })} + + + Enable captions + Disable captions + - - ${renderIcon('pip-enter', { class: 'media-icon media-icon--pip-enter' })} - ${renderIcon('pip-exit', { class: 'media-icon media-icon--pip-exit' })} - - - Enter picture-in-picture - Exit picture-in-picture - + + ${renderIcon('pip-enter', { class: 'media-icon media-icon--pip-enter' })} + ${renderIcon('pip-exit', { class: 'media-icon media-icon--pip-exit' })} + + + Enter picture-in-picture + Exit picture-in-picture + - - ${renderIcon('fullscreen-enter', { class: 'media-icon media-icon--fullscreen-enter' })} - ${renderIcon('fullscreen-exit', { class: 'media-icon media-icon--fullscreen-exit' })} - - - Enter fullscreen - Exit fullscreen - + + ${renderIcon('fullscreen-enter', { class: 'media-icon media-icon--fullscreen-enter' })} + ${renderIcon('fullscreen-exit', { class: 'media-icon media-icon--fullscreen-exit' })} + + + Enter fullscreen + Exit fullscreen + +
diff --git a/packages/react/src/presets/audio/minimal-skin.tailwind.tsx b/packages/react/src/presets/audio/minimal-skin.tailwind.tsx index 198e4f9c..0f29d189 100644 --- a/packages/react/src/presets/audio/minimal-skin.tailwind.tsx +++ b/packages/react/src/presets/audio/minimal-skin.tailwind.tsx @@ -166,7 +166,7 @@ export function MinimalAudioSkinTailwind(props: MinimalAudioSkinProps): ReactNod }> + }> {SEEK_TIME} @@ -180,7 +180,7 @@ export function MinimalAudioSkinTailwind(props: MinimalAudioSkinProps): ReactNod }> + }> {SEEK_TIME} diff --git a/packages/react/src/presets/audio/minimal-skin.tsx b/packages/react/src/presets/audio/minimal-skin.tsx index c0ebe58c..26146a25 100644 --- a/packages/react/src/presets/audio/minimal-skin.tsx +++ b/packages/react/src/presets/audio/minimal-skin.tsx @@ -139,10 +139,10 @@ export function MinimalAudioSkin(props: MinimalAudioSkinProps): ReactNode {
- - - - + + + + diff --git a/packages/react/src/presets/audio/skin.tailwind.tsx b/packages/react/src/presets/audio/skin.tailwind.tsx index 44b079cf..c920d3e0 100644 --- a/packages/react/src/presets/audio/skin.tailwind.tsx +++ b/packages/react/src/presets/audio/skin.tailwind.tsx @@ -9,6 +9,7 @@ import { } from '@videojs/icons/react'; import { button, + buttonGroup, controls, error, icon, @@ -146,50 +147,52 @@ export function AudioSkinTailwind(props: AudioSkinProps): ReactNode {
- - }> - - - - - } - /> - - - - +
+ + }> + + + + + } + /> + + + + - - }> - - - {SEEK_TIME} - - - } - /> - Seek backward {SEEK_TIME} seconds - + + }> + + + {SEEK_TIME} + + + } + /> + Seek backward {SEEK_TIME} seconds + - - }> - - - {SEEK_TIME} - - - } - /> - Seek forward {SEEK_TIME} seconds - + + }> + + + {SEEK_TIME} + + + } + /> + Seek forward {SEEK_TIME} seconds + +
- +
}> }> @@ -199,14 +202,16 @@ export function AudioSkinTailwind(props: AudioSkinProps): ReactNode { } /> - +
- - } />} /> - Toggle playback rate - +
+ + } />} /> + Toggle playback rate + - + +
diff --git a/packages/react/src/presets/audio/skin.tsx b/packages/react/src/presets/audio/skin.tsx index 2b2be95f..0120911a 100644 --- a/packages/react/src/presets/audio/skin.tsx +++ b/packages/react/src/presets/audio/skin.tsx @@ -93,51 +93,53 @@ export function AudioSkin(props: AudioSkinProps): ReactNode {
- - }> - - - - - } - /> - - - - +
+ + }> + + + + + } + /> + + + + - - }> - - - {SEEK_TIME} - - - } - /> - Seek backward {SEEK_TIME} seconds - + + }> + + + {SEEK_TIME} + + + } + /> + Seek backward {SEEK_TIME} seconds + - - }> - - - {SEEK_TIME} - - - } - /> - Seek forward {SEEK_TIME} seconds - + + }> + + + {SEEK_TIME} + + + } + /> + Seek forward {SEEK_TIME} seconds + +
- - +
+ @@ -145,17 +147,19 @@ export function AudioSkin(props: AudioSkinProps): ReactNode { - - + +
- - } />} - /> - Toggle playback rate - +
+ + } />} + /> + Toggle playback rate + - + +
diff --git a/packages/react/src/presets/video/minimal-skin.tailwind.tsx b/packages/react/src/presets/video/minimal-skin.tailwind.tsx index cd5a5506..32d8dd28 100644 --- a/packages/react/src/presets/video/minimal-skin.tailwind.tsx +++ b/packages/react/src/presets/video/minimal-skin.tailwind.tsx @@ -18,7 +18,8 @@ import { playbackRate } from '@videojs/skins/default/tailwind/video.tailwind'; import { bufferingIndicator, button, - buttonGroup, + buttonGroupEnd, + buttonGroupStart, controls, error, icon, @@ -201,7 +202,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod className={controls} > -
+
}> + }> {SEEK_TIME} @@ -234,7 +235,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod }> + }> {SEEK_TIME} @@ -263,13 +264,13 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
- +
-
+
} />} /> Toggle playback rate diff --git a/packages/react/src/presets/video/minimal-skin.tsx b/packages/react/src/presets/video/minimal-skin.tsx index 99086ac2..896612ef 100644 --- a/packages/react/src/presets/video/minimal-skin.tsx +++ b/packages/react/src/presets/video/minimal-skin.tsx @@ -182,10 +182,10 @@ export function MinimalVideoSkin(props: MinimalVideoSkinProps): ReactNode {
- - - - + + + + @@ -199,7 +199,7 @@ export function MinimalVideoSkin(props: MinimalVideoSkinProps): ReactNode {
- +
diff --git a/packages/react/src/presets/video/skin.tailwind.tsx b/packages/react/src/presets/video/skin.tailwind.tsx index 6794af1e..6e07c633 100644 --- a/packages/react/src/presets/video/skin.tailwind.tsx +++ b/packages/react/src/presets/video/skin.tailwind.tsx @@ -17,6 +17,8 @@ import { import { bufferingIndicator, button, + buttonGroupEnd, + buttonGroupStart, controls, error, icon, @@ -202,50 +204,52 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode { className={controls} > - - }> - - - - - } - /> - - - - +
+ + }> + + + + + } + /> + + + + - - }> - - - {SEEK_TIME} - - - } - /> - Seek backward {SEEK_TIME} seconds - + + }> + + + {SEEK_TIME} + + + } + /> + Seek backward {SEEK_TIME} seconds + - - }> - - - {SEEK_TIME} - - - } - /> - Seek forward {SEEK_TIME} seconds - + + }> + + + {SEEK_TIME} + + + } + /> + Seek forward {SEEK_TIME} seconds + +
- +
}> }> @@ -255,61 +259,63 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode { } />
- +
- +
- - } />} /> - Toggle playback rate - +
+ + } />} /> + Toggle playback rate + - + - - }> - - - - } - /> - - - - + + }> + + + + } + /> + + + + - - }> - - - - } - /> - - - - + + }> + + + + } + /> + + + + - - }> - - - - } - /> - - - - + + }> + + + + } + /> + + + + +
diff --git a/packages/react/src/presets/video/skin.tsx b/packages/react/src/presets/video/skin.tsx index 5741edf0..fdb3e8a6 100644 --- a/packages/react/src/presets/video/skin.tsx +++ b/packages/react/src/presets/video/skin.tsx @@ -138,51 +138,53 @@ export function VideoSkin(props: VideoSkinProps): ReactNode { - - }> - - - - - } - /> - - - - +
+ + }> + + + + + } + /> + + + + - - }> - - - {SEEK_TIME} - - - } - /> - Seek backward {SEEK_TIME} seconds - + + }> + + + {SEEK_TIME} + + + } + /> + Seek backward {SEEK_TIME} seconds + - - }> - - - {SEEK_TIME} - - - } - /> - Seek forward {SEEK_TIME} seconds - + + }> + + + {SEEK_TIME} + + + } + /> + Seek forward {SEEK_TIME} seconds + +
- - +
+ @@ -192,63 +194,65 @@ export function VideoSkin(props: VideoSkinProps): ReactNode {
- +
- - + +
- - } />} - /> - Toggle playback rate - +
+ + } />} + /> + Toggle playback rate + - + - - }> - - - - } - /> - - - - + + }> + + + + } + /> + + + + - - }> - - - - } - /> - - - - + + }> + + + + } + /> + + + + - - }> - - - - } - /> - - - - + + }> + + + + } + /> + + + + +
diff --git a/packages/sandbox/templates/cdn/index.html b/packages/sandbox/templates/cdn/index.html index 1db8639e..a3fa9a95 100644 --- a/packages/sandbox/templates/cdn/index.html +++ b/packages/sandbox/templates/cdn/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/html-audio/index.html b/packages/sandbox/templates/html-audio/index.html index 73b10839..222f41fb 100644 --- a/packages/sandbox/templates/html-audio/index.html +++ b/packages/sandbox/templates/html-audio/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/html-background-video/index.html b/packages/sandbox/templates/html-background-video/index.html index 39951d8f..bd2a9bcf 100644 --- a/packages/sandbox/templates/html-background-video/index.html +++ b/packages/sandbox/templates/html-background-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/html-dash-video/index.html b/packages/sandbox/templates/html-dash-video/index.html index 35793c23..03e61484 100644 --- a/packages/sandbox/templates/html-dash-video/index.html +++ b/packages/sandbox/templates/html-dash-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/html-hls-video/index.html b/packages/sandbox/templates/html-hls-video/index.html index a081ee0e..669273ca 100644 --- a/packages/sandbox/templates/html-hls-video/index.html +++ b/packages/sandbox/templates/html-hls-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/html-mux-video/index.html b/packages/sandbox/templates/html-mux-video/index.html index f8a7fdc8..201dfde5 100644 --- a/packages/sandbox/templates/html-mux-video/index.html +++ b/packages/sandbox/templates/html-mux-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/html-simple-hls-video/index.html b/packages/sandbox/templates/html-simple-hls-video/index.html index a081ee0e..669273ca 100644 --- a/packages/sandbox/templates/html-simple-hls-video/index.html +++ b/packages/sandbox/templates/html-simple-hls-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/html-video/index.html b/packages/sandbox/templates/html-video/index.html index a081ee0e..669273ca 100644 --- a/packages/sandbox/templates/html-video/index.html +++ b/packages/sandbox/templates/html-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/react-audio/index.html b/packages/sandbox/templates/react-audio/index.html index 1f2d2633..301b1dc2 100644 --- a/packages/sandbox/templates/react-audio/index.html +++ b/packages/sandbox/templates/react-audio/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/react-background-video/index.html b/packages/sandbox/templates/react-background-video/index.html index 44567c35..3fa99ab0 100644 --- a/packages/sandbox/templates/react-background-video/index.html +++ b/packages/sandbox/templates/react-background-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/react-dash-video/index.html b/packages/sandbox/templates/react-dash-video/index.html index ae4ecb9c..63967ead 100644 --- a/packages/sandbox/templates/react-dash-video/index.html +++ b/packages/sandbox/templates/react-dash-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/react-hls-video/index.html b/packages/sandbox/templates/react-hls-video/index.html index 77317e25..11c035c7 100644 --- a/packages/sandbox/templates/react-hls-video/index.html +++ b/packages/sandbox/templates/react-hls-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/react-mux-video/index.html b/packages/sandbox/templates/react-mux-video/index.html index 2596b060..4e909677 100644 --- a/packages/sandbox/templates/react-mux-video/index.html +++ b/packages/sandbox/templates/react-mux-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/react-simple-hls-video/index.html b/packages/sandbox/templates/react-simple-hls-video/index.html index 77317e25..11c035c7 100644 --- a/packages/sandbox/templates/react-simple-hls-video/index.html +++ b/packages/sandbox/templates/react-simple-hls-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/sandbox/templates/react-video/index.html b/packages/sandbox/templates/react-video/index.html index 77317e25..11c035c7 100644 --- a/packages/sandbox/templates/react-video/index.html +++ b/packages/sandbox/templates/react-video/index.html @@ -7,7 +7,7 @@ - +
diff --git a/packages/skins/src/default/css/audio.css b/packages/skins/src/default/css/audio.css index 66d634ef..dd30b596 100644 --- a/packages/skins/src/default/css/audio.css +++ b/packages/skins/src/default/css/audio.css @@ -5,7 +5,8 @@ @import "./components/error.css"; @import "./components/controls.css"; @import "./components/time.css"; -@import "./components/buttons.css"; +@import "./components/button.css"; +@import "./components/button-group.css"; @import "./components/icons.css"; @import "./components/slider.css"; @import "./components/popup.css"; @@ -17,7 +18,6 @@ ========================================================================== */ .media-default-skin--audio { - --media-border-color: oklch(0 0 0 / 0.1); --media-surface-background-color: oklch(1 0 0 / 0.5); --media-surface-inner-border-color: oklch(1 0 0 / 0.1); --media-surface-outer-border-color: oklch(0 0 0 / 0.05); @@ -28,6 +28,8 @@ --media-error-dialog-transition-delay: 100ms; --media-popup-transition-duration: 100ms; --media-popup-transition-timing-function: ease-out; + --media-tooltip-side-offset: 0.75rem; + --media-popover-side-offset: 0.75rem; @media (prefers-reduced-motion: reduce) { --media-error-dialog-transition-duration: 50ms; @@ -36,10 +38,20 @@ } @media (prefers-color-scheme: dark) { - --media-border-color: oklch(1 0 0 / 0.1); --media-surface-background-color: oklch(0 0 0 / 0.4); --media-text-color: var(--media-color-primary, oklch(1 0 0)); } + + @media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) { + --media-surface-background-color: oklch(1 0 0); + --media-surface-outer-border-color: oklch(0 0 0 / 0.05); + } + + @media (prefers-color-scheme: dark) and ((prefers-reduced-transparency: reduce) or (prefers-contrast: more)) { + --media-surface-background-color: oklch(0 0 0); + --media-surface-inner-border-color: oklch(1 0 0 / 0.2); + --media-surface-outer-border-color: transparent; + } } /* ========================================================================== diff --git a/packages/skins/src/default/css/components/button-group.css b/packages/skins/src/default/css/components/button-group.css new file mode 100644 index 00000000..db840f7c --- /dev/null +++ b/packages/skins/src/default/css/components/button-group.css @@ -0,0 +1,13 @@ +/* ========================================================================== + Button Groups + ========================================================================== */ + +.media-default-skin .media-button-group { + display: flex; + align-items: center; + gap: 0.075rem; + + @container media-root (width > 42rem) { + gap: 0.125rem; + } +} diff --git a/packages/skins/src/default/css/components/buttons.css b/packages/skins/src/default/css/components/button.css similarity index 95% rename from packages/skins/src/default/css/components/buttons.css rename to packages/skins/src/default/css/components/button.css index a55e8d07..06cfd69b 100644 --- a/packages/skins/src/default/css/components/buttons.css +++ b/packages/skins/src/default/css/components/button.css @@ -14,6 +14,8 @@ outline: 2px solid transparent; outline-offset: -2px; transition-property: background-color, outline-offset, scale; + /* Fix weird jumping when clicking on the buttons in Safari. */ + will-change: scale; transition-duration: 150ms; transition-timing-function: ease-out; cursor: pointer; @@ -66,7 +68,7 @@ /* Icon button variant */ .media-default-skin .media-button--icon { display: grid; - width: 2.125rem; + width: 2.25rem; padding: 0; aspect-ratio: 1; @@ -94,10 +96,6 @@ right: unset; left: -1px; } - - @container media-controls (width < 28rem) { - display: none; - } } /* Playback rate button */ diff --git a/packages/skins/src/default/css/components/captions.css b/packages/skins/src/default/css/components/captions.css index 4b5dfa59..6976c469 100644 --- a/packages/skins/src/default/css/components/captions.css +++ b/packages/skins/src/default/css/components/captions.css @@ -4,11 +4,17 @@ .media-default-skin { --media-caption-track-duration: var(--media-controls-transition-duration); - --media-caption-track-delay: calc(var(--media-controls-transition-delay) + 25ms); + --media-caption-track-delay: 25ms; --media-caption-track-y: -0.5rem; &:has(.media-controls[data-visible]) { - --media-caption-track-y: -3.5rem; + --media-caption-track-y: -5.5rem; + } + + @container media-root (width > 42rem) { + &:has(.media-controls[data-visible]) > * { + --media-caption-track-y: -3.5rem; + } } } diff --git a/packages/skins/src/default/css/components/controls.css b/packages/skins/src/default/css/components/controls.css index 13cb7ef2..7c2f9447 100644 --- a/packages/skins/src/default/css/components/controls.css +++ b/packages/skins/src/default/css/components/controls.css @@ -6,18 +6,13 @@ container: media-controls / inline-size; display: flex; align-items: center; - gap: 0.075rem; - padding: 0.175rem; - border-radius: calc(infinity * 1px); + column-gap: 0.075rem; + padding: 0.375rem; + border-radius: 1.5rem; --media-controls-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15)); --media-controls-current-shadow-color-subtle: oklch( from var(--media-controls-current-shadow-color) l c h / calc(alpha * 0.4) ); text-shadow: 0 1px 0 var(--media-controls-current-shadow-color); - - @container media-root (width > 40rem) { - gap: 0.125rem; - padding: 0.25rem; - } } diff --git a/packages/skins/src/default/css/components/overlay.css b/packages/skins/src/default/css/components/overlay.css index 2a19f1b1..1fe0cf4d 100644 --- a/packages/skins/src/default/css/components/overlay.css +++ b/packages/skins/src/default/css/components/overlay.css @@ -7,12 +7,11 @@ inset: 0; border-radius: inherit; background-image: linear-gradient(to top, oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.3), oklch(0 0 0 / 0)); - backdrop-filter: blur(0) saturate(1.5); + backdrop-filter: blur(0) saturate(1); opacity: 0; pointer-events: none; transition-property: opacity, backdrop-filter; transition-duration: var(--media-controls-transition-duration); - transition-delay: var(--media-controls-transition-delay); transition-timing-function: ease-out; } diff --git a/packages/skins/src/default/css/components/popup.css b/packages/skins/src/default/css/components/popup.css index 5a6fa5ce..78d277c3 100644 --- a/packages/skins/src/default/css/components/popup.css +++ b/packages/skins/src/default/css/components/popup.css @@ -69,8 +69,6 @@ } .media-default-skin .media-popover { - --media-popover-side-offset: 0.5rem; - &[data-side="top"]::before, &[data-side="bottom"]::before { height: var(--media-popover-side-offset); @@ -81,7 +79,7 @@ } } .media-default-skin .media-popover--volume { - padding: 0.625rem 0.25rem; + padding: 0.75rem 0; border-radius: calc(infinity * 1px); &:has(media-volume-slider[data-availability="unsupported"]) { @@ -94,7 +92,6 @@ border-radius: calc(infinity * 1px); font-size: 0.75rem; white-space: nowrap; - --media-tooltip-side-offset: 0.75rem; &[data-side="top"]::before, &[data-side="bottom"]::before { diff --git a/packages/skins/src/default/css/components/preview.css b/packages/skins/src/default/css/components/preview.css index e2f141ec..03bfafc8 100644 --- a/packages/skins/src/default/css/components/preview.css +++ b/packages/skins/src/default/css/components/preview.css @@ -4,6 +4,7 @@ .media-default-skin .media-preview { background-color: oklch(0 0 0 / 0.9); border-radius: 0.75rem; + pointer-events: none; & .media-preview__thumbnail { display: block; @@ -20,12 +21,11 @@ } } - & .media-preview__timestamp { + & .media-preview__time { position: absolute; bottom: 0.5rem; inset-inline: 0; text-align: center; - font-variant-numeric: tabular-nums; } & .media-overlay { diff --git a/packages/skins/src/default/css/components/root.css b/packages/skins/src/default/css/components/root.css index 1d81f526..e8844311 100644 --- a/packages/skins/src/default/css/components/root.css +++ b/packages/skins/src/default/css/components/root.css @@ -3,12 +3,12 @@ ========================================================================== */ .media-default-skin { + container: media-root / inline-size; position: relative; isolation: isolate; display: block; height: 100%; width: 100%; - container: media-root / inline-size; border-radius: var(--media-border-radius, 2rem); font-family: Inter Variable, @@ -16,9 +16,18 @@ ui-sans-serif, system-ui, sans-serif; - font-size: 0.8125rem; line-height: 1.5; letter-spacing: normal; -webkit-font-smoothing: auto; -moz-osx-font-smoothing: auto; + + & > * { + font-size: 0.75rem; /* 12px at 100% font size */ + } + + @container media-root (width > 48rem) { + & > * { + font-size: 0.875rem; /* 14px at 100% font size */ + } + } } diff --git a/packages/skins/src/default/css/components/slider.css b/packages/skins/src/default/css/components/slider.css index e639aba9..a23d85ac 100644 --- a/packages/skins/src/default/css/components/slider.css +++ b/packages/skins/src/default/css/components/slider.css @@ -15,11 +15,11 @@ &[data-orientation="horizontal"] { min-width: 5rem; width: 100%; - height: 1.25rem; + height: 2rem; } &[data-orientation="vertical"] { - width: 1.25rem; + width: 2rem; height: 5rem; } } diff --git a/packages/skins/src/default/css/components/surface.css b/packages/skins/src/default/css/components/surface.css index 1c8c1b0b..2e2ae90b 100644 --- a/packages/skins/src/default/css/components/surface.css +++ b/packages/skins/src/default/css/components/surface.css @@ -20,12 +20,4 @@ box-shadow: inset 0 0 0 1px var(--media-surface-inner-border-color); pointer-events: none; } - - @media (prefers-reduced-transparency: reduce) { - background-color: oklch(from var(--media-surface-background-color) l c h / 0.7); - } - - @media (prefers-contrast: more) { - background-color: oklch(from var(--media-surface-background-color) l c h / 0.9); - } } diff --git a/packages/skins/src/default/css/components/time.css b/packages/skins/src/default/css/components/time.css index 941f6f48..c205c34b 100644 --- a/packages/skins/src/default/css/components/time.css +++ b/packages/skins/src/default/css/components/time.css @@ -2,23 +2,15 @@ Time Display ========================================================================== */ -.media-default-skin .media-time { - container: media-time / inline-size; +.media-default-skin .media-time-controls { + container: media-time-controls / inline-size; display: flex; align-items: center; flex: 1; gap: 0.75rem; padding-inline: 0.5rem; - - & .media-time__value:first-child { - display: none; - - @container media-time (width > 18rem) { - display: block; - } - } } -.media-default-skin .media-time__value { +.media-default-skin .media-time { font-variant-numeric: tabular-nums; } diff --git a/packages/skins/src/default/css/video.css b/packages/skins/src/default/css/video.css index 7e1c9744..7a1e7893 100644 --- a/packages/skins/src/default/css/video.css +++ b/packages/skins/src/default/css/video.css @@ -7,7 +7,8 @@ @import "./components/error.css"; @import "./components/controls.css"; @import "./components/time.css"; -@import "./components/buttons.css"; +@import "./components/button.css"; +@import "./components/button-group.css"; @import "./components/icons.css"; @import "./components/poster.css"; @import "./components/preview.css"; @@ -42,13 +43,14 @@ --media-surface-backdrop-filter: blur(16px) saturate(1.5); --media-video-border-radius: var(--media-border-radius, 2rem); --media-controls-transition-duration: 100ms; - --media-controls-transition-delay: 0ms; --media-controls-transition-timing-function: ease-out; --media-error-dialog-transition-duration: 350ms; --media-error-dialog-transition-delay: 100ms; --media-error-dialog-transition-timing-function: var(--media-spring-transition); --media-popup-transition-duration: 100ms; --media-popup-transition-timing-function: ease-out; + --media-tooltip-side-offset: 0.75rem; + --media-popover-side-offset: 0.5rem; @media (prefers-reduced-motion: reduce) { --media-error-dialog-transition-duration: 50ms; @@ -61,10 +63,15 @@ --media-border-color: oklch(1 0 0 / 0.15); } + @media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) { + --media-surface-background-color: oklch(0 0 0); + --media-surface-inner-border-color: oklch(1 0 0 / 0.25); + --media-surface-outer-border-color: transparent; + } + &:has(.media-controls:not([data-visible])) { /* Slight delay to hide controls on non-touch devices after interaction */ @media (pointer: fine) { - --media-controls-transition-delay: 500ms; --media-controls-transition-duration: 300ms; } @media (pointer: coarse) { @@ -145,13 +152,13 @@ ========================================================================== */ .media-default-skin--video .media-controls { + flex-wrap: wrap; position: absolute; - bottom: 0.75rem; - inset-inline: 0.75rem; + bottom: 0.5rem; + inset-inline: 0.5rem; z-index: 10; color: var(--media-color-primary, oklch(1 0 0)); transition-duration: var(--media-controls-transition-duration); - transition-delay: var(--media-controls-transition-delay); transition-timing-function: var(--media-controls-transition-timing-function); transform-origin: bottom; @@ -178,17 +185,49 @@ scale: 1; } } + + & .media-time-controls { + order: -1; + flex: 0 0 100%; + padding-inline: 0.625rem; + } + + & .media-button-group:first-child { + flex: 1; + text-align: left; + } + + & .media-button-group:last-child { + flex: 1; + justify-content: end; + } + + @container media-root (width > 42rem) { + bottom: 0.75rem; + inset-inline: 0.75rem; + flex-wrap: nowrap; + column-gap: 0.125rem; + padding: 0.25rem; + + & .media-time-controls { + order: unset; + flex: 1; + } + + & .media-button-group:first-child, + & .media-button-group:last-child { + flex: 0 0 auto; + } + } } .media-default-skin--video .media-error[data-open] ~ .media-controls { display: none; } -/* Hide cursor when controls are hidden in fullscreen */ -@media (pointer: fine) { - .media-default-skin--video:fullscreen:has(.media-controls:not([data-visible])) { - cursor: none; - } +/* Hide cursor when controls are hidden */ +.media-default-skin--video:has(.media-controls:not([data-visible])) { + cursor: none; } /* ========================================================================== @@ -201,8 +240,20 @@ } .media-default-skin--video .media-slider__preview { + --media-preview-max-width: 11rem; + --media-preview-padding: -1.125rem; + /** + Inset is the difference between the container width and the slider (100%) width. + Divided by 2 as we render the time on both sides. + */ + --media-preview-inset: calc((100cqi - 100%) / 2); + position: absolute; - left: var(--media-slider-pointer); + left: clamp( + calc(var(--media-preview-max-width) / 2 + var(--media-preview-padding) - var(--media-preview-inset)), + var(--media-slider-pointer), + calc(100% - var(--media-preview-max-width) / 2 - var(--media-preview-padding) + var(--media-preview-inset)) + ); bottom: calc(100% + 1.2rem); translate: -50%; opacity: 0; @@ -215,7 +266,7 @@ pointer-events: none; & .media-preview__thumbnail { - max-width: 11rem; + max-width: var(--media-preview-max-width); } &:has(.media-preview__thumbnail[data-loading]) { diff --git a/packages/skins/src/default/tailwind/audio.tailwind.ts b/packages/skins/src/default/tailwind/audio.tailwind.ts index 250c4c29..ae577b04 100644 --- a/packages/skins/src/default/tailwind/audio.tailwind.ts +++ b/packages/skins/src/default/tailwind/audio.tailwind.ts @@ -5,7 +5,7 @@ import { error as baseError } from './components/error'; import { popup as basePopup } from './components/popup'; import { root as baseRoot } from './components/root'; import { slider as baseSlider } from './components/slider'; -import { surface as baseSurface } from './components/surface'; +import { surface } from './components/surface'; /* ========================================================================== Root @@ -14,32 +14,32 @@ import { surface as baseSurface } from './components/surface'; export const root = cn( baseRoot, '[--media-text-color:var(--media-color-primary,oklch(0_0_0))]', + '[--media-surface-background-color:oklch(1_0_0/0.5)]', + '[--media-surface-inner-border-color:oklch(1_0_0/0.1)]', + '[--media-surface-outer-border-color:oklch(0_0_0/0.05)]', + '[--media-surface-shadow-color:oklch(0_0_0/0.15)]', + '[--media-surface-backdrop-filter:blur(16px)_saturate(1.5)]', '[--media-error-dialog-transition-duration:250ms]', '[--media-error-dialog-transition-delay:100ms]', '[--media-popup-transition-duration:100ms]', '[--media-popup-transition-timing-function:ease-out]', + '[--media-tooltip-side-offset:0.75rem]', + '[--media-popover-side-offset:0.75rem]', 'motion-reduce:[--media-error-dialog-transition-duration:50ms]', 'motion-reduce:[--media-error-dialog-transition-delay:0ms]', 'motion-reduce:[--media-popup-transition-duration:0ms]', - 'dark:[--media-text-color:var(--media-color-primary,oklch(1_0_0))]' -); - -/* ========================================================================== - Surface (shared glass effect for tooltips, popovers, controls) - ========================================================================== */ - -export const surface = cn( - baseSurface, - 'bg-white/50 dark:bg-black/40', - 'backdrop-blur-lg backdrop-saturate-150', - // Border and shadow - 'ring-black/5 shadow-sm shadow-black/15', - // Border to enhance contrast on lighter pages - 'after:ring-white/10', - // Reduced transparency for users with preference - '[@media(prefers-reduced-transparency:reduce)]:bg-white/70 dark:[@media(prefers-reduced-transparency:reduce)]:bg-black/70', - // High contrast mode - 'contrast-more:bg-white/90 dark:contrast-more:bg-black/90' + 'dark:[--media-surface-background-color:oklch(0_0_0/0.4)]', + 'dark:[--media-text-color:var(--media-color-primary,oklch(1_0_0))]', + '[@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(1_0_0)]', + 'contrast-more:[--media-surface-background-color:oklch(1_0_0)]', + '[@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:oklch(0_0_0/0.05)]', + 'contrast-more:[--media-surface-outer-border-color:oklch(0_0_0/0.05)]', + 'dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)]', + 'dark:contrast-more:[--media-surface-background-color:oklch(0_0_0)]', + 'dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-inner-border-color:oklch(1_0_0/0.2)]', + 'dark:contrast-more:[--media-surface-inner-border-color:oklch(1_0_0/0.2)]', + 'dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:transparent]', + 'dark:contrast-more:[--media-surface-outer-border-color:transparent]' ); /* ========================================================================== @@ -89,8 +89,8 @@ export const error = { 'transition-[opacity,filter] ease-out', 'duration-(--media-error-dialog-transition-duration)', 'delay-(--media-error-dialog-transition-delay)', - 'group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-[4px]', - 'group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-[4px]', + 'group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-xs', + 'group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-xs', 'group-data-ending-style/error:delay-0' ), content: 'flex flex-1 items-center gap-2', @@ -103,6 +103,7 @@ export const error = { export { iconState } from '../../shared/tailwind/icon-state'; export { tooltipState } from '../../shared/tailwind/tooltip-state'; export { button } from './components/button'; +export { buttonGroup } from './components/button-group'; export { icon, iconContainer, iconFlipped, iconHidden } from './components/icon'; export { playbackRate } from './components/playback-rate'; export { seek } from './components/seek'; diff --git a/packages/skins/src/default/tailwind/components/button-group.ts b/packages/skins/src/default/tailwind/components/button-group.ts new file mode 100644 index 00000000..143d2b02 --- /dev/null +++ b/packages/skins/src/default/tailwind/components/button-group.ts @@ -0,0 +1,3 @@ +import { cn } from '@videojs/utils/style'; + +export const buttonGroup = cn('flex items-center gap-[0.075rem]', '@2xl/media-root:gap-0.5'); diff --git a/packages/skins/src/default/tailwind/components/button.ts b/packages/skins/src/default/tailwind/components/button.ts index 5dfc07f2..813d6978 100644 --- a/packages/skins/src/default/tailwind/components/button.ts +++ b/packages/skins/src/default/tailwind/components/button.ts @@ -5,7 +5,7 @@ export const button = { 'flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation', 'py-2 px-4 rounded-full', 'outline-2 outline-transparent -outline-offset-2', - 'transition-[background-color,outline-offset,scale] duration-150 ease-out', + 'transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out', 'active:scale-[0.98]', 'disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale', 'focus-visible:outline-current focus-visible:outline-offset-2', @@ -18,5 +18,5 @@ export const button = { 'focus-visible:bg-current/10', 'aria-expanded:bg-current/10' ), - icon: cn('grid w-[2.125rem] aspect-square p-0', 'active:scale-90'), + icon: cn('grid w-9 aspect-square p-0', 'active:scale-90'), }; diff --git a/packages/skins/src/default/tailwind/components/controls.ts b/packages/skins/src/default/tailwind/components/controls.ts index 9f718e5d..25092f12 100644 --- a/packages/skins/src/default/tailwind/components/controls.ts +++ b/packages/skins/src/default/tailwind/components/controls.ts @@ -5,13 +5,11 @@ export const controls = cn( 'peer/controls', // Layout '@container/media-controls', - 'p-[0.175rem] flex items-center gap-[0.075rem]', - 'rounded-full', + 'p-[0.375rem] flex items-center gap-x-[0.075rem]', + 'rounded-3xl', // Shadow color variables (derived from currentColor lightness) '[--media-controls-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))]', '[--media-controls-current-shadow-color-subtle:oklch(from_var(--media-controls-current-shadow-color)_l_c_h/calc(alpha*0.4))]', // Text shadow - 'text-shadow-[0_1px_0_var(--media-controls-current-shadow-color)]', - // Wider container - '@2xl/media-root:p-1 @2xl/media-root:gap-0.5' + 'text-shadow-2xs text-shadow-(color:--media-controls-current-shadow-color)' ); diff --git a/packages/skins/src/default/tailwind/components/overlay.ts b/packages/skins/src/default/tailwind/components/overlay.ts index 5630f020..f079574f 100644 --- a/packages/skins/src/default/tailwind/components/overlay.ts +++ b/packages/skins/src/default/tailwind/components/overlay.ts @@ -7,11 +7,10 @@ export const overlay = cn( // Default: hidden 'opacity-0', 'bg-linear-to-t from-black/50 via-black/30 to-transparent', - 'backdrop-blur-none backdrop-saturate-150', + 'backdrop-blur-none backdrop-saturate-100', // Transitions 'transition-[opacity,backdrop-filter]', 'duration-(--media-controls-transition-duration)', - 'delay-(--media-controls-transition-delay)', 'ease-out', // Shown when controls visible 'peer-data-visible/controls:opacity-100', @@ -19,5 +18,5 @@ export const overlay = cn( 'peer-data-open/error:opacity-100', 'peer-data-open/error:duration-(--media-error-dialog-transition-duration)', 'peer-data-open/error:delay-(--media-error-dialog-transition-delay)', - 'peer-data-open/error:backdrop-blur-lg' + 'peer-data-open/error:backdrop-blur-lg peer-data-open/error:backdrop-saturate-150' ); diff --git a/packages/skins/src/default/tailwind/components/popup.ts b/packages/skins/src/default/tailwind/components/popup.ts index c693d167..a18bf244 100644 --- a/packages/skins/src/default/tailwind/components/popup.ts +++ b/packages/skins/src/default/tailwind/components/popup.ts @@ -23,16 +23,14 @@ const base = cn( export const popup = { popover: cn( base, - '[--media-popover-side-offset:0.5rem]', 'data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset)', 'data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset)' ), tooltip: cn( base, 'py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap', - '[--media-tooltip-side-offset:0.75rem]', 'data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset)', 'data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)' ), - volume: 'py-2.5 px-1 rounded-full', + volume: 'py-3 px-0 rounded-full', }; diff --git a/packages/skins/src/default/tailwind/components/preview.ts b/packages/skins/src/default/tailwind/components/preview.ts index 58ed996e..66713fcd 100644 --- a/packages/skins/src/default/tailwind/components/preview.ts +++ b/packages/skins/src/default/tailwind/components/preview.ts @@ -9,7 +9,7 @@ export const preview = { 'after:bg-linear-to-t after:from-black/80 after:via-black/30 after:to-black/0', 'data-loading:opacity-0' ), - timestamp: 'absolute bottom-2 inset-x-0 text-center tabular-nums', + time: 'absolute bottom-2 inset-x-0 text-center tabular-nums', spinner: cn( 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0', 'transition-opacity duration-150 ease-out', diff --git a/packages/skins/src/default/tailwind/components/root.ts b/packages/skins/src/default/tailwind/components/root.ts index ffcaa973..bc354004 100644 --- a/packages/skins/src/default/tailwind/components/root.ts +++ b/packages/skins/src/default/tailwind/components/root.ts @@ -5,7 +5,8 @@ export const root = cn( 'block relative isolate h-full w-full @container/media-root', // Appearance 'rounded-(--media-border-radius,2rem)', - 'font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased', + 'font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] leading-normal subpixel-antialiased', + '[&>*]:text-xs @3xl/media-root:[&>*]:text-sm', // Resets '**:box-border', '[&_button]:font-[inherit]', diff --git a/packages/skins/src/default/tailwind/components/seek.ts b/packages/skins/src/default/tailwind/components/seek.ts index 03c2b51f..2b319821 100644 --- a/packages/skins/src/default/tailwind/components/seek.ts +++ b/packages/skins/src/default/tailwind/components/seek.ts @@ -1,5 +1,4 @@ export const seek = { - button: '@max-md/media-controls:hidden', label: 'text-[10px] font-[480] tabular-nums', labelForward: 'absolute -right-px -bottom-0.75', labelBackward: 'absolute -left-px -bottom-0.75', diff --git a/packages/skins/src/default/tailwind/components/slider.ts b/packages/skins/src/default/tailwind/components/slider.ts index 9fc72188..a72eef39 100644 --- a/packages/skins/src/default/tailwind/components/slider.ts +++ b/packages/skins/src/default/tailwind/components/slider.ts @@ -4,9 +4,9 @@ export const slider = { root: cn( 'group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer', // Horizontal - 'data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5', + 'data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8', // Vertical - 'data-[orientation=vertical]:w-5 data-[orientation=vertical]:h-20' + 'data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-20' ), track: cn( 'relative isolate overflow-hidden rounded-[inherit] select-none', diff --git a/packages/skins/src/default/tailwind/components/surface.ts b/packages/skins/src/default/tailwind/components/surface.ts index 9639ef35..97b61e96 100644 --- a/packages/skins/src/default/tailwind/components/surface.ts +++ b/packages/skins/src/default/tailwind/components/surface.ts @@ -1,8 +1,8 @@ import { cn } from '@videojs/utils/style'; export const surface = cn( - // Border and shadow - 'ring shadow-sm', - // Border to enhance contrast on lighter videos - 'after:absolute after:inset-0 after:ring after:ring-inset after:rounded-[inherit] after:pointer-events-none after:z-10' + 'bg-(--media-surface-background-color)', + '[backdrop-filter:var(--media-surface-backdrop-filter)]', + '[box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)]', + 'after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10' ); diff --git a/packages/skins/src/default/tailwind/video.tailwind.ts b/packages/skins/src/default/tailwind/video.tailwind.ts index bb7d0f13..7f357da6 100644 --- a/packages/skins/src/default/tailwind/video.tailwind.ts +++ b/packages/skins/src/default/tailwind/video.tailwind.ts @@ -1,12 +1,14 @@ import { cn } from '@videojs/utils/style'; import { bufferingIndicator as baseBufferingIndicator } from './components/buffering'; +import { buttonGroup as baseButtonGroup } from './components/button-group'; import { controls as baseControls } from './components/controls'; import { error as baseError } from './components/error'; import { popup as basePopup } from './components/popup'; import { preview as basePreview } from './components/preview'; import { root as baseRoot } from './components/root'; import { slider as baseSlider } from './components/slider'; -import { surface as baseSurface } from './components/surface'; +import { surface } from './components/surface'; +import { time as baseTime } from './components/time'; /* ========================================================================== Root @@ -29,26 +31,38 @@ export const root = (isShadowDOM: boolean) => '[--media-spring-transition:linear(0,0.034_1.5%,0.763_9.7%,1.066_13.9%,1.198_19.9%,1.184_21.8%,0.963_37.5%,0.997_50.9%,1)]', '[--media-video-border-radius:var(--media-border-radius,2rem)]', '[--media-controls-transition-duration:100ms]', - '[--media-controls-transition-delay:0ms]', '[--media-controls-transition-timing-function:ease-out]', '[--media-error-dialog-transition-duration:350ms]', '[--media-error-dialog-transition-delay:100ms]', '[--media-error-dialog-transition-timing-function:var(--media-spring-transition)]', '[--media-popup-transition-duration:100ms]', '[--media-popup-transition-timing-function:ease-out]', + '[--media-surface-background-color:oklch(1_0_0/0.1)]', + '[--media-surface-inner-border-color:oklch(1_0_0/0.05)]', + '[--media-surface-outer-border-color:oklch(0_0_0/0.1)]', + '[--media-surface-shadow-color:oklch(0_0_0/0.15)]', + '[--media-surface-backdrop-filter:blur(16px)_saturate(1.5)]', 'motion-reduce:[--media-error-dialog-transition-duration:50ms]', 'motion-reduce:[--media-error-dialog-transition-delay:0ms]', 'motion-reduce:[--media-error-dialog-transition-timing-function:ease-out]', + '[--media-tooltip-side-offset:0.75rem]', + '[--media-popover-side-offset:0.5rem]', 'motion-reduce:[--media-popup-transition-duration:0ms]', - '[@media(pointer:fine)]:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-delay:500ms]', + '[@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)]', + 'contrast-more:[--media-surface-background-color:oklch(0_0_0)]', + '[@media(prefers-reduced-transparency:reduce)]:[--media-surface-inner-border-color:oklch(1_0_0/0.25)]', + 'contrast-more:[--media-surface-inner-border-color:oklch(1_0_0/0.25)]', + '[@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:transparent]', + 'contrast-more:[--media-surface-outer-border-color:transparent]', '[@media(pointer:fine)]:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:300ms]', '[@media(pointer:coarse)]:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:150ms]', 'motion-reduce:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:50ms]', // Caption track CSS variables (consumed by the native caption bridge in light DOM) '[--media-caption-track-y:-0.5rem]', - '[--media-caption-track-delay:calc(var(--media-controls-transition-delay)_+_25ms)]', + '[--media-caption-track-delay:25ms]', '[--media-caption-track-duration:var(--media-controls-transition-duration)]', - 'has-[[data-controls][data-visible]]:[--media-caption-track-y:-3.5rem]', + 'has-[[data-controls][data-visible]]:[--media-caption-track-y:-5.5rem]', + '@2xl/media-root:has-[[data-controls][data-visible]]:[&>*]:[--media-caption-track-y:-3.5rem]', // Native caption track container !isShadowDOM ? [ @@ -70,24 +84,6 @@ export const root = (isShadowDOM: boolean) => } ); -/* ========================================================================== - Surface (shared glass effect for tooltips, popovers, controls) - ========================================================================== */ - -export const surface = cn( - baseSurface, - 'bg-white/10', - 'backdrop-saturate-150 backdrop-blur-lg', - // Border and shadow - 'ring-black/15 shadow-black/10', - // Border to enhance contrast on lighter videos - 'after:ring-white/5', - // Reduced transparency for users with preference - '[@media(prefers-reduced-transparency:reduce)]:bg-black/70', - // High contrast mode - 'contrast-more:bg-black/90' -); - /* ========================================================================== Controls (hide/show behavior) ========================================================================== */ @@ -95,13 +91,12 @@ export const surface = cn( export const controls = cn( baseControls, surface, - // Position - 'absolute bottom-3 inset-x-3', + // Position & wrapping layout (small) + 'absolute bottom-2 inset-x-2 flex-wrap', '[color:var(--media-color-primary,oklch(1_0_0))] z-10', 'peer-data-open/error:hidden', 'ease-(--media-controls-transition-timing-function) origin-bottom', 'duration-(--media-controls-transition-duration)', - 'delay-(--media-controls-transition-delay)', '[@media(pointer:fine)]:will-change-[scale,filter,opacity]', '[@media(pointer:fine)]:transition-[scale,filter,opacity]', '[@media(pointer:coarse)]:will-change-[scale,opacity]', @@ -109,9 +104,31 @@ export const controls = cn( // Hidden state 'not-data-visible:pointer-events-none not-data-visible:opacity-0', 'motion-safe:not-data-visible:scale-90', - '[@media(pointer:fine)]:motion-safe:not-data-visible:blur-sm' + '[@media(pointer:fine)]:motion-safe:not-data-visible:blur-sm', + // Single-row layout (large) + '@2xl/media-root:bottom-3 @2xl/media-root:inset-x-3 @2xl/media-root:flex-nowrap @2xl/media-root:gap-x-0.5 @2xl/media-root:p-1' ); +/* ========================================================================== + Button groups + ========================================================================== */ + +export const buttonGroupStart = cn(baseButtonGroup, 'flex-1 @2xl/media-root:flex-none'); +export const buttonGroupEnd = cn(baseButtonGroup, 'flex-1 justify-end @2xl/media-root:flex-none'); + +/* ========================================================================== + Time + ========================================================================== */ + +export const time = { + ...baseTime, + group: cn( + baseTime.group, + 'grow-0 shrink-0 basis-full order-[-1] px-2.5', + '@2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]' + ), +}; + /* ========================================================================== Preview (with video surface) ========================================================================== */ @@ -119,7 +136,8 @@ export const controls = cn( export const preview = { ...basePreview, root: cn( - 'absolute left-(--media-slider-pointer) bottom-[calc(100%+1.2rem)] -translate-x-1/2', + '[--media-preview-max-width:11rem] [--media-preview-padding:-1.125rem] [--media-preview-inset:calc((100cqi-100%)/2)]', + 'absolute [left:clamp(calc(var(--media-preview-max-width)/2+var(--media-preview-padding)-var(--media-preview-inset)),var(--media-slider-pointer),calc(100%-var(--media-preview-max-width)/2-var(--media-preview-padding)+var(--media-preview-inset)))] bottom-[calc(100%+1.2rem)] -translate-x-1/2', 'opacity-0 scale-80 blur-sm origin-bottom', 'transition-[scale,opacity,filter] duration-150', 'group-data-pointing/slider:opacity-100 group-data-pointing/slider:scale-100 group-data-pointing/slider:blur-none', @@ -128,7 +146,7 @@ export const preview = { surface, basePreview.root ), - thumbnail: cn(basePreview.thumbnail, 'max-w-44'), + thumbnail: cn(basePreview.thumbnail, 'max-w-(--media-preview-max-width)'), }; /* ========================================================================== @@ -137,7 +155,7 @@ export const preview = { export const slider = { ...baseSlider, - track: cn(baseSlider.track, 'bg-white/20 shadow-[0_0_0_1px_oklch(0_0_0/0.05)]'), + track: cn(baseSlider.track, 'bg-white/20 ring-1 ring-black/5'), }; /* ========================================================================== @@ -165,7 +183,7 @@ export const bufferingIndicator = { export const error = { ...baseError, - dialog: cn(baseError.dialog, surface, 'text-shadow-[0_1px_0_oklch(0_0_0/0.25)]'), + dialog: cn(baseError.dialog, surface, 'text-shadow-2xs text-shadow-black/25'), content: cn(baseError.content, 'text-shadow-inherit'), title: cn(baseError.title, 'text-base'), }; @@ -177,9 +195,9 @@ export const error = { export { iconState } from '../../shared/tailwind/icon-state'; export { tooltipState } from '../../shared/tailwind/tooltip-state'; export { button } from './components/button'; +export { buttonGroup } from './components/button-group'; export { icon, iconContainer, iconFlipped, iconHidden } from './components/icon'; export { overlay } from './components/overlay'; export { playbackRate } from './components/playback-rate'; export { poster } from './components/poster'; export { seek } from './components/seek'; -export { time } from './components/time'; diff --git a/packages/skins/src/minimal/css/audio.css b/packages/skins/src/minimal/css/audio.css index 498e4c90..04ab4d93 100644 --- a/packages/skins/src/minimal/css/audio.css +++ b/packages/skins/src/minimal/css/audio.css @@ -4,7 +4,8 @@ @import "./components/error.css"; @import "./components/controls.css"; @import "./components/time.css"; -@import "./components/buttons.css"; +@import "./components/button.css"; +@import "./components/button-group.css"; @import "./components/icons.css"; @import "./components/slider.css"; @import "./components/popup.css"; @@ -17,13 +18,19 @@ .media-minimal-skin--audio { --media-controls-background-color: oklch(1 0 0); - --media-controls-border-color: oklch(0 0 0 / 0.1); - --media-controls-padding: 0.375rem; - --media-text-color: var(--media-color-primary, oklch(0 0 0)); + --media-controls-backdrop-filter: blur(16px) saturate(1.5); + --media-controls-border-color: oklch(0 0 0 / 0.05); + --media-controls-text-color: var(--media-color-primary, oklch(0 0 0)); --media-error-dialog-transition-duration: 250ms; --media-error-dialog-transition-delay: 100ms; --media-popup-transition-duration: 100ms; --media-popup-transition-timing-function: ease-out; + --media-tooltip-background-color: oklch(1 0 0 / 0.1); + --media-tooltip-border-color: oklch(0 0 0 / 0.05); + --media-tooltip-backdrop-filter: blur(16px) saturate(1.5); + --media-tooltip-text-color: currentColor; + --media-tooltip-side-offset: 0.75rem; + --media-popover-side-offset: 0.75rem; @media (prefers-reduced-motion: reduce) { --media-error-dialog-transition-duration: 50ms; @@ -34,7 +41,15 @@ @media (prefers-color-scheme: dark) { --media-controls-background-color: oklch(0 0 0); --media-controls-border-color: oklch(1 0 0 / 0.1); - --media-text-color: var(--media-color-primary, oklch(1 0 0)); + --media-controls-text-color: var(--media-color-primary, oklch(1 0 0)); + } + + @media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) { + --media-tooltip-background-color: oklch(1 0 0); + } + + @media (prefers-color-scheme: dark) and ((prefers-reduced-transparency: reduce) or (prefers-contrast: more)) { + --media-tooltip-background-color: oklch(0 0 0); } } @@ -80,13 +95,11 @@ ========================================================================== */ .media-minimal-skin--audio .media-controls { + padding: 0.375rem; gap: 0.5rem; - padding: var(--media-controls-padding); - background-color: var(--media-controls-background-color); - backdrop-filter: blur(16px) saturate(1.5); - border-radius: var(--media-controls-radius); - color: var(--media-text-color); + color: var(--media-controls-text-color); box-shadow: 0 0 0 1px var(--media-controls-border-color); + border-radius: var(--media-border-radius, 1rem); } /* ========================================================================== @@ -95,6 +108,5 @@ .media-minimal-skin--audio .media-popover--volume { background: linear-gradient(to left, var(--media-controls-background-color) 80%, transparent 100%); - padding: 0.5rem 0 0.5rem 4rem; - --media-popover-side-offset: 0.75rem; + padding: 0 0 0 4rem; } diff --git a/packages/skins/src/minimal/css/components/button-group.css b/packages/skins/src/minimal/css/components/button-group.css new file mode 100644 index 00000000..54131548 --- /dev/null +++ b/packages/skins/src/minimal/css/components/button-group.css @@ -0,0 +1,13 @@ +/* ========================================================================== + Button Groups + ========================================================================== */ + +.media-minimal-skin .media-button-group { + display: flex; + align-items: center; + gap: 0.075rem; + + @container media-root (width > 42rem) { + gap: 0.125rem; + } +} diff --git a/packages/skins/src/minimal/css/components/buttons.css b/packages/skins/src/minimal/css/components/button.css similarity index 81% rename from packages/skins/src/minimal/css/components/buttons.css rename to packages/skins/src/minimal/css/components/button.css index 9c33a293..cdf75e9b 100644 --- a/packages/skins/src/minimal/css/components/buttons.css +++ b/packages/skins/src/minimal/css/components/button.css @@ -1,17 +1,3 @@ -/* ========================================================================== - Button Groups - ========================================================================== */ - -.media-minimal-skin .media-button-group { - display: flex; - align-items: center; - gap: 0.075rem; - - @container media-root (width > 40rem) { - gap: 0.125rem; - } -} - /* ========================================================================== Buttons ========================================================================== */ @@ -24,10 +10,12 @@ flex-shrink: 0; padding: 0.5rem 1rem; border: none; - border-radius: calc(var(--media-controls-radius) - var(--media-controls-padding)); + border-radius: 0.5rem; outline: 2px solid transparent; outline-offset: -2px; transition-property: background-color, outline-offset, scale; + /* Fix weird jumping when clicking on the buttons in Safari. */ + will-change: scale; transition-duration: 150ms; transition-timing-function: ease-out; cursor: pointer; @@ -58,7 +46,7 @@ /* biome-ignore lint/correctness/noUnknownProperty: corner-shape is an emerging CSS spec */ @supports (corner-shape: squircle) { .media-minimal-skin .media-button { - border-radius: var(--media-controls-radius); + border-radius: 1rem; /* biome-ignore lint/correctness/noUnknownProperty: corner-shape is an emerging CSS spec */ corner-shape: squircle; } @@ -107,7 +95,7 @@ position: absolute; right: -1px; bottom: -3px; - font-size: 10px; + font-size: 10px; /* Hard coded due to size limitations. */ font-weight: 480; font-variant-numeric: tabular-nums; } @@ -116,10 +104,6 @@ right: unset; left: -1px; } - - @container media-controls (width < 28rem) { - display: none; - } } /* Playback rate button */ diff --git a/packages/skins/src/minimal/css/components/captions.css b/packages/skins/src/minimal/css/components/captions.css index a092ac7c..32ffdcb8 100644 --- a/packages/skins/src/minimal/css/components/captions.css +++ b/packages/skins/src/minimal/css/components/captions.css @@ -4,11 +4,17 @@ .media-minimal-skin { --media-caption-track-duration: var(--media-controls-transition-duration); - --media-caption-track-delay: calc(var(--media-controls-transition-delay) + 25ms); + --media-caption-track-delay: 25ms; --media-caption-track-y: -0.5rem; &:has(.media-controls[data-visible]) { - --media-caption-track-y: -3rem; + --media-caption-track-y: -5rem; + } + + @container media-root (width > 42rem) { + &:has(.media-controls[data-visible]) > * { + --media-caption-track-y: -3rem; + } } } diff --git a/packages/skins/src/minimal/css/components/controls.css b/packages/skins/src/minimal/css/components/controls.css index 97a0b87d..be025e6f 100644 --- a/packages/skins/src/minimal/css/components/controls.css +++ b/packages/skins/src/minimal/css/components/controls.css @@ -11,5 +11,7 @@ from var(--media-controls-current-shadow-color) l c h / calc(alpha * 0.4) ); + background-color: var(--media-controls-background-color); + backdrop-filter: var(--media-controls-backdrop-filter); text-shadow: 0 1px 0 var(--media-controls-current-shadow-color); } diff --git a/packages/skins/src/minimal/css/components/overlay.css b/packages/skins/src/minimal/css/components/overlay.css index 32d498c9..555465b2 100644 --- a/packages/skins/src/minimal/css/components/overlay.css +++ b/packages/skins/src/minimal/css/components/overlay.css @@ -7,12 +7,11 @@ inset: 0; border-radius: inherit; 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)); - backdrop-filter: blur(0) saturate(1.5); + backdrop-filter: blur(0) saturate(1); opacity: 0; pointer-events: none; transition-property: opacity, backdrop-filter; transition-duration: var(--media-controls-transition-duration); - transition-delay: var(--media-controls-transition-delay); transition-timing-function: ease-out; } @@ -27,5 +26,5 @@ } .media-minimal-skin .media-error[data-open] ~ .media-overlay { - backdrop-filter: blur(16px) saturate(1.5); + backdrop-filter: blur(16px) saturate(1.2); } diff --git a/packages/skins/src/minimal/css/components/popup.css b/packages/skins/src/minimal/css/components/popup.css index af4350e1..4ec16cfa 100644 --- a/packages/skins/src/minimal/css/components/popup.css +++ b/packages/skins/src/minimal/css/components/popup.css @@ -81,15 +81,16 @@ .media-minimal-skin .media-tooltip { padding: 0.25rem 0.5rem; - border-radius: 0.25rem; - background-color: oklch(1 0 0 / 0.1); - backdrop-filter: blur(16px) saturate(1.5); + border-radius: 0.5rem; + background-color: var(--media-tooltip-background-color); + backdrop-filter: var(--media-tooltip-backdrop-filter); box-shadow: + 0 0 0 1px var(--media-tooltip-border-color), 0 4px 6px -1px oklch(0 0 0 / 0.1), 0 2px 4px -2px oklch(0 0 0 / 0.1); - font-size: 0.75rem; + color: var(--media-tooltip-text-color); + font-size: 0.75rem; /* 12px at 100% font size */ white-space: nowrap; - --media-tooltip-side-offset: 0.75rem; &[data-side="top"]::before, &[data-side="bottom"]::before { @@ -99,14 +100,6 @@ &[data-side="right"]::before { width: var(--media-tooltip-side-offset); } - - @media (prefers-reduced-transparency: reduce) { - background-color: oklch(0 0 0 / 0.7); - } - - @media (prefers-contrast: more) { - background-color: oklch(0 0 0 / 0.9); - } } .media-minimal-skin .media-popover--volume:has(media-volume-slider[data-availability="unsupported"]) { diff --git a/packages/skins/src/minimal/css/components/preview.css b/packages/skins/src/minimal/css/components/preview.css index 7c21e59e..c326f342 100644 --- a/packages/skins/src/minimal/css/components/preview.css +++ b/packages/skins/src/minimal/css/components/preview.css @@ -2,6 +2,8 @@ Media preview ========================================================================== */ .media-minimal-skin .media-preview { + pointer-events: none; + & .media-preview__thumbnail-wrapper { position: relative; border-radius: 0.5rem; @@ -12,9 +14,8 @@ border-radius: inherit; } - & .media-preview__timestamp { + & .media-preview__time { display: block; - font-variant-numeric: tabular-nums; text-align: center; margin-top: 0.5rem; } diff --git a/packages/skins/src/minimal/css/components/root.css b/packages/skins/src/minimal/css/components/root.css index 863ea0eb..03e03af6 100644 --- a/packages/skins/src/minimal/css/components/root.css +++ b/packages/skins/src/minimal/css/components/root.css @@ -3,12 +3,12 @@ ========================================================================== */ .media-minimal-skin { + container: media-root / inline-size; position: relative; isolation: isolate; display: block; height: 100%; width: 100%; - container: media-root / inline-size; border-radius: var(--media-border-radius, 0.75rem); font-family: Inter Variable, @@ -16,11 +16,18 @@ ui-sans-serif, system-ui, sans-serif; - font-size: 0.8125rem; line-height: 1.5; letter-spacing: normal; -webkit-font-smoothing: auto; -moz-osx-font-smoothing: auto; - --media-controls-radius: var(--media-border-radius, 1rem); + & > * { + font-size: 0.75rem; /* 12px at 100% font size */ + } + + @container media-root (width > 48rem) { + & > * { + font-size: 0.875rem; /* 14px at 100% font size */ + } + } } diff --git a/packages/skins/src/minimal/css/components/slider.css b/packages/skins/src/minimal/css/components/slider.css index 37e8c284..f94b61d5 100644 --- a/packages/skins/src/minimal/css/components/slider.css +++ b/packages/skins/src/minimal/css/components/slider.css @@ -15,11 +15,11 @@ &[data-orientation="horizontal"] { min-width: 5rem; width: 100%; - height: 1.25rem; + height: 2rem; } &[data-orientation="vertical"] { - width: 1.25rem; + width: 2rem; height: 4.5rem; } } diff --git a/packages/skins/src/minimal/css/components/time.css b/packages/skins/src/minimal/css/components/time.css index f5444463..650cb6b6 100644 --- a/packages/skins/src/minimal/css/components/time.css +++ b/packages/skins/src/minimal/css/components/time.css @@ -3,34 +3,41 @@ ========================================================================== */ .media-minimal-skin .media-time-controls { + container: media-time-controls / inline-size; display: flex; flex-direction: row-reverse; align-items: center; flex: 1; gap: 0.75rem; } -.media-minimal-skin .media-time { + +.media-minimal-skin .media-time-group { display: flex; align-items: center; gap: 0.25rem; } -.media-minimal-skin .media-time__value { + +.media-minimal-skin .media-time { font-variant-numeric: tabular-nums; } -.media-minimal-skin .media-time__value--current, -.media-minimal-skin .media-time__separator { + +.media-minimal-skin .media-time--current, +.media-minimal-skin .media-time-separator { display: none; } -@container media-controls (width > 28rem) { + +@container media-root (width > 42rem) { .media-minimal-skin .media-time-controls { flex-direction: row; } - .media-minimal-skin .media-time__value--duration, - .media-minimal-skin .media-time__separator { + + .media-minimal-skin .media-time--duration, + .media-minimal-skin .media-time-separator { color: oklch(from currentColor l c h / 0.6); } - .media-minimal-skin .media-time__value--current, - .media-minimal-skin .media-time__separator { + + .media-minimal-skin .media-time--current, + .media-minimal-skin .media-time-separator { display: inline; } } diff --git a/packages/skins/src/minimal/css/video.css b/packages/skins/src/minimal/css/video.css index 904f2449..ad3fadf0 100644 --- a/packages/skins/src/minimal/css/video.css +++ b/packages/skins/src/minimal/css/video.css @@ -6,7 +6,8 @@ @import "./components/error.css"; @import "./components/controls.css"; @import "./components/time.css"; -@import "./components/buttons.css"; +@import "./components/button.css"; +@import "./components/button-group.css"; @import "./components/icons.css"; @import "./components/poster.css"; @import "./components/preview.css"; @@ -25,15 +26,20 @@ background: oklch(0 0 0); --media-border-color: oklch(0 0 0 / 0.15); --media-video-border-radius: var(--media-border-radius, 0.75rem); - --media-controls-padding: 0.375rem; + --media-controls-background-color: transparent; --media-controls-transition-duration: 100ms; - --media-controls-transition-delay: 0ms; --media-controls-transition-timing-function: ease-out; --media-error-dialog-transition-duration: 250ms; --media-error-dialog-transition-delay: 100ms; --media-error-dialog-transition-timing-function: ease-out; --media-popup-transition-duration: 100ms; --media-popup-transition-timing-function: ease-out; + --media-tooltip-background-color: oklch(1 0 0 / 0.1); + --media-tooltip-border-color: transparent; + --media-tooltip-backdrop-filter: blur(16px) saturate(1.5); + --media-tooltip-text-color: currentColor; + --media-tooltip-side-offset: 0.5rem; + --media-popover-side-offset: 1.5rem; @media (prefers-reduced-motion: reduce) { --media-error-dialog-transition-duration: 50ms; @@ -45,10 +51,20 @@ --media-border-color: oklch(1 0 0 / 0.15); } + @media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) { + --media-controls-background-color: oklch(0 0 0); + --media-tooltip-background-color: oklch(0 0 0); + } + + @container media-root (width > 42rem) { + & > * { + --media-popover-side-offset: 0rem; + } + } + &:has(.media-controls:not([data-visible])) { /* Slight delay to hide controls on non-touch devices after interaction */ @media (pointer: fine) { - --media-controls-transition-delay: 500ms; --media-controls-transition-duration: 300ms; } @media (pointer: coarse) { @@ -135,15 +151,16 @@ ========================================================================== */ .media-minimal-skin--video .media-controls { + padding: 0.25rem; + column-gap: 0.5rem; + flex-wrap: wrap; position: absolute; - bottom: 0; - inset-inline: 0; + bottom: 0.25rem; + inset-inline: 0.25rem; z-index: 10; - gap: 0.5rem; - padding: 2rem var(--media-controls-padding) var(--media-controls-padding) var(--media-controls-padding); - color: var(--media-color-primary, oklch(1 0 0)); + color: oklch(1 0 0); + border-radius: 0.75rem; transition-duration: var(--media-controls-transition-duration); - transition-delay: var(--media-controls-transition-delay); transition-timing-function: var(--media-controls-transition-timing-function); @media (pointer: fine) { @@ -171,14 +188,41 @@ } } - @container media-root (width > 40rem) { - gap: 0.875rem; - padding: 2.5rem 0.75rem 0.75rem 0.75rem; + & .media-time-controls { + order: -1; + flex: 0 0 100%; + padding-inline: 0.625rem; + } + + & .media-button-group:first-child { + flex: 1; + text-align: left; + } + + & .media-button-group:last-child { + flex: 1; + justify-content: end; + } + + @container media-root (width > 42rem) { + flex-wrap: nowrap; + bottom: 0.5rem; + inset-inline: 0.5rem; + + & .media-time-controls { + order: unset; + flex: 1; + } + + & .media-button-group:first-child, + & .media-button-group:last-child { + flex: 0 0 auto; + } } } -/* Hide cursor when controls are hidden in fullscreen */ -.media-minimal-skin--video:fullscreen:has(.media-controls:not([data-visible])) { +/* Hide cursor when controls are hidden */ +.media-minimal-skin--video:has(.media-controls:not([data-visible])) { cursor: none; } @@ -195,9 +239,13 @@ ========================================================================== */ .media-minimal-skin--video .media-popover--volume { - --media-popover-side-offset: 0.5rem; background: transparent; - padding: 0.25rem; + padding-block: 0.75rem; + border-radius: 0.75rem; + + @media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) { + background: var(--media-controls-background-color); + } } /* ========================================================================== @@ -205,9 +253,21 @@ ========================================================================== */ .media-minimal-skin--video .media-slider__preview { + --media-preview-max-width: 11rem; + --media-preview-padding: -0.5rem; + /** + Inset is the difference between the container width and the slider (100%) width. + We only add to the end as we render the time there. + */ + --media-preview-inset: calc(100cqi - 100%); + position: absolute; - left: var(--media-slider-pointer); - bottom: calc(100% + 0.5rem); + left: clamp( + calc(var(--media-preview-max-width) / 2 + var(--media-preview-padding)), + var(--media-slider-pointer), + calc(100% - var(--media-preview-max-width) / 2 - var(--media-preview-padding) + var(--media-preview-inset)) + ); + bottom: 100%; translate: -50%; opacity: 0; scale: 0.8; @@ -217,6 +277,11 @@ transition-timing-function: ease-out; transform-origin: bottom; + @container media-root (width > 42rem) { + bottom: calc(100% + 0.25rem); + left: var(--media-slider-pointer); + } + & .media-preview__thumbnail-wrapper { position: relative; @@ -233,7 +298,7 @@ } & .media-preview__thumbnail { - max-width: 11rem; + max-width: var(--media-preview-max-width); } &:has(.media-preview__thumbnail[data-loading]) { diff --git a/packages/skins/src/minimal/tailwind/audio.tailwind.ts b/packages/skins/src/minimal/tailwind/audio.tailwind.ts index 23260346..d57bdd94 100644 --- a/packages/skins/src/minimal/tailwind/audio.tailwind.ts +++ b/packages/skins/src/minimal/tailwind/audio.tailwind.ts @@ -11,19 +11,29 @@ import { root as baseRoot } from './components/root'; export const root = cn( baseRoot, '[--media-controls-background-color:oklch(1_0_0)]', - '[--media-controls-border-color:oklch(0_0_0/0.1)]', - '[--media-controls-padding:0.375rem]', - '[--media-text-color:var(--media-color-primary,oklch(0_0_0))]', + '[--media-controls-backdrop-filter:blur(16px)_saturate(1.5)]', + '[--media-controls-border-color:oklch(0_0_0/0.05)]', + '[--media-controls-text-color:var(--media-color-primary,oklch(0_0_0))]', '[--media-error-dialog-transition-duration:250ms]', '[--media-error-dialog-transition-delay:100ms]', '[--media-popup-transition-duration:100ms]', '[--media-popup-transition-timing-function:ease-out]', + '[--media-tooltip-background-color:oklch(1_0_0/0.1)]', + '[--media-tooltip-border-color:oklch(0_0_0/0.05)]', + '[--media-tooltip-backdrop-filter:blur(16px)_saturate(1.5)]', + '[--media-tooltip-text-color:currentColor]', + '[--media-tooltip-side-offset:0.75rem]', + '[--media-popover-side-offset:0.75rem]', 'motion-reduce:[--media-error-dialog-transition-duration:50ms]', 'motion-reduce:[--media-error-dialog-transition-delay:0ms]', 'motion-reduce:[--media-popup-transition-duration:0ms]', 'dark:[--media-controls-background-color:oklch(0_0_0)]', 'dark:[--media-controls-border-color:oklch(1_0_0/0.1)]', - 'dark:[--media-text-color:var(--media-color-primary,oklch(1_0_0))]' + 'dark:[--media-controls-text-color:var(--media-color-primary,oklch(1_0_0))]', + '[@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(1_0_0)]', + 'contrast-more:[--media-tooltip-background-color:oklch(1_0_0)]', + 'dark:[@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(0_0_0)]', + 'dark:contrast-more:[--media-tooltip-background-color:oklch(0_0_0)]' ); /* ========================================================================== @@ -33,14 +43,11 @@ export const root = cn( export const controls = cn( baseControls, // Layout - 'gap-2 p-1.5', + 'p-1.5 gap-2', + 'rounded-(--media-border-radius,1rem)', 'peer-data-open/error:[&_*]:invisible', // Appearance - 'rounded-(--media-border-radius,0.75rem)', - 'bg-(--media-controls-background-color)', - 'text-(--media-text-color)', - // Backdrop filter - 'backdrop-blur backdrop-brightness-[0.98] backdrop-saturate-[1.2]', + 'text-(--media-controls-text-color)', // Border 'ring-1 ring-(color:--media-controls-border-color)' ); @@ -53,9 +60,8 @@ export const popup = { ...basePopup, volume: cn( basePopup.popover, - '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]' + 'p-0 pl-16', + 'bg-transparent bg-gradient-to-l from-(--media-controls-background-color) from-80% to-transparent' ), }; @@ -71,8 +77,8 @@ export const error = { 'transition-[opacity,filter,scale] ease-out', 'duration-(--media-error-dialog-transition-duration)', 'delay-(--media-error-dialog-transition-delay)', - 'group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-[4px] group-data-starting-style/error:scale-95', - 'group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-[4px] group-data-ending-style/error:scale-95', + 'group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-xs group-data-starting-style/error:scale-95', + 'group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-xs group-data-ending-style/error:scale-95', 'group-data-ending-style/error:delay-0' ), content: 'flex flex-1 items-center gap-2', diff --git a/packages/skins/src/minimal/tailwind/components/button.ts b/packages/skins/src/minimal/tailwind/components/button.ts index 3c6c4c3e..e17d6e5b 100644 --- a/packages/skins/src/minimal/tailwind/components/button.ts +++ b/packages/skins/src/minimal/tailwind/components/button.ts @@ -3,13 +3,13 @@ import { cn } from '@videojs/utils/style'; export const button = { base: cn( 'flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation', - 'py-2 px-4 rounded-[calc(var(--media-controls-radius)-var(--media-controls-padding))]', + 'py-2 px-4 rounded-lg', 'outline-2 outline-transparent -outline-offset-2', - 'transition-[background-color,outline-offset,scale] duration-150 ease-out', + 'transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out', 'focus-visible:outline-current focus-visible:outline-offset-2', 'active:scale-[0.98]', 'disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale', - 'supports-[corner-shape:squircle]:rounded-(--media-controls-radius)', + 'supports-[corner-shape:squircle]:rounded-[1rem]', 'supports-[corner-shape:squircle]:[corner-shape:squircle]', 'data-[availability=unavailable]:hidden' ), diff --git a/packages/skins/src/minimal/tailwind/components/controls.ts b/packages/skins/src/minimal/tailwind/components/controls.ts index 670443b6..7e96b293 100644 --- a/packages/skins/src/minimal/tailwind/components/controls.ts +++ b/packages/skins/src/minimal/tailwind/components/controls.ts @@ -9,6 +9,9 @@ export const controls = cn( // Shadow color variables (derived from currentColor lightness) '[--media-controls-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))]', '[--media-controls-current-shadow-color-subtle:oklch(from_var(--media-controls-current-shadow-color)_l_c_h/calc(alpha*0.4))]', + // Appearance (driven by CSS variables set on the root) + 'bg-(--media-controls-background-color)', + '[backdrop-filter:var(--media-controls-backdrop-filter)]', // Text shadow - 'text-shadow-[0_1px_0_var(--media-controls-current-shadow-color)]' + 'text-shadow-2xs text-shadow-(color:--media-controls-current-shadow-color)' ); diff --git a/packages/skins/src/minimal/tailwind/components/overlay.ts b/packages/skins/src/minimal/tailwind/components/overlay.ts index 0a283d71..56575e07 100644 --- a/packages/skins/src/minimal/tailwind/components/overlay.ts +++ b/packages/skins/src/minimal/tailwind/components/overlay.ts @@ -7,11 +7,10 @@ export const overlay = cn( // Default: hidden 'opacity-0', 'bg-linear-to-t from-black/70 via-black/50 via-[7.5rem] to-transparent', - 'backdrop-blur-none backdrop-saturate-150', + 'backdrop-blur-none backdrop-saturate-100', // Transitions 'transition-[opacity,backdrop-filter]', 'duration-(--media-controls-transition-duration)', - 'delay-(--media-controls-transition-delay)', 'ease-out', // Shown when controls visible 'peer-data-visible/controls:opacity-100', @@ -19,5 +18,5 @@ export const overlay = cn( 'peer-data-open/error:opacity-100', 'peer-data-open/error:duration-(--media-error-dialog-transition-duration)', 'peer-data-open/error:delay-(--media-error-dialog-transition-delay)', - 'peer-data-open/error:backdrop-blur-lg' + 'peer-data-open/error:backdrop-blur-lg peer-data-open/error:backdrop-saturate-120' ); diff --git a/packages/skins/src/minimal/tailwind/components/popup.ts b/packages/skins/src/minimal/tailwind/components/popup.ts index e4fc0431..869a4a21 100644 --- a/packages/skins/src/minimal/tailwind/components/popup.ts +++ b/packages/skins/src/minimal/tailwind/components/popup.ts @@ -29,11 +29,11 @@ export const popup = { ), tooltip: cn( base, - 'px-2 py-1 rounded-sm shadow-md shadow-black/10 bg-white/10 backdrop-blur-lg backdrop-saturate-150 text-[0.75rem] whitespace-nowrap', - '[--media-tooltip-side-offset:0.75rem]', + 'px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap', + 'bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)]', + 'ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/10', + 'text-(--media-tooltip-text-color)', 'data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset)', - 'data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)', - '[@media(prefers-reduced-transparency:reduce)]:bg-black/70', - 'contrast-more:bg-black/90' + 'data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)' ), }; diff --git a/packages/skins/src/minimal/tailwind/components/preview.ts b/packages/skins/src/minimal/tailwind/components/preview.ts index 60937d01..3f841a63 100644 --- a/packages/skins/src/minimal/tailwind/components/preview.ts +++ b/packages/skins/src/minimal/tailwind/components/preview.ts @@ -4,7 +4,7 @@ export const preview = { root: 'group/preview pointer-events-none', thumbnailWrapper: 'relative rounded-lg bg-black/90', thumbnail: cn('block rounded-[inherit] transition-opacity duration-150 ease-out', 'data-loading:opacity-0'), - timestamp: 'mt-2 block text-center tabular-nums', + time: 'mt-2 block text-center tabular-nums', spinner: cn( 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0', 'transition-opacity duration-150 ease-out', diff --git a/packages/skins/src/minimal/tailwind/components/root.ts b/packages/skins/src/minimal/tailwind/components/root.ts index 142e5a4a..8b456a51 100644 --- a/packages/skins/src/minimal/tailwind/components/root.ts +++ b/packages/skins/src/minimal/tailwind/components/root.ts @@ -5,8 +5,8 @@ export const root = cn( 'block relative isolate @container/media-root', // Appearance 'rounded-(--media-border-radius,0.75rem)', - '[--media-controls-radius:var(--media-border-radius,1rem)]', - 'font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased', + 'font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] leading-normal subpixel-antialiased', + '[&>*]:text-xs @3xl/media-root:[&>*]:text-sm', // Resets '**:box-border', '[&_button]:font-[inherit]', diff --git a/packages/skins/src/minimal/tailwind/components/seek.ts b/packages/skins/src/minimal/tailwind/components/seek.ts index 03c2b51f..2b319821 100644 --- a/packages/skins/src/minimal/tailwind/components/seek.ts +++ b/packages/skins/src/minimal/tailwind/components/seek.ts @@ -1,5 +1,4 @@ export const seek = { - button: '@max-md/media-controls:hidden', label: 'text-[10px] font-[480] tabular-nums', labelForward: 'absolute -right-px -bottom-0.75', labelBackward: 'absolute -left-px -bottom-0.75', diff --git a/packages/skins/src/minimal/tailwind/components/slider.ts b/packages/skins/src/minimal/tailwind/components/slider.ts index c0cb7c0b..5b223168 100644 --- a/packages/skins/src/minimal/tailwind/components/slider.ts +++ b/packages/skins/src/minimal/tailwind/components/slider.ts @@ -4,9 +4,9 @@ export const slider = { root: cn( 'group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer', // Horizontal - 'data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5', + 'data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8', // Vertical - 'data-[orientation=vertical]:w-5 data-[orientation=vertical]:h-[4.5rem]' + 'data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-[4.5rem]' ), track: cn( 'relative isolate overflow-hidden bg-current/20 rounded-[inherit] select-none', diff --git a/packages/skins/src/minimal/tailwind/components/time.ts b/packages/skins/src/minimal/tailwind/components/time.ts index e3617687..d1af4fc6 100644 --- a/packages/skins/src/minimal/tailwind/components/time.ts +++ b/packages/skins/src/minimal/tailwind/components/time.ts @@ -2,8 +2,8 @@ import { cn } from '@videojs/utils/style'; export const time = { group: 'flex items-center gap-1', - current: cn('hidden tabular-nums', '@md/media-controls:inline'), - separator: cn('hidden', '@md/media-controls:inline @md/media-controls:text-current/60'), - duration: cn('tabular-nums', '@md/media-controls:text-current/60'), - controls: cn('flex flex-row-reverse items-center flex-1 gap-3', '@md/media-controls:flex-row'), + current: cn('hidden tabular-nums', '@2xl/media-root:inline'), + separator: cn('hidden', '@2xl/media-root:inline @2xl/media-root:text-current/60'), + duration: cn('tabular-nums', '@2xl/media-root:text-current/60'), + controls: cn('@container flex flex-row-reverse items-center flex-1 gap-3', '@2xl/media-root:flex-row'), }; diff --git a/packages/skins/src/minimal/tailwind/video.tailwind.ts b/packages/skins/src/minimal/tailwind/video.tailwind.ts index 6e359428..b5fb2f3d 100644 --- a/packages/skins/src/minimal/tailwind/video.tailwind.ts +++ b/packages/skins/src/minimal/tailwind/video.tailwind.ts @@ -1,10 +1,12 @@ import { cn } from '@videojs/utils/style'; +import { buttonGroup as baseButtonGroup } from './components/button-group'; import { controls as baseControls } from './components/controls'; import { error as baseError } from './components/error'; import { popup as basePopup } from './components/popup'; import { preview as basePreview } from './components/preview'; import { root as baseRoot } from './components/root'; import { slider as baseSlider } from './components/slider'; +import { time as baseTime } from './components/time'; /* ========================================================================== Root @@ -25,27 +27,37 @@ export const root = (isShadowDOM: boolean) => !isShadowDOM, }, '[--media-video-border-radius:var(--media-border-radius,0.75rem)]', - '[--media-controls-padding:0.375rem]', + '[--media-controls-background-color:transparent]', '[--media-controls-transition-duration:100ms]', - '[--media-controls-transition-delay:0ms]', '[--media-controls-transition-timing-function:ease-out]', '[--media-error-dialog-transition-duration:250ms]', '[--media-error-dialog-transition-delay:100ms]', '[--media-error-dialog-transition-timing-function:ease-out]', '[--media-popup-transition-duration:100ms]', '[--media-popup-transition-timing-function:ease-out]', + '[--media-tooltip-background-color:oklch(1_0_0/0.1)]', + '[--media-tooltip-border-color:transparent]', + '[--media-tooltip-backdrop-filter:blur(16px)_saturate(1.5)]', + '[--media-tooltip-text-color:currentColor]', + '[--media-tooltip-side-offset:0.5rem]', + '[--media-popover-side-offset:1.5rem]', 'motion-reduce:[--media-error-dialog-transition-duration:50ms]', 'motion-reduce:[--media-error-dialog-transition-delay:0ms]', 'motion-reduce:[--media-popup-transition-duration:0ms]', - '[@media(pointer:fine)]:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-delay:500ms]', + '[@media(prefers-reduced-transparency:reduce)]:[--media-controls-background-color:oklch(0_0_0)]', + 'contrast-more:[--media-controls-background-color:oklch(0_0_0)]', + '[@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(0_0_0)]', + 'contrast-more:[--media-tooltip-background-color:oklch(0_0_0)]', + '@2xl/media-root:[&>*]:[--media-popover-side-offset:0rem]', '[@media(pointer:fine)]:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:300ms]', '[@media(pointer:coarse)]:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:150ms]', 'motion-reduce:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:50ms]', // Caption track CSS variables (consumed by the native caption bridge in light DOM) '[--media-caption-track-y:-0.5rem]', - '[--media-caption-track-delay:calc(var(--media-controls-transition-delay)_+_25ms)]', + '[--media-caption-track-delay:25ms]', '[--media-caption-track-duration:var(--media-controls-transition-duration)]', - 'has-[[data-controls][data-visible]]:[--media-caption-track-y:-3rem]', + 'has-[[data-controls][data-visible]]:[--media-caption-track-y:-5rem]', + '@2xl/media-root:has-[[data-controls][data-visible]]:[&>*]:[--media-caption-track-y:-3rem]', // Native caption track container !isShadowDOM ? [ @@ -73,14 +85,13 @@ export const root = (isShadowDOM: boolean) => export const controls = cn( baseControls, - // Position - 'absolute bottom-0 inset-x-0', - 'pt-8 px-(--media-controls-padding) pb-(--media-controls-padding) gap-2', - '[color:var(--media-color-primary,oklch(1_0_0))] z-10', + // Position & wrapping layout (small) + 'absolute bottom-1 inset-x-1', + 'p-1 gap-x-2 flex-wrap rounded-xl', + 'text-white z-10', 'peer-data-open/error:hidden', 'ease-(--media-controls-transition-timing-function)', 'duration-(--media-controls-transition-duration)', - 'delay-(--media-controls-transition-delay)', '[@media(pointer:fine)]:will-change-[translate,filter,opacity]', '[@media(pointer:fine)]:transition-[translate,filter,opacity]', '[@media(pointer:coarse)]:will-change-[translate,opacity]', @@ -89,11 +100,30 @@ export const controls = cn( 'not-data-visible:opacity-0 not-data-visible:pointer-events-none', 'motion-safe:not-data-visible:translate-y-full', '[@media(pointer:fine)]:motion-safe:not-data-visible:blur-sm', - // Wider container - '@sm/media-root:pt-10 @sm/media-root:px-3 @sm/media-root:pb-3', - '@sm/media-root:gap-3.5' + // Single-row layout (large) + '@2xl/media-root:flex-nowrap @2xl/media-root:bottom-2 @2xl/media-root:inset-x-2' ); +/* ========================================================================== + Button groups + ========================================================================== */ + +export const buttonGroupStart = cn(baseButtonGroup, 'flex-1 @2xl/media-root:flex-none'); +export const buttonGroupEnd = cn(baseButtonGroup, 'flex-1 justify-end @2xl/media-root:flex-none'); + +/* ========================================================================== + Time + ========================================================================== */ + +export const time = { + ...baseTime, + controls: cn( + baseTime.controls, + 'grow-0 shrink-0 basis-full order-[-1] px-2.5', + '@2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]' + ), +}; + /* ========================================================================== Error ========================================================================== */ @@ -112,7 +142,9 @@ export const error = { export const preview = { ...basePreview, root: cn( - 'absolute left-(--media-slider-pointer) bottom-[calc(100%+0.5rem)] -translate-x-1/2', + '[--media-preview-max-width:11rem] [--media-preview-padding:-0.5rem] [--media-preview-inset:calc(100cqi-100%)]', + 'absolute [left:clamp(calc(var(--media-preview-max-width)/2+var(--media-preview-padding)),var(--media-slider-pointer),calc(100%-var(--media-preview-max-width)/2-var(--media-preview-padding)+var(--media-preview-inset)))] bottom-full -translate-x-1/2', + '@2xl/media-root:bottom-[calc(100%+0.25rem)] @2xl/media-root:[left:var(--media-slider-pointer)]', 'opacity-0 scale-80 blur-sm origin-bottom', 'transition-[scale,opacity,filter] duration-150', 'group-data-pointing/slider:opacity-100 group-data-pointing/slider:scale-100 group-data-pointing/slider:blur-none', @@ -125,7 +157,7 @@ export const preview = { 'after:absolute after:inset-0 after:rounded-[inherit]', 'after:ring-1 after:ring-black/5 after:shadow-sm after:shadow-black/20' ), - thumbnail: cn(basePreview.thumbnail, 'max-w-44'), + thumbnail: cn(basePreview.thumbnail, 'max-w-(--media-preview-max-width)'), }; /* ========================================================================== @@ -143,7 +175,12 @@ export const slider = { export const popup = { ...basePopup, - volume: cn(basePopup.popover, '[--media-popover-side-offset:0.5rem] p-1 bg-transparent'), + volume: cn( + basePopup.popover, + 'py-3 px-0 bg-transparent rounded-xl', + '[@media(prefers-reduced-transparency:reduce)]:bg-(--media-controls-background-color)', + 'contrast-more:bg-(--media-controls-background-color)' + ), }; /* ========================================================================== @@ -160,4 +197,3 @@ export { overlay } from './components/overlay'; export { playbackRate } from './components/playback-rate'; export { poster } from './components/poster'; export { seek } from './components/seek'; -export { time } from './components/time';