library: the page lit by its own records, and portraits that prove out

The expander reserves once on the page root instead of standing min-heights
on the grid; shelf heads carry artist portraits keyed by path; every record
tints its own card, expander and glow from its cover; the artwork opens the
shared viewer; the banner wash reads the playing record and actually paints
(isolation, the artist page's own fix); descriptions animate on both pages
via partAnim. Portrait resolution rejects placeholder rows and never stores
a silhouette (pick_artist_portrait, 3 tests); repair re-resolves the missing.
This commit is contained in:
KeelrLeek committed 2026-08-06 17:21:23 -05:00
1 parent 741c4ae6dc
commit 185c29fc33
5 files changed
+1068 -49

No files matched your search

+37 -4
View File
@@ -3816,6 +3816,39 @@
};
}
/** Fade+lift ONE element from its own mount, with the same WAAPI call
* `staggerIn` makes.
*
* The description needs this because `in:partIn` above never actually ran
* for it, and that was measured on this page: opening a Weezer record and
* sampling `.rail-lede` every frame gave opacity 1 on all 1389 frames cold
* and all 711 warm, with no transform at any point.
*
* Both of the other mechanisms miss it by construction. `staggerIn` is an
* action on .xp-panel and queries for its parts when that element is
* created, but the description block is created microseconds later — same
* flush — so the query finds nothing, which is why .rail-desc was left out
* of SWAP_PARTS in the first place. And Svelte plays an `in:` only for an
* element added by a later UPDATE; landing in the same flush is not one, so
* the intro is skipped too. An action always runs when its own element is
* created, whichever flush that is, so it covers every arrival time. */
function partAnim(node, { delay = 0 } = {}) {
if (reducedMotion()) return;
const rest = getComputedStyle(node).opacity || "1";
node.animate(
[
{ opacity: 0, transform: "translateY(7px)" },
{ opacity: rest, transform: "none" },
],
{
duration: SWAP_MS * 0.7,
delay: delay * (SWAP_MS / 360),
easing: SWAP_EASE,
fill: "backwards",
},
);
}
/** Hold the stage at an eased height that TRACKS the panel, rather than
* animating once to a height measured up front. The panel is at its
* smallest when the swap begins — no description, no track rows — so a
@@ -5269,19 +5302,19 @@
keeps its 4-line clamp and
its mask fade. -->
{#if railDesc.lede}
<p class="rail-lede" in:partIn={{ delay: 55 }}>{railDesc.lede}</p>
<p class="rail-lede" use:partAnim={{ delay: 55 }}>{railDesc.lede}</p>
{/if}
{#if railDesc.rest}
<p class="rail-desc" class:clamped={!descExpanded} in:partIn={{ delay: 60 }}>{railDesc.rest}</p>
<p class="rail-desc" class:clamped={!descExpanded} use:partAnim={{ delay: 60 }}>{railDesc.rest}</p>
<button
class="rail-more"
class:open={descExpanded}
in:partIn={{ delay: 85 }}
use:partAnim={{ delay: 85 }}
onclick={() => (descExpanded = !descExpanded)}
>{descExpanded ? "Show less" : "Show more"} <Icon name="chevron-down" size={13} /></button>
{/if}
{#if openInfo.genre || openInfo.label}
<div class="rail-pills" in:partIn={{ delay: 100 }}>
<div class="rail-pills" use:partAnim={{ delay: 100 }}>
{#if openInfo.genre}<span class="gpill accent">{openInfo.genre}</span>{/if}
{#if openInfo.label}<span class="gpill">{openInfo.label}</span>{/if}
</div>
+878 -40
View File
File diff suppressed because it is too large. Load diff