feat(ui): add animated horizontal process steps
This commit is contained in:
155
shop/components/ProcessSteps.tsx
Normal file
155
shop/components/ProcessSteps.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
interface Step {
|
||||
number: string
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const steps: Step[] = [
|
||||
{
|
||||
number: '1',
|
||||
title: 'Kunde wählen',
|
||||
description: 'Gewünschten Kunden auswählen, der die Lizenz erhalten soll.'
|
||||
},
|
||||
{
|
||||
number: '2',
|
||||
title: 'Lizenz wählen',
|
||||
description: 'Gewünschte Lizenzen für den Kunden auswählen und die Bestellung abschließen.'
|
||||
},
|
||||
{
|
||||
number: '3',
|
||||
title: 'Live gehen',
|
||||
description: 'Nach dem Erhalt der Lizenz direkt live gehen und durchstarten.'
|
||||
}
|
||||
]
|
||||
|
||||
const containerVariants = {
|
||||
hidden: {},
|
||||
visible: {
|
||||
transition: {
|
||||
delayChildren: 0.2,
|
||||
staggerChildren: 0.35
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stepVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
y: 20
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
type: 'spring' as const,
|
||||
stiffness: 70,
|
||||
damping: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function ProcessSteps() {
|
||||
return (
|
||||
<section className="w-full py-16 px-4 relative z-10">
|
||||
<div className="container max-w-5xl mx-auto flex flex-col items-center">
|
||||
{/* Section Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ type: 'spring' as const, stiffness: 60, damping: 15 }}
|
||||
className="text-center space-y-3 mb-16 max-w-2xl"
|
||||
>
|
||||
<h2 className="text-3xl sm:text-4xl font-extrabold text-white tracking-tight">
|
||||
Wie funktioniert die Bereitstellung?
|
||||
</h2>
|
||||
<p className="text-slate-400 text-base sm:text-lg leading-relaxed">
|
||||
In nur drei einfachen Schritten richten Sie CASPOS Lizenzen für Ihre Kunden ein.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Horizontale Timeline */}
|
||||
<div className="relative w-full">
|
||||
{/* Basis-Linie exakt zwischen Schritt 1 und 3 */}
|
||||
<div className="hidden md:block absolute top-6 left-[16.6%] right-[16.6%] h-[2px] bg-white/10 z-0 rounded-full" />
|
||||
|
||||
{/* Animierte, aufgeladene Linie (sky-500) */}
|
||||
<motion.div
|
||||
initial={{ scaleX: 0 }}
|
||||
whileInView={{ scaleX: 1 }}
|
||||
viewport={{ once: true, margin: '-50px' }}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
ease: 'easeInOut',
|
||||
delay: 0.2
|
||||
}}
|
||||
className="hidden md:block absolute top-6 left-[16.6%] right-[16.6%] h-[2px] bg-sky-500 origin-left z-0 rounded-full shadow-[0_0_12px_rgba(14,165,233,0.8)]"
|
||||
/>
|
||||
|
||||
{/* 3 Schritte Nebeneinander */}
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: '-50px' }}
|
||||
className="grid grid-cols-1 md:grid-cols-3 gap-10 md:gap-6 w-full relative z-10"
|
||||
>
|
||||
{steps.map((step, index) => {
|
||||
const isLiveStep = index === 2
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={step.number}
|
||||
variants={stepVariants}
|
||||
className="group flex flex-col items-center text-center space-y-4 cursor-pointer relative"
|
||||
>
|
||||
{/* Nummer im runden Kreis auf der Linie */}
|
||||
<div className="relative">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.15 }}
|
||||
transition={{ type: 'spring' as const, stiffness: 300, damping: 20 }}
|
||||
className="w-12 h-12 rounded-full bg-slate-950 border-2 border-slate-700 text-slate-300 font-extrabold text-lg flex items-center justify-center group-hover:border-sky-400 group-hover:text-sky-400 group-hover:bg-slate-900 group-hover:shadow-[0_0_20px_rgba(56,189,248,0.4)] transition-all duration-300 z-10 relative"
|
||||
>
|
||||
{step.number}
|
||||
</motion.div>
|
||||
|
||||
{/* Pulsierender Ziel-Ring bei Schritt 3 */}
|
||||
{isLiveStep && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 1.6 }}
|
||||
className="absolute inset-0 rounded-full border-2 border-emerald-400/60 pointer-events-none animate-ping"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Text-Inhalt zentriert ohne Boxen-Hintergrund */}
|
||||
<div className="space-y-2 max-w-[280px]">
|
||||
<h3 className="text-xl font-bold text-white group-hover:text-sky-400 transition-colors duration-300 flex items-center justify-center gap-2">
|
||||
{step.title}
|
||||
{isLiveStep && (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
|
||||
LIVE
|
||||
</span>
|
||||
)}
|
||||
</h3>
|
||||
<p className="text-slate-400 text-sm leading-relaxed">
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
})}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user