Player full-screen: edge-to-edge themed stage + theme toggle in stage
Two fixes from on-device testing: 1. Fill the screen. The earlier stage capped the device at min(96vw,168vh) and centred it, leaving big margins on wide phones. Now the device frame goes transparent/borderless at position:absolute inset:0 and the OLED grows (flex:1) so the unit fills the whole viewport edge to edge. 2. Follow light/dark/system in full-screen. The full-screen skin is the themed page gradient (light in light mode, dark in dark, OS-driven on system), and a theme toggle (◐/☀/☾ — same cycle + "metronome.theme" key as the main page) now sits beside the exit ✕, since the top bar that normally holds it is hidden in stage. The themed gradient is painted on the device element rather than the body because a position:fixed body doesn't propagate its background to the canvas (left a white area). The decorative PWR dot is hidden in stage so it doesn't sit under the floating controls. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e22f267b83
commit
d1bd996675
1 changed files with 40 additions and 27 deletions
67
player.html
67
player.html
|
|
@ -145,40 +145,49 @@
|
||||||
.hint{ font-size:11px; color:var(--muted) }
|
.hint{ font-size:11px; color:var(--muted) }
|
||||||
code{ background:var(--field-bg); border:1px solid var(--field-bd); border-radius:4px; padding:1px 5px; font-size:11px }
|
code{ background:var(--field-bg); border:1px solid var(--field-bd); border-radius:4px; padding:1px 5px; font-size:11px }
|
||||||
|
|
||||||
/* ---- full-screen "stage" mode ---- */
|
/* ---- full-screen "stage" mode: edge-to-edge, follows light/dark/system ---- */
|
||||||
.fs-exit{ display:none; position:fixed; top:14px; right:14px; z-index:80;
|
.fs-ctrl{ display:none; position:fixed; top:max(12px,env(safe-area-inset-top)); z-index:80;
|
||||||
background:rgba(18,21,28,.7); color:#e7edf5; border:1px solid rgba(255,255,255,.28);
|
background:rgba(127,139,154,.16); color:var(--txt); border:1px solid rgba(127,139,154,.5);
|
||||||
border-radius:50%; width:40px; height:40px; font-size:17px; line-height:1; cursor:pointer;
|
border-radius:50%; width:40px; height:40px; font-size:17px; line-height:1; cursor:pointer;
|
||||||
backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px) }
|
backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px) }
|
||||||
.fs-exit:hover{ background:rgba(40,48,62,.9) }
|
.fs-ctrl:hover{ background:rgba(127,139,154,.32) }
|
||||||
|
#fsExit{ right:max(12px,env(safe-area-inset-right)) }
|
||||||
|
#fsThemeBtn{ right:calc(max(12px,env(safe-area-inset-right)) + 50px) }
|
||||||
.rotate-hint{ display:none }
|
.rotate-hint{ display:none }
|
||||||
|
|
||||||
body.stage{ position:fixed; inset:0; padding:2.5vmin; gap:0; justify-content:center; overflow:hidden }
|
/* the device frame goes transparent → the themed page background IS the full-screen
|
||||||
body.stage .topbar, body.stage .panel{ display:none }
|
skin (light in light mode, dark in dark mode); children flex to fill the screen */
|
||||||
body.stage .fs-exit{ display:block }
|
body.stage{ position:fixed; inset:0; padding:0; gap:0; overflow:hidden }
|
||||||
body.stage .device{ max-width:none; width:min(96vw, 168vh); margin:auto; border-radius:min(3vmin,22px) }
|
body.stage .topbar, body.stage .panel, body.stage .grille{ display:none }
|
||||||
/* enlarge the glanceable bits with viewport-relative units */
|
body.stage .fs-ctrl{ display:block }
|
||||||
body.stage .screen{ padding:2vh 3vw }
|
body.stage .device{ position:absolute; inset:0; width:auto; max-width:none; margin:0;
|
||||||
|
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); border:none; border-radius:0; box-shadow:none;
|
||||||
|
display:flex; flex-direction:column;
|
||||||
|
padding:max(2.2vh,env(safe-area-inset-top)) max(4vw,env(safe-area-inset-right))
|
||||||
|
max(2.4vh,env(safe-area-inset-bottom)) max(4vw,env(safe-area-inset-left)) }
|
||||||
|
body.stage .device::before, body.stage .device::after, body.stage .screw{ display:none }
|
||||||
|
body.stage .brandrow{ flex:0 0 auto; margin:0 0 2vh }
|
||||||
|
body.stage .pwr{ display:none } /* declutter the corner the floating controls sit in */
|
||||||
|
body.stage .logo .model, body.stage .knob-wrap{ color:var(--muted) }
|
||||||
|
body.stage .screen{ flex:1 1 auto; display:flex; flex-direction:column; justify-content:space-between; padding:2.4vh 3vw }
|
||||||
body.stage .scr-top{ font-size:2.8vh }
|
body.stage .scr-top{ font-size:2.8vh }
|
||||||
body.stage .scr-top .tempo{ font-size:3.4vh }
|
body.stage .scr-top .tempo{ font-size:3.6vh }
|
||||||
body.stage .scr-top .tempo b{ font-size:8.5vh }
|
body.stage .scr-top .tempo b{ font-size:9vh }
|
||||||
body.stage .scr-name{ font-size:6vh; margin:1.4vh 0 1.2vh }
|
body.stage .scr-name{ font-size:7vh; margin:0 }
|
||||||
body.stage .scr-bot{ font-size:2.8vh }
|
body.stage .scr-bot{ font-size:2.8vh }
|
||||||
body.stage .leds{ gap:1.6vmin; margin:2.6vh 0 1vh }
|
body.stage .leds{ flex:0 0 auto; gap:1.8vmin; margin:2.4vh 0 0 }
|
||||||
body.stage .led{ width:4.4vmin; height:4.4vmin }
|
body.stage .led{ width:4.6vmin; height:4.6vmin }
|
||||||
body.stage .controls{ gap:1.6vmin; margin-top:2.2vh }
|
body.stage .controls{ flex:0 0 auto; gap:1.8vmin; margin-top:2.2vh }
|
||||||
body.stage .controls .btn{ font-size:2.6vh; padding:1.5vh 1.8vw; min-width:7vw }
|
body.stage .controls .btn{ font-size:2.6vh; padding:1.6vh 2vw; min-width:8vw }
|
||||||
body.stage .controls .btn.play{ min-width:11vw; font-size:3.4vh }
|
body.stage .controls .btn.play{ min-width:12vw; font-size:3.6vh }
|
||||||
body.stage .controls .btn small{ font-size:1.3vh }
|
body.stage .controls .btn small{ font-size:1.3vh }
|
||||||
body.stage .brandrow{ margin-bottom:2vh }
|
|
||||||
body.stage .grille{ display:none }
|
|
||||||
|
|
||||||
/* portrait while staged (mainly iPhone, which can't lock) → prompt to rotate */
|
/* portrait while staged (mainly iPhone, which can't lock) → prompt to rotate */
|
||||||
@media (orientation: portrait){
|
@media (orientation: portrait){
|
||||||
body.stage .device{ filter:blur(3px) brightness(.5); pointer-events:none }
|
body.stage .device{ filter:blur(3px) brightness(.6); pointer-events:none }
|
||||||
body.stage .rotate-hint{ display:flex; position:fixed; inset:0; z-index:90;
|
body.stage .rotate-hint{ display:flex; position:fixed; inset:0; z-index:90;
|
||||||
flex-direction:column; align-items:center; justify-content:center; gap:18px;
|
flex-direction:column; align-items:center; justify-content:center; gap:18px;
|
||||||
background:var(--bg2); color:var(--txt); font-size:20px; text-align:center; padding:24px }
|
background:var(--bg1); color:var(--txt); font-size:20px; text-align:center; padding:24px }
|
||||||
body.stage .rotate-hint .rh-icon{ font-size:64px; line-height:1; color:var(--cyan) }
|
body.stage .rotate-hint .rh-icon{ font-size:64px; line-height:1; color:var(--cyan) }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -242,7 +251,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- stage-mode overlays (only visible in full-screen "stage" mode) -->
|
<!-- stage-mode overlays (only visible in full-screen "stage" mode) -->
|
||||||
<button id="fsExit" class="fs-exit" title="Exit full screen (Esc)" aria-label="Exit full screen">✕</button>
|
<button id="fsThemeBtn" class="fs-ctrl" title="Theme (system / light / dark)" aria-label="Toggle theme">☀</button>
|
||||||
|
<button id="fsExit" class="fs-ctrl" title="Exit full screen (Esc)" aria-label="Exit full screen">✕</button>
|
||||||
<div id="rotateHint" class="rotate-hint">
|
<div id="rotateHint" class="rotate-hint">
|
||||||
<span class="rh-icon">⟳</span>
|
<span class="rh-icon">⟳</span>
|
||||||
<span>Rotate your device to landscape</span>
|
<span>Rotate your device to landscape</span>
|
||||||
|
|
@ -417,10 +427,13 @@ function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); retu
|
||||||
function applyTheme(p){
|
function applyTheme(p){
|
||||||
try{ localStorage.setItem("metronome.theme",p); }catch(e){}
|
try{ localStorage.setItem("metronome.theme",p); }catch(e){}
|
||||||
document.documentElement.dataset.theme = effectiveTheme(p);
|
document.documentElement.dataset.theme = effectiveTheme(p);
|
||||||
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾";
|
const glyph = p==="system" ? "◐" : p==="light" ? "☀" : "☾";
|
||||||
$("themeBtn").title = "Theme: "+p+" (click to cycle: system → light → dark)";
|
const title = "Theme: "+p+" (click to cycle: system → light → dark)";
|
||||||
|
for(const id of ["themeBtn","fsThemeBtn"]){ const b=$(id); if(b){ b.textContent=glyph; b.title=title; } }
|
||||||
}
|
}
|
||||||
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
|
const cycleTheme = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
|
||||||
|
$("themeBtn").onclick = cycleTheme;
|
||||||
|
$("fsThemeBtn").onclick = cycleTheme;
|
||||||
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
|
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
|
||||||
applyTheme(themePref());
|
applyTheme(themePref());
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue