fix(plex): portal the full-screen player out of the page scroller

The player is `fixed left-0 top-0 z-50` but rendered from inside the page
scroller, so it only ever covered the viewport because that scroller happened
not to be a stacking context. Giving it the edge-fade made it one, and the mask
scoped the player's z-50 inside it: the rail, the side panel and the header
painted straight over the player, and its edges faded. Measured both ways, then
fixed where every other full-screen overlay here already sits — <body>.

Same for the Suspense fallback that stands in while the player chunk loads, and
for the floating draft save bar on the Configuration page. Swept the rest: every
other `fixed` element is either App-level or already portaled.
This commit is contained in:
2026-07-17 03:30:55 +02:00
parent 0ed58e3cb1
commit 9b4ee83ec8
4 changed files with 34 additions and 17 deletions
+10 -9
View File
@@ -30,18 +30,19 @@ export function useScrollFade(fadePx = 20): {
// a thread's messages decrypting, a list being filtered — changes neither the scroller's own
// box nor its scrollTop, so without the children nothing would re-evaluate the edges.
const ro = new ResizeObserver(update);
const observeAll = () => {
ro.disconnect();
ro.observe(el);
for (const child of Array.from(el.children)) ro.observe(child);
};
observeAll();
ro.observe(el);
for (const child of Array.from(el.children)) ro.observe(child);
// Children get SWAPPED, and a ResizeObserver holding a detached node simply never fires
// again. Measured: the Plex page opened with no bottom fade over a 3119px list, because the
// node observed at mount was the Suspense fallback the real page then replaced. (Same
// lazy-page race that broke BackToTop.) Re-observe whenever the child list changes.
const mo = new MutationObserver(() => {
observeAll();
// lazy-page race that broke BackToTop.) So follow the child list: the records name exactly
// which nodes arrived and left, which keeps this O(1) per change rather than re-observing
// every child of a long list on each append.
const mo = new MutationObserver((records) => {
for (const rec of records) {
for (const node of Array.from(rec.addedNodes)) if (node instanceof Element) ro.observe(node);
for (const node of Array.from(rec.removedNodes)) if (node instanceof Element) ro.unobserve(node);
}
update();
});
mo.observe(el, { childList: true });