Enhances UI with i18n, theme persistence and navigation

Adds internationalization (i18n) support using i18next, enabling multi-language support.

Persists theme selection in local storage, providing a consistent user experience across sessions.

Implements smooth scrolling navigation to different sections of the page.

Introduces a "Projects" section using a routing system, with tabs for professional and personal endeavors and detailed views using framer-motion for smooth transitions.

Includes updated resume PDFs.
This commit is contained in:
Louis
2025-10-28 14:21:02 +01:00
parent 5eb0beafb3
commit 3e3a6dd125
27 changed files with 468 additions and 358 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Louis Emard</title>
</head>
<body>
<div id="root"></div>

Binary file not shown.

Binary file not shown.

View File

@@ -17,8 +17,8 @@ const About = () => {
<p className="text-secondary/70">{t('about.experience_desc')}</p>
</div>
<div className="bg-secondary/5 p-6 rounded-lg border border-secondary/10">
<h3 className="text-xl font-semibold mb-3 text-accent">{t('about.passion_title')}</h3>
<p className="text-secondary/70">{t('about.passion_desc')}</p>
<h3 className="text-xl font-semibold mb-3 text-accent">{t('about.approach_title')}</h3>
<p className="text-secondary/70">{t('about.approach_desc')}</p>
</div>
</div>
</div>

View File

@@ -10,8 +10,22 @@ import { ThemeMenu, ThemeType, ThemeSelected } from './ThemeMenu';
function App() {
const { t, i18n } = useTranslation();
const [theme, setTheme] = useState<ThemeType>('dark');
const [themeSelected, setThemeSelected] = useState<ThemeSelected>('system');
const [theme, setTheme] = useState<ThemeType>(() => {
const savedTheme = localStorage.getItem('theme');
return (savedTheme as ThemeType) || 'dark';
});
const [themeSelected, setThemeSelected] = useState<ThemeSelected>(() => {
const savedThemeSelected = localStorage.getItem('themeSelected');
return (savedThemeSelected as ThemeSelected) || 'system';
});
// Charger la langue sauvegardée au démarrage
useEffect(() => {
const savedLanguage = localStorage.getItem('language');
if (savedLanguage && (savedLanguage === 'fr' || savedLanguage === 'en')) {
i18n.changeLanguage(savedLanguage);
}
}, [i18n]);
useEffect(() => {
if (themeSelected === 'system') {
@@ -30,32 +44,51 @@ function App() {
setTheme(themeSelected);
document.documentElement.className = `theme-${themeSelected}`;
}
}, [themeSelected]);
// Sauvegarder le thème sélectionné
localStorage.setItem('themeSelected', themeSelected);
localStorage.setItem('theme', theme);
}, [themeSelected, theme]);
const toggleLanguage = () => {
const newLang = i18n.language === 'fr' ? 'en' : 'fr';
i18n.changeLanguage(newLang);
localStorage.setItem('language', newLang);
};
const handleThemeChange = (newTheme: ThemeSelected) => {
setThemeSelected(newTheme);
};
const scrollToSection = (id: string) => {
const section = document.getElementById(id);
if (section) {
section.scrollIntoView({ behavior: 'smooth' });
}
}
return (
<div className="min-h-screen text-secondary bg-primary transition-colors duration-300">
<div className="min-h-screen text-secondary bg-primary/80 transition-colors duration-100">
{/* Background Blobs */}
<div className="background-blobs">
<div className="blob blob-1"></div>
<div className="blob blob-2"></div>
<div className="blob blob-3"></div>
</div>
{/* Header */}
<header className='fixed top-0 left-0 right-0 z-50 flex justify-between items-center gap-4 text-sm w-full px-8 py-6 bg-primary/80 backdrop-blur-md border-b border-secondary/10'>
<h1 className='text-2xl font-bold'>
<span className='text-accent'>L</span>ouis <span className='text-accent'>E</span>MARD
Louis EMARD
</h1>
<nav className='flex gap-6 h-full justify-center items-center'>
<a href='#about' className='hover:text-accent transition-colors'>{t('nav.about')}</a>
<a href='#experience' className='hover:text-accent transition-colors'>{t('nav.experience')}</a>
<a href='#skills' className='hover:text-accent transition-colors'>{t('nav.skills')}</a>
<a href='#education' className='hover:text-accent transition-colors'>{t('nav.education')}</a>
<a href='#contact' className='hover:text-accent transition-colors'>{t('nav.contact')}</a>
<a onClick={() => scrollToSection('about')} className='hover:text-accent transition-colors cursor-pointer'>{t('nav.about')}</a>
<a onClick={() => scrollToSection('experience')} className='hover:text-accent transition-colors cursor-pointer'>{t('nav.projects')}</a>
<a onClick={() => scrollToSection('skills')} className='hover:text-accent transition-colors cursor-pointer'>{t('nav.skills')}</a>
<a onClick={() => scrollToSection('education')} className='hover:text-accent transition-colors cursor-pointer'>{t('nav.education')}</a>
<a onClick={() => scrollToSection('contact')} className='hover:text-accent transition-colors cursor-pointer'>{t('nav.contact')}</a>
{/* Language Toggle */}
<button
onClick={toggleLanguage}
@@ -74,7 +107,7 @@ function App() {
</header>
{/* Main Content */}
<main>
<main className='bg-'>
<Hero />
<About />
<Experience />

View File

@@ -1,25 +1,32 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Routes, Route, useRoutes, Link, useParams, useNavigate } from 'react-router';
import { AnimatePresence, motion } from 'framer-motion';
type ProjectType = 'professional' | 'personal';
const Experience = () => {
const { t } = useTranslation();
const experiences = [
const [activeTab, setActiveTab] = useState<ProjectType>('professional');
const professionalExperiences = [
{
period: '2023 - 2024',
period: '2024',
title: 'experience.job1.title',
company: 'experience.job1.company',
description: 'experience.job1.description',
tasks: ['experience.job1.task1', 'experience.job1.task2', 'experience.job1.task3']
},
{
period: '2022',
period: '2024',
title: 'experience.job2.title',
company: 'experience.job2.company',
description: 'experience.job2.description',
tasks: ['experience.job2.task1', 'experience.job2.task2']
},
}
];
const personalExperiences = [
{
period: '2018 - 2025',
title: 'experience.job3.title',
@@ -29,17 +36,41 @@ const Experience = () => {
}
];
const currentExperiences = activeTab === 'professional' ? professionalExperiences : personalExperiences;
const routes = useRoutes(['/', '/experiences/:xpId'].map((path, id) => ({
path,
element: (
<>
<Link to={"/"}>
<p>Exit</p>
</Link>
<div className="max-w-4xl mx-auto">
<h2 className="text-4xl font-bold mb-12 text-accent">{t('experience.title')}</h2>
<h2 className="text-4xl font-bold mb-8 text-accent" id="experience">{t('experience.title')}</h2>
{/* Tabs */}
<div className="flex gap-4 mb-8 border-b border-secondary/20">
<button
onClick={() => setActiveTab('professional')}
className={`px-6 py-3 font-medium transition-all ${
activeTab === 'professional'
? 'text-accent border-b-2 border-accent'
: 'text-secondary/60 hover:text-secondary'
}`}
>
{t('experience.tabs.professional')}
</button>
<button
onClick={() => setActiveTab('personal')}
className={`px-6 py-3 font-medium transition-all ${
activeTab === 'personal'
? 'text-accent border-b-2 border-accent'
: 'text-secondary/60 hover:text-secondary'
}`}
>
{t('experience.tabs.personal')}
</button>
</div>
<div className="space-y-8">
<ExperiencesList experiences={experiences} t={t} />
<ExperiencesList experiences={currentExperiences} t={t} />
</div>
</div>
</>
@@ -70,7 +101,7 @@ const ExperiencesList = ({ experiences, t }: ExperiencesListProps) => {
const { xpId } = useParams();
return <div className=' flex flex-col space-y-8'>
{experiences.map((exp, index) => <ExperienceSummary key={index} index={index} exp={exp} t={t} />)}
<AnimatePresence>
<AnimatePresence mode="wait">
{xpId !== undefined && <DetailedExperience t={t} />}
</AnimatePresence>
</div>
@@ -91,20 +122,55 @@ interface ExperienceSummaryProps {
const ExperienceSummary = ({ index, exp, t }: ExperienceSummaryProps) => {
return <Link to={`/experiences/${index}`}>
<motion.div layoutId={`experience-${index}`} key={index} className="bg-primary p-6 rounded-lg border border-secondary/10 hover:border-accent/50 transition-all">
<motion.div
layoutId={`experience-${index}`}
key={index}
className="bg-primary p-6 rounded-lg border border-secondary/10 hover:border-accent/50 transition-colors cursor-pointer relative group"
transition={{ type: "spring", stiffness: 350, damping: 35 }}
>
<div className="flex flex-col md:flex-row md:justify-between md:items-start mb-4">
<motion.div layoutId={`experience-header-${index}`}>
<motion.div
layoutId={`experience-header-${index}`}
transition={{ type: "spring", stiffness: 350, damping: 35 }}
className="flex-1"
>
<h3 className="text-2xl font-semibold text-secondary mb-1">{t(exp.title)}</h3>
<p className="text-accent font-medium">{t(exp.company)}</p>
</motion.div>
<span className="text-secondary/60 text-sm md:text-base mt-2 md:mt-0">{exp.period}</span>
<div className="flex items-center gap-3 mt-2 md:mt-0">
<motion.span
layoutId={`experience-period-${index}`}
className="text-secondary/60 text-sm md:text-base"
transition={{ type: "spring", stiffness: 350, damping: 35 }}
>
{exp.period}
</motion.span>
<motion.svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5 text-accent opacity-60 group-hover:opacity-100 transition-opacity"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
whileHover={{ scale: 1.2 }}
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
</motion.svg>
</div>
</div>
<p className="text-secondary/70 mb-4">{t(exp.description)}</p>
<ul className="list-disc list-inside space-y-2 text-secondary/60">
<motion.p
initial={{ opacity: 1 }}
className="text-secondary/70 mb-4"
>
{t(exp.description)}
</motion.p>
<motion.ul
initial={{ opacity: 1 }}
className="list-disc list-inside space-y-2 text-secondary/60"
>
{exp.tasks.map((task, taskIndex) => (
<li key={taskIndex}>{t(task)}</li>
))}
</ul>
</motion.ul>
</motion.div>
</Link>
};
@@ -143,14 +209,65 @@ const DetailedExperience = ({ t }: DetailedExperienceProps) => {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className='fixed top-0 left-0 w-full h-full bg-black bg-opacity-80 flex flex-col items-center justify-center z-0 backdrop-blur-sm'
onClick={handleBackgroundClick}
>
<motion.div layoutId={`experience-${xpId}`} className="bg-primary p-8 rounded-lg border border-accent max-w-3xl w-full h-1/2" onClick={(e) => e.stopPropagation()}>
<motion.div layoutId={`experience-header-${xpId}`}>
<h3 className="text-2xl font-semibold text-secondary mb-1">Dev full stack</h3>
<motion.div
layoutId={`experience-${xpId}`}
className="bg-primary p-8 rounded-lg border border-accent/50 max-w-3xl w-full h-1/2 overflow-y-auto relative"
transition={{ type: "spring", stiffness: 350, damping: 35 }}
>
<motion.button
onClick={() => navigate('/')}
className="absolute top-4 right-4 p-2 rounded-full hover:bg-accent/10 transition-colors group"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 text-accent group-hover:text-accent/80"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</motion.button>
<div className="flex flex-col md:flex-row md:justify-between md:items-start mb-6">
<motion.div
layoutId={`experience-header-${xpId}`}
transition={{ type: "spring", stiffness: 350, damping: 35 }}
className="flex-1"
>
<h3 className="text-2xl font-semibold text-secondary mb-1">Dev full stack</h3>
<p className="text-accent font-medium">Meta Video</p>
</motion.div>
<div className="flex items-center gap-3 mt-2 md:mt-0">
<motion.span
layoutId={`experience-period-${xpId}`}
className="text-secondary/60 text-sm md:text-base"
transition={{ type: "spring", stiffness: 350, damping: 35 }}
>
2023 - 2024
</motion.span>
</div>
</div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<h4 className="text-xl font-semibold text-secondary mb-4">Détails supplémentaires</h4>
<p className="text-secondary/70 mb-4">Contenu détaillé de l'expérience...</p>
</motion.div>
</motion.div>
</motion.div>;

View File

@@ -5,9 +5,8 @@ import Moon from '../icons/Moon';
import Sun from '../icons/Sun';
import SystemIcon from '../icons/System';
export type CustomThemeColor = 'purple' | 'teal' | 'sunset' | 'forest' | 'ocean' | 'rose' | 'amber';
export type ThemeType = 'light' | 'dark' | `custom-${CustomThemeColor}`;
export type ThemeSelected = 'light' | 'dark' | `custom-${CustomThemeColor}` | 'system';
export type ThemeType = 'light' | 'dark';
export type ThemeSelected = 'light' | 'dark' | 'system';
interface ThemeMenuProps {
theme: ThemeType;
@@ -15,25 +14,9 @@ interface ThemeMenuProps {
onThemeChange: (theme: ThemeSelected) => void;
}
const customThemes: CustomThemeColor[] = ['purple', 'teal', 'sunset', 'forest', 'ocean', 'rose', 'amber'];
const getThemeEmoji = (color: CustomThemeColor): string => {
const emojis: Record<CustomThemeColor, string> = {
purple: '💜',
teal: '🩵',
sunset: '🌅',
forest: '🌲',
ocean: '🌊',
rose: '🌹',
amber: '⚡'
};
return emojis[color];
};
export const ThemeMenu = ({ theme, themeSelected, onThemeChange }: ThemeMenuProps) => {
const { t } = useTranslation();
const [showThemeMenu, setShowThemeMenu] = useState(false);
const [showCustomSubmenu, setShowCustomSubmenu] = useState(true);
const getThemeIcon = () => {
if (themeSelected === 'system') {
@@ -42,17 +25,12 @@ export const ThemeMenu = ({ theme, themeSelected, onThemeChange }: ThemeMenuProp
if (theme === 'light') {
return <Sun className='text-secondary'/>;
}
if (theme.startsWith('custom-')) {
const color = theme.split('-')[1] as CustomThemeColor;
return <span className='text-accent text-xl'>{getThemeEmoji(color)}</span>;
}
return <Moon className='text-secondary'/>;
};
const handleThemeSelect = (selectedTheme: ThemeSelected) => {
onThemeChange(selectedTheme);
setShowThemeMenu(false);
setShowCustomSubmenu(false);
};
return (
@@ -88,44 +66,6 @@ export const ThemeMenu = ({ theme, themeSelected, onThemeChange }: ThemeMenuProp
<Moon height={16} width={16} /> {t('theme.dark')}
</button>
{/* Custom Themes - with submenu */}
<div
className="relative"
>
<button
className={clsx(
"w-full px-4 py-3 text-left hover:bg-accent/10 hover:text-accent transition-colors flex items-center justify-between gap-3",
themeSelected.startsWith('custom-') && "bg-accent/20 text-accent"
)}
onClick={() => setShowCustomSubmenu(true)}
onBlur={() => setShowCustomSubmenu(false)}
>
<span className="flex items-center gap-3">
<span className='text-lg'></span> {t('theme.custom')}
</span>
<span className="text-xs"></span>
</button>
{/* Custom Submenu */}
{/* {showCustomSubmenu && ( */}
<div className=" top-0 ml-1 bg-primary border border-secondary/20 rounded-lg shadow-xl min-w-[160px] overflow-hidden">
{customThemes.map((color) => (
<button
key={color}
onClick={() => handleThemeSelect(`custom-${color}`)}
className={clsx(
"w-full px-4 py-3 text-left hover:bg-accent/10 hover:text-accent transition-colors flex items-center gap-3",
themeSelected === `custom-${color}` && "bg-accent/20 text-accent"
)}
>
<span className='text-lg'>{getThemeEmoji(color)}</span>
{t(`theme.custom_${color}`)}
</button>
))}
</div>
{/* )} */}
</div>
{/* System Theme */}
<button
onClick={() => handleThemeSelect('system')}

8
src/i18n/en/about.json Normal file
View File

@@ -0,0 +1,8 @@
{
"title": "About Me",
"description": "Im a fullstack developer specialized in React and Node.js, helping teams build clear, reliable, and scalable applications. I focus on code quality, long-term simplicity, and a strong alignment between technical decisions and product goals.",
"experience_title": "Experience",
"experience_desc": "Over 7 years in web development, including 5 in embedded systems. As a freelancer, I now work on fullstack projects, API integrations, and targeted application redesigns.",
"approach_title": "Approach",
"approach_desc": "I design robust, maintainable solutions by choosing the right tools, keeping code clean, and staying focused on real project needs."
}

5
src/i18n/en/contact.json Normal file
View File

@@ -0,0 +1,5 @@
{
"title": "Let's Work Together",
"description": "I'm always interested in hearing about new projects and opportunities. Feel free to reach out!",
"email": "Send Email"
}

View File

@@ -0,0 +1,13 @@
{
"title": "Education",
"degree1": {
"title": "Bachelor's Degree in Computer Science",
"school": "University Name",
"description": "Specialized in software engineering and web development"
},
"degree2": {
"title": "Technical Diploma",
"school": "Technical Institute",
"description": "Foundation in computer science and programming"
}
}

View File

@@ -0,0 +1,29 @@
{
"title": "Recent projects",
"tabs": {
"professional": "Professional projects",
"personal": "Personal projects"
},
"job1": {
"title": "Complete development environment setup",
"company": "Meta Video",
"description": "Porting a development environment to Docker to standardize configurations",
"task1": "Design of Dockerfiles and Docker Compose configurations",
"task2": "Integration of various services and dependencies into isolated containers",
"task3": "Integration of existing Git repositories into the new Docker environment"
},
"job2": {
"title": "Junior Developer",
"company": "Startup Inc.",
"description": "Contributed to various web development projects",
"task1": "Built responsive user interfaces with React and TailwindCSS",
"task2": "Optimized database queries and improved application performance"
},
"job3": {
"title": "Intern Developer",
"company": "Software Solutions",
"description": "Learned foundational software development practices",
"task1": "Assisted in development of internal tools and applications",
"task2": "Participated in code reviews and team meetings"
}
}

6
src/i18n/en/hero.json Normal file
View File

@@ -0,0 +1,6 @@
{
"title": "Full Stack Developer & Software Engineer",
"subtitle": "Passionate about creating innovative web solutions and building scalable applications with modern technologies.",
"cta": "Get in Touch",
"projects": "View Projects"
}

19
src/i18n/en/index.ts Normal file
View File

@@ -0,0 +1,19 @@
import nav from './nav.json';
import theme from './theme.json';
import hero from './hero.json';
import about from './about.json';
import experience from './experience.json';
import skills from './skills.json';
import education from './education.json';
import contact from './contact.json';
export default {
nav,
theme,
hero,
about,
experience,
skills,
education,
contact
};

7
src/i18n/en/nav.json Normal file
View File

@@ -0,0 +1,7 @@
{
"about": "About",
"skills": "Skills",
"education": "Education",
"projects": "Projects",
"contact": "Contact"
}

15
src/i18n/en/skills.json Normal file
View File

@@ -0,0 +1,15 @@
{
"title": "Skills & Technologies",
"languages": {
"title": "Programming Languages"
},
"frameworks": {
"title": "Frameworks & Libraries"
},
"tools": {
"title": "Tools & Platforms"
},
"other": {
"title": "Other Skills"
}
}

5
src/i18n/en/theme.json Normal file
View File

@@ -0,0 +1,5 @@
{
"light": "Light",
"dark": "Dark",
"system": "System"
}

8
src/i18n/fr/about.json Normal file
View File

@@ -0,0 +1,8 @@
{
"title": "À Propos",
"description": "Je suis un développeur full-stack passionné par la création d'applications efficaces, évolutives et conviviales. Avec une expertise en technologies web modernes et un souci du code propre, je donne vie aux idées à travers une conception réfléchie et une ingénierie robuste.",
"experience_title": "Expérience",
"experience_desc": "Plus de 7 ans en développement web, dont 5 en environnement embarqué. Aujourdhui freelance, jinterviens sur des projets fullstack, des intégrations API ou des refontes ciblées.",
"approach_title": "Approche",
"approach_desc": "Curieux et rigoureux, je conçois des solutions fiables et durables, en restant attentif aux bons outils, à la clarté du code et aux besoins concrets des projets."
}

5
src/i18n/fr/contact.json Normal file
View File

@@ -0,0 +1,5 @@
{
"title": "Travaillons Ensemble",
"description": "Je suis toujours intéressé par de nouveaux projets et opportunités. N'hésitez pas à me contacter !",
"email": "Envoyer un Email"
}

View File

@@ -0,0 +1,13 @@
{
"title": "Formation",
"degree1": {
"title": "Licence en Informatique",
"school": "Nom de l'Université",
"description": "Spécialisé en génie logiciel et développement web"
},
"degree2": {
"title": "Diplôme Technique",
"school": "Institut Technique",
"description": "Fondations en informatique et programmation"
}
}

View File

@@ -0,0 +1,32 @@
{
"title": "Projets récents",
"tabs": {
"professional": "Projets professionnels",
"personal": "Projets personnels"
},
"job1": {
"title": "Mise en place d'un environnement de développement",
"company": "Startup média",
"description": "Portage d'un environnement de dev vers Docker pour standardiser les configurations",
"task1": "Conception de Dockerfiles et de configurations Docker Compose",
"task2": "Intégration des différents services et dépendances dans des conteneurs isolés",
"task3": "Intégration des repos Git existants dans le nouvel environnement Docker"
},
"job2": {
"title": "Intégration d'une API de paiement",
"company": "Startup média",
"description": "Ajout d'une API de paiement à une plateforme multimédia pour permettre la monétisation des contenus",
"task1": "Conception d'une API modulaire pour gérer les transactions et les abonnements",
"task2": "Architecture de la base de données pour ajouter la monétisation sur le contenu existant"
},
"job3": {
"title": "Automatisation dun système de réservation de paniers",
"company": "Projet client local",
"description": "Mise en place dun site statique avec paiements Stripe, base de données et emailing automatisé",
"task1": "Création du site en HTML/CSS/JS pour présenter les offres",
"task2": "Automatisation des flux de commande avec mails et gestion logistique",
"task3": "Connexion sécurisée entre formulaire, paiements et gestion logistique",
"detailedDescription": "Projet conçu pour une entreprise de paniers de légumes, avec un objectif de déploiement rapide et de maintenance minimale. Jai développé une architecture légère mais robuste autour dun site statique, de liens Stripe pour les paiements, et de scénarios Make pour relier les paiements, la base de données logistique et la communication client. Résultat : un système fluide, sans saisie manuelle, fonctionnel dès les premiers jours.",
"technos": "Stripe, Make, Google Sheets, HTML, JavaScript"
}
}

6
src/i18n/fr/hero.json Normal file
View File

@@ -0,0 +1,6 @@
{
"title": "Développeur Full Stack & Ingénieur Logiciel",
"subtitle": "Passionné par la création de solutions web innovantes et le développement d'applications évolutives avec des technologies modernes.",
"cta": "Me Contacter",
"projects": "Voir les Projets"
}

19
src/i18n/fr/index.ts Normal file
View File

@@ -0,0 +1,19 @@
import nav from './nav.json';
import theme from './theme.json';
import hero from './hero.json';
import about from './about.json';
import experience from './experience.json';
import skills from './skills.json';
import education from './education.json';
import contact from './contact.json';
export default {
nav,
theme,
hero,
about,
experience,
skills,
education,
contact
};

7
src/i18n/fr/nav.json Normal file
View File

@@ -0,0 +1,7 @@
{
"about": "À propos",
"skills": "Compétences",
"education": "Formation",
"projects": "Projets",
"contact": "Contact"
}

15
src/i18n/fr/skills.json Normal file
View File

@@ -0,0 +1,15 @@
{
"title": "Compétences & Technologies",
"languages": {
"title": "Langages de Programmation"
},
"frameworks": {
"title": "Frameworks & Bibliothèques"
},
"tools": {
"title": "Outils & Plateformes"
},
"other": {
"title": "Autres Compétences"
}
}

5
src/i18n/fr/theme.json Normal file
View File

@@ -0,0 +1,5 @@
{
"light": "Clair",
"dark": "Sombre",
"system": "Système"
}

View File

@@ -1,230 +1,14 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './en';
import fr from './fr';
const resources = {
en: {
translation: {
// Navigation
nav: {
about: "About",
experience: "Experience",
skills: "Skills",
education: "Education",
projects: "Projects",
contact: "Contact"
},
// Theme
theme: {
light: "Light",
dark: "Dark",
custom: "Custom",
custom_purple: "Purple Haze",
custom_teal: "Teal Dream",
custom_sunset: "Sunset",
custom_forest: "Forest",
custom_ocean: "Ocean",
custom_rose: "Rose",
custom_amber: "Amber",
system: "System"
},
// Hero Section
hero: {
title: "Full Stack Developer & Software Engineer",
subtitle: "Passionate about creating innovative web solutions and building scalable applications with modern technologies.",
cta: "Get in Touch",
projects: "View Projects"
},
// About Section
about: {
title: "About Me",
description: "I'm a full-stack developer with a strong focus on creating efficient, scalable, and user-friendly applications. With expertise in modern web technologies and a passion for clean code, I bring ideas to life through thoughtful design and robust engineering.",
experience_title: "Experience",
experience_desc: "3+ years of experience in web development, working with cutting-edge technologies and agile methodologies.",
passion_title: "Passion",
passion_desc: "Dedicated to continuous learning and staying up-to-date with the latest trends in software development."
},
// Experience Section
experience: {
title: "Recent projects",
job1: {
title: "Full Stack Developer",
company: "Meta Video",
description: "Led development of modern web applications using React and Node.js",
task1: "Developed and maintained full-stack web applications",
task2: "Implemented RESTful APIs and microservices architecture",
task3: "Collaborated with cross-functional teams in an Agile environment"
},
job2: {
title: "Junior Developer",
company: "Startup Inc.",
description: "Contributed to various web development projects",
task1: "Built responsive user interfaces with React and TailwindCSS",
task2: "Optimized database queries and improved application performance"
},
job3: {
title: "Intern Developer",
company: "Software Solutions",
description: "Learned foundational software development practices",
task1: "Assisted in development of internal tools and applications",
task2: "Participated in code reviews and team meetings"
}
},
// Skills Section
skills: {
title: "Skills & Technologies",
languages: {
title: "Programming Languages"
},
frameworks: {
title: "Frameworks & Libraries"
},
tools: {
title: "Tools & Platforms"
},
other: {
title: "Other Skills"
}
},
// Education Section
education: {
title: "Education",
degree1: {
title: "Bachelor's Degree in Computer Science",
school: "University Name",
description: "Specialized in software engineering and web development"
},
degree2: {
title: "Technical Diploma",
school: "Technical Institute",
description: "Foundation in computer science and programming"
}
},
// Contact Section
contact: {
title: "Let's Work Together",
description: "I'm always interested in hearing about new projects and opportunities. Feel free to reach out!",
email: "Send Email"
}
}
translation: en
},
fr: {
translation: {
// Navigation
nav: {
about: "À propos",
experience: "Expérience",
skills: "Compétences",
education: "Formation",
projects: "Projets",
contact: "Contact"
},
// Theme
theme: {
light: "Clair",
dark: "Sombre",
custom: "Personnalisé",
custom_purple: "Violet Brumeux",
custom_teal: "Turquoise Rêveur",
custom_sunset: "Coucher de Soleil",
custom_forest: "Forêt",
custom_ocean: "Océan",
custom_rose: "Rose",
custom_amber: "Ambre",
system: "Système"
},
// Hero Section
hero: {
title: "Développeur Full Stack & Ingénieur Logiciel",
subtitle: "Passionné par la création de solutions web innovantes et le développement d'applications évolutives avec des technologies modernes.",
cta: "Me Contacter",
projects: "Voir les Projets"
},
// About Section
about: {
title: "À Propos",
description: "Je suis un développeur full-stack passionné par la création d'applications efficaces, évolutives et conviviales. Avec une expertise en technologies web modernes et un souci du code propre, je donne vie aux idées à travers une conception réfléchie et une ingénierie robuste.",
experience_title: "Expérience",
experience_desc: "Plus de 3 ans d'expérience en développement web, travaillant avec des technologies de pointe et des méthodologies agiles.",
passion_title: "Passion",
passion_desc: "Dédié à l'apprentissage continu et à rester à jour avec les dernières tendances en développement logiciel."
},
// Experience Section
experience: {
title: "Projets récents",
job1: {
title: "Développeur Full Stack",
company: "Meta Video",
description: "Direction du développement d'applications web modernes avec React et Node.js",
task1: "Développement et maintenance d'applications web full-stack",
task2: "Implémentation d'APIs RESTful et architecture microservices",
task3: "Collaboration avec des équipes pluridisciplinaires en environnement Agile"
},
job2: {
title: "Développeur Junior",
company: "Startup Inc.",
description: "Contribution à divers projets de développement web",
task1: "Construction d'interfaces utilisateur responsive avec React et TailwindCSS",
task2: "Optimisation des requêtes base de données et amélioration des performances"
},
job3: {
title: "Développeur Stagiaire",
company: "Solutions Logicielles",
description: "Apprentissage des pratiques fondamentales du développement logiciel",
task1: "Assistance au développement d'outils et applications internes",
task2: "Participation aux revues de code et réunions d'équipe"
}
},
// Skills Section
skills: {
title: "Compétences & Technologies",
languages: {
title: "Langages de Programmation"
},
frameworks: {
title: "Frameworks & Bibliothèques"
},
tools: {
title: "Outils & Plateformes"
},
other: {
title: "Autres Compétences"
}
},
// Education Section
education: {
title: "Formation",
degree1: {
title: "Licence en Informatique",
school: "Nom de l'Université",
description: "Spécialisé en génie logiciel et développement web"
},
degree2: {
title: "Diplôme Technique",
school: "Institut Technique",
description: "Fondations en informatique et programmation"
}
},
// Contact Section
contact: {
title: "Travaillons Ensemble",
description: "Je suis toujours intéressé par de nouveaux projets et opportunités. N'hésitez pas à me contacter !",
email: "Envoyer un Email"
}
}
translation: fr
}
};

View File

@@ -13,68 +13,87 @@
:root {
--color-primary: 232 232 227;
--color-secondary: 23 23 23;
--color-accent: 59 130 246; /* Bleu par défaut */
--color-accent: 59 130 246;
font-family: 'Open Sans', sans-serif;
background-color: rgb(var(--color-primary));
}
.theme-dark {
--color-primary: 23 23 23;
--color-secondary: 232 232 227;
--color-accent: 96 165 250; /* Bleu clair pour dark mode */
--color-accent: 96 165 250;
}
.theme-light {
--color-primary: 232 232 227;
--color-secondary: 23 23 23;
--color-accent: 37 99 235; /* Bleu foncé pour light mode */
--color-accent: 37 99 235;
}
/* Custom Themes - Purple Haze */
.theme-custom-purple {
--color-primary: 15 23 42; /* Slate foncé */
--color-secondary: 226 232 240; /* Slate clair */
--color-accent: 168 85 247; /* Violet/Purple */
/* Background blobs animations */
@keyframes blob {
0% {
transform: translate(0px, 0px) scale(1);
opacity: 1;
}
33% {
transform: translate(30px, -50px) scale(1.1);
opacity: 0.6;
}
66% {
transform: translate(-20px, 20px) scale(0.9);
opacity: 1;
}
100% {
transform: translate(0px, 0px) scale(1);
opacity: 1;
}
}
/* Custom Themes - Teal Dream */
.theme-custom-teal {
--color-primary: 17 24 39; /* Gray foncé */
--color-secondary: 229 231 235; /* Gray clair */
--color-accent: 20 184 166; /* Teal */
.background-blobs {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: -1;
pointer-events: none;
}
/* Custom Themes - Sunset Orange */
.theme-custom-sunset {
--color-primary: 30 27 27; /* Brun foncé */
--color-secondary: 254 240 215; /* Orange très clair */
--color-accent: 249 115 22; /* Orange vif */
.blob {
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 1;
z-index: 30;
animation: blob 20s infinite;
}
/* Custom Themes - Forest Green */
.theme-custom-forest {
--color-primary: 20 30 25; /* Vert très foncé */
--color-secondary: 236 253 243; /* Vert très clair */
--color-accent: 34 197 94; /* Vert */
.blob-1 {
top: -10%;
left: -10%;
width: 40%;
height: 40%;
background: rgb(var(--color-accent));
animation-delay: 0s;
}
/* Custom Themes - Ocean Blue */
.theme-custom-ocean {
--color-primary: 12 23 39; /* Bleu nuit */
--color-secondary: 224 242 254; /* Bleu très clair */
--color-accent: 14 165 233; /* Bleu océan */
.blob-2 {
top: 30%;
right: -10%;
width: 50%;
height: 50%;
background: rgb(var(--color-accent));
animation-delay: 4s;
}
/* Custom Themes - Rose Pink */
.theme-custom-rose {
--color-primary: 31 20 31; /* Violet foncé */
--color-secondary: 253 242 248; /* Rose très clair */
--color-accent: 244 63 94; /* Rose */
.blob-3 {
bottom: 0;
left: 10%;
width: 45%;
height: 45%;
background: rgb(var(--color-accent));
animation-delay: 2s;
opacity: 0.45;
}
/* Custom Themes - Amber Gold */
.theme-custom-amber {
--color-primary: 28 25 23; /* Brun foncé */
--color-secondary: 254 252 232; /* Jaune très clair */
--color-accent: 245 158 11; /* Ambre/Or */
}