added custom pic

This commit is contained in:
kinou-p 2025-10-02 13:55:48 +02:00
parent b56f422dd3
commit 8bb93301b9
43 changed files with 146 additions and 61 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 627 B

After

Width:  |  Height:  |  Size: 627 B

View File

Before

Width:  |  Height:  |  Size: 699 B

After

Width:  |  Height:  |  Size: 699 B

View File

Before

Width:  |  Height:  |  Size: 485 B

After

Width:  |  Height:  |  Size: 485 B

View File

Before

Width:  |  Height:  |  Size: 502 B

After

Width:  |  Height:  |  Size: 502 B

View File

Before

Width:  |  Height:  |  Size: 724 B

After

Width:  |  Height:  |  Size: 724 B

View File

Before

Width:  |  Height:  |  Size: 733 B

After

Width:  |  Height:  |  Size: 733 B

View File

Before

Width:  |  Height:  |  Size: 446 B

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 KiB

View File

@ -9,6 +9,8 @@ import ProjectPage from "./pages/ProjectPage";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
const IndexWrapper = () => <Index />;
const App = () => ( const App = () => (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<TooltipProvider> <TooltipProvider>
@ -16,7 +18,7 @@ const App = () => (
<Sonner /> <Sonner />
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
<Route path="/" element={<Index />} /> <Route path="/" element={<IndexWrapper />} />
<Route path="/project/:projectId" element={<ProjectPage />} /> <Route path="/project/:projectId" element={<ProjectPage />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />

View File

@ -22,12 +22,12 @@ export const Footer = () => {
const footerLinks = [ const footerLinks = [
{ {
icon: <FileText className="w-4 h-4" />, icon: <FileText className="w-4 h-4" />,
label: "Mentions légales", label: t("footer.legalNotice"),
onClick: () => setShowLegalNotice(true) onClick: () => setShowLegalNotice(true)
}, },
{ {
icon: <Cookie className="w-4 h-4" />, icon: <Cookie className="w-4 h-4" />,
label: "Gestion des cookies", label: t("footer.cookieManagement"),
onClick: reopenCookiePreferences onClick: reopenCookiePreferences
} }
]; ];
@ -48,21 +48,20 @@ export const Footer = () => {
return ( return (
<footer className="bg-background/50 backdrop-blur-sm border-t border-border/50"> <footer className="bg-background/50 backdrop-blur-sm border-t border-border/50">
<div className="container mx-auto px-4 py-12"> <div className="container mx-auto px-4 py-12">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8"> <div className="flex flex-col md:flex-row justify-between gap-8 mb-8">
{/* Colonne 1: Logo et description */} {/* Colonne 1: Logo et description */}
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="space-y-4" className="space-y-4 md:max-w-sm"
> >
<div className="text-2xl font-bold text-gradient"> <div className="text-2xl font-bold text-gradient">
Alexandre Pommier Alexandre Pommier
</div> </div>
<p className="text-sm text-muted-foreground leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed">
Étudiant développeur à 42, passionné par les technologies web et systèmes. {t("footer.description")}
Créateur de solutions innovantes pour un avenir numérique.
</p> </p>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{socialLinks.map((link, index) => ( {socialLinks.map((link, index) => (
@ -91,9 +90,9 @@ export const Footer = () => {
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }} transition={{ duration: 0.5, delay: 0.1 }}
className="space-y-4" className="space-y-4 md:max-w-xs"
> >
<h3 className="font-semibold text-lg">Navigation</h3> <h3 className="font-semibold text-lg">{t("footer.navigation")}</h3>
<nav className="space-y-2"> <nav className="space-y-2">
{["home", "projects", "skills", "contact"].map((item) => ( {["home", "projects", "skills", "contact"].map((item) => (
<Button <Button
@ -120,9 +119,9 @@ export const Footer = () => {
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }} transition={{ duration: 0.5, delay: 0.2 }}
className="space-y-4" className="space-y-4 md:max-w-xs"
> >
<h3 className="font-semibold text-lg">Informations</h3> <h3 className="font-semibold text-lg">{t("footer.information")}</h3>
<nav className="space-y-2"> <nav className="space-y-2">
{footerLinks.map((link, index) => ( {footerLinks.map((link, index) => (
<Button <Button
@ -151,11 +150,11 @@ export const Footer = () => {
className="flex flex-col md:flex-row justify-between items-center gap-4 text-sm text-muted-foreground" className="flex flex-col md:flex-row justify-between items-center gap-4 text-sm text-muted-foreground"
> >
<div className="text-center md:text-left"> <div className="text-center md:text-left">
© {currentYear} Alexandre Pommier. Tous droits réservés. © {currentYear} Alexandre Pommier. {t("footer.copyright")}
</div> </div>
<div className="flex items-center gap-4 text-xs"> <div className="flex items-center gap-4 text-xs">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
Construit avec {t("footer.builtWith")}
<a <a
href="https://reactjs.org" href="https://reactjs.org"
target="_blank" target="_blank"

View File

@ -3,10 +3,36 @@ import { Button } from "@/components/ui/button";
import { useTheme } from "@/contexts/ThemeContext"; import { useTheme } from "@/contexts/ThemeContext";
import { useLanguage } from "@/contexts/LanguageContext"; import { useLanguage } from "@/contexts/LanguageContext";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useLocation, useNavigate } from "react-router-dom";
export const Header = () => { export const Header = () => {
const { theme, toggleTheme } = useTheme(); const { theme, toggleTheme } = useTheme();
const { language, setLanguage, t } = useLanguage(); const { language, setLanguage, t } = useLanguage();
const location = useLocation();
const navigate = useNavigate();
// Check if we're on a project page
const isProjectPage = location.pathname.startsWith('/project/');
const handleNavigation = (section: string) => {
if (isProjectPage) {
// If on project page, navigate to home with section anchor
navigate(`/#${section}`);
} else {
// If on home page, scroll to section
scrollToSection(section);
}
};
const handleLogoClick = () => {
if (isProjectPage) {
// If on project page, navigate to home
navigate('/');
} else {
// If on home page, scroll to top
scrollToTop();
}
};
const scrollToSection = (id: string) => { const scrollToSection = (id: string) => {
const element = document.getElementById(id); const element = document.getElementById(id);
@ -30,9 +56,9 @@ export const Header = () => {
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
onClick={scrollToTop} onClick={handleLogoClick}
className="text-2xl font-bold text-gradient hover:scale-105 transition-transform duration-200 cursor-pointer" className="text-2xl font-bold text-gradient hover:scale-105 transition-transform duration-200 cursor-pointer"
aria-label="Retour en haut de page" aria-label={isProjectPage ? "Retour à l'accueil" : "Retour en haut de page"}
> >
AP AP
</motion.button> </motion.button>
@ -44,7 +70,7 @@ export const Header = () => {
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 * i }} transition={{ delay: 0.1 * i }}
onClick={() => scrollToSection(item)} onClick={() => handleNavigation(item)}
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors" className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
> >
{t(`nav.${item}`)} {t(`nav.${item}`)}

View File

@ -7,40 +7,40 @@ export const ProjectsSection = () => {
const { t } = useLanguage(); const { t } = useLanguage();
const projects = [ const projects = [
{
key: "avopieces",
icon: <Scale className="w-6 h-6 text-primary" />,
image: "/images/sites/avopieces/mookup/3-devices-white (1).png",
},
{ {
key: "nas", key: "nas",
icon: <Server className="w-6 h-6 text-primary" />, icon: <Server className="w-6 h-6 text-primary" />,
image: "/images/projects/nas.svg", image: "/images/projects/homemade_nas.png",
}, },
{ {
key: "transcendence", key: "transcendence",
icon: <Gamepad2 className="w-6 h-6 text-primary" />, icon: <Gamepad2 className="w-6 h-6 text-primary" />,
image: "/images/projects/transcendence.svg", image: "/images/projects/pong.png",
}, },
{ {
key: "cloud", key: "cloud",
icon: <Cloud className="w-6 h-6 text-primary" />, icon: <Cloud className="w-6 h-6 text-primary" />,
image: "/images/projects/cloud.svg", image: "/images/projects/cloud_1.png",
}, },
{ {
key: "minishell", key: "minishell",
icon: <Terminal className="w-6 h-6 text-primary" />, icon: <Terminal className="w-6 h-6 text-primary" />,
image: "/images/projects/minishell.svg", image: "/images/projects/minishell.png",
},
{
key: "cube3d",
icon: <Box className="w-6 h-6 text-primary" />,
image: "/images/projects/cube3d.svg",
}, },
{ {
key: "etsidemain", key: "etsidemain",
icon: <Globe className="w-6 h-6 text-primary" />, icon: <Globe className="w-6 h-6 text-primary" />,
image: "/images/projects/etsidemain.svg", image: "/images/sites/etsidemain/mookup/3-devices-white.png",
}, },
{ {
key: "avopieces", key: "cube3d",
icon: <Scale className="w-6 h-6 text-primary" />, icon: <Box className="w-6 h-6 text-primary" />,
image: "/images/projects/avopieces.svg", image: "/images/projects/cub3d.png",
}, },
]; ];

View File

@ -59,8 +59,8 @@ export const projectsData: Record<string, ProjectData> = {
fr: "Architecture microservices avec Docker Compose, reverse proxy Traefik automatisant les certificats Let's Encrypt, système de backup automatique avec rsync, et monitoring avec Prometheus.", fr: "Architecture microservices avec Docker Compose, reverse proxy Traefik automatisant les certificats Let's Encrypt, système de backup automatique avec rsync, et monitoring avec Prometheus.",
en: "Microservices architecture with Docker Compose, Traefik reverse proxy automating Let's Encrypt certificates, automatic backup system with rsync, and monitoring with Prometheus.", en: "Microservices architecture with Docker Compose, Traefik reverse proxy automating Let's Encrypt certificates, automatic backup system with rsync, and monitoring with Prometheus.",
}, },
mainImage: "/images/projects/nas.svg", mainImage: "/images/projects/homemade_nas.png",
images: ["/images/projects/nas.svg"], images: ["/images/projects/homemade_nas.png"],
technologies: [ technologies: [
{ name: "OpenMediaVault" }, { name: "OpenMediaVault" },
{ name: "Docker" }, { name: "Docker" },
@ -113,8 +113,8 @@ export const projectsData: Record<string, ProjectData> = {
fr: "Architecture MVC avec NestJS, WebSocket rooms pour les matchs, système de queuing Redis pour le matchmaking, JWT pour l'authentification, et Canvas HTML5 pour le rendu du jeu.", fr: "Architecture MVC avec NestJS, WebSocket rooms pour les matchs, système de queuing Redis pour le matchmaking, JWT pour l'authentification, et Canvas HTML5 pour le rendu du jeu.",
en: "MVC architecture with NestJS, WebSocket rooms for matches, Redis queuing system for matchmaking, JWT for authentication, and HTML5 Canvas for game rendering.", en: "MVC architecture with NestJS, WebSocket rooms for matches, Redis queuing system for matchmaking, JWT for authentication, and HTML5 Canvas for game rendering.",
}, },
mainImage: "/images/projects/transcendence.svg", mainImage: "/images/projects/pong.png",
images: ["/images/projects/transcendence.svg"], images: ["/images/projects/pong.png"],
technologies: [ technologies: [
{ name: "React", icon: "⚛️" }, { name: "React", icon: "⚛️" },
{ name: "TypeScript" }, { name: "TypeScript" },
@ -167,8 +167,8 @@ export const projectsData: Record<string, ProjectData> = {
fr: "Docker Compose pour l'orchestration, Ansible playbooks pour l'automatisation, volumes Docker pour la persistance, et réseau bridge personnalisé pour l'isolation.", fr: "Docker Compose pour l'orchestration, Ansible playbooks pour l'automatisation, volumes Docker pour la persistance, et réseau bridge personnalisé pour l'isolation.",
en: "Docker Compose for orchestration, Ansible playbooks for automation, Docker volumes for persistence, and custom bridge network for isolation.", en: "Docker Compose for orchestration, Ansible playbooks for automation, Docker volumes for persistence, and custom bridge network for isolation.",
}, },
mainImage: "/images/projects/cloud.svg", mainImage: "/images/projects/cloud_1.png",
images: ["/images/projects/cloud.svg"], images: ["/images/projects/cloud_1.png"],
technologies: [ technologies: [
{ name: "Docker", icon: "🐳" }, { name: "Docker", icon: "🐳" },
{ name: "Ansible" }, { name: "Ansible" },
@ -220,8 +220,8 @@ export const projectsData: Record<string, ProjectData> = {
fr: "Tokenizer/Lexer pour le parsing, AST pour représenter les commandes, gestion des descripteurs de fichiers pour les redirections, et table de hash pour les variables d'environnement.", fr: "Tokenizer/Lexer pour le parsing, AST pour représenter les commandes, gestion des descripteurs de fichiers pour les redirections, et table de hash pour les variables d'environnement.",
en: "Tokenizer/Lexer for parsing, AST to represent commands, file descriptor management for redirections, and hash table for environment variables.", en: "Tokenizer/Lexer for parsing, AST to represent commands, file descriptor management for redirections, and hash table for environment variables.",
}, },
mainImage: "/images/projects/minishell.svg", mainImage: "/images/projects/minishell.png",
images: ["/images/projects/minishell.svg"], images: ["/images/projects/minishell.png"],
technologies: [ technologies: [
{ name: "C" }, { name: "C" },
{ name: "Linux" }, { name: "Linux" },
@ -272,8 +272,8 @@ export const projectsData: Record<string, ProjectData> = {
fr: "Algorithme DDA pour le raycasting, lookup tables pour les calculs trigonométriques, buffer d'image pour le double buffering, et grille 2D pour la détection de collisions.", fr: "Algorithme DDA pour le raycasting, lookup tables pour les calculs trigonométriques, buffer d'image pour le double buffering, et grille 2D pour la détection de collisions.",
en: "DDA algorithm for raycasting, lookup tables for trigonometric calculations, image buffer for double buffering, and 2D grid for collision detection.", en: "DDA algorithm for raycasting, lookup tables for trigonometric calculations, image buffer for double buffering, and 2D grid for collision detection.",
}, },
mainImage: "/images/projects/cube3d.svg", mainImage: "/images/projects/cub3d.png",
images: ["/images/projects/cube3d.svg"], images: ["/images/projects/cub3d.png"],
technologies: [ technologies: [
{ name: "C" }, { name: "C" },
{ name: "MiniLibX" }, { name: "MiniLibX" },
@ -283,7 +283,7 @@ export const projectsData: Record<string, ProjectData> = {
}, },
etsidemain: { etsidemain: {
id: "etsidemain", id: "etsidemain",
title: { fr: "etsidemain.com", en: "etsidemain.com" }, title: { fr: "Site Et si demain...", en: "Et si demain... Website" },
shortDescription: { shortDescription: {
fr: "Site web vitrine pour cabinet de conseil en transformation régénérative. Design moderne et responsive avec animations CSS, formulaire de contact et optimisations SEO.", fr: "Site web vitrine pour cabinet de conseil en transformation régénérative. Design moderne et responsive avec animations CSS, formulaire de contact et optimisations SEO.",
en: "Showcase website for regenerative transformation consulting firm. Modern responsive design with CSS animations, contact form and SEO optimizations.", en: "Showcase website for regenerative transformation consulting firm. Modern responsive design with CSS animations, contact form and SEO optimizations.",
@ -324,8 +324,17 @@ export const projectsData: Record<string, ProjectData> = {
fr: "HTML5/CSS3 sémantique, animations avec transitions CSS et Intersection Observer, balises meta optimisées, structure de données JSON-LD, et optimisation des assets.", fr: "HTML5/CSS3 sémantique, animations avec transitions CSS et Intersection Observer, balises meta optimisées, structure de données JSON-LD, et optimisation des assets.",
en: "Semantic HTML5/CSS3, animations with CSS transitions and Intersection Observer, optimized meta tags, JSON-LD data structure, and asset optimization.", en: "Semantic HTML5/CSS3, animations with CSS transitions and Intersection Observer, optimized meta tags, JSON-LD data structure, and asset optimization.",
}, },
mainImage: "/images/projects/etsidemain.svg", mainImage: "/images/sites/etsidemain/mookup/3-devices-white.png",
images: ["/images/projects/etsidemain.svg"], images: [
"/images/sites/etsidemain/mookup/3-devices-white.png",
"/images/sites/etsidemain/mookup/desktop.png",
"/images/sites/etsidemain/mookup/laptop.png",
"/images/sites/etsidemain/mookup/tablet-white.png",
"/images/sites/etsidemain/mookup/mobile-white.png",
"/images/sites/etsidemain/pc.png",
"/images/sites/etsidemain/tablette.png",
"/images/sites/etsidemain/tel.png"
],
technologies: [ technologies: [
{ name: "HTML5" }, { name: "HTML5" },
{ name: "CSS3" }, { name: "CSS3" },
@ -336,7 +345,7 @@ export const projectsData: Record<string, ProjectData> = {
}, },
avopieces: { avopieces: {
id: "avopieces", id: "avopieces",
title: { fr: "avopieces.fr", en: "avopieces.fr" }, title: { fr: "Site Avo Pièces", en: "Avo Pieces Website" },
shortDescription: { shortDescription: {
fr: "Plateforme juridique intelligente pour le cabinet AvoCab, spécialisée dans les procédures de divorce. Intègre un chatbot IA analysant les documents uploadés, système de gestion de comptes client/admin, prise de RDV en ligne et vitrine du cabinet.", fr: "Plateforme juridique intelligente pour le cabinet AvoCab, spécialisée dans les procédures de divorce. Intègre un chatbot IA analysant les documents uploadés, système de gestion de comptes client/admin, prise de RDV en ligne et vitrine du cabinet.",
en: "Intelligent legal platform for AvoCab law firm, specialized in divorce procedures. Features AI chatbot analyzing uploaded documents, client/admin account management system, online appointment booking and law firm showcase.", en: "Intelligent legal platform for AvoCab law firm, specialized in divorce procedures. Features AI chatbot analyzing uploaded documents, client/admin account management system, online appointment booking and law firm showcase.",
@ -377,8 +386,17 @@ export const projectsData: Record<string, ProjectData> = {
fr: "Architecture MERN stack, RAG (Retrieval Augmented Generation) pour le chatbot, chiffrement des données, système de roles et permissions, API RESTful, et design system cohérent.", fr: "Architecture MERN stack, RAG (Retrieval Augmented Generation) pour le chatbot, chiffrement des données, système de roles et permissions, API RESTful, et design system cohérent.",
en: "MERN stack architecture, RAG (Retrieval Augmented Generation) for chatbot, data encryption, roles and permissions system, RESTful API, and consistent design system.", en: "MERN stack architecture, RAG (Retrieval Augmented Generation) for chatbot, data encryption, roles and permissions system, RESTful API, and consistent design system.",
}, },
mainImage: "/images/projects/avopieces.svg", mainImage: "/images/sites/avopieces/mookup/3-devices-white (1).png",
images: ["/images/projects/avopieces.svg"], images: [
"/images/sites/avopieces/mookup/3-devices-white (1).png",
"/images/sites/avopieces/mookup/desktop (1).png",
"/images/sites/avopieces/mookup/laptop (1).png",
"/images/sites/avopieces/mookup/tablet-white (1).png",
"/images/sites/avopieces/mookup/mobile-white (1).png",
"/images/sites/avopieces/pc.png",
"/images/sites/avopieces/tablette.png",
"/images/sites/avopieces/tel.png"
],
technologies: [ technologies: [
{ name: "React", icon: "⚛️" }, { name: "React", icon: "⚛️" },
{ name: "Node.js", icon: "📗" }, { name: "Node.js", icon: "📗" },

View File

@ -1,3 +1,5 @@
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
import { Header } from "@/components/Header"; import { Header } from "@/components/Header";
import { Footer } from "@/components/Footer"; import { Footer } from "@/components/Footer";
import { ScrollProgress } from "@/components/ScrollProgress"; import { ScrollProgress } from "@/components/ScrollProgress";
@ -11,6 +13,20 @@ import { LanguageProvider } from "@/contexts/LanguageContext";
import { CookieBannerProvider } from "@/contexts/CookieBannerContext"; import { CookieBannerProvider } from "@/contexts/CookieBannerContext";
const Index = () => { const Index = () => {
const location = useLocation();
// Handle navigation with hash from project pages
useEffect(() => {
if (location.hash) {
const element = document.getElementById(location.hash.substring(1));
if (element) {
setTimeout(() => {
element.scrollIntoView({ behavior: "smooth" });
}, 100);
}
}
}, [location.hash]);
return ( return (
<ThemeProvider> <ThemeProvider>
<LanguageProvider> <LanguageProvider>

View File

@ -1,10 +1,12 @@
import { useParams, useNavigate } from "react-router-dom"; import { useParams, useNavigate, useLocation } from "react-router-dom";
import { useEffect } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { ArrowLeft, Github, ExternalLink, Mail } from "lucide-react"; import { ArrowLeft, Github, ExternalLink, Mail } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { ScrollProgress } from "@/components/ScrollProgress"; import { ScrollProgress } from "@/components/ScrollProgress";
import { Header } from "@/components/Header";
import { ImageGallery } from "@/components/ImageGallery"; import { ImageGallery } from "@/components/ImageGallery";
import { SkillBadge } from "@/components/SkillBadge"; import { SkillBadge } from "@/components/SkillBadge";
import { useLanguage } from "@/contexts/LanguageContext"; import { useLanguage } from "@/contexts/LanguageContext";
@ -16,9 +18,15 @@ import { LanguageProvider } from "@/contexts/LanguageContext";
const ProjectPageContent = () => { const ProjectPageContent = () => {
const { projectId } = useParams<{ projectId: string }>(); const { projectId } = useParams<{ projectId: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const { language, t } = useLanguage(); const { language, t } = useLanguage();
const { theme } = useTheme(); const { theme } = useTheme();
// Reset scroll position when projectId changes
useEffect(() => {
window.scrollTo(0, 0);
}, [projectId, location.pathname]);
const project = projectId ? projectsData[projectId] : null; const project = projectId ? projectsData[projectId] : null;
if (!project) { if (!project) {
@ -38,19 +46,10 @@ const ProjectPageContent = () => {
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<ScrollProgress /> <ScrollProgress />
<Header />
{/* Header */}
<header className="sticky top-0 z-40 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<Button variant="ghost" onClick={() => navigate("/")} className="gap-2">
<ArrowLeft className="w-4 h-4" />
{t("project.back") || "Back"}
</Button>
</div>
</header>
{/* Hero Section */} {/* Hero Section */}
<section className="py-20 md:py-32"> <section className="py-20 md:py-32 mt-16">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}

View File

@ -37,11 +37,11 @@ export const translations = {
description: "Moteur RayCaster 3D inspiré de Wolfenstein 3D, développé avec MiniLibX et algorithmes graphiques avancés.", description: "Moteur RayCaster 3D inspiré de Wolfenstein 3D, développé avec MiniLibX et algorithmes graphiques avancés.",
}, },
etsidemain: { etsidemain: {
title: "etsidemain.com", title: "Site Et si demain...",
description: "Site web vitrine pour cabinet de conseil en transformation régénérative. Design moderne et responsive avec animations CSS, formulaire de contact et optimisations SEO.", description: "Site web vitrine pour cabinet de conseil en transformation régénérative. Design moderne et responsive avec animations CSS, formulaire de contact et optimisations SEO.",
}, },
avopieces: { avopieces: {
title: "avopieces.fr", title: "Site Avo Pièces",
description: "Plateforme juridique intelligente pour le cabinet AvoCab, spécialisée dans les procédures de divorce. Intègre un chatbot IA analysant les documents uploadés, système de gestion de comptes client/admin, prise de RDV en ligne et vitrine du cabinet.", description: "Plateforme juridique intelligente pour le cabinet AvoCab, spécialisée dans les procédures de divorce. Intègre un chatbot IA analysant les documents uploadés, système de gestion de comptes client/admin, prise de RDV en ligne et vitrine du cabinet.",
}, },
}, },
@ -58,6 +58,15 @@ export const translations = {
email: "Email", email: "Email",
github: "GitHub", github: "GitHub",
}, },
footer: {
description: "Étudiant développeur à 42, passionné par les technologies web et systèmes. Créateur de solutions innovantes pour un avenir numérique.",
navigation: "Navigation",
information: "Informations",
legalNotice: "Mentions légales",
cookieManagement: "Gestion des cookies",
copyright: "Tous droits réservés.",
builtWith: "Construit avec",
},
project: { project: {
back: "Retour", back: "Retour",
backToHome: "Retour à l'accueil", backToHome: "Retour à l'accueil",
@ -112,11 +121,11 @@ export const translations = {
description: "3D RayCaster engine inspired by Wolfenstein 3D, developed with MiniLibX and advanced graphics algorithms.", description: "3D RayCaster engine inspired by Wolfenstein 3D, developed with MiniLibX and advanced graphics algorithms.",
}, },
etsidemain: { etsidemain: {
title: "etsidemain.com", title: "Et si demain... Website",
description: "Showcase website for regenerative transformation consulting firm. Modern responsive design with CSS animations, contact form and SEO optimizations.", description: "Showcase website for regenerative transformation consulting firm. Modern responsive design with CSS animations, contact form and SEO optimizations.",
}, },
avopieces: { avopieces: {
title: "avopieces.fr", title: "Avo Pieces Website",
description: "Intelligent legal platform for AvoCab law firm, specialized in divorce procedures. Features AI chatbot analyzing uploaded documents, client/admin account management system, online appointment booking and law firm showcase.", description: "Intelligent legal platform for AvoCab law firm, specialized in divorce procedures. Features AI chatbot analyzing uploaded documents, client/admin account management system, online appointment booking and law firm showcase.",
}, },
}, },
@ -133,6 +142,15 @@ export const translations = {
email: "Email", email: "Email",
github: "GitHub", github: "GitHub",
}, },
footer: {
description: "Developer student at 42, passionate about web technologies and systems. Creator of innovative solutions for a digital future.",
navigation: "Navigation",
information: "Information",
legalNotice: "Legal Notice",
cookieManagement: "Cookie Management",
copyright: "All rights reserved.",
builtWith: "Built with",
},
project: { project: {
back: "Back", back: "Back",
backToHome: "Back to Home", backToHome: "Back to Home",

View File

@ -8,6 +8,13 @@ export default defineConfig(({ mode }) => ({
server: { server: {
host: "::", host: "::",
port: 8080, port: 8080,
allowedHosts: [
"alexandre-pommier.com",
"www.alexandre-pommier.com",
"localhost",
"127.0.0.1",
"0.0.0.0"
]
}, },
plugins: [react(), mode === "development" && componentTagger()].filter(Boolean), plugins: [react(), mode === "development" && componentTagger()].filter(Boolean),
resolve: { resolve: {