add freelance page components and localization for English and French

This commit is contained in:
2026-06-02 14:24:06 +02:00
parent 198e5e8a31
commit cbbfa81d7e
5 changed files with 565 additions and 14 deletions
+5 -1
View File
@@ -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)"
]
}
}
+392
View File
@@ -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<HTMLElement>(null);
const belowFoldRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const section = heroRef.current;
if (!section) return;
const items = section.querySelectorAll<HTMLElement>('.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<HTMLElement>('[data-reveal-section]');
const observers: IntersectionObserver[] = [];
sections.forEach((section) => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.querySelectorAll<HTMLElement>('.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 (
<main className="bg-brand-beige text-brand-brown flex flex-col">
{/* ── Hero ── */}
<section
ref={heroRef}
className="w-full min-h-[calc(100vh-(2*11px))] rounded-[10px] bg-brand-brown text-brand-beige flex flex-col overflow-hidden"
>
<div className="shrink-0" style={{ height: 'clamp(120px, 15vh, 160px)' }} />
<div className="flex flex-col lg:flex-row flex-1 items-center px-8 md:px-14 lg:px-20 gap-12 lg:gap-0">
{/* Left — text */}
<div className="flex flex-col justify-center lg:w-[55%] lg:pr-16 gap-8 lg:gap-10">
<div
className="hero-reveal"
style={{ opacity: 0, transform: 'translateY(28px)', transition: 'opacity 700ms cubic-bezier(0.22,1,0.36,1), transform 700ms cubic-bezier(0.22,1,0.36,1)' }}
>
<span className="inline-flex items-center gap-2 font-sans text-xs uppercase tracking-[0.2em] text-brand-beige/50">
<span className="w-6 h-px bg-brand-beige/30" />
{t('hero.label')}
</span>
</div>
<div
className="hero-reveal"
style={{ opacity: 0, transform: 'translateY(28px)', transition: 'opacity 700ms cubic-bezier(0.22,1,0.36,1), transform 700ms cubic-bezier(0.22,1,0.36,1)' }}
>
<h1 className="font-serif leading-none text-[clamp(28px,4vw,64px)] text-brand-beige">
{t('hero.title')}
</h1>
</div>
<div
className="hero-reveal"
style={{ opacity: 0, transform: 'translateY(28px)', transition: 'opacity 700ms cubic-bezier(0.22,1,0.36,1), transform 700ms cubic-bezier(0.22,1,0.36,1)' }}
>
<p className="font-sans text-base font-light text-brand-beige/60 max-w-md leading-relaxed">
{t('hero.subtitle')}
</p>
</div>
<div
className="hero-reveal flex flex-col sm:flex-row gap-4"
style={{ opacity: 0, transform: 'translateY(28px)', transition: 'opacity 700ms cubic-bezier(0.22,1,0.36,1), transform 700ms cubic-bezier(0.22,1,0.36,1)' }}
>
<a
href="mailto:matheo.guilbert49@gmail.com?subject=Projet freelance"
className="inline-flex items-center justify-center gap-2 font-sans text-sm text-brand-beige border border-brand-beige/40 rounded-lg px-6 py-3 hover:bg-brand-beige/10 transition-all duration-200"
>
{t('hero.cta1')}
</a>
<button
onClick={() => document.getElementById('works')?.scrollIntoView({ behavior: 'smooth' })}
className="group inline-flex items-center gap-2 font-sans text-sm text-brand-beige/70 hover:text-brand-beige transition-colors duration-200"
>
<span className="relative">
{t('hero.cta2')}
<span className="absolute bottom-0 left-0 w-full h-px bg-brand-beige/40 origin-left scale-x-0 group-hover:scale-x-100 transition-transform duration-300" />
</span>
<svg className="w-4 h-4 group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</button>
</div>
</div>
{/* Right — service preview cards */}
<div
className="hero-reveal flex flex-col gap-3 lg:w-[45%] lg:pl-12 w-full"
style={{ opacity: 0, transform: 'translateY(28px)', transition: 'opacity 900ms cubic-bezier(0.22,1,0.36,1), transform 900ms cubic-bezier(0.22,1,0.36,1)' }}
>
{heroCards.map((card, i) => (
<div
key={i}
className="flex items-center gap-5 border border-brand-beige/10 rounded-2xl px-6 py-5 bg-brand-beige/5 hover:bg-brand-beige/8 transition-colors duration-300 cursor-default"
>
<span className="font-sans text-xs text-brand-beige/25 tracking-widest shrink-0">{card.num}</span>
<span className="font-serif text-brand-beige text-[clamp(1rem,1.8vw,1.35rem)] leading-tight">{card.label}</span>
</div>
))}
</div>
</div>
<div className="shrink-0" style={{ height: 'clamp(60px, 8vh, 80px)' }} />
</section>
{/* ── Below fold ── */}
<div ref={belowFoldRef} className="px-8 md:px-14 lg:px-20">
{/* ── Needs ── */}
<section data-reveal-section className="w-full py-24 md:py-36">
<div className="reveal flex items-center gap-3 mb-12 md:mb-16">
<span className="w-8 h-px bg-brand-brown/40" />
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">
{t('needs.sectionLabel')}
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 md:gap-6">
{needs.map((item, i) => (
<div
key={i}
className="reveal flex flex-col gap-4 p-7 rounded-2xl border border-brand-brown/8 bg-white/40 hover:bg-white/70 hover:border-brand-brown/15 transition-all duration-300 cursor-default"
style={{ transitionDelay: `${i * 80}ms` }}
>
<span className="font-sans text-xs text-brand-brown/25 tracking-[0.2em]">
{String(i + 1).padStart(2, '0')}
</span>
<div>
<h3 className="font-serif text-brand-brown text-[clamp(1.15rem,1.8vw,1.45rem)] leading-snug mb-2">
{item.title}
</h3>
<p className="font-sans text-sm text-brand-brown/60 leading-relaxed">
{item.text}
</p>
</div>
</div>
))}
</div>
</section>
{/* ── Services — catalog grid ── */}
<section data-reveal-section className="w-full py-24 md:py-36 border-t border-brand-brown/8">
<div className="reveal flex items-center gap-3 mb-12 md:mb-16">
<span className="w-8 h-px bg-brand-brown/40" />
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">
{t('services.sectionLabel')}
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{services.map((service, i) => (
<div
key={i}
className="reveal flex flex-col gap-5 p-8 rounded-2xl border border-brand-brown/8 bg-white/40 hover:bg-white/70 hover:border-brand-brown/15 transition-all duration-300 cursor-default"
style={{ transitionDelay: `${i * 100}ms` }}
>
<span className="font-sans text-xs text-brand-brown/25 tracking-[0.2em]">
{String(i + 1).padStart(2, '0')}
</span>
<h3 className="font-serif text-brand-brown text-[clamp(1.2rem,1.8vw,1.55rem)] leading-tight">
{service.title}
</h3>
<p className="font-sans text-sm text-brand-brown/70 leading-relaxed flex-1">
{service.desc}
</p>
<div className="border-t border-brand-brown/8 pt-5 flex flex-col gap-3">
<div>
<span className="font-sans text-[11px] uppercase tracking-[0.15em] text-brand-brown/40">
{t('services.idealLabel')}
</span>
<p className="font-sans text-sm text-brand-brown/60 mt-1 leading-relaxed">
{service.ideal}
</p>
</div>
<div className="flex flex-wrap gap-1.5">
{service.examples.map((ex, j) => (
<span
key={j}
className="font-sans text-xs text-brand-brown/50 border border-brand-brown/10 rounded-full px-3 py-1 bg-brand-brown/5"
>
{ex}
</span>
))}
</div>
</div>
</div>
))}
</div>
</section>
{/* ── Method — card grid ── */}
<section data-reveal-section className="w-full py-24 md:py-36 border-t border-brand-brown/8">
<div className="reveal flex items-center gap-3 mb-12 md:mb-16">
<span className="w-8 h-px bg-brand-brown/40" />
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">
{t('method.sectionLabel')}
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
{methodSteps.map((step, i) => (
<div
key={i}
className="reveal flex flex-col gap-4 p-6 rounded-2xl border border-brand-brown/8 bg-white/40 hover:bg-white/70 hover:border-brand-brown/15 transition-all duration-300 cursor-default"
style={{ transitionDelay: `${i * 80}ms` }}
>
<span className="font-sans text-xs text-brand-brown/25 tracking-[0.2em]">
{String(i + 1).padStart(2, '0')}
</span>
<h3 className="font-serif text-brand-brown text-[clamp(1.1rem,1.4vw,1.25rem)] leading-tight">
{step.title}
</h3>
<p className="font-sans text-sm text-brand-brown/60 leading-relaxed">
{step.text}
</p>
</div>
))}
</div>
</section>
{/* ── Works — project cards ── */}
<section id="works" className="w-full py-24 md:py-36 border-t border-brand-brown/8">
<div data-reveal-section className="mb-12 md:mb-16">
<div className="reveal flex items-center gap-3">
<span className="w-8 h-px bg-brand-brown/40" />
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">
{t('works.sectionLabel')}
</span>
</div>
</div>
{projects.map((project, i) => (
<ProjectCard
key={project.slug}
project={project}
locale={locale}
index={i}
isTheLast={i === projects.length - 1}
/>
))}
</section>
{/* ── Why ── */}
<section data-reveal-section className="w-full py-24 md:py-36 border-t border-brand-brown/8">
<div className="reveal flex items-center gap-3 mb-12 md:mb-16">
<span className="w-8 h-px bg-brand-brown/40" />
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">
{t('why.sectionLabel')}
</span>
</div>
<div className="flex flex-col lg:flex-row gap-12 lg:gap-20">
{/* Left — intro text */}
<div className="reveal lg:w-2/5">
<p className="font-sans text-base text-brand-brown/70 leading-relaxed">
{t('why.intro')}
</p>
</div>
{/* Right — 2×2 arguments */}
<div className="lg:w-3/5 grid grid-cols-1 sm:grid-cols-2 gap-4">
{whyArgs.map((arg, i) => (
<div
key={i}
className="reveal flex flex-col gap-3 p-6 rounded-2xl border border-brand-brown/8 bg-white/40 hover:bg-white/70 hover:border-brand-brown/15 transition-all duration-300 cursor-default"
style={{ transitionDelay: `${i * 80}ms` }}
>
<h4 className="font-serif text-brand-brown text-lg leading-tight">{arg.title}</h4>
<p className="font-sans text-sm text-brand-brown/60 leading-relaxed">{arg.text}</p>
</div>
))}
</div>
</div>
</section>
{/* ── CTA final ── */}
<section data-reveal-section className="w-full py-24 md:py-36 border-t border-brand-brown/8">
<div className="flex justify-center">
<div className="reveal w-full max-w-2xl flex flex-col items-center text-center gap-8 p-10 md:p-14 rounded-2xl border border-brand-brown/8 bg-white/40">
<h2 className="font-serif text-brand-brown text-[clamp(1.5rem,2.8vw,2.2rem)] leading-tight">
{t('cta.title')}
</h2>
<p className="font-sans text-sm text-brand-brown/60 leading-relaxed max-w-md">
{t('cta.text')}
</p>
<div className="flex flex-col items-center gap-4">
<a
href="mailto:matheo.guilbert49@gmail.com?subject=Projet freelance"
className="inline-flex items-center gap-2 font-sans text-sm text-brand-beige bg-brand-brown rounded-lg px-8 py-3.5 hover:bg-brand-brown/90 transition-colors duration-200"
>
{t('cta.button')}
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</a>
<div className="flex flex-col gap-1">
<span className="font-sans text-xs text-brand-brown/40">{t('cta.note1')}</span>
<span className="font-sans text-xs text-brand-brown/40">{t('cta.note2')}</span>
</div>
</div>
</div>
</div>
</section>
</div>
</main>
);
}
+13 -6
View File
@@ -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 (
<div>
const slugOrder = ['MGDev', 'Thence', 'EverEastSolutions'];
const projects = slugOrder
.map((s) => getProjectBySlug(s, locale))
.filter((p): p is Project => p !== null && p.ready);
</div>
);
return <FreelanceClient projects={projects} locale={locale} />;
}
+77 -3
View File
@@ -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 products 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 dont 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 products 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."
}
}
}
+74
View File
@@ -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."
}
}
}