forked from Keelrleek/SoulStream
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:
1 parent
15746893a9
commit
5e2ad18269
5 files changed
+148
-5
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user