Initialize Next.js project with essential configurations and components

- Added .gitignore to exclude unnecessary files and directories.
- Created next.config.js for Next.js configuration.
- Set up package.json and package-lock.json with dependencies including Next.js, React, and TypeScript.
- Implemented Tailwind CSS for styling with a dedicated tailwind.config.ts.
- Developed core application structure including layout, pages, and components for header, footer, and various sections (Hero, Services, Pricing, etc.).
- Integrated contact form functionality using nodemailer for email handling.
- Established global styles in globals.css and added animations with Framer Motion.
- Included README.md for project documentation and setup instructions.
This commit is contained in:
SarTron-NorthBlue
2025-11-14 16:15:21 +04:00
commit bed824059a
24 changed files with 4229 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
'use client'
export default function BackgroundAnimations({ variant = 'default' }: { variant?: 'default' | 'grid' | 'particles' | 'gradient' }) {
if (variant === 'grid') {
return (
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="animated-grid absolute inset-0 opacity-30" />
</div>
)
}
if (variant === 'particles') {
return (
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="animated-grid absolute inset-0 opacity-20" />
{/* Particules flottantes */}
{[...Array(6)].map((_, i) => (
<div
key={i}
className="floating-blob absolute rounded-full"
style={{
width: `${20 + i * 10}px`,
height: `${20 + i * 10}px`,
background: `radial-gradient(circle, rgba(0, 255, 136, ${0.1 + i * 0.05}) 0%, transparent 70%)`,
left: `${10 + i * 15}%`,
top: `${10 + i * 12}%`,
animationDelay: `${i * 0.5}s`,
animationDuration: `${15 + i * 3}s`,
}}
/>
))}
</div>
)
}
if (variant === 'gradient') {
return (
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="animated-grid absolute inset-0 opacity-25" />
<div
className="absolute top-1/4 right-1/4 w-96 h-96 bg-security-accent/5 rounded-full blur-3xl pulse-glow"
style={{ animationDelay: '0s' }}
/>
<div
className="absolute bottom-1/4 left-1/4 w-96 h-96 bg-green-500/5 rounded-full blur-3xl pulse-glow"
style={{ animationDelay: '2s' }}
/>
<div
className="absolute top-1/2 left-1/2 w-80 h-80 bg-emerald-500/5 rounded-full blur-3xl pulse-glow"
style={{ animationDelay: '4s', transform: 'translate(-50%, -50%)' }}
/>
</div>
)
}
// Default variant
return (
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="animated-grid absolute inset-0 opacity-20" />
<div
className="floating-blob absolute top-1/4 right-1/4 w-96 h-96 bg-security-accent/5 rounded-full blur-3xl"
style={{ animationDelay: '0s' }}
/>
<div
className="floating-blob absolute bottom-1/4 left-1/4 w-96 h-96 bg-green-500/5 rounded-full blur-3xl"
style={{ animationDelay: '1s' }}
/>
<div
className="floating-blob absolute top-1/2 right-1/3 w-80 h-80 bg-emerald-500/5 rounded-full blur-3xl"
style={{ animationDelay: '2s' }}
/>
</div>
)
}

130
components/Footer.tsx Normal file
View File

@@ -0,0 +1,130 @@
'use client'
import { motion } from 'framer-motion'
import { Shield, Mail, Phone, MapPin } from 'lucide-react'
import Link from 'next/link'
export default function Footer() {
const currentYear = new Date().getFullYear()
return (
<footer className="bg-security-dark border-t border-security-border">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-8">
{/* Brand */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
<div className="flex items-center space-x-3 mb-4">
<Shield className="w-8 h-8 text-security-accent" />
<span className="text-2xl font-bold text-white">
RUNLOCK<span className="text-security-accent">.re</span>
</span>
</div>
<p className="text-gray-400 text-sm leading-relaxed">
Solutions de gestion de mots de passe sécurisées pour les entreprises.
Protégez vos données avec Vaultwarden.
</p>
</motion.div>
{/* Navigation */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
>
<h3 className="text-white font-semibold mb-4">Navigation</h3>
<ul className="space-y-2">
{['Accueil', 'Services', 'Tarifs', 'FAQ', 'Contact'].map((item) => (
<li key={item}>
<a
href={`#${item.toLowerCase()}`}
className="text-gray-400 hover:text-security-accent transition-colors text-sm"
>
{item}
</a>
</li>
))}
</ul>
</motion.div>
{/* Services */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<h3 className="text-white font-semibold mb-4">Services</h3>
<ul className="space-y-2">
{['Hébergement Cloud', 'Installation NAS', 'Installation Mini PC', 'Support technique'].map((service) => (
<li key={service}>
<a
href="#services"
className="text-gray-400 hover:text-security-accent transition-colors text-sm"
>
{service}
</a>
</li>
))}
</ul>
</motion.div>
{/* Contact */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<h3 className="text-white font-semibold mb-4">Contact</h3>
<ul className="space-y-3">
<li className="flex items-center space-x-2 text-gray-400 text-sm">
<Mail className="w-4 h-4 text-security-accent" />
<a href="mailto:contact@runlock.re" className="hover:text-security-accent transition-colors">
contact@runlock.re
</a>
</li>
<li className="flex items-center space-x-2 text-gray-400 text-sm">
<Phone className="w-4 h-4 text-security-accent" />
<a href="tel:0693511558" className="hover:text-security-accent transition-colors">
0693 51 15 58
</a>
</li>
<li className="flex items-center space-x-2 text-gray-400 text-sm">
<MapPin className="w-4 h-4 text-security-accent" />
<span>La Réunion</span>
</li>
</ul>
</motion.div>
</div>
<div className="border-t border-security-border pt-8 mt-8">
<div className="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
<p className="text-gray-400 text-sm">
© {currentYear} Runlock.re - Tous droits réservés
</p>
<div className="flex items-center space-x-6 text-sm">
<a href="#" className="text-gray-400 hover:text-security-accent transition-colors">
Mentions légales
</a>
<a href="#" className="text-gray-400 hover:text-security-accent transition-colors">
Politique de confidentialité
</a>
<a href="#" className="text-gray-400 hover:text-security-accent transition-colors">
RGPD
</a>
</div>
</div>
<p className="text-gray-500 text-xs mt-4 text-center">
Service édité par: httpdesign.fr
</p>
</div>
</div>
</footer>
)
}

124
components/Header.tsx Normal file
View File

@@ -0,0 +1,124 @@
'use client'
import { useState, useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Menu, X, Lock, Shield } from 'lucide-react'
import Link from 'next/link'
export default function Header() {
const [isScrolled, setIsScrolled] = useState(false)
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 20)
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])
const navItems = [
{ href: '#accueil', label: 'Accueil' },
{ href: '#services', label: 'Services' },
{ href: '#tarifs', label: 'Tarifs' },
{ href: '#faq', label: 'FAQ' },
{ href: '#contact', label: 'Contact' },
]
return (
<motion.header
initial={{ y: -100 }}
animate={{ y: 0 }}
transition={{ duration: 0.5 }}
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
isScrolled
? 'bg-security-dark/95 backdrop-blur-md shadow-lg shadow-black/50 border-b border-security-border'
: 'bg-transparent'
}`}
>
<nav className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-20">
{/* Logo */}
<Link href="/" className="flex items-center space-x-3 group">
<div className="relative">
<div className="absolute inset-0 bg-security-accent/20 rounded-xl blur-lg group-hover:blur-xl transition-all duration-300" />
<Shield className="relative w-10 h-10 text-security-accent group-hover:scale-110 transition-transform duration-300" />
</div>
<span className="text-2xl font-bold text-white group-hover:text-security-accent transition-colors">
RUNLOCK<span className="gradient-text">.re</span>
</span>
</Link>
{/* Desktop Navigation */}
<div className="hidden md:flex items-center space-x-8">
{navItems.map((item, index) => (
<motion.a
key={item.href}
href={item.href}
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
className="text-gray-300 hover:text-security-accent font-medium transition-colors duration-300 relative group"
>
{item.label}
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-security-accent group-hover:w-full transition-all duration-300" />
</motion.a>
))}
<motion.a
href="#contact"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.5 }}
className="px-6 py-2.5 bg-gradient-to-r from-security-accent to-green-500 text-black rounded-xl font-semibold hover:shadow-lg hover:shadow-security-accent/50 transition-all duration-300 hover:scale-105"
>
Demander un devis
</motion.a>
</div>
{/* Mobile Menu Button */}
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="md:hidden text-gray-300 hover:text-security-accent transition-colors"
aria-label="Menu"
>
{isMobileMenuOpen ? <X size={28} /> : <Menu size={28} />}
</button>
</div>
</nav>
{/* Mobile Menu */}
<AnimatePresence>
{isMobileMenuOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
className="md:hidden bg-security-dark/98 backdrop-blur-md border-t border-security-border shadow-lg"
>
<div className="container mx-auto px-4 py-6 space-y-4">
{navItems.map((item) => (
<a
key={item.href}
href={item.href}
onClick={() => setIsMobileMenuOpen(false)}
className="block text-gray-300 hover:text-security-accent font-medium transition-colors py-2"
>
{item.label}
</a>
))}
<a
href="#contact"
onClick={() => setIsMobileMenuOpen(false)}
className="block w-full text-center px-6 py-3 bg-gradient-to-r from-security-accent to-green-500 text-black rounded-xl font-semibold mt-4"
>
Demander un devis
</a>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.header>
)
}

View File

@@ -0,0 +1,312 @@
'use client'
import { motion } from 'framer-motion'
import { useState } from 'react'
import { Mail, Phone, MapPin, Clock, Send, Shield, MessageSquare } from 'lucide-react'
import BackgroundAnimations from '@/components/BackgroundAnimations'
const contactInfo = [
{
icon: Mail,
label: 'Email',
value: 'contact@runlock.re',
href: 'mailto:contact@runlock.re',
},
{
icon: Phone,
label: 'Téléphone',
value: '0693 51 15 58',
href: 'tel:0693511558',
},
{
icon: MapPin,
label: 'Adresse',
value: 'La Réunion',
href: null,
},
{
icon: Clock,
label: 'Horaires',
value: 'Lundi - Vendredi: 9h00 - 18h00\nWeekend: Fermé',
href: null,
},
]
const services = [
'Hébergement Cloud',
'Installation NAS',
'Installation Mini PC',
'Autre',
]
export default function Contact() {
const [formData, setFormData] = useState({
name: '',
email: '',
company: '',
service: '',
message: '',
})
const [isSubmitting, setIsSubmitting] = useState(false)
const [submitStatus, setSubmitStatus] = useState<{ type: 'success' | 'error' | null; message: string }>({
type: null,
message: '',
})
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsSubmitting(true)
setSubmitStatus({ type: null, message: '' })
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
const data = await response.json()
if (response.ok) {
setSubmitStatus({
type: 'success',
message: data.message || 'Votre message a été envoyé avec succès !',
})
// Réinitialiser le formulaire
setFormData({
name: '',
email: '',
company: '',
service: '',
message: '',
})
} else {
setSubmitStatus({
type: 'error',
message: data.error || 'Une erreur est survenue. Veuillez réessayer.',
})
}
} catch (error) {
console.error('Erreur lors de l\'envoi du formulaire:', error)
setSubmitStatus({
type: 'error',
message: 'Une erreur est survenue lors de l\'envoi de votre message. Veuillez réessayer.',
})
} finally {
setIsSubmitting(false)
}
}
return (
<section id="contact" className="section-padding bg-security-darker relative overflow-hidden">
<BackgroundAnimations variant="gradient" />
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
{/* Centered Header */}
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="text-center mb-16"
>
<div className="inline-flex items-center justify-center w-20 h-20 bg-security-accent/10 border border-security-accent/30 rounded-2xl mb-6">
<MessageSquare className="w-10 h-10 text-security-accent" />
</div>
<h2 className="text-5xl md:text-6xl font-black mb-4 text-white">
<span className="gradient-text">Contactez-nous</span>
</h2>
<p className="text-xl text-gray-400 max-w-2xl mx-auto">
Nous sommes pour répondre à vos questions
</p>
</motion.div>
<div className="max-w-5xl mx-auto">
<div className="grid lg:grid-cols-3 gap-8">
{/* Left - Contact Info Cards */}
<div className="lg:col-span-1 space-y-4">
{contactInfo.map((info, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="card-dark p-5 rounded-xl card-hover"
>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 bg-security-accent/10 rounded-xl flex items-center justify-center flex-shrink-0">
<info.icon className="w-6 h-6 text-security-accent" />
</div>
<div>
<div className="text-xs text-gray-500 mb-1">{info.label}</div>
{info.href ? (
<a
href={info.href}
className="text-white hover:text-security-accent transition-colors font-medium text-sm"
>
{info.value}
</a>
) : (
<div className="text-white whitespace-pre-line font-medium text-sm">{info.value}</div>
)}
</div>
</div>
</motion.div>
))}
{/* Security Badge */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.4 }}
className="card-dark p-5 rounded-xl border border-security-accent/20"
>
<div className="flex items-center space-x-3 mb-2">
<Shield className="w-5 h-5 text-security-accent" />
<span className="text-white font-semibold text-sm">Protection anti-spam</span>
</div>
<p className="text-gray-400 text-xs">
Vos données sont sécurisées et ne seront jamais partagées.
</p>
</motion.div>
</div>
{/* Right - Contact Form */}
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="lg:col-span-2 card-dark p-8 rounded-3xl"
>
<h3 className="text-2xl font-bold text-white mb-6">Envoyez-nous un message</h3>
{/* Message de statut */}
{submitStatus.type && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className={`p-4 rounded-xl mb-6 ${
submitStatus.type === 'success'
? 'bg-green-500/10 border border-green-500/30 text-green-400'
: 'bg-red-500/10 border border-red-500/30 text-red-400'
}`}
>
<div className="flex items-center space-x-2">
{submitStatus.type === 'success' ? (
<Shield className="w-5 h-5" />
) : (
<MessageSquare className="w-5 h-5" />
)}
<span className="text-sm font-medium">{submitStatus.message}</span>
</div>
</motion.div>
)}
<form onSubmit={handleSubmit} className="space-y-5">
<div className="grid md:grid-cols-2 gap-5">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-400 mb-2">
Nom complet
</label>
<input
type="text"
id="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-4 py-3 bg-security-dark border border-security-border rounded-xl text-white placeholder-gray-500 focus:outline-none focus:border-security-accent transition-colors"
placeholder="Votre nom"
required
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-400 mb-2">
Email
</label>
<input
type="email"
id="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full px-4 py-3 bg-security-dark border border-security-border rounded-xl text-white placeholder-gray-500 focus:outline-none focus:border-security-accent transition-colors"
placeholder="votre@email.com"
required
/>
</div>
</div>
<div className="grid md:grid-cols-2 gap-5">
<div>
<label htmlFor="company" className="block text-sm font-medium text-gray-400 mb-2">
Entreprise
</label>
<input
type="text"
id="company"
value={formData.company}
onChange={(e) => setFormData({ ...formData, company: e.target.value })}
className="w-full px-4 py-3 bg-security-dark border border-security-border rounded-xl text-white placeholder-gray-500 focus:outline-none focus:border-security-accent transition-colors"
placeholder="Nom de votre entreprise"
/>
</div>
<div>
<label htmlFor="service" className="block text-sm font-medium text-gray-400 mb-2">
Service souhaité
</label>
<select
id="service"
value={formData.service}
onChange={(e) => setFormData({ ...formData, service: e.target.value })}
className="w-full px-4 py-3 bg-security-dark border border-security-border rounded-xl text-white focus:outline-none focus:border-security-accent transition-colors"
required
>
<option value="">Sélectionnez un service</option>
{services.map((service) => (
<option key={service} value={service}>
{service}
</option>
))}
</select>
</div>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-gray-400 mb-2">
Message
</label>
<textarea
id="message"
value={formData.message}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
rows={5}
className="w-full px-4 py-3 bg-security-dark border border-security-border rounded-xl text-white placeholder-gray-500 focus:outline-none focus:border-security-accent transition-colors resize-none"
placeholder="Votre message..."
required
/>
</div>
<motion.button
type="submit"
disabled={isSubmitting}
whileHover={!isSubmitting ? { scale: 1.02 } : {}}
whileTap={!isSubmitting ? { scale: 0.98 } : {}}
className={`w-full py-4 px-6 bg-gradient-to-r from-security-accent to-green-500 text-black rounded-xl font-bold shadow-lg shadow-security-accent/50 hover:shadow-xl transition-all duration-300 flex items-center justify-center space-x-2 ${
isSubmitting ? 'opacity-50 cursor-not-allowed' : ''
}`}
>
<span>{isSubmitting ? 'Envoi en cours...' : 'Envoyer le message'}</span>
{!isSubmitting && <Send className="w-5 h-5" />}
</motion.button>
</form>
</motion.div>
</div>
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,97 @@
'use client'
import { motion } from 'framer-motion'
import { useInView } from 'framer-motion'
import { useRef } from 'react'
import { Lock, Key, Shield, TrendingUp } from 'lucide-react'
import BackgroundAnimations from '@/components/BackgroundAnimations'
const algorithms = [
{
name: 'AES-256-GCM',
bits: '256',
details: 'Rounds 14 • 3.7 GB/s',
icon: Lock,
gradient: 'from-blue-500 to-cyan-500',
},
{
name: 'RSA-4096',
bits: '4096',
details: 'Primes 2048 bits • Sécurité Ultra',
icon: Key,
gradient: 'from-purple-500 to-pink-500',
},
{
name: 'ECDSA P-384',
bits: '384',
details: 'Courbe P-384 • Vérification Instantanée',
icon: Shield,
gradient: 'from-security-accent to-green-500',
},
]
export default function Encryption() {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, margin: '-100px' })
return (
<section className="section-padding bg-security-dark relative overflow-hidden">
<BackgroundAnimations variant="particles" />
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
{/* Split Layout - Left Text, Right Visual */}
<div className="grid lg:grid-cols-2 gap-16 items-center">
{/* Left Side - Text Content */}
<motion.div
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<div className="inline-block px-4 py-2 bg-security-accent/10 border border-security-accent/30 rounded-full mb-6">
<span className="text-security-accent text-sm font-semibold">Chiffrement</span>
</div>
<h2 className="text-5xl md:text-6xl font-black mb-6 text-white">
<span className="gradient-text">Avancé</span> pour vos données
</h2>
<p className="text-xl text-gray-400 mb-8 leading-relaxed">
Algorithmes cryptographiques de pointe pour protéger vos données avec une sécurité maximale.
</p>
<div className="flex items-center space-x-4 text-gray-300">
<TrendingUp className="w-6 h-6 text-security-accent" />
<span className="text-sm">Performance optimale garantie</span>
</div>
</motion.div>
{/* Right Side - Algorithm Cards */}
<div ref={ref} className="space-y-6">
{algorithms.map((algo, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: 50 }}
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.6, delay: index * 0.2 }}
className="card-dark p-6 rounded-2xl card-hover"
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className={`w-16 h-16 bg-gradient-to-br ${algo.gradient} rounded-xl flex items-center justify-center shadow-lg`}>
<algo.icon className="w-8 h-8 text-white" />
</div>
<div>
<h3 className="text-xl font-bold text-white mb-1">{algo.name}</h3>
<p className="text-sm text-gray-400">{algo.details}</p>
</div>
</div>
<div className="text-right">
<div className="text-3xl font-black gradient-text">{algo.bits}</div>
<div className="text-xs text-gray-500">bits</div>
</div>
</div>
</motion.div>
))}
</div>
</div>
</div>
</section>
)
}

113
components/sections/FAQ.tsx Normal file
View File

@@ -0,0 +1,113 @@
'use client'
import { motion, AnimatePresence } from 'framer-motion'
import { useState } from 'react'
import { ChevronDown, HelpCircle, MessageCircle } from 'lucide-react'
import BackgroundAnimations from '@/components/BackgroundAnimations'
const faqs = [
{
question: "Qu'est-ce que Vaultwarden ?",
answer: "Une implémentation légère et performante du serveur Bitwarden open-source. Permet de gérer vos mots de passe de manière sécurisée avec un chiffrement de bout en bout.",
},
{
question: 'Est-ce que mes données restent chez moi ?',
answer: "Oui, avec nos solutions NAS ou Mini PC, vos données restent 100% chez vous. Pour l'hébergement cloud, vos données sont stockées sur nos serveurs sécurisés en France avec chiffrement de bout en bout.",
},
{
question: 'Que se passe-t-il si mon serveur tombe en panne ?',
answer: "Notre hébergement cloud garantit 99.9% d'uptime. Pour les installations locales, nous proposons des contrats de support. Les apps Bitwarden fonctionnent aussi en mode offline.",
},
{
question: 'Quelle est la différence entre Bitwarden et Vaultwarden ?',
answer: "Vaultwarden est plus léger et consomme moins de ressources, écrit en Rust. Parfaitement compatible avec Bitwarden, avec toutes les fonctionnalités premium incluses.",
},
{
question: "Proposez-vous une migration depuis d'autres gestionnaires ?",
answer: "Oui, nous accompagnons la migration depuis LastPass, 1Password, Dashlane, KeePass et autres. Outils et assistance fournis pour un transfert sécurisé.",
},
{
question: 'Quels sont les prérequis pour l\'installation NAS ?',
answer: "NAS compatible (Synology, QNAP, etc.) avec Docker installé, minimum 2 Go de RAM et quelques Go d'espace disque. Nom de domaine et accès réseau requis.",
},
]
export default function FAQ() {
const [openIndex, setOpenIndex] = useState<number | null>(null)
return (
<section id="faq" className="section-padding bg-security-dark relative overflow-hidden">
<BackgroundAnimations variant="default" />
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
{/* Split Layout */}
<div className="grid lg:grid-cols-2 gap-16 items-start">
{/* Left - Header */}
<motion.div
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="sticky top-24"
>
<div className="inline-flex items-center justify-center w-20 h-20 bg-security-accent/10 border border-security-accent/30 rounded-2xl mb-6">
<HelpCircle className="w-10 h-10 text-security-accent" />
</div>
<h2 className="text-5xl md:text-6xl font-black mb-6 text-white">
Questions <span className="gradient-text">Fréquentes</span>
</h2>
<p className="text-xl text-gray-400 leading-relaxed mb-8">
Tout ce que vous devez savoir sur nos solutions Vaultwarden
</p>
<div className="flex items-center space-x-3 text-gray-400">
<MessageCircle className="w-5 h-5 text-security-accent" />
<span className="text-sm">Besoin d'aide ? Contactez-nous</span>
</div>
</motion.div>
{/* Right - FAQ Items */}
<div className="space-y-4">
{faqs.map((faq, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: 50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="card-dark rounded-2xl overflow-hidden"
>
<button
onClick={() => setOpenIndex(openIndex === index ? null : index)}
className="w-full px-6 py-5 flex items-center justify-between text-left hover:bg-security-card/50 transition-colors"
>
<span className="text-lg font-bold text-white pr-8">{faq.question}</span>
<motion.div
animate={{ rotate: openIndex === index ? 180 : 0 }}
transition={{ duration: 0.3 }}
className="flex-shrink-0"
>
<ChevronDown className="w-5 h-5 text-security-accent" />
</motion.div>
</button>
<AnimatePresence>
{openIndex === index && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3 }}
className="overflow-hidden"
>
<div className="px-6 pb-5 text-gray-400 leading-relaxed border-t border-security-border">
{faq.answer}
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
))}
</div>
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,112 @@
'use client'
import { motion } from 'framer-motion'
import { Lock, Shield, ArrowRight, Sparkles, Zap, Globe } from 'lucide-react'
import BackgroundAnimations from '@/components/BackgroundAnimations'
export default function Hero() {
return (
<section id="accueil" className="relative min-h-screen flex items-center justify-center overflow-hidden pt-20 gradient-bg">
{/* Animated Grid Background */}
<BackgroundAnimations variant="gradient" />
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="max-w-6xl mx-auto">
{/* Top Badge */}
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="flex justify-center mb-8"
>
<div className="inline-flex items-center space-x-2 px-6 py-3 bg-security-card border border-security-border rounded-full">
<Zap className="w-4 h-4 text-security-accent" />
<span className="text-sm font-semibold text-gray-300">Expert Vaultwarden à la Réunion</span>
</div>
</motion.div>
{/* Main Content - Centered Layout */}
<div className="text-center mb-16">
<motion.h1
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }}
className="text-6xl md:text-8xl font-black mb-8 leading-tight"
>
<span className="block text-white mb-4">Sécurisez vos</span>
<span className="gradient-text block text-7xl md:text-9xl">mots de passe</span>
<span className="block text-4xl md:text-5xl mt-4 text-gray-400 font-light">
d'entreprise
</span>
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
className="text-xl md:text-2xl text-gray-400 mb-12 max-w-3xl mx-auto leading-relaxed"
>
Hébergement sécurisé, installation NAS et Mini PC. Solutions de gestion de mots de passe
pour entreprises <span className="text-security-accent font-bold">974</span>.
</motion.p>
{/* CTA Buttons - Horizontal */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }}
className="flex flex-col sm:flex-row gap-4 justify-center items-center"
>
<motion.a
href="#contact"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="group px-10 py-5 bg-gradient-to-r from-security-accent to-green-500 text-black rounded-2xl font-bold text-lg shadow-2xl shadow-security-accent/50 flex items-center space-x-3 hover:shadow-security-accent/70 transition-all duration-300"
>
<span>Voir nos offres</span>
<ArrowRight className="w-6 h-6 group-hover:translate-x-2 transition-transform" />
</motion.a>
<motion.a
href="#services"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-10 py-5 border-2 border-security-accent text-security-accent rounded-2xl font-bold text-lg hover:bg-security-accent/10 transition-all duration-300"
>
En savoir plus
</motion.a>
</motion.div>
</div>
{/* Stats Grid - 3 Columns */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.8 }}
className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-4xl mx-auto"
>
{[
{ icon: Shield, label: 'AES-256', value: 'Chiffrement', color: 'text-security-accent' },
{ icon: Lock, label: 'Zero-Knowledge', value: 'Architecture', color: 'text-green-400' },
{ icon: Globe, label: 'RGPD', value: 'Conformité 100%', color: 'text-emerald-400' },
].map((stat, index) => (
<motion.div
key={index}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 1 + index * 0.2 }}
className="p-6 card-dark security-glow card-hover"
>
<stat.icon className={`w-10 h-10 ${stat.color} mb-4 mx-auto`} />
<div className="text-2xl font-bold text-white mb-2">{stat.label}</div>
<div className="text-sm text-gray-400">{stat.value}</div>
</motion.div>
))}
</motion.div>
</div>
</div>
{/* Bottom Wave */}
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-security-dark to-transparent" />
</section>
)
}

View File

@@ -0,0 +1,164 @@
'use client'
import { motion } from 'framer-motion'
import { useInView } from 'framer-motion'
import { useRef } from 'react'
import { Cloud, Server, HardDrive, CheckCircle, ArrowRight, Star } from 'lucide-react'
import BackgroundAnimations from '@/components/BackgroundAnimations'
const plans = [
{
icon: Cloud,
name: 'Cloud Hébergé',
subtitle: 'Hébergement sécurisé chez nous',
price: '20',
period: '/ mois par utilisateur',
badge: null,
features: [
'5 à 100 utilisateurs',
'Stockage 10 à 100 Go',
'Sauvegardes quotidiennes',
'SSL/TLS inclus',
'Support email',
'Mises à jour automatiques',
],
cta: 'Demander un devis',
gradient: 'from-blue-500 to-cyan-500',
},
{
icon: HardDrive,
name: 'Installation Mini PC',
subtitle: 'Solution clé en main',
price: '500',
period: ' installation',
badge: 'Recommandé',
features: [
'Matériel inclus',
'Installation complète',
'Configuration réseau',
'Formation sur site',
'Support 60 jours inclus',
'Garantie matériel 1 an',
],
cta: 'Demander un devis',
gradient: 'from-security-accent to-green-500',
},
{
icon: Server,
name: 'Installation NAS',
subtitle: 'Sur votre infrastructure',
price: '350',
period: ' installation',
badge: null,
features: [
'Configuration complète',
'Formation personnalisée',
'Documentation détaillée',
'Certificat SSL',
'Support 30 jours inclus',
'Support optionnel dispo',
],
cta: 'Demander un devis',
gradient: 'from-purple-500 to-pink-500',
},
]
export default function Pricing() {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, margin: '-100px' })
return (
<section id="tarifs" className="section-padding bg-security-darker relative overflow-hidden">
<BackgroundAnimations variant="default" />
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
{/* Section Header - Centered */}
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="text-center mb-16"
>
<div className="inline-block px-4 py-2 bg-security-accent/10 border border-security-accent/30 rounded-full mb-6">
<span className="text-security-accent text-sm font-semibold">Nos Offres</span>
</div>
<h2 className="text-5xl md:text-6xl font-black mb-6 text-white">
Nos <span className="gradient-text">Tarifs</span>
</h2>
<p className="text-xl text-gray-400 leading-relaxed max-w-2xl mx-auto">
Des offres transparentes pour tous les budgets, de l'hébergement cloud aux installations sur site
</p>
</motion.div>
{/* Pricing Cards - Centered Grid Layout */}
<div ref={ref} className="flex justify-center">
<div className="grid md:grid-cols-3 gap-8 max-w-7xl">
{plans.map((plan, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 50 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: index * 0.2 }}
className={`relative ${plan.badge ? 'neon-border' : 'card-dark'} p-10 rounded-3xl card-hover flex flex-col`}
>
{plan.badge && (
<div className="absolute -top-3 left-8 z-10">
<span className="px-4 py-1 bg-gradient-to-r from-security-accent to-green-500 text-black text-sm font-bold rounded-full shadow-lg flex items-center space-x-1">
<Star className="w-3 h-3" />
<span>{plan.badge}</span>
</span>
</div>
)}
{/* Icon and Price */}
<div className="text-center mb-8">
<div className={`w-24 h-24 bg-gradient-to-br ${plan.gradient} rounded-2xl flex items-center justify-center mb-6 shadow-2xl mx-auto`}>
<plan.icon className="w-12 h-12 text-white" />
</div>
<div className="mb-3">
<div className="text-sm text-gray-400 mb-2">À partir de</div>
<div className="flex items-baseline justify-center">
<span className="text-5xl font-black gradient-text">{plan.price}€</span>
<span className="text-gray-400 ml-2 text-base">{plan.period}</span>
</div>
</div>
</div>
{/* Title and Description */}
<div className="text-center mb-8 flex-grow">
<h3 className="text-2xl font-bold text-white mb-3">{plan.name}</h3>
<p className="text-gray-400 text-base mb-8">{plan.subtitle}</p>
{/* Features */}
<ul className="space-y-4 text-left">
{plan.features.map((feature, idx) => (
<li key={idx} className="flex items-center space-x-3 text-gray-300">
<CheckCircle className="w-5 h-5 text-security-accent flex-shrink-0" />
<span className="text-base">{feature}</span>
</li>
))}
</ul>
</div>
{/* CTA Button */}
<motion.a
href="#contact"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className={`w-full py-4 px-6 rounded-xl font-bold text-center transition-all duration-300 flex items-center justify-center space-x-2 ${
plan.badge
? 'bg-gradient-to-r from-security-accent to-green-500 text-black shadow-lg shadow-security-accent/50 hover:shadow-xl'
: 'bg-security-card border-2 border-security-border text-white hover:border-security-accent'
}`}
>
<span>{plan.cta}</span>
<ArrowRight className="w-5 h-5" />
</motion.a>
</motion.div>
))}
</div>
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,118 @@
'use client'
import { motion } from 'framer-motion'
import { useInView } from 'framer-motion'
import { useRef } from 'react'
import { Cloud, Server, HardDrive, CheckCircle, ArrowRight } from 'lucide-react'
import BackgroundAnimations from '@/components/BackgroundAnimations'
const services = [
{
icon: Cloud,
title: 'Hébergement Cloud Vaultwarden Réunion',
description: 'Hébergement sécurisé de votre Vaultwarden à la Réunion avec sauvegardes automatiques quotidiennes.',
features: ['Sauvegardes automatiques', 'SSL/TLS inclus', 'Mise à jour gérée', 'Support technique'],
gradient: 'from-blue-500 to-cyan-500',
},
{
icon: HardDrive,
title: 'Installation Vaultwarden Mini PC Réunion',
description: 'Solution compacte clé en main pour entreprises de la Réunion sans NAS.',
features: ['Matériel fourni', 'Installation complète', 'Configuration réseau', 'Support 60 jours'],
gradient: 'from-security-accent to-green-500',
popular: true,
},
{
icon: Server,
title: 'Installation Vaultwarden NAS Réunion',
description: 'Installation complète de Vaultwarden sur votre NAS. Formation incluse.',
features: ['Configuration complète', 'Formation personnalisée', 'Documentation détaillée', 'Support 30 jours'],
gradient: 'from-purple-500 to-pink-500',
},
]
export default function Services() {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, margin: '-100px' })
return (
<section id="services" className="section-padding bg-security-dark relative overflow-hidden">
<BackgroundAnimations variant="particles" />
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
{/* Section Header - Centered */}
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="text-center mb-16"
>
<div className="inline-block px-4 py-2 bg-security-accent/10 border border-security-accent/30 rounded-full mb-6">
<span className="text-security-accent text-sm font-semibold">Nos Services</span>
</div>
<h2 className="text-5xl md:text-6xl font-black mb-6 text-white">
Des solutions <span className="gradient-text">adaptées</span>
</h2>
<p className="text-xl text-gray-400 max-w-2xl mx-auto leading-relaxed">
Des solutions adaptées à vos besoins pour sécuriser vos mots de passe d'entreprise
</p>
</motion.div>
{/* Services - Centered Grid Layout */}
<div ref={ref} className="flex justify-center">
<div className="grid md:grid-cols-3 gap-6 max-w-7xl">
{services.map((service, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 50 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: index * 0.15 }}
className={`relative ${service.popular ? 'neon-border' : 'card-dark'} p-8 rounded-3xl card-hover flex flex-col`}
>
{service.popular && (
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 z-10">
<span className="px-4 py-1 bg-gradient-to-r from-security-accent to-green-500 text-black text-sm font-bold rounded-full shadow-lg">
POPULAIRE
</span>
</div>
)}
{/* Icon */}
<div className="flex-shrink-0 mb-6">
<div className={`w-20 h-20 bg-gradient-to-br ${service.gradient} rounded-2xl flex items-center justify-center shadow-2xl`}>
<service.icon className="w-10 h-10 text-white" />
</div>
</div>
{/* Content */}
<div className="flex-grow">
<h3 className="text-2xl font-bold text-white mb-3">{service.title}</h3>
<p className="text-gray-400 leading-relaxed mb-6 text-sm">{service.description}</p>
{/* Features - Vertical List */}
<ul className="space-y-3 mb-6">
{service.features.map((feature, idx) => (
<li key={idx} className="flex items-center space-x-2 text-gray-300">
<CheckCircle className="w-4 h-4 text-security-accent flex-shrink-0" />
<span className="text-sm">{feature}</span>
</li>
))}
</ul>
<motion.a
href="#contact"
whileHover={{ x: 5 }}
className="inline-flex items-center space-x-2 text-security-accent font-semibold hover:text-green-400 transition-colors"
>
<span>Découvrir</span>
<ArrowRight className="w-4 h-4" />
</motion.a>
</div>
</motion.div>
))}
</div>
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,75 @@
'use client'
import { motion } from 'framer-motion'
import { useInView } from 'framer-motion'
import { useRef, useState, useEffect } from 'react'
import { Lock, Clock, Ban, CheckCircle } from 'lucide-react'
import BackgroundAnimations from '@/components/BackgroundAnimations'
export default function Stats() {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, margin: '-100px' })
// Générer un nombre aléatoire pour les tentatives bloquées
// Initialiser à 0 pour éviter les erreurs d'hydratation (serv/client doivent correspondre)
const [blockedAttempts, setBlockedAttempts] = useState<number>(0)
// Mettre à jour le nombre aléatoire après le montage et périodiquement
useEffect(() => {
// Générer le premier nombre aléatoire uniquement côté client
const randomValue = Math.floor(Math.random() * 10000)
setBlockedAttempts(randomValue)
// Mettre à jour le nombre aléatoire périodiquement pour donner un effet dynamique
const interval = setInterval(() => {
// Générer un nouveau nombre aléatoire entre 0 et 9999
const newRandomValue = Math.floor(Math.random() * 10000)
setBlockedAttempts(newRandomValue)
}, 5000) // Mise à jour toutes les 5 secondes
return () => clearInterval(interval)
}, [])
const stats = [
{ icon: Clock, value: '99.9', unit: '%', label: 'Uptime', gradient: 'from-blue-500 to-cyan-500', isDynamic: false },
{ icon: Lock, value: '256', unit: ' Bit', label: 'Encryption', gradient: 'from-security-accent to-green-500', isDynamic: false },
{ icon: Clock, value: '24', unit: '/7', label: 'Support', gradient: 'from-purple-500 to-pink-500', isDynamic: false },
{ icon: Ban, value: blockedAttempts.toString(), label: 'Tentatives bloquées', gradient: 'from-red-500 to-orange-500', isDynamic: true },
{ icon: CheckCircle, value: '100', unit: '%', label: 'Conformité RGPD', gradient: 'from-emerald-500 to-teal-500', isDynamic: false },
]
return (
<section className="section-padding bg-security-dark relative overflow-hidden">
<BackgroundAnimations variant="grid" />
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div ref={ref} className="grid grid-cols-2 md:grid-cols-5 gap-4 md:gap-6">
{stats.map((stat, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30, rotateY: -15 }}
animate={isInView ? { opacity: 1, y: 0, rotateY: 0 } : {}}
transition={{ duration: 0.6, delay: index * 0.1 }}
className="text-center p-6 card-dark card-hover relative overflow-hidden group"
>
{/* Gradient Background on Hover */}
<div className={`absolute inset-0 bg-gradient-to-br ${stat.gradient} opacity-0 group-hover:opacity-10 transition-opacity duration-300`} />
<div className={`w-14 h-14 bg-gradient-to-br ${stat.gradient} rounded-xl flex items-center justify-center mx-auto mb-4 relative z-10 shadow-lg`}>
<stat.icon className="w-7 h-7 text-white" />
</div>
<div className="mb-2 relative z-10">
<span className="text-3xl md:text-4xl font-bold text-white">
{stat.isDynamic ? blockedAttempts.toString() : (isInView ? stat.value : '0')}
</span>
{stat.unit && (
<span className="text-xl text-gray-400">{stat.unit}</span>
)}
</div>
<div className="text-sm text-gray-400 font-medium relative z-10">{stat.label}</div>
</motion.div>
))}
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,129 @@
'use client'
import { motion } from 'framer-motion'
import { useInView } from 'framer-motion'
import { useRef } from 'react'
import { Clock, Database, Key, Shield, Bug, FileText } from 'lucide-react'
const timelineItems = [
{
time: '24/7',
title: 'Surveillance Continue',
status: 'Actif',
description: 'Surveillance 24/7 avec détection automatique des menaces',
metrics: ['99.9% Uptime', '0 Incidents'],
icon: Clock,
gradient: 'from-security-accent to-green-500',
},
{
time: 'T+2min',
title: 'Sauvegarde Automatique',
status: 'À jour',
description: 'Sauvegarde incrémentielle toutes les 2 minutes',
metrics: ['100% Intégrité', '3x Redondance'],
icon: Database,
gradient: 'from-blue-500 to-cyan-500',
},
{
time: 'T+5min',
title: 'Rotation des Clés',
status: 'Renouvelé',
description: 'Rotation automatique des clés de chiffrement',
metrics: ['AES-256', '4096 Bits RSA'],
icon: Key,
gradient: 'from-purple-500 to-pink-500',
},
{
time: 'T+10min',
title: 'Audit de Sécurité',
status: 'Validé',
description: 'Vérification automatique des permissions',
metrics: ['100% Conformité', '0 Anomalies'],
icon: Shield,
gradient: 'from-yellow-500 to-orange-500',
},
{
time: 'T+15min',
title: 'Test de Pénétration',
status: 'Réussi',
description: 'Test automatique des vulnérabilités',
metrics: ['0 Vulnérabilités', '100% Protection'],
icon: Bug,
gradient: 'from-red-500 to-pink-500',
},
{
time: 'T+30min',
title: 'Rapport de Sécurité',
status: 'Généré',
description: 'Rapport détaillé des activités de sécurité',
metrics: ['ISO 27001', 'RGPD Conformité'],
icon: FileText,
gradient: 'from-teal-500 to-cyan-500',
},
]
export default function Timeline() {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, margin: '-100px' })
return (
<section className="section-padding bg-security-darker relative overflow-hidden">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
{/* Centered Header */}
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="text-center mb-16"
>
<h2 className="text-5xl md:text-6xl font-black mb-4 text-white">
Timeline de <span className="gradient-text">Sécurité</span>
</h2>
<p className="text-xl text-gray-400 max-w-2xl mx-auto">
Protection continue de votre infrastructure
</p>
</motion.div>
{/* Horizontal Timeline - Cards in a Row */}
<div ref={ref} className="relative">
<div className="grid md:grid-cols-3 lg:grid-cols-6 gap-4">
{timelineItems.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 50 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: index * 0.1 }}
className="card-dark p-6 rounded-2xl card-hover relative"
>
{/* Time Badge */}
<div className={`w-12 h-12 bg-gradient-to-br ${item.gradient} rounded-xl flex items-center justify-center mb-4 shadow-lg`}>
<item.icon className="w-6 h-6 text-white" />
</div>
<div className="text-xs font-bold text-security-accent mb-2">{item.time}</div>
<h3 className="text-lg font-bold text-white mb-2">{item.title}</h3>
<p className="text-xs text-gray-400 mb-4">{item.description}</p>
<div className="space-y-1">
{item.metrics.map((metric, idx) => (
<div key={idx} className="text-xs text-gray-500">
{metric}
</div>
))}
</div>
{/* Status Badge */}
<div className="absolute top-4 right-4">
<span className="px-2 py-1 bg-security-accent/20 text-security-accent text-xs font-semibold rounded-full">
{item.status}
</span>
</div>
</motion.div>
))}
</div>
</div>
</div>
</section>
)
}