From 3cdad1e714e8c46ee6ab3e53c36f9cf1fe4e86c7 Mon Sep 17 00:00:00 2001 From: Martin Bauer Date: Sun, 20 Sep 2026 15:12:26 +0200 Subject: [PATCH] Stop rendering album cards that are off screen, and split card blur from panel blur Searching a single letter over a real library puts 340 cards in one grid, each with a cover image, a shadow and a backdrop blur, all of them live whether or not they are on screen. On musicdolphin that saturated the renderer badly enough that a DevTools Runtime.evaluate could not be scheduled on the main thread inside 30 seconds, which is a fair description of what "sluggish" felt like. content-visibility: auto on the grid's cards is the browser's own answer: off-screen cards skip layout, paint and compositing and come back as they scroll near the viewport. The grid's tracks are sized by minmax(180px, 1fr) rather than by card content, so skipping that content cannot move the columns; contain-intrinsic-size supplies the block-axis guess and `auto` remembers each card's real size after its first render, so scroll height and offsetTop - which BrowseView's keep-the-selection- on-screen effect reads - stay honest. Not behind ?pi=1: there is no visual difference to trade away. SHOW_CARD_BLUR separates the repeated frosted surfaces (every card, every list row) from the handful of panels wrapped around them, because the two behave nothing alike and lumping them together is what made the first Pi profile five times slower. A panel is one live backdrop copy that buys its whole subtree a compositing layer; a card is one of three hundred sitting directly over the animated canvas. ?pi=1 now drops the card blur and keeps the panel blur. Co-Authored-By: Claude Opus 5 --- web/src/App.tsx | 8 ++++++-- web/src/lib/theme.ts | 12 ++++++++++++ web/src/styles/app.css | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index 8900f47..d63cd46 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -35,7 +35,7 @@ import { playPop } from "./lib/pop"; import { targetForAlbum } from "./lib/remote"; import type { Group, Results, SongHit } from "./lib/search"; import { buildSearchIndex, categoryMatches, groupOf, results as computeResults } from "./lib/search"; -import { SHOW_AMBIENCE, SHOW_GLASS_BLUR } from "./lib/theme"; +import { SHOW_AMBIENCE, SHOW_CARD_BLUR, SHOW_GLASS_BLUR } from "./lib/theme"; /** How long an armed "A" waits for the digit that completes the shortcut. */ const ASSIGN_PENDING_TIMEOUT_MS = 4000; @@ -412,7 +412,11 @@ export function App() { setUi((previous) => ({ ...previous, page: previous.page === page ? "music" : page })); return ( -
+
{state && SHOW_AMBIENCE && ( .card { + content-visibility: auto; + contain-intrinsic-size: auto 289px; +} + .shelf-row { display: flex; gap: 12px;