Spaces:
Sleeping
Sleeping
Update pages/GameLucky.tsx
Browse files- pages/GameLucky.tsx +46 -32
pages/GameLucky.tsx
CHANGED
|
@@ -70,29 +70,33 @@ const LuckyWheel = ({ config, isSpinning, result, onSpin }: {
|
|
| 70 |
const angle = (seg.weight / totalWeight) * 360;
|
| 71 |
const start = currentAngle;
|
| 72 |
currentAngle += angle;
|
| 73 |
-
|
|
|
|
|
|
|
| 74 |
});
|
| 75 |
|
| 76 |
useEffect(() => {
|
| 77 |
if (result && isSpinning) {
|
| 78 |
// Find target slice index
|
| 79 |
-
const
|
| 80 |
-
// Default to first if not found (shouldn't happen if config syncs)
|
| 81 |
-
const safeIndex = targetIndex >= 0 ? targetIndex : slices.length - 1;
|
| 82 |
-
const targetSlice = slices[safeIndex];
|
| 83 |
|
| 84 |
-
//
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
-
//
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
// SVG coordinate system usually 0 is right (3 o'clock).
|
| 91 |
-
// We rotate wheel -90deg initially so 0 is top.
|
| 92 |
-
// Target rotation = (360 - centerAngle).
|
| 93 |
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
setRotation(finalRotation);
|
| 98 |
}
|
|
@@ -106,18 +110,10 @@ const LuckyWheel = ({ config, isSpinning, result, onSpin }: {
|
|
| 106 |
};
|
| 107 |
|
| 108 |
return (
|
| 109 |
-
<div className="flex flex-col items-center justify-center h-full w-full max-w-
|
| 110 |
-
<div className="relative w-full aspect-square max-w-[
|
| 111 |
-
{/*
|
| 112 |
-
<div className="
|
| 113 |
-
<ArrowDown size={40} fill="currentColor" strokeWidth={2} stroke="white"/>
|
| 114 |
-
</div>
|
| 115 |
-
|
| 116 |
-
{/* Wheel */}
|
| 117 |
-
<div
|
| 118 |
-
className="w-full h-full rounded-full border-8 border-yellow-400 shadow-2xl overflow-hidden relative transition-transform duration-[4000ms] cubic-bezier(0.2, 0.8, 0.2, 1)"
|
| 119 |
-
style={{ transform: `rotate(${rotation}deg)` }}
|
| 120 |
-
>
|
| 121 |
<svg viewBox="-1 -1 2 2" style={{ transform: 'rotate(-90deg)' }} className="w-full h-full">
|
| 122 |
{slices.map((slice, i) => {
|
| 123 |
// Calculate SVG path
|
|
@@ -136,13 +132,14 @@ const LuckyWheel = ({ config, isSpinning, result, onSpin }: {
|
|
| 136 |
<text
|
| 137 |
x="0" y="0"
|
| 138 |
fill="white"
|
| 139 |
-
fontSize="0.
|
| 140 |
fontWeight="bold"
|
| 141 |
textAnchor="middle"
|
| 142 |
dominantBaseline="middle"
|
| 143 |
transform="rotate(90)"
|
|
|
|
| 144 |
>
|
| 145 |
-
{slice.name.length >
|
| 146 |
</text>
|
| 147 |
</g>
|
| 148 |
</g>
|
|
@@ -151,12 +148,29 @@ const LuckyWheel = ({ config, isSpinning, result, onSpin }: {
|
|
| 151 |
</svg>
|
| 152 |
</div>
|
| 153 |
|
| 154 |
-
{/*
|
| 155 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
<button
|
| 157 |
onClick={onSpin}
|
| 158 |
disabled={isSpinning}
|
| 159 |
-
className="w-16 h-16 md:w-20 md:h-20 bg-gradient-to-b from-yellow-300 to-yellow-500 rounded-full border-4 border-white shadow-
|
| 160 |
>
|
| 161 |
{isSpinning ? '...' : '抽奖'}
|
| 162 |
</button>
|
|
|
|
| 70 |
const angle = (seg.weight / totalWeight) * 360;
|
| 71 |
const start = currentAngle;
|
| 72 |
currentAngle += angle;
|
| 73 |
+
// Center angle is where the pointer should land for this segment
|
| 74 |
+
const center = start + (angle / 2);
|
| 75 |
+
return { ...seg, startAngle: start, endAngle: currentAngle, angle, centerAngle: center };
|
| 76 |
});
|
| 77 |
|
| 78 |
useEffect(() => {
|
| 79 |
if (result && isSpinning) {
|
| 80 |
// Find target slice index
|
| 81 |
+
const targetSlice = slices.find(s => s.name === result.prize) || slices[slices.length - 1];
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
+
// Logic: Rotate the POINTER to align with the slice center
|
| 84 |
+
// Base rounds + Target Angle
|
| 85 |
+
// We need to ensure we always spin forward (clockwise)
|
| 86 |
+
const minSpins = 5;
|
| 87 |
+
const baseRotation = Math.floor(rotation / 360) * 360 + (minSpins * 360);
|
| 88 |
|
| 89 |
+
// Current effective angle
|
| 90 |
+
const currentMod = rotation % 360;
|
| 91 |
+
const targetMod = targetSlice.centerAngle;
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
+
let diff = targetMod - currentMod;
|
| 94 |
+
if (diff <= 0) diff += 360; // Ensure positive delta for clockwise spin
|
| 95 |
+
|
| 96 |
+
// Add a little randomness within the slice (-40% to +40% width) to simulate real physics
|
| 97 |
+
const jitter = (Math.random() - 0.5) * (targetSlice.angle * 0.8);
|
| 98 |
+
|
| 99 |
+
const finalRotation = rotation + (minSpins * 360) + diff + jitter;
|
| 100 |
|
| 101 |
setRotation(finalRotation);
|
| 102 |
}
|
|
|
|
| 110 |
};
|
| 111 |
|
| 112 |
return (
|
| 113 |
+
<div className="flex flex-col items-center justify-center h-full w-full max-w-2xl mx-auto p-4">
|
| 114 |
+
<div className="relative w-full aspect-square max-w-[500px] md:max-w-[600px]">
|
| 115 |
+
{/* Wheel (Static Background) */}
|
| 116 |
+
<div className="w-full h-full rounded-full border-8 border-yellow-400 shadow-2xl overflow-hidden relative bg-white">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
<svg viewBox="-1 -1 2 2" style={{ transform: 'rotate(-90deg)' }} className="w-full h-full">
|
| 118 |
{slices.map((slice, i) => {
|
| 119 |
// Calculate SVG path
|
|
|
|
| 132 |
<text
|
| 133 |
x="0" y="0"
|
| 134 |
fill="white"
|
| 135 |
+
fontSize="0.08"
|
| 136 |
fontWeight="bold"
|
| 137 |
textAnchor="middle"
|
| 138 |
dominantBaseline="middle"
|
| 139 |
transform="rotate(90)"
|
| 140 |
+
style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.3)' }}
|
| 141 |
>
|
| 142 |
+
{slice.name.length > 6 ? slice.name.substring(0,5)+'..' : slice.name}
|
| 143 |
</text>
|
| 144 |
</g>
|
| 145 |
</g>
|
|
|
|
| 148 |
</svg>
|
| 149 |
</div>
|
| 150 |
|
| 151 |
+
{/* Spinning Pointer Container (Centered) */}
|
| 152 |
+
<div
|
| 153 |
+
className="absolute inset-0 pointer-events-none flex items-center justify-center z-20"
|
| 154 |
+
style={{
|
| 155 |
+
transform: `rotate(${rotation}deg)`,
|
| 156 |
+
transition: isSpinning ? 'transform 4s cubic-bezier(0.2, 0.8, 0.2, 1)' : 'none'
|
| 157 |
+
}}
|
| 158 |
+
>
|
| 159 |
+
{/* The Needle Graphic (Extends UP from center) */}
|
| 160 |
+
<div className="relative w-0 h-0 -mt-[35%]">
|
| 161 |
+
{/* Needle Body */}
|
| 162 |
+
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-4 md:w-6 h-32 md:h-48 bg-red-600 rounded-t-full shadow-lg border-2 border-white origin-bottom"></div>
|
| 163 |
+
{/* Needle Base Decoration */}
|
| 164 |
+
<div className="absolute bottom-[-10px] left-1/2 -translate-x-1/2 w-8 h-8 md:w-10 md:h-10 bg-red-700 rounded-full border-2 border-white shadow-md"></div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
|
| 168 |
+
{/* Center Button (Static Pivot) */}
|
| 169 |
+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-30 pointer-events-auto">
|
| 170 |
<button
|
| 171 |
onClick={onSpin}
|
| 172 |
disabled={isSpinning}
|
| 173 |
+
className="w-16 h-16 md:w-20 md:h-20 bg-gradient-to-b from-yellow-300 to-yellow-500 rounded-full border-4 border-white shadow-[0_4px_10px_rgba(0,0,0,0.3)] flex items-center justify-center font-black text-red-600 text-lg md:text-xl hover:scale-105 active:scale-95 disabled:grayscale transition-all active:shadow-inner"
|
| 174 |
>
|
| 175 |
{isSpinning ? '...' : '抽奖'}
|
| 176 |
</button>
|