apply translations in all pages
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import HeroSection from "@/components/sections/HeroSection";
|
import HeroSection from "@/components/sections/HeroSection";
|
||||||
import WorksSection from "@/components/sections/WorksSection";
|
import WorksSection from "@/components/sections/WorksSection";
|
||||||
import FeatureSection from "@/components/sections/FeatureSection";
|
import ApproachSection from "@/components/sections/AppoachSection";
|
||||||
import TimelineSection from "@/components/sections/AboutSection";
|
import TimelineSection from "@/components/sections/AboutSection";
|
||||||
|
|
||||||
import { getAllProjects } from "@/lib/projects/get-projects";
|
import { getAllProjects } from "@/lib/projects/get-projects";
|
||||||
@@ -20,7 +20,7 @@ export default async function Home({ params }: props) {
|
|||||||
<HeroSection />
|
<HeroSection />
|
||||||
<div className="px-8 md:px-14 lg:px-20">
|
<div className="px-8 md:px-14 lg:px-20">
|
||||||
<WorksSection projects={projects} locale={locale} />
|
<WorksSection projects={projects} locale={locale} />
|
||||||
<FeatureSection />
|
<ApproachSection />
|
||||||
<TimelineSection />
|
<TimelineSection />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { FiFacebook, FiInstagram, FiLinkedin, FiGithub } from "react-icons/fi";
|
import { FiFacebook, FiInstagram, FiLinkedin, FiGithub } from "react-icons/fi";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
|
const t = useTranslations('footer');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="w-[calc(100vw - 11px*2)] bg-brand-brown rounded-t-[10px] mx-2.75 flex flex-col items-center justify-center py-15 text-brand-beige gap-6">
|
<footer className="w-[calc(100vw - 11px*2)] bg-brand-brown rounded-t-[10px] mx-2.75 flex flex-col items-center justify-center py-15 text-brand-beige gap-6">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
@@ -9,13 +12,13 @@ export default function Footer() {
|
|||||||
<div className="flex flex-col lg:flex-row justify-center items-start gap-16">
|
<div className="flex flex-col lg:flex-row justify-center items-start gap-16">
|
||||||
<div className="flex flex-col items-start justify-start gap-4">
|
<div className="flex flex-col items-start justify-start gap-4">
|
||||||
<h3 className="font-serif text-3xl">Mathéo Guilbert EI</h3>
|
<h3 className="font-serif text-3xl">Mathéo Guilbert EI</h3>
|
||||||
<p className="font-sans text-base">Développeur Full Stack & Architecte Web</p>
|
<p className="font-sans text-base">{t('me')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-start justify-start gap-4">
|
<div className="flex flex-col items-start justify-start gap-4">
|
||||||
<h3 className="font-serif text-3xl">Légal</h3>
|
<h3 className="font-serif text-3xl">{t('legal.title')}</h3>
|
||||||
<p className="font-sans text-base">Mentions Légales</p>
|
<p className="font-sans text-base">{t('legal.legalNotice')}</p>
|
||||||
<p className="font-sans text-base">Conditions Générales de Vente</p>
|
<p className="font-sans text-base">{t('legal.termsAndConditions')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-start justify-start gap-4">
|
<div className="flex flex-col items-start justify-start gap-4">
|
||||||
@@ -32,7 +35,7 @@ export default function Footer() {
|
|||||||
<FiGithub />
|
<FiGithub />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-brand-beige/70 font-sans text-sm">© 2026 M. Guilbert. Tous droits réservés.</p>
|
<p className="text-brand-beige/70 font-sans text-sm">{t('copyright')}</p>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import React, { useState, useEffect, MouseEvent } from "react";
|
|||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams, usePathname } from "next/navigation";
|
||||||
import { CiMenuFries } from "react-icons/ci";
|
import { CiMenuFries } from "react-icons/ci";
|
||||||
import { IoCloseOutline } from "react-icons/io5";
|
import { IoCloseOutline } from "react-icons/io5";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import LanguageSwitcher from "../ui/LanguageSwitcher";
|
import LanguageSwitcher from "../ui/LanguageSwitcher";
|
||||||
|
|
||||||
@@ -12,13 +13,14 @@ export default function Header() {
|
|||||||
const params = useParams<{ locale?: string }>();
|
const params = useParams<{ locale?: string }>();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const locale = params?.locale ?? "fr";
|
const locale = params?.locale ?? "fr";
|
||||||
|
const t = useTranslations('header');
|
||||||
const homePath = `/${locale}`;
|
const homePath = `/${locale}`;
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{ text: "Accueil", target: homePath },
|
{ text: t('home'), target: homePath },
|
||||||
{ text: "Travaux", target: `${homePath}#works` },
|
{ text: t('works'), target: `${homePath}#works` },
|
||||||
{ text: "A propos", target: `${homePath}#about` },
|
{ text: t('about'), target: `${homePath}#about` },
|
||||||
{ text: "Blog", target: `${homePath}/blog` },
|
{ text: t('blog'), target: `${homePath}/blog` },
|
||||||
];
|
];
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|||||||
@@ -1,37 +1,39 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useRef, useEffect, useState } from 'react';
|
import { useRef, useEffect, useState } from 'react';
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export default function TimelineSection() {
|
||||||
|
const sectionRef = useRef<HTMLElement>(null);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const t = useTranslations('about');
|
||||||
|
|
||||||
|
const lineTop = '58%';
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
title: 'Les Débuts',
|
title: t('step1.title'),
|
||||||
text: "J'ai commencé par le web en 2019 suite à un stage, je n'ai plus jamais arrêté. Aujourd'hui je veux en faire mon métier.",
|
text: t('step1.text'),
|
||||||
year: '2019',
|
year: '2019',
|
||||||
position: 'top' as const,
|
position: 'top' as const,
|
||||||
left: '16%',
|
left: '16%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'L\'Évolution',
|
title: t('step2.title'),
|
||||||
text: "Après un bac STI2D avec mention Très bien j'ai entamé un BUT Informatique Parcours Développement IA.",
|
text: t('step2.text'),
|
||||||
year: '2022',
|
year: '2022',
|
||||||
position: 'bottom' as const,
|
position: 'bottom' as const,
|
||||||
left: '50%',
|
left: '50%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Aujourd\'hui',
|
title: t('step3.title'),
|
||||||
text: "Je suis entrepreneur, co-fondateur d'une startup et développeur freelance, je vis de ma passion.",
|
text: t('step3.text'),
|
||||||
year: '2026',
|
year: '2026',
|
||||||
position: 'top' as const,
|
position: 'top' as const,
|
||||||
left: '84%',
|
left: '84%',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const lineTop = '58%';
|
|
||||||
|
|
||||||
export default function TimelineSection() {
|
|
||||||
const sectionRef = useRef<HTMLElement>(null);
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const section = sectionRef.current;
|
const section = sectionRef.current;
|
||||||
if (!section) return;
|
if (!section) return;
|
||||||
@@ -56,11 +58,11 @@ export default function TimelineSection() {
|
|||||||
{/* Label */}
|
{/* Label */}
|
||||||
<div className="reveal flex items-center gap-3 mb-6">
|
<div className="reveal flex items-center gap-3 mb-6">
|
||||||
<span className="w-8 h-px bg-brand-brown/40" />
|
<span className="w-8 h-px bg-brand-brown/40" />
|
||||||
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">Parcours</span>
|
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">{t('sectionLabel')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="reveal font-serif text-[clamp(48px,7vw,100px)] leading-none text-brand-brown mb-16 md:mb-24">
|
<h2 className="reveal font-serif text-[clamp(48px,7vw,100px)] leading-none text-brand-brown mb-16 md:mb-24">
|
||||||
À propos
|
{t('title')}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Mobile layout */}
|
{/* Mobile layout */}
|
||||||
|
|||||||
@@ -1,29 +1,30 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useRef, useEffect } from 'react';
|
import { useRef, useEffect } from 'react';
|
||||||
import { FeatureColumn } from '@/components/ui/FeatureColumn';
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export default function ApproachSection() {
|
||||||
|
const sectionRef = useRef<HTMLElement>(null);
|
||||||
|
const t = useTranslations('approach');
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{
|
{
|
||||||
imagePath: '/icons/brain.svg',
|
imagePath: '/icons/brain.svg',
|
||||||
title: 'Plus qu\'un simple exécutant',
|
title: t('feature1.title'),
|
||||||
text: 'Grâce à mon background en conception numérique et mobile, je ne me contente pas de coder. J\'apporte un regard critique et constructif sur l\'ensemble de votre projet.',
|
text: t('feature1.text'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
imagePath: '/icons/building.svg',
|
imagePath: '/icons/building.svg',
|
||||||
title: 'Des fondations en béton',
|
title: t('feature2.title'),
|
||||||
text: 'Ma spécialité : l\'architecture. Je construis des solutions pérennes, évolutives et capables d\'absorber la croissance de votre produit dans le temps.',
|
text: t('feature2.text'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
imagePath: '/icons/people.svg',
|
imagePath: '/icons/people.svg',
|
||||||
title: 'L\'utilisateur au centre',
|
title: t('feature3.title'),
|
||||||
text: 'Le code parfait n\'est rien si le produit ne répond pas à un besoin. Je place l\'expérience utilisateur et la fonctionnalité au cœur de mes développements.',
|
text: t('feature3.text'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function FeatureSection() {
|
|
||||||
const sectionRef = useRef<HTMLElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const section = sectionRef.current;
|
const section = sectionRef.current;
|
||||||
if (!section) return;
|
if (!section) return;
|
||||||
@@ -51,7 +52,7 @@ export default function FeatureSection() {
|
|||||||
{/* Section label */}
|
{/* Section label */}
|
||||||
<div className="reveal flex items-center gap-3 mb-12 md:mb-16">
|
<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="w-8 h-px bg-brand-brown/40" />
|
||||||
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">Approche</span>
|
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">{t('sectionLabel')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 md:gap-6 lg:gap-16">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 md:gap-6 lg:gap-16">
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
export default function HeroSection() {
|
export default function HeroSection() {
|
||||||
const sectionRef = useRef<HTMLElement>(null);
|
const sectionRef = useRef<HTMLElement>(null);
|
||||||
|
const t = useTranslations('hero');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const section = sectionRef.current;
|
const section = sectionRef.current;
|
||||||
@@ -36,7 +38,7 @@ export default function HeroSection() {
|
|||||||
>
|
>
|
||||||
<span className="inline-flex items-center gap-2 font-sans text-xs uppercase tracking-[0.2em] text-brand-beige/50">
|
<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" />
|
<span className="w-6 h-px bg-brand-beige/30" />
|
||||||
Développeur Full Stack & Architecte Web
|
{t('sectionLabel')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -46,9 +48,7 @@ export default function HeroSection() {
|
|||||||
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)' }}
|
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-[0.95] text-[clamp(36px,5.5vw,88px)] text-brand-beige">
|
<h1 className="font-serif leading-[0.95] text-[clamp(36px,5.5vw,88px)] text-brand-beige">
|
||||||
Ensemble,<br />
|
{t('title')}
|
||||||
<em className="not-italic text-brand-beige/70">construisons</em><br />
|
|
||||||
ce qui dure.
|
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -58,8 +58,7 @@ export default function HeroSection() {
|
|||||||
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)' }}
|
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">
|
<p className="font-sans text-base font-light text-brand-beige/60 max-w-md leading-relaxed">
|
||||||
Je conçois des architectures robustes, scalables et pérennes —
|
{t('subtitle')}
|
||||||
des fondations qui supportent la croissance de votre produit dans le temps.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ export default function HeroSection() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="relative">
|
<span className="relative">
|
||||||
Voir mes travaux
|
{t('cta')}
|
||||||
<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 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>
|
</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">
|
<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">
|
||||||
@@ -135,7 +134,7 @@ export default function HeroSection() {
|
|||||||
animationDelay: '1s'
|
animationDelay: '1s'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="font-sans text-[10px] uppercase tracking-[0.15em]">Défiler</span>
|
<span className="font-sans text-[10px] uppercase tracking-[0.15em]">{t('scrollPrompt')}</span>
|
||||||
<svg
|
<svg
|
||||||
className="w-4 h-4"
|
className="w-4 h-4"
|
||||||
style={{ animation: 'arrowBounce 1.8s ease-in-out infinite' }}
|
style={{ animation: 'arrowBounce 1.8s ease-in-out infinite' }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useRef, useEffect } from 'react';
|
import { useRef, useEffect } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
type props = {
|
type props = {
|
||||||
locale: string;
|
locale: string;
|
||||||
@@ -15,6 +16,7 @@ type props = {
|
|||||||
|
|
||||||
export function OtherProjects({ locale, projects }: props) {
|
export function OtherProjects({ locale, projects }: props) {
|
||||||
const ref = useRef<HTMLElement>(null);
|
const ref = useRef<HTMLElement>(null);
|
||||||
|
const t = useTranslations('projectPage.otherProjects');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = ref.current;
|
const el = ref.current;
|
||||||
@@ -45,12 +47,12 @@ export function OtherProjects({ locale, projects }: props) {
|
|||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<span className="w-6 h-px bg-brand-brown/40" />
|
<span className="w-6 h-px bg-brand-brown/40" />
|
||||||
<span className="font-sans text-[10px] uppercase tracking-[0.2em] text-brand-brown/40">
|
<span className="font-sans text-[10px] uppercase tracking-[0.2em] text-brand-brown/40">
|
||||||
Autres projets
|
{t('sectionLabel')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="reveal font-serif text-[clamp(48px,6vw,80px)] leading-none text-brand-brown mb-12">
|
<h2 className="reveal font-serif text-[clamp(48px,6vw,80px)] leading-none text-brand-brown mb-12">
|
||||||
Voir aussi
|
{t('title')}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
type props = {
|
type props = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -13,6 +14,7 @@ type props = {
|
|||||||
|
|
||||||
export function ProjectHero({ title, description, dateLabel, locale, cover }: props) {
|
export function ProjectHero({ title, description, dateLabel, locale, cover }: props) {
|
||||||
const ref = useRef<HTMLElement>(null);
|
const ref = useRef<HTMLElement>(null);
|
||||||
|
const t = useTranslations('projectPage.hero');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const section = ref.current;
|
const section = ref.current;
|
||||||
@@ -46,7 +48,7 @@ export function ProjectHero({ title, description, dateLabel, locale, cover }: pr
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Back link */}
|
{/* Back link */}
|
||||||
<div className="relative z-10 px-8 md:px-14 lg:px-20 pt-10">
|
<div className="relative z-10 px-8 md:px-14 lg:px-20 pt-30">
|
||||||
<div
|
<div
|
||||||
className="hero-reveal"
|
className="hero-reveal"
|
||||||
style={{ opacity: 0, transform: 'translateY(20px)', transition: 'opacity 500ms ease, transform 500ms cubic-bezier(0.22,1,0.36,1)' }}
|
style={{ opacity: 0, transform: 'translateY(20px)', transition: 'opacity 500ms ease, transform 500ms cubic-bezier(0.22,1,0.36,1)' }}
|
||||||
@@ -64,7 +66,7 @@ export function ProjectHero({ title, description, dateLabel, locale, cover }: pr
|
|||||||
>
|
>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 16l-4-4m0 0l4-4m-4 4h18" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 16l-4-4m0 0l4-4m-4 4h18" />
|
||||||
</svg>
|
</svg>
|
||||||
Retour aux projets
|
{t('backToProjects')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,7 +109,7 @@ export function ProjectHero({ title, description, dateLabel, locale, cover }: pr
|
|||||||
{/* Scroll indicator */}
|
{/* Scroll indicator */}
|
||||||
<div className="relative z-10 flex justify-center pb-8 text-brand-beige/30">
|
<div className="relative z-10 flex justify-center pb-8 text-brand-beige/30">
|
||||||
<div className="flex flex-col items-center gap-1.5">
|
<div className="flex flex-col items-center gap-1.5">
|
||||||
<span className="font-sans text-[10px] uppercase tracking-[0.15em]">Défiler</span>
|
<span className="font-sans text-[10px] uppercase tracking-[0.15em]">{t('scrollPrompt')}</span>
|
||||||
<svg
|
<svg
|
||||||
className="w-4 h-4"
|
className="w-4 h-4"
|
||||||
style={{ animation: 'arrowBounce 1.8s ease-in-out infinite' }}
|
style={{ animation: 'arrowBounce 1.8s ease-in-out infinite' }}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useRef, useEffect } from 'react';
|
import { useRef, useEffect } from 'react';
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
type props = {
|
type props = {
|
||||||
dateLabel: string;
|
dateLabel: string;
|
||||||
@@ -24,6 +25,7 @@ function MetaItem({ label, children }: { label: string; children: React.ReactNod
|
|||||||
|
|
||||||
export function ProjectMeta({ dateLabel, category, roles, client }: props) {
|
export function ProjectMeta({ dateLabel, category, roles, client }: props) {
|
||||||
const ref = useRef<HTMLElement>(null);
|
const ref = useRef<HTMLElement>(null);
|
||||||
|
const t = useTranslations('projectPage.meta');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = ref.current;
|
const el = ref.current;
|
||||||
@@ -47,16 +49,16 @@ export function ProjectMeta({ dateLabel, category, roles, client }: props) {
|
|||||||
<div className="w-full h-px bg-brand-brown/10 mb-10" />
|
<div className="w-full h-px bg-brand-brown/10 mb-10" />
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-8 md:grid-cols-4 md:gap-12">
|
<div className="grid grid-cols-2 gap-8 md:grid-cols-4 md:gap-12">
|
||||||
<MetaItem label="Année">{dateLabel}</MetaItem>
|
<MetaItem label={t('year')}>{dateLabel}</MetaItem>
|
||||||
<MetaItem label="Catégorie">{category}</MetaItem>
|
<MetaItem label={t('category')}>{category}</MetaItem>
|
||||||
<MetaItem label="Rôles">
|
<MetaItem label={t('roles')}>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
{roles.map((role) => (
|
{roles.map((role) => (
|
||||||
<span key={role}>{role}</span>
|
<span key={role}>{role}</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</MetaItem>
|
</MetaItem>
|
||||||
<MetaItem label="Client">{client}</MetaItem>
|
<MetaItem label={t('client')}>{client}</MetaItem>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom divider */}
|
{/* Bottom divider */}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useRef, useEffect } from 'react';
|
import { useRef, useEffect } from 'react';
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
type props = {
|
type props = {
|
||||||
technologies: string[];
|
technologies: string[];
|
||||||
@@ -8,6 +9,7 @@ type props = {
|
|||||||
|
|
||||||
export function ProjectTechnologies({ technologies }: props) {
|
export function ProjectTechnologies({ technologies }: props) {
|
||||||
const ref = useRef<HTMLElement>(null);
|
const ref = useRef<HTMLElement>(null);
|
||||||
|
const t = useTranslations('projectPage.technologies');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = ref.current;
|
const el = ref.current;
|
||||||
@@ -32,7 +34,7 @@ export function ProjectTechnologies({ technologies }: props) {
|
|||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<span className="w-6 h-px bg-brand-brown/40" />
|
<span className="w-6 h-px bg-brand-brown/40" />
|
||||||
<span className="font-sans text-[10px] uppercase tracking-[0.2em] text-brand-brown/40">
|
<span className="font-sans text-[10px] uppercase tracking-[0.2em] text-brand-brown/40">
|
||||||
Technologies
|
{t('sectionLabel')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useRef, useEffect } from 'react';
|
import { useRef, useEffect } from 'react';
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import { Project } from "@/lib/projects/types";
|
import { Project } from "@/lib/projects/types";
|
||||||
import { ProjectCard } from "../ui/ProjectCard";
|
import { ProjectCard } from "../ui/ProjectCard";
|
||||||
|
|
||||||
@@ -11,6 +13,7 @@ type props = {
|
|||||||
|
|
||||||
export default function WorksSection({ projects, locale }: props) {
|
export default function WorksSection({ projects, locale }: props) {
|
||||||
const sectionRef = useRef<HTMLElement>(null);
|
const sectionRef = useRef<HTMLElement>(null);
|
||||||
|
const t = useTranslations('works');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const section = sectionRef.current;
|
const section = sectionRef.current;
|
||||||
@@ -39,11 +42,11 @@ export default function WorksSection({ projects, locale }: props) {
|
|||||||
{/* Section label */}
|
{/* Section label */}
|
||||||
<div className="reveal flex items-center gap-3 mb-4">
|
<div className="reveal flex items-center gap-3 mb-4">
|
||||||
<span className="w-8 h-px bg-brand-brown/40" />
|
<span className="w-8 h-px bg-brand-brown/40" />
|
||||||
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">Sélection de projets</span>
|
<span className="font-sans text-[11px] uppercase tracking-[0.2em] text-brand-brown/45">{t('sectionLabel')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="reveal font-serif text-[clamp(56px,8vw,112px)] text-brand-brown leading-none mb-12 md:mb-16">
|
<h2 className="reveal font-serif text-[clamp(56px,8vw,112px)] text-brand-brown leading-none mb-12 md:mb-16">
|
||||||
Travaux
|
{t('title')}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
type props = {
|
|
||||||
imagePath: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
delay?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FeatureColumn = ({ imagePath, title, text, delay = 0 }: props) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="reveal flex flex-col items-center justify-start max-w-[320px] gap-6"
|
|
||||||
style={{ transitionDelay: `${delay}ms` }}
|
|
||||||
>
|
|
||||||
{/* Icon container */}
|
|
||||||
<div className="w-20 h-20 rounded-2xl bg-brand-brown/6 flex items-center justify-center group-hover:bg-brand-brown/10 transition-colors duration-300">
|
|
||||||
<img
|
|
||||||
className="w-10 h-10"
|
|
||||||
src={imagePath}
|
|
||||||
alt=""
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="font-serif text-brand-brown text-center leading-[1.1] text-[clamp(1.5rem,2.5vw,1.75rem)]">
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="font-sans text-sm text-center text-brand-brown/65 leading-relaxed">
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { useRef, useEffect } from 'react';
|
import { useRef, useEffect } from 'react';
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import { Project } from "@/lib/projects/types";
|
import { Project } from "@/lib/projects/types";
|
||||||
|
|
||||||
type props = {
|
type props = {
|
||||||
@@ -14,6 +16,7 @@ type props = {
|
|||||||
export const ProjectCard = ({ project, locale, isTheLast = false, index = 0 }: props) => {
|
export const ProjectCard = ({ project, locale, isTheLast = false, index = 0 }: props) => {
|
||||||
const num = String(index + 1).padStart(2, '0');
|
const num = String(index + 1).padStart(2, '0');
|
||||||
const cardRef = useRef<HTMLDivElement>(null);
|
const cardRef = useRef<HTMLDivElement>(null);
|
||||||
|
const t = useTranslations('projectCard');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = cardRef.current;
|
const el = cardRef.current;
|
||||||
@@ -85,7 +88,7 @@ export const ProjectCard = ({ project, locale, isTheLast = false, index = 0 }: p
|
|||||||
|
|
||||||
{/* CTA */}
|
{/* CTA */}
|
||||||
<div className="flex items-center gap-2 font-sans text-xs uppercase tracking-[0.18em] text-brand-brown border-b border-brand-brown/30 pb-0.5 group-hover:border-brand-brown transition-colors duration-200">
|
<div className="flex items-center gap-2 font-sans text-xs uppercase tracking-[0.18em] text-brand-brown border-b border-brand-brown/30 pb-0.5 group-hover:border-brand-brown transition-colors duration-200">
|
||||||
<span>Lire le cas</span>
|
<span>{t('cta')}</span>
|
||||||
<svg
|
<svg
|
||||||
className="w-3.5 h-3.5 group-hover:translate-x-1.5 transition-transform duration-200"
|
className="w-3.5 h-3.5 group-hover:translate-x-1.5 transition-transform duration-200"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|||||||
+3
-5
@@ -7,11 +7,7 @@
|
|||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
"sectionLabel": "Full Stack Developer & Web Architect",
|
"sectionLabel": "Full Stack Developer & Web Architect",
|
||||||
"title": [
|
"title": "Together, let's build something that lasts.",
|
||||||
"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"
|
||||||
@@ -55,6 +51,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
|
"me": "Full Stack Developer & Web Architect",
|
||||||
"legal": {
|
"legal": {
|
||||||
"title": "Legal",
|
"title": "Legal",
|
||||||
"legalNotice": "Legal Notice",
|
"legalNotice": "Legal Notice",
|
||||||
@@ -64,6 +61,7 @@
|
|||||||
},
|
},
|
||||||
"projectPage": {
|
"projectPage": {
|
||||||
"hero": {
|
"hero": {
|
||||||
|
"backToProjects": "Back to projects",
|
||||||
"scrollPrompt": "Scroll"
|
"scrollPrompt": "Scroll"
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
|
|||||||
+3
-5
@@ -7,11 +7,7 @@
|
|||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
"sectionLabel": "Développeur Full Stack & Architecte Web",
|
"sectionLabel": "Développeur Full Stack & Architecte Web",
|
||||||
"title": [
|
"title": "Ensemble, construisons quelque chose de durable.",
|
||||||
"Ensemble,",
|
|
||||||
"construisons",
|
|
||||||
"ce qui dure."
|
|
||||||
],
|
|
||||||
"subtitle": "Je conçois des architectures robustes, scalables et pérennes — des fondations qui supportent la croissance de votre produit dans le temps.",
|
"subtitle": "Je conçois des architectures robustes, scalables et pérennes — des fondations qui supportent la croissance de votre produit dans le temps.",
|
||||||
"cta": "Voir mes travaux",
|
"cta": "Voir mes travaux",
|
||||||
"scrollPrompt": "Défiler"
|
"scrollPrompt": "Défiler"
|
||||||
@@ -55,6 +51,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
|
"me": "Développeur Full Stack & Architecte Web",
|
||||||
"legal": {
|
"legal": {
|
||||||
"title": "Légal",
|
"title": "Légal",
|
||||||
"legalNotice": "Mentions Légales",
|
"legalNotice": "Mentions Légales",
|
||||||
@@ -64,6 +61,7 @@
|
|||||||
},
|
},
|
||||||
"projectPage": {
|
"projectPage": {
|
||||||
"hero": {
|
"hero": {
|
||||||
|
"backToProjects": "Retour aux projets",
|
||||||
"scrollPrompt": "Défiler"
|
"scrollPrompt": "Défiler"
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
|
|||||||
Reference in New Issue
Block a user