Compare commits

...

8 Commits

Author SHA1 Message Date
ghassan
eb5cea8630 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.
2026-07-28 23:26:45 +03:00
ghassan
0f3110f59d Fullscreen drawer: squeeze the video instead of overlaying it
Better layout: when the drawer opens in fullscreen, shrink the player's inner
.ytp container to calc(100% - drawer width) so the video sits in the remaining
left area and the drawer owns the right — no overlap, both fully visible side
by side. Because controls, the coach caption and the Highlights button all live
inside .ytp, they move with the video automatically, so the previous overlay
compensations (control/gradient shift, caption re-anchor, button reposition)
are dropped. Drawer is now a clean solid panel (var(--bg-secondary)) since the
video is no longer behind it; blurred-glass background + @supports fallback
removed. Animated via a width transition on .ytp.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 23:24:36 +03:00
ghassan
4cd1d195a7 Fix fullscreen drawer stretching full-width over the video
On a <=991px landscape phone the mobile sheet rule (width:100% !important) beat
the fullscreen drawer's non-important width, so the drawer covered the whole
video instead of just the right side. Make the drawer width !important so it
keeps its narrow clamp(230px, 30vw, 360px).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 23:18:50 +03:00
ghassan
f8cefc1bf9 Match highlights: fullscreen landscape drawer (narrow + clear glass)
Native fullscreen paints only #ytpWrap and its descendants, so the Highlights
pane was invisible in fullscreen. On fullscreenchange the pane is now relocated
INTO the player (and moved back on exit), and CSS reshapes it into a narrow,
transparent right-side drawer:

- Blurred-glass background (Clear) + text shadows so it barely covers the match
  while staying readable; @supports fallback firms up the fill where
  backdrop-filter is unsupported.
- Width clamp(230px, 30vw, 360px) — the Narrow choice, usable on any screen.
- While open: player controls + gradient pull in to the video area (scrubber
  never hidden), coach caption stays bottom-left, and the Highlights button
  slides to the drawer's left edge so it stays tappable to close.
- Same pane/data/tabs/CRUD; windowed bottom-sheet behavior unchanged.

Note: iPhone Safari forces native <video> fullscreen (no custom UI), so the
drawer applies where element fullscreen is supported (Android, desktop, iPad).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 23:16:50 +03:00
ghassan
cffd8491d0 Match highlights sheet: flush to player bottom + non-blocking coach caption
- Sheet top now pins to the player's exact bottom edge (JS measures #ytpWrap
  height into --hl-sheet-top; 56.25vw fallback), so no title/actions row shows
  between the video and the sheet. Recomputes on open and on resize/rotate.
- Coach-note overlay on mobile is now a slim 2-line caption pinned to the very
  bottom of the player instead of a 16px block mid-frame, so it no longer
  covers the video. Full note stays readable in the sheet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 22:56:33 +03:00
ghassan
ea95a60d79 Match highlights sheet: hide bottom nav on open too
While the sheet is open the fixed bottom nav now slides down out of view,
matching the top bar so the sheet owns the whole lower area; it slides back
the moment the sheet closes. Same body.hl-sheet-open trigger, reduced-motion
safe. The !important is needed to beat the base rule that pins the nav's
transform to none on mobile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 22:45:55 +03:00
ghassan
ecb89f9514 Match highlights sheet: full-brightness video + hide top bar on open
Two mobile refinements to the bottom sheet:
- Drop the dark scrim (was 32% black over the whole screen). The backdrop is
  now a transparent tap-to-dismiss catcher, so the match video keeps full
  brightness and detail while the sheet is open.
- While the sheet is open, slide the fixed top bar up and float the scroll
  area to top:0 so the player gets the reclaimed 56px. Both revert the instant
  the sheet closes. Driven by a body.hl-sheet-open class; reduced-motion safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 22:42:05 +03:00
ghassan
18fb4fbd63 Match highlights: mobile bottom sheet (Option A)
On mobile (<=991px) the Points/Coach-review pane no longer stacks below the
comments. Tapping "Highlights" now slides it up as a bottom sheet over the
lower ~62vh, and scrolls the player back to the top so the user can watch and
read at once. Includes a drag handle, a light tap-to-dismiss scrim, and
internal scrolling. Desktop keeps its side pane exactly as before — all new
CSS is inside the max-width:991px media query.

- Sheet only restores its open state on desktop; on mobile it stays closed on
  load so it never pops open unexpectedly.
- Scrim sits above the fixed bottom nav (z-index 1400 > 1000).
- Honors prefers-reduced-motion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 22:35:04 +03:00

View File

@ -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;
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);
}
}
});
</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>