first commit
This commit is contained in:
140
src/components/About.tsx
Normal file
140
src/components/About.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { User, Heart, Target, Coffee } from 'lucide-react';
|
||||
|
||||
const About = () => {
|
||||
const stats = [
|
||||
{ icon: <User size={24} />, value: "3ème", label: "Année d'études" },
|
||||
{ icon: <Heart size={24} />, value: "100%", label: "Passion" },
|
||||
{ icon: <Target size={24} />, value: "∞", label: "Objectifs" },
|
||||
{ icon: <Coffee size={24} />, value: "☕", label: "Fuel quotidien" }
|
||||
];
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
delayChildren: 0.3
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 50 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.25, 0.1, 0.25, 1] as const
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="about" className="about">
|
||||
<div className="container">
|
||||
<motion.div
|
||||
className="section-header"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h2 className="section-title">À propos de moi</h2>
|
||||
<p className="section-subtitle">
|
||||
Découvrez qui je suis et ce qui me passionne
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="about-content">
|
||||
<motion.div
|
||||
className="about-text"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<motion.div className="about-card" variants={itemVariants}>
|
||||
<h3>Mon parcours</h3>
|
||||
<p>
|
||||
Actuellement en 3ème année de <strong>Technologies de l'Informatique</strong> à la
|
||||
HELHa de Tournai, je me passionne pour le développement d'applications et les
|
||||
nouvelles technologies. Mon parcours m'a permis d'acquérir une solide base
|
||||
technique et une approche méthodique du développement.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div className="about-card" variants={itemVariants}>
|
||||
<h3>Ma passion</h3>
|
||||
<p>
|
||||
Ce qui m'anime le plus, c'est la création d'solutions innovantes qui résolvent
|
||||
des problèmes réels. J'aime particulièrement le développement mobile avec
|
||||
<strong> Flutter</strong> et le développement web moderne avec
|
||||
<strong> React</strong> et <strong> TypeScript</strong>.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div className="about-card" variants={itemVariants}>
|
||||
<h3>Mes objectifs</h3>
|
||||
<p>
|
||||
Je cherche constamment à améliorer mes compétences et à rester à jour avec
|
||||
les dernières tendances technologiques. Mon objectif est de devenir un
|
||||
développeur full-stack polyvalent et de contribuer à des projets qui ont
|
||||
un impact positif.
|
||||
</p>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="about-stats"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{stats.map((stat, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="stat-card"
|
||||
variants={itemVariants}
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
y: -5,
|
||||
transition: { duration: 0.3 }
|
||||
}}
|
||||
>
|
||||
<div className="stat-icon">
|
||||
{stat.icon}
|
||||
</div>
|
||||
<div className="stat-value">{stat.value}</div>
|
||||
<div className="stat-label">{stat.label}</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="about-highlight"
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.5 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<div className="highlight-content">
|
||||
<h3>En quelques mots</h3>
|
||||
<p>
|
||||
"La technologie n'est rien. Ce qui est important, c'est d'avoir la foi en les gens,
|
||||
qu'ils soient fondamentalement bons et intelligents, et si vous leur donnez des outils,
|
||||
ils feront des choses merveilleuses avec."
|
||||
</p>
|
||||
<cite>- Steve Jobs</cite>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
341
src/components/Contact.tsx
Normal file
341
src/components/Contact.tsx
Normal file
@@ -0,0 +1,341 @@
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Mail, Phone, MapPin, Send, Github, Linkedin, MessageCircle, CheckCircle } from 'lucide-react';
|
||||
|
||||
const Contact = () => {
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
subject: '',
|
||||
message: ''
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Simulation d'envoi (remplacez par votre logique d'envoi réelle)
|
||||
setTimeout(() => {
|
||||
setIsSubmitting(false);
|
||||
setIsSubmitted(true);
|
||||
setFormData({ name: '', email: '', subject: '', message: '' });
|
||||
|
||||
// Reset du message de succès après 5 secondes
|
||||
setTimeout(() => {
|
||||
setIsSubmitted(false);
|
||||
}, 5000);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const contactInfo = [
|
||||
{
|
||||
icon: <Mail size={24} />,
|
||||
title: "Email",
|
||||
content: "dayron.vanleemput@example.com", // Remplacez par votre email
|
||||
link: "mailto:dayron.vanleemput@example.com",
|
||||
color: "#EA4335"
|
||||
},
|
||||
{
|
||||
icon: <Phone size={24} />,
|
||||
title: "Téléphone",
|
||||
content: "+32 XXX XX XX XX", // Remplacez par votre numéro
|
||||
link: "tel:+32XXXXXXXXX",
|
||||
color: "#34A853"
|
||||
},
|
||||
{
|
||||
icon: <MapPin size={24} />,
|
||||
title: "Localisation",
|
||||
content: "Tournai, Belgique",
|
||||
link: "https://maps.google.com/?q=Tournai,Belgium",
|
||||
color: "#4285F4"
|
||||
}
|
||||
];
|
||||
|
||||
const socialLinks = [
|
||||
{
|
||||
icon: <Github size={24} />,
|
||||
name: "GitHub",
|
||||
url: "https://github.com/dayronvanleemput", // Remplacez par votre profil
|
||||
color: "#333"
|
||||
},
|
||||
{
|
||||
icon: <Linkedin size={24} />,
|
||||
name: "LinkedIn",
|
||||
url: "https://linkedin.com/in/dayronvanleemput", // Remplacez par votre profil
|
||||
color: "#0077B5"
|
||||
}
|
||||
];
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
delayChildren: 0.3
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 50 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.25, 0.1, 0.25, 1] as const
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="contact" className="contact">
|
||||
<div className="container">
|
||||
<motion.div
|
||||
className="section-header"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h2 className="section-title">Contactez-moi</h2>
|
||||
<p className="section-subtitle">
|
||||
Une question, un projet ou simplement envie d'échanger ? N'hésitez pas à me contacter !
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="contact-content"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{/* Informations de contact */}
|
||||
<motion.div className="contact-info" variants={itemVariants}>
|
||||
<div className="contact-intro">
|
||||
<h3>
|
||||
<MessageCircle size={24} />
|
||||
Restons en contact
|
||||
</h3>
|
||||
<p>
|
||||
Je suis toujours intéressé par de nouveaux projets, des collaborations
|
||||
ou simplement des discussions autour de la technologie. N'hésitez pas
|
||||
à me contacter !
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="contact-methods">
|
||||
{contactInfo.map((contact, index) => (
|
||||
<motion.a
|
||||
key={index}
|
||||
href={contact.link}
|
||||
className="contact-method"
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
x: 10,
|
||||
transition: { duration: 0.2 }
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<div
|
||||
className="contact-icon"
|
||||
style={{ backgroundColor: `${contact.color}20`, color: contact.color }}
|
||||
>
|
||||
{contact.icon}
|
||||
</div>
|
||||
<div className="contact-details">
|
||||
<h4>{contact.title}</h4>
|
||||
<span>{contact.content}</span>
|
||||
</div>
|
||||
</motion.a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="social-links">
|
||||
<h4>Retrouvez-moi aussi sur :</h4>
|
||||
<div className="social-grid">
|
||||
{socialLinks.map((social, index) => (
|
||||
<motion.a
|
||||
key={index}
|
||||
href={social.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="social-link"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
whileHover={{
|
||||
scale: 1.1,
|
||||
rotate: 5,
|
||||
transition: { duration: 0.2 }
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<div
|
||||
className="social-icon"
|
||||
style={{ backgroundColor: `${social.color}20`, color: social.color }}
|
||||
>
|
||||
{social.icon}
|
||||
</div>
|
||||
<span>{social.name}</span>
|
||||
</motion.a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Formulaire de contact */}
|
||||
<motion.div className="contact-form-container" variants={itemVariants}>
|
||||
<h3>Envoyez-moi un message</h3>
|
||||
|
||||
{isSubmitted && (
|
||||
<motion.div
|
||||
className="success-message"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.8 }}
|
||||
>
|
||||
<CheckCircle size={20} />
|
||||
Message envoyé avec succès ! Je vous répondrai bientôt.
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="contact-form">
|
||||
<div className="form-row">
|
||||
<motion.div
|
||||
className="form-group"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<label htmlFor="name">Nom complet</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Votre nom"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="form-group"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="votre.email@example.com"
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="form-group"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.3 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<label htmlFor="subject">Sujet</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
value={formData.subject}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Objet de votre message"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="form-group"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.4 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<label htmlFor="message">Message</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
required
|
||||
rows={6}
|
||||
placeholder="Votre message..."
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.button
|
||||
type="submit"
|
||||
className={`submit-btn ${isSubmitting ? 'submitting' : ''}`}
|
||||
disabled={isSubmitting}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.5 }}
|
||||
whileHover={!isSubmitting ? { scale: 1.05 } : {}}
|
||||
whileTap={!isSubmitting ? { scale: 0.95 } : {}}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<div className="loading-spinner" />
|
||||
Envoi en cours...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send size={20} />
|
||||
Envoyer le message
|
||||
</>
|
||||
)}
|
||||
</motion.button>
|
||||
</form>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Footer */}
|
||||
<motion.footer
|
||||
className="contact-footer"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.5 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<p>
|
||||
© 2025 Dayron Van Leemput. Développé avec ❤️ en React et TypeScript.
|
||||
</p>
|
||||
</motion.footer>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
270
src/components/Education.tsx
Normal file
270
src/components/Education.tsx
Normal file
@@ -0,0 +1,270 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { GraduationCap, Calendar, MapPin, Award, BookOpen, Target } from 'lucide-react';
|
||||
|
||||
const Education = () => {
|
||||
const education = [
|
||||
{
|
||||
id: 1,
|
||||
degree: "Bachelier en Technologies de l'Informatique",
|
||||
school: "HELHa - Haute École Louvain en Hainaut",
|
||||
location: "Tournai, Belgique",
|
||||
period: "2023 - 2026",
|
||||
currentYear: "3ème année",
|
||||
status: "En cours",
|
||||
description: "Formation complète en développement logiciel, programmation, bases de données, réseaux et gestion de projets informatiques.",
|
||||
highlights: [
|
||||
"Programmation orientée objet (Java, C#)",
|
||||
"Développement web (HTML, CSS, JavaScript, React)",
|
||||
"Développement mobile (Flutter, Dart)",
|
||||
"Bases de données et SQL",
|
||||
"Gestion de projets",
|
||||
"Réseaux et systèmes"
|
||||
],
|
||||
color: "#4CAF50",
|
||||
icon: <GraduationCap size={24} />
|
||||
}
|
||||
];
|
||||
|
||||
const certifications = [
|
||||
{
|
||||
title: "Développement Mobile Flutter",
|
||||
provider: "Formation autodidacte",
|
||||
date: "2024",
|
||||
skills: ["Dart", "Flutter", "Firebase", "API REST"],
|
||||
color: "#2196F3"
|
||||
},
|
||||
{
|
||||
title: "React & TypeScript",
|
||||
provider: "Projets personnels",
|
||||
date: "2024",
|
||||
skills: ["React", "TypeScript", "Hooks", "Context API"],
|
||||
color: "#FF9800"
|
||||
}
|
||||
];
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
delayChildren: 0.3
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, x: -50 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.25, 0.1, 0.25, 1] as const
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="education" className="education">
|
||||
<div className="container">
|
||||
<motion.div
|
||||
className="section-header"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h2 className="section-title">Formation & Apprentissage</h2>
|
||||
<p className="section-subtitle">
|
||||
Mon parcours académique et mes apprentissages continus
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="education-content">
|
||||
{/* Formation principale */}
|
||||
<motion.div
|
||||
className="education-main"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{education.map((edu) => (
|
||||
<motion.div
|
||||
key={edu.id}
|
||||
className="education-card main-education"
|
||||
variants={itemVariants}
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
y: -5,
|
||||
transition: { duration: 0.3 }
|
||||
}}
|
||||
>
|
||||
<div className="education-timeline">
|
||||
<div
|
||||
className="timeline-dot"
|
||||
style={{ backgroundColor: edu.color }}
|
||||
>
|
||||
{edu.icon}
|
||||
</div>
|
||||
<div className="timeline-line"></div>
|
||||
</div>
|
||||
|
||||
<div className="education-content-card">
|
||||
<div className="education-header">
|
||||
<div className="education-title">
|
||||
<h3>{edu.degree}</h3>
|
||||
<div className="education-meta">
|
||||
<span className="school-name">
|
||||
<BookOpen size={16} />
|
||||
{edu.school}
|
||||
</span>
|
||||
<span className="location">
|
||||
<MapPin size={16} />
|
||||
{edu.location}
|
||||
</span>
|
||||
<span className="period">
|
||||
<Calendar size={16} />
|
||||
{edu.period}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="education-status">
|
||||
<span
|
||||
className="status-badge"
|
||||
style={{ backgroundColor: `${edu.color}20`, color: edu.color }}
|
||||
>
|
||||
{edu.status}
|
||||
</span>
|
||||
<span className="current-year">{edu.currentYear}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="education-description">{edu.description}</p>
|
||||
|
||||
<div className="education-highlights">
|
||||
<h4>
|
||||
<Target size={16} />
|
||||
Matières principales
|
||||
</h4>
|
||||
<div className="highlights-grid">
|
||||
{edu.highlights.map((highlight, index) => (
|
||||
<motion.span
|
||||
key={index}
|
||||
className="highlight-tag"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{highlight}
|
||||
</motion.span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Certifications et formations complémentaires */}
|
||||
<motion.div
|
||||
className="certifications"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h3 className="certifications-title">
|
||||
<Award size={24} />
|
||||
Formations complémentaires & Autodidacte
|
||||
</h3>
|
||||
<div className="certifications-grid">
|
||||
{certifications.map((cert, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="certification-card"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
whileHover={{
|
||||
scale: 1.03,
|
||||
y: -3,
|
||||
transition: { duration: 0.2 }
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<div className="cert-header">
|
||||
<h4>{cert.title}</h4>
|
||||
<span className="cert-provider">{cert.provider}</span>
|
||||
<span className="cert-date">{cert.date}</span>
|
||||
</div>
|
||||
<div className="cert-skills">
|
||||
{cert.skills.map((skill, skillIndex) => (
|
||||
<span
|
||||
key={skillIndex}
|
||||
className="cert-skill-tag"
|
||||
style={{ backgroundColor: `${cert.color}20`, color: cert.color }}
|
||||
>
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Objectifs d'apprentissage */}
|
||||
<motion.div
|
||||
className="learning-goals"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h3>Objectifs d'apprentissage 2025</h3>
|
||||
<div className="goals-grid">
|
||||
{[
|
||||
{ goal: "Maîtriser Firebase et les services cloud", progress: 60 },
|
||||
{ goal: "Approfondir Spring Boot pour le backend", progress: 30 },
|
||||
{ goal: "Apprendre Docker et les conteneurs", progress: 20 },
|
||||
{ goal: "Développer mes compétences en UI/UX", progress: 45 }
|
||||
].map((item, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="goal-item"
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<div className="goal-header">
|
||||
<span className="goal-text">{item.goal}</span>
|
||||
<span className="goal-percentage">{item.progress}%</span>
|
||||
</div>
|
||||
<div className="goal-progress">
|
||||
<motion.div
|
||||
className="goal-progress-bar"
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{ width: `${item.progress}%` }}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
delay: index * 0.1 + 0.5,
|
||||
ease: [0.25, 0.1, 0.25, 1] as const
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Education;
|
||||
120
src/components/Header.tsx
Normal file
120
src/components/Header.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Menu, X, Sun, Moon } from 'lucide-react';
|
||||
|
||||
interface HeaderProps {
|
||||
darkMode: boolean;
|
||||
toggleDarkMode: () => void;
|
||||
}
|
||||
|
||||
const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
const menuItems = [
|
||||
{ name: 'Accueil', href: '#hero' },
|
||||
{ name: 'À propos', href: '#about' },
|
||||
{ name: 'Compétences', href: '#skills' },
|
||||
{ name: 'Projets', href: '#projects' },
|
||||
{ name: 'Formation', href: '#education' },
|
||||
{ name: 'Contact', href: '#contact' }
|
||||
];
|
||||
|
||||
const scrollToSection = (href: string) => {
|
||||
const element = document.querySelector(href);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
setIsMenuOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.header
|
||||
initial={{ y: -100 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="header"
|
||||
>
|
||||
<nav className="nav">
|
||||
<motion.div
|
||||
className="nav-brand"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<a href="#hero" onClick={(e) => { e.preventDefault(); scrollToSection('#hero'); }}>
|
||||
Dayron Van Leemput
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
{/* Navigation desktop */}
|
||||
<ul className="nav-menu desktop-menu">
|
||||
{menuItems.map((item, index) => (
|
||||
<motion.li
|
||||
key={item.name}
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<a
|
||||
href={item.href}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
scrollToSection(item.href);
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
</motion.li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="nav-controls">
|
||||
{/* Toggle thème */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={toggleDarkMode}
|
||||
className="theme-toggle"
|
||||
aria-label="Changer de thème"
|
||||
>
|
||||
{darkMode ? <Sun size={20} /> : <Moon size={20} />}
|
||||
</motion.button>
|
||||
|
||||
{/* Menu hamburger mobile */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
className="menu-toggle"
|
||||
aria-label="Menu"
|
||||
>
|
||||
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
{/* Navigation mobile */}
|
||||
<motion.ul
|
||||
className={`nav-menu mobile-menu ${isMenuOpen ? 'open' : ''}`}
|
||||
initial={false}
|
||||
animate={isMenuOpen ? { opacity: 1, x: 0 } : { opacity: 0, x: '100%' }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
{menuItems.map((item) => (
|
||||
<li key={item.name}>
|
||||
<a
|
||||
href={item.href}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
scrollToSection(item.href);
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</motion.ul>
|
||||
</nav>
|
||||
</motion.header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
159
src/components/Hero.tsx
Normal file
159
src/components/Hero.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Download, Github, Linkedin, Mail } from 'lucide-react';
|
||||
|
||||
const Hero = () => {
|
||||
const handleDownloadCV = () => {
|
||||
// Ici, vous pouvez ajouter le lien vers votre CV
|
||||
const link = document.createElement('a');
|
||||
link.href = '/cv-dayron-van-leemput.pdf'; // Ajoutez votre CV dans le dossier public
|
||||
link.download = 'CV-Dayron-Van-Leemput.pdf';
|
||||
link.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="hero" className="hero">
|
||||
<div className="hero-content">
|
||||
<motion.div
|
||||
className="hero-text"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
>
|
||||
<motion.h1
|
||||
className="hero-title"
|
||||
initial={{ opacity: 0, x: -50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
>
|
||||
Dayron Van Leemput
|
||||
</motion.h1>
|
||||
|
||||
<motion.h2
|
||||
className="hero-subtitle"
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.6 }}
|
||||
>
|
||||
Étudiant en Technologies de l'Informatique
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
className="hero-description"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.8 }}
|
||||
>
|
||||
Bac 3 à la HELHa de Tournai | Jeune développeur passionné par les nouvelles technologies
|
||||
et le développement d'applications innovantes
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
className="hero-buttons"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 1 }}
|
||||
>
|
||||
<motion.button
|
||||
onClick={handleDownloadCV}
|
||||
className="btn btn-primary"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Download size={20} />
|
||||
Télécharger mon CV
|
||||
</motion.button>
|
||||
|
||||
<motion.a
|
||||
href="#contact"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
document.querySelector('#contact')?.scrollIntoView({ behavior: 'smooth' });
|
||||
}}
|
||||
className="btn btn-secondary"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Mail size={20} />
|
||||
Me contacter
|
||||
</motion.a>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="hero-social"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 1.2 }}
|
||||
>
|
||||
<motion.a
|
||||
href="https://github.com/dayronvanleemput" // Remplacez par votre profil GitHub
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="social-link"
|
||||
whileHover={{ scale: 1.2, rotate: 5 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
>
|
||||
<Github size={24} />
|
||||
</motion.a>
|
||||
|
||||
<motion.a
|
||||
href="https://linkedin.com/in/dayronvanleemput" // Remplacez par votre profil LinkedIn
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="social-link"
|
||||
whileHover={{ scale: 1.2, rotate: -5 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
>
|
||||
<Linkedin size={24} />
|
||||
</motion.a>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="hero-image"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 1, delay: 0.5 }}
|
||||
>
|
||||
<motion.div
|
||||
className="hero-avatar"
|
||||
whileHover={{ scale: 1.05, rotate: 5 }}
|
||||
transition={{ type: "spring", stiffness: 300, damping: 10 }}
|
||||
>
|
||||
{/* Vous pouvez remplacer ceci par votre photo */}
|
||||
<div className="avatar-placeholder">
|
||||
<span>DV</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Particules d'animation en arrière-plan */}
|
||||
<div className="hero-particles">
|
||||
{[...Array(20)].map((_, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="particle"
|
||||
initial={{
|
||||
opacity: 0,
|
||||
scale: 0,
|
||||
x: Math.random() * window.innerWidth,
|
||||
y: Math.random() * window.innerHeight,
|
||||
}}
|
||||
animate={{
|
||||
opacity: [0, 1, 0],
|
||||
scale: [0, 1, 0],
|
||||
y: [null, -100],
|
||||
}}
|
||||
transition={{
|
||||
duration: Math.random() * 3 + 2,
|
||||
repeat: Infinity,
|
||||
delay: Math.random() * 2,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Hero;
|
||||
250
src/components/Projects.tsx
Normal file
250
src/components/Projects.tsx
Normal file
@@ -0,0 +1,250 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { ExternalLink, Github, Calendar, MapPin } from 'lucide-react';
|
||||
|
||||
const Projects = () => {
|
||||
const projects = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Travel Mate",
|
||||
description: "Application mobile conçue pour simplifier l'organisation de voyages de groupe. Elle permet de centraliser toutes les informations importantes d'un voyage : planification, gestion des dépenses, découverte d'activités et coordination entre les participants.",
|
||||
status: "Bientôt disponible sur App Store et Play Store",
|
||||
technologies: ["Dart", "Flutter", "Firebase"],
|
||||
features: [
|
||||
"Planification de voyage collaborative",
|
||||
"Gestion des dépenses partagées",
|
||||
"Découverte d'activités locales",
|
||||
"Coordination en temps réel"
|
||||
],
|
||||
color: "#4CAF50",
|
||||
icon: <MapPin size={24} />,
|
||||
links: {
|
||||
github: "#", // Remplacez par votre lien GitHub
|
||||
demo: "#"
|
||||
},
|
||||
image: "/travel-mate-preview.png" // Ajoutez votre image dans le dossier public
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Portfolio Web",
|
||||
description: "Site web personnel moderne et responsive développé avec React et TypeScript. Inclut des animations fluides, un mode sombre/clair et une architecture modulaire.",
|
||||
status: "Projet actuel",
|
||||
technologies: ["React", "TypeScript", "Framer Motion", "CSS3"],
|
||||
features: [
|
||||
"Design responsive",
|
||||
"Animations fluides",
|
||||
"Mode sombre/clair",
|
||||
"Performance optimisée"
|
||||
],
|
||||
color: "#2196F3",
|
||||
icon: <ExternalLink size={24} />,
|
||||
links: {
|
||||
github: "https://github.com/dayronvanleemput/portfolio", // Remplacez par votre lien
|
||||
demo: "https://dayronvanleemput.dev" // Remplacez par votre lien
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.3,
|
||||
delayChildren: 0.2
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const projectVariants = {
|
||||
hidden: { opacity: 0, y: 50 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.8,
|
||||
ease: [0.25, 0.1, 0.25, 1] as const
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="projects" className="projects">
|
||||
<div className="container">
|
||||
<motion.div
|
||||
className="section-header"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h2 className="section-title">Mes Projets</h2>
|
||||
<p className="section-subtitle">
|
||||
Découvrez les projets sur lesquels j'ai travaillé et qui me tiennent à cœur
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="projects-grid"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{projects.map((project, index) => (
|
||||
<motion.div
|
||||
key={project.id}
|
||||
className="project-card"
|
||||
variants={projectVariants}
|
||||
whileHover={{
|
||||
y: -10,
|
||||
scale: 1.02,
|
||||
transition: { duration: 0.3 }
|
||||
}}
|
||||
>
|
||||
<div className="project-header">
|
||||
<div
|
||||
className="project-icon"
|
||||
style={{ backgroundColor: `${project.color}20`, color: project.color }}
|
||||
>
|
||||
{project.icon}
|
||||
</div>
|
||||
<div className="project-status">
|
||||
<span className="status-badge" style={{ backgroundColor: `${project.color}20`, color: project.color }}>
|
||||
{project.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="project-content">
|
||||
<h3 className="project-title">{project.title}</h3>
|
||||
<p className="project-description">{project.description}</p>
|
||||
|
||||
<div className="project-technologies">
|
||||
{project.technologies.map((tech, techIndex) => (
|
||||
<motion.span
|
||||
key={tech}
|
||||
className="tech-tag"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
delay: (index * 0.1) + (techIndex * 0.05)
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{tech}
|
||||
</motion.span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="project-features">
|
||||
<h4>Fonctionnalités principales :</h4>
|
||||
<ul>
|
||||
{project.features.map((feature, featureIndex) => (
|
||||
<motion.li
|
||||
key={featureIndex}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: (index * 0.2) + (featureIndex * 0.1)
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{feature}
|
||||
</motion.li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="project-footer">
|
||||
<div className="project-links">
|
||||
{project.links.github !== "#" && (
|
||||
<motion.a
|
||||
href={project.links.github}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="project-link"
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Github size={20} />
|
||||
Code
|
||||
</motion.a>
|
||||
)}
|
||||
{project.links.demo !== "#" && (
|
||||
<motion.a
|
||||
href={project.links.demo}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="project-link primary"
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<ExternalLink size={20} />
|
||||
Voir le projet
|
||||
</motion.a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Section des projets futurs */}
|
||||
<motion.div
|
||||
className="future-projects"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h3 className="future-title">Projets à venir</h3>
|
||||
<div className="future-projects-grid">
|
||||
{[
|
||||
{
|
||||
title: "Application de gestion de tâches",
|
||||
description: "Une app de productivité avec synchronisation cloud",
|
||||
tech: "Flutter • Firebase • Bloc"
|
||||
},
|
||||
{
|
||||
title: "API REST e-commerce",
|
||||
description: "Backend complet pour application e-commerce",
|
||||
tech: "Java • Spring Boot • PostgreSQL"
|
||||
},
|
||||
{
|
||||
title: "Dashboard analytique",
|
||||
description: "Interface web pour visualiser des données",
|
||||
tech: "React • D3.js • TypeScript"
|
||||
}
|
||||
].map((futureProject, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="future-project-card"
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
y: -2,
|
||||
transition: { duration: 0.2 }
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<div className="future-project-icon">
|
||||
<Calendar size={20} />
|
||||
</div>
|
||||
<h4>{futureProject.title}</h4>
|
||||
<p>{futureProject.description}</p>
|
||||
<span className="future-tech">{futureProject.tech}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Projects;
|
||||
191
src/components/Skills.tsx
Normal file
191
src/components/Skills.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Code, Database, Smartphone, Globe, Server, Wrench } from 'lucide-react';
|
||||
|
||||
const Skills = () => {
|
||||
const skillCategories = [
|
||||
{
|
||||
icon: <Smartphone size={32} />,
|
||||
title: "Mobile",
|
||||
color: "#4FC3F7",
|
||||
skills: [
|
||||
{ name: "Dart", level: 85, color: "#0175C2" },
|
||||
{ name: "Flutter", level: 80, color: "#02569B" }
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: <Globe size={32} />,
|
||||
title: "Frontend",
|
||||
color: "#42A5F5",
|
||||
skills: [
|
||||
{ name: "React", level: 75, color: "#61DAFB" },
|
||||
{ name: "TypeScript", level: 70, color: "#3178C6" },
|
||||
{ name: "JavaScript", level: 80, color: "#F7DF1E" }
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: <Server size={32} />,
|
||||
title: "Backend",
|
||||
color: "#66BB6A",
|
||||
skills: [
|
||||
{ name: "Java", level: 75, color: "#ED8B00" },
|
||||
{ name: "C#", level: 65, color: "#239120" }
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: <Database size={32} />,
|
||||
title: "Outils & Autres",
|
||||
color: "#AB47BC",
|
||||
skills: [
|
||||
{ name: "Git", level: 70, color: "#F05032" },
|
||||
{ name: "VS Code", level: 90, color: "#007ACC" },
|
||||
{ name: "Android Studio", level: 75, color: "#3DDC84" }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
delayChildren: 0.3
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const categoryVariants = {
|
||||
hidden: { opacity: 0, y: 50 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.25, 0.1, 0.25, 1] as const
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="skills" className="skills">
|
||||
<div className="container">
|
||||
<motion.div
|
||||
className="section-header"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h2 className="section-title">Compétences & Technologies</h2>
|
||||
<p className="section-subtitle">
|
||||
Les technologies que je maîtrise et avec lesquelles j'aime travailler
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="skills-grid"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{skillCategories.map((category, categoryIndex) => (
|
||||
<motion.div
|
||||
key={category.title}
|
||||
className="skill-category"
|
||||
variants={categoryVariants}
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
y: -5,
|
||||
transition: { duration: 0.3 }
|
||||
}}
|
||||
>
|
||||
<div className="category-header">
|
||||
<div
|
||||
className="category-icon"
|
||||
style={{ backgroundColor: `${category.color}20`, color: category.color }}
|
||||
>
|
||||
{category.icon}
|
||||
</div>
|
||||
<h3 className="category-title">{category.title}</h3>
|
||||
</div>
|
||||
|
||||
<div className="skills-list">
|
||||
{category.skills.map((skill, skillIndex) => (
|
||||
<motion.div
|
||||
key={skill.name}
|
||||
className="skill-item"
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: (categoryIndex * 0.2) + (skillIndex * 0.1)
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<div className="skill-header">
|
||||
<span className="skill-name">{skill.name}</span>
|
||||
<span className="skill-percentage">{skill.level}%</span>
|
||||
</div>
|
||||
|
||||
<div className="skill-bar">
|
||||
<motion.div
|
||||
className="skill-progress"
|
||||
style={{ backgroundColor: skill.color }}
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{ width: `${skill.level}%` }}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
delay: (categoryIndex * 0.2) + (skillIndex * 0.1) + 0.5,
|
||||
ease: [0.25, 0.1, 0.25, 1] as const
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Section des soft skills */}
|
||||
<motion.div
|
||||
className="soft-skills"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<h3 className="soft-skills-title">Autres compétences</h3>
|
||||
<div className="soft-skills-grid">
|
||||
{[
|
||||
{ name: "Résolution de problèmes", icon: <Wrench size={20} /> },
|
||||
{ name: "Travail en équipe", icon: <Code size={20} /> },
|
||||
{ name: "Apprentissage continu", icon: <Database size={20} /> },
|
||||
{ name: "Communication", icon: <Globe size={20} /> }
|
||||
].map((softSkill, index) => (
|
||||
<motion.div
|
||||
key={softSkill.name}
|
||||
className="soft-skill-tag"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
transition: { duration: 0.2 }
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{softSkill.icon}
|
||||
<span>{softSkill.name}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Skills;
|
||||
Reference in New Issue
Block a user