Keep Glass Stage section navigation and panel content clear of playback chrome

This commit is contained in:
Jonathan Sykes
2026-10-03 21:45:34 +08:00
parent d3f5133572
commit 3c0adb2577
7 changed files with 166 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
/* Glass Stage section shortcuts participate in layout; Classic keeps its rail. */
(function (root) {
'use strict';
function bottomClearance(viewport, boxes, gap = 16) {
const occupied = boxes.reduce((height, b) => b.fixed && b.height > 0 && b.top < viewport && b.bottom > 0 ? Math.max(height, viewport - b.top) : height, 0);
return Math.ceil(occupied + (occupied ? gap : 0));
}
function mount(doc) {
const win = doc.defaultView, html = doc.documentElement, rail = doc.getElementById('sectionRail'), panel = doc.getElementById('listPane');
if (!rail || !panel) return;
const original = doc.createComment('section rail original position'); rail.before(original);
let frame = 0;
function active() { return html.dataset.layout === 'glass-stage'; }
function schedule() { if (!frame) frame = win.requestAnimationFrame(measure); }
function set(name, value) { if (html.style.getPropertyValue(name) !== value) html.style.setProperty(name, value); }
function box(el) { const r = el.getBoundingClientRect(); return { top: r.top, bottom: r.bottom, height: el.classList.contains('hidden') ? 0 : r.height, fixed: win.getComputedStyle(el).position === 'fixed' }; }
function measure() {
frame = 0; if (!active()) return;
const nav = doc.getElementById('bottomNav'), mini = doc.getElementById('miniBar');
const navBox = box(nav);
set('--glass-nav-height', `${Math.ceil(navBox.height)}px`);
// Read again after nav-height changes: the mini player's bottom depends on it.
set('--glass-bottom-clearance', `${bottomClearance(win.innerHeight, [box(nav), box(mini)])}px`);
}
function sync() {
if (active()) { if (rail.parentElement !== panel) panel.prepend(rail); schedule(); }
else {
if (rail.parentNode !== original.parentNode) original.after(rail);
html.style.removeProperty('--glass-nav-height'); html.style.removeProperty('--glass-bottom-clearance');
}
}
const observer = new MutationObserver(sync); observer.observe(html, { attributes: true, attributeFilter: ['data-layout'] });
const chrome = [doc.getElementById('bottomNav'), doc.getElementById('miniBar')].filter(Boolean);
const changes = new MutationObserver(schedule); chrome.forEach(el => changes.observe(el, { attributes: true, attributeFilter: ['class', 'style'] }));
const resize = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null; chrome.forEach(el => resize?.observe(el));
win.addEventListener('resize', schedule); win.addEventListener('orientationchange', schedule); win.visualViewport?.addEventListener('resize', schedule);
sync();
}
if (typeof module !== 'undefined') module.exports = { bottomClearance };
else if (root.document.readyState === 'loading') root.document.addEventListener('DOMContentLoaded', () => mount(root.document), { once: true });
else mount(root.document);
}(typeof window !== 'undefined' ? window : globalThis));