feat(player): fullscreen "Listening Room" mode

Toggleable theater-scale player view that takes over the viewport
and amplifies the existing Studio Reference aesthetic — same fonts,
same copper/ink palette, just dialed up for immersive listening.

Layout & typography:
- Two-column centerfold: massive vinyl stage left (clamp(., 72vh, 720px)),
  editorial column right with Fraunces italic title at clamp(48px, 6.4vw,
  112px), Geist Mono console-style metadata strip, oversized timecodes,
  full-width amplitude spectrum.
- Mobile / portrait flips to vertical theater (vinyl top, masthead+
  transport below) at <=900px or any portrait orientation.

Ambient bloom:
- Duplicate of #album-art rendered behind everything at blur(110px)
  saturate(1.6) opacity(0.42) — paints the room in the record's color.
  Slow 28s drift animation. Light-theme variant at lower opacity.
- MutationObserver keeps bloom art in sync as tracks change.
- Vignette + edge darkening + subtle paper-grain veil frame the stage.

Interaction:
- Header button (corner-arrows-out icon) toggles; pressing 'F' anywhere
  outside text inputs also toggles; ESC exits.
- Native Fullscreen API requested as best-effort sugar on top of the
  CSS overlay (works on TV / tablet); CSS overlay alone covers the
  CSS-only fallback case (iOS Safari, embedded webviews).
- fullscreenchange listener mirrors OS-level exit back into the overlay.
- Auto-hide chrome + cursor after 2.5s idle, restored on mousemove.
- Focus moves to play/pause on enter; restored to invoking element on
  exit.
- Hides mini-player, tab bar, header, folio marks, and other tabs while
  active.

Motion:
- 320ms fade-in for the stage, 600ms vinyl rise, 1.4s bloom-in,
  staggered 80ms ladder for kicker -> title -> byline -> album -> meta
  -> spectrum -> transport. prefers-reduced-motion disables all.

i18n:
- player.fullscreen / player.fullscreen.exit / player.fullscreen.exit_short
  added to en.json and ru.json.

Files: index.html (header button + fs-chrome strip + fs-bloom layer),
styles.css (~360-line fullscreen block at end, scoped to body.is-
fullscreen-player), player.js (toggle + init + idle/key/observer
plumbing, ~170 lines), app.js (import + window export + init call).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 14:47:53 +03:00
parent 2a474ea52c
commit 59840a1190
6 changed files with 668 additions and 0 deletions
+23
View File
@@ -100,6 +100,10 @@
<button class="header-btn" onclick="toggleDynamicBackground()" data-i18n-title="player.background" title="Dynamic background" aria-label="Dynamic background" id="bgToggle">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 3v10.55A4 4 0 1 0 14 17V7h4V3h-6z"/></svg>
</button>
<button class="header-btn" onclick="togglePlayerFullscreen()" data-i18n-title="player.fullscreen" title="Fullscreen player" aria-label="Fullscreen player" id="fullscreenToggle">
<svg id="fullscreen-icon-enter" viewBox="0 0 24 24"><path fill="currentColor" d="M5 5h5v2H7v3H5V5zm9 0h5v5h-2V7h-3V5zm0 14v-2h3v-3h2v5h-5zM5 14h2v3h3v2H5v-5z"/></svg>
<svg id="fullscreen-icon-exit" viewBox="0 0 24 24" style="display:none"><path fill="currentColor" d="M8 8H5v2H3V6a1 1 0 0 1 1-1h4v3zm8 0h3v2h2V6a1 1 0 0 0-1-1h-4v3zm0 8h3v-2h2v4a1 1 0 0 1-1 1h-4v-3zM8 16H5v-2H3v4a1 1 0 0 0 1 1h4v-3z"/></svg>
</button>
<button class="header-btn" onclick="toggleTheme()" data-i18n-title="player.theme" title="Toggle theme" aria-label="Toggle theme" id="theme-toggle">
<svg id="theme-icon-sun" viewBox="0 0 24 24" style="display: none;">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
@@ -158,6 +162,25 @@
</div>
<div class="player-container" data-tab-content="player" role="tabpanel" id="panel-player">
<!-- Fullscreen-only chrome: floating top strip with kicker + exit. Auto-hides on idle. -->
<div class="fs-chrome" id="fsChrome" aria-hidden="true">
<div class="fs-chrome-mark">
<span class="fs-chrome-edition" data-i18n="header.edition">Studio Reference</span>
<span class="fs-chrome-sep">·</span>
<span class="fs-chrome-kicker" data-i18n="player.kicker">Now Playing</span>
</div>
<button class="fs-chrome-exit" onclick="togglePlayerFullscreen()" data-i18n-title="player.fullscreen.exit" title="Exit fullscreen" aria-label="Exit fullscreen">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M8 8H5v2H3V6a1 1 0 0 1 1-1h4v3zm8 0h3v2h2V6a1 1 0 0 0-1-1h-4v3zm0 8h3v-2h2v4a1 1 0 0 1-1 1h-4v-3zM8 16H5v-2H3v4a1 1 0 0 0 1 1h4v-3z"/></svg>
<span data-i18n="player.fullscreen.exit_short">Exit</span>
<kbd class="fs-chrome-kbd">ESC</kbd>
</button>
</div>
<!-- Ambient album-art bloom: paints the room in the record's color while in fullscreen -->
<div class="fs-bloom" id="fsBloom" aria-hidden="true">
<img id="fs-bloom-art" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3C/svg%3E" alt="">
</div>
<section class="now-playing player-layout">
<!-- Vinyl stage: cardstock sleeve + disc peeking out, plus tonearm -->