portfolio_v2/app/page.tsx

405 lines
15 KiB
TypeScript
Raw Normal View History

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",
],
};
2026-09-08 08:34:42 +04:00
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">
2026-09-08 08:34:42 +04:00
<a
href="https://forgejo.francktwl.fr/Franck"
target="_blank"
rel="noreferrer noopener"
className="text-muted hover:text-accent"
2026-09-08 08:34:42 +04:00
>
git<span className="sr-only"> (nouvel onglet)</span>
</a>
2026-09-08 08:34:42 +04:00
<a
href="mailto:francktwl@francktwl.fr"
className="text-muted hover:text-accent"
2026-09-08 08:34:42 +04:00
>
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>
2026-09-08 08:34:42 +04:00
</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"
2026-09-08 08:34:42 +04:00
/>
<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 &amp; DevOps · La Réunion
</span>
</p>
<p className="mt-4">
Étudiant en L3 informatique. J&apos;auto-héberge mon infra depuis 2020
: serveurs Linux, services en Docker, mon propre forge git. J&apos;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&apos;est Franck 👋</h2>
<p className="max-w-[65ch]">
Passionné d&apos;informatique depuis le collège — les jeux vidéo
d&apos;abord, puis l&apos;assemblage de mon premier PC. Je suis
aujourd&apos;hui en 3ᵉ année de licence informatique à La Réunion.
</p>
<p className="max-w-[65ch]">
Ce qui me plaît vraiment, c&apos;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&apos;électronique est un domaine que j&apos;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&apos;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>
</>
2026-09-08 08:34:42 +04:00
);
}