fix(wheel): kill the center-line LED artifact during spin (drop CSS blur filter)

The rim lights are static cx/cy SVG circles; their glow used a CSS filter:blur(2px). During the spin the wheel's transform transition promotes the SVG to a GPU layer, and Chrome mis-renders CSS filters on geometry-positioned SVG elements — the blurs streak toward the view-box origin, drawing a line of lights through the centre. Replace the blur with a soft radial-gradient fill (no filter, so the artefact can't occur).

Also drive both the chase speed and the per-dot delay from one --led-dur var so the chase stays in order at the faster spin speed instead of scrambling (the delay was a fixed 6s value that broke once the duration dropped to 2s).
This commit is contained in:
c0mrade
2026-06-01 16:24:02 +03:00
parent b877d7f175
commit 8a41aa7781

View File

@@ -206,6 +206,17 @@ const FortuneWheel = memo(function FortuneWheel({
<filter id="textShadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="1" stdDeviation="1" floodColor="#000" floodOpacity="0.7" />
</filter>
{/* LED glow as a soft radial gradient instead of a CSS blur filter.
A `filter: blur()` on cx/cy-positioned SVG circles mis-renders under
GPU compositing during the spin — the blurs streak toward the
view-box origin and draw a line of lights through the centre. A
gradient fill needs no filter, so that artefact can't occur. */}
<radialGradient id="ledGlowGrad">
<stop offset="0%" stopColor="#FEF08A" stopOpacity="0.95" />
<stop offset="45%" stopColor="#FDE047" stopOpacity="0.55" />
<stop offset="100%" stopColor="#FDE047" stopOpacity="0" />
</radialGradient>
</defs>
{/* Background shadow */}
@@ -242,10 +253,20 @@ const FortuneWheel = memo(function FortuneWheel({
0%, 100% { opacity: 0; }
10%, 30% { opacity: 0.4; }
}
.led-dot { animation: ledChase 6s linear infinite; }
.led-glow { opacity: 0; animation: ledGlow 6s linear infinite; }
.led-spinning .led-dot { animation-duration: 2s; }
.led-spinning .led-glow { animation-duration: 2s; }
/* --led-dur drives both the speed and the per-dot delay, so they
scale together and the chase stays in order at the faster spin
speed (the delay used to be a fixed 6s value, which scrambled
the order once the duration dropped to 2s). */
.led-dot, .led-glow {
--led-dur: 6s;
animation-duration: var(--led-dur);
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-delay: calc(var(--led-i, 0) / 20 * var(--led-dur));
}
.led-dot { animation-name: ledChase; }
.led-glow { opacity: 0; animation-name: ledGlow; }
.led-spinning .led-dot, .led-spinning .led-glow { --led-dur: 2s; }
`}
</style>
<g className={isSpinning ? 'led-spinning' : undefined}>
@@ -254,26 +275,11 @@ const FortuneWheel = memo(function FortuneWheel({
const ledRadius = outerRadius + 3;
const dotX = center + ledRadius * Math.cos(angle);
const dotY = center + ledRadius * Math.sin(angle);
// Delay as fraction of full cycle — CSS handles speed via animation-duration
const delay = `${(i / 20) * 6}s`;
return (
<g key={`led-${i}`}>
<circle
className="led-glow"
cx={dotX}
cy={dotY}
r={5}
fill="#FEF08A"
style={{ filter: 'blur(2px)', animationDelay: delay }}
/>
<circle
className="led-dot"
cx={dotX}
cy={dotY}
r={3.5}
strokeWidth="1"
style={{ animationDelay: delay }}
/>
// --led-i (dot index) drives the staggered chase delay in CSS
<g key={`led-${i}`} style={{ '--led-i': i } as React.CSSProperties}>
<circle className="led-glow" cx={dotX} cy={dotY} r={9} fill="url(#ledGlowGrad)" />
<circle className="led-dot" cx={dotX} cy={dotY} r={3.5} strokeWidth="1" />
</g>
);
})}