mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
Improve wheel: fix lights animation and add prize legend
- Fix lights animation to run continuously (even when idle) - Lights now move sequentially around the circle - Remove text labels from wheel, keep only emoji - Add WheelLegend component with color indicators - Display legend on right (desktop) and bottom (mobile) - Add 'prizes' translation key for all languages
This commit is contained in:
@@ -26,16 +26,17 @@ const FortuneWheel = memo(function FortuneWheel({
|
||||
const [displayRotation, setDisplayRotation] = useState(0);
|
||||
const [lightPhase, setLightPhase] = useState(0);
|
||||
|
||||
// Animated lights effect - running lights when spinning
|
||||
// Animated lights effect - always running, speed depends on spinning state
|
||||
useEffect(() => {
|
||||
if (isSpinning) {
|
||||
const interval = setInterval(() => {
|
||||
setLightPhase((p) => (p + 1) % 20); // Shift position around the wheel
|
||||
}, 150); // Fast interval for running effect
|
||||
return () => clearInterval(interval);
|
||||
} else {
|
||||
setLightPhase(0);
|
||||
}
|
||||
// Faster animation when spinning, slower when idle
|
||||
const interval = setInterval(
|
||||
() => {
|
||||
setLightPhase((p) => (p + 1) % 20);
|
||||
},
|
||||
isSpinning ? 100 : 300,
|
||||
); // 100ms when spinning, 300ms when idle
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [isSpinning]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -58,13 +59,16 @@ const FortuneWheel = memo(function FortuneWheel({
|
||||
|
||||
// Memoize light pattern calculation
|
||||
const lightPattern = useMemo(() => {
|
||||
const numLights = isSpinning ? 3 : 4; // 3 lights when spinning, 4 when idle
|
||||
|
||||
return Array.from({ length: 20 }, (_, i) => {
|
||||
if (!isSpinning) {
|
||||
return i % 2 === 0; // Static alternating pattern
|
||||
// Check if this light index is within the active range
|
||||
for (let j = 0; j < numLights; j++) {
|
||||
if (i === (lightPhase + j) % 20) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Running lights - 3 lit lights move around the wheel
|
||||
const pos = (i - lightPhase + 20) % 20;
|
||||
return pos < 3;
|
||||
return false;
|
||||
});
|
||||
}, [isSpinning, lightPhase]);
|
||||
|
||||
@@ -118,17 +122,6 @@ const FortuneWheel = memo(function FortuneWheel({
|
||||
};
|
||||
};
|
||||
|
||||
// Position for text - between hub and emoji
|
||||
const getTextPosition = (index: number) => {
|
||||
const angle = (index * sectorAngle + sectorAngle / 2 - 90) * (Math.PI / 180);
|
||||
const textRadius = prizeRadius * 0.45;
|
||||
return {
|
||||
x: center + textRadius * Math.cos(angle),
|
||||
y: center + textRadius * Math.sin(angle),
|
||||
rotation: index * sectorAngle + sectorAngle / 2,
|
||||
};
|
||||
};
|
||||
|
||||
// Alternate colors for sectors
|
||||
const getSectorColors = (index: number, baseColor?: string) => {
|
||||
if (baseColor) return baseColor;
|
||||
@@ -145,15 +138,6 @@ const FortuneWheel = memo(function FortuneWheel({
|
||||
return colors[index % colors.length];
|
||||
};
|
||||
|
||||
// Truncate text intelligently
|
||||
const truncateText = (text: string, maxLen: number) => {
|
||||
if (text.length <= maxLen) return text;
|
||||
return text.substring(0, maxLen - 1) + '..';
|
||||
};
|
||||
|
||||
// Calculate max text length based on number of sectors
|
||||
const maxTextLength = prizes.length <= 4 ? 12 : prizes.length <= 6 ? 10 : 8;
|
||||
|
||||
return (
|
||||
<div className="relative mx-auto w-full max-w-[380px] select-none">
|
||||
{/* Outer glow effect */}
|
||||
@@ -366,28 +350,7 @@ const FortuneWheel = memo(function FortuneWheel({
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Prize content - Text */}
|
||||
{prizes.map((prize, index) => {
|
||||
const pos = getTextPosition(index);
|
||||
const displayText = truncateText(prize.display_name, maxTextLength);
|
||||
return (
|
||||
<text
|
||||
key={`text-${prize.id}`}
|
||||
x={pos.x}
|
||||
y={pos.y}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
fontSize={prizes.length <= 4 ? '13' : prizes.length <= 6 ? '11' : '9'}
|
||||
fontWeight="700"
|
||||
fill="#FFFFFF"
|
||||
transform={`rotate(${pos.rotation}, ${pos.x}, ${pos.y})`}
|
||||
filter="url(#textShadow)"
|
||||
style={{ letterSpacing: '0.03em' }}
|
||||
>
|
||||
{displayText}
|
||||
</text>
|
||||
);
|
||||
})}
|
||||
{/* Prize content - Text removed, only emoji visible on wheel */}
|
||||
</g>
|
||||
|
||||
{/* Center hub */}
|
||||
|
||||
62
src/components/wheel/WheelLegend.tsx
Normal file
62
src/components/wheel/WheelLegend.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { memo } from 'react';
|
||||
import type { WheelPrize } from '../../api/wheel';
|
||||
|
||||
interface WheelLegendProps {
|
||||
prizes: WheelPrize[];
|
||||
}
|
||||
|
||||
const WheelLegend = memo(function WheelLegend({ prizes }: WheelLegendProps) {
|
||||
// Get sector color (same logic as in FortuneWheel)
|
||||
const getSectorColor = (index: number, baseColor?: string) => {
|
||||
if (baseColor) return baseColor;
|
||||
const colors = [
|
||||
'#8B5CF6',
|
||||
'#EC4899',
|
||||
'#3B82F6',
|
||||
'#10B981',
|
||||
'#F59E0B',
|
||||
'#EF4444',
|
||||
'#6366F1',
|
||||
'#14B8A6',
|
||||
];
|
||||
return colors[index % colors.length];
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{prizes.map((prize, index) => {
|
||||
const color = getSectorColor(index, prize.color);
|
||||
return (
|
||||
<div
|
||||
key={prize.id}
|
||||
className="flex items-center gap-3 rounded-lg border border-dark-700/30 bg-dark-800/50 p-2.5 transition-colors hover:bg-dark-800"
|
||||
>
|
||||
{/* Color indicator */}
|
||||
<div className="h-8 w-1 shrink-0 rounded-full" style={{ backgroundColor: color }} />
|
||||
|
||||
{/* Emoji */}
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center text-xl">
|
||||
{prize.emoji}
|
||||
</div>
|
||||
|
||||
{/* Prize name */}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-sm font-medium text-dark-100">{prize.display_name}</div>
|
||||
</div>
|
||||
|
||||
{/* Prize value (if applicable) */}
|
||||
{prize.prize_type !== 'nothing' && prize.value && (
|
||||
<div className="shrink-0 text-xs text-dark-400">
|
||||
{prize.prize_type === 'balance' && `${prize.value} ₽`}
|
||||
{prize.prize_type === 'subscription_days' && `${prize.value}д`}
|
||||
{prize.prize_type === 'promocode' && '🎫'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default WheelLegend;
|
||||
Reference in New Issue
Block a user