feat: Follow-up: add portrait orientation design for mobile that only triggers in portrait pwa mode
Task #43 completed by ClaudeQueue ClaudeQueue
This commit is contained in:
@@ -495,6 +495,9 @@ const Player = {
|
||||
navigator.mediaSession.setActionHandler('seekbackward', (d) => Player.seek(Math.max(0, Player.master.currentTime - (d.seekOffset || 10))));
|
||||
navigator.mediaSession.setActionHandler('seekforward', (d) => Player.seek(Player.master.currentTime + (d.seekOffset || 10)));
|
||||
}
|
||||
// In portrait PWA mode, scroll the player into view so it's immediately
|
||||
// visible without the user having to swipe up manually.
|
||||
scrollPlayerIntoViewPortrait();
|
||||
// Restore A-B markers and load related (non-blocking)
|
||||
restoreAbMarkers();
|
||||
loadRelated();
|
||||
@@ -1976,6 +1979,52 @@ function showModal(title, bodyNode, actions) {
|
||||
}
|
||||
function closeModal() { $('modal').classList.add('hidden'); }
|
||||
|
||||
// ============================================================================
|
||||
// Portrait PWA detection & behavioral layer
|
||||
// Mirrors the CSS media query: (display-mode: standalone) and (orientation: portrait)
|
||||
// The JS layer adds:
|
||||
// • A `portrait-pwa` class on .app for any JS-driven conditional logic.
|
||||
// • Auto-close the sidebar when rotating into portrait-standalone mode.
|
||||
// • Auto-scroll the player into view when a video starts in portrait mode.
|
||||
// ============================================================================
|
||||
const _portraitPwaMQ = window.matchMedia
|
||||
? window.matchMedia('(display-mode: standalone) and (orientation: portrait)')
|
||||
: { matches: false, addEventListener: () => {} };
|
||||
|
||||
function isPortraitPWA() {
|
||||
return _portraitPwaMQ.matches;
|
||||
}
|
||||
|
||||
function applyPortraitPwaClass() {
|
||||
const app = document.querySelector('.app');
|
||||
if (!app) return;
|
||||
if (isPortraitPWA()) {
|
||||
app.classList.add('portrait-pwa');
|
||||
} else {
|
||||
app.classList.remove('portrait-pwa');
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll the player pane to the top of the scrollable .body so it's visible.
|
||||
// Only fires when in portrait-standalone mode where .body is the scroll root.
|
||||
function scrollPlayerIntoViewPortrait() {
|
||||
if (!isPortraitPWA()) return;
|
||||
const body = document.querySelector('.body');
|
||||
if (body) {
|
||||
body.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
function setupPortraitPwaWatcher() {
|
||||
applyPortraitPwaClass();
|
||||
_portraitPwaMQ.addEventListener('change', () => {
|
||||
applyPortraitPwaClass();
|
||||
// Rotating into portrait-standalone: close any open sidebar drawer so the
|
||||
// full-width layout isn't obscured.
|
||||
if (isPortraitPWA()) closeSidebar();
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Events
|
||||
// ============================================================================
|
||||
@@ -2623,6 +2672,7 @@ async function boot() {
|
||||
wirePlayerEvents();
|
||||
wireUI();
|
||||
wireShortcutHelp();
|
||||
setupPortraitPwaWatcher();
|
||||
try {
|
||||
const loaded = await API.loadData();
|
||||
if (loaded && typeof loaded === 'object') {
|
||||
|
||||
@@ -1755,3 +1755,24 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); }
|
||||
.ph-logo { width: 68px; height: 68px; font-size: 26px; }
|
||||
.hero-tagline { font-size: 13px; }
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* PORTRAIT PWA — JS-class mirror (.portrait-pwa on .app)
|
||||
*
|
||||
* The JS layer (setupPortraitPwaWatcher in app.js) adds / removes
|
||||
* `.portrait-pwa` on `.app` whenever the media query fires, giving a CSS hook
|
||||
* that works inside JS-conditional code paths (e.g. scroll position checks,
|
||||
* layout reads) without re-evaluating matchMedia inline.
|
||||
*
|
||||
* These selectors intentionally duplicate nothing from the @media block above
|
||||
* — they only fill gaps that require the JS class (e.g. interaction states
|
||||
* that can't be expressed purely with CSS media queries).
|
||||
* ========================================================================== */
|
||||
.portrait-pwa .body {
|
||||
/* Reinforce scroll snapping so the player stays at the viewport top after
|
||||
JS scrolls to it; helps on iOS Safari where scroll restoration can fight. */
|
||||
scroll-snap-type: y proximity;
|
||||
}
|
||||
.portrait-pwa .player-pane {
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user