Measure the bottom bars so the section rail is never hidden behind the mini player
This commit is contained in:
@@ -5440,7 +5440,7 @@ const SectionRail = (() => {
|
|||||||
{ id: 'related', sel: '#relatedPanel', icon: '🔗', label: 'Related', fold: true },
|
{ id: 'related', sel: '#relatedPanel', icon: '🔗', label: 'Related', fold: true },
|
||||||
{ id: 'list', sel: '#listPane', icon: '📋', label: 'List' },
|
{ id: 'list', sel: '#listPane', icon: '📋', label: 'List' },
|
||||||
];
|
];
|
||||||
const st = { active: '', btns: new Map(), labelTimer: null, raf: 0 };
|
const st = { active: '', btns: new Map(), labelTimer: null, raf: 0, bottom: -1 };
|
||||||
const rail = () => document.getElementById('sectionRail');
|
const rail = () => document.getElementById('sectionRail');
|
||||||
const folds = () => (data.settings.foldedCards ||= {});
|
const folds = () => (data.settings.foldedCards ||= {});
|
||||||
const isFolded = (id) => !!folds()[id];
|
const isFolded = (id) => !!folds()[id];
|
||||||
@@ -5524,6 +5524,7 @@ const SectionRail = (() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.classList.remove('hidden');
|
r.classList.remove('hidden');
|
||||||
|
placeRail();
|
||||||
paint();
|
paint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5577,6 +5578,29 @@ const SectionRail = (() => {
|
|||||||
return best || near;
|
return best || near;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The mini player and the bottom nav are both position:fixed bottom:0, and
|
||||||
|
// either can be absent (nothing playing / landscape). Measure whatever is
|
||||||
|
// actually pinned down there and park the rail above it, rather than
|
||||||
|
// guessing an offset that is wrong on the next phone.
|
||||||
|
function placeRail() {
|
||||||
|
const r = rail();
|
||||||
|
if (!r || r.classList.contains('hidden')) return;
|
||||||
|
let occupied = 0;
|
||||||
|
for (const id of ['bottomNav', 'miniBar']) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (!el || el.classList.contains('hidden')) continue;
|
||||||
|
const box = el.getBoundingClientRect();
|
||||||
|
if (!box.height) continue;
|
||||||
|
// Only count it when it really is pinned to the bottom edge.
|
||||||
|
if (box.bottom >= window.innerHeight - 4) occupied = Math.max(occupied, window.innerHeight - box.top);
|
||||||
|
}
|
||||||
|
const want = Math.round(occupied + 14);
|
||||||
|
if (st.bottom !== want) {
|
||||||
|
st.bottom = want;
|
||||||
|
r.style.setProperty('--rail-bottom', want + 'px');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function paint() {
|
function paint() {
|
||||||
for (const [id, btn] of st.btns) {
|
for (const [id, btn] of st.btns) {
|
||||||
btn.classList.toggle('active', id === st.active);
|
btn.classList.toggle('active', id === st.active);
|
||||||
@@ -5593,6 +5617,7 @@ const SectionRail = (() => {
|
|||||||
// would leave the rail with nothing highlighted. build() is a no-op
|
// would leave the rail with nothing highlighted. build() is a no-op
|
||||||
// unless the set of visible cards actually changed.
|
// unless the set of visible cards actually changed.
|
||||||
build();
|
build();
|
||||||
|
placeRail();
|
||||||
const id = currentId();
|
const id = currentId();
|
||||||
if (id && id !== st.active) { st.active = id; paint(); }
|
if (id && id !== st.active) { st.active = id; paint(); }
|
||||||
});
|
});
|
||||||
@@ -5607,7 +5632,22 @@ const SectionRail = (() => {
|
|||||||
// keeps working when the scroller changes between browser and installed
|
// keeps working when the scroller changes between browser and installed
|
||||||
// PWA (different elements scroll in each).
|
// PWA (different elements scroll in each).
|
||||||
document.addEventListener('scroll', onScroll, { passive: true, capture: true });
|
document.addEventListener('scroll', onScroll, { passive: true, capture: true });
|
||||||
window.addEventListener('resize', () => { build(); onScroll(); });
|
window.addEventListener('resize', () => { build(); placeRail(); onScroll(); });
|
||||||
|
// The mini player appears AFTER the scroll that reveals it, so a scroll
|
||||||
|
// handler alone measures the layout as it was a moment ago and parks the
|
||||||
|
// rail underneath it. Watch the two bottom bars for real.
|
||||||
|
const watch = ['bottomNav', 'miniBar'].map((id) => document.getElementById(id)).filter(Boolean);
|
||||||
|
if (watch.length && typeof MutationObserver !== 'undefined') {
|
||||||
|
// The mini bar slides in over 0.25s, so a measurement taken the instant
|
||||||
|
// it appears catches it mid-animation and lands the rail a few pixels
|
||||||
|
// too low. Measure now and again once it has settled.
|
||||||
|
const mo = new MutationObserver(() => { placeRail(); setTimeout(placeRail, 320); });
|
||||||
|
watch.forEach((el) => mo.observe(el, { attributes: true, attributeFilter: ['class', 'style'] }));
|
||||||
|
}
|
||||||
|
if (watch.length && typeof ResizeObserver !== 'undefined') {
|
||||||
|
const ro = new ResizeObserver(() => placeRail());
|
||||||
|
watch.forEach((el) => ro.observe(el));
|
||||||
|
}
|
||||||
build();
|
build();
|
||||||
onScroll();
|
onScroll();
|
||||||
}
|
}
|
||||||
@@ -5616,6 +5656,7 @@ const SectionRail = (() => {
|
|||||||
function refresh() {
|
function refresh() {
|
||||||
SECTIONS.forEach(applyFold);
|
SECTIONS.forEach(applyFold);
|
||||||
build();
|
build();
|
||||||
|
placeRail();
|
||||||
onScroll();
|
onScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4230,6 +4230,12 @@ body.landscape-fs .player-stage { touch-action: none; } /* fullscreen (real or t
|
|||||||
/* Phones only — on desktop the whole page is visible at once. */
|
/* Phones only — on desktop the whole page is visible at once. */
|
||||||
@media (min-width: 861px) { .sec-rail { display: none !important; } }
|
@media (min-width: 861px) { .sec-rail { display: none !important; } }
|
||||||
@media (max-width: 860px) {
|
@media (max-width: 860px) {
|
||||||
/* clear of the bottom nav / mini player */
|
/* Sits above the bottom nav and the mini player. Both are position:fixed
|
||||||
.sec-rail { top: auto; bottom: 132px; transform: none; }
|
bottom:0 and either can be absent, so SectionRail.placeRail() measures
|
||||||
|
them and overrides --rail-bottom; this is only the pre-measure default. */
|
||||||
|
.sec-rail {
|
||||||
|
top: auto;
|
||||||
|
bottom: var(--rail-bottom, 150px);
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user