the periscopes never built on this machine: bounded idle gate, and the fog rides the scroller's timeline instead of chasing it

THE HEADLINE, and it invalidates a lot of earlier reasoning about the glass
bar: all three periscopes were gated on an idle callback granting a >=15ms
slice, and with any frame continuously pending this 60Hz laptop caps
timeRemaining at ~13.5ms. The gate NEVER passed — traced hundreds of
consecutive refusals — so the fog never built, on any pane, all session. What
the bar had been showing all along was no fog at all: a thin tint over the raw
page. That is why Kyle read the top bar as "so light compared to the left and
right sidebar" (on album pages the raw page there is the banner, deliberately
bled up bright), and why the Now Playing blur "goes away whenever the next
track plays and its glitchy" — every track change clears that pane's fog for
rebuild, and the rebuild starved forever. The skip is now bounded: after eight
starved retries (~1-2s of honest waiting) it builds anyway. Same three lines in
GlassBar, Sidebar and NowPlaying — the pattern is copied, so a fix to one is
not a fix.

THE BAND'S TINT, once there was a fog to judge. The bar wore
rgba(20,20,24,0.28): a non-black colour that adds ~6 luma of its own light, at
a net 0.676 against the panes' 0.7075, so it read lighter than the panes by
construction. It now wears the panes' exact frost — pure black 0.35 over the
0.55 canvas — applied as .peri::after, because the bar's own background paints
UNDER .peri and cannot tint the fog (measured: an 0.85 background moved the
band 34.6 -> 33.5). Same material as the sidebar and Now Playing bands;
brightness then follows the wallpaper for all of them alike, which is why they
measure 20-36 across one screen and no absolute number could have matched.

THE FOG RIDES THE COMPOSITOR NOW. It chased scroll events on the main thread
and landed frames late ("the content behind the glass bar is delayed with
scrolling"). .pane-shell publishes the scroller as a named timeline
(--pane-scroll) and the clone animates against it, so the ride equals -scrollTop
with the main thread nowhere in the loop. Verified as a live ScrollTimeline
animation tracking scrollTop exactly. The rules live in global.css on purpose:
Svelte's -global- keyframes prefix stripping desynced animation-name from
@keyframes when they lived in the component, and getAnimations() showed it dead.
The old event sync stays as the no-timeline-support fallback.

THE SEARCH WASH reaches the top of the bar again. Its contract is that "the bar
frosts over colour rather than over black", but the frost veil dimmed the whole
fog and left a dark step where bright wash met the band; and the blur kernel at
the band's top edge sampled past the wash's own top edge into nothing, fading
the colour out over the first ~20px — the dark strip Kyle photographed twice.
The cloned wash is boosted to compensate for the veil exactly, and bled into the
fog's 48px overscan so the kernel always has colour to sample. The album banner
never showed this because its box already bleeds -70px above the page.

Judged on OS-level captures throughout (CopyFromScreen): CDP screenshots cannot
see the acrylic and led every wrong turn in this file's history. 98/98 tests.
This commit is contained in:
KeelrLeek committed 2026-08-31 05:53:42 -05:00
1 parent 15746893a9
commit 5e2ad18269
5 files changed
+148 -5

No files matched your search

+10
View File
@@ -309,6 +309,11 @@
artwork behind the bar (search) has to start below it, or its first
heading sits under the frost at rest. */
--bar-h: 46px;
/* Publishes the scroller's timeline to the glass bar's fog, which
is a SIBLING of the scroller — the fog's ride animates against
this instead of chasing scroll events on the main thread (the
chase lagged visibly on the 60Hz laptop; see GlassBar). */
timeline-scope: --pane-scroll;
}
.content {
@@ -323,6 +328,11 @@
away with the content, keeping the seam colors identical at every
scroll position. */
background-attachment: local;
/* The named timeline the fog rides (scope on .pane-shell above).
The artist page overwrites this with its own INLINE
scroll-timeline for its hero rides — that page is painted, has
no fog, and needs no pane timeline. */
scroll-timeline: --pane-scroll block;
overflow-y: auto;
overflow-anchor: none;
/* Main scroll pane: no elastic rubber-band at its edges. Inner regions
+95 -3
View File
@@ -61,6 +61,9 @@
let pending = 0;
let recloneGen = 0;
let lastRecloneMs = 4;
// Consecutive idle callbacks refused for a too-thin slice — see the
// bounded gate in queueReclone.
let thinSlices = 0;
// Observer mode. On a painted page the full subtree+src watch fed a
// guaranteed no-op pipeline: the artist hero swaps its src every 20s
// and covers stream in, and every batch armed the debounce, burned an
@@ -139,6 +142,21 @@
// the band fogged VOID over the hero. Shed it; the animation-none
// rule below drops all cloned rides to their base styles.
c.style.removeProperty("scroll-timeline");
// The fog rides the SCROLLER'S timeline on the compositor
// (--pane-scroll, published by .pane-shell): progress is
// scrollTop/range and the keyframe span is -range, so the
// ride equals -scrollTop at every point with the main thread
// nowhere in the loop — sync()'s per-event transform chased
// scroll a few frames late on the 60Hz laptop (Kyle,
// 2026-08-30: "the content behind the glass bar is delayed").
// sync() stays as the fallback: a running animation outranks
// its inline transform, and without timeline support the
// inline write is what remains.
c.style.setProperty(
"--peri-range",
Math.max(0, view.scrollHeight - view.clientHeight) + "px",
);
c.classList.add("peri-ride");
// Canvases clone BLANK — dead weight in the fog.
c.querySelectorAll("canvas").forEach((n) => n.remove());
const banner = c.querySelector(".banner");
@@ -196,12 +214,23 @@
// build. A 17px-blurred band going stale for the extra beat
// is invisible; a clone task landing mid-scroll is the
// measured freeze-then-teleport stall.
if (document.hidden) return;
if (!appScrollIdleFor(1000)) return queueReclone();
if ("requestIdleCallback" in window) {
requestIdleCallback((deadline) => {
if (!appScrollIdleFor(1000)) return queueReclone();
if (deadline && deadline.timeRemaining() < 15) return queueReclone();
// The thin-slice skip is BOUNDED. With any frame
// continuously pending, Chromium caps timeRemaining
// at the frame budget minus rendering — measured
// 2026-08-30 on the 60Hz laptop: ~13.5ms, hundreds
// of callbacks in a row — so the old hard <15 gate
// never passed and the fog NEVER built: the band
// rested as a thin veil over the raw page, the
// "lighter pane" Kyle reported. Eight skips is
// ~1-2s of honest waiting; after that, one possibly-
// tight build beats no fog for the whole session.
if (deadline && deadline.timeRemaining() < 15 && ++thinSlices < 8) {
return queueReclone();
}
thinSlices = 0;
reclone();
});
} else {
@@ -393,6 +422,42 @@
:global(:root.glass) .glassbar {
backdrop-filter: none;
-webkit-backdrop-filter: none;
/* THE PANES' OWN FROST, exactly: pure black 0.35 over the 0.55
canvas nets the same 0.7075 the sidebar, content pane and Now
Playing stand on — same acrylic transmission, and none of the
solid-mode tint's self-light (20,20,24 at 0.28 adds ~6 luma of
its own, measured on-screen 2026-08-30, band 26 vs rail 32).
The band is the same material as the panes by construction;
brightness then follows the wallpaper for both alike. */
background: rgba(0, 0, 0, 0.35);
}
/* Match the SIDE PANES' brightness by matching their TRANSPARENCY, not
by painting over it. On screen every pane glows with the acrylic
behind it (~0.29 of the desktop through the 0.7075 frost) — a dark
veil over the band blocks that glow and reads as a black slab beside
lit panes (Kyle, 2026-08-30, tried and rejected the same day; CDP
captures cannot see the acrylic, which is how it shipped). So the
band keeps the panes' own alpha stack — tint 0.28 over the canvas —
and the FOG CONTENT is dimmed instead: the album banner bleeding up
bright was the only thing separating this band from the sidebar's,
whose fogged rows are already dark. The ghost of the page stays,
the wallpaper glow stays, the brightness difference goes. */
/* The fog at full strength with the PANES' FROST veiled over it —
the same stack the sidebar and Now Playing bands wear (blurred
content under their bar tint), which is what makes all three read
as one glass while still ghosting whatever slides beneath (Kyle,
2026-08-30: "the glass bar does not blur the content behind it
like the left and right sidebar"). The veil must be a ::after —
the bar's own background paints UNDER .peri and cannot tint the
fog (measured: an 0.85 background moved the band 34.6 -> 33.5).
Pure black 0.35, the tint Kyle approved: no self-light, and over
the 0.55 canvas it nets the panes' own 0.7075 wherever the fog is
translucent or still building. */
:global(:root.glass .pane-shell:not(.painted-page)) .glassbar .peri::after {
content: "";
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.35);
}
/* Painted pages ground the glass themselves — the original recipe is
physically safe there and IS the designed hero-under-glass look. */
@@ -407,6 +472,33 @@
z-index: 0;
pointer-events: none;
}
/* The fog's compositor ride lives in global.css (.peri-ride): the
clone is built by JS with no scope hash, and Svelte's -global-
keyframes prefix-stripping desynced the animation-name from the
@keyframes on the first attempt — verified dead via getAnimations().
Plain CSS cannot be mangled. */
/* The page's colour wash rides the fog at (near) full strength. The
veil above dims the whole fog to 0.65, which turned "the bar frosts
over colour rather than over black" (the wash's own contract, see
CatalogSearch) into a dark step right where the wash met the band
(Kyle, 2026-08-30). Boosting only the CLONED wash restores its
effective brightness through the veil — 0.92 × 0.65 lands on the
wash's own 0.6 — while rows and art scrolling under the bar keep
the veil's full dimming. The real .wash is not inside .peri, so
this cannot touch the page itself. */
.peri :global(.wash) {
opacity: 0.92;
/* Bled into the fog's 48px overscan on the three closed sides —
the wash starts exactly at the pane top, so the blur kernel at
the band's top edge sampled past it into nothing and faded the
colour out over the first ~2×17px: a dark band at the very top
of the bar (Kyle, 2026-08-30). The album banner never showed
this because its own box bleeds -70px above the page. The
gradient shift from starting 48px higher is ~3% — invisible. */
top: -48px;
left: -48px;
right: -48px;
}
/* No animations inside the fog: cloned scroll-driven rides can never
advance (their timeline source is the real scroller, not an ancestor
here) and would hold empty from-poses. Base styles are the sensible
+12 -1
View File
@@ -355,6 +355,9 @@
let pending = 0;
let recloneGen = 0;
let lastRecloneMs = 4;
// Consecutive idle callbacks refused for a too-thin slice — see the
// bounded gate in queueReclone.
let thinSlices = 0;
const sync = () => {
markAppScroll();
const c = peri.firstElementChild?.firstElementChild;
@@ -439,7 +442,15 @@
if ("requestIdleCallback" in window) {
requestIdleCallback((deadline) => {
if (!appScrollIdleFor(1000)) return queueReclone();
if (deadline && deadline.timeRemaining() < 15) return queueReclone();
// Bounded, same fix as GlassBar (2026-08-30): with a
// frame continuously pending, this machine's idle
// slices cap at ~13.5ms and a hard <15 never passes —
// this pane's fog died at the first content churn
// (every track change) and never rebuilt.
if (deadline && deadline.timeRemaining() < 15 && ++thinSlices < 8) {
return queueReclone();
}
thinSlices = 0;
reclone();
});
} else {
+12 -1
View File
@@ -45,6 +45,9 @@
let pending = 0;
let recloneGen = 0;
let lastRecloneMs = 4;
// Consecutive idle callbacks refused for a too-thin slice — see the
// bounded gate in queueReclone.
let thinSlices = 0;
const sync = () => {
markAppScroll();
const c = peri.firstElementChild?.firstElementChild;
@@ -114,7 +117,15 @@
if ("requestIdleCallback" in window) {
requestIdleCallback((deadline) => {
if (!appScrollIdleFor(1000)) return queueReclone();
if (deadline && deadline.timeRemaining() < 15) return queueReclone();
// Bounded, same fix as GlassBar (2026-08-30): with a
// frame continuously pending, this machine's idle
// slices cap at ~13.5ms and a hard <15 never passes —
// this pane's fog died at the first content churn
// (every track change) and never rebuilt.
if (deadline && deadline.timeRemaining() < 15 && ++thinSlices < 8) {
return queueReclone();
}
thinSlices = 0;
reclone();
});
} else {
+19
View File
@@ -284,3 +284,22 @@ input {
::-webkit-scrollbar-track {
background: transparent;
}
/* The glass bar fog's compositor ride (built by GlassBar.svelte's reclone,
which sets --peri-range to the scroller's scroll range at clone time).
Progress on --pane-scroll (published by .pane-shell) times -range equals
-scrollTop exactly, with no main-thread chase — the scroll-event sync in
GlassBar lagged visibly on the 60Hz laptop and stays only as the
no-timeline-support fallback (a running animation outranks its inline
transform). Plain CSS on purpose: Svelte's -global- keyframes prefix
stripping desynced name from reference when this lived in the component
(2026-08-30, verified dead via getAnimations). */
.peri-ride {
animation: peri-ride linear both;
animation-timeline: --pane-scroll;
}
@keyframes peri-ride {
to {
transform: translateY(calc(-1 * var(--peri-range)));
}
}