- Accueil façon profil GitHub (bio, homelab, projets épinglés, parcours) avec fond dégradé et panneaux liquid glass - /cv : consultable en ligne (thème sombre), imprimable en PDF propre (thème clair, 1 page) ; numéro de téléphone non scrapable - Pages d'erreur 404 / 500 / global-error animées (GSAP) - Métadonnées : Open Graph, Twitter, JSON-LD Person, sitemap, robots (allowlist Google + bots d'aperçu, reste bloqué) - Images OG et favicon en PNG statiques - Contenu du CV : Saint-Paul, langues, expériences détaillées - Export statique (output: export) + DEPLOY.md - Retrait de l'indicateur de dev Next.js et des fichiers du starter Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3hzMzvei419MwkKFZkvMX
288 lines
10 KiB
TypeScript
288 lines
10 KiB
TypeScript
import type { Metadata } from "next";
|
||
import CvToolbar from "./cv-toolbar";
|
||
import RevealPhone from "./reveal-phone";
|
||
|
||
export const metadata: Metadata = {
|
||
title: "CV",
|
||
description:
|
||
"CV de Franck Thiaw-Woaye-Lapierre — développeur web & DevOps à La Réunion. Consultable en ligne ou en PDF.",
|
||
alternates: { canonical: "/cv" },
|
||
};
|
||
|
||
const experiences = [
|
||
{
|
||
role: "Développeur web & administrateur système",
|
||
org: "Exhaust Esport — La Réunion",
|
||
period: "Juin 2022 → aujourd'hui",
|
||
points: [
|
||
"Administration des serveurs web et de jeux (Minecraft, VPN), déployés en conteneurs Docker.",
|
||
"Développement et maintenance du site de l'organisation (Laravel, Tailwind CSS).",
|
||
],
|
||
},
|
||
{
|
||
role: "Stagiaire — datacenter",
|
||
org: "Le Tampon — La Réunion",
|
||
period: "Août – septembre 2020",
|
||
points: [
|
||
"Bases du DevOps et de l'administration système ; installation et gestion de serveurs.",
|
||
"Acquisition et configuration de mon premier serveur dédié.",
|
||
],
|
||
},
|
||
];
|
||
|
||
const formation = [
|
||
{
|
||
title: "Licence Informatique",
|
||
org: "Université de La Réunion — Saint-Denis",
|
||
period: "2023 → aujourd'hui",
|
||
note: "Actuellement en L3, après une L1 et une L2 validées.",
|
||
},
|
||
{
|
||
title: "Baccalauréat",
|
||
org: "Lycée Évariste de Parny — Saint-Paul",
|
||
period: "2023",
|
||
note: "",
|
||
},
|
||
];
|
||
|
||
const projets = [
|
||
{
|
||
name: "Exhaust Esport",
|
||
desc: "Site officiel de l'équipe esport.",
|
||
stack: "Laravel · Tailwind CSS",
|
||
url: "https://exhaust.re",
|
||
},
|
||
{
|
||
name: "Pro Game",
|
||
desc: "Site et suivi de réparations pour un atelier de réparation et micro-soudure.",
|
||
stack: "Laravel",
|
||
url: "https://progame.re",
|
||
},
|
||
{
|
||
name: "wifi-deauth-test",
|
||
desc: "Assistant Tkinter guidant un test de déauthentification Wi-Fi, dans un cadre autorisé (aircrack-ng).",
|
||
stack: "Python",
|
||
url: "https://forgejo.francktwl.fr/Franck/wifi-deauth-test",
|
||
},
|
||
{
|
||
name: "Rixguard",
|
||
desc: "Site d'un service VPN (projet archivé).",
|
||
stack: "Laravel",
|
||
url: "",
|
||
},
|
||
];
|
||
|
||
const competences = [
|
||
{ label: "Langages", value: "PHP, Python, JavaScript, HTML, CSS, Ruby" },
|
||
{
|
||
label: "Frameworks",
|
||
value: "Laravel, React, Next.js, Node.js, Symfony, Tailwind CSS, Sass",
|
||
},
|
||
{
|
||
label: "Infrastructure",
|
||
value: "Linux, Docker, reverse proxy, auto-hébergement, Git",
|
||
},
|
||
{
|
||
label: "Systèmes",
|
||
value: "Serveurs web et de jeux, VPN, DNS, administration système",
|
||
},
|
||
];
|
||
|
||
const contact = [
|
||
{ label: "Email", value: "francktwl@francktwl.fr", href: "mailto:francktwl@francktwl.fr" },
|
||
{ label: "Téléphone", value: "", href: "" },
|
||
{ label: "Localisation", value: "Saint-Paul, La Réunion" },
|
||
{ label: "Site", value: "francktwl.fr", href: "https://francktwl.fr" },
|
||
{
|
||
label: "Code",
|
||
value: "forgejo.francktwl.fr/Franck",
|
||
href: "https://forgejo.francktwl.fr/Franck",
|
||
},
|
||
];
|
||
|
||
function SectionTitle({ children }: { children: string }) {
|
||
return (
|
||
<h2 className="mb-1.5 border-b border-line pb-1 text-[14px] font-semibold">
|
||
{children}
|
||
</h2>
|
||
);
|
||
}
|
||
|
||
export default function CvPage() {
|
||
return (
|
||
<div className="min-h-dvh bg-canvas text-fg print:min-h-0">
|
||
<CvToolbar />
|
||
|
||
<article className="print:m-[12mm]">
|
||
<header className="border-b border-line">
|
||
<div className="mx-auto max-w-[860px] px-6 py-6 sm:px-8 print:px-0">
|
||
<h1 className="text-[26px] font-semibold tracking-tight sm:text-[32px] sm:leading-[1.12]">
|
||
Franck Thiaw-Woaye-Lapierre
|
||
</h1>
|
||
<p className="mt-1 text-base font-medium text-accent">
|
||
Développeur & DevOps
|
||
</p>
|
||
</div>
|
||
</header>
|
||
|
||
<div className="mx-auto grid max-w-[860px] gap-x-10 gap-y-5 px-6 pt-5 pb-8 text-[13px] leading-[1.5] sm:grid-cols-[200px_minmax(0,1fr)] sm:px-8 print:px-0">
|
||
<aside className="space-y-4 text-[12px]">
|
||
<section className="break-inside-avoid">
|
||
<SectionTitle>Contact</SectionTitle>
|
||
<ul className="space-y-1.5">
|
||
{contact.map((c) => (
|
||
<li key={c.label}>
|
||
<span className="block text-[11px] font-semibold tracking-wide text-muted uppercase">
|
||
{c.label}
|
||
</span>
|
||
{c.label === "Téléphone" ? (
|
||
<RevealPhone />
|
||
) : c.href ? (
|
||
<a
|
||
href={c.href}
|
||
className="break-words text-accent hover:underline"
|
||
target={c.href.startsWith("http") ? "_blank" : undefined}
|
||
rel={
|
||
c.href.startsWith("http")
|
||
? "noreferrer noopener"
|
||
: undefined
|
||
}
|
||
>
|
||
{c.value}
|
||
</a>
|
||
) : (
|
||
<span>{c.value}</span>
|
||
)}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</section>
|
||
|
||
<section className="break-inside-avoid">
|
||
<SectionTitle>Compétences</SectionTitle>
|
||
<ul className="space-y-1.5">
|
||
{competences.map((c) => (
|
||
<li key={c.label}>
|
||
<span className="block text-[11px] font-semibold tracking-wide text-muted uppercase">
|
||
{c.label}
|
||
</span>
|
||
<span className="text-fg/85">{c.value}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</section>
|
||
|
||
<section className="break-inside-avoid">
|
||
<SectionTitle>Langues</SectionTitle>
|
||
<ul className="space-y-1.5">
|
||
<li>
|
||
<span className="font-semibold">Français</span>
|
||
<span className="text-muted"> — natif</span>
|
||
</li>
|
||
<li>
|
||
<span className="font-semibold">Anglais</span>
|
||
<span className="text-muted"> — TOEIC</span>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
</aside>
|
||
|
||
<main className="space-y-4">
|
||
<section>
|
||
<SectionTitle>Profil</SectionTitle>
|
||
<p className="max-w-[64ch] text-fg/90">
|
||
Étudiant en L3 informatique à l'Université de La Réunion,
|
||
passionné d'informatique depuis le collège. Compétences en
|
||
développement web, administration de serveurs et auto-hébergement.
|
||
Objectif : travailler dans le DevOps et l'infrastructure ;
|
||
l'électronique est un domaine que j'aimerais explorer
|
||
par la suite.
|
||
</p>
|
||
</section>
|
||
|
||
<section>
|
||
<SectionTitle>Expérience</SectionTitle>
|
||
<div className="space-y-2.5">
|
||
{experiences.map((e) => (
|
||
<div key={e.role} className="break-inside-avoid">
|
||
<div className="flex flex-wrap items-baseline justify-between gap-x-4">
|
||
<h3 className="text-[14px] font-semibold">{e.role}</h3>
|
||
<span className="text-[11px] whitespace-nowrap text-muted tabular-nums">
|
||
{e.period}
|
||
</span>
|
||
</div>
|
||
<p className="text-[12.5px] font-medium text-accent">
|
||
{e.org}
|
||
</p>
|
||
<ul className="mt-1 space-y-0.5 text-fg/85">
|
||
{e.points.map((p) => (
|
||
<li key={p} className="flex gap-2">
|
||
<span
|
||
aria-hidden="true"
|
||
className="mt-[7px] size-1 shrink-0 rounded-full bg-accent"
|
||
/>
|
||
<span>{p}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<SectionTitle>Formation</SectionTitle>
|
||
<div className="space-y-2.5">
|
||
{formation.map((f) => (
|
||
<div key={f.title} className="break-inside-avoid">
|
||
<div className="flex flex-wrap items-baseline justify-between gap-x-4">
|
||
<h3 className="text-[14px] font-semibold">{f.title}</h3>
|
||
<span className="text-[11px] whitespace-nowrap text-muted tabular-nums">
|
||
{f.period}
|
||
</span>
|
||
</div>
|
||
<p className="text-[12.5px] font-medium text-accent">
|
||
{f.org}
|
||
{f.note ? ` — ${f.note}` : ""}
|
||
</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<SectionTitle>Projets</SectionTitle>
|
||
<ul className="space-y-1.5">
|
||
{projets.map((p) => (
|
||
<li key={p.name} className="break-inside-avoid">
|
||
{p.url ? (
|
||
<a
|
||
href={p.url}
|
||
target="_blank"
|
||
rel="noreferrer noopener"
|
||
className="text-[13px] font-semibold text-accent hover:underline"
|
||
>
|
||
{p.name}
|
||
</a>
|
||
) : (
|
||
<span className="text-[13px] font-semibold">{p.name}</span>
|
||
)}
|
||
<span className="text-fg/85"> — {p.desc}</span>
|
||
<span className="text-[11px] text-muted"> ({p.stack})</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</section>
|
||
</main>
|
||
</div>
|
||
</article>
|
||
|
||
<style>{`
|
||
@media print {
|
||
@page { size: A4; margin: 0; }
|
||
}
|
||
`}</style>
|
||
</div>
|
||
);
|
||
}
|