portfolio_v2/app/cv/page.tsx

288 lines
10 KiB
TypeScript
Raw Permalink Normal View History

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 &amp; 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&apos;Université de La Réunion,
passionné d&apos;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&apos;infrastructure ;
l&apos;électronique est un domaine que j&apos;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>
);
}