add freelance page components and localization for English and French
This commit is contained in:
@@ -8,7 +8,11 @@
|
|||||||
"WebFetch(domain:localhost)",
|
"WebFetch(domain:localhost)",
|
||||||
"Bash(curl -s http://localhost:3000/fr)",
|
"Bash(curl -s http://localhost:3000/fr)",
|
||||||
"Bash(npx tsc *)",
|
"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)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
const slugOrder = ['MGDev', 'Thence', 'EverEastSolutions'];
|
||||||
<div>
|
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
@@ -8,7 +8,7 @@
|
|||||||
"hero": {
|
"hero": {
|
||||||
"sectionLabel": "Full Stack Developer & Web Architect",
|
"sectionLabel": "Full Stack Developer & Web Architect",
|
||||||
"title": "Together, let's build something that lasts.",
|
"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",
|
"cta": "View my work",
|
||||||
"scrollPrompt": "Scroll"
|
"scrollPrompt": "Scroll"
|
||||||
},
|
},
|
||||||
@@ -28,11 +28,11 @@
|
|||||||
"sectionLabel": "Approach",
|
"sectionLabel": "Approach",
|
||||||
"feature1": {
|
"feature1": {
|
||||||
"title": "More than just a doer",
|
"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": {
|
"feature2": {
|
||||||
"title": "Concrete foundations",
|
"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": {
|
"feature3": {
|
||||||
"title": "User-centered",
|
"title": "User-centered",
|
||||||
@@ -99,5 +99,79 @@
|
|||||||
"visitButton": {
|
"visitButton": {
|
||||||
"text": "Visit the website"
|
"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."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,5 +99,79 @@
|
|||||||
"visitButton": {
|
"visitButton": {
|
||||||
"text": "Visiter le site"
|
"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."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user