- 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
405 lines
15 KiB
TypeScript
405 lines
15 KiB
TypeScript
import Link from "next/link";
|
||
|
||
const badges = [
|
||
{ alt: "Linux", src: "https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black" },
|
||
{ alt: "Docker", src: "https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white" },
|
||
{ alt: "Python", src: "https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" },
|
||
{ alt: "PHP", src: "https://img.shields.io/badge/PHP-777BB4?style=for-the-badge&logo=php&logoColor=white" },
|
||
{ alt: "Laravel", src: "https://img.shields.io/badge/Laravel-FF2D20?style=for-the-badge&logo=laravel&logoColor=white" },
|
||
{ alt: "JavaScript", src: "https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black" },
|
||
{ alt: "React", src: "https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB" },
|
||
{ alt: "Tailwind CSS", src: "https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white" },
|
||
{ alt: "Git", src: "https://img.shields.io/badge/Git-F05032?style=for-the-badge&logo=git&logoColor=white" },
|
||
];
|
||
|
||
type Repo = {
|
||
name: string;
|
||
desc: string;
|
||
lang: string;
|
||
langColor: string;
|
||
topics: string[];
|
||
url?: string;
|
||
archived?: boolean;
|
||
};
|
||
|
||
const repos: Repo[] = [
|
||
{
|
||
name: "wifi-deauth-test",
|
||
desc: "Assistant Tkinter qui guide un test de déauthentification Wi-Fi sur un client, dans un cadre autorisé, en s'appuyant sur aircrack-ng.",
|
||
lang: "Python",
|
||
langColor: "#3572A5",
|
||
topics: ["python", "tkinter", "aircrack-ng", "wifi-security"],
|
||
url: "https://forgejo.francktwl.fr/Franck/wifi-deauth-test",
|
||
},
|
||
{
|
||
name: "exhaust-esport",
|
||
desc: "Site officiel de l'équipe esport Exhaust.",
|
||
lang: "PHP",
|
||
langColor: "#4F5D95",
|
||
topics: ["laravel", "php", "tailwindcss", "esport"],
|
||
url: "https://exhaust.re",
|
||
},
|
||
{
|
||
name: "progame",
|
||
desc: "Site et suivi de réparations pour Pro Game, atelier de réparation et micro-soudure à La Réunion.",
|
||
lang: "PHP",
|
||
langColor: "#4F5D95",
|
||
topics: ["laravel", "php", "suivi-réparation"],
|
||
url: "https://progame.re",
|
||
},
|
||
{
|
||
name: "rixguard",
|
||
desc: "Site vitrine et espace client d'un service VPN. Le service a fermé, le site n'existe plus aujourd'hui.",
|
||
lang: "PHP",
|
||
langColor: "#4F5D95",
|
||
topics: ["laravel", "php", "vpn"],
|
||
archived: true,
|
||
},
|
||
];
|
||
|
||
const parcours = [
|
||
{
|
||
year: "2023",
|
||
title: "Licence Informatique — Université de La Réunion",
|
||
body: "Actuellement en L3, après une L1 et une L2 validées.",
|
||
},
|
||
{
|
||
year: "juin 2022",
|
||
title: "Développeur web & administrateur système — Exhaust Esport",
|
||
body: "Serveurs web et de jeux (Minecraft, VPN), site de l'organisation en Laravel, services conteneurisés avec Docker.",
|
||
},
|
||
{
|
||
year: "2022–2023",
|
||
title: "Magicien — centre de loisirs, Saint-Paul",
|
||
body: "Animation de spectacles pour enfants pendant les vacances scolaires.",
|
||
},
|
||
{
|
||
year: "2022",
|
||
title: "Baccalauréat — Lycée Évariste de Parny",
|
||
body: "Plateau Caillou, La Réunion.",
|
||
},
|
||
{
|
||
year: "2020",
|
||
title: "Stage — datacenter, Le Tampon",
|
||
body: "Bases du DevOps et de l'administration système. Premier serveur dédié acquis à cette occasion.",
|
||
},
|
||
];
|
||
|
||
const homelab: { icon: string; name: string; note: string; url?: string }[] = [
|
||
{
|
||
icon: "🗃️",
|
||
name: "Forgejo",
|
||
note: "",
|
||
url: "https://forgejo.francktwl.fr/Franck",
|
||
},
|
||
{ icon: "☁️", name: "Nextcloud", note: "" },
|
||
{ icon: "🛡️", name: "AdGuard Home", note: "" },
|
||
{ icon: "🔒", name: "VPN", note: "" },
|
||
{ icon: "🌐", name: "Mes sites web", note: "" },
|
||
];
|
||
|
||
const personLd = {
|
||
"@context": "https://schema.org",
|
||
"@type": "Person",
|
||
name: "Franck Thiaw-Woaye-Lapierre",
|
||
alternateName: "francktwl",
|
||
url: "https://francktwl.fr",
|
||
image: "https://francktwl.fr/pp.jpg",
|
||
jobTitle: "Développeur web & DevOps",
|
||
description:
|
||
"Étudiant en L3 informatique à La Réunion, orienté DevOps, administration de serveurs Linux et auto-hébergement.",
|
||
address: {
|
||
"@type": "PostalAddress",
|
||
addressLocality: "Saint-Paul",
|
||
addressRegion: "La Réunion",
|
||
addressCountry: "RE",
|
||
},
|
||
knowsLanguage: ["fr", "en"],
|
||
alumniOf: {
|
||
"@type": "CollegeOrUniversity",
|
||
name: "Université de La Réunion",
|
||
},
|
||
knowsAbout: [
|
||
"DevOps",
|
||
"Linux",
|
||
"Docker",
|
||
"Laravel",
|
||
"Auto-hébergement",
|
||
"Administration système",
|
||
"Réseau",
|
||
],
|
||
sameAs: [
|
||
"https://forgejo.francktwl.fr/Franck",
|
||
"https://www.instagram.com/francktwl_photo/",
|
||
"https://discordapp.com/users/320512642785607681",
|
||
],
|
||
};
|
||
|
||
export default function Home() {
|
||
return (
|
||
<>
|
||
<script
|
||
type="application/ld+json"
|
||
dangerouslySetInnerHTML={{ __html: JSON.stringify(personLd) }}
|
||
/>
|
||
<header className="sticky top-0 z-20 border-b border-white/10 bg-[#0a0e17]/72 backdrop-blur-xl">
|
||
<div className="mx-auto flex max-w-[1280px] items-center gap-3 px-4 py-3 md:px-6">
|
||
<span className="glass grid size-8 place-items-center rounded-full font-bold">
|
||
F
|
||
</span>
|
||
<span className="font-semibold">Franck Thiaw-Woaye-Lapierre</span>
|
||
<nav className="ml-auto flex items-center gap-4">
|
||
<a
|
||
href="https://forgejo.francktwl.fr/Franck"
|
||
target="_blank"
|
||
rel="noreferrer noopener"
|
||
className="text-muted hover:text-accent"
|
||
>
|
||
git<span className="sr-only"> (nouvel onglet)</span>
|
||
</a>
|
||
<a
|
||
href="mailto:francktwl@francktwl.fr"
|
||
className="text-muted hover:text-accent"
|
||
>
|
||
email
|
||
</a>
|
||
<Link
|
||
href="/cv"
|
||
className="rounded-md border border-line bg-surface px-3 py-1.5 text-sm font-semibold hover:border-muted"
|
||
>
|
||
CV
|
||
</Link>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
<div className="mx-auto grid max-w-[1100px] gap-6 px-4 pt-6 pb-16 md:grid-cols-[296px_minmax(0,1fr)] md:px-6">
|
||
<aside data-reveal className="flex flex-col">
|
||
<div className="flex items-start gap-4 md:block">
|
||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||
<img
|
||
src="/pp.jpg"
|
||
alt="Franck Thiaw-Woaye-Lapierre"
|
||
className="size-16 shrink-0 rounded-full border border-line object-cover md:h-auto md:w-full md:aspect-square"
|
||
/>
|
||
<div className="md:mt-4">
|
||
<h1 className="text-xl font-semibold leading-tight md:text-2xl">
|
||
Franck Thiaw-Woaye-Lapierre
|
||
</h1>
|
||
<p className="text-base font-light text-muted md:text-xl">
|
||
@francktwl
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<p className="mt-3">
|
||
<span className="glass inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs">
|
||
<span aria-hidden="true">💻</span> Développeur & DevOps · La Réunion
|
||
</span>
|
||
</p>
|
||
|
||
<p className="mt-4">
|
||
Étudiant en L3 informatique. J'auto-héberge mon infra depuis 2020
|
||
: serveurs Linux, services en Docker, mon propre forge git. J'apprends
|
||
surtout en cassant des choses pour comprendre comment elles tiennent.
|
||
</p>
|
||
|
||
<ul className="mt-4 space-y-2 text-muted">
|
||
<li>
|
||
<span aria-hidden="true">📍</span> Saint-Paul, La Réunion
|
||
</li>
|
||
<li>
|
||
<span aria-hidden="true">🎓</span> Licence Informatique · Université
|
||
de La Réunion
|
||
</li>
|
||
<li>
|
||
<span aria-hidden="true">🗣️</span> Français (natif) · Anglais
|
||
(TOEIC)
|
||
</li>
|
||
<li>
|
||
<span aria-hidden="true">🔗</span>{" "}
|
||
<a
|
||
href="https://forgejo.francktwl.fr/Franck"
|
||
target="_blank"
|
||
rel="noreferrer noopener"
|
||
className="text-accent hover:underline"
|
||
>
|
||
forgejo.francktwl.fr<span className="sr-only"> (nouvel onglet)</span>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<span aria-hidden="true">✉️</span>{" "}
|
||
<a
|
||
href="mailto:francktwl@francktwl.fr"
|
||
className="text-accent hover:underline"
|
||
>
|
||
francktwl@francktwl.fr
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<span aria-hidden="true">📸</span>{" "}
|
||
<a
|
||
href="https://www.instagram.com/francktwl_photo/"
|
||
target="_blank"
|
||
rel="noreferrer noopener"
|
||
className="text-accent hover:underline"
|
||
>
|
||
@francktwl_photo<span className="sr-only"> (nouvel onglet)</span>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<span aria-hidden="true">💬</span>{" "}
|
||
<a
|
||
href="https://discordapp.com/users/320512642785607681"
|
||
target="_blank"
|
||
rel="noreferrer noopener"
|
||
className="text-accent hover:underline"
|
||
>
|
||
Discord<span className="sr-only"> (nouvel onglet)</span>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</aside>
|
||
|
||
<main className="min-w-0">
|
||
<section data-reveal className="glass overflow-hidden rounded-2xl">
|
||
<div className="flex items-center gap-2 border-b border-white/10 px-4 py-3 text-muted">
|
||
<span aria-hidden="true">📖</span>
|
||
<span className="text-xs">francktwl / README.md</span>
|
||
</div>
|
||
<div className="space-y-4 p-4 md:p-6">
|
||
<h2 className="text-xl font-semibold">Salut, moi c'est Franck 👋</h2>
|
||
<p className="max-w-[65ch]">
|
||
Passionné d'informatique depuis le collège — les jeux vidéo
|
||
d'abord, puis l'assemblage de mon premier PC. Je suis
|
||
aujourd'hui en 3ᵉ année de licence informatique à La Réunion.
|
||
</p>
|
||
<p className="max-w-[65ch]">
|
||
Ce qui me plaît vraiment, c'est gérer des machines :
|
||
administrer des serveurs Linux, les configurer, faire tourner mes
|
||
services dans Docker. Mon objectif est de travailler dans le
|
||
DevOps ; l'électronique est un domaine que j'aimerais
|
||
explorer plus tard.
|
||
</p>
|
||
<div className="flex flex-wrap gap-1.5 pt-1">
|
||
{badges.map((b) => (
|
||
// eslint-disable-next-line @next/next/no-img-element
|
||
<img key={b.alt} src={b.src} alt={b.alt} className="h-7" />
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section data-reveal className="mt-6">
|
||
<h2 className="mb-3 text-base font-semibold">Homelab</h2>
|
||
<div className="glass rounded-2xl p-4">
|
||
<p className="text-sm text-muted">
|
||
J'héberge mes propres services derrière un reverse proxy, sur
|
||
un serveur dédié en ligne depuis 2020 — tout en conteneurs.
|
||
</p>
|
||
<ul className="mt-3 grid gap-x-4 gap-y-2 sm:grid-cols-2">
|
||
{homelab.map((h) => (
|
||
<li key={h.name} className="flex items-baseline gap-2 text-sm">
|
||
<span aria-hidden="true">{h.icon}</span>
|
||
<span>
|
||
{h.url ? (
|
||
<a
|
||
href={h.url}
|
||
target="_blank"
|
||
rel="noreferrer noopener"
|
||
className="font-semibold text-accent hover:underline"
|
||
>
|
||
{h.name}
|
||
<span className="sr-only"> (nouvel onglet)</span>
|
||
</a>
|
||
) : (
|
||
<span className="font-semibold">{h.name}</span>
|
||
)}
|
||
</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="projets" data-reveal className="mt-6 scroll-mt-6">
|
||
<h2 className="mb-3 text-base font-semibold">Épinglé</h2>
|
||
<ul className="grid gap-4 sm:grid-cols-2">
|
||
{repos.map((r) => (
|
||
<li
|
||
key={r.name}
|
||
className="glass flex h-full flex-col rounded-2xl p-4"
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-muted" aria-hidden="true">
|
||
📁
|
||
</span>
|
||
{r.url ? (
|
||
<a
|
||
href={r.url}
|
||
target="_blank"
|
||
rel="noreferrer noopener"
|
||
className="font-semibold text-accent hover:underline"
|
||
>
|
||
{r.name}
|
||
<span className="sr-only"> (nouvel onglet)</span>
|
||
</a>
|
||
) : (
|
||
<span className="font-semibold">{r.name}</span>
|
||
)}
|
||
<span
|
||
className={`ml-auto rounded-full border px-2 py-0.5 text-xs ${
|
||
r.archived
|
||
? "border-[#d29922]/40 text-[#d29922]"
|
||
: "border-line text-muted"
|
||
}`}
|
||
>
|
||
{r.archived ? "Archivé" : "Public"}
|
||
</span>
|
||
</div>
|
||
<p className="mt-2 text-xs text-muted">{r.desc}</p>
|
||
<ul className="mt-3 flex flex-wrap gap-1.5">
|
||
{r.topics.map((t) => (
|
||
<li
|
||
key={t}
|
||
className="rounded-full bg-accent/10 px-2 py-0.5 text-xs font-medium text-accent"
|
||
>
|
||
{t}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
<p className="mt-auto flex items-center gap-1.5 pt-3 text-xs text-muted">
|
||
<span
|
||
className="inline-block size-3 rounded-full"
|
||
style={{ backgroundColor: r.langColor }}
|
||
aria-hidden="true"
|
||
/>
|
||
{r.lang}
|
||
</p>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</section>
|
||
|
||
<section data-reveal className="mt-6">
|
||
<h2 className="mb-3 text-base font-semibold">Parcours</h2>
|
||
<ol className="border-l border-line">
|
||
{parcours.map((p) => (
|
||
<li key={p.year} className="relative pb-6 pl-6 last:pb-0">
|
||
<span
|
||
className="absolute top-1.5 -left-[5px] size-2.5 rounded-full border-2 border-canvas bg-muted"
|
||
aria-hidden="true"
|
||
/>
|
||
<p className="text-xs text-muted">{p.year}</p>
|
||
<p className="font-semibold">{p.title}</p>
|
||
<p className="mt-1 max-w-[65ch] text-muted">{p.body}</p>
|
||
</li>
|
||
))}
|
||
</ol>
|
||
</section>
|
||
</main>
|
||
</div>
|
||
</>
|
||
);
|
||
}
|