artist page glass: the hero fades INTO the frost - the burial moves from the sheet's paint to the photo's own alpha (gfade frame+counter: in-flow pass-throughs wrapping every photo layer, seam mirror excluded; a STATIC inverted-smoothstep mask parked at --fog-rest rides the sheet's glide on the frame while the counter rides the exact inverse, so the photo stays pinned, the mask never animates, and photo-visible-fraction equals the base fade at every pixel by construction); under glass the sheet's band goes transparent and its tail lands on the frost tint, so the same dissolve now lands on desktop glow - solid mode byte-identical (wrappers inert, from-poses no-ops); the wrappers must NEVER be positioned: the in-flow banner is what sizes hero-bg and absolute wrappers collapse the chain to zero

This commit is contained in:
KeelrLeek committed 2026-08-26 16:26:59 -05:00
1 parent 6ba1864498
commit 7ab678fd22
1 file changed
+158
+158
View File
@@ -4644,6 +4644,17 @@
rotation, the outgoing pair stays underneath until the new pair
fades in. -->
<div class="hero-bg" aria-hidden="true">
<!-- Dark-glass fade frame + counter (inert in solid mode:
two absolute inset-0 pass-through boxes, no transform,
no mask). Under glass the FRAME carries a static mask
whose band mirrors the sheet's smoothstep and rides
the sheet's own glide; the COUNTER rides the exact
inverse, keeping every photo layer visually pinned —
both transforms compositor-side, the mask never
animates. The seam-clip lives OUTSIDE the pair: the
bar's mirror must never fade. -->
<div class="gfade-frame" aria-hidden="true">
<div class="gfade-counter">
<div class="hero-fallback" style="--seed-hue: {seedHue}"></div>
{#if seedUrl}
<div
@@ -4738,6 +4749,8 @@
{:else if !selectedUsesGpu}
<div class="banner base"></div>
{/if}
</div>
</div>
<!-- THE SEAM TAIL (last child on purpose: paints over every
hero layer). The clip pins at the bar's TOP line and
does not ride: the mirror inside rides with the hero,
@@ -5734,6 +5747,83 @@
var(--bg-base) 100%
);
}
/* ── DARK GLASS: the hero fades INTO the frost ─────────────────────
The burial moves from the sheet's paint to the photo's own alpha.
The .gfade-frame (wrapping every photo layer; seam-clip excluded)
carries a STATIC mask whose band is the sheet's exact smoothstep
INVERTED (mask alpha ≡ 1 − sheet alpha), parked at var(--fog-rest)
— which IS the band's hero-local rest offset: the sheetclip sits at
−pin-origin, the sheet at --band-top, and hero-bg at
--band-from-pin = --band-top − pin-origin, so the offsets cancel to
fog-rest exactly. The frame rides the sheet's glide (same sampler,
same range) carrying the mask; the counter rides the inverse so the
photo stays pinned. Photo-visible-fraction across the band therefore
equals the base fade at every pixel BY CONSTRUCTION — the only
intended difference is what the fade lands ON: the sheet's band is
transparent under glass (the photo fades itself) and its tail is the
frost tint, so the photo dissolves into desktop glow. Above the
band the mask is solid — the photo stays full under the bar at every
pose, so the data-painted contract holds with no stand-in strip.
Pre-measure, an unset --fog-rest resolves the mask solid via the
999999px sentinel: a no-op, base look. */
/* The frame/counter are IN-FLOW pass-throughs, deliberately not
positioned: the in-flow banner img is what gives .hero-bg its
height, and an absolute wrapper collapses that chain to zero (the
whole hero then clips away — measured the hard way). Their boxes
coincide with hero-bg's content box, so when the glass transform
makes the frame a containing block, absolute hero layers re-anchor
to an identical box. No rule needed in solid mode at all. */
:global(:root.glass) .gfade-frame {
mask-image: linear-gradient(
to bottom,
#000 0,
#000 var(--fog-rest, 999999px),
rgba(0, 0, 0, 0.981) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.08),
rgba(0, 0, 0, 0.919) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.17),
rgba(0, 0, 0, 0.844) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.25),
rgba(0, 0, 0, 0.741) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.33),
rgba(0, 0, 0, 0.603) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.42),
rgba(0, 0, 0, 0.5) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.5),
rgba(0, 0, 0, 0.397) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.58),
rgba(0, 0, 0, 0.259) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.67),
rgba(0, 0, 0, 0.156) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.75),
rgba(0, 0, 0, 0.081) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.83),
rgba(0, 0, 0, 0.019) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.92),
transparent calc(var(--fog-rest, 999999px) + var(--fog-span, 170px)),
transparent 100%
);
-webkit-mask-image: linear-gradient(
to bottom,
#000 0,
#000 var(--fog-rest, 999999px),
rgba(0, 0, 0, 0.981) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.08),
rgba(0, 0, 0, 0.919) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.17),
rgba(0, 0, 0, 0.844) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.25),
rgba(0, 0, 0, 0.741) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.33),
rgba(0, 0, 0, 0.603) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.42),
rgba(0, 0, 0, 0.5) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.5),
rgba(0, 0, 0, 0.397) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.58),
rgba(0, 0, 0, 0.259) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.67),
rgba(0, 0, 0, 0.156) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.75),
rgba(0, 0, 0, 0.081) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.83),
rgba(0, 0, 0, 0.019) calc(var(--fog-rest, 999999px) + var(--fog-span, 170px) * 0.92),
transparent calc(var(--fog-rest, 999999px) + var(--fog-span, 170px)),
transparent 100%
);
}
/* Under glass the sheet no longer buries the photo (the mask above
does), so its band is fully transparent and its page below is the
FROST TINT — the ground the fade lands on. Solid mode keeps the
original gradient untouched. */
:global(:root.glass) .sheet {
background: linear-gradient(
180deg,
rgba(18, 18, 18, 0) 0px,
rgba(18, 18, 18, 0) var(--fog-span),
rgba(18, 18, 18, 0.5) calc(var(--fog-span) * 1.15),
rgba(18, 18, 18, 0.5) 100%
);
}
/* LAYER 3 — the content, over the sheet and the hero. Its one special
behaviour: rows fade out over 18px as they slide under the banner
@@ -5917,6 +6007,22 @@
));
}
/* Dark glass on this path: the fade frame/counter track the sheet's
p² delta with the same live calc (delta only — the frame's rest
pose is 0, its mask already parks at --fog-rest). */
:global(:root.glass) .gfade-frame {
transform: translateY(calc(
var(--p, 0) * var(--p, 0)
* (var(--strip) - var(--fog-above) - var(--fog-rest))
));
}
:global(:root.glass) .gfade-counter {
transform: translateY(calc(
-1 * var(--p, 0) * var(--p, 0)
* (var(--strip) - var(--fog-above) - var(--fog-rest))
));
}
.artist-page[data-scroll="1"] .banner,
.artist-page[data-scroll="1"] :global(.gpu-hero) {
filter: var(--mono);
@@ -6124,6 +6230,38 @@
animation-range: 0px var(--travel, 999999px);
}
/* Dark glass: the fade frame rides the sheet's glide (same p²
sampler, same range — the two must never disagree by a pixel)
carrying its static mask; the counter rides the exact inverse so
the photo layers stay pinned. Both are pure compositor
transforms; the mask itself never animates. */
:global(:root.glass) .gfade-frame {
animation-name: gfade-frame-ride;
animation-duration: auto;
animation-timing-function: linear(
0, 0.0025 5%, 0.01 10%, 0.0225 15%, 0.04 20%, 0.0625 25%,
0.09 30%, 0.1225 35%, 0.16 40%, 0.2025 45%, 0.25 50%,
0.3025 55%, 0.36 60%, 0.4225 65%, 0.49 70%, 0.5625 75%,
0.64 80%, 0.7225 85%, 0.81 90%, 0.9025 95%, 1
);
animation-fill-mode: both;
animation-timeline: --artist-pane;
animation-range: 0px var(--travel, 999999px);
}
:global(:root.glass) .gfade-counter {
animation-name: gfade-counter-ride;
animation-duration: auto;
animation-timing-function: linear(
0, 0.0025 5%, 0.01 10%, 0.0225 15%, 0.04 20%, 0.0625 25%,
0.09 30%, 0.1225 35%, 0.16 40%, 0.2025 45%, 0.25 50%,
0.3025 55%, 0.36 60%, 0.4225 65%, 0.49 70%, 0.5625 75%,
0.64 80%, 0.7225 85%, 0.81 90%, 0.9025 95%, 1
);
animation-fill-mode: both;
animation-timeline: --artist-pane;
animation-range: 0px var(--travel, 999999px);
}
/* The identity block, ungated for the same reason. Today its top
sheds p·travel via --edge (a per-frame LAYOUT of the whole idpin
subtree); translateY(-p·travel) is the identical rigid motion of
@@ -6488,6 +6626,26 @@
}
}
/* Dark glass: the fade frame carries the sheet's exact delta (its rest
pose is 0 — the mask parks at --fog-rest in its own gradient), the
counter the exact inverse. Same endpoints as sheet-glide-ride. */
@keyframes gfade-frame-ride {
from {
transform: translateY(0);
}
to {
transform: translateY(var(--fog-ride, 0px));
}
}
@keyframes gfade-counter-ride {
from {
transform: translateY(0);
}
to {
transform: translateY(calc(-1 * var(--fog-ride, 0px)));
}
}
@keyframes idblock-ride {
from {
transform: translateY(0);