123 lines
4.1 KiB
Vue
123 lines
4.1 KiB
Vue
<template>
|
|
<section
|
|
class="py-10 lg:py-12 bg-gradient-to-r from-primary-50 via-cyan-50 to-accent-50 dark:from-primary-950/30 dark:via-cyan-950/30 dark:to-accent-950/30"
|
|
>
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<!-- Stats grid -->
|
|
<div class="grid grid-cols-2 lg:grid-cols-4 gap-8 lg:gap-12">
|
|
<div
|
|
v-for="(stat, index) in stats"
|
|
:key="index"
|
|
class="text-center animate-fade-in-up"
|
|
:style="{ animationDelay: `${index * 100}ms` }"
|
|
>
|
|
<div class="relative inline-block mb-3">
|
|
<!-- Animated number -->
|
|
<span ref="counterRefs" class="font-heading text-4xl sm:text-5xl lg:text-6xl font-bold gradient-text">
|
|
{{ animatedValues[index] }}{{ stat.suffix }}
|
|
</span>
|
|
|
|
<!-- Decorative glow -->
|
|
<div class="absolute -inset-4 bg-primary-400/20 blur-2xl rounded-full -z-10 animate-pulse-glow" />
|
|
</div>
|
|
|
|
<p class="text-sm sm:text-base text-gray-600 dark:text-gray-300 font-medium">
|
|
{{ stat.label }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Trust badges -->
|
|
<div class="mt-10 pt-8 border-t border-gray-200 dark:border-gray-800">
|
|
<p class="text-center text-sm text-gray-500 dark:text-gray-400 mb-8">Vertrauen & Sicherheit</p>
|
|
|
|
<div class="flex flex-wrap justify-center items-center gap-8 lg:gap-12">
|
|
<div
|
|
v-for="(badge, index) in trustBadges"
|
|
:key="index"
|
|
class="flex items-center gap-3 px-4 py-2 rounded-full bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 shadow-sm hover:shadow-md transition-shadow animate-fade-in-up"
|
|
:style="{ animationDelay: `${(index + 4) * 100}ms` }"
|
|
>
|
|
<div
|
|
class="w-8 h-8 rounded-full bg-gradient-to-br from-primary-100 to-cyan-100 dark:from-primary-900/50 dark:to-cyan-900/50 flex items-center justify-center"
|
|
>
|
|
<UIcon :name="badge.icon" class="w-4 h-4 text-primary-600 dark:text-primary-400" />
|
|
</div>
|
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">{{ badge.label }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const stats = [
|
|
{ value: 70, suffix: '%', label: 'Zeitersparnis im Verfahren' },
|
|
{ value: 100, suffix: '%', label: 'Revisionssicher dokumentiert' },
|
|
{ value: 24, suffix: '/7', label: 'Verfügbarkeit' },
|
|
{ value: 0, suffix: '', label: 'Medienbrüche' }
|
|
]
|
|
|
|
const trustBadges = [
|
|
{ icon: 'i-lucide-shield-check', label: 'DSGVO-konform' },
|
|
{ icon: 'i-lucide-server', label: 'Hosting in Deutschland' },
|
|
{ icon: 'i-lucide-lock', label: 'Ende-zu-Ende verschlüsselt' },
|
|
{ icon: 'i-lucide-key', label: 'SSO-fähig' }
|
|
]
|
|
|
|
// Animated counter values
|
|
const animatedValues = ref(stats.map(() => 0))
|
|
const hasAnimated = ref(false)
|
|
|
|
// Counter animation function
|
|
function animateCounter(index: number, target: number, duration: number = 2000) {
|
|
const start = 0
|
|
const startTime = performance.now()
|
|
|
|
function update(currentTime: number) {
|
|
const elapsed = currentTime - startTime
|
|
const progress = Math.min(elapsed / duration, 1)
|
|
|
|
// Easing function (ease-out cubic)
|
|
const easeOut = 1 - Math.pow(1 - progress, 3)
|
|
|
|
animatedValues.value[index] = Math.round(start + (target - start) * easeOut)
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(update)
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(update)
|
|
}
|
|
|
|
// Start animation when section is visible
|
|
onMounted(() => {
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting && !hasAnimated.value) {
|
|
hasAnimated.value = true
|
|
stats.forEach((stat, index) => {
|
|
setTimeout(() => {
|
|
animateCounter(index, stat.value)
|
|
}, index * 200)
|
|
})
|
|
}
|
|
})
|
|
},
|
|
{ threshold: 0.3 }
|
|
)
|
|
|
|
const section = document.querySelector('section')
|
|
if (section) {
|
|
observer.observe(section)
|
|
}
|
|
|
|
onUnmounted(() => {
|
|
observer.disconnect()
|
|
})
|
|
})
|
|
</script>
|