first attempt

This commit is contained in:
kdee committed 2026-09-23 12:31:15 -07:00
1 parent 5883a664d0
commit 36df765000
1 file changed
+75 -14
+75 -14
View File
@@ -51,15 +51,64 @@
}
`;
// Jellyfin's own nav chrome has moved before (left drawer -> top app bar in
// 12.x) and will likely move again. Rather than hardcoding selectors that
// break on every redesign, find whatever is fixed/sticky and anchored near
// the top of the viewport at runtime, and push it down (Y) by the banner's
// height so the banner never just sits in front of (Z) live nav chrome.
const originalMarginTop = new WeakMap();
const offsetElements = new Set();
function isTopBarCandidate(el) {
if (el.id === BANNER_ID || el.closest('#' + BANNER_ID)) return false;
const style = getComputedStyle(el);
if (style.position !== 'fixed' && style.position !== 'sticky') return false;
if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) return false;
const rect = el.getBoundingClientRect();
if (rect.width < window.innerWidth * 0.5) return false; // must span most of the viewport width
if (rect.height <= 0 || rect.height > window.innerHeight * 0.6) return false; // skip full-page overlays/modals
if (rect.top > 400) return false; // must be anchored near the top of the page
return true;
}
function findTopBars() {
return Array.prototype.filter.call(document.body.querySelectorAll('*'), isTopBarCandidate);
}
function pushDownTopBars(offsetPx) {
const candidates = new Set(findTopBars());
// Restore anything we previously offset that's no longer a candidate
// (e.g. replaced by the SPA router on navigation).
offsetElements.forEach(function (el) {
if (!candidates.has(el) || !document.contains(el)) {
if (originalMarginTop.has(el)) {
el.style.marginTop = originalMarginTop.get(el);
}
offsetElements.delete(el);
}
});
candidates.forEach(function (el) {
if (!originalMarginTop.has(el)) {
originalMarginTop.set(el, el.style.marginTop || '');
}
const base = originalMarginTop.get(el);
el.style.marginTop = offsetPx > 0
? 'calc(' + (base || '0px') + ' + ' + offsetPx + 'px)'
: base;
offsetElements.add(el);
});
if (offsetPx > 0 && candidates.size === 0) {
console.warn('[custom-banner] No fixed top navigation elements detected to push down; banner may overlap the menu.');
}
}
function applyOffset() {
const banner = document.getElementById(BANNER_ID);
const offset = banner ? (banner.offsetHeight + 'px') : '';
const header = document.querySelector('.headerTop');
const drawer = document.querySelector('.mainDrawer');
const sections = document.querySelector('.homeSectionsContainer');
if (header) header.style.marginTop = offset;
if (drawer) drawer.style.marginTop = offset;
if (sections) sections.style.marginTop = offset;
const offset = banner ? banner.offsetHeight : 0;
pushDownTopBars(offset);
}
function isHomePage() {
@@ -101,15 +150,27 @@
style.textContent = BANNER_CSS;
document.head.appendChild(style);
// React to SPA navigation
window.addEventListener('hashchange', update);
// Watch for Jellyfin's deferred body rendering (SPA bootstraps after DOMContentLoaded)
// subtree: true catches .headerTop wherever it appears in the DOM
const observer = new MutationObserver(function () {
// Debounce: the SPA's own DOM churn (list virtualization, image loads, etc.)
// can fire many mutations per second. We don't need per-frame precision for
// nav-bar layout, so coalesce bursts instead of rescanning the whole DOM
// on every single mutation.
let debounceTimer = null;
function scheduleUpdate(delay) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function () {
update();
applyOffset();
});
}, delay || 150);
}
// React to SPA navigation
window.addEventListener('hashchange', update);
window.addEventListener('resize', function () { scheduleUpdate(); });
// Watch for Jellyfin's deferred body rendering (SPA bootstraps after
// DOMContentLoaded) and re-run on DOM churn, since the nav bar can be
// unmounted/remounted by the SPA router.
const observer = new MutationObserver(function () { scheduleUpdate(); });
document.addEventListener('DOMContentLoaded', function () {
observer.observe(document.body, { childList: true, subtree: true });
update();