diff --git a/css/tailwind.source.css b/css/tailwind.source.css index a8b1c781..42327500 100644 --- a/css/tailwind.source.css +++ b/css/tailwind.source.css @@ -6102,43 +6102,75 @@ /* Embed & Video Modal Fixes */ @layer components { - /* ensure the video fills the stage and doesn't show a black strip */ - #embedVideo, + /* Force the video stage to let the video fully fill it and prevent 80vh constraints */ + .video-modal__stage, .video-modal__video, - .video-modal__video > .video-modal__media, - .video-modal__video video#modalVideo, - .video-modal__video .video-modal__media { - display: block; + .video-modal__panel { + /* allow child to fill: do not let other rules limit height */ + max-height: none !important; + height: 100% !important; + min-height: 0 !important; + padding: 0 !important; + margin: 0 !important; + display: flex !important; + align-items: stretch !important; /* allows the video to stretch vertically */ + justify-content: center !important; + } + + /* Prevent any earlier max-height on modal container from clashing */ + .bv-modal__panel[role="dialog"], + .bv-modal__panel { + max-height: none !important; + } + + /* Definitive video rules */ + #modalVideo, + .video-modal__media { + display: block !important; width: 100% !important; height: 100% !important; - max-width: 100%; - max-height: 100%; - object-fit: cover; - object-position: center center; + max-width: 100% !important; + max-height: 100% !important; /* ensure no earlier max-height causes clipping */ + object-fit: cover !important; /* or contain if you prefer letterbox */ + object-position: center center !important; background: transparent !important; - padding: 0; - margin: 0; - box-sizing: border-box; + padding: 0 !important; + margin: 0 !important; + box-sizing: border-box !important; } - /* make sure the stage wrapper doesn’t add bottom padding/space */ - .video-modal__stage, - .video-modal__video { - padding: 0; - margin: 0; - display: flex; - align-items: stretch; - justify-content: center; + /* permanent class to force fill */ + .video-modal__media--fill { + width: 100% !important; + height: 100% !important; + max-width: 100% !important; + max-height: 100% !important; + object-position: center center !important; + background: transparent !important; } + .video-modal__stage { + position: relative; /* ensure ambient is absolutely positioned relative to stage */ + overflow: hidden; /* clip any bleed visually; prevents layout expansion */ + } + + .video-modal__ambient { + position: absolute; + inset: calc(var(--video-modal-ambient-bleed) * -1); + pointer-events: none; /* ensure it never captures pointer events */ + z-index: 0; /* behind content */ + will-change: transform, opacity; + } + + /* ensure video is above ambient */ + .video-modal__video { position: relative; z-index: 1; } + /* if there is an ambient canvas or overlay, ensure it doesn't change layout */ - .video-modal__ambient, .video-modal__ambient-canvas { - position: absolute; - inset: 0; + position: relative; width: 100%; height: 100%; - pointer-events: none; + display: block; } /* Logo accent wiring */ diff --git a/js/ui/components/VideoModal.js b/js/ui/components/VideoModal.js index 2309f8b7..e0dafdbe 100644 --- a/js/ui/components/VideoModal.js +++ b/js/ui/components/VideoModal.js @@ -390,6 +390,81 @@ export class VideoModal { ) { this.modalVideo = videoElement; this.bindVideoEvents(); + + try { + if (this.modalVideo) { + // give the element the fill class so CSS can control layout + this.modalVideo.classList.add("video-modal__media--fill"); + + // ensure the stage/wrapper allow stretching (safety) + const videoWrap = + this.playerModal?.querySelector(".video-modal__video") || + this.document.querySelector(".video-modal__video"); + + if (videoWrap) { + videoWrap.style.display = "flex"; + videoWrap.style.alignItems = "stretch"; + videoWrap.style.justifyContent = "center"; + videoWrap.style.padding = "0"; + } + + // choose object-fit dynamically after metadata loads so we can inspect intrinsic size: + const decideObjectFit = () => { + try { + const vw = + this.modalVideo.videoWidth || + this.modalVideo.naturalWidth || + this.modalVideo.clientWidth; + const vh = + this.modalVideo.videoHeight || + this.modalVideo.naturalHeight || + this.modalVideo.clientHeight; + const wrap = this.modalVideo.parentElement; + const cw = wrap ? wrap.clientWidth : this.modalVideo.clientWidth; + const ch = wrap ? wrap.clientHeight : this.modalVideo.clientHeight; + + // If we can't compute, default to cover + if (!vw || !vh || !cw || !ch) { + this.modalVideo.style.objectFit = "cover"; + return; + } + + const videoRatio = vw / vh; + const containerRatio = cw / ch; + const diff = Math.abs(containerRatio - videoRatio); + + // if aspect ratios are close, prefer contain to avoid cropping small edges, + // otherwise use cover to avoid large letterbox bars + const THRESHOLD = 0.12; // tweakable + this.modalVideo.style.objectFit = + diff > THRESHOLD ? "cover" : "contain"; + } catch (e) { + this.modalVideo.style.objectFit = "cover"; + } + }; + + if (this.modalVideo.readyState >= 1) { + // metadata already available + decideObjectFit(); + } else { + const onMeta = () => { + decideObjectFit(); + this.modalVideo.removeEventListener("loadedmetadata", onMeta); + }; + this.modalVideo.addEventListener("loadedmetadata", onMeta); + } + + // always ensure background transparent + this.modalVideo.style.background = "transparent"; + this.modalVideo.style.objectPosition = "center center"; + } + } catch (err) { + // swallow, but log for diagnostics + this.logger?.log?.( + "[VideoModal] setVideoElement style enforcement failed", + err + ); + } } else { this.modalVideo = null; }