perf(vibe): stop building a plasma and reading the library on a phone
Two things made the Vibe page crawl on mobile. The ambient aura pushed three counter-rotating layers through an SVG turbulence and displacement pass whose noise field was animated in SMIL. SVG filters rasterise on the CPU, so that regenerated the whole field every frame, under a blur, across a 420x380 element. Below the breakpoint the filter is no longer mounted and the layers churn behind a plain blur instead. A phone gets the motion, not the noise pass. The start screen read all 5,000 tracks to show fifty of them. The sample is now drawn in the database, which keeps Surprise me uniform over the whole library while sending only what is on screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KENqSChfyqWnor6ud2WWH6
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useMediaQuery } from '../hooks/useMediaQuery';
|
||||
|
||||
export interface VibeProfile {
|
||||
energy?: number;
|
||||
@@ -19,6 +20,9 @@ function describeProfile(energy: number, novelty: number) {
|
||||
|
||||
/** An ambient representation of the live recommendation profile. */
|
||||
export function VibeAura({ profile, ambient = false }: { profile: VibeProfile; ambient?: boolean }) {
|
||||
// The displacement pass is desktop-only; see the comment in the ambient
|
||||
// branch below. Same breakpoint the layout already uses for this element.
|
||||
const plasma = useMediaQuery('(min-width: 640px)');
|
||||
const energy = clamp(profile.energy, 0.5);
|
||||
const novelty = clamp(profile.noveltyHunger ?? profile.explorationCoefficient, 0.3);
|
||||
const { energyLabel, discoveryLabel } = describeProfile(energy, novelty);
|
||||
@@ -58,6 +62,13 @@ export function VibeAura({ profile, ambient = false }: { profile: VibeProfile; a
|
||||
// Displacing clean gradients by animated fractal noise is what separates a
|
||||
// plasma from a blurred blob. baseFrequency is animated in SMIL rather than
|
||||
// CSS because no CSS property reaches inside an SVG filter primitive.
|
||||
//
|
||||
// That pass is also why the Vibe page crawled on a phone. An SVG filter is
|
||||
// rasterised on the CPU, and animating baseFrequency regenerates the whole
|
||||
// turbulence field every frame — for a 420x380 element, under a blur, over
|
||||
// three counter-rotating layers. Below the breakpoint the filter is not
|
||||
// mounted at all and the same layers churn behind a plain blur, which is
|
||||
// the difference between a heavy effect and a scrolling page.
|
||||
return (
|
||||
<div
|
||||
className="vibe-aura-blob pointer-events-none absolute z-0 h-[380px] w-[420px] -translate-x-1/2 -translate-y-1/2 opacity-85 mix-blend-screen sm:h-[560px] sm:w-[660px] sm:opacity-95"
|
||||
@@ -65,32 +76,34 @@ export function VibeAura({ profile, ambient = false }: { profile: VibeProfile; a
|
||||
role="img"
|
||||
aria-label={`Current Vibe: ${energyLabel} energy and ${discoveryLabel} discovery`}
|
||||
>
|
||||
<svg aria-hidden className="absolute h-0 w-0">
|
||||
<filter id="vibe-plasma" x="-30%" y="-30%" width="160%" height="160%">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.009 0.014"
|
||||
numOctaves={3}
|
||||
seed={7}
|
||||
result="noise"
|
||||
>
|
||||
<animate
|
||||
attributeName="baseFrequency"
|
||||
dur={`${(18 - energy * 8).toFixed(1)}s`}
|
||||
values="0.009 0.014; 0.021 0.007; 0.009 0.014"
|
||||
repeatCount="indefinite"
|
||||
{plasma && (
|
||||
<svg aria-hidden className="absolute h-0 w-0">
|
||||
<filter id="vibe-plasma" x="-30%" y="-30%" width="160%" height="160%">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.009 0.014"
|
||||
numOctaves={3}
|
||||
seed={7}
|
||||
result="noise"
|
||||
>
|
||||
<animate
|
||||
attributeName="baseFrequency"
|
||||
dur={`${(18 - energy * 8).toFixed(1)}s`}
|
||||
values="0.009 0.014; 0.021 0.007; 0.009 0.014"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</feTurbulence>
|
||||
<feDisplacementMap
|
||||
in="SourceGraphic"
|
||||
in2="noise"
|
||||
scale={String(Math.round(46 + energy * 70))}
|
||||
xChannelSelector="R"
|
||||
yChannelSelector="G"
|
||||
/>
|
||||
</feTurbulence>
|
||||
<feDisplacementMap
|
||||
in="SourceGraphic"
|
||||
in2="noise"
|
||||
scale={String(Math.round(46 + energy * 70))}
|
||||
xChannelSelector="R"
|
||||
yChannelSelector="G"
|
||||
/>
|
||||
</filter>
|
||||
</svg>
|
||||
<div className="vibe-aura-stack">
|
||||
</filter>
|
||||
</svg>
|
||||
)}
|
||||
<div className={plasma ? 'vibe-aura-stack vibe-aura-stack-plasma' : 'vibe-aura-stack'}>
|
||||
<div className="vibe-aura-layer vibe-aura-rays-layer" />
|
||||
<div className="vibe-aura-layer vibe-aura-swirl-layer" />
|
||||
<div className="vibe-aura-layer vibe-aura-core-layer" />
|
||||
|
||||
Reference in New Issue
Block a user