82 lines
2.7 KiB
Vue
82 lines
2.7 KiB
Vue
<template>
|
|
<div class="flex flex-col items-center justify-center py-5">
|
|
<div
|
|
class="relative w-[180px] h-[180px]"
|
|
:class="{ spinning: spinning }"
|
|
:style="spinning ? '' : 'animation: donutPulse 3s ease-in-out infinite, donutBreathe 4s ease-in-out infinite'"
|
|
>
|
|
<svg viewBox="0 0 180 180" class="w-[180px] h-[180px]" :style="spinning ? 'animation: ringSpin 1.5s linear infinite' : 'transform: rotate(-90deg)'">
|
|
<defs>
|
|
<linearGradient id="healthGrad" x1="0" y1="0" x2="1" y2="0">
|
|
<stop offset="0%" stop-color="var(--ins-negative)" />
|
|
<stop offset="40%" stop-color="var(--ins-warning)" />
|
|
<stop offset="100%" stop-color="var(--ins-positive)" />
|
|
</linearGradient>
|
|
</defs>
|
|
<circle
|
|
cx="90" cy="90" r="76"
|
|
fill="none"
|
|
stroke="var(--bg-base)"
|
|
stroke-width="14"
|
|
/>
|
|
<circle
|
|
cx="90" cy="90" r="76"
|
|
fill="none"
|
|
stroke="var(--ins-indigo)"
|
|
stroke-width="18"
|
|
opacity="0.1"
|
|
:stroke-dasharray="circumference"
|
|
:stroke-dashoffset="glowOffset"
|
|
style="filter: blur(4px)"
|
|
/>
|
|
<circle
|
|
cx="90" cy="90" r="76"
|
|
fill="none"
|
|
:stroke="spinning ? 'var(--ins-indigo)' : 'url(#healthGrad)'"
|
|
stroke-width="14"
|
|
stroke-linecap="round"
|
|
:stroke-dasharray="circumference"
|
|
:stroke-dashoffset="spinning ? 380 : fillOffset"
|
|
:style="spinning ? '' : 'transition: stroke-dashoffset 1s cubic-bezier(.22,1,.36,1)'"
|
|
/>
|
|
</svg>
|
|
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
|
<div class="text-5xl font-extrabold tracking-tighter leading-none text-[var(--ins-text-primary)]">
|
|
{{ score }}
|
|
</div>
|
|
<div class="text-[10px] font-bold text-[var(--ins-text-tertiary)] uppercase tracking-widest mt-1">
|
|
Health Score
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button
|
|
class="mt-2.5 px-3.5 py-1 rounded-full text-xs font-bold cursor-pointer transition-all duration-150 hover:scale-105"
|
|
:class="
|
|
score >= 80
|
|
? 'bg-[var(--ins-positive-bg)] text-[var(--ins-positive)]'
|
|
: score >= 50
|
|
? 'bg-[var(--ins-warning-bg)] text-[var(--ins-warning)]'
|
|
: 'bg-[var(--ins-negative-bg)] text-[var(--ins-negative)]'
|
|
"
|
|
@click="$emit('attention')"
|
|
>
|
|
{{ score >= 80 ? '✓ Хорошо' : score >= 50 ? '⚠ Требует внимания' : '✕ Критично' }}
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from "vue"
|
|
|
|
const props = defineProps({
|
|
score: { type: Number, default: 75 },
|
|
spinning: { type: Boolean, default: false },
|
|
})
|
|
|
|
defineEmits(["attention"])
|
|
|
|
const circumference = 2 * Math.PI * 76 // ~478
|
|
const fillOffset = computed(() => circumference * (1 - props.score / 100))
|
|
const glowOffset = computed(() => circumference * (1 - props.score / 100))
|
|
</script>
|