diff --git a/frontend/fullscreen-orientation.css b/frontend/fullscreen-orientation.css index beb1b98..9cd9565 100644 --- a/frontend/fullscreen-orientation.css +++ b/frontend/fullscreen-orientation.css @@ -1,17 +1,36 @@ /* Shared fullscreen surface; inline player styling is unchanged. */ .orientation-fullscreen .fullscreen-hud[hidden] { display: none; } .orientation-fullscreen .fullscreen-hud { - position: absolute; z-index: 20; bottom: max(12px, env(safe-area-inset-bottom)); - left: max(12px, env(safe-area-inset-left)); right: max(12px, env(safe-area-inset-right)); - display: flex; align-items: center; gap: 8px; padding: 8px; border-radius: 12px; - background: rgb(0 0 0 / .8); color: white; + --hud-glass: color-mix(in srgb, var(--bg-1, #111) 74%, transparent); + --hud-ink: var(--text, #fff); + --hud-line: color-mix(in srgb, var(--line, #fff) 32%, transparent); + position: absolute; z-index: 20; + right: max(12px, env(safe-area-inset-right)); + bottom: max(12px, env(safe-area-inset-bottom)); + left: max(12px, env(safe-area-inset-left)); + display: flex; align-items: center; gap: 6px; + padding: 5px; border: 1px solid var(--hud-line); border-radius: 999px; + background: var(--hud-glass); color: var(--hud-ink); + -webkit-backdrop-filter: saturate(150%) blur(18px); + backdrop-filter: saturate(150%) blur(18px); + box-shadow: 0 8px 28px rgb(0 0 0 / .2); + opacity: 1; transition: opacity .22s ease; } +.orientation-fullscreen .fullscreen-hud.hud-hidden { opacity: 0; pointer-events: none; } .fullscreen-hud button { - min-height: 44px; padding: 8px 12px; border: 1px solid rgb(255 255 255 / .25); - border-radius: 8px; background: rgb(255 255 255 / .12); color: white; font: inherit; + display: grid; place-items: center; flex: none; + width: 48px; min-width: 48px; height: 48px; min-height: 48px; padding: 0; + border: 1px solid transparent; border-radius: 50%; + background: color-mix(in srgb, var(--hud-ink, #fff) 10%, transparent); + color: var(--hud-ink, #fff); font: inherit; cursor: pointer; + -webkit-tap-highlight-color: transparent; } -.fullscreen-hud button:focus-visible { outline: 2px solid white; outline-offset: 2px; } -.fullscreen-hud input { min-width: 40px; flex: 1; height: 44px; } +.fullscreen-hud button:hover { background: color-mix(in srgb, var(--hud-ink, #fff) 18%, transparent); } +.fullscreen-hud button:active { transform: scale(.94); } +.fullscreen-hud button svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } +.fullscreen-hud button .fill { fill: currentColor; stroke: none; } +.fullscreen-hud button:focus-visible { outline: 2px solid var(--accent, #fff); outline-offset: 2px; } +.fullscreen-hud input { min-width: 40px; min-height: 44px; height: 44px; flex: 1; margin: 0 4px; accent-color: var(--accent, #fff); touch-action: none; } .player-stage.orientation-fullscreen { background: #000; } .player-stage.orientation-overlay { position: fixed !important; z-index: 10000 !important; inset: 0 !important; @@ -39,7 +58,10 @@ .orientation-overlay[data-fullscreen-rotation="-90"] .fullscreen-hud { bottom: max(12px, env(safe-area-inset-right)); left: max(12px, env(safe-area-inset-bottom)); right: max(12px, env(safe-area-inset-top)); } -@media(max-width: 460px) { .fullscreen-hud { flex-wrap: wrap; } .fullscreen-hud input { flex-basis: 100%; order: -1; } .fullscreen-hud button { flex: 1; padding-inline: 6px; font-size: 12px; } } +@media (max-width: 460px) { + .orientation-fullscreen .fullscreen-hud { gap: 2px; padding: 3px; } + .fullscreen-hud button { width: 44px; min-width: 44px; height: 44px; min-height: 44px; } +} /* Raise existing ancestry instead of moving a playing video in WebKit. */ body.landscape-fs .orientation-ancestor { z-index: 10001 !important; transform: none !important; filter: none !important; overflow: visible !important; } body.landscape-fs { overflow: hidden; } diff --git a/frontend/fullscreen-orientation.js b/frontend/fullscreen-orientation.js index 05f8d5a..79c7a50 100644 --- a/frontend/fullscreen-orientation.js +++ b/frontend/fullscreen-orientation.js @@ -13,19 +13,32 @@ } function create(options) { const { stage, settings, persist, toggle, seek, state } = options; - let active = false, custom = false, native = false, locked = false, angle = 0, timer; + let active = false, custom = false, native = false, locked = false, angle = 0, timer, hideTimer; const ancestors = []; for (let el = stage.parentElement; el && el !== document.body; el = el.parentElement) ancestors.push(el); const hud = document.createElement('div'); hud.className = 'fullscreen-hud'; hud.hidden = true; - hud.innerHTML = ''; + hud.innerHTML = ''; stage.append(hud); const [play, orientation, exit] = hud.querySelectorAll('button'), range = hud.querySelector('input'); + function revealControls() { + if (!active) return; + hud.classList.remove('hud-hidden'); clearTimeout(hideTimer); + hideTimer = setTimeout(() => { + if (!hud.contains(document.activeElement)) hud.classList.add('hud-hidden'); + }, 3200); + } + stage.addEventListener('pointerdown', revealControls, { passive: true }); + stage.addEventListener('pointermove', event => { if (event.pointerType === 'mouse') revealControls(); }, { passive: true }); + hud.addEventListener('focusin', () => { clearTimeout(hideTimer); hud.classList.remove('hud-hidden'); }); + hud.addEventListener('focusout', () => setTimeout(revealControls, 0)); + hud.addEventListener('click', revealControls); play.onclick = toggle; exit.onclick = leave; function layout() { if (!active) return; angle = custom ? rotation(settings().fullscreenOrientation, innerWidth, innerHeight) : 0; stage.dataset.fullscreenRotation = String(angle); - orientation.textContent = `Orientation: ${normalize(settings().fullscreenOrientation)}`; + orientation.setAttribute('aria-label', `Change fullscreen orientation (currently ${normalize(settings().fullscreenOrientation)})`); + orientation.title = `Orientation: ${normalize(settings().fullscreenOrientation)}`; } async function orient() { if (locked) { screen.orientation?.unlock?.(); locked = false; } @@ -48,9 +61,9 @@ hud.addEventListener('pointerdown', event => event.stopPropagation()); hud.addEventListener('pointerup', event => event.stopPropagation()); function cleanup() { - active = false; custom = false; native = false; angle = 0; clearInterval(timer); + active = false; custom = false; native = false; angle = 0; clearInterval(timer); clearTimeout(hideTimer); ancestors.forEach(el => el.classList.remove('orientation-ancestor')); - hud.hidden = true; stage.classList.remove('orientation-overlay', 'orientation-fullscreen'); + hud.hidden = true; hud.classList.remove('hud-hidden'); stage.classList.remove('orientation-overlay', 'orientation-fullscreen'); delete stage.dataset.fullscreenRotation; document.body.classList.remove('landscape-fs'); if (locked) { screen.orientation?.unlock?.(); locked = false; } } @@ -60,7 +73,7 @@ } async function enter() { if (active) { leave(); return; } - active = true; ancestors.forEach(el => el.classList.add('orientation-ancestor')); stage.classList.add('orientation-fullscreen'); hud.hidden = false; + active = true; ancestors.forEach(el => el.classList.add('orientation-ancestor')); stage.classList.add('orientation-fullscreen'); hud.hidden = false; revealControls(); document.body.classList.add('landscape-fs'); try { if (!stage.requestFullscreen) throw new Error('Custom fullscreen'); @@ -70,7 +83,15 @@ if (!active) return; await orient(); if (!active) return; - timer = setInterval(() => { const s = state(); range.value = String(s.duration ? s.time / s.duration * 1000 : 0); play.textContent = s.paused ? 'Play' : 'Pause'; }, 250); + timer = setInterval(() => { + const s = state(); + range.value = String(s.duration ? s.time / s.duration * 1000 : 0); + play.setAttribute('aria-label', s.paused ? 'Play video' : 'Pause video'); + play.title = s.paused ? 'Play' : 'Pause'; + play.innerHTML = s.paused + ? '' + : ''; + }, 250); } document.addEventListener('fullscreenchange', () => { if (active && !document.fullscreenElement && native) cleanup(); }); document.addEventListener('keydown', event => { if (active && event.key === 'Escape') leave(); });