From 5f07744c18d5a59d5fc26f1d2da344ab6f2adc2f Mon Sep 17 00:00:00 2001 From: Evan Date: Sat, 1 Aug 2026 15:55:10 -0700 Subject: [PATCH] perf(store): stop packs tab stutter from icon filter animations and per-tile backdrop blur MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The store packs tab ran three infinite CSS animations per plutonium icon, including one animating filter: drop-shadow (re-rasterized every frame) and a 0.15s sub-pixel jiggle, over a grid where every cosmetic tile also had backdrop-filter: blur(8px). Any one of these animations forced the whole blurred scene to re-render each frame: with the production catalog the packs tab measured ~18fps (median frame 50ms) in a software-rendered harness, and 60fps with the icon animations paused. - PlutoniumIcon: pulse now animates opacity/scale of a pre-blurred radial-gradient glow layer (compositor-only) with a static drop-shadow on the icon; the jiggle is removed; the 7s rotation stays. - CosmeticContainer: drop the per-tile backdrop blur — tile backgrounds are ~85% opaque gradients over the already-blurred modal shell, so it was invisible but multiplied blur surfaces (867 -> 21). Same harness after the change: ~39fps with all animations running, and a 16.7ms median frame once the modal-shell blur (GPU-trivial on real hardware) is excluded. Visuals are unchanged. Co-Authored-By: Claude Fable 5 --- src/client/components/CosmeticContainer.ts | 4 +++- src/client/components/PlutoniumIcon.ts | 27 +++++++++++----------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/client/components/CosmeticContainer.ts b/src/client/components/CosmeticContainer.ts index 496c05018b..f5956b7116 100644 --- a/src/client/components/CosmeticContainer.ts +++ b/src/client/components/CosmeticContainer.ts @@ -221,7 +221,9 @@ export class CosmeticContainer extends LitElement { this.style.position = "relative"; this.style.background = `linear-gradient(to top, ${cfg.gradient} 0%, rgba(15,15,20,0.85) 100%)`; this.style.border = `1px solid ${this.selected ? cfg.glow : cfg.border}`; - this.style.backdropFilter = "blur(8px)"; + // No per-tile backdrop-filter: the tile gradient is ~85% opaque over the + // modal's already-blurred shell, so the blur is invisible — but dozens of + // blur surfaces made every animated frame in the store expensive. this.style.borderRadius = "0.75rem"; this.style.transition = "border-color 0.2s, background 0.2s, transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s"; diff --git a/src/client/components/PlutoniumIcon.ts b/src/client/components/PlutoniumIcon.ts index 17cf3a242d..02b187e90c 100644 --- a/src/client/components/PlutoniumIcon.ts +++ b/src/client/components/PlutoniumIcon.ts @@ -6,22 +6,19 @@ const STYLE_ID = "plutonium-icon-styles"; if (!document.getElementById(STYLE_ID)) { const style = document.createElement("style"); style.id = STYLE_ID; + // Only compositor-animatable properties (transform/opacity) may appear in + // these keyframes: animating filter/drop-shadow here re-rasterizes every + // icon each frame and forces the store's backdrop-blur surfaces to redraw, + // which stutters the whole packs menu (see #4816 follow-up). style.textContent = ` @keyframes plutonium-pulse { - 0% { filter: drop-shadow(0 0 4px rgba(34,197,94,0.6)) drop-shadow(0 0 8px rgba(34,197,94,0.3)); scale: 1; } - 50% { filter: drop-shadow(0 0 10px rgba(34,197,94,0.9)) drop-shadow(0 0 20px rgba(34,197,94,0.5)) drop-shadow(0 0 30px rgba(34,197,94,0.2)); scale: 1.04; } - 100% { filter: drop-shadow(0 0 4px rgba(34,197,94,0.6)) drop-shadow(0 0 8px rgba(34,197,94,0.3)); scale: 1; } + 0%, 100% { opacity: 0.45; transform: scale(1); } + 50% { opacity: 1; transform: scale(1.12); } } @keyframes plutonium-rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } - @keyframes plutonium-jiggle { - 0%, 100% { translate: 0 0; } - 25% { translate: -0.4px 0.3px; } - 50% { translate: 0.3px -0.4px; } - 75% { translate: -0.3px -0.3px; } - } `; document.head.appendChild(style); } @@ -38,15 +35,17 @@ export class PlutoniumIcon extends LitElement { render() { return html`
+
Plutonium