diff --git a/.claude/skills/css-to-tailwind/SKILL.md b/.claude/skills/css-to-tailwind/SKILL.md
index 4fe06190..c85acd34 100644
--- a/.claude/skills/css-to-tailwind/SKILL.md
+++ b/.claude/skills/css-to-tailwind/SKILL.md
@@ -17,6 +17,11 @@ Guidance for converting legacy CSS to Tailwind **v4** class strings while preser
Theme and shared animation tokens are declared in CSS (**`@theme`**, keyed custom properties consumed by utilities). Apps use **`@import "tailwindcss"`**; **`packages/skins`** layers shared **`@keyframes` / `@property` / `@theme`** in **`shared/tailwind.css`**. When a pattern repeats across components, prefer **`@utility`** or **`@custom-variant`** in CSS (see **`site`** / **`apps/sandbox`** styles) rather than proliferating arbitrary values in TS presets.
+## Video.js migration constraints
+
+- Never add legacy `media-*` semantic or BEM marker classes to Tailwind markup or class strings, such as `media-button--mute` or `media-popover--volume`. Use Tailwind utilities, exported Tailwind component classes, named groups/container variants, data attributes, or custom-element selectors instead. Named Tailwind variants such as `@container/media-root` and `group/controls` are valid.
+- Treat Tailwind v4's `--spacing` as the base spacing variable. Convert `calc(var(--spacing) * N)` to the native utility (`p-N`, `gap-N`, etc.) where one exists. For arbitrary values and custom properties with a literal multiplier, use Tailwind v4's build-time `--spacing(N)` function, such as `rounded-[--spacing(7)]`, rather than carrying the `calc(var(--spacing) * N)` expression into the class string. Keep a `calc(var(--spacing) * var(...))` expression when the multiplier is runtime-derived. Do not carry over `--base-size` or `--size`; resolve font and icon sizes to rem values.
+
See [references/migration.md](references/migration.md) for v4-focused rules.
## When to use
diff --git a/.claude/skills/css-to-tailwind/references/migration.md b/.claude/skills/css-to-tailwind/references/migration.md
index e5ee5a88..9f47210b 100644
--- a/.claude/skills/css-to-tailwind/references/migration.md
+++ b/.claude/skills/css-to-tailwind/references/migration.md
@@ -36,7 +36,25 @@ Legacy **`tailwind.config.js`** theme spreads are not the primary path here—ex
- `border-radius: 8px` → prefer `rounded-lg` (or a theme radius key) if equivalent/acceptable
- `font-size`, `spacing`, `colors`, `shadow`, `z-index`, `radius` should map to **`@theme` or default v4 scales** when acceptable
-### 3. When arbitrary values are allowed
+### 3. Use Tailwind's `--spacing` scale
+
+Tailwind v4 spacing utilities are based on the `--spacing` variable. Translate spacing calculations directly to native utilities:
+
+- `padding: calc(var(--spacing) * 2)` → `p-2`
+- `margin-inline: calc(var(--spacing) * 3)` → `mx-3`
+- `gap: var(--spacing)` → `gap-1`
+
+Use built-in responsive or named container variants (`md:`, `@md:`, `@md/media-root:`) for media-query behavior. If a scoped design needs to scale all spacing, overriding `--spacing` at that scope is valid because native utilities inherit it. Ignore `--base-size` and `--size`; resolve font and icon sizes to rem values.
+
+For arbitrary values, Tailwind v4 also provides the build-time `--spacing(N)` function. Use it for literal spacing multipliers that do not map cleanly to a native utility:
+
+- `border-radius: calc(var(--spacing) * 7)` → `rounded-[--spacing(7)]`
+- `[--max-width:calc(var(--spacing)*44)]` → `[--max-width:--spacing(44)]`
+- `[bottom:calc(100% + var(--spacing) * 4.8)]` → `[bottom:calc(100%+--spacing(4.8))]`
+
+`rounded-(--spacing(7))` is not the equivalent syntax: the parenthesized form is for a custom-property utility and would look for `var(--spacing(7))`. Keep `calc(var(--spacing) * var(...))` when the multiplier is runtime-derived because `--spacing()` only replaces literal values.
+
+### 4. When arbitrary values are allowed
Only when:
@@ -45,7 +63,7 @@ Only when:
- The value is required for pixel-perfect migration, **or**
- The CSS property has no Tailwind utility
-### 4. Avoid arbitrary values for common scale values
+### 5. Avoid arbitrary values for common scale values
Bad:
@@ -61,7 +79,7 @@ Good:
- `rounded-lg`
- `text-sm`
-### 5. Prefer theme-backed utilities (not raw `var()` in class strings)
+### 6. Prefer theme-backed utilities (not raw `var()` in class strings)
Avoid lots of:
@@ -75,7 +93,7 @@ Prefer:
(Adapt names to the project's **`@theme`** variable names; add tokens to CSS when missing.)
-### 6. Use arbitrary variants/properties sparingly
+### 7. Use arbitrary variants/properties sparingly
Allowed examples:
@@ -84,7 +102,7 @@ Allowed examples:
If repeated, recommend **`@utility`** or extending **`@theme`** instead of copying the same arbitrary class everywhere.
-### 7. Preserve responsive, state, and media behavior
+### 8. Preserve responsive, state, and media behavior
- `@media (min-width: 768px)` → `md:` (match project breakpoints from **`@theme`** / default v4 screens)
- Named container queries (e.g. **`@container media-root`**) → match existing utilities such as **`@*/media-root:`** / **`max-*` / `@2xl`** patterns used in skins—do not silently switch to plain `md:` if the source is container-based
@@ -92,7 +110,11 @@ If repeated, recommend **`@utility`** or extending **`@theme`** instead of copyi
- `:focus-visible` → `focus-visible:`
- `[data-state='open']` → `data-[state=open]:`
-### 8. After migration — short report
+### 9. Avoid legacy semantic class hooks
+
+Do not add `media-*` semantic or BEM marker classes to Tailwind templates, including forms such as `media-button--*`, `media-popover--*`, `media-menu__*`, or `media-sr-only`. Use the corresponding Tailwind utility, component class, data attribute, or custom-element selector. Before reporting completion, scan Tailwind source files for `media-...--` and `media-...__` marker classes.
+
+### 10. After migration — short report
Include:
diff --git a/apps/e2e/tests/visual/video-skin.spec.ts b/apps/e2e/tests/visual/video-skin.spec.ts
index 4502e548..d17100f3 100644
--- a/apps/e2e/tests/visual/video-skin.spec.ts
+++ b/apps/e2e/tests/visual/video-skin.spec.ts
@@ -113,7 +113,7 @@ test.describe('Visual — HTML Portrait Layout', () => {
const thumbnail = document.querySelector('video-skin')!.shadowRoot!.querySelector('media-slider-thumbnail')!;
const style = getComputedStyle(thumbnail);
const probe = document.createElement('div');
- probe.style.height = style.getPropertyValue('--media-slider-thumbnail-max-height');
+ probe.style.height = style.getPropertyValue('--max-height');
document.body.append(probe);
const configuredMaxHeight = parseFloat(getComputedStyle(probe).height);
diff --git a/packages/core/src/dom/ui/popover/popover-positioning.ts b/packages/core/src/dom/ui/popover/popover-positioning.ts
index 063fcfa5..5705a17f 100644
--- a/packages/core/src/dom/ui/popover/popover-positioning.ts
+++ b/packages/core/src/dom/ui/popover/popover-positioning.ts
@@ -420,6 +420,7 @@ export function getManualPositionStyle(
*/
export function resolveOffsets(el: Element, cssVars: PositioningCSSVars = PopoverCSSVars): ManualOffsets {
const computed = getComputedStyle(el);
+
return {
sideOffset: resolveCSSLength(el, computed.getPropertyValue(cssVars.sideOffset)),
alignOffset: resolveCSSLength(el, computed.getPropertyValue(cssVars.alignOffset)),
diff --git a/packages/html/src/define/audio/minimal-skin.tailwind.ts b/packages/html/src/define/audio/minimal-skin.tailwind.ts
index 5fc3d2ce..0bb065f6 100644
--- a/packages/html/src/define/audio/minimal-skin.tailwind.ts
+++ b/packages/html/src/define/audio/minimal-skin.tailwind.ts
@@ -107,6 +107,21 @@ function getTemplateHTML() {
+
+ ${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/minimal-skin.ts b/packages/html/src/define/audio/minimal-skin.ts
index 4d41a597..8ea71d9c 100644
--- a/packages/html/src/define/audio/minimal-skin.ts
+++ b/packages/html/src/define/audio/minimal-skin.ts
@@ -89,20 +89,6 @@ function getTemplateHTML() {
-
-
-
${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })}
${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })}
@@ -117,6 +103,21 @@ function getTemplateHTML() {
+
+
+
+
diff --git a/packages/html/src/define/audio/minimal-ui.ts b/packages/html/src/define/audio/minimal-ui.ts
index 5a1d3250..bbc3aa68 100644
--- a/packages/html/src/define/audio/minimal-ui.ts
+++ b/packages/html/src/define/audio/minimal-ui.ts
@@ -10,12 +10,15 @@ import { PlaybackRateButtonElement } from '../../ui/playback-rate-button/playbac
import { PlaybackRateRadioGroupElement } from '../../ui/playback-rate-radio-group/playback-rate-radio-group-element';
import { PopoverElement } from '../../ui/popover/popover-element';
import { SeekButtonElement } from '../../ui/seek-button/seek-button-element';
-import { TooltipElement } from '../../ui/tooltip/tooltip-element';
-import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
-import { TooltipLabelElement } from '../../ui/tooltip/tooltip-label-element';
-import { TooltipShortcutElement } from '../../ui/tooltip/tooltip-shortcut-element';
import { safeDefine } from '../safe-define';
-import { defineErrorDialog, defineMenu, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds';
+import {
+ defineErrorDialog,
+ defineMenu,
+ defineTime,
+ defineTimeSlider,
+ defineTooltips,
+ defineVolumeSlider,
+} from '../ui/compounds';
// Value import — player.ts body runs before this module's body.
import { AudioPlayerElement } from './player';
@@ -41,7 +44,4 @@ safeDefine(PlaybackRateButtonElement);
safeDefine(PlaybackRateRadioGroupElement);
safeDefine(PopoverElement);
safeDefine(SeekButtonElement);
-safeDefine(TooltipLabelElement);
-safeDefine(TooltipShortcutElement);
-safeDefine(TooltipElement);
-safeDefine(TooltipGroupElement);
+defineTooltips();
diff --git a/packages/html/src/define/audio/skin.tailwind.ts b/packages/html/src/define/audio/skin.tailwind.ts
index 60e7e0aa..d82ed83a 100644
--- a/packages/html/src/define/audio/skin.tailwind.ts
+++ b/packages/html/src/define/audio/skin.tailwind.ts
@@ -64,7 +64,7 @@ function getTemplateHTML() {
-
+
${renderIcon('seek', { class: cn(icon, iconFlipped) })}
${SEEK_TIME}
@@ -75,7 +75,7 @@ function getTemplateHTML() {
-
+
${renderIcon('seek', { class: icon })}
${SEEK_TIME}
diff --git a/packages/html/src/define/audio/ui.ts b/packages/html/src/define/audio/ui.ts
index ec567cfc..6cd3c465 100644
--- a/packages/html/src/define/audio/ui.ts
+++ b/packages/html/src/define/audio/ui.ts
@@ -14,12 +14,8 @@ import { PlaybackRateRadioGroupElement } from '../../ui/playback-rate-radio-grou
import { PopoverElement } from '../../ui/popover/popover-element';
import { SeekButtonElement } from '../../ui/seek-button/seek-button-element';
import { TextElement } from '../../ui/text/text-element';
-import { TooltipElement } from '../../ui/tooltip/tooltip-element';
-import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
-import { TooltipLabelElement } from '../../ui/tooltip/tooltip-label-element';
-import { TooltipShortcutElement } from '../../ui/tooltip/tooltip-shortcut-element';
import { safeDefine } from '../safe-define';
-import { defineErrorDialog, defineMenu, defineSliders, defineTime } from '../ui/compounds';
+import { defineErrorDialog, defineMenu, defineSliders, defineTime, defineTooltips } from '../ui/compounds';
// Value import — player.ts body runs before this module's body.
import { AudioPlayerElement } from './player';
@@ -48,7 +44,4 @@ safeDefine(PlaybackRateRadioGroupElement);
safeDefine(PopoverElement);
safeDefine(SeekButtonElement);
safeDefine(TextElement);
-safeDefine(TooltipLabelElement);
-safeDefine(TooltipShortcutElement);
-safeDefine(TooltipElement);
-safeDefine(TooltipGroupElement);
+defineTooltips();
diff --git a/packages/html/src/define/live-audio/minimal-ui.ts b/packages/html/src/define/live-audio/minimal-ui.ts
index 7b429948..c7ee3348 100644
--- a/packages/html/src/define/live-audio/minimal-ui.ts
+++ b/packages/html/src/define/live-audio/minimal-ui.ts
@@ -8,10 +8,8 @@ import { LiveButtonElement } from '../../ui/live-button/live-button-element';
import { MuteButtonElement } from '../../ui/mute-button/mute-button-element';
import { PlayButtonElement } from '../../ui/play-button/play-button-element';
import { PopoverElement } from '../../ui/popover/popover-element';
-import { TooltipElement } from '../../ui/tooltip/tooltip-element';
-import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
import { safeDefine } from '../safe-define';
-import { defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds';
+import { defineErrorDialog, defineTime, defineTimeSlider, defineTooltips, defineVolumeSlider } from '../ui/compounds';
// Value import — player.ts body runs before this module's body.
import { LiveAudioPlayerElement } from './player';
@@ -33,5 +31,4 @@ safeDefine(LiveButtonElement);
safeDefine(MuteButtonElement);
safeDefine(PlayButtonElement);
safeDefine(PopoverElement);
-safeDefine(TooltipElement);
-safeDefine(TooltipGroupElement);
+defineTooltips();
diff --git a/packages/html/src/define/live-audio/ui.ts b/packages/html/src/define/live-audio/ui.ts
index 60be6655..b3cc727c 100644
--- a/packages/html/src/define/live-audio/ui.ts
+++ b/packages/html/src/define/live-audio/ui.ts
@@ -12,10 +12,8 @@ import { MuteButtonElement } from '../../ui/mute-button/mute-button-element';
import { PlayButtonElement } from '../../ui/play-button/play-button-element';
import { PopoverElement } from '../../ui/popover/popover-element';
import { TextElement } from '../../ui/text/text-element';
-import { TooltipElement } from '../../ui/tooltip/tooltip-element';
-import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
import { safeDefine } from '../safe-define';
-import { defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds';
+import { defineErrorDialog, defineTime, defineTimeSlider, defineTooltips, defineVolumeSlider } from '../ui/compounds';
// Value import — player.ts body runs before this module's body.
import { LiveAudioPlayerElement } from './player';
@@ -41,5 +39,4 @@ safeDefine(MuteButtonElement);
safeDefine(PlayButtonElement);
safeDefine(PopoverElement);
safeDefine(TextElement);
-safeDefine(TooltipElement);
-safeDefine(TooltipGroupElement);
+defineTooltips();
diff --git a/packages/html/src/define/live-video/minimal-skin.tailwind.ts b/packages/html/src/define/live-video/minimal-skin.tailwind.ts
index 698629ff..4b60d60b 100644
--- a/packages/html/src/define/live-video/minimal-skin.tailwind.ts
+++ b/packages/html/src/define/live-video/minimal-skin.tailwind.ts
@@ -65,30 +65,31 @@ function getTemplateHTML() {
+
+
+ ${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) })}
-
-
-
${renderIcon('captions-off', { class: cn(icon, iconState.captions.off) })}
${renderIcon('captions-on', { class: cn(icon, iconState.captions.on) })}
-
-
-
-
${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' })}
diff --git a/packages/html/src/define/live-video/minimal-ui.ts b/packages/html/src/define/live-video/minimal-ui.ts
index d91abfe0..b7b3da2f 100644
--- a/packages/html/src/define/live-video/minimal-ui.ts
+++ b/packages/html/src/define/live-video/minimal-ui.ts
@@ -17,8 +17,6 @@ import { PiPButtonElement } from '../../ui/pip-button/pip-button-element';
import { PlayButtonElement } from '../../ui/play-button/play-button-element';
import { PopoverElement } from '../../ui/popover/popover-element';
import { PosterElement } from '../../ui/poster/poster-element';
-import { TooltipElement } from '../../ui/tooltip/tooltip-element';
-import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
import { safeDefine } from '../safe-define';
import {
defineControls,
@@ -27,6 +25,7 @@ import {
defineMenu,
defineTime,
defineTimeSlider,
+ defineTooltips,
defineVolumeSlider,
} from '../ui/compounds';
@@ -62,5 +61,4 @@ safeDefine(PiPButtonElement);
safeDefine(PlayButtonElement);
safeDefine(PopoverElement);
safeDefine(PosterElement);
-safeDefine(TooltipElement);
-safeDefine(TooltipGroupElement);
+defineTooltips();
diff --git a/packages/html/src/define/live-video/skin.tailwind.ts b/packages/html/src/define/live-video/skin.tailwind.ts
index eaa90ea5..33d80fc0 100644
--- a/packages/html/src/define/live-video/skin.tailwind.ts
+++ b/packages/html/src/define/live-video/skin.tailwind.ts
@@ -88,7 +88,7 @@ function getTemplateHTML() {
${renderIcon('captions-off', { class: cn(icon, iconState.captions.off) })}
${renderIcon('captions-on', { class: cn(icon, iconState.captions.on) })}
-
-
+
@@ -124,6 +124,7 @@ function getTemplateHTML() {
+
diff --git a/packages/html/src/define/live-video/ui.ts b/packages/html/src/define/live-video/ui.ts
index 5b52b509..816ac60d 100644
--- a/packages/html/src/define/live-video/ui.ts
+++ b/packages/html/src/define/live-video/ui.ts
@@ -19,8 +19,6 @@ import { PlayButtonElement } from '../../ui/play-button/play-button-element';
import { PopoverElement } from '../../ui/popover/popover-element';
import { PosterElement } from '../../ui/poster/poster-element';
import { TextElement } from '../../ui/text/text-element';
-import { TooltipElement } from '../../ui/tooltip/tooltip-element';
-import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
import { safeDefine } from '../safe-define';
import {
defineControls,
@@ -29,6 +27,7 @@ import {
defineMenu,
defineSliders,
defineTime,
+ defineTooltips,
} from '../ui/compounds';
// Value import — player.ts body runs before this module's body.
@@ -64,5 +63,4 @@ safeDefine(PlayButtonElement);
safeDefine(PopoverElement);
safeDefine(PosterElement);
safeDefine(TextElement);
-safeDefine(TooltipElement);
-safeDefine(TooltipGroupElement);
+defineTooltips();
diff --git a/packages/html/src/define/ui/compounds.ts b/packages/html/src/define/ui/compounds.ts
index 07234770..7ff8f6e0 100644
--- a/packages/html/src/define/ui/compounds.ts
+++ b/packages/html/src/define/ui/compounds.ts
@@ -33,6 +33,10 @@ import { TimeElement } from '../../ui/time/time-element';
import { TimeGroupElement } from '../../ui/time/time-group-element';
import { TimeSeparatorElement } from '../../ui/time/time-separator-element';
import { TimeSliderElement } from '../../ui/time-slider/time-slider-element';
+import { TooltipElement } from '../../ui/tooltip/tooltip-element';
+import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
+import { TooltipLabelElement } from '../../ui/tooltip/tooltip-label-element';
+import { TooltipShortcutElement } from '../../ui/tooltip/tooltip-shortcut-element';
import { VolumeIndicatorElement } from '../../ui/volume-indicator/volume-indicator-element';
import { VolumeIndicatorFillElement } from '../../ui/volume-indicator/volume-indicator-fill-element';
import { VolumeIndicatorValueElement } from '../../ui/volume-indicator/volume-indicator-value-element';
@@ -108,6 +112,13 @@ export function defineTimeSlider(): void {
safeDefine(SliderThumbnailElement);
}
+export function defineTooltips(): void {
+ safeDefine(TooltipGroupElement);
+ safeDefine(TooltipLabelElement);
+ safeDefine(TooltipShortcutElement);
+ safeDefine(TooltipElement);
+}
+
export function defineVolumeSlider(): void {
safeDefine(VolumeSliderElement);
defineSliderParts();
diff --git a/packages/html/src/define/video/minimal-skin.tailwind.ts b/packages/html/src/define/video/minimal-skin.tailwind.ts
index 7c595f93..08d28bb7 100644
--- a/packages/html/src/define/video/minimal-skin.tailwind.ts
+++ b/packages/html/src/define/video/minimal-skin.tailwind.ts
@@ -8,7 +8,6 @@ import {
controls,
error,
icon,
- iconContainer,
iconFlipped,
iconState,
inputFeedback,
@@ -17,7 +16,6 @@ import {
popup,
poster,
root,
- seek,
slider,
thumbnail,
time,
@@ -30,8 +28,6 @@ import { SkinElement } from '../skin-element';
// Register the player, container, and all UI custom elements.
import './minimal-ui';
-const SEEK_TIME = 10;
-
function getTemplateHTML() {
return /*html*/ `
@@ -72,27 +68,19 @@ function getTemplateHTML() {
-
-
- ${renderIcon('seek', { class: cn(icon, iconFlipped) })}
- ${SEEK_TIME}
-
-
-
-
-
-
- ${renderIcon('seek', { class: icon })}
- ${SEEK_TIME}
-
-
-
+
+ ${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) })}
+
+
@@ -123,29 +111,14 @@ function getTemplateHTML() {
diff --git a/packages/react/src/presets/audio/minimal-skin.tsx b/packages/react/src/presets/audio/minimal-skin.tsx
index 8a4d6e4f..bd99d3e0 100644
--- a/packages/react/src/presets/audio/minimal-skin.tsx
+++ b/packages/react/src/presets/audio/minimal-skin.tsx
@@ -216,14 +216,14 @@ 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 f3095ed0..ee679587 100644
--- a/packages/react/src/presets/audio/skin.tailwind.tsx
+++ b/packages/react/src/presets/audio/skin.tailwind.tsx
@@ -215,7 +215,7 @@ export function AudioSkinTailwind(props: AudioSkinProps): ReactNode {
}>
+ }>
{SEEK_TIME}
@@ -232,7 +232,7 @@ export function AudioSkinTailwind(props: AudioSkinProps): ReactNode {
}>
+ }>
{SEEK_TIME}
diff --git a/packages/react/src/presets/live-video/minimal-skin.tailwind.tsx b/packages/react/src/presets/live-video/minimal-skin.tailwind.tsx
index 31d6d018..6ada0184 100644
--- a/packages/react/src/presets/live-video/minimal-skin.tailwind.tsx
+++ b/packages/react/src/presets/live-video/minimal-skin.tailwind.tsx
@@ -126,10 +126,10 @@ function VolumePopover(): ReactNode {
if (volumeUnsupported) return muteButton;
return (
-
+
- }>
+ }>
}>
} />
@@ -262,13 +262,13 @@ export function MinimalLiveVideoSkinTailwind(props: MinimalLiveVideoSkinProps):
+
+
-
-
diff --git a/packages/react/src/presets/live-video/minimal-skin.tsx b/packages/react/src/presets/live-video/minimal-skin.tsx
index c05dbd63..c32460a1 100644
--- a/packages/react/src/presets/live-video/minimal-skin.tsx
+++ b/packages/react/src/presets/live-video/minimal-skin.tsx
@@ -79,10 +79,10 @@ function VolumePopover(): ReactNode {
if (volumeUnsupported) return muteButton;
return (
-
+
-
+
@@ -228,13 +228,13 @@ export function MinimalLiveVideoSkin(props: MinimalLiveVideoSkinProps): ReactNod
+
+
-
-
diff --git a/packages/react/src/presets/live-video/skin.tsx b/packages/react/src/presets/live-video/skin.tsx
index 90d5e6f7..f9834c14 100644
--- a/packages/react/src/presets/live-video/skin.tsx
+++ b/packages/react/src/presets/live-video/skin.tsx
@@ -173,7 +173,7 @@ export function LiveVideoSkin(props: LiveVideoSkinProps): ReactNode {
return (
@@ -205,7 +205,7 @@ export function LiveVideoSkin(props: LiveVideoSkinProps): ReactNode {
-
+
diff --git a/packages/react/src/presets/video/minimal-skin.tailwind.tsx b/packages/react/src/presets/video/minimal-skin.tailwind.tsx
index ebc8de20..88ca9f3d 100644
--- a/packages/react/src/presets/video/minimal-skin.tailwind.tsx
+++ b/packages/react/src/presets/video/minimal-skin.tailwind.tsx
@@ -7,7 +7,6 @@ import {
controls,
error,
icon,
- iconContainer,
iconFlipped,
iconState,
inputFeedback,
@@ -16,7 +15,6 @@ import {
popup,
poster,
root,
- seek,
slider,
thumbnail,
time,
@@ -43,7 +41,6 @@ import {
PlayIcon,
QualityIcon,
RestartIcon,
- SeekIcon,
SpeechIcon,
SpeedIcon,
SpinnerIcon,
@@ -71,7 +68,6 @@ import { usePlaybackRateOptions } from '@/ui/playback-rate';
import { Popover } from '@/ui/popover';
import { Poster } from '@/ui/poster';
import { useQualityOptions } from '@/ui/quality';
-import { SeekButton } from '@/ui/seek-button';
import { SeekIndicator } from '@/ui/seek-indicator';
import { Slider } from '@/ui/slider';
import { StatusAnnouncer } from '@/ui/status-announcer';
@@ -151,10 +147,10 @@ function VolumePopover(): ReactNode {
if (volumeUnsupported) return muteButton;
return (
-
+
-
- }>
+
+ }>
}>
} />
@@ -184,11 +180,7 @@ function SettingsMenu(): ReactNode {
return (
- }
- >
+ }>
@@ -198,7 +190,7 @@ function SettingsMenu(): ReactNode {
(
@@ -252,7 +244,7 @@ function SettingsMenu(): ReactNode {
(
@@ -302,7 +294,7 @@ function SettingsMenu(): ReactNode {
(
@@ -352,7 +344,7 @@ function SettingsMenu(): ReactNode {
(
@@ -469,39 +461,7 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
-
- }>
-
-
- {SEEK_TIME}
-
-
- }
- />
-
-
-
-
-
-
-
- }>
-
-
- {SEEK_TIME}
-
-
- }
- />
-
-
-
-
-
+
@@ -531,8 +491,6 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod
-
-
diff --git a/packages/react/src/presets/video/minimal-skin.tsx b/packages/react/src/presets/video/minimal-skin.tsx
index 33aa8cc0..99157861 100644
--- a/packages/react/src/presets/video/minimal-skin.tsx
+++ b/packages/react/src/presets/video/minimal-skin.tsx
@@ -20,7 +20,6 @@ import {
PlayIcon,
QualityIcon,
RestartIcon,
- SeekIcon,
SpeechIcon,
SpeedIcon,
SpinnerIcon,
@@ -48,7 +47,6 @@ import { usePlaybackRateOptions } from '@/ui/playback-rate';
import { Popover } from '@/ui/popover';
import { Poster } from '@/ui/poster';
import { useQualityOptions } from '@/ui/quality';
-import { SeekButton } from '@/ui/seek-button';
import { SeekIndicator } from '@/ui/seek-indicator';
import { Slider } from '@/ui/slider';
import { StatusAnnouncer } from '@/ui/status-announcer';
@@ -92,10 +90,10 @@ function VolumePopover(): ReactNode {
if (volumeUnsupported) return muteButton;
return (
-
+
-
+
@@ -401,39 +399,7 @@ export function MinimalVideoSkin(props: MinimalVideoSkinProps): ReactNode {
-
- }>
-
-
- {SEEK_TIME}
-
-
- }
- />
-
-
-
-
-
-
-
- }>
-
-
- {SEEK_TIME}
-
-
- }
- />
-
-
-
-
-
+
@@ -457,6 +423,7 @@ export function MinimalVideoSkin(props: MinimalVideoSkinProps): ReactNode {
+
@@ -464,8 +431,6 @@ 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 212ee19c..1ab70c87 100644
--- a/packages/react/src/presets/video/skin.tailwind.tsx
+++ b/packages/react/src/presets/video/skin.tailwind.tsx
@@ -16,6 +16,7 @@ import {
popup,
poster,
root,
+ secondaryControls,
seek,
slider,
thumbnail,
@@ -184,11 +185,7 @@ function SettingsMenu(): ReactNode {
return (
- }
- >
+ }>
@@ -198,7 +195,7 @@ function SettingsMenu(): ReactNode {
(
@@ -252,7 +249,7 @@ function SettingsMenu(): ReactNode {
(
@@ -302,7 +299,7 @@ function SettingsMenu(): ReactNode {
(
@@ -352,7 +349,7 @@ function SettingsMenu(): ReactNode {
(
@@ -447,6 +444,72 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
+
+
+
+
+ }>
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+ }>
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+ }>
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+ }>
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
}>
+ }>
{SEEK_TIME}
@@ -489,7 +552,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
}>
+ }>
{SEEK_TIME}
@@ -532,7 +595,10 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
}>
+ }
+ >
@@ -547,7 +613,10 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
}>
+ }
+ >
@@ -562,7 +631,7 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
}>
+ }>
@@ -577,7 +646,10 @@ export function VideoSkinTailwind(props: VideoSkinProps): ReactNode {
}>
+ }
+ >
diff --git a/packages/react/src/presets/video/skin.tsx b/packages/react/src/presets/video/skin.tsx
index 2545b750..aff07d80 100644
--- a/packages/react/src/presets/video/skin.tsx
+++ b/packages/react/src/presets/video/skin.tsx
@@ -1,3 +1,4 @@
+import type { TooltipProps } from '@videojs/core';
import { isString } from '@videojs/utils/predicate';
import { cn } from '@videojs/utils/style';
import { type ComponentProps, type CSSProperties, forwardRef, type ReactNode } from 'react';
@@ -341,6 +342,85 @@ function SettingsMenu(): ReactNode {
);
}
+type ControlProps = {
+ tooltipSide?: TooltipProps['side'];
+};
+
+function CastControl({ tooltipSide = 'top' }: ControlProps) {
+ return (
+
+ }>
+
+
+
+ }
+ />
+
+
+
+
+
+ );
+}
+
+function AirPlayControl({ tooltipSide = 'top' }: ControlProps) {
+ return (
+
+ }>
+
+
+
+ }
+ />
+
+
+
+
+
+ );
+}
+
+function PiPControl({ tooltipSide = 'top' }: ControlProps) {
+ return (
+
+ }>
+
+
+
+ }
+ />
+
+
+
+
+
+ );
+}
+function FullscreenControl({ tooltipSide = 'top' }: ControlProps) {
+ return (
+
+ }>
+
+
+
+ }
+ />
+
+
+
+
+
+ );
+}
+
export function VideoSkin(props: VideoSkinProps): ReactNode {
const { children, className, poster, placeholder, style, ...rest } = props;
@@ -382,7 +462,14 @@ export function VideoSkin(props: VideoSkinProps): ReactNode {
-
+
+
+
+
+
+
+
+
@@ -459,68 +546,11 @@ export function VideoSkin(props: VideoSkinProps): ReactNode {
-
-
-
- }>
-
-
-
- }
- />
-
-
-
-
-
-
-
- }>
-
-
-
- }
- />
-
-
-
-
-
-
-
- }>
-
-
-
- }
- />
-
-
-
-
-
-
-
- }>
-
-
-
- }
- />
-
-
-
-
-
+
+
+
+
diff --git a/packages/skins/src/default/css/audio.css b/packages/skins/src/default/css/audio.css
index 3ad339fd..61db0efd 100644
--- a/packages/skins/src/default/css/audio.css
+++ b/packages/skins/src/default/css/audio.css
@@ -25,14 +25,12 @@
--media-surface-shadow-color: oklch(0 0 0 / 0.15);
--media-surface-backdrop-filter: blur(16px) saturate(1.5);
--media-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-side-offset: 0.75rem;
- --media-tooltip-boundary-offset: 0.75rem;
- --media-popover-side-offset: 0.75rem;
- --media-popover-boundary-offset: 0.75rem;
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
@@ -66,9 +64,9 @@
inset: 0;
z-index: 20;
display: flex;
- gap: 0.75rem;
+ gap: calc(var(--spacing) * 3);
align-items: center;
- padding-inline: 1.25rem 0.125rem;
+ padding-inline: calc(var(--spacing) * 5) calc(var(--spacing) * 0.5);
color: var(--media-text-color);
background-color: var(--media-surface-background-color);
border-radius: calc(infinity * 1px);
@@ -91,7 +89,7 @@
.media-default-skin--audio .media-error__content {
display: flex;
flex: 1;
- gap: 0.5rem;
+ gap: calc(var(--spacing) * 2);
align-items: center;
}
@@ -100,7 +98,14 @@
========================================================================== */
.media-default-skin--audio .media-controls {
+ --base-boundary-offset: 2;
color: var(--media-text-color);
+
+ & .media-button--seek {
+ @container media-root (width < 32rem) {
+ display: none;
+ }
+ }
}
/* ==========================================================================
diff --git a/packages/skins/src/default/css/components/badge.css b/packages/skins/src/default/css/components/badge.css
index 97d80ad4..50502bf6 100644
--- a/packages/skins/src/default/css/components/badge.css
+++ b/packages/skins/src/default/css/components/badge.css
@@ -3,12 +3,12 @@
========================================================================== */
.media-default-skin .media-badge {
- padding: 0.125rem 0.375rem;
- font-size: 0.6875rem;
+ padding: calc(var(--spacing) * 0.5) calc(var(--spacing) * 1.5);
+ font-size: var(--font-size-small);
font-weight: 500;
line-height: 1;
color: oklch(from currentColor l c h / 0.85);
white-space: nowrap;
background-color: oklch(from currentColor l c h / 0.1);
- border-radius: calc(infinity * 1px);
+ border-radius: calc(Infinity * 1px);
}
diff --git a/packages/skins/src/default/css/components/button-group.css b/packages/skins/src/default/css/components/button-group.css
index 7c4007b3..31eb16c0 100644
--- a/packages/skins/src/default/css/components/button-group.css
+++ b/packages/skins/src/default/css/components/button-group.css
@@ -4,10 +4,10 @@
.media-default-skin .media-button-group {
display: flex;
- gap: 0.075rem;
+ gap: calc(var(--spacing) * 0.3);
align-items: center;
@container media-root (width > 42rem) {
- gap: 0.125rem;
+ gap: calc(var(--spacing) * 0.5);
}
}
diff --git a/packages/skins/src/default/css/components/button.css b/packages/skins/src/default/css/components/button.css
index f8837c35..33cf6f3a 100644
--- a/packages/skins/src/default/css/components/button.css
+++ b/packages/skins/src/default/css/components/button.css
@@ -8,8 +8,9 @@
flex-shrink: 0;
align-items: center;
justify-content: center;
+ height: calc(var(--spacing) * 9);
min-height: 0;
- padding: 0.5rem 1rem;
+ padding: calc(var(--spacing) * 2) calc(var(--spacing) * 4);
text-align: center;
touch-action: manipulation;
cursor: pointer;
@@ -17,7 +18,7 @@
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
- border-radius: calc(infinity * 1px);
+ border-radius: calc(Infinity * 1px);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: background-color, outline-offset, scale;
@@ -70,7 +71,6 @@
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
- width: 2.25rem;
aspect-ratio: 1;
padding: 0;
@@ -98,7 +98,7 @@
position: absolute;
right: -1px;
bottom: -3px;
- font-size: 10px;
+ font-size: 0.715em; /* 10px */
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: -0.05em;
@@ -151,12 +151,12 @@
text content. */
.media-default-skin .media-button--live {
display: inline-flex;
- gap: 0.4rem;
+ gap: calc(var(--spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
- padding: 0.5rem 0.75rem;
- font-size: 0.75rem;
+ padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
+ font-size: var(--font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
@@ -165,8 +165,8 @@
&::before {
display: inline-block;
flex-shrink: 0;
- width: 0.5rem;
- height: 0.5rem;
+ width: calc(var(--spacing) * 2);
+ height: calc(var(--spacing) * 2);
content: "";
background-color: oklch(from currentColor l c h / 0.4);
border-radius: 50%;
diff --git a/packages/skins/src/default/css/components/captions.css b/packages/skins/src/default/css/components/captions.css
index 3c6a6556..f0212c2f 100644
--- a/packages/skins/src/default/css/components/captions.css
+++ b/packages/skins/src/default/css/components/captions.css
@@ -5,15 +5,15 @@
.media-default-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
- --media-caption-track-y: -0.5rem;
+ --media-caption-track-y: calc(var(--spacing) * -2);
&:has(.media-controls[data-visible]) {
- --media-caption-track-y: -5.5rem;
+ --media-caption-track-y: calc(var(--spacing) * -22);
}
@container media-root (width > 42rem) {
&:has(.media-controls[data-visible]) > * {
- --media-caption-track-y: -3.5rem;
+ --media-caption-track-y: calc(var(--spacing) * -14);
}
}
}
diff --git a/packages/skins/src/default/css/components/controls.css b/packages/skins/src/default/css/components/controls.css
index c8bcc828..58d11d87 100644
--- a/packages/skins/src/default/css/components/controls.css
+++ b/packages/skins/src/default/css/components/controls.css
@@ -3,11 +3,16 @@
========================================================================== */
.media-default-skin .media-controls {
+ --media-popover-side-offset: calc(var(--spacing) * (var(--base-side-offset, 2) + 1));
+ --media-tooltip-side-offset: var(--media-popover-side-offset);
+ --media-popover-boundary-offset: calc(var(--spacing) * var(--base-boundary-offset, 2));
+ --media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
+
display: flex;
- column-gap: 0.075rem;
+ column-gap: calc(0.0625 * var(--base-size));
align-items: center;
- padding: 0.375rem;
+ padding: calc(var(--spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
- border-radius: 1.5rem;
+ border-radius: calc(Infinity * 1px);
}
diff --git a/packages/skins/src/default/css/components/error.css b/packages/skins/src/default/css/components/error.css
index a72ca20a..6d7a9472 100644
--- a/packages/skins/src/default/css/components/error.css
+++ b/packages/skins/src/default/css/components/error.css
@@ -22,7 +22,7 @@
.media-default-skin .media-error__actions {
display: flex;
- gap: 0.5rem;
+ gap: calc(var(--spacing) * 2);
& > * {
flex: 1;
diff --git a/packages/skins/src/default/css/components/input-feedback.css b/packages/skins/src/default/css/components/input-feedback.css
index cf29b70e..55786319 100644
--- a/packages/skins/src/default/css/components/input-feedback.css
+++ b/packages/skins/src/default/css/components/input-feedback.css
@@ -6,7 +6,7 @@
position: absolute;
inset-inline: 0;
top: 0;
- bottom: 3.5rem; /* Shift up a little in smaller containers */
+ bottom: calc(var(--spacing) * 14); /* Shift up a little in smaller containers */
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
@@ -24,7 +24,7 @@
.media-default-skin .media-input-feedback-island {
--media-surface-background-color: oklch(0 0 0 / 0.25);
position: absolute;
- top: 0.75rem;
+ top: calc(var(--spacing) * 3);
font-weight: 500;
color: inherit;
pointer-events: none;
@@ -35,11 +35,11 @@
.media-input-feedback-island__content {
display: flex;
- gap: 0.5rem;
+ gap: calc(var(--spacing) * 2);
align-items: center;
justify-content: space-between;
width: 100%;
- padding: 0.25rem 0.625rem;
+ padding: calc(var(--spacing) * 1) calc(var(--spacing) * 2.5);
/* Increase contrast of the content */
* {
@@ -91,7 +91,7 @@
}
.media-default-skin .media-input-feedback-island--volume {
- width: min(80%, 12rem);
+ width: min(80%, calc(var(--spacing) * 48));
.media-input-feedback-island__content {
--media-progress-fill: var(--media-volume-fill);
@@ -138,7 +138,7 @@
grid-row: 1;
grid-column: 2;
place-content: center;
- padding: 1rem;
+ padding: calc(var(--spacing) * 4);
text-align: center;
/* Central bubble (play, pause) */
@@ -169,10 +169,10 @@
/* Directional bubbles (seek) */
&[data-direction] {
- gap: 0.25rem;
+ gap: calc(var(--spacing) * 1);
@container media-root (width > 24rem) {
- padding: 1.5rem;
+ padding: calc(var(--spacing) * 6);
}
}
diff --git a/packages/skins/src/default/css/components/menus.css b/packages/skins/src/default/css/components/menus.css
index e43d243d..14b0e349 100644
--- a/packages/skins/src/default/css/components/menus.css
+++ b/packages/skins/src/default/css/components/menus.css
@@ -5,11 +5,12 @@
.media-default-skin .media-menu {
--menu-transition-duration: 250ms;
- --menu-max-height: 14rem;
- --menu-padding: 0.25rem;
- --menu-border-radius: 0.75rem;
+ --menu-max-height: calc(var(--spacing) * 56);
+ --menu-padding: calc(var(--spacing) * 1);
+ --menu-border-radius: calc(var(--spacing) * 3);
--menu-item-border-radius: calc(var(--menu-border-radius) - var(--menu-padding));
box-sizing: border-box;
+ min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--menu-max-height)), var(--menu-max-height));
padding: var(--menu-padding);
@@ -78,7 +79,7 @@
}
& .media-menu__separator {
- margin-block: 0.25rem;
+ margin-block: calc(var(--spacing) * 1);
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
@@ -86,7 +87,7 @@
& .media-menu__group {
display: flex;
flex-direction: column;
- gap: 0.125rem;
+ gap: calc(var(--spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
@@ -105,9 +106,9 @@
& .media-menu__item,
& .media-menu__back {
display: flex;
- gap: 0.375rem;
+ gap: calc(var(--spacing) * 1.5);
align-items: center;
- padding: 0.375rem 0.5rem;
+ padding: calc(var(--spacing) * 1.5) calc(var(--spacing) * 2);
text-align: left;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
cursor: pointer;
@@ -127,11 +128,6 @@
&:hover,
&[data-highlighted] {
background-color: oklch(from currentColor l c h / 0.1);
-
- @supports (top: anchor(top)) {
- anchor-name: --media-menu-item-highlight-anchor;
- background-color: transparent;
- }
}
& .media-icon {
@@ -143,7 +139,7 @@
& .media-menu__indicator {
flex-shrink: 0;
- margin-right: -0.25rem;
+ margin-right: calc(var(--spacing) * -1);
margin-left: auto;
opacity: 0;
@@ -171,12 +167,20 @@
&[data-availability="unsupported"] {
display: none;
}
+
+ &:hover,
+ &[data-highlighted] {
+ @supports (top: anchor(top)) {
+ anchor-name: --media-menu-item-highlight-anchor;
+ background-color: transparent;
+ }
+ }
}
& .media-menu__tier {
padding-top: 1px;
- padding-left: 0.125rem;
- font-size: 0.5625rem;
+ padding-left: calc(var(--spacing) * 0.5);
+ font-size: var(--font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
@@ -184,36 +188,35 @@
& .media-menu__back {
width: 100%;
- margin-bottom: 0.125rem;
+ margin-bottom: calc(var(--spacing) * 0.5);
}
& .media-menu__hint {
display: inline-flex;
- gap: 0.25rem;
+ gap: calc(var(--spacing) * 1);
align-items: center;
min-width: 0;
- padding-left: 0.5rem;
+ padding-left: calc(var(--spacing) * 2);
margin-left: auto;
- font-size: 0.75rem;
color: oklch(from currentColor l c h / 0.65);
}
& .media-menu__hint-label {
- max-width: 6rem;
+ max-width: calc(var(--spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .media-menu__chevron {
- width: 0.875rem;
- height: 0.875rem;
+ width: calc(var(--spacing) * 3.5);
+ height: calc(var(--spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
- min-width: 12rem;
+ min-width: calc(var(--spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Add height and width transitions. */
diff --git a/packages/skins/src/default/css/components/popup.css b/packages/skins/src/default/css/components/popup.css
index 30ed8072..ccb61352 100644
--- a/packages/skins/src/default/css/components/popup.css
+++ b/packages/skins/src/default/css/components/popup.css
@@ -12,7 +12,7 @@
.media-default-skin .media-popover,
.media-default-skin .media-tooltip {
- --popup-translate-distance: 0.5rem;
+ --popup-translate-distance: calc(0.5 * var(--base-size));
margin: 0;
overflow: visible;
color: inherit;
@@ -94,8 +94,8 @@
}
}
.media-default-skin .media-popover--volume {
- padding: 0.75rem 0;
- border-radius: calc(infinity * 1px);
+ padding: calc(var(--spacing) * 3) 0;
+ border-radius: calc(Infinity * 1px);
&:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
@@ -103,15 +103,15 @@
}
.media-default-skin .media-tooltip {
- padding: 0.25rem 0.625rem;
- font-size: 0.75rem;
+ padding: calc(var(--spacing) * 1) calc(var(--spacing) * 2.5);
+ font-size: var(--font-size-base);
white-space: nowrap;
- border-radius: calc(infinity * 1px);
+ border-radius: calc(Infinity * 1px);
/* `display: flex` must not apply while closed — it overrides UA `[popover]` hiding. */
&[data-open] {
display: flex;
- column-gap: 0.25rem;
+ column-gap: calc(var(--spacing) * 1);
align-items: center;
}
@@ -128,11 +128,11 @@
min-width: 1.5em;
padding: 0.1em;
font-family: inherit;
- font-size: 90%;
+ font-size: var(--font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
- border-radius: 0.25rem;
+ border-radius: calc(var(--spacing) * 1);
}
}
diff --git a/packages/skins/src/default/css/components/root.css b/packages/skins/src/default/css/components/root.css
index d5d687fb..34013371 100644
--- a/packages/skins/src/default/css/components/root.css
+++ b/packages/skins/src/default/css/components/root.css
@@ -3,10 +3,24 @@
========================================================================== */
.media-default-skin {
+ /* Colors */
--media-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-current-shadow-color-subtle: oklch(from var(--media-current-shadow-color) l c h / calc(alpha * 0.4));
- --media-icon-size: 18px;
- --media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.3);
+ --scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
+
+ /* Scaling (used in video fullscreen) */
+ --scale: 1;
+ /* What value and unit to use - 1rem assumes the document font size remains at the default 16px */
+ --base-size: 1rem;
+ --size: calc(var(--base-size) * var(--scale));
+ /* Create a Tailwind-like spacing unit */
+ --spacing: calc(var(--size) / 4);
+ --font-size-medium: calc(0.9375 * var(--size)); /* 15px */
+ --font-size-base: calc(0.8125 * var(--size)); /* 13px */
+ --font-size-small: calc(0.6875 * var(--size)); /* 11px */
+ --font-size-tiny: calc(0.5625 * var(--size)); /* 9px */
+ --media-icon-size: calc(1.125 * var(--size)); /* 18px */
+
position: relative;
display: block;
width: 100%;
@@ -18,16 +32,16 @@
ui-sans-serif,
system-ui,
sans-serif;
- font-size: 0.8125rem; /* 13px at 100% font size */
+ font-size: var(--font-size-base);
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
line-height: 1.5;
letter-spacing: normal;
outline: 2px solid transparent;
outline-offset: -4px;
- scrollbar-color: var(--media-color-scrollbar-thumb) transparent;
+ scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;
- border-radius: var(--media-border-radius, 2rem);
+ border-radius: var(--media-border-radius, 1.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
@@ -39,12 +53,12 @@
}
&::-webkit-scrollbar-thumb {
- background: var(--media-color-scrollbar-thumb);
+ background: var(--scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
- --media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.8);
+ --scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
diff --git a/packages/skins/src/default/css/components/slider.css b/packages/skins/src/default/css/components/slider.css
index 3116caf1..c44a7a15 100644
--- a/packages/skins/src/default/css/components/slider.css
+++ b/packages/skins/src/default/css/components/slider.css
@@ -13,208 +13,202 @@
border-radius: calc(infinity * 1px);
&[data-orientation="horizontal"] {
- width: 100%;
- min-width: 5rem;
- height: 2rem;
+ width: var(--slider-width, 100%);
+ min-width: calc(var(--spacing) * 20);
+ height: var(--slider-height, calc(var(--spacing) * 8));
}
-
&[data-orientation="vertical"] {
- width: 2rem;
- height: 5rem;
- }
-}
-
-/* Track */
-.media-default-skin .media-slider__track {
- position: relative;
- overflow: hidden;
- user-select: none;
- border-radius: inherit;
- isolation: isolate;
-
- &[data-orientation="horizontal"] {
- width: 100%;
- height: 0.25rem;
+ width: var(--slider-width, calc(var(--spacing) * 8));
+ height: var(--slider-height, calc(var(--spacing) * 20));
}
- &[data-orientation="vertical"] {
- width: 0.25rem;
- height: 100%;
- }
-}
-
-/* Thumb */
-.media-default-skin .media-slider__thumb {
- position: absolute;
- z-index: 10;
- width: 0.625rem;
- height: 0.625rem;
- user-select: none;
- outline: 4px solid transparent;
- outline-offset: -4px;
- background-color: currentColor;
- border-radius: calc(infinity * 1px);
- box-shadow:
- 0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.1)),
- 0 1px 3px 0 oklch(0 0 0 / 0.35),
- 0 1px 2px -1px oklch(0 0 0 / 0.35);
- opacity: 0;
- translate: -50% -50%;
- transition-timing-function: ease-out;
- transition-duration: 150ms;
- transition-property: opacity, height, width, outline-offset;
-
- &[data-orientation="horizontal"] {
- top: 50%;
- left: var(--media-slider-fill);
- }
-
- &[data-orientation="vertical"] {
- top: calc(100% - var(--media-slider-fill));
- left: 50%;
- }
-
- &:hover,
- &:focus {
- outline-color: oklch(from currentColor l c h / 0.15);
- outline-offset: 0;
- }
-
- &::after {
- position: absolute;
- inset: -4px;
- content: "";
+ /* Track */
+ & .media-slider__track {
+ position: relative;
+ overflow: hidden;
+ user-select: none;
border-radius: inherit;
- box-shadow: 0 0 0 2px currentColor;
+ isolation: isolate;
+
+ &[data-orientation="horizontal"] {
+ width: 100%;
+ height: calc(var(--spacing) * 1);
+ }
+ &[data-orientation="vertical"] {
+ width: calc(var(--spacing) * 1);
+ height: 100%;
+ }
+ }
+
+ /* Thumb */
+ & .media-slider__thumb {
+ position: absolute;
+ z-index: 10;
+ width: calc(var(--spacing) * 2.5);
+ height: calc(var(--spacing) * 2.5);
+ user-select: none;
+ outline: 4px solid transparent;
+ outline-offset: -4px;
+ background-color: currentColor;
+ border-radius: calc(Infinity * 1px);
+ box-shadow:
+ 0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.1)),
+ 0 1px 3px 0 oklch(0 0 0 / 0.35),
+ 0 1px 2px -1px oklch(0 0 0 / 0.35);
+ opacity: 0;
+ translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
- transition-property: opacity, scale;
+ transition-property: opacity, height, width, outline-offset;
+
+ &[data-orientation="horizontal"] {
+ top: 50%;
+ left: var(--media-slider-fill);
+ }
+ &[data-orientation="vertical"] {
+ top: calc(100% - var(--media-slider-fill));
+ left: 50%;
+ }
+
+ &:hover,
+ &:focus {
+ outline-color: oklch(from currentColor l c h / 0.15);
+ outline-offset: 0;
+ }
+
+ &::after {
+ position: absolute;
+ inset: -4px;
+ content: "";
+ border-radius: inherit;
+ box-shadow: 0 0 0 2px currentColor;
+ transition-timing-function: ease-out;
+ transition-duration: 150ms;
+ transition-property: opacity, scale;
+ }
+
+ &:not(:focus-visible)::after {
+ opacity: 0;
+ scale: 0.5;
+ }
}
- &:not(:focus-visible)::after {
- opacity: 0;
- scale: 0.5;
- }
-}
-
-.media-default-skin .media-slider:active .media-slider__thumb,
-.media-default-skin .media-slider__thumb--persistent {
- width: 0.75rem;
- height: 0.75rem;
-}
-
-.media-default-skin .media-slider:hover .media-slider__thumb,
-.media-default-skin .media-slider__thumb:focus-visible,
-.media-default-skin .media-slider__thumb--persistent {
- opacity: 1;
-}
-
-/* Preview */
-.media-default-skin .media-slider__preview {
- & .media-slider__value,
- &::before {
- opacity: 0;
- scale: 0.5;
- transition-timing-function: ease-out;
- transition-duration: 200ms;
- transition-property: opacity, scale;
+ &:active .media-slider__thumb,
+ &:focus-within .media-slider__thumb,
+ & .media-slider__thumb--persistent {
+ width: calc(var(--spacing) * 3);
+ height: calc(var(--spacing) * 3);
}
- & .media-slider__value {
- position: absolute;
- bottom: 2.25rem;
- text-shadow: 0 1px 0 var(--media-current-shadow-color);
- filter: blur(8px);
- translate: -50% 0.5rem;
- transition-property: filter, opacity, scale, translate;
- }
-
- &::before {
- display: block;
- min-width: 0.25rem;
- height: 0.25rem;
- content: "";
- background-color: currentColor;
- border-radius: 100%;
- box-shadow:
- 0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
- 0 1px 2px 0 oklch(0 0 0 / 0.35);
- }
-
- &[data-pointing] .media-slider__value,
- &[data-pointing]:not([data-dragging])::before {
+ &:hover .media-slider__thumb,
+ & .media-slider__thumb:focus-visible,
+ & .media-slider__thumb--persistent {
opacity: 1;
- scale: 1;
- }
- &[data-pointing] .media-slider__value {
- filter: blur(0);
- translate: -50% 0;
- }
-}
-
-/* Shared track fills */
-.media-default-skin .media-slider__buffer,
-.media-default-skin .media-slider__fill {
- position: absolute;
- pointer-events: none;
- border-radius: inherit;
-}
-
-.media-default-skin .media-slider__buffer[data-orientation="horizontal"],
-.media-default-skin .media-slider__fill[data-orientation="horizontal"] {
- inset-block: 0;
- left: 0;
-}
-
-.media-default-skin .media-slider__buffer[data-orientation="vertical"],
-.media-default-skin .media-slider__fill[data-orientation="vertical"] {
- inset-inline: 0;
- bottom: 0;
-}
-
-/* Buffer */
-.media-default-skin .media-slider__buffer {
- background-color: oklch(from currentColor l c h / 0.2);
- transition-timing-function: ease-out;
- transition-duration: 0.25s;
-
- &[data-orientation="horizontal"] {
- width: var(--media-slider-buffer);
- transition-property: width;
}
- &[data-orientation="vertical"] {
- height: var(--media-slider-buffer);
- transition-property: height;
- }
-}
+ /* Preview */
+ & .media-slider__preview {
+ & .media-slider__value,
+ &::before {
+ opacity: 0;
+ scale: 0.5;
+ transition-timing-function: ease-out;
+ transition-duration: 200ms;
+ transition-property: opacity, scale;
+ }
-/* Fill */
-.media-default-skin .media-slider__fill {
- background-color: currentColor;
+ & .media-slider__value {
+ position: absolute;
+ bottom: calc(var(--spacing) * 9);
+ text-shadow: 0 1px 0 var(--media-current-shadow-color);
+ filter: blur(8px);
+ translate: -50% calc(var(--spacing) * 2);
+ transition-property: filter, opacity, scale, translate;
+ }
- &[data-orientation="horizontal"] {
- width: var(--media-slider-fill);
+ &::before {
+ display: block;
+ min-width: calc(var(--spacing) * 1);
+ height: calc(var(--spacing) * 1);
+ content: "";
+ background-color: currentColor;
+ border-radius: 100%;
+ box-shadow:
+ 0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
+ 0 1px 2px 0 oklch(0 0 0 / 0.35);
+ }
+
+ &[data-pointing] .media-slider__value,
+ &[data-pointing]:not([data-dragging])::before {
+ opacity: 1;
+ scale: 1;
+ }
+ &[data-pointing] .media-slider__value {
+ filter: blur(0);
+ translate: -50% 0;
+ }
}
- &[data-orientation="vertical"] {
- height: var(--media-slider-fill);
+ /* Shared track fills */
+ & .media-slider__buffer,
+ & .media-slider__fill {
+ position: absolute;
+ pointer-events: none;
+ border-radius: inherit;
+
+ &[data-orientation="horizontal"],
+ &[data-orientation="horizontal"] {
+ inset-block: 0;
+ left: 0;
+ }
+ &[data-orientation="vertical"],
+ &[data-orientation="vertical"] {
+ inset-inline: 0;
+ bottom: 0;
+ }
+ }
+
+ /* Buffer */
+ & .media-slider__buffer {
+ background-color: oklch(from currentColor l c h / 0.2);
+ transition-timing-function: ease-out;
+ transition-duration: 0.25s;
+
+ &[data-orientation="horizontal"] {
+ width: var(--media-slider-buffer);
+ transition-property: width;
+ }
+ &[data-orientation="vertical"] {
+ height: var(--media-slider-buffer);
+ transition-property: height;
+ }
+ }
+
+ /* Fill */
+ & .media-slider__fill {
+ background-color: currentColor;
+
+ &[data-orientation="horizontal"] {
+ width: var(--media-slider-fill);
+ }
+ &[data-orientation="vertical"] {
+ height: var(--media-slider-fill);
+ }
+ }
+
+ /* Dragging — thumb and fill follow the pointer position */
+ &[data-dragging] {
+ & .media-slider__thumb[data-orientation="horizontal"] {
+ left: var(--media-slider-pointer);
+ }
+ & .media-slider__thumb[data-orientation="vertical"] {
+ top: calc(100% - var(--media-slider-pointer));
+ }
+ & .media-slider__fill[data-orientation="horizontal"] {
+ width: var(--media-slider-pointer);
+ }
+ & .media-slider__fill[data-orientation="vertical"] {
+ height: var(--media-slider-pointer);
+ }
}
}
-
-/* Dragging — thumb and fill follow the pointer position */
-.media-default-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="horizontal"] {
- left: var(--media-slider-pointer);
-}
-
-.media-default-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="vertical"] {
- top: calc(100% - var(--media-slider-pointer));
-}
-
-.media-default-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="horizontal"] {
- width: var(--media-slider-pointer);
-}
-
-.media-default-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="vertical"] {
- height: var(--media-slider-pointer);
-}
diff --git a/packages/skins/src/default/css/components/surface.css b/packages/skins/src/default/css/components/surface.css
index 173c7c24..6ec2bda4 100644
--- a/packages/skins/src/default/css/components/surface.css
+++ b/packages/skins/src/default/css/components/surface.css
@@ -18,6 +18,8 @@
pointer-events: none;
content: "";
border-radius: inherit;
- box-shadow: inset 0 0 0 1px var(--media-surface-inner-border-color);
+ box-shadow:
+ inset 0 1px 0 0 var(--media-surface-inner-border-color),
+ inset 0 0 0 1px oklch(from var(--media-surface-inner-border-color) l c h / calc(alpha * 0.5));
}
}
diff --git a/packages/skins/src/default/css/components/thumbnail.css b/packages/skins/src/default/css/components/thumbnail.css
index 53596bed..adac216e 100644
--- a/packages/skins/src/default/css/components/thumbnail.css
+++ b/packages/skins/src/default/css/components/thumbnail.css
@@ -5,7 +5,7 @@
.media-default-skin .media-thumbnail {
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
- border-radius: 0.75rem;
+ border-radius: calc(var(--spacing) * 3);
& .media-thumbnail__image {
position: relative;
@@ -17,7 +17,7 @@
position: absolute;
inset: 0;
content: "";
- background-image: linear-gradient(to top, oklch(0 0 0 / 0.8), oklch(0 0 0 / 0.3), oklch(0 0 0 / 0));
+ background-image: linear-gradient(to top, oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.1), oklch(0 0 0 / 0));
border-radius: inherit;
}
}
@@ -25,7 +25,7 @@
& .media-thumbnail__time {
position: absolute;
inset-inline: 0;
- bottom: 0.5rem;
+ bottom: calc(var(--spacing) * 2);
text-align: center;
}
diff --git a/packages/skins/src/default/css/components/time.css b/packages/skins/src/default/css/components/time.css
index 6686212f..1d1b75a0 100644
--- a/packages/skins/src/default/css/components/time.css
+++ b/packages/skins/src/default/css/components/time.css
@@ -5,10 +5,16 @@
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
- gap: 0.75rem;
+ gap: calc(var(--spacing) * 2.5);
align-items: center;
- padding-inline: 0.5rem;
+ padding-inline: calc(var(--spacing) * 1.5);
container: media-time-controls / inline-size;
+
+ & .media-time:last-child {
+ @container media-time-controls (width < 10rem) {
+ display: none;
+ }
+ }
}
.media-default-skin .media-time {
@@ -19,7 +25,7 @@
cursor: pointer;
outline: 2px solid transparent;
outline-offset: -2px;
- border-radius: 0.25rem;
+ border-radius: calc(var(--spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
diff --git a/packages/skins/src/default/css/video.css b/packages/skins/src/default/css/video.css
index abbbbab6..dfe1f402 100644
--- a/packages/skins/src/default/css/video.css
+++ b/packages/skins/src/default/css/video.css
@@ -40,22 +40,24 @@
);
--media-border-color: oklch(0 0 0 / 0.1);
--media-surface-background-color: oklch(1 0 0 / 0.1);
- --media-surface-inner-border-color: oklch(1 0 0 / 0.05);
+ --media-surface-inner-border-color: oklch(1 0 0 / 0.1);
--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);
- --media-video-border-radius: var(--media-border-radius, 2rem);
+
+ /* We have to create an alias so that the shadow DOM can inherit the custom property value */
+ --media-video-border-radius: var(--media-border-radius, 1.75rem);
+
--media-controls-transition-duration: 100ms;
--media-controls-transition-timing-function: ease-out;
+
--media-error-dialog-transition-duration: 350ms;
--media-error-dialog-transition-delay: 100ms;
--media-error-dialog-transition-timing-function: var(--media-spring-timing-function);
+
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
- --media-tooltip-side-offset: 0.75rem;
- --media-tooltip-boundary-offset: 0.5rem;
- --media-popover-side-offset: 0.5rem;
- --media-popover-boundary-offset: 0.5rem;
+
overflow: clip;
background: oklch(0 0 0);
@@ -102,6 +104,30 @@
&:fullscreen {
--media-border-radius: 0;
+
+ &::after {
+ display: none;
+ }
+
+ .media-controls {
+ max-width: 1024px;
+ margin-inline: auto;
+ }
+
+ @media (width > 1536px) {
+ --scale: 1.25;
+
+ .media-controls {
+ max-width: 1152px;
+ }
+ }
+ @media (width > 1920px) {
+ --scale: 1.5;
+
+ .media-controls {
+ max-width: 1280px;
+ }
+ }
}
}
@@ -121,12 +147,12 @@
.media-default-skin--video .media-error__dialog {
display: flex;
flex-direction: column;
- gap: 0.75rem;
- max-width: 18rem;
- padding: 0.75rem;
+ gap: calc(var(--spacing) * 3);
+ max-width: calc(var(--spacing) * 72);
+ padding: calc(var(--spacing) * 3);
color: oklch(1 0 0);
text-shadow: 0 1px 0 oklch(0 0 0 / 0.25);
- border-radius: 1.75rem;
+ border-radius: calc(var(--spacing) * 7);
transition-delay: var(--media-error-dialog-transition-delay);
transition-timing-function: var(--media-error-dialog-transition-timing-function);
transition-duration: var(--media-error-dialog-transition-duration);
@@ -145,13 +171,13 @@
.media-default-skin--video .media-error__content {
display: flex;
flex-direction: column;
- gap: 0.5rem;
- padding: 0.5rem 0.5rem 0.375rem;
+ gap: calc(var(--spacing) * 2);
+ padding: calc(var(--spacing) * 2) calc(var(--spacing) * 2) calc(var(--spacing) * 1.5);
text-shadow: inherit;
}
.media-default-skin--video .media-error__title {
- font-size: 1rem;
+ font-size: var(--font-size-medium);
}
/* ==========================================================================
@@ -159,30 +185,47 @@
========================================================================== */
.media-default-skin--video .media-controls {
- position: absolute;
- inset-inline: 0.5rem;
- bottom: 0.5rem;
- z-index: 10;
- flex-wrap: wrap;
- color: var(--media-color-primary, oklch(1 0 0));
- transform-origin: bottom;
- transition-timing-function: var(--media-controls-transition-timing-function);
- transition-duration: var(--media-controls-transition-duration);
+ --inset-factor: 2;
+ --inset: calc(var(--spacing) * var(--inset-factor));
+ --base-boundary-offset: var(--inset-factor);
- @media (pointer: fine) {
- transition-property: scale, filter, opacity;
- will-change: scale, filter, opacity;
+ position: absolute;
+ z-index: 10;
+ color: var(--media-color-primary, oklch(1 0 0));
+ transition-timing-function: var(--media-controls-transition-timing-function);
+ /* Speed up the entry transition */
+ transition-duration: calc(var(--media-controls-transition-duration) / 2);
+
+ &.media-controls--primary {
+ inset-inline: var(--inset);
+ bottom: var(--inset);
+ transform-origin: bottom;
+
+ &:not([data-visible]) {
+ translate: 0 4px;
+ }
}
- @media (pointer: coarse) {
- transition-property: scale, opacity;
- will-change: scale, opacity;
+ &.media-controls--secondary {
+ top: var(--inset);
+ right: var(--inset);
+ container-type: normal;
+ transform-origin: top;
+
+ @container media-root (width >= 32rem) {
+ display: none;
+ }
+
+ &:not([data-visible]) {
+ translate: 0 -4px;
+ }
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
scale: 0.95;
+ transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
@@ -194,36 +237,37 @@
}
& .media-time-controls {
- flex: 0 0 100%;
- order: -1;
- padding-inline: 0.625rem;
+ flex: 1;
+
+ @container media-controls (width >= 30rem) {
+ padding-inline: calc(var(--spacing) * 2.5);
+ }
}
- & .media-button-group:first-child {
- flex: 1;
- text-align: left;
+ @media (pointer: fine) {
+ transition-property: filter, opacity, scale, translate;
+ will-change: filter, opacity, scale, translate;
}
- & .media-button-group:last-child {
- flex: 1;
- justify-content: end;
+ @media (pointer: coarse) {
+ transition-property: opacity, scale, translate;
+ will-change: opacity, scale, translate;
}
@container media-root (width > 42rem) {
- inset-inline: 0.75rem;
- bottom: 0.75rem;
- flex-wrap: nowrap;
- column-gap: 0.125rem;
- padding: 0.25rem;
+ --inset-factor: 3;
+ column-gap: calc(var(--spacing) * 0.5);
+ }
+}
- & .media-time-controls {
- flex: 1;
- order: unset;
- }
-
- & .media-button-group:first-child,
- & .media-button-group:last-child {
- flex: 0 0 auto;
+.media-default-skin--video:not(.media-default-skin--live) .media-controls--primary {
+ & .media-button--seek,
+ & .media-button--cast,
+ & .media-button--airplay,
+ & .media-button--pip,
+ & .media-button--fullscreen {
+ @container media-root (width < 32rem) {
+ display: none;
}
}
}
@@ -247,32 +291,21 @@
}
.media-default-skin--video .media-slider__thumbnail {
- --media-slider-thumbnail-max-width: 11rem;
- --media-slider-thumbnail-max-height: 8rem;
- --media-slider-thumbnail-padding: -1.125rem;
+ --max-width: calc(var(--spacing) * 44);
+ --max-height: calc(var(--spacing) * 32);
+ --padding: calc(var(--spacing) * -4.5);
/**
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-slider-thumbnail-inset: calc((100cqi - 100%) / 2);
+ --inset: calc((100cqi - 100%) / 2);
position: absolute;
- bottom: calc(100% + 1.2rem);
+ bottom: calc(100% + calc(var(--spacing) * 4.8));
left: clamp(
- calc(
- var(--media-slider-thumbnail-max-width) /
- 2 +
- var(--media-slider-thumbnail-padding) -
- var(--media-slider-thumbnail-inset)
- ),
+ calc(var(--max-width) / 2 + var(--padding) - var(--inset)),
var(--media-slider-pointer),
- calc(
- 100% -
- var(--media-slider-thumbnail-max-width) /
- 2 -
- var(--media-slider-thumbnail-padding) +
- var(--media-slider-thumbnail-inset)
- )
+ calc(100% - var(--max-width) / 2 - var(--padding) + var(--inset))
);
pointer-events: none;
opacity: 0;
@@ -285,8 +318,8 @@
transition-property: scale, opacity, filter;
& .media-thumbnail__image {
- max-width: var(--media-slider-thumbnail-max-width);
- max-height: var(--media-slider-thumbnail-max-height);
+ max-width: var(--max-width);
+ max-height: var(--max-height);
}
}
.media-default-skin--video .media-slider[data-pointing] .media-slider__thumbnail:has([role="img"]:not([data-hidden])) {
diff --git a/packages/skins/src/default/tailwind/audio.tailwind.ts b/packages/skins/src/default/tailwind/audio.tailwind.ts
index e8ed93a9..80500f9b 100644
--- a/packages/skins/src/default/tailwind/audio.tailwind.ts
+++ b/packages/skins/src/default/tailwind/audio.tailwind.ts
@@ -22,10 +22,6 @@ export const root = cn(
'[--media-error-dialog-transition-delay:100ms]',
'[--media-popup-transition-duration:100ms]',
'[--media-popup-transition-timing-function:ease-out]',
- '[--media-tooltip-side-offset:0.75rem]',
- '[--media-tooltip-boundary-offset:0.75rem]',
- '[--media-popover-side-offset:0.75rem]',
- '[--media-popover-boundary-offset:0.75rem]',
'motion-reduce:[--media-error-dialog-transition-duration:50ms]',
'motion-reduce:[--media-error-dialog-transition-delay:0ms]',
'motion-reduce:[--media-popup-transition-duration:0ms]',
@@ -47,7 +43,13 @@ export const root = cn(
Controls
========================================================================== */
-export const controls = cn(baseControls, surface, 'text-(--media-text-color)', 'peer-data-open/error:**:invisible');
+export const controls = cn(
+ baseControls,
+ surface,
+ '[--base-boundary-offset:2]',
+ 'text-(--media-text-color)',
+ 'peer-data-open/error:**:invisible'
+);
export const playButton = {
wrapper: 'group/play inline-flex relative',
diff --git a/packages/skins/src/default/tailwind/components/badge.ts b/packages/skins/src/default/tailwind/components/badge.ts
index 61e059b9..08697459 100644
--- a/packages/skins/src/default/tailwind/components/badge.ts
+++ b/packages/skins/src/default/tailwind/components/badge.ts
@@ -1,2 +1,2 @@
export const badge =
- 'rounded-full bg-current/10 px-1.5 py-0.5 text-[0.6875rem] font-medium leading-none text-current/70';
+ 'rounded-full bg-current/10 px-1.5 py-0.5 text-(length:--font-size-small) font-medium leading-none text-current/70';
diff --git a/packages/skins/src/default/tailwind/components/button-group.ts b/packages/skins/src/default/tailwind/components/button-group.ts
index 143d2b02..edc919ab 100644
--- a/packages/skins/src/default/tailwind/components/button-group.ts
+++ b/packages/skins/src/default/tailwind/components/button-group.ts
@@ -1,3 +1,3 @@
import { cn } from '@videojs/utils/style';
-export const buttonGroup = cn('flex items-center gap-[0.075rem]', '@2xl/media-root:gap-0.5');
+export const buttonGroup = cn('flex items-center [gap:--spacing(0.3)]', '@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 7eeec140..4e3ad08c 100644
--- a/packages/skins/src/default/tailwind/components/button.ts
+++ b/packages/skins/src/default/tailwind/components/button.ts
@@ -2,7 +2,7 @@ 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 min-h-0',
+ 'flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 h-9',
'py-2 px-4 rounded-full',
'outline-2 outline-transparent -outline-offset-2',
'transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out',
@@ -19,7 +19,7 @@ export const button = {
'focus-visible:bg-current/10',
'aria-expanded:bg-current/10'
),
- icon: cn('grid w-9 aspect-square p-0', 'active:scale-90'),
+ icon: cn('grid aspect-square p-0!', 'active:scale-90'),
/**
* Live variant: wide pill button with a status dot rendered via `::before`
* (gray → red at the live edge) and "LIVE" as the button's own text.
@@ -27,7 +27,7 @@ export const button = {
live: cn(
'inline-flex items-center gap-1.5',
'aspect-auto w-auto px-3 py-2',
- 'text-xs font-semibold uppercase tracking-wider leading-none',
+ 'text-(length:--font-size-small) font-semibold uppercase tracking-wider leading-none',
'before:inline-block before:size-2 before:shrink-0 before:rounded-full',
'before:bg-current/40 before:transition-colors before:duration-150 before:ease-out',
'before:content-[""]',
diff --git a/packages/skins/src/default/tailwind/components/controls.ts b/packages/skins/src/default/tailwind/components/controls.ts
index 6d85c18f..0db2ae17 100644
--- a/packages/skins/src/default/tailwind/components/controls.ts
+++ b/packages/skins/src/default/tailwind/components/controls.ts
@@ -5,8 +5,12 @@ export const controls = cn(
'peer/controls',
// Layout
'@container/media-controls',
- 'p-[0.375rem] flex items-center gap-x-[0.075rem]',
- 'rounded-3xl',
+ '[--media-popover-side-offset:calc(var(--spacing)*(var(--base-side-offset,2)+var(--controls-padding,1)))]',
+ '[--media-tooltip-side-offset:var(--media-popover-side-offset)]',
+ '[--media-popover-boundary-offset:calc(var(--spacing)*var(--base-boundary-offset,2))]',
+ '[--media-tooltip-boundary-offset:var(--media-popover-boundary-offset)]',
+ '[padding:calc(var(--spacing)*var(--controls-padding,1))] flex items-center gap-x-px',
+ 'rounded-full',
// Text shadow
'text-shadow-2xs text-shadow-(color:--media-current-shadow-color)'
);
diff --git a/packages/skins/src/default/tailwind/components/error.ts b/packages/skins/src/default/tailwind/components/error.ts
index a85f6d4c..4e725b54 100644
--- a/packages/skins/src/default/tailwind/components/error.ts
+++ b/packages/skins/src/default/tailwind/components/error.ts
@@ -3,7 +3,7 @@ import { cn } from '@videojs/utils/style';
export const error = {
root: 'peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none',
dialog: cn(
- 'flex flex-col gap-3 max-w-72 p-3 rounded-[1.75rem] text-white',
+ 'flex flex-col gap-3 max-w-72 p-3 rounded-[--spacing(7)] text-white',
// Animation
'transition-[opacity,scale,transform]',
'duration-(--media-error-dialog-transition-duration)',
diff --git a/packages/skins/src/default/tailwind/components/input-feedback.ts b/packages/skins/src/default/tailwind/components/input-feedback.ts
index 684bb42d..e9708f48 100644
--- a/packages/skins/src/default/tailwind/components/input-feedback.ts
+++ b/packages/skins/src/default/tailwind/components/input-feedback.ts
@@ -52,7 +52,7 @@ export const inputFeedback = {
),
// Volume island sizing + progress-fill gradient on the content child
volume: cn(
- 'w-[min(80%,12rem)]',
+ 'w-[min(80%,--spacing(48))]',
'*:[--media-progress-fill:var(--media-volume-fill)]',
'*:rounded-[inherit]',
'*:[background-image:linear-gradient(to_right,currentColor_0%,currentColor_var(--media-progress-fill),transparent_var(--media-progress-fill),transparent_100%)]',
diff --git a/packages/skins/src/default/tailwind/components/menu.ts b/packages/skins/src/default/tailwind/components/menu.ts
index f1f20420..2ba30972 100644
--- a/packages/skins/src/default/tailwind/components/menu.ts
+++ b/packages/skins/src/default/tailwind/components/menu.ts
@@ -43,8 +43,11 @@ const itemBase = cn(
);
const menuTokens = cn(
- '[--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.25rem]',
- '[--menu-border-radius:0.75rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))]',
+ '[--menu-transition-duration:250ms]',
+ '[--menu-max-height:--spacing(56)]',
+ '[--menu-padding:--spacing(1)]',
+ '[--menu-border-radius:--spacing(3)]',
+ '[--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))]',
'motion-reduce:[--menu-transition-duration:0ms]'
);
@@ -66,7 +69,7 @@ const menuHostShell = cn(
popup.popover,
surface,
menuTokens,
- 'max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))]',
+ 'min-w-max max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))]',
'box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none'
);
@@ -79,9 +82,10 @@ export const menu = {
// Add height and width transitions.
'[--media-popup-transition:var(--media-popup-base-transition),height_var(--media-popup-transition-timing-function)_var(--menu-transition-duration),width_var(--media-popup-transition-timing-function)_var(--menu-transition-duration)]',
// Don't transition width and height on open/close.
- 'data-starting-style:[--media-popup-transition:var(--media-popup-base-transition)] data-ending-style:[--media-popup-transition:var(--media-popup-base-transition)]',
- 'min-w-48 w-(--media-menu-width) h-(--media-menu-height)',
- '!overflow-hidden'
+ 'data-starting-style:[--media-popup-transition:var(--media-popup-base-transition)]',
+ 'data-ending-style:[--media-popup-transition:var(--media-popup-base-transition)]',
+ 'min-w-48! w-(--media-menu-width) h-(--media-menu-height)',
+ 'overflow-hidden!'
),
group,
item: cn(
@@ -91,7 +95,7 @@ export const menu = {
'aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50'
),
separator: 'my-1 border-b border-[oklch(0_0_0/0.1)] shadow-[0_1px_0_0_oklch(1_0_0/0.075)]',
- tier: 'pl-0.5 pt-px text-[0.5625rem] font-semibold leading-none text-current/70',
+ tier: 'pl-0.5 pt-px text-(length:--font-size-tiny) font-semibold leading-none text-current/70',
indicator: 'ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100',
icon: 'shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]',
/** Root settings view — slides out when a submenu is active. */
@@ -99,7 +103,7 @@ export const menu = {
/** Submenu panel — slides in/out alongside the root view. */
submenuPanel,
back: cn(itemBase, 'mb-0.5 w-full'),
- hint: 'ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65',
+ hint: 'ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-current/65',
hintLabel: 'max-w-24 overflow-hidden text-ellipsis whitespace-nowrap',
chevron: 'size-3.5',
settingsGroup: 'group/settings',
diff --git a/packages/skins/src/default/tailwind/components/popup.ts b/packages/skins/src/default/tailwind/components/popup.ts
index a650c8f5..6eb00cec 100644
--- a/packages/skins/src/default/tailwind/components/popup.ts
+++ b/packages/skins/src/default/tailwind/components/popup.ts
@@ -31,13 +31,13 @@ export const popup = {
),
tooltip: cn(
base,
- 'py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap',
+ 'py-1 px-2.5 rounded-full text-(length:--font-size-base) whitespace-nowrap',
'data-open:flex data-open:items-center data-open:gap-1',
'data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset)',
'data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)'
),
volume: 'py-3 px-0 rounded-full',
tooltipShortcut: cn(
- 'min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded'
+ 'min-w-[1.5em] p-[0.1em] bg-current/30 text-(length:--font-size-small) font-semibold font-[inherit] leading-[1.25] text-center rounded-[--spacing(1)]'
),
};
diff --git a/packages/skins/src/default/tailwind/components/root.ts b/packages/skins/src/default/tailwind/components/root.ts
index bc22c839..8c4809a4 100644
--- a/packages/skins/src/default/tailwind/components/root.ts
+++ b/packages/skins/src/default/tailwind/components/root.ts
@@ -6,8 +6,8 @@ export const root = cn(
// Layout & containment
'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',
+ 'rounded-(--media-border-radius,1.75rem)',
+ 'font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-(length:--font-size-base) leading-normal subpixel-antialiased',
// Focus ring
'outline-2 outline-transparent -outline-offset-4',
'transition-[outline-offset,outline-color] duration-100 ease-out',
@@ -19,6 +19,10 @@ export const root = cn(
// Shadow color variables (derived from currentColor lightness)
'[--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))]',
'[--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))]',
- // Icon sizing
- '[--media-icon-size:18px]'
+ // Font and icon sizing
+ '[--scale:1] [--font-size-base:calc(0.8125rem*var(--scale))] [--font-size-small:calc(0.6875rem*var(--scale))] [--font-size-medium:calc(0.9375rem*var(--scale))] [--font-size-tiny:calc(0.5625rem*var(--scale))]',
+ '[--media-icon-size:calc(--spacing(4.5)*var(--scale))]',
+ // Preserve the container's Tailwind spacing value and scale descendants from it.
+ '[--spacing-proxy:var(--spacing)]',
+ '[&>*]:[--spacing:calc(var(--spacing-proxy)*var(--scale))]'
);
diff --git a/packages/skins/src/default/tailwind/components/seek.ts b/packages/skins/src/default/tailwind/components/seek.ts
index f4b14949..dbfde9f9 100644
--- a/packages/skins/src/default/tailwind/components/seek.ts
+++ b/packages/skins/src/default/tailwind/components/seek.ts
@@ -1,5 +1,5 @@
export const seek = {
- label: 'text-[10px] font-medium tracking-tighter tabular-nums',
+ label: 'text-[0.715em] font-medium tracking-tighter 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 40fb2109..4c2336e2 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-8',
+ 'data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-(--slider-width,100%) data-[orientation=horizontal]:h-(--slider-height,--spacing(8))',
// Vertical
- 'data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-20'
+ 'data-[orientation=vertical]:w-(--slider-width,--spacing(8)) data-[orientation=vertical]:h-(--slider-height,--spacing(20))'
),
track: cn(
'relative isolate overflow-hidden rounded-[inherit] select-none',
@@ -64,7 +64,7 @@ export const slider = {
interactive: cn(
'size-2.5',
'opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100',
- 'group-active/slider:size-3'
+ 'group-active/slider:size-3 group-focus-within/slider:size-3'
),
},
preview: cn(
diff --git a/packages/skins/src/default/tailwind/components/surface.ts b/packages/skins/src/default/tailwind/components/surface.ts
index 97b61e96..462f5dfd 100644
--- a/packages/skins/src/default/tailwind/components/surface.ts
+++ b/packages/skins/src/default/tailwind/components/surface.ts
@@ -4,5 +4,5 @@ export const surface = cn(
'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'
+ 'after:absolute after:inset-0 after:[box-shadow:inset_0_1px_0_0_var(--media-surface-inner-border-color),inset_0_0_0_1px_oklch(from_var(--media-surface-inner-border-color)_l_c_h/calc(alpha*0.5))] after:rounded-[inherit] after:pointer-events-none after:z-10'
);
diff --git a/packages/skins/src/default/tailwind/components/thumbnail.ts b/packages/skins/src/default/tailwind/components/thumbnail.ts
index 822a2270..86df116c 100644
--- a/packages/skins/src/default/tailwind/components/thumbnail.ts
+++ b/packages/skins/src/default/tailwind/components/thumbnail.ts
@@ -1,12 +1,12 @@
import { cn } from '@videojs/utils/style';
export const thumbnail = {
- root: cn('group/thumbnail peer/thumbnail pointer-events-none bg-black/90 rounded-xl'),
+ root: cn('group/thumbnail peer/thumbnail pointer-events-none bg-black/90 rounded-[--spacing(3)]'),
image: cn(
'block relative overflow-clip rounded-[inherit]',
'transition-opacity duration-150 ease-out',
'after:absolute after:inset-0 after:rounded-[inherit]',
- 'after:bg-linear-to-t after:from-black/80 after:via-black/30 after:to-black/0',
+ 'after:bg-linear-to-t after:from-black/50 after:via-black/10 after:to-black/0',
'data-loading:opacity-0'
),
time: 'absolute bottom-2 inset-x-0 text-center tabular-nums',
diff --git a/packages/skins/src/default/tailwind/components/time.ts b/packages/skins/src/default/tailwind/components/time.ts
index 622464a4..fab151e7 100644
--- a/packages/skins/src/default/tailwind/components/time.ts
+++ b/packages/skins/src/default/tailwind/components/time.ts
@@ -1,6 +1,7 @@
export const time = {
- group: '@container/media-time flex items-center flex-1 gap-3 px-2',
+ group:
+ '@container/media-time flex items-center flex-1 gap-2.5 px-1.5 @max-[10rem]/media-time:[&>*:last-child]:hidden',
current: 'hidden @2xs/media-time:block tabular-nums',
duration:
- 'tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2',
+ 'tabular-nums cursor-pointer rounded-[--spacing(1)] outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2',
};
diff --git a/packages/skins/src/default/tailwind/video.tailwind.ts b/packages/skins/src/default/tailwind/video.tailwind.ts
index e63c03c0..6541b450 100644
--- a/packages/skins/src/default/tailwind/video.tailwind.ts
+++ b/packages/skins/src/default/tailwind/video.tailwind.ts
@@ -19,9 +19,11 @@ import { time as baseTime } from './components/time';
export const root = (isShadowDOM: boolean) =>
cn(
baseRoot,
+ 'group/skin',
'bg-black overflow-clip',
// Inner border ring
'after:absolute after:pointer-events-none after:rounded-[inherit] after:z-10',
+ '[&:fullscreen]:after:hidden',
'after:inset-0 after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15',
// Video element
{
@@ -31,7 +33,7 @@ export const root = (isShadowDOM: boolean) =>
!isShadowDOM,
},
'[--media-spring-timing-function:linear(0,0.034_1.5%,0.763_9.7%,1.066_13.9%,1.198_19.9%,1.184_21.8%,0.963_37.5%,0.997_50.9%,1)]',
- '[--media-video-border-radius:var(--media-border-radius,2rem)]',
+ '[--media-video-border-radius:var(--media-border-radius,1.75rem)]',
'[--media-controls-transition-duration:100ms]',
'[--media-controls-transition-timing-function:ease-out]',
'[--media-error-dialog-transition-duration:350ms]',
@@ -40,17 +42,15 @@ export const root = (isShadowDOM: boolean) =>
'[--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-inner-border-color:oklch(1_0_0/0.1)]',
'[--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)]',
+ // Fullscreen scale
+ 'min-[1536px]:[&:fullscreen]:[--scale:1.25] min-[1920px]:[&:fullscreen]:[--scale:1.5]',
'motion-reduce:[--media-error-dialog-transition-duration:50ms]',
'motion-reduce:[--media-error-dialog-transition-delay:0ms]',
'motion-reduce:[--media-error-dialog-transition-timing-function:ease-out]',
- '[--media-tooltip-side-offset:0.75rem]',
- '[--media-tooltip-boundary-offset:0.5rem]',
- '[--media-popover-side-offset:0.5rem]',
- '[--media-popover-boundary-offset:0.5rem]',
'motion-reduce:[--media-popup-transition-duration:0ms]',
'[@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)]',
'contrast-more:[--media-surface-background-color:oklch(0_0_0)]',
@@ -62,11 +62,11 @@ export const root = (isShadowDOM: boolean) =>
'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-y:--spacing(-2)]',
'[--media-caption-track-delay:25ms]',
'[--media-caption-track-duration:var(--media-controls-transition-duration)]',
- 'has-[[data-controls][data-visible]]:[--media-caption-track-y:-5.5rem]',
- '@2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:-3.5rem]',
+ 'has-[[data-controls][data-visible]]:[--media-caption-track-y:--spacing(-22)]',
+ '@2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:--spacing(-14)]',
// Native caption track container
!isShadowDOM
? [
@@ -105,33 +105,46 @@ export const root = (isShadowDOM: boolean) =>
Controls (hide/show behavior)
========================================================================== */
-export const controls = cn(
+const controlsBase = cn(
baseControls,
surface,
- // 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)',
- 'pointer-fine:will-change-[scale,filter,opacity]',
- 'pointer-fine:transition-[scale,filter,opacity]',
- 'pointer-coarse:will-change-[scale,opacity]',
- 'pointer-coarse:transition-[scale,opacity]',
+ 'duration-[calc(var(--media-controls-transition-duration)/2)]',
+ 'not-data-visible:duration-(--media-controls-transition-duration)',
+ 'pointer-fine:will-change-[filter,opacity,scale,translate]',
+ 'pointer-fine:transition-[filter,opacity,scale,translate]',
+ 'pointer-coarse:will-change-[opacity,scale,translate]',
+ 'pointer-coarse:transition-[opacity,scale,translate]',
// Hidden state
'not-data-visible:pointer-events-none not-data-visible:opacity-0',
- 'motion-safe:not-data-visible:scale-90',
+ 'motion-safe:not-data-visible:scale-95',
'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'
+ '@2xl/media-root:gap-x-0.5 @2xl/media-root:[--base-boundary-offset:3]'
+);
+
+export const controls = cn(
+ controlsBase,
+ 'absolute bottom-2 inset-x-2 @2xl/media-root:bottom-3 @2xl/media-root:inset-x-3',
+ 'group-[:fullscreen]/skin:mx-auto group-[:fullscreen]/skin:max-w-5xl',
+ 'min-[1536px]:group-[:fullscreen]/skin:max-w-6xl min-[1920px]:group-[:fullscreen]/skin:max-w-7xl',
+ 'motion-safe:not-data-visible:translate-y-1'
+);
+
+export const secondaryControls = cn(
+ controlsBase,
+ 'absolute top-2 right-2 @2xl/media-root:top-3 @2xl/media-root:right-3 origin-top @container-normal',
+ '@min-[32rem]/media-root:hidden',
+ 'motion-safe:not-data-visible:-translate-y-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');
+export const buttonGroupStart = baseButtonGroup;
+export const buttonGroupEnd = baseButtonGroup;
/* ==========================================================================
Time
@@ -139,11 +152,7 @@ export const buttonGroupEnd = cn(baseButtonGroup, 'flex-1 justify-end @2xl/media
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]'
- ),
+ group: cn(baseTime.group, '@min-[30rem]/media-controls:px-2.5'),
};
/* ==========================================================================
@@ -155,22 +164,18 @@ export const thumbnail = {
root: cn(
baseThumbnail.root,
surface,
- '[--media-slider-thumbnail-max-width:11rem]',
- '[--media-slider-thumbnail-max-height:8rem]',
- '[--media-slider-thumbnail-padding:-1.125rem]',
- '[--media-slider-thumbnail-inset:calc((100cqi-100%)/2)]',
- 'absolute [left:clamp(calc(var(--media-slider-thumbnail-max-width)/2+var(--media-slider-thumbnail-padding)-var(--media-slider-thumbnail-inset)),var(--media-slider-pointer),calc(100%-var(--media-slider-thumbnail-max-width)/2-var(--media-slider-thumbnail-padding)+var(--media-slider-thumbnail-inset)))] bottom-[calc(100%+1.2rem)] -translate-x-1/2',
+ '[--max-width:--spacing(44)]',
+ '[--max-height:--spacing(32)]',
+ '[--padding:--spacing(-4.5)]',
+ '[--inset:calc((100cqi-100%)/2)]',
+ 'absolute [left:clamp(calc(var(--max-width)/2+var(--padding)-var(--inset)),var(--media-slider-pointer),calc(100%-var(--max-width)/2-var(--padding)+var(--inset)))] [bottom:calc(100%+--spacing(4.8))] -translate-x-1/2',
'opacity-0 scale-80 blur-sm origin-bottom',
'transition-[scale,opacity,filter] duration-150',
'has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:opacity-100',
'has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:scale-100',
'has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:blur-none'
),
- image: cn(
- baseThumbnail.image,
- 'max-w-(--media-slider-thumbnail-max-width)',
- 'max-h-(--media-slider-thumbnail-max-height)'
- ),
+ image: cn(baseThumbnail.image, 'max-w-(--max-width)', 'max-h-(--max-height)'),
};
/* ==========================================================================
@@ -196,12 +201,10 @@ export const popup = {
Menu
========================================================================== */
-const menuOffsets = '[--media-popover-side-offset:0.5rem] [--media-popover-boundary-offset:0.5rem]';
-
export const menu = {
...baseMenu,
- root: cn(baseMenu.root, menuOffsets),
- settings: cn(baseMenu.settings, menuOffsets),
+ root: baseMenu.root,
+ settings: baseMenu.settings,
};
/* ==========================================================================
@@ -218,7 +221,7 @@ export const error = {
...baseError,
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'),
+ title: cn(baseError.title, 'text-(length:--font-size-medium)'),
};
/* ==========================================================================
diff --git a/packages/skins/src/minimal/css/audio.css b/packages/skins/src/minimal/css/audio.css
index 1d931a08..9bc167ba 100644
--- a/packages/skins/src/minimal/css/audio.css
+++ b/packages/skins/src/minimal/css/audio.css
@@ -22,21 +22,22 @@
--media-controls-backdrop-filter: blur(16px) saturate(1.5);
--media-controls-border-color: oklch(0 0 0 / 0.1);
--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);
- --media-tooltip-border-color: oklch(0 0 0 / 0.1);
- --media-tooltip-backdrop-filter: blur(16px) saturate(1.5);
+
+ --media-popover-backdrop-filter: blur(16px) saturate(1.5);
+ --media-popover-background-color: oklch(1 0 0);
+ --media-popover-border-color: oklch(0 0 0 / 0.1);
+
+ --media-tooltip-backdrop-filter: var(--media-popover-backdrop-filter);
+ --media-tooltip-background-color: var(--media-popover-background-color);
+ --media-tooltip-border-color: var(--media-popover-border-color);
+
--media-tooltip-text-color: currentColor;
- --media-tooltip-side-offset: 0.75rem;
- --media-tooltip-boundary-offset: 0.75rem;
- --media-popover-background-color: var(--media-tooltip-background-color);
- --media-popover-border-color: var(--media-tooltip-border-color);
- --media-popover-backdrop-filter: var(--media-tooltip-backdrop-filter);
- --media-popover-side-offset: var(--media-tooltip-side-offset);
- --media-popover-boundary-offset: var(--media-tooltip-boundary-offset);
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
@@ -68,11 +69,11 @@
inset: 0;
z-index: 20;
display: flex;
- gap: 1rem;
+ gap: calc(var(--spacing) * 4);
align-items: center;
- padding-inline: 1.25rem 0.5rem;
+ padding-inline: calc(var(--spacing) * 5) calc(var(--spacing) * 2);
background-color: oklch(from var(--media-controls-background-color) l c h / 1);
- border-radius: calc(infinity * 1px);
+ border-radius: calc(Infinity * 1px);
transition-delay: var(--media-error-dialog-transition-delay);
transition-timing-function: ease-out;
transition-duration: var(--media-error-dialog-transition-duration);
@@ -92,7 +93,7 @@
.media-minimal-skin--audio .media-error__content {
display: flex;
flex: 1;
- gap: 0.5rem;
+ gap: calc(var(--spacing) * 2);
align-items: center;
}
@@ -101,10 +102,12 @@
========================================================================== */
.media-minimal-skin--audio .media-controls {
- gap: 0.5rem;
- padding: 0.375rem;
+ --base-side-offset: 2;
+ --base-boundary-offset: 2;
+
+ gap: calc(var(--spacing) * 2);
color: var(--media-controls-text-color);
- border-radius: var(--media-border-radius, 1rem);
+ border-radius: var(--media-border-radius, calc(var(--spacing) * 3.5));
box-shadow: 0 0 0 1px var(--media-controls-border-color);
}
@@ -133,10 +136,11 @@
========================================================================== */
.media-minimal-skin--audio .media-popover--volume {
- padding: 0 0 0 4rem;
+ --media-popover-side-offset: 0rem;
+ padding: 0 calc(var(--spacing) * 2) 0 calc(var(--spacing) * 16);
background: linear-gradient(to left, var(--media-controls-background-color) 80%, transparent 100%);
}
-.media-minimal-skin--audio .media-slider__preview .media-slider__value {
- bottom: 2.5rem;
+.media-minimal-skin--audio .media-slider .media-slider__preview .media-slider__value {
+ bottom: calc(var(--spacing) * 10);
}
diff --git a/packages/skins/src/minimal/css/components/badge.css b/packages/skins/src/minimal/css/components/badge.css
index 279bad2a..95ab89fa 100644
--- a/packages/skins/src/minimal/css/components/badge.css
+++ b/packages/skins/src/minimal/css/components/badge.css
@@ -3,12 +3,12 @@
========================================================================== */
.media-minimal-skin .media-badge {
- padding: 0.25rem 0.375rem;
- font-size: 0.6875rem;
+ padding: calc(var(--spacing) * 1) calc(var(--spacing) * 1.5);
+ font-size: var(--font-size-small);
font-weight: 500;
line-height: 1;
color: oklch(from currentColor l c h / 0.85);
white-space: nowrap;
background-color: oklch(from currentColor l c h / 0.1);
- border-radius: 0.25rem;
+ border-radius: calc(var(--spacing) * 1);
}
diff --git a/packages/skins/src/minimal/css/components/button-group.css b/packages/skins/src/minimal/css/components/button-group.css
index fcf4068b..5dd76b4e 100644
--- a/packages/skins/src/minimal/css/components/button-group.css
+++ b/packages/skins/src/minimal/css/components/button-group.css
@@ -4,10 +4,10 @@
.media-minimal-skin .media-button-group {
display: flex;
- gap: 0.075rem;
+ gap: calc(var(--spacing) * 0.3);
align-items: center;
@container media-root (width > 42rem) {
- gap: 0.125rem;
+ gap: calc(var(--spacing) * 0.5);
}
}
diff --git a/packages/skins/src/minimal/css/components/button.css b/packages/skins/src/minimal/css/components/button.css
index ed2cd40b..9cef7d9e 100644
--- a/packages/skins/src/minimal/css/components/button.css
+++ b/packages/skins/src/minimal/css/components/button.css
@@ -8,8 +8,9 @@
flex-shrink: 0;
align-items: center;
justify-content: center;
+ height: calc(var(--spacing) * 9.5);
min-height: 0;
- padding: 0.5rem 1rem;
+ padding: calc(var(--spacing) * 2) calc(var(--spacing) * 4);
text-align: center;
touch-action: manipulation;
cursor: pointer;
@@ -17,7 +18,7 @@
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
- border-radius: 0.5rem;
+ border-radius: calc(var(--spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: background-color, outline-offset, scale;
@@ -47,7 +48,7 @@
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
- border-radius: 1rem;
+ border-radius: calc(var(--spacing) * 4);
corner-shape: squircle;
}
}
@@ -76,7 +77,6 @@
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
- width: 2.375rem;
aspect-ratio: 1;
padding: 0;
@@ -104,7 +104,7 @@
position: absolute;
right: -1px;
bottom: -3px;
- font-size: 10px; /* Hard coded due to size limitations. */
+ font-size: 0.715em; /* 10px */
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: -0.05em;
@@ -157,12 +157,12 @@
text content. */
.media-minimal-skin .media-button--live {
display: inline-flex;
- gap: 0.4rem;
+ gap: calc(var(--spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
- padding: 0.5rem 0.75rem;
- font-size: 0.75rem;
+ padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
+ font-size: var(--font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
@@ -171,8 +171,8 @@
&::before {
display: inline-block;
flex-shrink: 0;
- width: 0.5rem;
- height: 0.5rem;
+ width: calc(var(--spacing) * 2);
+ height: calc(var(--spacing) * 2);
content: "";
background-color: oklch(from currentColor l c h / 0.4);
border-radius: 50%;
diff --git a/packages/skins/src/minimal/css/components/captions.css b/packages/skins/src/minimal/css/components/captions.css
index e3e54f48..702eb090 100644
--- a/packages/skins/src/minimal/css/components/captions.css
+++ b/packages/skins/src/minimal/css/components/captions.css
@@ -5,15 +5,15 @@
.media-minimal-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
- --media-caption-track-y: -0.5rem;
+ --media-caption-track-y: calc(var(--spacing) * -2);
&:has(.media-controls[data-visible]) {
- --media-caption-track-y: -5rem;
+ --media-caption-track-y: calc(var(--spacing) * -20);
}
@container media-root (width > 42rem) {
&:has(.media-controls[data-visible]) > * {
- --media-caption-track-y: -3rem;
+ --media-caption-track-y: calc(var(--spacing) * -12);
}
}
}
diff --git a/packages/skins/src/minimal/css/components/controls.css b/packages/skins/src/minimal/css/components/controls.css
index 3e5d7e72..2518cfab 100644
--- a/packages/skins/src/minimal/css/components/controls.css
+++ b/packages/skins/src/minimal/css/components/controls.css
@@ -3,8 +3,14 @@
========================================================================== */
.media-minimal-skin .media-controls {
+ --media-popover-side-offset: calc(var(--spacing) * (var(--base-side-offset, 2) + 1));
+ --media-tooltip-side-offset: var(--media-popover-side-offset);
+ --media-popover-boundary-offset: calc(var(--spacing) * (var(--base-boundary-offset, 0) + 1));
+ --media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
+
display: flex;
align-items: center;
+ padding: calc(var(--spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
background-color: var(--media-controls-background-color);
diff --git a/packages/skins/src/minimal/css/components/error.css b/packages/skins/src/minimal/css/components/error.css
index f54389b9..e4de8eb3 100644
--- a/packages/skins/src/minimal/css/components/error.css
+++ b/packages/skins/src/minimal/css/components/error.css
@@ -18,7 +18,7 @@
.media-minimal-skin .media-error__actions {
display: flex;
- gap: 0.5rem;
+ gap: calc(var(--spacing) * 2);
& > * {
flex: 1;
diff --git a/packages/skins/src/minimal/css/components/input-feedback.css b/packages/skins/src/minimal/css/components/input-feedback.css
index b0a6a4fe..b97fbff0 100644
--- a/packages/skins/src/minimal/css/components/input-feedback.css
+++ b/packages/skins/src/minimal/css/components/input-feedback.css
@@ -6,7 +6,7 @@
position: absolute;
inset-inline: 0;
top: 0;
- bottom: 3.5rem; /* Shift up a little in smaller containers */
+ bottom: calc(var(--spacing) * 14); /* Shift up a little in smaller containers */
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
@@ -29,22 +29,27 @@
top: 0;
display: flex;
justify-content: center;
- padding-top: 0.75rem;
- padding-bottom: 8rem;
+ padding-top: calc(var(--spacing) * 3);
+ padding-bottom: calc(var(--spacing) * 32);
color: inherit;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
pointer-events: none;
- background-image: linear-gradient(to bottom, oklch(0 0 0 / 0.35), oklch(0 0 0 / 0.2) 3rem, oklch(0 0 0 / 0));
+ background-image: linear-gradient(
+ to bottom,
+ oklch(0 0 0 / 0.35),
+ oklch(0 0 0 / 0.2) calc(var(--spacing) * 12),
+ oklch(0 0 0 / 0)
+ );
transform-origin: top center;
transition-timing-function: ease-out;
transition-duration: 100ms;
.media-input-feedback-island__content {
display: flex;
- gap: 0.5rem;
+ gap: calc(var(--spacing) * 2);
align-items: center;
justify-content: space-between;
- padding: 0.25rem 0.625rem;
+ padding: calc(var(--spacing) * 1) calc(var(--spacing) * 2.5);
}
.media-icon {
@@ -74,7 +79,7 @@
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
.media-input-feedback-island__content {
background: var(--media-controls-background-color);
- border-radius: 0.5rem;
+ border-radius: calc(var(--spacing) * 2);
}
}
@@ -99,13 +104,13 @@
.media-minimal-skin .media-input-feedback-island--volume {
.media-input-feedback-island__content {
- width: min(80%, 14rem);
+ width: min(80%, calc(var(--spacing) * 56));
}
.media-input-feedback-island__progress {
--media-progress-fill: var(--media-volume-fill);
width: 100%;
- height: 0.1875rem;
+ height: calc(var(--spacing) * 0.75);
background-image: linear-gradient(
to right,
currentColor 0%,
@@ -149,7 +154,7 @@
grid-row: 1;
grid-column: 2;
place-content: center;
- padding: 1rem;
+ padding: calc(var(--spacing) * 4);
text-align: center;
/* Central bubble (play, pause) */
@@ -177,10 +182,10 @@
/* Directional bubbles (seek) */
&[data-direction] {
- gap: 0.25rem;
+ gap: calc(var(--spacing) * 1);
@container media-root (width > 24rem) {
- padding: 1.5rem;
+ padding: calc(var(--spacing) * 6);
}
}
diff --git a/packages/skins/src/minimal/css/components/menus.css b/packages/skins/src/minimal/css/components/menus.css
index 1288af42..bf6e5464 100644
--- a/packages/skins/src/minimal/css/components/menus.css
+++ b/packages/skins/src/minimal/css/components/menus.css
@@ -5,11 +5,12 @@
.media-minimal-skin .media-menu {
--menu-transition-duration: 250ms;
- --menu-max-height: 14rem;
- --menu-padding: 0.5rem;
- --menu-border-radius: 1rem;
+ --menu-max-height: calc(var(--spacing) * 56);
+ --menu-padding: calc(var(--spacing) * 1);
+ --menu-border-radius: calc(var(--spacing) * 2.5);
--menu-item-border-radius: calc(var(--menu-border-radius) - var(--menu-padding));
box-sizing: border-box;
+ min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--menu-max-height)), var(--menu-max-height));
padding: var(--menu-padding);
@@ -84,14 +85,14 @@
}
& .media-menu__separator {
- margin: 0.25rem 0;
+ margin: calc(var(--spacing) * 1) 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
& .media-menu__group {
display: flex;
flex-direction: column;
- gap: 0.125rem;
+ gap: calc(var(--spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
@@ -110,9 +111,9 @@
& .media-menu__item,
& .media-menu__back {
display: flex;
- gap: 0.375rem;
+ gap: calc(var(--spacing) * 1.5);
align-items: center;
- padding: 0.375rem 0.5rem;
+ padding: calc(var(--spacing) * 1.5) calc(var(--spacing) * 2);
text-align: left;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
cursor: pointer;
@@ -132,11 +133,6 @@
&:hover,
&[data-highlighted] {
background-color: oklch(from currentColor l c h / 0.1);
-
- @supports (top: anchor(top)) {
- anchor-name: --media-menu-item-highlight-anchor;
- background-color: transparent;
- }
}
& .media-icon {
@@ -148,7 +144,7 @@
& .media-menu__indicator {
flex-shrink: 0;
- margin-right: -0.25rem;
+ margin-right: calc(var(--spacing) * -1);
margin-left: auto;
opacity: 0;
}
@@ -172,12 +168,20 @@
&[data-availability="unsupported"] {
display: none;
}
+
+ &:hover,
+ &[data-highlighted] {
+ @supports (top: anchor(top)) {
+ anchor-name: --media-menu-item-highlight-anchor;
+ background-color: transparent;
+ }
+ }
}
& .media-menu__tier {
padding-top: 1px;
- padding-left: 0.125rem;
- font-size: 0.5625rem;
+ padding-left: calc(var(--spacing) * 0.5);
+ font-size: var(--font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
@@ -185,36 +189,35 @@
& .media-menu__back {
width: 100%;
- margin-bottom: 0.125rem;
+ margin-bottom: calc(var(--spacing) * 0.5);
}
& .media-menu__hint {
display: inline-flex;
- gap: 0.25rem;
+ gap: calc(var(--spacing) * 1);
align-items: center;
min-width: 0;
- padding-left: 0.5rem;
+ padding-left: calc(var(--spacing) * 2);
margin-left: auto;
- font-size: 0.75rem;
color: oklch(from currentColor l c h / 0.65);
}
& .media-menu__hint-label {
- max-width: 6rem;
+ max-width: calc(var(--spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .media-menu__chevron {
- width: 0.875rem;
- height: 0.875rem;
+ width: calc(var(--spacing) * 3.5);
+ height: calc(var(--spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
- min-width: 12rem;
+ min-width: calc(var(--spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Add height and width transitions. */
diff --git a/packages/skins/src/minimal/css/components/overlay.css b/packages/skins/src/minimal/css/components/overlay.css
index b04dc487..e3247114 100644
--- a/packages/skins/src/minimal/css/components/overlay.css
+++ b/packages/skins/src/minimal/css/components/overlay.css
@@ -6,7 +6,12 @@
position: absolute;
inset: 0;
pointer-events: none;
- 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));
+ background-image: linear-gradient(
+ to top,
+ oklch(0 0 0 / 0.7),
+ oklch(0 0 0 / 0.5) calc(var(--spacing) * 30),
+ oklch(0 0 0 / 0)
+ );
border-radius: inherit;
opacity: 0;
backdrop-filter: blur(0) saturate(1);
diff --git a/packages/skins/src/minimal/css/components/popup.css b/packages/skins/src/minimal/css/components/popup.css
index 085912e7..c450a8cd 100644
--- a/packages/skins/src/minimal/css/components/popup.css
+++ b/packages/skins/src/minimal/css/components/popup.css
@@ -12,7 +12,7 @@
.media-minimal-skin .media-popover,
.media-minimal-skin .media-tooltip {
- --popup-translate-distance: 0.5rem;
+ --popup-translate-distance: calc(var(--spacing) * 2);
margin: 0;
overflow: visible;
color: inherit;
@@ -22,13 +22,13 @@
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
- filter: blur(4px);
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--popup-translate-x-distance, 0), var(--popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
+ filter: blur(4px);
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
@@ -95,12 +95,12 @@
}
.media-minimal-skin .media-tooltip {
- padding: 0.25rem 0.5rem;
- font-size: 0.75rem; /* 12px at 100% font size */
+ padding: calc(var(--spacing) * 1) calc(var(--spacing) * 2);
+ font-size: var(--font-size-base);
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
- border-radius: 0.5rem;
+ border-radius: calc(var(--spacing) * 2);
box-shadow:
0 0 0 1px var(--media-tooltip-border-color),
0 4px 6px -1px oklch(0 0 0 / 0.2),
@@ -110,7 +110,7 @@
/* `display: flex` must not apply while closed — it overrides UA `[popover]` hiding. */
&[data-open] {
display: flex;
- column-gap: 0.25rem;
+ column-gap: calc(var(--spacing) * 1);
align-items: center;
}
@@ -126,14 +126,14 @@
& .media-tooltip__kbd {
min-width: 1.5em;
padding: 0.1em;
- margin-right: -0.25rem;
+ margin-right: calc(var(--spacing) * -1);
font-family: inherit;
- font-size: 90%;
+ font-size: var(--font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
- border-radius: 0.25rem;
+ border-radius: calc(var(--spacing) * 1);
}
}
diff --git a/packages/skins/src/minimal/css/components/root.css b/packages/skins/src/minimal/css/components/root.css
index 22d703ed..8d33fec4 100644
--- a/packages/skins/src/minimal/css/components/root.css
+++ b/packages/skins/src/minimal/css/components/root.css
@@ -3,10 +3,23 @@
========================================================================== */
.media-minimal-skin {
+ /* Colors */
--media-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-current-shadow-color-subtle: oklch(from var(--media-current-shadow-color) l c h / calc(alpha * 0.4));
- --media-icon-size: 18px;
- --media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.3);
+ --scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
+ /* Scaling (used in video fullscreen) */
+ --scale: 1;
+ /* What value and unit to use - 1rem assumes the document font size remains at the default 16px */
+ --base-size: 1rem;
+ --size: calc(var(--base-size) * var(--scale));
+ /* Create a Tailwind-like spacing unit. */
+ --spacing: calc(var(--size) / 4);
+ --font-size-medium: calc(0.9375 * var(--size)); /* 15px */
+ --font-size-base: calc(0.8125 * var(--size)); /* 13px */
+ --font-size-small: calc(0.6875 * var(--size)); /* 11px */
+ --font-size-tiny: calc(0.5625 * var(--size)); /* 9px */
+ --media-icon-size: calc(1.125 * var(--size)); /* 18px */
+
position: relative;
display: block;
width: 100%;
@@ -18,14 +31,14 @@
ui-sans-serif,
system-ui,
sans-serif;
- font-size: 0.8125rem; /* 13px at 100% font size */
+ font-size: var(--font-size-base);
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
line-height: 1.5;
letter-spacing: normal;
outline: 2px solid transparent;
outline-offset: -4px;
- scrollbar-color: var(--media-color-scrollbar-thumb) transparent;
+ scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-border-radius, 0.75rem);
isolation: isolate;
@@ -39,12 +52,12 @@
}
&::-webkit-scrollbar-thumb {
- background: var(--media-color-scrollbar-thumb);
+ background: var(--scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
- --media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.8);
+ --scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
diff --git a/packages/skins/src/minimal/css/components/slider.css b/packages/skins/src/minimal/css/components/slider.css
index a8639a52..380a638c 100644
--- a/packages/skins/src/minimal/css/components/slider.css
+++ b/packages/skins/src/minimal/css/components/slider.css
@@ -10,196 +10,186 @@
justify-content: center;
cursor: pointer;
outline: none;
- border-radius: calc(infinity * 1px);
+ border-radius: calc(Infinity * 1px);
&[data-orientation="horizontal"] {
- width: 100%;
- min-width: 5rem;
- height: 2rem;
+ width: var(--slider-width, 100%);
+ min-width: calc(var(--spacing) * 20);
+ height: var(--slider-height, calc(var(--spacing) * 8));
}
-
&[data-orientation="vertical"] {
- width: 2rem;
- height: 4.5rem;
- }
-}
-
-/* Track */
-.media-minimal-skin .media-slider__track {
- position: relative;
- overflow: hidden;
- user-select: none;
- background-color: oklch(from currentColor l c h / 0.2);
- border-radius: inherit;
- isolation: isolate;
-
- &[data-orientation="horizontal"] {
- width: 100%;
- height: 0.1875rem;
+ width: var(--slider-width, calc(var(--spacing) * 8));
+ height: var(--slider-height, calc(var(--spacing) * 20));
}
- &[data-orientation="vertical"] {
- width: 0.1875rem;
- height: 100%;
- }
-}
+ /* Track */
+ & .media-slider__track {
+ position: relative;
+ overflow: hidden;
+ user-select: none;
+ background-color: oklch(from currentColor l c h / 0.2);
+ border-radius: inherit;
+ isolation: isolate;
-/* Thumb */
-.media-minimal-skin .media-slider__thumb {
- position: absolute;
- z-index: 10;
- width: 0.75rem;
- height: 0.75rem;
- user-select: none;
- outline: 2px solid transparent;
- outline-offset: -2px;
- background-color: currentColor;
- border-radius: calc(infinity * 1px);
- box-shadow:
- 0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
- 0 1px 3px 0 oklch(0 0 0 / 0.15),
- 0 1px 2px -1px oklch(0 0 0 / 0.15);
- opacity: 0;
- transform-origin: center;
- scale: 0.7;
- translate: -50% -50%;
- transition-timing-function: ease-out;
- transition-duration: 150ms;
- transition-property: opacity, scale, outline-offset;
-
- &[data-orientation="horizontal"] {
- top: 50%;
- left: var(--media-slider-fill);
+ &[data-orientation="horizontal"] {
+ width: 100%;
+ height: calc(var(--spacing) * 0.75);
+ }
+ &[data-orientation="vertical"] {
+ width: calc(var(--spacing) * 0.75);
+ height: 100%;
+ }
}
- &[data-orientation="vertical"] {
- top: calc(100% - var(--media-slider-fill));
- left: 50%;
- }
-
- &:focus-visible {
- outline-color: currentColor;
- outline-offset: 2px;
- }
-}
-
-.media-minimal-skin .media-slider:hover .media-slider__thumb,
-.media-minimal-skin .media-slider:focus-within .media-slider__thumb,
-.media-minimal-skin .media-slider__thumb--persistent {
- opacity: 1;
- scale: 1;
-}
-
-/* Preview */
-.media-minimal-skin .media-slider__preview {
- & .media-slider__value,
- &::before {
- opacity: 0;
- scale: 0.5;
- transition-timing-function: ease-out;
- transition-duration: 200ms;
- transition-property: opacity, scale;
- }
-
- & .media-slider__value {
+ /* Thumb */
+ & .media-slider__thumb {
position: absolute;
- bottom: 1.5rem;
- text-shadow: 0 1px 0 var(--media-current-shadow-color);
- filter: blur(8px);
- translate: -50% 0.5rem;
- transition-property: filter, opacity, scale, translate;
+ z-index: 10;
+ width: calc(var(--spacing) * 3);
+ height: calc(var(--spacing) * 3);
+ user-select: none;
+ outline: 2px solid transparent;
+ outline-offset: -2px;
+ background-color: currentColor;
+ border-radius: calc(Infinity * 1px);
+ box-shadow:
+ 0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
+ 0 1px 3px 0 oklch(0 0 0 / 0.15),
+ 0 1px 2px -1px oklch(0 0 0 / 0.15);
+ opacity: 0;
+ transform-origin: center;
+ scale: 0.7;
+ translate: -50% -50%;
+ transition-timing-function: ease-out;
+ transition-duration: 150ms;
+ transition-property: opacity, scale, outline-offset;
+
+ &[data-orientation="horizontal"] {
+ top: 50%;
+ left: var(--media-slider-fill);
+ }
+ &[data-orientation="vertical"] {
+ top: calc(100% - var(--media-slider-fill));
+ left: 50%;
+ }
+
+ &:focus-visible {
+ outline-color: currentColor;
+ outline-offset: 2px;
+ }
}
- &::before {
- display: block;
- content: "";
- background-color: oklch(from currentColor l c h / 0.35);
- }
-
- &[data-pointing] .media-slider__value,
- &[data-pointing]:not([data-dragging])::before {
+ &:hover .media-slider__thumb,
+ &:focus-within .media-slider__thumb,
+ & .media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
- &[data-pointing] .media-slider__value {
- filter: blur(0);
- translate: -50% 0;
+
+ /* Preview */
+ & .media-slider__preview {
+ & .media-slider__value,
+ &::before {
+ opacity: 0;
+ scale: 0.5;
+ transition-timing-function: ease-out;
+ transition-duration: 200ms;
+ transition-property: opacity, scale;
+ }
+
+ & .media-slider__value {
+ position: absolute;
+ bottom: calc(var(--spacing) * 6);
+ text-shadow: 0 1px 0 var(--media-current-shadow-color);
+ filter: blur(8px);
+ translate: -50% calc(var(--spacing) * 2);
+ transition-property: filter, opacity, scale, translate;
+ }
+
+ &::before {
+ display: block;
+ content: "";
+ background-color: oklch(from currentColor l c h / 0.35);
+ }
+
+ &[data-pointing] .media-slider__value,
+ &[data-pointing]:not([data-dragging])::before {
+ opacity: 1;
+ scale: 1;
+ }
+ &[data-pointing] .media-slider__value {
+ filter: blur(0);
+ translate: -50% 0;
+ }
+
+ &[data-orientation="horizontal"]::before {
+ min-width: 1px;
+ height: calc(var(--spacing) * 5);
+ }
+ &[data-orientation="vertical"]::before {
+ width: calc(var(--spacing) * 5);
+ min-height: 1px;
+ }
}
- &[data-orientation="horizontal"]::before {
- min-width: 1px;
- height: 1.25rem;
+ /* Shared track fills */
+ & .media-slider__buffer,
+ & .media-slider__fill {
+ position: absolute;
+ pointer-events: none;
+ border-radius: inherit;
+
+ &[data-orientation="horizontal"] {
+ inset-block: 0;
+ left: 0;
+ }
+ &[data-orientation="vertical"] {
+ inset-inline: 0;
+ bottom: 0;
+ }
}
- &[data-orientation="vertical"]::before {
- width: 1.25rem;
- min-height: 1px;
- }
-}
+ /* Buffer */
+ & .media-slider__buffer {
+ background-color: oklch(from currentColor l c h / 0.2);
+ transition-timing-function: ease-out;
+ transition-duration: 0.25s;
-/* Shared track fills */
-.media-minimal-skin .media-slider__buffer,
-.media-minimal-skin .media-slider__fill {
- position: absolute;
- pointer-events: none;
- border-radius: inherit;
-}
-
-.media-minimal-skin .media-slider__buffer[data-orientation="horizontal"],
-.media-minimal-skin .media-slider__fill[data-orientation="horizontal"] {
- inset-block: 0;
- left: 0;
-}
-
-.media-minimal-skin .media-slider__buffer[data-orientation="vertical"],
-.media-minimal-skin .media-slider__fill[data-orientation="vertical"] {
- inset-inline: 0;
- bottom: 0;
-}
-
-/* Buffer */
-.media-minimal-skin .media-slider__buffer {
- background-color: oklch(from currentColor l c h / 0.2);
- transition-timing-function: ease-out;
- transition-duration: 0.25s;
-
- &[data-orientation="horizontal"] {
- width: var(--media-slider-buffer);
- transition-property: width;
+ &[data-orientation="horizontal"] {
+ width: var(--media-slider-buffer);
+ transition-property: width;
+ }
+ &[data-orientation="vertical"] {
+ height: var(--media-slider-buffer);
+ transition-property: height;
+ }
}
- &[data-orientation="vertical"] {
- height: var(--media-slider-buffer);
- transition-property: height;
- }
-}
+ /* Fill */
+ & .media-slider__fill {
+ background-color: currentColor;
-/* Fill */
-.media-minimal-skin .media-slider__fill {
- background-color: currentColor;
-
- &[data-orientation="horizontal"] {
- width: var(--media-slider-fill);
+ &[data-orientation="horizontal"] {
+ width: var(--media-slider-fill);
+ }
+ &[data-orientation="vertical"] {
+ height: var(--media-slider-fill);
+ }
}
- &[data-orientation="vertical"] {
- height: var(--media-slider-fill);
+ /* Dragging — thumb and fill follow the pointer position */
+ &[data-dragging] {
+ & .media-slider__thumb[data-orientation="horizontal"] {
+ left: var(--media-slider-pointer);
+ }
+ & .media-slider__thumb[data-orientation="vertical"] {
+ top: calc(100% - var(--media-slider-pointer));
+ }
+ & .media-slider__fill[data-orientation="horizontal"] {
+ width: var(--media-slider-pointer);
+ }
+ &.media-slider__fill[data-orientation="vertical"] {
+ height: var(--media-slider-pointer);
+ }
}
}
-
-/* Dragging — thumb and fill follow the pointer position */
-.media-minimal-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="horizontal"] {
- left: var(--media-slider-pointer);
-}
-
-.media-minimal-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="vertical"] {
- top: calc(100% - var(--media-slider-pointer));
-}
-
-.media-minimal-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="horizontal"] {
- width: var(--media-slider-pointer);
-}
-
-.media-minimal-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="vertical"] {
- height: var(--media-slider-pointer);
-}
diff --git a/packages/skins/src/minimal/css/components/thumbnail.css b/packages/skins/src/minimal/css/components/thumbnail.css
index d53fbb14..6f06aa99 100644
--- a/packages/skins/src/minimal/css/components/thumbnail.css
+++ b/packages/skins/src/minimal/css/components/thumbnail.css
@@ -8,7 +8,7 @@
& .media-thumbnail__image-wrapper {
position: relative;
background-color: oklch(0 0 0 / 0.9);
- border-radius: 0.5rem;
+ border-radius: calc(var(--spacing) * 2);
}
& .media-thumbnail__image {
display: block;
@@ -17,7 +17,7 @@
& .media-thumbnail__time {
display: block;
- margin-top: 0.5rem;
+ margin-top: calc(var(--spacing) * 2);
text-align: center;
}
diff --git a/packages/skins/src/minimal/css/components/time.css b/packages/skins/src/minimal/css/components/time.css
index 804aac4f..cbd11899 100644
--- a/packages/skins/src/minimal/css/components/time.css
+++ b/packages/skins/src/minimal/css/components/time.css
@@ -6,14 +6,14 @@
display: flex;
flex: 1;
flex-direction: row-reverse;
- gap: 0.75rem;
+ gap: calc(var(--spacing) * 3);
align-items: center;
container: media-time-controls / inline-size;
}
.media-minimal-skin .media-time-group {
display: flex;
- gap: 0.25rem;
+ gap: calc(var(--spacing) * 1);
align-items: center;
}
@@ -25,13 +25,13 @@
cursor: pointer;
outline: 2px solid transparent;
outline-offset: -2px;
- border-radius: 0.25rem;
+ border-radius: calc(var(--spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
- border-radius: 1rem;
+ border-radius: calc(var(--spacing) * 4);
corner-shape: squircle;
}
diff --git a/packages/skins/src/minimal/css/video.css b/packages/skins/src/minimal/css/video.css
index b6aa65ab..28ad22be 100644
--- a/packages/skins/src/minimal/css/video.css
+++ b/packages/skins/src/minimal/css/video.css
@@ -28,25 +28,28 @@
.media-minimal-skin--video {
--media-border-color: oklch(0 0 0 / 0.15);
--media-video-border-radius: var(--media-border-radius, 0.75rem);
+
--media-controls-background-color: transparent;
--media-controls-transition-duration: 100ms;
--media-controls-transition-timing-function: ease-out;
+
--media-error-dialog-transition-duration: 150ms;
--media-error-dialog-transition-delay: 100ms;
--media-error-dialog-transition-timing-function: ease-out;
+
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
- --media-tooltip-background-color: oklch(0 0 0 / 0.5);
- --media-tooltip-border-color: oklch(1 0 0 / 0.1);
- --media-tooltip-backdrop-filter: blur(16px) saturate(1.5);
+
+ --media-popover-backdrop-filter: blur(16px) saturate(1.5);
+ --media-popover-background-color: oklch(0 0 0 / 0.5);
+ --media-popover-border-color: oklch(1 0 0 / 0.1);
+
+ --media-tooltip-backdrop-filter: var(--media-popover-backdrop-filter);
+ --media-tooltip-background-color: var(--media-popover-background-color);
+ --media-tooltip-border-color: var(--media-popover-border-color);
+
--media-tooltip-text-color: currentColor;
- --media-tooltip-side-offset: 0.5rem;
- --media-tooltip-boundary-offset: 0.5rem;
- --media-popover-background-color: var(--media-tooltip-background-color);
- --media-popover-border-color: var(--media-tooltip-border-color);
- --media-popover-backdrop-filter: var(--media-tooltip-backdrop-filter);
- --media-popover-side-offset: 1.5rem;
- --media-popover-boundary-offset: var(--media-tooltip-boundary-offset);
+
overflow: clip;
background: oklch(0 0 0);
@@ -65,12 +68,6 @@
--media-tooltip-background-color: oklch(0 0 0);
}
- @container media-root (width > 42rem) {
- & > * {
- --media-popover-side-offset: 0.5rem;
- }
- }
-
&:has(.media-controls:not([data-visible])) {
/* Slight delay to hide controls on non-touch devices after interaction */
@media (pointer: fine) {
@@ -95,9 +92,32 @@
box-shadow: inset 0 0 0 1px var(--media-border-color);
}
- /* Fullscreen */
&:fullscreen {
--media-border-radius: 0;
+
+ &::after {
+ display: none;
+ }
+
+ .media-controls {
+ max-width: 1024px;
+ margin-inline: auto;
+ }
+
+ @media (width > 1536px) {
+ --scale: 1.25;
+
+ .media-controls {
+ max-width: 1152px;
+ }
+ }
+ @media (width > 1920px) {
+ --scale: 1.5;
+
+ .media-controls {
+ max-width: 1280px;
+ }
+ }
}
}
@@ -119,9 +139,9 @@
.media-minimal-skin--video .media-error__dialog {
display: flex;
flex-direction: column;
- gap: 0.75rem;
- max-width: 16rem;
- padding: 1rem;
+ gap: calc(var(--spacing) * 3);
+ max-width: calc(var(--spacing) * 64);
+ padding: calc(var(--spacing) * 4);
color: oklch(1 0 0);
text-shadow: 0 1px 0 oklch(0 0 0 / 0.5);
pointer-events: auto;
@@ -143,12 +163,12 @@
.media-minimal-skin--video .media-error__content {
display: flex;
flex-direction: column;
- gap: 0.5rem;
- padding: 0.375rem 0;
+ gap: calc(var(--spacing) * 2);
+ padding: calc(var(--spacing) * 1.5) 0;
}
.media-minimal-skin--video .media-error__title {
- font-size: 1.125rem;
+ font-size: var(--font-size-medium);
}
.media-minimal-skin--video .media-error[data-open] ~ .media-controls {
@@ -160,17 +180,22 @@
========================================================================== */
.media-minimal-skin--video .media-controls {
+ --base-side-offset: 7;
+ --base-boundary-offset: 1;
+ --inset-factor: 1;
+ --inset: calc(var(--spacing) * var(--inset-factor));
+
position: absolute;
- inset-inline: 0.25rem;
- bottom: 0.25rem;
+ inset-inline: var(--inset);
+ bottom: var(--inset);
z-index: 10;
flex-wrap: wrap;
- column-gap: 0.5rem;
- padding: 0.25rem;
+ column-gap: calc(var(--spacing) * 2);
color: oklch(1 0 0);
- border-radius: 0.75rem;
+ border-radius: calc(var(--spacing) * 3);
transition-timing-function: var(--media-controls-transition-timing-function);
- transition-duration: var(--media-controls-transition-duration);
+ /* Speed up the entry transition */
+ transition-duration: calc(var(--media-controls-transition-duration) / 2);
@media (pointer: fine) {
transition-property: translate, filter, opacity;
@@ -185,6 +210,7 @@
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
+ transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
@@ -196,9 +222,11 @@
}
& .media-time-controls {
+ --slider-height: calc(var(--spacing) * 5);
+
flex: 0 0 100%;
order: -1;
- padding-inline: 0.625rem;
+ padding-inline: calc(var(--spacing) * 1.5);
}
& .media-button-group:first-child {
@@ -212,11 +240,14 @@
}
@container media-root (width > 42rem) {
- inset-inline: 0.5rem;
- bottom: 0.5rem;
+ --inset-factor: 2;
+ --base-side-offset: 2;
+
flex-wrap: nowrap;
& .media-time-controls {
+ --slider-height: calc(var(--spacing) * 8);
+
flex: 1;
order: unset;
}
@@ -225,6 +256,20 @@
& .media-button-group:last-child {
flex: 0 0 auto;
}
+
+ & .media-time-controls {
+ mask-position: 100% 0;
+ mask-size: 200% 100%;
+ transition: mask-position 50ms ease-out;
+ }
+
+ &:has(.media-button--mute[aria-expanded="true"]) .media-time-controls {
+ mask-image: linear-gradient(to right, transparent 10%, #000 25%, #000 100%);
+ }
+
+ &:has(.media-button--mute[aria-expanded="true"]) .media-time-controls {
+ mask-position: 0 0;
+ }
}
}
@@ -246,19 +291,8 @@
========================================================================== */
.media-minimal-skin--video .media-popover--volume {
- padding-block: 0.75rem;
+ padding: 0;
background: transparent;
- border-radius: 0.75rem;
-
- @media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
- background: var(--media-controls-background-color);
- }
-
- @container media-root (width > 42rem) {
- & > * {
- --media-popover-side-offset: 0rem;
- }
- }
}
/* ==========================================================================
@@ -266,27 +300,21 @@
========================================================================== */
.media-minimal-skin--video .media-slider__thumbnail {
- --media-slider-thumbnail-max-width: 11rem;
- --media-slider-thumbnail-max-height: 8rem;
- --media-slider-thumbnail-padding: -0.5rem;
+ --max-width: calc(var(--spacing) * 44);
+ --max-height: calc(var(--spacing) * 32);
+ --padding: calc(var(--spacing) * -2);
/**
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-slider-thumbnail-inset: calc(100cqi - 100%);
+ --thumbnail-inset: calc(100cqi - 100%);
position: absolute;
bottom: 100%;
left: clamp(
- calc(var(--media-slider-thumbnail-max-width) / 2 + var(--media-slider-thumbnail-padding)),
+ calc(var(--max-width) / 2 + var(--padding)),
var(--media-slider-pointer),
- calc(
- 100% -
- var(--media-slider-thumbnail-max-width) /
- 2 -
- var(--media-slider-thumbnail-padding) +
- var(--media-slider-thumbnail-inset)
- )
+ calc(100% - var(--max-width) / 2 - var(--padding) + var(--thumbnail-inset))
);
opacity: 0;
filter: blur(8px);
@@ -317,8 +345,8 @@
}
& .media-thumbnail__image {
- max-width: var(--media-slider-thumbnail-max-width);
- max-height: var(--media-slider-thumbnail-max-height);
+ max-width: var(--max-width);
+ max-height: var(--max-height);
}
}
.media-minimal-skin--video .media-slider[data-pointing] .media-slider__thumbnail:has([role="img"]:not([data-hidden])) {
diff --git a/packages/skins/src/minimal/tailwind/audio.tailwind.ts b/packages/skins/src/minimal/tailwind/audio.tailwind.ts
index f9389748..87ec0527 100644
--- a/packages/skins/src/minimal/tailwind/audio.tailwind.ts
+++ b/packages/skins/src/minimal/tailwind/audio.tailwind.ts
@@ -19,17 +19,13 @@ export const root = cn(
'[--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)]',
- '[--media-tooltip-border-color:oklch(0_0_0/0.1)]',
- '[--media-tooltip-backdrop-filter:blur(16px)_saturate(1.5)]',
+ '[--media-popover-backdrop-filter:blur(16px)_saturate(1.5)]',
+ '[--media-popover-background-color:oklch(1_0_0)]',
+ '[--media-popover-border-color:oklch(0_0_0/0.1)]',
+ '[--media-tooltip-backdrop-filter:var(--media-popover-backdrop-filter)]',
+ '[--media-tooltip-background-color:var(--media-popover-background-color)]',
+ '[--media-tooltip-border-color:var(--media-popover-border-color)]',
'[--media-tooltip-text-color:currentColor]',
- '[--media-tooltip-side-offset:0.75rem]',
- '[--media-tooltip-boundary-offset:0.75rem]',
- '[--media-popover-background-color:var(--media-tooltip-background-color)]',
- '[--media-popover-border-color:var(--media-tooltip-border-color)]',
- '[--media-popover-backdrop-filter:var(--media-tooltip-backdrop-filter)]',
- '[--media-popover-side-offset:var(--media-tooltip-side-offset)]',
- '[--media-popover-boundary-offset:var(--media-tooltip-boundary-offset)]',
'motion-reduce:[--media-error-dialog-transition-duration:50ms]',
'motion-reduce:[--media-error-dialog-transition-delay:0ms]',
'motion-reduce:[--media-popup-transition-duration:0ms]',
@@ -49,8 +45,9 @@ export const root = cn(
export const controls = cn(
baseControls,
// Layout
- 'p-1.5 gap-2',
- 'rounded-(--media-border-radius,1rem)',
+ 'p-1 gap-2',
+ 'rounded-(--media-border-radius,0.875rem)',
+ '[--base-side-offset:2] [--base-boundary-offset:2]',
'peer-data-open/error:**:invisible',
// Appearance
'text-(--media-controls-text-color)',
@@ -78,7 +75,7 @@ export const popup = {
...basePopup,
volume: cn(
basePopup.popover,
- 'p-0 pl-16',
+ 'p-0 pr-2 pl-16 [--media-popover-side-offset:0rem]',
'bg-transparent bg-gradient-to-l from-(--media-controls-background-color) from-80% to-transparent'
),
};
diff --git a/packages/skins/src/minimal/tailwind/components/badge.ts b/packages/skins/src/minimal/tailwind/components/badge.ts
index 61e059b9..8fae14fe 100644
--- a/packages/skins/src/minimal/tailwind/components/badge.ts
+++ b/packages/skins/src/minimal/tailwind/components/badge.ts
@@ -1,2 +1,2 @@
export const badge =
- 'rounded-full bg-current/10 px-1.5 py-0.5 text-[0.6875rem] font-medium leading-none text-current/70';
+ 'rounded-[--spacing(1)] bg-current/10 px-1.5 py-1 text-(length:--font-size-small) font-medium leading-none text-current/70';
diff --git a/packages/skins/src/minimal/tailwind/components/button-group.ts b/packages/skins/src/minimal/tailwind/components/button-group.ts
index 143d2b02..edc919ab 100644
--- a/packages/skins/src/minimal/tailwind/components/button-group.ts
+++ b/packages/skins/src/minimal/tailwind/components/button-group.ts
@@ -1,3 +1,3 @@
import { cn } from '@videojs/utils/style';
-export const buttonGroup = cn('flex items-center gap-[0.075rem]', '@2xl/media-root:gap-0.5');
+export const buttonGroup = cn('flex items-center [gap:--spacing(0.3)]', '@2xl/media-root:gap-0.5');
diff --git a/packages/skins/src/minimal/tailwind/components/button.ts b/packages/skins/src/minimal/tailwind/components/button.ts
index 64e3c153..f8c19396 100644
--- a/packages/skins/src/minimal/tailwind/components/button.ts
+++ b/packages/skins/src/minimal/tailwind/components/button.ts
@@ -2,14 +2,14 @@ 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 min-h-0',
- 'py-2 px-4 rounded-lg',
+ 'flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 h-[--spacing(9.5)]',
+ 'py-2 px-4 rounded-[--spacing(2)]',
'outline-2 outline-transparent -outline-offset-2',
'transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out',
'focus-visible:outline-current focus-visible:outline-offset-2',
'active:scale-[0.98]',
'disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale',
- 'supports-[corner-shape:squircle]:rounded-[1rem]',
+ 'supports-[corner-shape:squircle]:rounded-[--spacing(4)]',
'supports-[corner-shape:squircle]:[corner-shape:squircle]',
'data-[availability=unavailable]:hidden',
'data-[availability=unsupported]:hidden'
@@ -21,7 +21,7 @@ export const button = {
'focus-visible:bg-current/10',
'aria-expanded:bg-current/10'
),
- icon: cn('grid w-[2.375rem] aspect-square p-0', 'active:scale-90'),
+ icon: cn('grid aspect-square p-0!', 'active:scale-90'),
/**
* Live variant: wide pill button with a status dot rendered via `::before`
* (gray → red at the live edge) and "LIVE" as the button's own text.
@@ -29,7 +29,7 @@ export const button = {
live: cn(
'inline-flex items-center gap-1.5',
'aspect-auto w-auto px-3 py-2',
- 'text-xs font-semibold uppercase tracking-wider leading-none',
+ 'text-(length:--font-size-small) font-semibold uppercase tracking-wider leading-none',
'before:inline-block before:size-2 before:shrink-0 before:rounded-full',
'before:bg-current/40 before:transition-colors before:duration-150 before:ease-out',
'before:content-[""]',
diff --git a/packages/skins/src/minimal/tailwind/components/controls.ts b/packages/skins/src/minimal/tailwind/components/controls.ts
index 070910c4..3439fda0 100644
--- a/packages/skins/src/minimal/tailwind/components/controls.ts
+++ b/packages/skins/src/minimal/tailwind/components/controls.ts
@@ -5,7 +5,11 @@ export const controls = cn(
'peer/controls',
// Layout
'@container/media-controls',
- 'flex items-center',
+ '[--media-popover-side-offset:calc(var(--spacing)*(var(--base-side-offset,0)+var(--controls-padding,1)))]',
+ '[--media-tooltip-side-offset:var(--media-popover-side-offset)]',
+ '[--media-popover-boundary-offset:calc(var(--spacing)*(var(--base-boundary-offset,0)+var(--controls-padding,1)))]',
+ '[--media-tooltip-boundary-offset:var(--media-popover-boundary-offset)]',
+ '[padding:calc(var(--spacing)*var(--controls-padding,1))] flex items-center',
// Appearance (driven by CSS variables set on the root)
'bg-(--media-controls-background-color)',
'[backdrop-filter:var(--media-controls-backdrop-filter)]',
diff --git a/packages/skins/src/minimal/tailwind/components/input-feedback.ts b/packages/skins/src/minimal/tailwind/components/input-feedback.ts
index 4cf2d32a..08846ae2 100644
--- a/packages/skins/src/minimal/tailwind/components/input-feedback.ts
+++ b/packages/skins/src/minimal/tailwind/components/input-feedback.ts
@@ -34,7 +34,7 @@ export const inputFeedback = {
'data-starting-style:ease-in',
'data-ending-style:duration-400',
'data-ending-style:ease-in',
- '[background-image:linear-gradient(to_bottom,oklch(0_0_0/0.35),oklch(0_0_0/0.2)_3rem,oklch(0_0_0/0))]',
+ '[background-image:linear-gradient(to_bottom,oklch(0_0_0/0.35),oklch(0_0_0/0.2)_--spacing(12),oklch(0_0_0/0))]',
'text-shadow-2xs text-shadow-(color:--media-current-shadow-color)',
// Pointer-dependent transition props
'pointer-fine:will-change-[translate,filter,opacity]',
@@ -51,13 +51,13 @@ export const inputFeedback = {
'*:last:ml-auto',
// Reduced transparency / high contrast: solid content background
'[@media(prefers-reduced-transparency:reduce)]:bg-(--media-controls-background-color)',
- '[@media(prefers-reduced-transparency:reduce)]:rounded-lg',
- 'contrast-more:bg-(--media-controls-background-color) contrast-more:rounded-lg'
+ '[@media(prefers-reduced-transparency:reduce)]:rounded-[--spacing(2)]',
+ 'contrast-more:bg-(--media-controls-background-color) contrast-more:rounded-[--spacing(2)]'
),
// Volume island sizing + progress-fill on nested __progress element
volume: cn(
// Content is sized
- '*:data-feedback-island-content:w-[min(80%,14rem)]'
+ '*:data-feedback-island-content:w-[min(80%,--spacing(56))]'
),
// Progress bar (nested inside content) — its own element in the minimal variant
volumeProgress: cn(
diff --git a/packages/skins/src/minimal/tailwind/components/menu.ts b/packages/skins/src/minimal/tailwind/components/menu.ts
index 6a9d9d53..04cb7e7c 100644
--- a/packages/skins/src/minimal/tailwind/components/menu.ts
+++ b/packages/skins/src/minimal/tailwind/components/menu.ts
@@ -42,8 +42,11 @@ const itemBase = cn(
);
const menuTokens = cn(
- '[--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.5rem]',
- '[--menu-border-radius:1rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))]',
+ '[--menu-transition-duration:250ms]',
+ '[--menu-max-height:--spacing(56)]',
+ '[--menu-padding:--spacing(1)]',
+ '[--menu-border-radius:--spacing(2.5)]',
+ '[--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))]',
'motion-reduce:[--menu-transition-duration:0ms]'
);
@@ -64,7 +67,7 @@ const group = cn(
const menuHostShell = cn(
popup.popover,
menuTokens,
- 'max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))]',
+ 'min-w-max max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))]',
'bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)]',
'shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)]',
'box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none'
@@ -79,9 +82,10 @@ export const menu = {
// Add height and width transitions.
'[--media-popup-transition:var(--media-popup-base-transition),height_var(--media-popup-transition-timing-function)_var(--menu-transition-duration),width_var(--media-popup-transition-timing-function)_var(--menu-transition-duration)]',
// Don't transition width and height on open/close.
- 'data-starting-style:[--media-popup-transition:var(--media-popup-base-transition)] data-ending-style:[--media-popup-transition:var(--media-popup-base-transition)]',
- 'min-w-48 w-(--media-menu-width) h-(--media-menu-height)',
- '!overflow-hidden'
+ 'data-starting-style:[--media-popup-transition:var(--media-popup-base-transition)]',
+ 'data-ending-style:[--media-popup-transition:var(--media-popup-base-transition)]',
+ 'min-w-48! w-(--media-menu-width) h-(--media-menu-height)',
+ 'overflow-hidden!'
),
group,
item: cn(
@@ -91,7 +95,7 @@ export const menu = {
'aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50'
),
separator: 'my-1 border-b border-[oklch(1_0_0/0.1)]',
- tier: 'pl-0.5 pt-px text-[0.5625rem] font-semibold leading-none text-current/70',
+ tier: 'pl-0.5 pt-px text-(length:--font-size-tiny) font-semibold leading-none text-current/70',
indicator: 'ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100',
icon: 'shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]',
/** Root settings view — slides out when a submenu is active. */
@@ -99,7 +103,7 @@ export const menu = {
/** Submenu panel — slides in/out alongside the root view. */
submenuPanel,
back: cn(itemBase, 'mb-0.5 w-full'),
- hint: 'ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65',
+ hint: 'ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-current/65',
hintLabel: 'max-w-24 overflow-hidden text-ellipsis whitespace-nowrap',
chevron: 'size-3.5',
settingsGroup: 'group/settings',
diff --git a/packages/skins/src/minimal/tailwind/components/overlay.ts b/packages/skins/src/minimal/tailwind/components/overlay.ts
index 2d23c0b1..6c6e4c23 100644
--- a/packages/skins/src/minimal/tailwind/components/overlay.ts
+++ b/packages/skins/src/minimal/tailwind/components/overlay.ts
@@ -6,7 +6,7 @@ export const overlay = cn(
'pointer-events-none rounded-[inherit]',
// Default: hidden
'opacity-0',
- 'bg-linear-to-t from-black/70 via-black/50 via-[7.5rem] to-transparent',
+ '[background-image:linear-gradient(to_top,oklch(0_0_0/0.7),oklch(0_0_0/0.5)_--spacing(30),oklch(0_0_0/0))]',
'backdrop-blur-none backdrop-saturate-100',
// Transitions
'transition-[opacity,backdrop-filter]',
diff --git a/packages/skins/src/minimal/tailwind/components/popup.ts b/packages/skins/src/minimal/tailwind/components/popup.ts
index 605562e3..11b6d93b 100644
--- a/packages/skins/src/minimal/tailwind/components/popup.ts
+++ b/packages/skins/src/minimal/tailwind/components/popup.ts
@@ -3,11 +3,11 @@ import { cn } from '@videojs/utils/style';
const base = cn(
// Reset default popover styles
'[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)]',
- '[--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible',
+ '[--popup-translate-distance:--spacing(2)] m-0 border-0 text-inherit overflow-visible',
// Animation
'[transition:var(--media-popup-transition,var(--media-popup-base-transition))]',
// We have to use transform here for translate as the translate property is used for positioning by core.
- 'data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))]',
+ 'data-starting-style:opacity-0 data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))]',
'data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none',
// Speed up the exit transition.
'data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))]',
@@ -32,7 +32,7 @@ export const popup = {
),
tooltip: cn(
base,
- 'px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap',
+ 'px-2 py-1 rounded-[--spacing(2)] text-(length:--font-size-base) whitespace-nowrap',
'data-open:flex data-open:items-center data-open:gap-1',
'bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)]',
'ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20',
@@ -41,6 +41,6 @@ export const popup = {
'data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)'
),
tooltipShortcut: cn(
- 'min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded'
+ 'min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-(length:--font-size-small) font-semibold font-[inherit] leading-[1.25] text-center rounded-[--spacing(1)]'
),
};
diff --git a/packages/skins/src/minimal/tailwind/components/root.ts b/packages/skins/src/minimal/tailwind/components/root.ts
index 9e5a2249..5214aa18 100644
--- a/packages/skins/src/minimal/tailwind/components/root.ts
+++ b/packages/skins/src/minimal/tailwind/components/root.ts
@@ -7,7 +7,7 @@ export const root = cn(
'block relative isolate h-full w-full @container/media-root',
// Appearance
'rounded-(--media-border-radius,0.75rem)',
- '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] text-(length:--font-size-base) leading-normal subpixel-antialiased',
// Focus ring
'outline-2 outline-transparent -outline-offset-4',
'transition-[outline-offset,outline-color] duration-100 ease-out',
@@ -19,6 +19,10 @@ export const root = cn(
// Shadow color variables (derived from currentColor lightness)
'[--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))]',
'[--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))]',
- // Icon sizing
- '[--media-icon-size:18px]'
+ // Font and icon sizing
+ '[--scale:1] [--font-size-base:calc(0.8125rem*var(--scale))] [--font-size-small:calc(0.6875rem*var(--scale))] [--font-size-medium:calc(0.9375rem*var(--scale))] [--font-size-tiny:calc(0.5625rem*var(--scale))]',
+ '[--media-icon-size:calc(--spacing(4.5)*var(--scale))]',
+ // Preserve the container's Tailwind spacing value and scale descendants from it.
+ '[--spacing-proxy:var(--spacing)]',
+ '[&>*]:[--spacing:calc(var(--spacing-proxy)*var(--scale))]'
);
diff --git a/packages/skins/src/minimal/tailwind/components/seek.ts b/packages/skins/src/minimal/tailwind/components/seek.ts
index f4b14949..dbfde9f9 100644
--- a/packages/skins/src/minimal/tailwind/components/seek.ts
+++ b/packages/skins/src/minimal/tailwind/components/seek.ts
@@ -1,5 +1,5 @@
export const seek = {
- label: 'text-[10px] font-medium tracking-tighter tabular-nums',
+ label: 'text-[0.715em] font-medium tracking-tighter 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 36e452f4..242c2cf4 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-8',
+ 'data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-(--slider-width,100%) data-[orientation=horizontal]:h-(--slider-height,--spacing(8))',
// Vertical
- 'data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-[4.5rem]'
+ 'data-[orientation=vertical]:w-(--slider-width,--spacing(8)) data-[orientation=vertical]:h-(--slider-height,--spacing(20))'
),
track: cn(
'relative isolate overflow-hidden bg-current/20 rounded-[inherit] select-none',
diff --git a/packages/skins/src/minimal/tailwind/components/thumbnail.ts b/packages/skins/src/minimal/tailwind/components/thumbnail.ts
index 6861cf23..c8c8d229 100644
--- a/packages/skins/src/minimal/tailwind/components/thumbnail.ts
+++ b/packages/skins/src/minimal/tailwind/components/thumbnail.ts
@@ -2,9 +2,9 @@ import { cn } from '@videojs/utils/style';
export const thumbnail = {
root: 'group/thumbnail peer/thumbnail pointer-events-none',
- imageWrapper: 'relative rounded-lg bg-black/90',
+ imageWrapper: 'relative rounded-[--spacing(2)] bg-black/90',
image: cn('block rounded-[inherit] transition-opacity duration-150 ease-out', 'data-loading:opacity-0'),
- time: '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/time.ts b/packages/skins/src/minimal/tailwind/components/time.ts
index f96befd1..dd2847dc 100644
--- a/packages/skins/src/minimal/tailwind/components/time.ts
+++ b/packages/skins/src/minimal/tailwind/components/time.ts
@@ -3,14 +3,14 @@ import { cn } from '@videojs/utils/style';
export const time = {
group: 'flex items-center gap-1',
current: cn(
- 'hidden tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2',
+ 'hidden tabular-nums cursor-pointer rounded-[--spacing(1)] outline-2 outline-transparent -outline-offset-2',
'transition-[outline-color,outline-offset] duration-100 ease-out',
- 'supports-[corner-shape:squircle]:rounded-4',
+ 'supports-[corner-shape:squircle]:rounded-[--spacing(4)]',
'supports-[corner-shape:squircle]:[corner-shape:squircle]',
'focus-visible:outline-current focus-visible:outline-offset-2',
'@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'),
+ controls: cn('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 f01b84a0..f3aac156 100644
--- a/packages/skins/src/minimal/tailwind/video.tailwind.ts
+++ b/packages/skins/src/minimal/tailwind/video.tailwind.ts
@@ -16,9 +16,11 @@ import { time as baseTime } from './components/time';
export const root = (isShadowDOM: boolean) =>
cn(
baseRoot,
+ 'group/skin',
'bg-black overflow-clip',
// Border ring (::after)
'after:absolute after:pointer-events-none after:rounded-[inherit] after:z-10',
+ '[&:fullscreen]:after:hidden',
'after:inset-0 after:ring-1 after:ring-inset after:ring-black/15 dark:after:ring-white/15',
// Video element
{
@@ -36,17 +38,15 @@ export const root = (isShadowDOM: boolean) =>
'[--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(0_0_0/0.5)]',
- '[--media-tooltip-border-color:oklch(1_0_0/0.1)]',
- '[--media-tooltip-backdrop-filter:blur(16px)_saturate(1.5)]',
+ '[--media-popover-backdrop-filter:blur(16px)_saturate(1.5)]',
+ '[--media-popover-background-color:oklch(0_0_0/0.5)]',
+ '[--media-popover-border-color:oklch(1_0_0/0.1)]',
+ '[--media-tooltip-backdrop-filter:var(--media-popover-backdrop-filter)]',
+ '[--media-tooltip-background-color:var(--media-popover-background-color)]',
+ '[--media-tooltip-border-color:var(--media-popover-border-color)]',
'[--media-tooltip-text-color:currentColor]',
- '[--media-tooltip-side-offset:0.5rem]',
- '[--media-tooltip-boundary-offset:0.5rem]',
- '[--media-popover-background-color:var(--media-tooltip-background-color)]',
- '[--media-popover-border-color:var(--media-tooltip-border-color)]',
- '[--media-popover-backdrop-filter:var(--media-tooltip-backdrop-filter)]',
- '[--media-popover-side-offset:1.5rem]',
- '[--media-popover-boundary-offset:var(--media-tooltip-boundary-offset)]',
+ // Fullscreen scale
+ 'min-[1536px]:[&:fullscreen]:[--scale:1.25] min-[1920px]:[&:fullscreen]:[--scale:1.5]',
'motion-reduce:[--media-error-dialog-transition-duration:50ms]',
'motion-reduce:[--media-error-dialog-transition-delay:0ms]',
'motion-reduce:[--media-popup-transition-duration:0ms]',
@@ -54,16 +54,15 @@ export const root = (isShadowDOM: boolean) =>
'contrast-more:[--media-controls-background-color:oklch(0_0_0)]',
'[@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(0_0_0)]',
'contrast-more:[--media-tooltip-background-color:oklch(0_0_0)]',
- '@2xl/media-root:*:[--media-popover-side-offset:0.5rem]',
'pointer-fine:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:300ms]',
'pointer-coarse:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:150ms]',
'motion-reduce:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:50ms]',
// Caption track CSS variables (consumed by the native caption bridge in light DOM)
- '[--media-caption-track-y:-0.5rem]',
+ '[--media-caption-track-y:--spacing(-2)]',
'[--media-caption-track-delay:25ms]',
'[--media-caption-track-duration:var(--media-controls-transition-duration)]',
- 'has-[[data-controls][data-visible]]:[--media-caption-track-y:-5rem]',
- '@2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:-3rem]',
+ 'has-[[data-controls][data-visible]]:[--media-caption-track-y:--spacing(-20)]',
+ '@2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:--spacing(-12)]',
// Native caption track container
!isShadowDOM
? [
@@ -106,11 +105,15 @@ export const controls = cn(
baseControls,
// Position & wrapping layout (small)
'absolute bottom-1 inset-x-1',
- 'p-1 gap-x-2 flex-wrap rounded-xl',
- 'text-white z-10',
+ 'group-[:fullscreen]/skin:mx-auto group-[:fullscreen]/skin:max-w-5xl',
+ 'min-[1536px]:group-[:fullscreen]/skin:max-w-6xl min-[1920px]:group-[:fullscreen]/skin:max-w-7xl',
+ '[--base-side-offset:2] [--base-boundary-offset:1]',
+ 'gap-x-2 flex-wrap rounded-[--spacing(3)] group/controls',
+ 'text-white z-20',
'peer-data-open/error:hidden',
'ease-(--media-controls-transition-timing-function)',
- 'duration-(--media-controls-transition-duration)',
+ 'duration-[calc(var(--media-controls-transition-duration)/2)]',
+ 'not-data-visible:duration-(--media-controls-transition-duration)',
'pointer-fine:will-change-[translate,filter,opacity]',
'pointer-fine:transition-[translate,filter,opacity]',
'pointer-coarse:will-change-[translate,opacity]',
@@ -120,8 +123,7 @@ export const controls = cn(
'motion-safe:not-data-visible:translate-y-full',
'pointer-fine:motion-safe:not-data-visible:blur-sm',
// Single-row layout (large)
- '@2xl/media-root:flex-nowrap @2xl/media-root:bottom-2 @2xl/media-root:inset-x-2',
- '@2xl/media-root:*:[--media-popover-side-offset:0rem]'
+ '@2xl/media-root:flex-nowrap @2xl/media-root:[--controls-padding:2] @2xl/media-root:[--base-side-offset:0]'
);
/* ==========================================================================
@@ -139,8 +141,12 @@ 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]'
+ '[--slider-height:--spacing(5)] grow-0 shrink-0 basis-full order-[-1] px-1.5',
+ '@2xl/media-root:[--slider-height:--spacing(8)] @2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]',
+ '@2xl/media-root:[mask-position:100%_0] @2xl/media-root:[mask-size:200%_100%]',
+ '@2xl/media-root:[transition:mask-position_50ms_ease-out]',
+ 'group-has-[[data-volume-level][aria-expanded=true]]/controls:@2xl/media-root:[mask-image:linear-gradient(to_right,transparent_10%,black_25%,black_100%)]',
+ 'group-has-[[data-volume-level][aria-expanded=true]]/controls:@2xl/media-root:[mask-position:0_0]'
),
};
@@ -151,8 +157,9 @@ export const time = {
export const error = {
...baseError,
root: cn(baseError.root, 'pointer-events-none outline-none'),
- dialog: cn(baseError.dialog, 'pointer-events-auto'),
- title: cn(baseError.title, 'text-lg'),
+ dialog: cn(baseError.dialog, 'pointer-events-auto max-w-64 p-4 rounded-none'),
+ content: cn(baseError.content, 'p-0 py-1.5'),
+ title: 'text-(length:--font-size-medium)',
};
/* ==========================================================================
@@ -163,11 +170,11 @@ export const thumbnail = {
...baseThumbnail,
root: cn(
baseThumbnail.root,
- '[--media-slider-thumbnail-max-width:11rem]',
- '[--media-slider-thumbnail-max-height:8rem]',
- '[--media-slider-thumbnail-padding:-0.5rem]',
- '[--media-slider-thumbnail-inset:calc(100cqi-100%)]',
- 'absolute [left:clamp(calc(var(--media-slider-thumbnail-max-width)/2+var(--media-slider-thumbnail-padding)),var(--media-slider-pointer),calc(100%-var(--media-slider-thumbnail-max-width)/2-var(--media-slider-thumbnail-padding)+var(--media-slider-thumbnail-inset)))] bottom-full -translate-x-1/2',
+ '[--max-width:--spacing(44)]',
+ '[--max-height:--spacing(32)]',
+ '[--padding:--spacing(-2)]',
+ '[--inset:calc(100cqi-100%)]',
+ 'absolute [left:clamp(calc(var(--max-width)/2+var(--padding)),var(--media-slider-pointer),calc(100%-var(--max-width)/2-var(--padding)+var(--inset)))] bottom-full -translate-x-1/2',
'@2xl/media-root:[left:var(--media-slider-pointer)]',
'opacity-0 scale-80 blur-sm origin-bottom',
'transition-[scale,opacity,filter] duration-150',
@@ -180,11 +187,7 @@ export const thumbnail = {
'after:absolute after:inset-0 after:rounded-[inherit]',
'after:ring-1 after:ring-black/5 after:shadow-sm after:shadow-black/20'
),
- image: cn(
- baseThumbnail.image,
- 'max-w-(--media-slider-thumbnail-max-width)',
- 'max-h-(--media-slider-thumbnail-max-height)'
- ),
+ image: cn(baseThumbnail.image, 'max-w-(--max-width)', 'max-h-(--max-height)'),
};
/* ==========================================================================
@@ -202,27 +205,17 @@ export const slider = {
export const popup = {
...basePopup,
- 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)'
- ),
+ volume: cn(basePopup.popover, 'p-0 bg-transparent'),
};
/* ==========================================================================
Menu
========================================================================== */
-const menuOffsets = cn(
- '[--media-popover-side-offset:1.5rem] [--media-popover-boundary-offset:0.5rem]',
- '@2xl/media-root:[--media-popover-side-offset:0.5rem]'
-);
-
export const menu = {
...baseMenu,
- root: cn(baseMenu.root, menuOffsets),
- settings: cn(baseMenu.settings, menuOffsets),
+ root: baseMenu.root,
+ settings: baseMenu.settings,
};
/* ==========================================================================
diff --git a/packages/utils/src/dom/style.ts b/packages/utils/src/dom/style.ts
index 143e10c9..9bac5b4d 100644
--- a/packages/utils/src/dom/style.ts
+++ b/packages/utils/src/dom/style.ts
@@ -17,23 +17,22 @@ export function resolveCSSLength(el: Element, value: string): number {
const parsed = Number.parseFloat(trimmed);
- if (Number.isNaN(parsed)) return 0;
- if (/^-?\d*\.?\d+$/.test(trimmed) || trimmed.endsWith('px')) return parsed;
+ if (!Number.isNaN(parsed) && (/^-?\d*\.?\d+$/.test(trimmed) || trimmed.endsWith('px'))) return parsed;
const doc = el.ownerDocument;
const root = doc?.documentElement;
- if (trimmed.endsWith('rem')) {
+ if (!Number.isNaN(parsed) && trimmed.endsWith('rem')) {
const rootFontSize = root ? Number.parseFloat(getComputedStyle(root).fontSize) || 16 : 16;
return parsed * rootFontSize;
}
- if (trimmed.endsWith('em')) {
+ if (!Number.isNaN(parsed) && trimmed.endsWith('em')) {
const fontSize = el instanceof HTMLElement ? Number.parseFloat(getComputedStyle(el).fontSize) || 16 : 16;
return parsed * fontSize;
}
- if (!doc) return parsed;
+ if (!doc) return Number.isNaN(parsed) ? 0 : parsed;
const measurementEl = doc.createElement('div');
measurementEl.style.position = 'absolute';
@@ -45,14 +44,27 @@ export function resolveCSSLength(el: Element, value: string): number {
measurementEl.style.border = '0';
measurementEl.style.inset = '0';
+ const computed = getComputedStyle(el);
+ measurementEl.style.fontSize = computed.fontSize;
+
+ for (let i = 0; i < computed.length; i++) {
+ const name = computed.item(i);
+
+ if (name.startsWith('--')) {
+ measurementEl.style.setProperty(name, computed.getPropertyValue(name));
+ }
+ }
+
const parent = doc.body ?? doc.documentElement;
- if (!parent) return parsed;
+ if (!parent) return Number.isNaN(parsed) ? 0 : parsed;
parent.appendChild(measurementEl);
const pixels = measurementEl.getBoundingClientRect().width;
measurementEl.remove();
- return Number.isFinite(pixels) ? pixels : parsed;
+ if (Number.isFinite(pixels)) return pixels;
+
+ return Number.isNaN(parsed) ? 0 : parsed;
}
diff --git a/packages/utils/src/dom/tests/style.test.ts b/packages/utils/src/dom/tests/style.test.ts
index 2680b8a3..5ece392c 100644
--- a/packages/utils/src/dom/tests/style.test.ts
+++ b/packages/utils/src/dom/tests/style.test.ts
@@ -1,8 +1,12 @@
-import { describe, expect, it, vi } from 'vitest';
+import { afterEach, describe, expect, it, vi } from 'vitest';
import { resolveCSSLength } from '../style';
describe('resolveCSSLength', () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
it('Returns px values directly', () => {
const el = document.createElement('div');
@@ -98,4 +102,75 @@ describe('resolveCSSLength', () => {
createElementSpy.mockRestore();
});
+
+ it('Falls back to measurement for calc values', () => {
+ const el = document.createElement('div');
+ const createElement = document.createElement.bind(document);
+ const createElementSpy = vi.spyOn(document, 'createElement').mockImplementation((tagName: string) => {
+ const node = createElement(tagName);
+
+ if (tagName === 'div') {
+ vi.spyOn(node, 'getBoundingClientRect').mockImplementation(() => ({
+ x: 0,
+ y: 0,
+ width: node.style.inlineSize.startsWith('calc(') ? 8 : 0,
+ height: 0,
+ top: 0,
+ left: 0,
+ right: 8,
+ bottom: 0,
+ toJSON() {},
+ }));
+ }
+
+ return node;
+ });
+
+ expect(resolveCSSLength(el, 'calc(0.5 * 16px)')).toBe(8);
+
+ createElementSpy.mockRestore();
+ });
+
+ it('Copies custom properties from the source element when measuring', () => {
+ const el = document.createElement('div');
+
+ const createElement = document.createElement.bind(document);
+ const getComputedStyleSpy = vi.spyOn(globalThis, 'getComputedStyle').mockImplementation(
+ () =>
+ ({
+ length: 1,
+ fontSize: '14px',
+ item(index: number) {
+ return index === 0 ? '--size' : '';
+ },
+ getPropertyValue(name: string) {
+ return name === '--size' ? '16px' : '';
+ },
+ }) as CSSStyleDeclaration
+ );
+ const createElementSpy = vi.spyOn(document, 'createElement').mockImplementation((tagName: string) => {
+ const node = createElement(tagName);
+
+ if (tagName === 'div') {
+ vi.spyOn(node, 'getBoundingClientRect').mockImplementation(() => ({
+ x: 0,
+ y: 0,
+ width: node.style.getPropertyValue('--size') === '16px' ? 8 : 0,
+ height: 0,
+ top: 0,
+ left: 0,
+ right: 8,
+ bottom: 0,
+ toJSON() {},
+ }));
+ }
+
+ return node;
+ });
+
+ expect(resolveCSSLength(el, 'calc(0.5 * var(--size))')).toBe(8);
+
+ createElementSpy.mockRestore();
+ getComputedStyleSpy.mockRestore();
+ });
});
diff --git a/site/src/content/docs/concepts/ui-components.mdx b/site/src/content/docs/concepts/ui-components.mdx
index e3f9876a..f92d42e0 100644
--- a/site/src/content/docs/concepts/ui-components.mdx
+++ b/site/src/content/docs/concepts/ui-components.mdx
@@ -21,7 +21,7 @@ However, you should consider placing your components in ``. Co
-UI Components can go anywhere inside a ``.
+UI Components can go anywhere inside a ``.
However, you should consider placing your components in ``. Components in `` will go fullscreen with the player, respond to user activity, and more.