Merge branch 'match-video' into master
Match Highlights mobile UX: windowed bottom sheet (video + list on screen, top/bottom bars hidden, flush to player, non-blocking coach caption) and a fullscreen landscape drawer that squeezes the video beside a solid side panel.
This commit is contained in:
commit
eb5cea8630
@ -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; }
|
||||
}
|
||||
</style>
|
||||
@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;
|
||||
|
||||
// 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');
|
||||
}
|
||||
};
|
||||
|
||||
toggleBtn.addEventListener('click', function() {
|
||||
const isOpen = sidebar.classList.toggle('show');
|
||||
toggleBtn.classList.toggle('expanded');
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -2253,7 +2440,11 @@
|
||||
|
||||
<!-- Sidebar - Match Highlights -->
|
||||
<div class="yt-sidebar-container" style="display: flex; flex-direction: column; gap: 16px;">
|
||||
{{-- Mobile-only scrim behind the bottom sheet (tap to dismiss) --}}
|
||||
<div class="hl-sheet-backdrop" id="hlSheetBackdrop"></div>
|
||||
<aside class="events-sidebar">
|
||||
{{-- Mobile-only drag handle at the top of the sheet --}}
|
||||
<div class="hl-sheet-grab"></div>
|
||||
<div class="events-sidebar-header" style="justify-content: center;">
|
||||
<h3>Match Highlights</h3>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user