-
-
-
-
-
- }>
-
-
-
- }
- />
-
-
-
-
-
-
-
- }>
-
-
-
- }
- />
-
-
-
-
-
-
-
- }>
-
-
-
- }
- />
-
-
-
-
-
-
-
- }>
-
-
-
- }
- />
-
-
-
-
-
+
diff --git a/packages/skins/src/default/css/audio.css b/packages/skins/src/default/css/audio.css
index 3ad339fd..c94e3361 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,18 @@
========================================================================== */
.media-default-skin--audio .media-controls {
+ --base-boundary-offset: 2;
color: var(--media-text-color);
+
+ & .media-time-controls {
+ padding-inline: calc(var(--spacing) * 3);
+ }
+
+ & .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..40b835a5 100644
--- a/packages/skins/src/default/css/components/button-group.css
+++ b/packages/skins/src/default/css/components/button-group.css
@@ -4,10 +4,6 @@
.media-default-skin .media-button-group {
display: flex;
- gap: 0.075rem;
+ gap: 1px;
align-items: center;
-
- @container media-root (width > 42rem) {
- gap: 0.125rem;
- }
}
diff --git a/packages/skins/src/default/css/components/button.css b/packages/skins/src/default/css/components/button.css
index f8837c35..5d4f7925 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;
@@ -127,8 +127,6 @@
/* Settings button */
.media-default-skin .media-button--settings {
- display: none;
-
& .media-icon--settings {
transition: transform 150ms ease-in-out;
@@ -142,21 +140,17 @@
}
}
-.media-default-skin .media-button-group:has([data-availability="available"]) .media-button--settings {
- display: grid;
-}
-
/* Live button — wide pill button with a status dot (gray → red at the live
edge) rendered via ::before, and "LIVE" text rendered as the button's own
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 +159,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..db258089 100644
--- a/packages/skins/src/default/css/components/controls.css
+++ b/packages/skins/src/default/css/components/controls.css
@@ -3,11 +3,15 @@
========================================================================== */
.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;
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 377f3496..2036cb53 100644
--- a/packages/skins/src/default/css/components/input-feedback.css
+++ b/packages/skins/src/default/css/components/input-feedback.css
@@ -4,19 +4,13 @@
.media-default-skin .media-input-feedback {
position: absolute;
- inset-inline: 0;
- top: 0;
- bottom: 3.5rem; /* Shift up a little in smaller containers */
+ inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
color: var(--media-color-primary, oklch(1 0 0));
pointer-events: none;
-
- @container media-root (width > 24rem) {
- bottom: 0;
- }
}
/* --- Feedback islands ------------------------------------------------------- */
@@ -24,7 +18,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 +29,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 +85,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 +132,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 +163,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..f689ba2c 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,15 +79,16 @@
}
& .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);
}
& .media-menu__group {
+ anchor-scope: --media-menu-item-highlight-anchor;
display: flex;
flex-direction: column;
- gap: 0.125rem;
+ gap: calc(var(--spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
@@ -105,9 +107,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 +129,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 +140,7 @@
& .media-menu__indicator {
flex-shrink: 0;
- margin-right: -0.25rem;
+ margin-right: calc(var(--spacing) * -1);
margin-left: auto;
opacity: 0;
@@ -171,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);
@@ -184,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/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..5d704e03 100644
--- a/packages/skins/src/default/css/components/time.css
+++ b/packages/skins/src/default/css/components/time.css
@@ -5,10 +5,15 @@
.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;
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 +24,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..19e693b5 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);
@@ -76,7 +78,7 @@
--media-surface-outer-border-color: transparent;
}
- &:has(.media-controls:not([data-visible])) {
+ &:has(.media-controls--root:not([data-visible])) {
/* Slight delay to hide controls on non-touch devices after interaction */
@media (pointer: fine) {
--media-controls-transition-duration: 300ms;
@@ -102,6 +104,20 @@
&:fullscreen {
--media-border-radius: 0;
+
+ &::after {
+ display: none;
+ }
+
+ @media (width >= 1280px) {
+ --scale: 1.25;
+ }
+ @media (width >= 1536px) {
+ --scale: 1.5;
+ }
+ @media (width >= 1920px) {
+ --scale: 1.75;
+ }
}
}
@@ -121,12 +137,13 @@
.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);
+ width: 100%;
+ 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,86 +162,155 @@
.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);
}
/* ==========================================================================
Controls (hide/show behavior)
========================================================================== */
-.media-default-skin--video .media-controls {
- position: absolute;
- inset-inline: 0.5rem;
- bottom: 0.5rem;
+.media-default-skin--video .media-controls--root {
+ --inset-factor: 2;
+ --inset: calc(var(--spacing) * var(--inset-factor));
+ --base-boundary-offset: var(--inset-factor);
+
z-index: 10;
- flex-wrap: wrap;
+ display: contents;
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);
+ /* Speed up the entry transition */
+ transition-duration: calc(var(--media-controls-transition-duration) / 2);
- @media (pointer: fine) {
- transition-property: scale, filter, opacity;
- will-change: scale, filter, opacity;
+ & .media-controls--primary,
+ & .media-controls--secondary {
+ position: absolute;
+ z-index: 10;
}
- @media (pointer: coarse) {
- transition-property: scale, opacity;
- will-change: scale, opacity;
+ & .media-controls--primary {
+ inset-inline: var(--inset);
+ bottom: var(--inset);
+ transform-origin: bottom;
}
- &:not([data-visible]) {
- pointer-events: none;
- opacity: 0;
- scale: 0.95;
+ & .media-controls--secondary {
+ top: var(--inset);
+ right: var(--inset);
+ container-type: normal;
+ transform-origin: top;
+ }
- @media (pointer: fine) and (prefers-reduced-motion: no-preference) {
- filter: blur(8px);
+ @container media-root (width < 32rem) {
+ & .media-controls--primary,
+ & .media-controls--secondary {
+ transition-timing-function: inherit;
+ transition-duration: inherit;
+
+ @media (pointer: fine) {
+ transition-property: filter, opacity, scale, translate;
+ will-change: filter, opacity, scale, translate;
+ }
+
+ @media (pointer: coarse) {
+ transition-property: opacity, scale, translate;
+ will-change: opacity, scale, translate;
+ }
}
- @media (prefers-reduced-motion: reduce) {
- scale: 1;
+ &:after {
+ display: none;
+ }
+
+ &:not([data-visible]) {
+ & .media-controls--primary,
+ & .media-controls--secondary {
+ 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);
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ scale: 1;
+ }
+ }
+
+ & .media-controls--primary {
+ @media (prefers-reduced-motion: no-preference) {
+ translate: 0 4px;
+ }
+ }
+
+ & .media-controls--secondary {
+ @media (prefers-reduced-motion: no-preference) {
+ translate: 0 -4px;
+ }
+ }
+ }
+ }
+
+ @container media-root (width >= 32rem) {
+ position: absolute;
+ inset-inline: var(--inset);
+ bottom: var(--inset);
+ display: flex;
+ transform-origin: bottom;
+
+ & .media-controls--primary,
+ & .media-controls--secondary {
+ display: contents;
+
+ &::after {
+ display: none;
+ }
+ }
+
+ &: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);
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ scale: 1;
+ }
+
+ @media (prefers-reduced-motion: no-preference) {
+ translate: 0 4px;
+ }
}
}
& .media-time-controls {
- flex: 0 0 100%;
- order: -1;
- padding-inline: 0.625rem;
+ flex: 1;
+ padding-inline: calc(var(--spacing) * 3);
}
- & .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;
-
- & .media-time-controls {
- flex: 1;
- order: unset;
- }
-
- & .media-button-group:first-child,
- & .media-button-group:last-child {
- flex: 0 0 auto;
- }
+ --inset-factor: 3;
}
}
@@ -233,7 +319,7 @@
}
/* Hide cursor when controls are hidden */
-.media-default-skin--video:has(.media-controls:not([data-visible])) {
+.media-default-skin--video:has(.media-controls--root:not([data-visible])) {
cursor: none;
}
@@ -247,32 +333,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 +360,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..d0649594 100644
--- a/packages/skins/src/default/tailwind/audio.tailwind.ts
+++ b/packages/skins/src/default/tailwind/audio.tailwind.ts
@@ -5,6 +5,7 @@ import { popup as basePopup } from './components/popup';
import { root as baseRoot } from './components/root';
import { slider as baseSlider } from './components/slider';
import { surface } from './components/surface';
+import { time as baseTime } from './components/time';
/* ==========================================================================
Root
@@ -22,10 +23,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 +44,20 @@ 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 spacer = 'grow';
+
+export const time = {
+ ...baseTime,
+ group: cn(baseTime.group, 'px-3'),
+};
export const playButton = {
wrapper: 'group/play inline-flex relative',
@@ -113,4 +123,3 @@ export { icon, iconContainer, iconFlipped, iconHidden } from './components/icon'
export { menu } from './components/menu';
export { playbackRate } from './components/playback-rate';
export { seek } from './components/seek';
-export { time } from './components/time';
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..c79e9952 100644
--- a/packages/skins/src/default/tailwind/components/button-group.ts
+++ b/packages/skins/src/default/tailwind/components/button-group.ts
@@ -1,3 +1 @@
-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 = 'flex items-center gap-px';
diff --git a/packages/skins/src/default/tailwind/components/button.ts b/packages/skins/src/default/tailwind/components/button.ts
index 7eeec140..ba382a15 100644
--- a/packages/skins/src/default/tailwind/components/button.ts
+++ b/packages/skins/src/default/tailwind/components/button.ts
@@ -1,8 +1,10 @@
import { cn } from '@videojs/utils/style';
+const hideAtSmall = '@max-lg/media-root:hidden';
+
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 +21,8 @@ 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'),
+ seek: hideAtSmall,
/**
* 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,10 +30,9 @@ 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-[""]',
'data-[live-edge]:before:bg-red-500'
),
};
diff --git a/packages/skins/src/default/tailwind/components/controls.ts b/packages/skins/src/default/tailwind/components/controls.ts
index 6d85c18f..3846f228 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',
+ '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..05d353b8 100644
--- a/packages/skins/src/default/tailwind/components/input-feedback.ts
+++ b/packages/skins/src/default/tailwind/components/input-feedback.ts
@@ -8,10 +8,8 @@ import { cn } from '@videojs/utils/style';
export const inputFeedback = {
root: cn(
// Layout
- 'absolute inset-x-0 top-0 bottom-14 pointer-events-none',
+ 'absolute inset-0 pointer-events-none',
'grid grid-cols-3 items-center justify-items-center',
- // Shift to full extent in larger containers
- '@2xl/media-root:bottom-0',
// Color
'[color:var(--media-color-primary,oklch(1_0_0))]'
),
@@ -52,7 +50,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..7843c6b2 100644
--- a/packages/skins/src/default/tailwind/components/menu.ts
+++ b/packages/skins/src/default/tailwind/components/menu.ts
@@ -43,13 +43,17 @@ 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]'
);
const group = cn(
'flex flex-col gap-0.5',
+ '[anchor-scope:--media-menu-item-highlight-anchor]',
'supports-[top:anchor(top)]:before:absolute',
'supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor]',
'supports-[top:anchor(top)]:before:[inset:anchor(inside)]',
@@ -66,7 +70,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 +83,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 +96,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,10 +104,10 @@ 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',
- settingsTrigger: 'group hidden group-has-[[data-availability=available]]/settings:grid',
+ settingsTrigger: 'group',
settingsIcon: 'transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0',
};
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..c6e13810 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,14 @@ 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..4881371c 100644
--- a/packages/skins/src/default/tailwind/components/time.ts
+++ b/packages/skins/src/default/tailwind/components/time.ts
@@ -1,6 +1,6 @@
export const time = {
- group: '@container/media-time flex items-center flex-1 gap-3 px-2',
- current: 'hidden @2xs/media-time:block tabular-nums',
+ group: '@container/media-time flex items-center flex-1 gap-2.5 @max-[10rem]/media-time:[&>*:last-child]:hidden',
+ current: '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..567fae8e 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,17 @@ 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-[1280px]:[&:fullscreen]:[--scale:1.25]',
+ 'min-[1536px]:[&:fullscreen]:[--scale:1.5]',
+ 'min-[1920px]:[&:fullscreen]:[--scale:1.75]',
'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 +64,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 +107,67 @@ 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]',
- // Hidden state
- 'not-data-visible:pointer-events-none not-data-visible:opacity-0',
- 'motion-safe:not-data-visible:scale-90',
- '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'
+ 'peer-data-open/error:hidden!',
+ 'ease-(--media-controls-transition-timing-function)',
+ 'duration-[calc(var(--media-controls-transition-duration)/2)]',
+ '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]',
+ '@2xl/media-root:[--base-boundary-offset:3]'
+);
+
+export const controls = cn(
+ controlsBase,
+ 'group/controls contents! after:hidden',
+ '@lg/media-root:absolute @lg/media-root:flex!',
+ '@lg/media-root:bottom-2 @lg/media-root:inset-x-2',
+ '@2xl/media-root:bottom-3 @2xl/media-root:inset-x-3',
+ '@lg/media-root:after:block @lg/media-root:origin-bottom',
+ // Hidden state (large)
+ '@lg/media-root:not-data-visible:pointer-events-none',
+ '@lg/media-root:not-data-visible:opacity-0',
+ '@lg/media-root:not-data-visible:duration-(--media-controls-transition-duration)',
+ '@lg/media-root:motion-safe:not-data-visible:scale-95',
+ '@lg/media-root:pointer-fine:motion-safe:not-data-visible:blur-sm',
+ '@lg/media-root:motion-safe:not-data-visible:translate-y-1'
+);
+
+const splitControls = cn(
+ controlsBase,
+ 'absolute @max-lg/media-root:duration-[inherit] @max-lg/media-root:ease-[inherit]',
+ '@lg/media-root:contents! @lg/media-root:after:hidden',
+ '@max-lg/media-root:group-[:not([data-visible])]/controls:pointer-events-none',
+ '@max-lg/media-root:group-[:not([data-visible])]/controls:opacity-0',
+ '@max-lg/media-root:group-[:not([data-visible])]/controls:duration-(--media-controls-transition-duration)',
+ '@max-lg/media-root:motion-safe:group-[:not([data-visible])]/controls:scale-95',
+ '@max-lg/media-root:pointer-fine:motion-safe:group-[:not([data-visible])]/controls:blur-sm'
+);
+
+export const primaryControls = cn(
+ splitControls,
+ 'bottom-2 inset-x-2 origin-bottom',
+ '@max-lg/media-root:motion-safe:group-[:not([data-visible])]/controls:translate-y-1'
+);
+
+export const secondaryControls = cn(
+ splitControls,
+ 'top-2 right-2 origin-top @container-normal',
+ '@max-lg/media-root:motion-safe:group-[:not([data-visible])]/controls:-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;
+
+export const spacer = 'grow';
/* ==========================================================================
Time
@@ -139,11 +175,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, 'px-3'),
};
/* ==========================================================================
@@ -155,22 +187,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 +224,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,
};
/* ==========================================================================
@@ -216,9 +242,9 @@ export const bufferingIndicator = baseBufferingIndicator;
export const error = {
...baseError,
- dialog: cn(baseError.dialog, surface, 'text-shadow-2xs text-shadow-black/25'),
+ dialog: cn(baseError.dialog, surface, 'w-full 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..22b00c87 100644
--- a/packages/skins/src/minimal/css/components/button-group.css
+++ b/packages/skins/src/minimal/css/components/button-group.css
@@ -4,10 +4,6 @@
.media-minimal-skin .media-button-group {
display: flex;
- gap: 0.075rem;
+ gap: 1px;
align-items: center;
-
- @container media-root (width > 42rem) {
- gap: 0.125rem;
- }
}
diff --git a/packages/skins/src/minimal/css/components/button.css b/packages/skins/src/minimal/css/components/button.css
index ed2cd40b..49b3998f 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;
@@ -133,8 +133,6 @@
/* Settings button */
.media-minimal-skin .media-button--settings {
- display: none;
-
& .media-icon--settings {
transition: transform 150ms ease-in-out;
@@ -148,21 +146,17 @@
}
}
-.media-minimal-skin .media-button-group:has([data-availability="available"]) .media-button--settings {
- display: grid;
-}
-
/* Live button — wide pill button with a status dot (gray → red at the live
edge) rendered via ::before, and "LIVE" text rendered as the button's own
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 +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/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..7865f177 100644
--- a/packages/skins/src/minimal/css/components/input-feedback.css
+++ b/packages/skins/src/minimal/css/components/input-feedback.css
@@ -4,9 +4,7 @@
.media-minimal-skin .media-input-feedback {
position: absolute;
- inset-inline: 0;
- top: 0;
- bottom: 3.5rem; /* Shift up a little in smaller containers */
+ inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
@@ -15,10 +13,6 @@
color: var(--media-color-primary, oklch(1 0 0));
pointer-events: none;
border-radius: inherit;
-
- @container media-root (width > 24rem) {
- bottom: 0;
- }
}
/* --- Feedback islands ------------------------------------------------------- */
@@ -29,22 +23,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 +73,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 +98,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 +148,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 +176,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..1695ef2a 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,15 @@
}
& .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 {
+ anchor-scope: --media-menu-item-highlight-anchor;
display: flex;
flex-direction: column;
- gap: 0.125rem;
+ gap: calc(var(--spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
@@ -110,9 +112,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 +134,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 +145,7 @@
& .media-menu__indicator {
flex-shrink: 0;
- margin-right: -0.25rem;
+ margin-right: calc(var(--spacing) * -1);
margin-left: auto;
opacity: 0;
}
@@ -172,12 +169,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 +190,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..3a24f606 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 f2df4d0c..cb4cec90 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,22 @@
box-shadow: inset 0 0 0 1px var(--media-border-color);
}
- /* Fullscreen */
&:fullscreen {
--media-border-radius: 0;
+
+ &::after {
+ display: none;
+ }
+
+ @media (width >= 1280px) {
+ --scale: 1.25;
+ }
+ @media (width >= 1536px) {
+ --scale: 1.5;
+ }
+ @media (width >= 1920px) {
+ --scale: 1.75;
+ }
}
}
@@ -119,9 +129,10 @@
.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);
+ width: 100%;
+ 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;
@@ -134,12 +145,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-starting-style] .media-error__dialog,
@@ -156,17 +167,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;
@@ -181,6 +197,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);
@@ -192,9 +209,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 {
@@ -208,11 +227,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;
}
@@ -221,6 +243,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 +282,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 +291,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 +336,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..20de0b89 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)',
@@ -58,6 +55,8 @@ export const controls = cn(
'ring-1 ring-(color:--media-controls-border-color)'
);
+export const spacer = 'grow';
+
export const playButton = {
wrapper: 'group/play inline-flex relative',
/** `peer/play-buffering` on `bufferingRoot`; merge onto the play trigger after the peer in DOM. */
@@ -78,7 +77,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..c79e9952 100644
--- a/packages/skins/src/minimal/tailwind/components/button-group.ts
+++ b/packages/skins/src/minimal/tailwind/components/button-group.ts
@@ -1,3 +1 @@
-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 = 'flex items-center gap-px';
diff --git a/packages/skins/src/minimal/tailwind/components/button.ts b/packages/skins/src/minimal/tailwind/components/button.ts
index 64e3c153..9c03a511 100644
--- a/packages/skins/src/minimal/tailwind/components/button.ts
+++ b/packages/skins/src/minimal/tailwind/components/button.ts
@@ -1,15 +1,17 @@
import { cn } from '@videojs/utils/style';
+const hideAtSmall = '@max-lg/media-root:hidden';
+
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 +23,12 @@ 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'),
+ seek: hideAtSmall,
+ cast: hideAtSmall,
+ airplay: hideAtSmall,
+ pip: hideAtSmall,
+ fullscreen: hideAtSmall,
/**
* 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,10 +36,9 @@ 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-[""]',
'data-[live-edge]:before:bg-red-500'
),
};
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..39559cd0 100644
--- a/packages/skins/src/minimal/tailwind/components/input-feedback.ts
+++ b/packages/skins/src/minimal/tailwind/components/input-feedback.ts
@@ -8,11 +8,9 @@ import { cn } from '@videojs/utils/style';
export const inputFeedback = {
root: cn(
// Layout
- 'absolute inset-x-0 top-0 bottom-14 pointer-events-none',
+ 'absolute inset-0 pointer-events-none',
'grid grid-cols-3 items-center justify-items-center overflow-hidden',
'rounded-[inherit]',
- // Shift to full extent in larger containers
- '@2xl/media-root:bottom-0',
// Color
'[color:var(--media-color-primary,oklch(1_0_0))]'
),
@@ -34,7 +32,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 +49,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..7e56d101 100644
--- a/packages/skins/src/minimal/tailwind/components/menu.ts
+++ b/packages/skins/src/minimal/tailwind/components/menu.ts
@@ -42,13 +42,17 @@ 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]'
);
const group = cn(
'flex flex-col gap-0.5',
+ '[anchor-scope:--media-menu-item-highlight-anchor]',
'supports-[top:anchor(top)]:before:absolute',
'supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor]',
'supports-[top:anchor(top)]:before:[inset:anchor(inside)]',
@@ -64,7 +68,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 +83,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 +96,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,10 +104,10 @@ 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',
- settingsTrigger: 'group hidden group-has-[[data-availability=available]]/settings:grid',
+ settingsTrigger: 'group',
settingsIcon: 'transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0',
};
diff --git a/packages/skins/src/minimal/tailwind/components/overlay.ts b/packages/skins/src/minimal/tailwind/components/overlay.ts
index 2d23c0b1..938619e1 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',
+ 'bg-linear-to-t from-black/70 via-black/50 via-[--spacing(30)] to-transparent',
'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..43c34f8e 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,14 @@ 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..826198fe 100644
--- a/packages/skins/src/minimal/tailwind/components/thumbnail.ts
+++ b/packages/skins/src/minimal/tailwind/components/thumbnail.ts
@@ -2,9 +2,10 @@ 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',
+ // `cn` intentionally does not merge Tailwind utilities, so keep the important modifier here.
+ 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..280ac137 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,17 @@ 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-[1280px]:[&:fullscreen]:[--scale:1.25]',
+ 'min-[1536px]:[&:fullscreen]:[--scale:1.5]',
+ 'min-[1920px]:[&:fullscreen]:[--scale:1.75]',
'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 +56,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 +107,13 @@ 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',
+ '[--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]'
);
/* ==========================================================================
@@ -131,6 +133,8 @@ export const controls = cn(
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 spacer = 'grow';
+
/* ==========================================================================
Time
========================================================================== */
@@ -139,8 +143,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 +159,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 w-full max-w-64 p-4 rounded-none'),
+ content: cn(baseError.content, 'p-0 py-1.5'),
+ title: 'text-(length:--font-size-medium)',
};
/* ==========================================================================
@@ -163,11 +172,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 +189,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 +207,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..d0c925df 100644
--- a/packages/utils/src/dom/style.ts
+++ b/packages/utils/src/dom/style.ts
@@ -17,42 +17,62 @@ 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';
measurementEl.style.visibility = 'hidden';
measurementEl.style.pointerEvents = 'none';
measurementEl.style.inlineSize = trimmed;
+
+ if (!measurementEl.style.inlineSize) return 0;
+
measurementEl.style.blockSize = '0';
measurementEl.style.padding = '0';
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);
+ if (getComputedStyle(measurementEl).inlineSize === 'auto') {
+ measurementEl.remove();
+ return 0;
+ }
+
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..403c8d47 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,139 @@ describe('resolveCSSLength', () => {
createElementSpy.mockRestore();
});
+
+ it('Returns zero for invalid CSS lengths', () => {
+ 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: 640,
+ height: 0,
+ top: 0,
+ left: 0,
+ right: 640,
+ bottom: 0,
+ toJSON() {},
+ }));
+ }
+
+ return node;
+ });
+
+ expect(resolveCSSLength(el, 'not-a-length')).toBe(0);
+
+ createElementSpy.mockRestore();
+ });
+
+ it('Returns zero when a CSS length resolves to auto', () => {
+ const el = document.createElement('div');
+ const createElement = document.createElement.bind(document);
+ const getComputedStyleSpy = vi
+ .spyOn(globalThis, 'getComputedStyle')
+ .mockImplementation((target: Element) =>
+ target === el
+ ? ({ length: 0, fontSize: '14px' } as CSSStyleDeclaration)
+ : ({ inlineSize: 'auto' } 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: 640,
+ height: 0,
+ top: 0,
+ left: 0,
+ right: 640,
+ bottom: 0,
+ toJSON() {},
+ }));
+ }
+
+ return node;
+ });
+
+ expect(resolveCSSLength(el, 'var(--missing-size)')).toBe(0);
+
+ createElementSpy.mockRestore();
+ getComputedStyleSpy.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.