From d6e471a8377e9ee8ef63df9097810c6d0c1bb2f9 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 15 Mar 2026 22:41:46 +1100 Subject: [PATCH] fix(html): simplify styles for slotted video (#953) Co-authored-by: Claude Opus 4.6 --- .../dom/media/custom-media-element/index.ts | 20 +++++++++-------- packages/html/src/define/base.css | 22 +++++++++---------- packages/html/src/define/shared.css | 4 ++++ .../src/default/css/components/captions.css | 7 +++++- .../src/default/css/components/media.css | 13 +++++------ packages/skins/src/default/css/video.css | 8 ++++++- .../src/default/tailwind/video.tailwind.ts | 22 ++++++++++--------- .../src/minimal/css/components/captions.css | 13 +++++------ .../src/minimal/css/components/media.css | 15 ++++++------- packages/skins/src/minimal/css/video.css | 8 ++++++- .../src/minimal/tailwind/video.tailwind.ts | 22 ++++++++++--------- site/src/components/home/HeroVideo.tsx | 10 +++++++-- 12 files changed, 96 insertions(+), 68 deletions(-) diff --git a/packages/core/src/dom/media/custom-media-element/index.ts b/packages/core/src/dom/media/custom-media-element/index.ts index 99194d69..23579300 100644 --- a/packages/core/src/dom/media/custom-media-element/index.ts +++ b/packages/core/src/dom/media/custom-media-element/index.ts @@ -93,22 +93,24 @@ function getVideoTemplateHTML(attrs: Record): string { return /*html*/ ` diff --git a/packages/html/src/define/base.css b/packages/html/src/define/base.css index 52f880c7..845ef2ea 100644 --- a/packages/html/src/define/base.css +++ b/packages/html/src/define/base.css @@ -2,24 +2,22 @@ video-player { display: contents; } -video-player video, -video-player hls-video, -video-player simple-hls-video { +/* +Required to override any default video styles (such as Tailwind's CSS reset) +and ensure the video element fills the container as expected. +*/ +video-player video { display: block; width: 100%; height: 100%; + object-fit: var(--media-object-fit, contain); + object-position: var(--media-object-position, center); } video-player video::-webkit-media-text-track-container { - transition: transform var(--media-caption-track-duration, 150ms) ease-out; - transition-delay: var(--media-caption-track-delay, 600ms); + transition: transform var(--media-caption-track-duration, 0) ease-out; + transition-delay: var(--media-caption-track-delay, 0); transform: translateY(var(--media-caption-track-y, 0)) scale(0.98); - z-index: var(--media-caption-track-z, 1); + z-index: 1; font-family: inherit; } - -@media (prefers-reduced-motion: reduce) { - video-player video::-webkit-media-text-track-container { - transition-duration: 50ms; - } -} diff --git a/packages/html/src/define/shared.css b/packages/html/src/define/shared.css index d67093d5..8c2d4e12 100644 --- a/packages/html/src/define/shared.css +++ b/packages/html/src/define/shared.css @@ -1,3 +1,7 @@ media-tooltip-group { display: contents; } + +:host { + display: grid; +} diff --git a/packages/skins/src/default/css/components/captions.css b/packages/skins/src/default/css/components/captions.css index 2befb520..6d69862e 100644 --- a/packages/skins/src/default/css/components/captions.css +++ b/packages/skins/src/default/css/components/captions.css @@ -3,6 +3,7 @@ ========================================================================== */ .media-default-skin { + --media-caption-track-duration: 150ms; --media-caption-track-delay: 600ms; --media-caption-track-y: -0.5rem; @@ -10,11 +11,15 @@ --media-caption-track-delay: 25ms; --media-caption-track-y: -3.5rem; } + + @media (prefers-reduced-motion: reduce) { + --media-caption-track-duration: 50ms; + } } .media-default-skin video::-webkit-media-text-track-container { /* NOTE: The delay must account for the controls delay/duration */ - transition: transform 150ms ease-out; + transition: transform var(--media-caption-track-duration) ease-out; transition-delay: var(--media-caption-track-delay); transform: translateY(var(--media-caption-track-y)) scale(0.98); z-index: 1; diff --git a/packages/skins/src/default/css/components/media.css b/packages/skins/src/default/css/components/media.css index 98d0ec8d..72d28b4f 100644 --- a/packages/skins/src/default/css/components/media.css +++ b/packages/skins/src/default/css/components/media.css @@ -7,9 +7,11 @@ display: block; width: 100%; height: 100%; + object-fit: var(--media-object-fit, contain); + object-position: var(--media-object-position, center); } .media-default-skin ::slotted(video) { - border-radius: var(--media-border-radius, 2rem); + border-radius: var(--media-video-border-radius); } .media-default-skin video { border-radius: inherit; @@ -24,7 +26,8 @@ inset: 0; width: 100%; height: 100%; - object-fit: cover; + object-fit: var(--media-object-fit, contain); + object-position: var(--media-object-position, center); transition: opacity 0.25s; pointer-events: none; border-radius: inherit; @@ -38,11 +41,7 @@ Fullscreen ========================================================================== */ -.media-default-skin:fullscreen video, .media-default-skin:fullscreen ::slotted(video), -.media-default-skin:fullscreen > img { +.media-default-skin:fullscreen video { object-fit: contain; } -.media-default-skin:fullscreen ::slotted(video) { - border-radius: 0; -} diff --git a/packages/skins/src/default/css/video.css b/packages/skins/src/default/css/video.css index 507e25e6..87ac14f4 100644 --- a/packages/skins/src/default/css/video.css +++ b/packages/skins/src/default/css/video.css @@ -28,6 +28,7 @@ --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); @media (prefers-color-scheme: dark) { --media-border-color: oklch(1 0 0 / 0.1); @@ -45,7 +46,7 @@ } &:fullscreen { - border-radius: 0; + --media-border-radius: 0; } } @@ -92,6 +93,11 @@ } } +/* Hide cursor when controls are hidden in fullscreen */ +.media-default-skin--video:fullscreen:has(.media-controls:not([data-visible])) { + cursor: none; +} + /* ========================================================================== Sliders ========================================================================== */ diff --git a/packages/skins/src/default/tailwind/video.tailwind.ts b/packages/skins/src/default/tailwind/video.tailwind.ts index 6cbfad1b..54fc14f2 100644 --- a/packages/skins/src/default/tailwind/video.tailwind.ts +++ b/packages/skins/src/default/tailwind/video.tailwind.ts @@ -21,41 +21,43 @@ export const root = (isShadowDOM: boolean) => 'after:inset-0 after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/10', // Video element { - '[&_::slotted(video)]:block [&_::slotted(video)]:w-full [&_::slotted(video)]:h-full [&_::slotted(video)]:rounded-(--media-border-radius,2rem)': + '[&_::slotted(video)]:block [&_::slotted(video)]:w-full [&_::slotted(video)]:h-full [&_::slotted(video)]:rounded-(--media-video-border-radius) [&_::slotted(video)]:[object-fit:var(--media-object-fit,contain)] [&_::slotted(video)]:[object-position:var(--media-object-position,center)]': isShadowDOM, - '[&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit]': !isShadowDOM, + '[&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit] [&_video]:[object-fit:var(--media-object-fit,contain)] [&_video]:[object-position:var(--media-object-position,center)]': + !isShadowDOM, }, + '[--media-video-border-radius:var(--media-border-radius,2rem)]', // Poster image '[&>img]:absolute [&>img]:inset-0 [&>img]:w-full [&>img]:h-full [&>img]:rounded-[inherit]', - '[&>img]:object-cover [&>img]:pointer-events-none', + '[&>img]:[object-fit:var(--media-object-fit,contain)] [&>img]:[object-position:var(--media-object-position,center)] [&>img]:pointer-events-none', '[&>img]:transition-opacity [&>img]:duration-250', '[&>img:not([data-visible])]:opacity-0', // Caption track CSS variables (consumed by the native caption bridge in light DOM) - '[--media-caption-track-delay:600ms]', '[--media-caption-track-y:-0.5rem]', + '[--media-caption-track-delay:600ms]', + '[--media-caption-track-duration:150ms]', + 'motion-reduce:[--media-caption-track-duration:50ms]', 'has-[[data-controls][data-visible]]:[--media-caption-track-delay:25ms]', 'has-[[data-controls][data-visible]]:[--media-caption-track-y:-3.5rem]', // Native caption track container !isShadowDOM ? [ '[&_video::-webkit-media-text-track-container]:transition-transform', - '[&_video::-webkit-media-text-track-container]:duration-150', + '[&_video::-webkit-media-text-track-container]:duration-(--media-caption-track-duration)', '[&_video::-webkit-media-text-track-container]:ease-out', '[&_video::-webkit-media-text-track-container]:delay-(--media-caption-track-delay)', '[&_video::-webkit-media-text-track-container]:translate-y-(--media-caption-track-y)', '[&_video::-webkit-media-text-track-container]:scale-98', '[&_video::-webkit-media-text-track-container]:z-1', '[&_video::-webkit-media-text-track-container]:font-[inherit]', - 'motion-reduce:[&_video::-webkit-media-text-track-container]:duration-50', ] : [], // Fullscreen - '[&:fullscreen]:rounded-none', + '[&:fullscreen]:[--media-border-radius:0]', { '[&:fullscreen_video]:object-contain': !isShadowDOM, - '[&:fullscreen_::slotted(video)]:object-contain [&:fullscreen_::slotted(video)]:rounded-none': isShadowDOM, - }, - '[&:fullscreen>img]:object-contain' + '[&:fullscreen_::slotted(video)]:object-contain': isShadowDOM, + } ); /* ========================================================================== diff --git a/packages/skins/src/minimal/css/components/captions.css b/packages/skins/src/minimal/css/components/captions.css index b80450b2..b44f09d0 100644 --- a/packages/skins/src/minimal/css/components/captions.css +++ b/packages/skins/src/minimal/css/components/captions.css @@ -3,6 +3,7 @@ ========================================================================== */ .media-minimal-skin { + --media-caption-track-duration: 150ms; --media-caption-track-delay: 600ms; --media-caption-track-y: -0.5rem; @@ -10,19 +11,17 @@ --media-caption-track-delay: 25ms; --media-caption-track-y: -3rem; } + + @media (prefers-reduced-motion: reduce) { + --media-caption-track-duration: 50ms; + } } .media-minimal-skin video::-webkit-media-text-track-container { /* NOTE: The delay must account for the controls delay/duration */ - transition: transform 150ms ease-out; + transition: transform var(--media-caption-track-duration) ease-out; transition-delay: var(--media-caption-track-delay); transform: translateY(var(--media-caption-track-y)) scale(0.98); z-index: 1; font-family: inherit; } - -@media (prefers-reduced-motion: reduce) { - .media-minimal-skin video::-webkit-media-text-track-container { - transition-duration: 50ms; - } -} diff --git a/packages/skins/src/minimal/css/components/media.css b/packages/skins/src/minimal/css/components/media.css index f31475ea..75d67167 100644 --- a/packages/skins/src/minimal/css/components/media.css +++ b/packages/skins/src/minimal/css/components/media.css @@ -7,9 +7,11 @@ display: block; width: 100%; height: 100%; + object-fit: var(--media-object-fit, contain); + object-position: var(--media-object-position, center); } .media-minimal-skin ::slotted(video) { - border-radius: var(--media-border-radius, 0.75rem); + border-radius: var(--media-video-border-radius); } .media-minimal-skin video { border-radius: inherit; @@ -24,10 +26,11 @@ inset: 0; width: 100%; height: 100%; - border-radius: inherit; - object-fit: cover; + object-fit: var(--media-object-fit, contain); + object-position: var(--media-object-position, center); transition: opacity 0.25s; pointer-events: none; + border-radius: inherit; &:not([data-visible]) { opacity: 0; @@ -38,11 +41,7 @@ Fullscreen ========================================================================== */ -.media-minimal-skin:fullscreen video, .media-minimal-skin:fullscreen ::slotted(video), -.media-minimal-skin:fullscreen > img { +.media-minimal-skin:fullscreen video { object-fit: contain; } -.media-minimal-skin:fullscreen ::slotted(video) { - border-radius: 0; -} diff --git a/packages/skins/src/minimal/css/video.css b/packages/skins/src/minimal/css/video.css index 4016c537..3f1579c5 100644 --- a/packages/skins/src/minimal/css/video.css +++ b/packages/skins/src/minimal/css/video.css @@ -22,6 +22,7 @@ .media-minimal-skin--video { background: oklch(0 0 0); --media-border-color: oklch(0 0 0 / 0.15); + --media-video-border-radius: var(--media-border-radius, 0.75rem); @media (prefers-color-scheme: dark) { --media-border-color: oklch(1 0 0 / 0.15); @@ -40,7 +41,7 @@ /* Fullscreen */ &:fullscreen { - border-radius: 0; + --media-border-radius: 0; } } @@ -90,6 +91,11 @@ } } +/* Hide cursor when controls are hidden in fullscreen */ +.media-minimal-skin--video:fullscreen:has(.media-controls:not([data-visible])) { + cursor: none; +} + /* ========================================================================== Sliders ========================================================================== */ diff --git a/packages/skins/src/minimal/tailwind/video.tailwind.ts b/packages/skins/src/minimal/tailwind/video.tailwind.ts index a11ccaa8..cc278264 100644 --- a/packages/skins/src/minimal/tailwind/video.tailwind.ts +++ b/packages/skins/src/minimal/tailwind/video.tailwind.ts @@ -19,41 +19,43 @@ export const root = (isShadowDOM: boolean) => 'dark:after:ring-white/15', // Video element { - '[&_::slotted(video)]:block [&_::slotted(video)]:w-full [&_::slotted(video)]:h-full [&_::slotted(video)]:rounded-(--media-border-radius,0.75rem)': + '[&_::slotted(video)]:block [&_::slotted(video)]:w-full [&_::slotted(video)]:h-full [&_::slotted(video)]:rounded-(--media-video-border-radius) [&_::slotted(video)]:[object-fit:var(--media-object-fit,cover)] [&_::slotted(video)]:[object-position:var(--media-object-position,center)]': isShadowDOM, - '[&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit]': !isShadowDOM, + '[&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit] [&_video]:[object-fit:var(--media-object-fit,contain)] [&_video]:[object-position:var(--media-object-position,center)]': + !isShadowDOM, }, + '[--media-video-border-radius:var(--media-border-radius,0.75rem)]', // Poster image '[&>img]:absolute [&>img]:inset-0 [&>img]:w-full [&>img]:h-full [&>img]:rounded-[inherit]', - '[&>img]:object-cover [&>img]:pointer-events-none', + '[&>img]:[object-fit:var(--media-object-fit,contain)] [&>img]:[object-position:var(--media-object-position,center)] [&>img]:pointer-events-none', '[&>img]:transition-opacity [&>img]:duration-250', '[&>img:not([data-visible])]:opacity-0', // Caption track CSS variables (consumed by the native caption bridge in light DOM) - '[--media-caption-track-delay:600ms]', '[--media-caption-track-y:-0.5rem]', + '[--media-caption-track-delay:600ms]', + '[--media-caption-track-duration:150ms]', + 'motion-reduce:[--media-caption-track-duration:50ms]', 'has-[[data-controls][data-visible]]:[--media-caption-track-delay:25ms]', 'has-[[data-controls][data-visible]]:[--media-caption-track-y:-3.5rem]', // Native caption track container !isShadowDOM ? [ '[&_video::-webkit-media-text-track-container]:transition-transform', - '[&_video::-webkit-media-text-track-container]:duration-150', + '[&_video::-webkit-media-text-track-container]:duration-(--media-caption-track-duration)', '[&_video::-webkit-media-text-track-container]:ease-out', '[&_video::-webkit-media-text-track-container]:delay-(--media-caption-track-delay)', '[&_video::-webkit-media-text-track-container]:translate-y-(--media-caption-track-y)', '[&_video::-webkit-media-text-track-container]:scale-98', '[&_video::-webkit-media-text-track-container]:z-1', '[&_video::-webkit-media-text-track-container]:font-[inherit]', - 'motion-reduce:[&_video::-webkit-media-text-track-container]:duration-50', ] : [], // Fullscreen - '[&:fullscreen]:rounded-none', + '[&:fullscreen]:[--media-border-radius:0]', { '[&:fullscreen_video]:object-contain': !isShadowDOM, - '[&:fullscreen_::slotted(video)]:object-contain [&:fullscreen_::slotted(video)]:rounded-none': isShadowDOM, - }, - '[&:fullscreen>img]:object-contain' + '[&:fullscreen_::slotted(video)]:object-contain': isShadowDOM, + } ); /* ========================================================================== diff --git a/site/src/components/home/HeroVideo.tsx b/site/src/components/home/HeroVideo.tsx index 14c66af4..c6a1a156 100644 --- a/site/src/components/home/HeroVideo.tsx +++ b/site/src/components/home/HeroVideo.tsx @@ -25,9 +25,15 @@ export default function HeroVideo({ - +