cool update

This commit is contained in:
gpatruno
2026-04-19 23:06:50 +02:00
parent 5c423bae47
commit baef85bd99
27 changed files with 398 additions and 306 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/png" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Portfolio</title> <title>Portfolio</title>
</head> </head>
Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

+28 -24
View File
@@ -1,17 +1,19 @@
export function ContentStack({ panel1, panel1b, panel2, panel3 }) { export function ContentStack({ panel1, panel1b, panel2, panel3 }) {
return ( return (
<div className="content-stack" aria-label="Contenu principal"> <div className="content-stack" aria-label="Contenu principal">
<section {panel1 != null ? (
className={[ <section
'content-stack__panel', className={[
panel1 ? 'content-stack__panel--padded' : null, 'content-stack__panel',
] panel1 ? 'content-stack__panel--padded' : null,
.filter(Boolean) ]
.join(' ')} .filter(Boolean)
aria-label="Bloc 1" .join(' ')}
> aria-label="Bloc 1"
{panel1} >
</section> {panel1}
</section>
) : null}
{panel1b != null ? ( {panel1b != null ? (
<section <section
className={[ className={[
@@ -23,19 +25,21 @@ export function ContentStack({ panel1, panel1b, panel2, panel3 }) {
{panel1b} {panel1b}
</section> </section>
) : null} ) : null}
<section {panel2 != null ? (
className={[ <section
'content-stack__panel', className={[
panel2 ? 'content-stack__panel--padded' : null, 'content-stack__panel',
] panel2 ? 'content-stack__panel--padded' : null,
.filter(Boolean) ]
.join(' ')} .filter(Boolean)
aria-label={ .join(' ')}
panel3 != null || panel1b != null ? 'Bloc 3' : 'Bloc 2' aria-label={
} panel3 != null || panel1b != null ? 'Bloc 3' : 'Bloc 2'
> }
{panel2} >
</section> {panel2}
</section>
) : null}
{panel3 != null ? ( {panel3 != null ? (
<section <section
className={[ className={[
+3 -2
View File
@@ -22,13 +22,14 @@ const EVENTS = [
sequence: 0, sequence: 0,
side: 'left', side: 'left',
title: 'CAP Mécanique auto', title: 'CAP Mécanique auto',
description: 'AF13', description: 'AF13, Alternance',
}, },
{ {
period: '2021-2022', period: '2021-2022',
sequence: 0, sequence: 0,
side: 'right', side: 'right',
title: 'CAP Plomberie chauffagerie', title: 'CAP Plomberie chauffagerie',
description: '3 stages de 3 mois',
}, },
{ {
period: '2022-2025', period: '2022-2025',
@@ -38,7 +39,7 @@ const EVENTS = [
description: 'OpenMotion', description: 'OpenMotion',
}, },
{ {
period: '2022-2026', period: '2024-2026',
sequence: 0, sequence: 0,
side: 'right', side: 'right',
title: 'Installation et configuration de serveurs personnels', title: 'Installation et configuration de serveurs personnels',
-2
View File
@@ -4,11 +4,9 @@ const TAGS = [
{ label: 'Curieux', tone: 'pos' }, { label: 'Curieux', tone: 'pos' },
{ label: 'Polyvalent', tone: 'pos' }, { label: 'Polyvalent', tone: 'pos' },
{ label: 'Autodidacte', tone: 'pos' }, { label: 'Autodidacte', tone: 'pos' },
{ label: 'Logique', tone: 'pos' },
{ label: 'Persévérance', tone: 'pos' }, { label: 'Persévérance', tone: 'pos' },
{ label: "Esprit d'Analyse", tone: 'pos' }, { label: "Esprit d'Analyse", tone: 'pos' },
{ label: 'Autonomie', tone: 'pos' }, { label: 'Autonomie', tone: 'pos' },
{ label: "Capacité d'Adaptation", tone: 'pos' },
{ label: 'Impatient', tone: 'neg' }, { label: 'Impatient', tone: 'neg' },
{ label: 'Besoin de comprendre', tone: 'neg' }, { label: 'Besoin de comprendre', tone: 'neg' },
{ label: 'Pédagogie', tone: 'neg' }, { label: 'Pédagogie', tone: 'neg' },
@@ -1,28 +0,0 @@
import { PROFIL_TRAITS } from '../data/profilTraits.js'
export function HomeProfilQualitesPanel() {
return (
<div className="home-intro home-intro--profil-box">
<section
className="home-profil-traits"
aria-labelledby="home-profil-traits-title"
>
<h2 className="home-intro__heading" id="home-profil-traits-title">
Qualité / Défauts
</h2>
<ul className="home-profil-traits__list">
{PROFIL_TRAITS.map(({ label, tone, desc }) => (
<li key={label} className="home-profil-traits__row">
<span
className={`home-profil-traits__label home-tag home-tag--${tone}`}
>
{label}
</span>
<p className="home-profil-traits__desc">{desc}</p>
</li>
))}
</ul>
</section>
</div>
)
}
+4 -44
View File
@@ -1,25 +1,3 @@
import { createElement } from 'react'
import { FaGithub, FaLinkedinIn } from 'react-icons/fa6'
import { SiTryhackme } from 'react-icons/si'
const SOCIAL = [
{
label: 'LinkedIn',
href: 'https://www.linkedin.com/',
Icon: FaLinkedinIn,
},
{
label: 'GitHub',
href: 'https://github.com/',
Icon: FaGithub,
},
{
label: 'TryHackMe',
href: 'https://tryhackme.com/',
Icon: SiTryhackme,
},
]
export function HomeSidebar({ layout = 'vertical' }) { export function HomeSidebar({ layout = 'vertical' }) {
const isHorizontal = layout === 'horizontal' const isHorizontal = layout === 'horizontal'
@@ -34,33 +12,15 @@ export function HomeSidebar({ layout = 'vertical' }) {
> >
<img <img
className="home-sidebar__photo" className="home-sidebar__photo"
src="https://www.thispersondoesnotexist.com" src="/images/profile-photo.png"
alt="" alt="Photo de profil"
width={280} width={300}
height={280} height={300}
loading="lazy" loading="lazy"
decoding="async" decoding="async"
/> />
<div className="home-sidebar__cluster"> <div className="home-sidebar__cluster">
<p className="home-sidebar__name">Prénom Nom</p> <p className="home-sidebar__name">Prénom Nom</p>
<ul className="home-sidebar__social" aria-label="Liens sociaux">
{SOCIAL.map(({ label, href, Icon }) => (
<li key={label}>
<a
className="home-sidebar__social-link"
href={href}
target="_blank"
rel="noopener noreferrer"
aria-label={label}
>
{createElement(Icon, {
'aria-hidden': true,
className: 'home-sidebar__social-icon',
})}
</a>
</li>
))}
</ul>
</div> </div>
<div className="home-sidebar__extra"> <div className="home-sidebar__extra">
<ul className="home-sidebar__extra-list"> <ul className="home-sidebar__extra-list">
+4 -7
View File
@@ -1,11 +1,10 @@
import { PROJETS } from '../data/projets.js' import { PROJETS } from '../data/projets.js'
import { getSkillCategory } from '../data/skills.js'
const SKILL_CATS = ['green', 'blue', 'yellow', 'red']
export function ProjetsGrid() { export function ProjetsGrid() {
return ( return (
<ul className="projets-grid"> <ul className="projets-grid">
{PROJETS.map((projet, index) => ( {PROJETS.map((projet) => (
<li key={projet.id} className="projets-grid__cell"> <li key={projet.id} className="projets-grid__cell">
<article className="projet-card" aria-labelledby={`projet-title-${projet.id}`}> <article className="projet-card" aria-labelledby={`projet-title-${projet.id}`}>
<div className="projet-card__media"> <div className="projet-card__media">
@@ -23,12 +22,10 @@ export function ProjetsGrid() {
{projet.title} {projet.title}
</h2> </h2>
<ul className="projet-card__skills" aria-label="Compétences"> <ul className="projet-card__skills" aria-label="Compétences">
{projet.skills.map((skill, i) => ( {projet.skills.map((skill) => (
<li key={skill}> <li key={skill}>
<span <span
className={`home-skill home-skill--${ className={`home-skill home-skill--${getSkillCategory(skill)}`}
SKILL_CATS[(index + i) % SKILL_CATS.length]
}`}
> >
{skill} {skill}
</span> </span>
+20
View File
@@ -1,4 +1,6 @@
import { createElement } from 'react'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { SITE_SOCIAL } from '../data/socialLinks.js'
function getNavLinkClassName({ isActive }) { function getNavLinkClassName({ isActive }) {
return ['topnav__link', isActive ? 'is-active' : null].filter(Boolean).join(' ') return ['topnav__link', isActive ? 'is-active' : null].filter(Boolean).join(' ')
@@ -21,6 +23,24 @@ export function TopNav() {
<NavLink to="/projets" className={getNavLinkClassName}> <NavLink to="/projets" className={getNavLinkClassName}>
Projets Projets
</NavLink> </NavLink>
<ul className="topnav__social" aria-label="Liens sociaux">
{SITE_SOCIAL.map(({ label, href, Icon }) => (
<li key={label}>
<a
className="topnav__social-link"
href={href}
target="_blank"
rel="noopener noreferrer"
aria-label={label}
>
{createElement(Icon, {
'aria-hidden': true,
className: 'topnav__social-icon',
})}
</a>
</li>
))}
</ul>
</nav> </nav>
</div> </div>
</header> </header>
-52
View File
@@ -1,52 +0,0 @@
export const PROFIL_TRAITS = [
{
label: 'La Logique',
tone: 'pos',
desc: 'Capacité à décomposer un problème complexe en étapes simples.',
},
{
label: 'La Curiosité',
tone: 'pos',
desc: "S'intéresser facilement, aimer apprendre de nouvelles choses.",
},
{
label: "L'Autonomie",
tone: 'pos',
desc: 'Savoir trouver la solution dans une documentation ou sur un forum sans aide extérieure.',
},
{
label: 'La Persévérance',
tone: 'pos',
desc: 'Ne pas abandonner facilement.',
},
{
label: "L'Esprit d'Analyse",
tone: 'pos',
desc: "Savoir anticiper les failles ou les besoins futurs d'un système.",
},
{
label: "La Capacité d'Adaptation",
tone: 'pos',
desc: "Changer de projet rapidement sans difficulté. s'adapter à de nouvelles technologies.",
},
{
label: 'Polyvalent',
tone: 'pos',
desc: "Administration système, développement logiciel, réparation d'équipements électroniques, modification de hardware, Mécanique, Plomberie, Electricité.",
},
{
label: "L'Impatience",
tone: 'neg',
desc: 'L\'envie que les choses avancent, que l\'attente est une perte de temps.',
},
{
label: 'Pédagogie',
tone: 'neg',
desc: "Capacité à expliquer un problème technique complexe à un utilisateur qui n'y connaît rien.",
},
{
label: 'Besoin de comprendre',
tone: 'neg',
desc: 'Ne pas se contenter de voir que quelque chose fonctionne, mais comprendre comment ça fonctionne.',
},
]
+186 -77
View File
@@ -1,21 +1,10 @@
/** Cartes projet (page Projets) : titre, compétences, visuel, texte, lien externe. */ /** Cartes projet (page Projets) : titre, compétences, visuel, texte, lien externe. */
export const PROJETS = [ export const PROJETS = [
{
id: 'Self-Hosting',
title: 'Self Hosting',
skills: [ 'Linux', 'Bash', 'Dockers', 'Sécurité'],
imageSrc: 'https://picsum.photos/seed/pdfredact/640/360',
imageAlt: 'Home Lab, Serveurs personnels',
description:
'Installation et configuration de serveurs personnels pour des projets personnels et professionnels.',
link: 'https://pdfbox.apache.org/',
linkLabel: 'Apache PDFBox',
},
{ {
id: 'androidpayload', id: 'androidpayload',
title: 'androidpayload', title: 'androidpayload',
skills: ['Linux', 'Bash', 'Java', 'Hardware'], skills: ['Linux', 'Bash', 'Java', 'Hardware'],
imageSrc: 'https://www.thispersondoesnotexist.com/', imageSrc: 'https://picsum.photos/seed/pacmanc/640/360',
imageAlt: 'Visuel du projet androidpayload', imageAlt: 'Visuel du projet androidpayload',
description: description:
'Application pour récupérer tous les droits sur un appareil Android (ROOT).', 'Application pour récupérer tous les droits sur un appareil Android (ROOT).',
@@ -23,92 +12,212 @@ export const PROJETS = [
linkLabel: 'Magisk sur GitHub', linkLabel: 'Magisk sur GitHub',
}, },
{ {
id: 'homelab-stack', id: 'wikicyber',
title: 'HomelabStack', title: 'WIKICYBER',
skills: ['Docker', 'Linux', 'Ansible', 'Nginx'], skills: ['Web', 'OSINT', 'Cybersécurité', 'Documentation'],
imageSrc: 'https://picsum.photos/seed/homelab/640/360', imageSrc: '/images/wikicyber-preview.png',
imageAlt: 'Schéma de services conteneurisés', imageAlt: 'Aperçu du projet WIKICYBER',
description: description:
'Automatisation du déploiement de services auto-hébergés, reverse proxy et sauvegardes planifiées.', 'Wiki informatique, réseau social anonyme WIKICYBER.\nSite web regroupant des outils informatiques / réseau social anonyme.\nCyber sécurité, OSINT, documentation…\nProjet perso — GitHub.',
link: 'https://github.com/ansible/ansible', link: 'https://github.com/Foufure13/wiki.cyber',
linkLabel: 'Ansible sur GitHub', linkLabel: 'wiki.cyber sur GitHub',
}, },
{ {
id: 'capteurs-maison', id: 'lk-find-post',
title: 'CapteursMaison', title: 'LK-Find-Post',
skills: ['C', 'MQTT', 'ESP32', 'Électronique'], skills: ['Scraping', 'API', 'Base de données', 'Python'],
imageSrc: 'https://picsum.photos/seed/iotcapteurs/640/360', imageSrc: '/images/lk-find-post-preview.png',
imageAlt: 'Maquette capteurs connectés', imageAlt: 'Aperçu du projet LK-Find-Post',
description: description:
'Firmware embarqué et passerelle MQTT pour relevés de température et humidité en temps réel.', 'Outil de scraping de posts et de filtrage pour récupérer un thème spécifique.\nEnvoi par API vers la base de données.\nProjet perso — GitHub.',
link: 'https://mqtt.org/', link: 'https://github.com/Foufure13/Linkedin-find-post',
linkLabel: 'Protocole MQTT', linkLabel: 'Linkedin-find-post sur GitHub',
}, },
{ {
id: 'veille-marche', id: 'rotmg-like',
title: 'VeilleMarché', title: 'ROTMG-LIKE',
skills: ['Python', 'REST', 'SQLite', 'CLI'], skills: ['C#', 'Multijoueur', 'Jeu vidéo'],
imageSrc: 'https://picsum.photos/seed/chartscli/640/360', imageSrc: '/images/rotmglike-preview.png',
imageAlt: 'Courbes et terminal', imageAlt: 'Aperçu du jeu ROTMG-LIKE',
description: description:
'Outil en ligne de commande pour agréger des cours, historiser en local et déclencher des alertes.', 'Jeu vidéo en C# multijoueur / solo.\nGraphisme et gameplay inspirés du jeu dorigine, avec les fonctionnalités de base : 1 type de classe, niveau max 20.\nStuff : arme, collier, armure, anneau — 4 types par catégorie.\n4 types dennemis : à leur mort, gain dXP et 1 drop darme par ennemi.\nProjet scolaire personnel — GitHub.',
link: 'https://docs.python.org/3/library/argparse.html', link: 'https://github.com/Foufure13/ROTMG-LIKE-Csharp',
linkLabel: 'argparse (Python)', linkLabel: 'ROTMG-LIKE sur GitHub',
}, },
{ {
id: 'photo-archiver', id: 'twitch-bot-interactif',
title: 'PhotoArchiver', title: 'Twitch Bot Interactif',
skills: ['Rust', 'CLI', 'Filesystem', 'EXIF'], skills: ['Twitch', 'API', 'STT', 'Bot'],
imageSrc: 'https://picsum.photos/seed/photoarch/640/360', imageSrc: '/images/twitch-bot-preview.png',
imageAlt: 'Dossiers et vignettes photo', imageAlt: 'Aperçu du bot Twitch',
description: description:
'Indexation dune photothèque locale, détection de doublons par empreinte et renommage par date EXIF.', 'Bot connecté au flux de chat et au streaming dun live Twitch.\nFonctionnalité 1 : écouter le chat et répondre quand il est mentionné.\nFonctionnalité 2 : enregistrer les voix du flux, traduire en texte et interagir selon les détections.\nProjet personnel — GitHub.',
link: 'https://exiftool.org/', link: 'https://github.com/Foufure13/twitchBot-intelligent',
linkLabel: 'ExifTool', linkLabel: 'twitchBot-intelligent sur GitHub',
}, },
{ {
id: 'net-pulse', id: 'billeterie-cinema',
title: 'NetPulse', title: 'Billeterie cinéma',
skills: ['Go', 'Prometheus', 'Grafana', 'HTTP'], skills: ['Web', 'E-mail', 'Base de données'],
imageSrc: 'https://picsum.photos/seed/netpulse/640/360', imageSrc: '/images/billeterie-cinema-preview.png',
imageAlt: 'Graphiques de métriques réseau', imageAlt: 'Aperçu de la billetterie cinéma',
description: description:
'Sonde légère et tableaux de bord pour suivre latence, codes HTTP et disponibilité des services du lab.', 'Site web de réservation de places avec choix du siège dans la salle.\nConfirmation par e-mail.\nProjet scolaire en groupe — GitHub.',
link: 'https://prometheus.io/', link: 'https://github.com/Foufure13/Billetterie-en-ligne',
linkLabel: 'Prometheus', linkLabel: 'Billetterie-en-ligne sur GitHub',
}, },
{ {
id: 'bench-auto', id: 'realmaster',
title: 'BenchAuto', title: 'RealMaster',
skills: ['Bash', 'Jenkins', 'Git', 'curl'], skills: ['Web', 'Social', 'Stats'],
imageSrc: 'https://picsum.photos/seed/benchauto/640/360', imageSrc: 'https://picsum.photos/seed/lkfindpost/640/360',
imageAlt: 'Pipeline et rapports de tests', imageAlt: 'Aperçu du projet RealMaster',
description: description:
'Jobs planifiés pour enchaîner scénarios de charge, collecter les métriques et publier un rapport HTML.', 'Réseau social sécurisé sur le thème du jeu vidéo ROTMG.\nOutils pour le jeu : prévisualisation avatar, statistiques stuff.\nProjet scolaire personnel — GitHub.',
link: 'https://www.jenkins.io/', link: 'https://github.com/Foufure13/realmaster',
linkLabel: 'Jenkins', linkLabel: 'realmaster sur GitHub',
}, },
{ {
id: 'mesh-relay', id: 'pacman-c',
title: 'MeshRelay', title: 'Pacman C',
skills: ['Zigbee', 'Node', 'MQTT', 'Home Assistant'], skills: ['C', 'Jeux vidéo', 'Procédural', 'Carte'],
imageSrc: 'https://picsum.photos/seed/meshrelay/640/360', imageSrc: '/images/pacman-c-preview.png',
imageAlt: 'Passerelle et objets connectés', imageAlt: 'Aperçu du Pacman en C',
description: description:
'Automatisations domotiques : capteurs Zigbee, scénarios jour/nuit et notifications sur événements.', 'Pacman codé en C avec modifications.\nGénération procédurale de la carte, choix de la taille de la carte.\nProjet scolaire personnel — GitHub.',
link: 'https://www.home-assistant.io/', link: 'https://github.com/Foufure13/Pacman_C',
linkLabel: 'Home Assistant', linkLabel: 'Pacman_C sur GitHub',
}, },
{ {
id: 'sync-vault', id: 'airsoft-vitrine',
title: 'SyncVault', title: 'Airsoft vitrine',
skills: ['rsync', 'S3', 'LUKS', 'Cron'], skills: ['Web', 'Association', 'Vitrine'],
imageSrc: 'https://picsum.photos/seed/syncvault/640/360', imageSrc: '/images/airsoft-vitrine-preview.png',
imageAlt: 'Sauvegarde et chiffrement', imageAlt: 'Aperçu du site Airsoft',
description: description:
'Volumes chiffrés, sauvegardes incrémentielles locales et miroir distant compatible stockage objet.', 'Site web créé pour une association.\nProjet personnel — GitHub.',
link: 'https://rclone.org/', link: 'https://github.com/Foufure13/Airsoft',
linkLabel: 'rclone', linkLabel: 'Airsoft sur GitHub',
},
{
id: 'cryptobot',
title: 'CryptoBot',
skills: ['API', 'Dashboard', 'Crypto', 'Trading'],
imageSrc: '/images/cryptobot-preview.png',
imageAlt: 'Aperçu du projet CryptoBot',
description:
'Outil complet de gestion de comptes crypto par API.\nSite web dashboard, sécurité compte / base de données.\nRécupération des flux marché (API), aide à la décision dinvestissement, trading sur portefeuille (API).\nDépôt GitHub privé.',
link: 'https://github.com/Foufure13',
linkLabel: 'Profil GitHub (dépôt privé)',
},
{
id: 'bot-instagram',
title: 'Bot autonome Instagram',
skills: ['Social', 'IA', 'API', 'Génération'],
imageSrc: 'https://www.thispersondoesnotexist.com',
imageAlt: 'Aperçu du bot Instagram',
description:
'Outils pour générer du contenu pour les réseaux sociaux et publier (yoloGenerator, Controler_prompt).\nGénération dimages générique dans un même style ou thème ; API pour communiquer avec un outil de génération dimage et ladministrer.\nProjet personnel — GitHub privé.',
link: 'https://github.com/Foufure13',
linkLabel: 'Profil GitHub (dépôt privé)',
},
{
id: 'site-partage-info',
title: 'Site web de partage dinformation',
skills: ['Web', 'Forum', 'Light'],
imageSrc: '/images/site-partage-preview.png',
imageAlt: 'Aperçu du site de partage',
description:
'Forum léger : possibilité dajouter du contenu pour tout utilisateur.\nProjet webjoke — GitHub privé.',
link: 'https://github.com/Foufure13',
linkLabel: 'Profil GitHub (dépôt privé)',
},
{
id: 'mecafollow',
title: 'mecaFollow',
skills: ['Web', 'Suivi', 'E-mail'],
imageSrc: '/images/mecafollow-preview.png',
imageAlt: 'Aperçu du projet mecaFollow',
description:
'Site web personnel ou professionnel : enregistrement et suivi dune voiture ou autre véhicule pour faciliter le suivi des entretiens (un ou plusieurs véhicules).\nNotifications par e-mail lorsquune intervention est nécessaire.\nProjet personnel et professionnel — GitHub sécurisé.',
link: 'https://github.com/Foufure13',
linkLabel: 'Profil GitHub (dépôt sécurisé)',
},
{
id: 'clipautocs2',
title: 'clipAutoCS2',
skills: ['Vidéo', 'YouTube'],
imageSrc: 'https://picsum.photos/seed/billeterie/640/360',
imageAlt: 'Aperçu du projet clipAutoCS2',
description:
'Création de clips highlight pro sur CS2.\nRécupération dun match e-sport et traitement des données.\nEnregistrement automatisé des meilleurs moments, montage vidéo, publication sur YouTube.\nProjet personnel — GitHub perso.',
link: 'https://github.com/Foufure13',
linkLabel: 'Profil GitHub',
},
{
id: 'wiki-documentation',
title: 'Wiki Documentation',
skills: ['Documentation', 'Docker', 'BookStack', 'Web'],
imageSrc: 'https://picsum.photos/seed/wiki-mira-ceti/640/360',
imageAlt: 'Aperçu du wiki BookStack Mira-Ceti',
description:
'Documentation des services, configurations et modes dutilisation pour serveurs et projets personnels ou professionnels.\nTechnologie : wiki BookStack, déployé avec Docker.\nInstance publique en lecture.',
link: 'https://wiki.mira-ceti.ovh',
linkLabel: 'wiki.mira-ceti.ovh',
},
{
id: 'seafile',
title: 'Seafile',
skills: ['Docker', 'Stockage', 'Web', 'API'],
imageSrc: 'https://picsum.photos/seed/seafile-stack/640/360',
imageAlt: 'Aperçu du déploiement Seafile',
description:
'Synchronisation et partage de fichiers en auto-hébergement (clients desktop et mobile), espaces d’équipe et historique de versions.\nStack conteneurisée, intégration sauvegardes et accès contrôlé.\nProjet personnel / professionnel.',
link: 'https://www.seafile.com/',
linkLabel: 'Seafile — site officiel',
},
{
id: 'gitea',
title: 'Gitea',
skills: ['Docker', 'Git', 'Web', 'API'],
imageSrc: 'https://picsum.photos/seed/gitea-selfhosted/640/360',
imageAlt: 'Aperçu du déploiement Gitea',
description:
'Forge Git légère : dépôts, issues, pull requests, actions CI.\nHébergement de code perso et projets internes derrière authentification.\nDéploiement Docker, sauvegardes et mise à jour documentés sur le wiki.',
link: 'https://gitea.io/',
linkLabel: 'Gitea — site officiel',
},
{
id: 'immich',
title: 'Immich',
skills: ['Docker', 'Photos', 'Web', 'Stockage'],
imageSrc: 'https://picsum.photos/seed/immich-gallery/640/360',
imageAlt: 'Aperçu du déploiement Immich',
description:
'Galerie photos et vidéos auto-hébergée : import depuis mobile, reconnaissance faciale optionnelle, albums et partage contrôlé.\nAlternative aux clouds grand public, stack Docker avec stockage objet ou disque.',
link: 'https://immich.app/',
linkLabel: 'Immich — site officiel',
},
{
id: 'authelia',
title: 'Authelia',
skills: ['Docker', 'Authentification', 'Cybersécurité', 'Web'],
imageSrc: 'https://picsum.photos/seed/authelia-sso/640/360',
imageAlt: 'Aperçu du déploiement Authelia',
description:
'Couche dauthentification et dautorisation (SSO, 2FA, politiques daccès) devant les services exposés.\nProtection des applications internes, intégration reverse proxy.\nConfiguration Docker et bonnes pratiques documentées sur le wiki.',
link: 'https://www.authelia.com/',
linkLabel: 'Authelia — site officiel',
},
{
id: 'jellyfin',
title: 'Jellyfin',
skills: ['Docker', 'Vidéo', 'Streaming', 'Web'],
imageSrc: 'https://picsum.photos/seed/jellyfin-media/640/360',
imageAlt: 'Aperçu du déploiement Jellyfin',
description:
'Serveur multimédia libre : films, séries, musique et livres audio pour le réseau local ou à distance.\nTranscodage optionnel, clients TV et mobile.\nStack Docker, bibliothèques et accès utilisateurs gérés localement.',
link: 'https://jellyfin.org/',
linkLabel: 'Jellyfin — site officiel',
}, },
] ]
+72 -1
View File
@@ -1,3 +1,53 @@
/** Catégories visuelles partagées (badges `.home-skill--{cat}` dans `index.css`). */
export const SKILL_CATEGORY_KEYS = ['green', 'blue', 'yellow', 'red']
/**
* Compétences listées hors `SKILLS` (ex. cartes projet) : couleur fixe par libellé.
* `Java` est aligné sur `JAVA` (même famille).
*/
const SKILL_CATEGORY_EXTRA = {
Java: 'yellow',
Web: 'green',
OSINT: 'blue',
Cybersécurité: 'blue',
Documentation: 'yellow',
Scraping: 'green',
API: 'blue',
'Base de données': 'blue',
'C#': 'yellow',
Multijoueur: 'yellow',
'Jeu vidéo': 'yellow',
Twitch: 'blue',
STT: 'blue',
Bot: 'green',
'E-mail': 'blue',
Social: 'blue',
Stats: 'blue',
C: 'red',
'Jeux vidéo': 'yellow',
Procédural: 'yellow',
Carte: 'green',
Association: 'green',
Vitrine: 'blue',
Dashboard: 'blue',
Crypto: 'yellow',
Trading: 'red',
IA: 'green',
Génération: 'green',
Forum: 'blue',
Light: 'green',
Suivi: 'blue',
Vidéo: 'blue',
YouTube: 'red',
Docker: 'blue',
BookStack: 'blue',
Stockage: 'yellow',
Git: 'blue',
Photos: 'yellow',
Authentification: 'blue',
Streaming: 'blue',
}
/** Compétences : couleur + courte phrase (page Profil). */ /** Compétences : couleur + courte phrase (page Profil). */
export const SKILLS = [ export const SKILLS = [
{ {
@@ -27,7 +77,7 @@ export const SKILLS = [
}, },
{ {
label: 'Hardware', label: 'Hardware',
cat: 'yellow', cat: 'blue',
desc: 'Utilisé en entreprise et au domicile. Diagnostic, réparation et modification de fonctionnement.', desc: 'Utilisé en entreprise et au domicile. Diagnostic, réparation et modification de fonctionnement.',
}, },
{ {
@@ -41,3 +91,24 @@ export const SKILLS = [
desc: 'Utilisé en entreprise et au domicile lecture de partitions et modification de cette derniere, afin de modifier le comportement d\'appareils android, modification de comportement de logicel : jeux video, etc.', desc: 'Utilisé en entreprise et au domicile lecture de partitions et modification de cette derniere, afin de modifier le comportement d\'appareils android, modification de comportement de logicel : jeux video, etc.',
}, },
] ]
/** Libellé → catégorie (profil + projets + tout libellé ajouté dans `SKILL_CATEGORY_EXTRA`). */
export const SKILL_CATEGORY_BY_LABEL = {
...Object.fromEntries(SKILLS.map(({ label, cat }) => [label, cat])),
...SKILL_CATEGORY_EXTRA,
}
/**
* Catégorie de couleur pour un badge compétence (stable pour un libellé donné).
* Libellés inconnus : hash déterministe sur les 4 catégories.
*/
export function getSkillCategory(label) {
const mapped = SKILL_CATEGORY_BY_LABEL[label]
if (mapped) return mapped
let h = 2166136261
for (let i = 0; i < label.length; i++) {
h ^= label.charCodeAt(i)
h = Math.imul(h, 16777619)
}
return SKILL_CATEGORY_KEYS[(h >>> 0) % SKILL_CATEGORY_KEYS.length]
}
+20
View File
@@ -0,0 +1,20 @@
import { FaGithub, FaLinkedinIn } from 'react-icons/fa6'
import { SiTryhackme } from 'react-icons/si'
export const SITE_SOCIAL = [
{
label: 'LinkedIn',
href: 'https://www.linkedin.com/',
Icon: FaLinkedinIn,
},
{
label: 'GitHub',
href: 'https://github.com/',
Icon: FaGithub,
},
{
label: 'TryHackMe',
href: 'https://tryhackme.com/',
Icon: SiTryhackme,
},
]
+47 -50
View File
@@ -182,6 +182,40 @@ code {
gap: 0.35rem 1.25rem; gap: 0.35rem 1.25rem;
} }
.topnav__social {
list-style: none;
margin: 0 0 0 auto;
padding: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.45rem;
}
.topnav__social-link {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--social-bg);
color: var(--text-h);
transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}
.topnav__social-link:hover {
box-shadow: var(--shadow);
border-color: var(--accent-border);
transform: translateY(-1px);
}
.topnav__social-icon {
width: 1.1rem;
height: 1.1rem;
}
.topnav__link { .topnav__link {
font-size: 0.95rem; font-size: 0.95rem;
color: var(--text); color: var(--text);
@@ -214,10 +248,10 @@ code {
align-items: stretch; align-items: stretch;
} }
/* Profil : bandeau type « box » pleine largeur au-dessus du contenu */ /* Profil : pleine largeur, sans bandeau sidebar */
.page--home-profil { .page--home-profil {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: auto minmax(0, 1fr); grid-template-rows: minmax(0, 1fr);
} }
.page--stack-only { .page--stack-only {
@@ -262,10 +296,6 @@ code {
min-width: 0; min-width: 0;
} }
.home-sidebar__inner--horizontal .home-sidebar__social {
justify-content: flex-start;
}
.home-sidebar__inner--horizontal .home-sidebar__extra { .home-sidebar__inner--horizontal .home-sidebar__extra {
width: auto; width: auto;
flex: 0 0 auto; flex: 0 0 auto;
@@ -292,11 +322,11 @@ code {
margin-inline: auto; margin-inline: auto;
} }
/* Même format visuel que la page Accueil (carré 260px max) */ /* Bandeau horizontal : même cadre carré max 300px */
.home-sidebar--horizontal .home-sidebar__photo { .home-sidebar--horizontal .home-sidebar__photo {
width: 100%; width: min(100%, 300px);
max-width: 260px; max-width: 300px;
height: auto; max-height: 300px;
aspect-ratio: 1; aspect-ratio: 1;
object-fit: cover; object-fit: cover;
flex-shrink: 0; flex-shrink: 0;
@@ -304,10 +334,14 @@ code {
} }
.home-sidebar__photo { .home-sidebar__photo {
width: 100%; display: block;
max-width: 260px; flex-shrink: 0;
width: min(100%, 300px);
max-width: 300px;
max-height: 300px;
aspect-ratio: 1; aspect-ratio: 1;
object-fit: cover; object-fit: cover;
object-position: center;
border-radius: 10px; border-radius: 10px;
border: 1px solid var(--border); border: 1px solid var(--border);
background: var(--border); background: var(--border);
@@ -322,40 +356,6 @@ code {
letter-spacing: -0.02em; letter-spacing: -0.02em;
} }
.home-sidebar__social {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.65rem;
}
.home-sidebar__social-link {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 10px;
border: 1px solid var(--border);
background: var(--social-bg);
color: var(--text-h);
transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}
.home-sidebar__social-link:hover {
box-shadow: var(--shadow);
border-color: var(--accent-border);
transform: translateY(-1px);
}
.home-sidebar__social-icon {
width: 1.25rem;
height: 1.25rem;
}
.home-sidebar__extra { .home-sidebar__extra {
width: 100%; width: 100%;
margin-top: 0.25rem; margin-top: 0.25rem;
@@ -391,10 +391,6 @@ code {
align-items: center; align-items: center;
} }
.home-sidebar__inner--horizontal .home-sidebar__social {
justify-content: center;
}
.home-sidebar__inner--horizontal .home-sidebar__extra { .home-sidebar__inner--horizontal .home-sidebar__extra {
margin-left: 0; margin-left: 0;
width: 100%; width: 100%;
@@ -816,6 +812,7 @@ code {
line-height: 150%; line-height: 150%;
color: var(--text); color: var(--text);
flex: 1 1 auto; flex: 1 1 auto;
white-space: pre-line;
} }
.projet-card__link { .projet-card__link {
+13 -18
View File
@@ -1,13 +1,11 @@
import { ContentStack } from '../components/ContentStack.jsx' import { ContentStack } from '../components/ContentStack.jsx'
import { HomeAutobiographiePanel } from '../components/HomeAutobiographiePanel.jsx'
import { HomeEducationTimeline } from '../components/HomeEducationTimeline.jsx' import { HomeEducationTimeline } from '../components/HomeEducationTimeline.jsx'
import { HomeIntroPanel } from '../components/HomeIntroPanel.jsx' import { HomeIntroPanel } from '../components/HomeIntroPanel.jsx'
import { HomeProfilCompetencesPanel } from '../components/HomeProfilCompetencesPanel.jsx' import { HomeProfilCompetencesPanel } from '../components/HomeProfilCompetencesPanel.jsx'
import { HomeProfilQualitesPanel } from '../components/HomeProfilQualitesPanel.jsx'
import { HomeSkillsBlock } from '../components/HomeSkillsBlock.jsx' import { HomeSkillsBlock } from '../components/HomeSkillsBlock.jsx'
import { HomeSidebar } from '../components/HomeSidebar.jsx' import { HomeSidebar } from '../components/HomeSidebar.jsx'
/** Accueil : sidebar à gauche. Profil (`variant="profil"`) : bandeau profil horizontal au-dessus du contenu. */ /** Accueil : sidebar à gauche. Profil (`variant="profil"`) : contenu sans bandeau ni autobiographie. */
export function HomePageView({ variant = 'home' }) { export function HomePageView({ variant = 'home' }) {
const isProfil = variant === 'profil' const isProfil = variant === 'profil'
@@ -17,25 +15,22 @@ export function HomePageView({ variant = 'home' }) {
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
> >
<aside {!isProfil ? (
className={['home-sidebar', isProfil ? 'home-sidebar--horizontal' : null] <aside className="home-sidebar" aria-label="Profil">
.filter(Boolean) <HomeSidebar layout="vertical" />
.join(' ')} </aside>
aria-label="Profil" ) : null}
>
<HomeSidebar layout={isProfil ? 'horizontal' : 'vertical'} />
</aside>
<div className="home-main"> <div className="home-main">
<ContentStack <ContentStack
panel1={ panel1={isProfil ? null : <HomeSkillsBlock />}
isProfil ? <HomeAutobiographiePanel /> : <HomeIntroPanel />
}
panel1b={ panel1b={
isProfil ? <HomeProfilCompetencesPanel /> : <HomeSkillsBlock /> isProfil ? (
} <HomeProfilCompetencesPanel />
panel2={ ) : (
isProfil ? <HomeProfilQualitesPanel /> : <HomeEducationTimeline /> <HomeEducationTimeline />
)
} }
panel2={isProfil ? null : <HomeIntroPanel />}
/> />
</div> </div>
</div> </div>