Spaces:
Sleeping
Sleeping
Update pages/GameLucky.tsx
Browse files- pages/GameLucky.tsx +57 -30
pages/GameLucky.tsx
CHANGED
|
@@ -70,7 +70,7 @@ const LuckyWheel = ({ config, isSpinning, result, onSpin }: {
|
|
| 70 |
const angle = (seg.weight / totalWeight) * 360;
|
| 71 |
const start = currentAngle;
|
| 72 |
currentAngle += angle;
|
| 73 |
-
// Center angle
|
| 74 |
const center = start + (angle / 2);
|
| 75 |
return { ...seg, startAngle: start, endAngle: currentAngle, angle, centerAngle: center };
|
| 76 |
});
|
|
@@ -80,23 +80,30 @@ const LuckyWheel = ({ config, isSpinning, result, onSpin }: {
|
|
| 80 |
// Find target slice index
|
| 81 |
const targetSlice = slices.find(s => s.name === result.prize) || slices[slices.length - 1];
|
| 82 |
|
| 83 |
-
//
|
| 84 |
-
//
|
| 85 |
-
//
|
|
|
|
| 86 |
const minSpins = 5;
|
| 87 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
-
// Current
|
| 90 |
const currentMod = rotation % 360;
|
| 91 |
-
const targetMod = targetSlice.centerAngle;
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
|
|
|
| 95 |
|
| 96 |
-
// Add
|
|
|
|
| 97 |
const jitter = (Math.random() - 0.5) * (targetSlice.angle * 0.8);
|
| 98 |
|
| 99 |
-
const finalRotation = rotation +
|
| 100 |
|
| 101 |
setRotation(finalRotation);
|
| 102 |
}
|
|
@@ -110,10 +117,16 @@ const LuckyWheel = ({ config, isSpinning, result, onSpin }: {
|
|
| 110 |
};
|
| 111 |
|
| 112 |
return (
|
| 113 |
-
<div className="flex flex-col items-center justify-center h-full w-full max-w-
|
| 114 |
<div className="relative w-full aspect-square max-w-[500px] md:max-w-[600px]">
|
| 115 |
-
{/* Wheel (
|
| 116 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
@@ -148,29 +161,43 @@ const LuckyWheel = ({ config, isSpinning, result, onSpin }: {
|
|
| 148 |
</svg>
|
| 149 |
</div>
|
| 150 |
|
| 151 |
-
{/*
|
| 152 |
-
<div
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-
|
| 174 |
>
|
| 175 |
{isSpinning ? '...' : '抽奖'}
|
| 176 |
</button>
|
|
|
|
| 70 |
const angle = (seg.weight / totalWeight) * 360;
|
| 71 |
const start = currentAngle;
|
| 72 |
currentAngle += angle;
|
| 73 |
+
// Center angle relative to start of wheel (0 deg)
|
| 74 |
const center = start + (angle / 2);
|
| 75 |
return { ...seg, startAngle: start, endAngle: currentAngle, angle, centerAngle: center };
|
| 76 |
});
|
|
|
|
| 80 |
// Find target slice index
|
| 81 |
const targetSlice = slices.find(s => s.name === result.prize) || slices[slices.length - 1];
|
| 82 |
|
| 83 |
+
// LOGIC: The POINTER is fixed at TOP (0 deg or 360 deg).
|
| 84 |
+
// We need to rotate the WHEEL so that the Target Slice center hits 0 deg.
|
| 85 |
+
// Target Rotation = (360 - TargetSliceCenter) + FullSpins.
|
| 86 |
+
|
| 87 |
const minSpins = 5;
|
| 88 |
+
const spinAngle = minSpins * 360;
|
| 89 |
+
|
| 90 |
+
// Calculate where we need to land relative to 0
|
| 91 |
+
// Since SVG 0 starts at Top (after -90deg rotation),
|
| 92 |
+
// if we rotate by (360 - centerAngle), that slice will be at Top.
|
| 93 |
+
const targetLandingAngle = 360 - targetSlice.centerAngle;
|
| 94 |
|
| 95 |
+
// Current Rotation Modulo
|
| 96 |
const currentMod = rotation % 360;
|
|
|
|
| 97 |
|
| 98 |
+
// Calculate forward distance to target
|
| 99 |
+
let distance = targetLandingAngle - currentMod;
|
| 100 |
+
if (distance < 0) distance += 360; // Ensure positive to spin clockwise
|
| 101 |
|
| 102 |
+
// Add slight randomness within the slice (-40% to +40% width)
|
| 103 |
+
// Note: If we add angle, we shift the slice CW, meaning pointer hits earlier (CCW shift relative to slice)
|
| 104 |
const jitter = (Math.random() - 0.5) * (targetSlice.angle * 0.8);
|
| 105 |
|
| 106 |
+
const finalRotation = rotation + spinAngle + distance + jitter;
|
| 107 |
|
| 108 |
setRotation(finalRotation);
|
| 109 |
}
|
|
|
|
| 117 |
};
|
| 118 |
|
| 119 |
return (
|
| 120 |
+
<div className="flex flex-col items-center justify-center h-full w-full max-w-3xl mx-auto p-4">
|
| 121 |
<div className="relative w-full aspect-square max-w-[500px] md:max-w-[600px]">
|
| 122 |
+
{/* 1. Wheel Container (Rotates) */}
|
| 123 |
+
<div
|
| 124 |
+
className="w-full h-full rounded-full border-8 border-yellow-400 shadow-2xl overflow-hidden relative bg-white"
|
| 125 |
+
style={{
|
| 126 |
+
transform: `rotate(${rotation}deg)`,
|
| 127 |
+
transition: isSpinning ? 'transform 4s cubic-bezier(0.15, 0.85, 0.35, 1)' : 'none'
|
| 128 |
+
}}
|
| 129 |
+
>
|
| 130 |
<svg viewBox="-1 -1 2 2" style={{ transform: 'rotate(-90deg)' }} className="w-full h-full">
|
| 131 |
{slices.map((slice, i) => {
|
| 132 |
// Calculate SVG path
|
|
|
|
| 161 |
</svg>
|
| 162 |
</div>
|
| 163 |
|
| 164 |
+
{/* 2. Static Pointer (Fixed Overlay) */}
|
| 165 |
+
<div className="absolute inset-0 pointer-events-none z-20 flex justify-center">
|
| 166 |
+
{/* Container spanning from top to center (height 50%) */}
|
| 167 |
+
<div className="relative w-10 md:w-14 h-[50%]">
|
| 168 |
+
{/*
|
| 169 |
+
The Pointer Shape:
|
| 170 |
+
A long triangle stretching from the bottom (center of wheel) to the top (edge).
|
| 171 |
+
Using SVG to ensure perfect sharpness and responsiveness.
|
| 172 |
+
*/}
|
| 173 |
+
<svg
|
| 174 |
+
viewBox="0 0 40 300"
|
| 175 |
+
preserveAspectRatio="none"
|
| 176 |
+
className="w-full h-full drop-shadow-xl"
|
| 177 |
+
style={{ filter: 'drop-shadow(0px 4px 4px rgba(0,0,0,0.4))' }}
|
| 178 |
+
>
|
| 179 |
+
{/* Gradient Def */}
|
| 180 |
+
<defs>
|
| 181 |
+
<linearGradient id="pointerGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
| 182 |
+
<stop offset="0%" stopColor="#dc2626" />
|
| 183 |
+
<stop offset="50%" stopColor="#ef4444" />
|
| 184 |
+
<stop offset="100%" stopColor="#b91c1c" />
|
| 185 |
+
</linearGradient>
|
| 186 |
+
</defs>
|
| 187 |
+
{/* Triangle Path: Top-Center (20,0) -> Bottom-Right (40,300) -> Bottom-Left (0,300) */}
|
| 188 |
+
<path d="M 20 0 L 40 300 L 0 300 Z" fill="url(#pointerGrad)" stroke="white" strokeWidth="2" />
|
| 189 |
+
{/* Inner Highlight line for 3D effect */}
|
| 190 |
+
<path d="M 20 0 L 20 300" stroke="rgba(255,255,255,0.3)" strokeWidth="1" />
|
| 191 |
+
</svg>
|
| 192 |
</div>
|
| 193 |
</div>
|
| 194 |
|
| 195 |
+
{/* 3. Center Button (Static Pivot) */}
|
| 196 |
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-30 pointer-events-auto">
|
| 197 |
<button
|
| 198 |
onClick={onSpin}
|
| 199 |
disabled={isSpinning}
|
| 200 |
+
className="w-20 h-20 md:w-24 md:h-24 bg-gradient-to-b from-yellow-300 to-yellow-500 rounded-full border-4 border-white shadow-[0_4px_15px_rgba(0,0,0,0.4)] flex items-center justify-center font-black text-red-600 text-xl md:text-2xl hover:scale-105 active:scale-95 disabled:grayscale transition-all active:shadow-inner"
|
| 201 |
>
|
| 202 |
{isSpinning ? '...' : '抽奖'}
|
| 203 |
</button>
|