Files
Lucky_Wheel/apps/client/src/components/wheels/NeonCyberWheel.vue
T
2026-09-10 01:58:29 +03:30

141 lines
4.9 KiB
Vue

<script setup lang="ts">
import { toFarsiDigits, WheelDesignProps } from '@lucky-wheel/shared-types';
import { computed, watch } from 'vue';
import { useWheelRotation } from '../../composables/useWheelRotation';
const props = defineProps<WheelDesignProps>();
const emit = defineEmits<{
(e: 'spin-finished'): void;
}>();
const { wheelStyle, pointerStyle, isSpinning, spinTo } = useWheelRotation({
segmentCount: () => props.segments.length,
pegCount: () => 24,
onFinished: () => emit('spin-finished'),
});
watch(
[() => props.spinning, () => props.targetSegmentIndex],
([newSpinning, newTargetIdx]) => {
if (newSpinning && newTargetIdx !== null && newTargetIdx !== undefined && !isSpinning.value) {
spinTo(newTargetIdx);
}
},
{ flush: 'post' },
);
const totalSegments = computed(() => props.segments.length);
const sliceAngle = computed(() => (totalSegments.value > 0 ? 360 / totalSegments.value : 0));
function getSlicePath(index: number): string {
if (totalSegments.value <= 1) return '';
const r = 138;
const cx = 150;
const cy = 150;
const startAngle = (index * sliceAngle.value - 90) * (Math.PI / 180);
const endAngle = ((index + 1) * sliceAngle.value - 90) * (Math.PI / 180);
const x1 = cx + r * Math.cos(startAngle);
const y1 = cy + r * Math.sin(startAngle);
const x2 = cx + r * Math.cos(endAngle);
const y2 = cy + r * Math.sin(endAngle);
const largeArc = sliceAngle.value > 180 ? 1 : 0;
return `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y2} Z`;
}
function getTextRotation(index: number): string {
const midAngle = index * sliceAngle.value + sliceAngle.value / 2;
return `rotate(${midAngle} 150 150)`;
}
// 24 Cyberpunk Glowing Rim Pegs
const cyberPegs = computed(() => {
const count = 24;
return Array.from({ length: count }, (_, i) => {
const angle = (i * (360 / count) - 90) * (Math.PI / 180);
return {
x: 150 + 143 * Math.cos(angle),
y: 150 + 143 * Math.sin(angle),
};
});
});
</script>
<template>
<div class="relative w-full max-w-[340px] xs:max-w-[380px] sm:max-w-[420px] aspect-square mx-auto select-none">
<!-- Cyberpunk Glowing Bezel -->
<div
class="absolute inset-0 rounded-full p-2 bg-gradient-to-tr from-cyan-500 via-fuchsia-600 to-blue-600 shadow-[0_0_30px_rgba(0,240,255,0.4)] animate-neon-pulse"
>
<div class="w-full h-full rounded-full border-2 border-cyan-400 relative overflow-hidden bg-[#070913]">
<!-- Rotating SVG Wheel -->
<svg
class="w-full h-full"
:style="wheelStyle"
viewBox="0 0 300 300"
>
<!-- Neon Grids & Segments -->
<g v-for="(seg, idx) in segments" :key="seg.id">
<path
:d="getSlicePath(idx)"
:fill="seg.color || (idx % 2 === 0 ? '#111827' : '#1e1b4b')"
stroke="#00f0ff"
stroke-width="1.5"
stroke-opacity="0.8"
/>
<g :transform="getTextRotation(idx)">
<text
x="150"
y="45"
text-anchor="middle"
fill="#ffffff"
font-size="11"
font-weight="800"
class="wheel-slice-text tracking-wide uppercase font-sans"
>
{{ toFarsiDigits(seg.title) }}
</text>
<image
v-if="seg.image"
:href="seg.image"
x="138"
y="54"
width="24"
height="24"
/>
</g>
</g>
<!-- 24 Neon Rim Nodes -->
<g v-for="(peg, pIdx) in cyberPegs" :key="pIdx">
<circle :cx="peg.x" :cy="peg.y" r="3.2" fill="#05070e" stroke="#00f0ff" stroke-width="1" />
<circle :cx="peg.x" :cy="peg.y" r="1.5" fill="#00f0ff" />
</g>
<!-- High-Tech Center Arc Reactor -->
<circle cx="150" cy="150" r="32" fill="#05070e" stroke="#00f0ff" stroke-width="2" />
<circle cx="150" cy="150" r="22" fill="#1e1b4b" stroke="#ec4899" stroke-width="2" />
<circle cx="150" cy="150" r="10" fill="#00f0ff" class="animate-ping" opacity="0.3" />
<circle cx="150" cy="150" r="8" fill="#00f0ff" />
</svg>
</div>
</div>
<!-- Cyberpunk Laser Pointer at 12 o'clock with Tactile Peg Deflection -->
<div class="absolute -top-3 left-1/2 -translate-x-1/2 z-20 pointer-events-none drop-shadow-[0_0_12px_#00f0ff]">
<div :style="pointerStyle" class="w-[34px] h-[42px] origin-[50%_10px] will-change-transform">
<svg width="34" height="42" viewBox="0 0 34 42" fill="none" class="overflow-visible">
<path
d="M17 42L4 12C2 7 6 2 11 2H23C28 2 32 7 30 12L17 42Z"
fill="#00f0ff"
stroke="#ffffff"
stroke-width="1.5"
/>
<circle cx="17" cy="10" r="4" fill="#ff007f" />
</svg>
</div>
</div>
</div>
</template>