From 7716e32eecea6e43264342635090d8e4f68e4d87 Mon Sep 17 00:00:00 2001 From: c0mrade Date: Mon, 1 Jun 2026 16:45:35 +0300 Subject: [PATCH] fix(wheel): scatter spin sparkles so they don't streak through the centre MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The real cause of the center line: SPARKLE_POSITIONS (yellow dots shown only while spinning) used `20 + (i*10)%60` / `15 + (i*13)%70`. For the first ~6 sparkles the modulo never wraps, so top/left increment linearly and the dots land on a straight diagonal through the centre — the 'line of lights' during the spin. Scatter them on a golden-angle spiral inside the wheel instead. Reverts the earlier LED-glow change (it wasn't the cause): the rim-light blur is restored to the original. --- src/components/wheel/FortuneWheel.tsx | 72 ++++++++++++++------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/src/components/wheel/FortuneWheel.tsx b/src/components/wheel/FortuneWheel.tsx index 10adacc..3bf5974 100644 --- a/src/components/wheel/FortuneWheel.tsx +++ b/src/components/wheel/FortuneWheel.tsx @@ -8,12 +8,20 @@ interface FortuneWheelProps { onSpinComplete: () => void; } -// Pre-generate sparkle positions to avoid recalculating on each render -const SPARKLE_POSITIONS = Array.from({ length: 8 }, (_, i) => ({ - top: `${20 + ((i * 10) % 60)}%`, - left: `${15 + ((i * 13) % 70)}%`, - delay: `${i * 0.15}s`, -})); +// Pre-generate sparkle positions (shown only while spinning). The old formula +// `20 + (i*10)%60 / 15 + (i*13)%70` left the first ~6 sparkles colinear (the +// modulo never wrapped for small i), so they lined up as a diagonal streak +// through the centre. Scatter them on a phyllotaxis (golden-angle) spiral +// inside the wheel instead — well-distributed, never colinear. +const SPARKLE_POSITIONS = Array.from({ length: 8 }, (_, i) => { + const angle = i * 2.39996; // golden angle (radians) + const radius = 16 + (i % 4) * 8; // 16–40% from centre, kept within the wheel + return { + top: `${(50 + radius * Math.sin(angle)).toFixed(1)}%`, + left: `${(50 + radius * Math.cos(angle)).toFixed(1)}%`, + delay: `${i * 0.15}s`, + }; +}); const FortuneWheel = memo(function FortuneWheel({ prizes, @@ -206,17 +214,6 @@ const FortuneWheel = memo(function FortuneWheel({ - - {/* 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. */} - - - - - {/* Background shadow */} @@ -253,20 +250,10 @@ const FortuneWheel = memo(function FortuneWheel({ 0%, 100% { opacity: 0; } 10%, 30% { opacity: 0.4; } } - /* --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; } + .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; } `} @@ -275,11 +262,26 @@ 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 ( - // --led-i (dot index) drives the staggered chase delay in CSS - - - + + + ); })}