first commit
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user