polish: complete copy rewrite + i18n fixes + dead link cleanup
All checks were successful
Build & Push / build-and-push (push) Successful in 1m47s

- Rewrite all site copy based on founder interview: honest, outcome-focused,
  premium but approachable tone
- Hero: "Websites, software, and infrastructure — designed and built entirely around you."
- Services: "Design. Build. Run." with clear pillars (Websites, Custom Software, Infrastructure)
- Philosophy: "Your tools should belong to you." — removed Kubernetes reference
- Trust bar: concrete value props (Built From Scratch, You Own Everything, One Team, AI)
- CTA: "Ready to build something?" — direct, no fluff
- Projects: honest descriptions of what was actually delivered
- Fix all French translation gaps: hero accent word, configurator cards,
  project descriptions, footer links, philosophy quote
- Fix dead links: footer service links → /#services, work/about → anchors
- Restore case study links (dynamic route exists)
- Fix mobile hero padding (pt-40 → pt-24)
- AI narrative positioned honestly as add-on capability

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 16:12:50 +01:00
parent b2f9fd3222
commit 683e0b3e43
8 changed files with 309 additions and 192 deletions

View File

@@ -80,7 +80,7 @@ export default function Configurator() {
className="flex flex-col gap-3 pt-2"
>
<p className="text-xs font-semibold uppercase tracking-label text-outline/70">
How it works
{t('howItWorks')}
</p>
{/* Vertical accent line + steps */}
<div className="flex gap-4">
@@ -99,7 +99,7 @@ export default function Configurator() {
className="pt-1 flex items-center gap-2"
>
<ShieldCheck size={14} strokeWidth={1.75} className="text-primary flex-shrink-0" aria-hidden="true" />
<p className="text-xs text-outline">No commitment required</p>
<p className="text-xs text-outline">{t('noCommitment')}</p>
</motion.div>
</motion.div>
</div>

View File

@@ -79,10 +79,9 @@ const rightColumnVariant = {
export default function Hero() {
const t = useTranslations('hero');
// Split the raw title on the {exclusively} placeholder
// Expected translation: "Built {exclusively} for ambitious brands."
// Split the raw title on the {accentWord} placeholder
const rawTitle: string = t.raw('title');
const parts = rawTitle.split('{exclusively}');
const parts = rawTitle.split('{accentWord}');
const before = parts[0] ?? '';
const after = parts[1] ?? '';
@@ -99,7 +98,7 @@ export default function Hero() {
: [];
const allWords: WordItem[] = [
...beforeWords,
{ type: 'accent', text: 'exclusively' },
{ type: 'accent', text: t('accentWord') },
...afterWords,
];
@@ -128,7 +127,7 @@ export default function Hero() {
<div className="relative z-10 w-full max-w-screen-xl mx-auto px-6 lg:px-12 xl:px-16 flex flex-col lg:flex-row lg:items-center min-h-screen">
{/* ── LEFT COLUMN — text content (55% on desktop) ─────────────── */}
<div className="flex-1 lg:max-w-[58%] flex flex-col justify-center pt-40 pb-16 lg:pt-28 lg:pb-0">
<div className="flex-1 lg:max-w-[58%] flex flex-col justify-center pt-24 pb-16 lg:pt-28 lg:pb-0">
{/* Headline — word-by-word stagger */}
<motion.h1
@@ -136,7 +135,7 @@ export default function Hero() {
variants={headlineContainer}
initial="hidden"
animate="visible"
aria-label={rawTitle.replace('{exclusively}', 'exclusively')}
aria-label={rawTitle.replace('{accentWord}', t('accentWord'))}
>
{allWords.map((word, i) =>
word.type === 'accent' ? (

View File

@@ -421,7 +421,7 @@ export default function Philosophy() {
/>
{/* Attribution */}
<p className="label-md text-outline">Founded on the Côte d&rsquo;Azur</p>
<p className="label-md text-outline">{t('philosophy.foundedLocation')}</p>
</motion.div>
</div>

View File

@@ -15,46 +15,44 @@ import { Lock, Clock, ArrowRight } from 'lucide-react';
// ─── Types ────────────────────────────────────────────────────────────────────
interface Project {
title: string;
description: string;
tags: string[];
/** i18n key under "work.projects" (e.g. "monaco") */
i18nKey: string;
slug: string;
/** number of tags to resolve from the translation array */
tagCount: number;
featured?: boolean;
}
interface ComingSoonItem {
title: string;
subtitle: string;
/** i18n key under "work.comingSoonProjects" (e.g. "riviera") */
i18nKey: string;
confidential?: boolean;
}
// ─── Data ──────────────────────────────────────────────────────────────────────
const PROJECTS: Project[] = [
{
title: 'Monaco Ocean Protection Challenge',
description:
"A comprehensive judging and analytics system with advanced AI jury integration for one of the Mediterranean's most prestigious conservation events.",
tags: ['AI Integration', 'Platform'],
i18nKey: 'monaco',
slug: 'monaco-ocean',
tagCount: 2,
featured: true,
},
{
title: 'Port Nimara',
description: 'Scalable digital hub for maritime logistics.',
tags: ['Website', 'Infrastructure'],
i18nKey: 'portNimara',
slug: 'port-nimara',
tagCount: 2,
},
{
title: 'Port Amador',
description: 'Premium digital experience for elite nautical services.',
tags: ['Website', 'Infrastructure'],
i18nKey: 'portAmador',
slug: 'port-amador',
tagCount: 2,
},
];
const COMING_SOON: ComingSoonItem[] = [
{ title: 'Confidential Riviera Project', subtitle: 'Coming Soon' },
{ title: 'Sophia Antipolis AI Startup', subtitle: 'Launching Q4' },
{ i18nKey: 'riviera', confidential: true },
{ i18nKey: 'sophia' },
];
// ─── Animation Variants ───────────────────────────────────────────────────────
@@ -371,7 +369,11 @@ function TagChip({ label, showDot = false }: { label: string; showDot?: boolean
// ─── Featured Card ────────────────────────────────────────────────────────────
function FeaturedCard({ project, readLabel }: { project: Project; readLabel: string }) {
function FeaturedCard({ project, readLabel, t }: { project: Project; readLabel: string; t: (key: string) => string }) {
const tags = Array.from({ length: project.tagCount }, (_, i) =>
t(`work.projects.${project.i18nKey}.tags.${i}`),
);
return (
<motion.article
variants={featuredCardVariants}
@@ -392,24 +394,24 @@ function FeaturedCard({ project, readLabel }: { project: Project; readLabel: str
<div className="flex flex-col flex-1 p-7 gap-4">
{/* Tags */}
<div className="flex flex-wrap gap-2">
{project.tags.map((tag, i) => (
{tags.map((tag, i) => (
<TagChip key={tag} label={tag} showDot={i > 0} />
))}
</div>
{/* Title */}
<h3 className="font-serif text-2xl font-semibold text-on-surface leading-snug">
{project.title}
{t(`work.projects.${project.i18nKey}.title`)}
</h3>
{/* Description */}
<p className="text-sm text-outline leading-relaxed flex-1">
{project.description}
{t(`work.projects.${project.i18nKey}.description`)}
</p>
{/* CTA */}
<Link
href={`/work/${project.slug}`}
href={`/work/${project.slug}` as any}
className={cn(
'inline-flex items-center gap-2 text-sm font-medium text-primary-dark',
'mt-1 group/link',
@@ -435,8 +437,11 @@ const SLUG_TO_VARIANT: Record<string, 'nimara' | 'amador'> = {
'port-amador': 'amador',
};
function SmallCard({ project, readLabel }: { project: Project; readLabel: string }) {
function SmallCard({ project, readLabel, t }: { project: Project; readLabel: string; t: (key: string) => string }) {
const cardVariant = SLUG_TO_VARIANT[project.slug] ?? 'nimara';
const tags = Array.from({ length: project.tagCount }, (_, i) =>
t(`work.projects.${project.i18nKey}.tags.${i}`),
);
return (
<motion.article
@@ -469,24 +474,24 @@ function SmallCard({ project, readLabel }: { project: Project; readLabel: string
<div className="flex flex-col flex-1 p-5 gap-3">
{/* Tags */}
<div className="flex flex-wrap gap-1.5">
{project.tags.map((tag, i) => (
{tags.map((tag, i) => (
<TagChip key={tag} label={tag} showDot={i > 0} />
))}
</div>
{/* Title */}
<h3 className="font-serif text-lg font-semibold text-on-surface leading-snug">
{project.title}
{t(`work.projects.${project.i18nKey}.title`)}
</h3>
{/* Description */}
<p className="text-xs text-outline leading-relaxed flex-1">
{project.description}
{t(`work.projects.${project.i18nKey}.description`)}
</p>
{/* CTA */}
<Link
href={`/work/${project.slug}`}
href={`/work/${project.slug}` as any}
className="inline-flex items-center gap-1.5 text-xs font-medium text-primary-dark group/link"
>
<span className="relative after:absolute after:bottom-0 after:left-0 after:h-px after:w-full after:bg-primary-dark after:origin-left after:scale-x-100 after:transition-transform after:duration-200">
@@ -504,9 +509,8 @@ function SmallCard({ project, readLabel }: { project: Project; readLabel: string
// ─── Coming Soon Card ─────────────────────────────────────────────────────────
function ComingSoonCard({ item }: { item: ComingSoonItem }) {
const isConfidential = item.subtitle === 'Coming Soon';
const Icon = isConfidential ? Lock : Clock;
function ComingSoonCard({ item, t }: { item: ComingSoonItem; t: (key: string) => string }) {
const Icon = item.confidential ? Lock : Clock;
return (
<motion.div
@@ -533,9 +537,11 @@ function ComingSoonCard({ item }: { item: ComingSoonItem }) {
/>
<div>
<p className="font-serif text-base font-medium text-on-surface/60 leading-snug">
{item.title}
{t(`work.comingSoonProjects.${item.i18nKey}.title`)}
</p>
<p className="label-md text-outline/70 mt-1">
{t(`work.comingSoonProjects.${item.i18nKey}.subtitle`)}
</p>
<p className="label-md text-outline/70 mt-1">{item.subtitle}</p>
</div>
</div>
</motion.div>
@@ -609,6 +615,7 @@ export default function SelectedWorks() {
<FeaturedCard
project={featuredProject}
readLabel={t('work.readCaseStudy')}
t={t}
/>
</div>
@@ -619,6 +626,7 @@ export default function SelectedWorks() {
key={project.slug}
project={project}
readLabel={t('work.readCaseStudy')}
t={t}
/>
))}
</div>
@@ -633,7 +641,7 @@ export default function SelectedWorks() {
className="grid grid-cols-1 sm:grid-cols-2 gap-5"
>
{COMING_SOON.map((item) => (
<ComingSoonCard key={item.title} item={item} />
<ComingSoonCard key={item.i18nKey} item={item} t={t} />
))}
</motion.div>