diff --git a/resources/views/videos/types/match.blade.php b/resources/views/videos/types/match.blade.php index dc5d347..fd1f145 100644 --- a/resources/views/videos/types/match.blade.php +++ b/resources/views/videos/types/match.blade.php @@ -1882,6 +1882,133 @@ font-size: 0.75rem; } } + + /* ============================================================ + MATCH HIGHLIGHTS — mobile bottom sheet (Option A) + Desktop keeps its side pane untouched. Below 991px the pane + becomes a bottom sheet so the player stays visible up top + while points / coach reviews scroll in the lower half. + ============================================================ */ + .hl-sheet-backdrop { display: none; } + .hl-sheet-grab { display: none; } + + @media (max-width: 991px) { + .events-sidebar { + display: flex !important; /* override desktop display:none so it can slide */ + position: fixed; + left: 0; right: 0; bottom: 0; + /* Top sits exactly at the player's bottom edge so nothing (title, + actions, channel row) shows between the video and the sheet. JS + sets the precise px; the 56.25vw fallback = a full-width 16:9 player. */ + top: var(--hl-sheet-top, 56.25vw); + width: 100% !important; + height: auto; max-height: none; + margin: 0 !important; + border-radius: 18px 18px 0 0; + box-shadow: 0 -14px 44px rgba(0, 0, 0, 0.55); + transform: translateY(100%); + transition: transform 0.34s cubic-bezier(0.32, 0.72, 0, 1); + pointer-events: none; + z-index: 1400; /* above the fixed bottom nav (z-index 1000) */ + } + .events-sidebar.show { + display: flex !important; + transform: translateY(0); + pointer-events: auto; + } + /* Drag affordance at the top of the sheet */ + .hl-sheet-grab { + display: block; flex-shrink: 0; + width: 40px; height: 4px; border-radius: 3px; + background: rgba(255, 255, 255, 0.3); + margin: 10px auto 4px; cursor: pointer; + } + /* Transparent tap-to-dismiss catcher — no dim, so the match video keeps + full brightness and detail while the sheet is open. */ + .hl-sheet-backdrop { + display: block; + position: fixed; inset: 0; + background: transparent; + visibility: hidden; + z-index: 1399; + } + .hl-sheet-backdrop.show { visibility: visible; } + + /* Reclaim the top bar's space while the sheet is open: slide the header + up and float the scroll area to the very top so the player gets more + room. Both revert the moment the sheet closes. */ + .yt-header { transition: transform 0.3s ease; } + body.hl-sheet-open .yt-header { transform: translateY(-100%); } + #main.yt-main { transition: top 0.3s ease; } + body.hl-sheet-open #main.yt-main { top: 0 !important; } + + /* Hide the bottom nav too so the sheet owns the whole lower area. + !important overrides the base `.yt-bottom-nav { transform: none !important }`. */ + .yt-bottom-nav { transition: transform 0.3s ease; } + body.hl-sheet-open .yt-bottom-nav { transform: translateY(100%) !important; } + + /* Coach-note overlay on the small mobile player: a slim caption pinned to + the very bottom edge (max 2 lines) so it never blocks the view. The full + note stays readable in the sheet. */ + .coach-note-overlay { + bottom: 6px; + font-size: 12px; + font-weight: 600; + line-height: 1.3; + padding: 5px 10px; + max-width: 94%; + } + .coach-note-overlay.show { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + } + @media (max-width: 991px) and (prefers-reduced-motion: reduce) { + .events-sidebar, + .yt-header, + .yt-bottom-nav, + #main.yt-main { transition: none; } + } + + /* ============================================================ + MATCH HIGHLIGHTS — fullscreen landscape drawer + Native fullscreen renders only #ytpWrap + descendants, so JS + relocates the pane inside the player. It becomes a narrow solid + right-side drawer, and the video (inside .ytp) is squeezed into + the remaining left area so nothing overlaps — both are fully + visible side by side. Only while .ytp-fullscreen is on. + ============================================================ */ + .ytp-wrap.ytp-fullscreen { --hl-drawer-w: clamp(230px, 30vw, 360px); } + + .ytp-wrap.ytp-fullscreen .events-sidebar { + display: flex !important; + position: absolute; top: 0; right: 0; bottom: 0; left: auto; + /* !important is required: on a <=991px landscape phone the mobile sheet + rule sets width:100% !important, which would otherwise stretch the + drawer across the whole video. */ + width: var(--hl-drawer-w) !important; + height: auto !important; max-height: none !important; + margin: 0 !important; border-radius: 0 !important; + background: var(--bg-secondary, #17171b); /* solid: the video is squeezed aside, not behind it */ + border-left: 1px solid rgba(255, 255, 255, 0.10); + box-shadow: none; + transform: translateX(100%); + transition: transform 0.34s cubic-bezier(0.32, 0.72, 0, 1); + pointer-events: none; z-index: 40; + } + .ytp-wrap.ytp-fullscreen .events-sidebar.show { transform: translateX(0); pointer-events: auto; } + .ytp-wrap.ytp-fullscreen .events-sidebar .hl-sheet-grab { display: none; } + + /* Squeeze the video into the remaining left area when the drawer is open. + Everything else (controls, coach caption, the Highlights button) lives + inside .ytp, so it all moves with the video — nothing sits under the drawer. */ + .ytp-wrap.ytp-fullscreen .ytp { transition: width 0.34s cubic-bezier(0.32, 0.72, 0, 1); } + .ytp-wrap.ytp-fullscreen.hl-drawer-open .ytp { width: calc(100% - var(--hl-drawer-w)); } + @media (prefers-reduced-motion: reduce) { + .ytp-wrap.ytp-fullscreen .events-sidebar { transition: none; } + } @endsection @@ -2222,26 +2349,86 @@ const videoId = @json(isset($video) ? $video->getRouteKey() : ''); if (toggleBtn && sidebar) { - // Load saved state on page load - const savedState = localStorage.getItem(`highlights_${videoId}`); - if (savedState === 'open') { - sidebar.classList.add('show'); - toggleBtn.classList.add('expanded'); - toggleBtn.textContent = 'Highlights'; - } + const backdrop = document.getElementById('hlSheetBackdrop'); + const grab = sidebar.querySelector('.hl-sheet-grab'); + const mainScroll = document.getElementById('main'); + const player = document.getElementById('ytpWrap'); + const isMobile = () => window.matchMedia('(max-width: 991px)').matches; - toggleBtn.addEventListener('click', function() { - const isOpen = sidebar.classList.toggle('show'); - toggleBtn.classList.toggle('expanded'); + // Pin the sheet's top edge to the player's exact bottom so the + // gap between them is zero on any device/orientation. + const positionSheet = () => { + if (!isMobile()) { sidebar.style.removeProperty('--hl-sheet-top'); return; } + const wrap = document.getElementById('ytpWrap'); + if (wrap) { + const h = Math.round(wrap.getBoundingClientRect().height); + if (h > 0) sidebar.style.setProperty('--hl-sheet-top', h + 'px'); + } + }; - // Save state to localStorage - if (isOpen) { + const setOpen = (open) => { + sidebar.classList.toggle('show', open); + toggleBtn.classList.toggle('expanded', open); + // Fullscreen drawer state — harmless when not fullscreen (CSS is gated). + if (player) player.classList.toggle('hl-drawer-open', open); + if (backdrop) backdrop.classList.toggle('show', open && isMobile()); + // Hide the top bar (mobile only) to give the player more room. + document.body.classList.toggle('hl-sheet-open', open && isMobile()); + if (open) { localStorage.setItem(`highlights_${videoId}`, 'open'); - toggleBtn.textContent = 'Highlights'; + // Mobile: the pane is a bottom sheet — bring the player back + // to the top so the user can watch while reading highlights. + if (isMobile()) { + if (mainScroll) mainScroll.scrollTo({ top: 0, behavior: 'smooth' }); + positionSheet(); + } } else { localStorage.removeItem(`highlights_${videoId}`); } + }; + + // Restore last state on DESKTOP only — a sheet auto-popping open on + // every mobile page load would be jarring. + if (localStorage.getItem(`highlights_${videoId}`) === 'open' && !isMobile()) { + setOpen(true); + } + + toggleBtn.addEventListener('click', () => setOpen(!sidebar.classList.contains('show'))); + if (backdrop) backdrop.addEventListener('click', () => setOpen(false)); + if (grab) grab.addEventListener('click', () => setOpen(false)); + + // Keep the scrim + header state honest if the viewport crosses + // the mobile boundary while the pane is open. + window.addEventListener('resize', () => { + const openMobile = sidebar.classList.contains('show') && isMobile(); + if (backdrop) backdrop.classList.toggle('show', openMobile); + document.body.classList.toggle('hl-sheet-open', openMobile); + positionSheet(); }); + + // Fullscreen: native fullscreen paints only #ytpWrap and its + // descendants, so the pane must live INSIDE the player to be + // visible. Relocate it there on enter, move it home on exit; CSS + // reshapes it into the transparent right-side drawer. + if (player) { + let fsHome = null; + const onFsChange = () => { + const fsEl = document.fullscreenElement || document.webkitFullscreenElement; + const inPlayerFs = fsEl === player; + if (inPlayerFs && !fsHome) { + fsHome = document.createComment('events-sidebar-home'); + sidebar.parentNode.insertBefore(fsHome, sidebar); + player.appendChild(sidebar); + player.classList.toggle('hl-drawer-open', sidebar.classList.contains('show')); + } else if (!inPlayerFs && fsHome) { + fsHome.parentNode.insertBefore(sidebar, fsHome); + fsHome.parentNode.removeChild(fsHome); + fsHome = null; + } + }; + document.addEventListener('fullscreenchange', onFsChange); + document.addEventListener('webkitfullscreenchange', onFsChange); + } } }); @@ -2253,7 +2440,11 @@