From cbbfa81d7e3a9d8d209f8bb2316e08a74182d35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=A9o=20GUILBERT?= Date: Tue, 2 Jun 2026 14:24:06 +0200 Subject: [PATCH] add freelance page components and localization for English and French --- .claude/settings.local.json | 6 +- app/[locale]/freelance/_client.tsx | 392 +++++++++++++++++++++++++++++ app/[locale]/freelance/page.tsx | 23 +- messages/en.json | 82 +++++- messages/fr.json | 76 +++++- 5 files changed, 565 insertions(+), 14 deletions(-) create mode 100644 app/[locale]/freelance/_client.tsx diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 9d1045e..43f4eed 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,11 @@ "WebFetch(domain:localhost)", "Bash(curl -s http://localhost:3000/fr)", "Bash(npx tsc *)", - "Bash(npm install *)" + "Bash(npm install *)", + "Bash(Get-ChildItem -Path \"d:\\\\2 - Projets\\\\matheoguilbert.fr\" -Recurse -Depth 2)", + "Bash(Select-Object -First 50)", + "Bash(Format-Table FullName)", + "PowerShell(Get-ChildItem -Path \"d:\\\\2 - Projets\\\\matheoguilbert.fr\" -Recurse -Depth 2 | Select-Object -ExpandProperty FullName | Where-Object { $_ -notmatch 'node_modules|\\\\.next' } | Select-Object -First 80)" ] } } diff --git a/app/[locale]/freelance/_client.tsx b/app/[locale]/freelance/_client.tsx new file mode 100644 index 0000000..39a4d9f --- /dev/null +++ b/app/[locale]/freelance/_client.tsx @@ -0,0 +1,392 @@ +'use client'; + +import { useEffect, useRef } from 'react'; +import { useTranslations } from 'next-intl'; +import { ProjectCard } from '@/components/ui/ProjectCard'; +import { Project } from '@/lib/projects/types'; + +type props = { + projects: Project[]; + locale: string; +}; + +export default function FreelanceClient({ projects, locale }: props) { + const t = useTranslations('freelancePage'); + + const heroRef = useRef(null); + const belowFoldRef = useRef(null); + + useEffect(() => { + const section = heroRef.current; + if (!section) return; + const items = section.querySelectorAll('.hero-reveal'); + items.forEach((el, i) => { + setTimeout(() => { + el.style.opacity = '1'; + el.style.transform = 'translateY(0)'; + }, 120 + i * 110); + }); + }, []); + + useEffect(() => { + const container = belowFoldRef.current; + if (!container) return; + + const sections = container.querySelectorAll('[data-reveal-section]'); + const observers: IntersectionObserver[] = []; + + sections.forEach((section) => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.querySelectorAll('.reveal').forEach((el, i) => { + setTimeout(() => el.classList.add('visible'), i * 100); + }); + observer.unobserve(entry.target); + } + }); + }, + { threshold: 0.1 } + ); + observer.observe(section); + observers.push(observer); + }); + + return () => observers.forEach((o) => o.disconnect()); + }, []); + + const needs = [ + { title: t('needs.item1Title'), text: t('needs.item1Text') }, + { title: t('needs.item2Title'), text: t('needs.item2Text') }, + { title: t('needs.item3Title'), text: t('needs.item3Text') }, + { title: t('needs.item4Title'), text: t('needs.item4Text') }, + ]; + + const services = [ + { + title: t('services.service1Title'), + desc: t('services.service1Desc'), + ideal: t('services.service1Ideal'), + examples: t('services.service1Examples').split(' · '), + }, + { + title: t('services.service2Title'), + desc: t('services.service2Desc'), + ideal: t('services.service2Ideal'), + examples: t('services.service2Examples').split(' · '), + }, + { + title: t('services.service3Title'), + desc: t('services.service3Desc'), + ideal: t('services.service3Ideal'), + examples: t('services.service3Examples').split(' · '), + }, + ]; + + const methodSteps = Array.from({ length: 5 }, (_, i) => ({ + title: t(`method.step${i + 1}Title`), + text: t(`method.step${i + 1}Text`), + })); + + const whyArgs = Array.from({ length: 4 }, (_, i) => ({ + title: t(`why.arg${i + 1}Title`), + text: t(`why.arg${i + 1}Text`), + })); + + const heroCards = [ + { num: '01', label: t('hero.card1Label') }, + { num: '02', label: t('hero.card2Label') }, + { num: '03', label: t('hero.card3Label') }, + ]; + + return ( +
+ + {/* ── Hero ── */} +
+
+ +
+ + {/* Left — text */} +
+ +
+ + + {t('hero.label')} + +
+ +
+

+ {t('hero.title')} +

+
+ +
+

+ {t('hero.subtitle')} +

+
+ +
+ + {t('hero.cta1')} + + +
+
+ + {/* Right — service preview cards */} +
+ {heroCards.map((card, i) => ( +
+ {card.num} + {card.label} +
+ ))} +
+
+ +
+
+ + {/* ── Below fold ── */} +
+ + {/* ── Needs ── */} +
+
+ + + {t('needs.sectionLabel')} + +
+ +
+ {needs.map((item, i) => ( +
+ + {String(i + 1).padStart(2, '0')} + +
+

+ {item.title} +

+

+ {item.text} +

+
+
+ ))} +
+
+ + {/* ── Services — catalog grid ── */} +
+
+ + + {t('services.sectionLabel')} + +
+ +
+ {services.map((service, i) => ( +
+ + {String(i + 1).padStart(2, '0')} + +

+ {service.title} +

+

+ {service.desc} +

+
+
+ + {t('services.idealLabel')} + +

+ {service.ideal} +

+
+
+ {service.examples.map((ex, j) => ( + + {ex} + + ))} +
+
+
+ ))} +
+
+ + {/* ── Method — card grid ── */} +
+
+ + + {t('method.sectionLabel')} + +
+ +
+ {methodSteps.map((step, i) => ( +
+ + {String(i + 1).padStart(2, '0')} + +

+ {step.title} +

+

+ {step.text} +

+
+ ))} +
+
+ + {/* ── Works — project cards ── */} +
+
+
+ + + {t('works.sectionLabel')} + +
+
+ + {projects.map((project, i) => ( + + ))} +
+ + {/* ── Why ── */} +
+
+ + + {t('why.sectionLabel')} + +
+ +
+ {/* Left — intro text */} +
+

+ {t('why.intro')} +

+
+ {/* Right — 2×2 arguments */} +
+ {whyArgs.map((arg, i) => ( +
+

{arg.title}

+

{arg.text}

+
+ ))} +
+
+
+ + {/* ── CTA final ── */} +
+
+
+

+ {t('cta.title')} +

+

+ {t('cta.text')} +

+
+ + {t('cta.button')} + + +
+ {t('cta.note1')} + {t('cta.note2')} +
+
+
+
+
+ +
+
+ ); +} diff --git a/app/[locale]/freelance/page.tsx b/app/[locale]/freelance/page.tsx index fe78a9d..7b6b0af 100644 --- a/app/[locale]/freelance/page.tsx +++ b/app/[locale]/freelance/page.tsx @@ -1,11 +1,18 @@ -'use client'; +import { getProjectBySlug } from "@/lib/projects/get-projects"; +import { Project } from "@/lib/projects/types"; +import FreelanceClient from "./_client"; -export default function FreelancePage() { +type props = { + params: Promise<{ locale: string }>; +}; +export default async function FreelancePage({ params }: props) { + const { locale } = await params; - return ( -
- -
- ); -} \ No newline at end of file + const slugOrder = ['MGDev', 'Thence', 'EverEastSolutions']; + const projects = slugOrder + .map((s) => getProjectBySlug(s, locale)) + .filter((p): p is Project => p !== null && p.ready); + + return ; +} diff --git a/messages/en.json b/messages/en.json index 44de445..bc0b4af 100644 --- a/messages/en.json +++ b/messages/en.json @@ -8,7 +8,7 @@ "hero": { "sectionLabel": "Full Stack Developer & Web Architect", "title": "Together, let's build something that lasts.", - "subtitle": "I design robust, scalable, and sustainable architectures—foundations that support your product’s growth over time.", + "subtitle": "I design robust, scalable, and sustainable architectures—foundations that support your product's growth over time.", "cta": "View my work", "scrollPrompt": "Scroll" }, @@ -28,11 +28,11 @@ "sectionLabel": "Approach", "feature1": { "title": "More than just a doer", - "text": "Thanks to my background in digital and mobile design, I don’t just write code. I bring a critical and constructive perspective to every aspect of your project." + "text": "Thanks to my background in digital and mobile design, I don't just write code. I bring a critical and constructive perspective to every aspect of your project." }, "feature2": { "title": "Concrete foundations", - "text": "My specialty is architecture. I build sustainable, scalable solutions that can accommodate your product’s growth over time." + "text": "My specialty is architecture. I build sustainable, scalable solutions that can accommodate your product's growth over time." }, "feature3": { "title": "User-centered", @@ -99,5 +99,79 @@ "visitButton": { "text": "Visit the website" } + }, + "freelancePage": { + "hero": { + "label": "Freelance — Custom web development", + "title": "Business software, websites, and custom web platforms.", + "subtitle": "I design clear, reliable, and scalable web solutions for businesses, independents, and project owners who need a specific challenge turned into a concrete tool.", + "cta1": "Discuss your project", + "cta2": "See examples", + "card1Label": "Business software", + "card2Label": "Professional websites", + "card3Label": "Web platforms" + }, + "needs": { + "sectionLabel": "You need to…", + "item1Title": "Replace spreadsheets", + "item1Text": "Centralize your data in a clear, accessible tool.", + "item2Title": "Automate a process", + "item2Text": "Reduce repetitive tasks and manual errors.", + "item3Title": "Modernize your website", + "item3Text": "Present your business with a more professional image.", + "item4Title": "Create an online space", + "item4Text": "Let your clients, members, or teams access a service." + }, + "services": { + "sectionLabel": "Services", + "idealLabel": "Ideal for", + "service1Title": "Custom business software", + "service1Desc": "To build a tool tailored to your way of working: internal tracking, request management, dashboards, client portals, automation, or admin interfaces.", + "service1Ideal": "SMEs, associations, internal teams, independents with specific processes.", + "service1Examples": "Lightweight CRM · Dashboard · Client portal · Back-office", + "service2Title": "Professional showcase websites", + "service2Desc": "To clearly present your business, reassure your visitors, and make it easy to get in touch — with a fast, responsive, and scalable website.", + "service2Ideal": "Freelancers, artisans, consultants, associations, small businesses.", + "service2Examples": "Business site · Redesign · Multilingual · SEO", + "service3Title": "Dynamic websites and web platforms", + "service3Desc": "For projects that require user accounts, manageable content, a database, advanced forms, or external integrations.", + "service3Ideal": "Project owners, startups, agencies, businesses with specific web needs.", + "service3Examples": "Authentication · CMS · API · Advanced forms" + }, + "method": { + "sectionLabel": "A simple, structured approach", + "step1Title": "Understand", + "step1Text": "A conversation about your needs, constraints, and goals.", + "step2Title": "Frame", + "step2Text": "Define useful features, priorities, and scope.", + "step3Title": "Design", + "step3Text": "Screen structure, user flows, and technical choices.", + "step4Title": "Develop", + "step4Text": "Build the solution on a clean, maintainable, and scalable foundation.", + "step5Title": "Launch", + "step5Text": "Deployment, testing, adjustments, and ongoing support." + }, + "works": { + "sectionLabel": "A few examples" + }, + "why": { + "sectionLabel": "Why work with me?", + "intro": "I don't just produce pages or features. My role is to understand your need, structure a realistic solution, and build a reliable tool you can actually use.", + "arg1Title": "Product vision", + "arg1Text": "I help you clarify the need before building.", + "arg2Title": "Full stack profile", + "arg2Text": "UI, business logic, database, API, deployment.", + "arg3Title": "Durable solutions", + "arg3Text": "Maintainable code, clean structure, room to evolve.", + "arg4Title": "Clear communication", + "arg4Text": "Accessible explanations, even without a technical background." + }, + "cta": { + "title": "You have a project or a specific need?", + "text": "Simply tell me what you'd like to build, improve, or automate. I'll help you figure out the best approach.", + "button": "Contact me", + "note1": "Response within a few days.", + "note2": "First conversation, no commitment." + } } -} \ No newline at end of file +} diff --git a/messages/fr.json b/messages/fr.json index 15e2ef1..9d7f807 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -99,5 +99,79 @@ "visitButton": { "text": "Visiter le site" } + }, + "freelancePage": { + "hero": { + "label": "Freelance — Développement web sur mesure", + "title": "Logiciels métier, sites web et plateformes sur mesure.", + "subtitle": "Je conçois des solutions web claires, fiables et évolutives pour les entreprises, indépendants et porteurs de projets qui ont un besoin spécifique à transformer en outil concret.", + "cta1": "Discuter de votre projet", + "cta2": "Voir des exemples", + "card1Label": "Logiciels métier", + "card2Label": "Sites vitrines", + "card3Label": "Plateformes web" + }, + "needs": { + "sectionLabel": "Vous avez besoin de…", + "item1Title": "Remplacer des fichiers Excel", + "item1Text": "Centraliser vos données dans un outil clair et accessible.", + "item2Title": "Automatiser un processus", + "item2Text": "Réduire les tâches répétitives et les erreurs manuelles.", + "item3Title": "Moderniser votre site", + "item3Text": "Présenter votre activité avec une image plus professionnelle.", + "item4Title": "Créer un espace en ligne", + "item4Text": "Permettre à vos clients, membres ou équipes d'accéder à un service." + }, + "services": { + "sectionLabel": "Prestations", + "idealLabel": "Idéal pour", + "service1Title": "Logiciels métier sur mesure", + "service1Desc": "Pour créer un outil adapté à votre façon de travailler : suivi interne, gestion de demandes, tableau de bord, espace client, automatisation ou interface d'administration.", + "service1Ideal": "PME, associations, équipes internes, indépendants avec des processus spécifiques.", + "service1Examples": "CRM léger · Tableau de bord · Espace client · Back-office", + "service2Title": "Sites vitrines professionnels", + "service2Desc": "Pour présenter clairement votre activité, rassurer vos visiteurs et faciliter la prise de contact avec un site rapide, responsive et évolutif.", + "service2Ideal": "Indépendants, artisans, consultants, associations, petites entreprises.", + "service2Examples": "Site d'entreprise · Refonte · Multilingue · SEO", + "service3Title": "Sites dynamiques et plateformes web", + "service3Desc": "Pour les projets qui nécessitent des comptes utilisateurs, du contenu administrable, une base de données, des formulaires avancés ou des intégrations externes.", + "service3Ideal": "Porteurs de projets, startups, agences, entreprises avec un besoin web spécifique.", + "service3Examples": "Authentification · CMS · API · Formulaires avancés" + }, + "method": { + "sectionLabel": "Une méthode simple et structurée", + "step1Title": "Comprendre", + "step1Text": "Échange autour de votre besoin, de vos contraintes et de vos objectifs.", + "step2Title": "Cadrer", + "step2Text": "Définition des fonctionnalités utiles, des priorités et du périmètre.", + "step3Title": "Concevoir", + "step3Text": "Structure des écrans, parcours utilisateurs et choix techniques.", + "step4Title": "Développer", + "step4Text": "Création de la solution avec une base propre, maintenable et évolutive.", + "step5Title": "Mettre en ligne", + "step5Text": "Déploiement, tests, ajustements et accompagnement." + }, + "works": { + "sectionLabel": "Quelques exemples de réalisations" + }, + "why": { + "sectionLabel": "Pourquoi travailler avec moi ?", + "intro": "Je ne me limite pas à produire des pages ou des fonctionnalités. Mon rôle est de comprendre votre besoin, de structurer une solution réaliste et de construire un outil fiable que vous pourrez réellement utiliser.", + "arg1Title": "Vision produit", + "arg1Text": "Je vous aide à clarifier le besoin avant de développer.", + "arg2Title": "Profil full stack", + "arg2Text": "Interface, logique métier, base de données, API, déploiement.", + "arg3Title": "Solutions durables", + "arg3Text": "Code maintenable, structure propre, évolution possible.", + "arg4Title": "Communication claire", + "arg4Text": "Explications accessibles, même sans profil technique." + }, + "cta": { + "title": "Vous avez un projet ou un besoin spécifique ?", + "text": "Expliquez-moi simplement ce que vous souhaitez construire, améliorer ou automatiser. Je vous aiderai à clarifier la meilleure approche.", + "button": "Me contacter", + "note1": "Réponse sous quelques jours.", + "note2": "Premier échange sans engagement." + } } -} \ No newline at end of file +}