cool update
This commit is contained in:
@@ -1,17 +1,19 @@
|
||||
export function ContentStack({ panel1, panel1b, panel2, panel3 }) {
|
||||
return (
|
||||
<div className="content-stack" aria-label="Contenu principal">
|
||||
<section
|
||||
className={[
|
||||
'content-stack__panel',
|
||||
panel1 ? 'content-stack__panel--padded' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
aria-label="Bloc 1"
|
||||
>
|
||||
{panel1}
|
||||
</section>
|
||||
{panel1 != null ? (
|
||||
<section
|
||||
className={[
|
||||
'content-stack__panel',
|
||||
panel1 ? 'content-stack__panel--padded' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
aria-label="Bloc 1"
|
||||
>
|
||||
{panel1}
|
||||
</section>
|
||||
) : null}
|
||||
{panel1b != null ? (
|
||||
<section
|
||||
className={[
|
||||
@@ -23,19 +25,21 @@ export function ContentStack({ panel1, panel1b, panel2, panel3 }) {
|
||||
{panel1b}
|
||||
</section>
|
||||
) : null}
|
||||
<section
|
||||
className={[
|
||||
'content-stack__panel',
|
||||
panel2 ? 'content-stack__panel--padded' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
aria-label={
|
||||
panel3 != null || panel1b != null ? 'Bloc 3' : 'Bloc 2'
|
||||
}
|
||||
>
|
||||
{panel2}
|
||||
</section>
|
||||
{panel2 != null ? (
|
||||
<section
|
||||
className={[
|
||||
'content-stack__panel',
|
||||
panel2 ? 'content-stack__panel--padded' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
aria-label={
|
||||
panel3 != null || panel1b != null ? 'Bloc 3' : 'Bloc 2'
|
||||
}
|
||||
>
|
||||
{panel2}
|
||||
</section>
|
||||
) : null}
|
||||
{panel3 != null ? (
|
||||
<section
|
||||
className={[
|
||||
|
||||
@@ -22,13 +22,14 @@ const EVENTS = [
|
||||
sequence: 0,
|
||||
side: 'left',
|
||||
title: 'CAP Mécanique auto',
|
||||
description: 'AF13',
|
||||
description: 'AF13, Alternance',
|
||||
},
|
||||
{
|
||||
period: '2021-2022',
|
||||
sequence: 0,
|
||||
side: 'right',
|
||||
title: 'CAP Plomberie chauffagerie',
|
||||
description: '3 stages de 3 mois',
|
||||
},
|
||||
{
|
||||
period: '2022-2025',
|
||||
@@ -38,7 +39,7 @@ const EVENTS = [
|
||||
description: 'OpenMotion',
|
||||
},
|
||||
{
|
||||
period: '2022-2026',
|
||||
period: '2024-2026',
|
||||
sequence: 0,
|
||||
side: 'right',
|
||||
title: 'Installation et configuration de serveurs personnels',
|
||||
|
||||
@@ -4,11 +4,9 @@ const TAGS = [
|
||||
{ label: 'Curieux', tone: 'pos' },
|
||||
{ label: 'Polyvalent', tone: 'pos' },
|
||||
{ label: 'Autodidacte', tone: 'pos' },
|
||||
{ label: 'Logique', tone: 'pos' },
|
||||
{ label: 'Persévérance', tone: 'pos' },
|
||||
{ label: "Esprit d'Analyse", tone: 'pos' },
|
||||
{ label: 'Autonomie', tone: 'pos' },
|
||||
{ label: "Capacité d'Adaptation", tone: 'pos' },
|
||||
{ label: 'Impatient', tone: 'neg' },
|
||||
{ label: 'Besoin de comprendre', 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>
|
||||
)
|
||||
}
|
||||
@@ -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' }) {
|
||||
const isHorizontal = layout === 'horizontal'
|
||||
|
||||
@@ -34,33 +12,15 @@ export function HomeSidebar({ layout = 'vertical' }) {
|
||||
>
|
||||
<img
|
||||
className="home-sidebar__photo"
|
||||
src="https://www.thispersondoesnotexist.com"
|
||||
alt=""
|
||||
width={280}
|
||||
height={280}
|
||||
src="/images/profile-photo.png"
|
||||
alt="Photo de profil"
|
||||
width={300}
|
||||
height={300}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<div className="home-sidebar__cluster">
|
||||
<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 className="home-sidebar__extra">
|
||||
<ul className="home-sidebar__extra-list">
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { PROJETS } from '../data/projets.js'
|
||||
|
||||
const SKILL_CATS = ['green', 'blue', 'yellow', 'red']
|
||||
import { getSkillCategory } from '../data/skills.js'
|
||||
|
||||
export function ProjetsGrid() {
|
||||
return (
|
||||
<ul className="projets-grid">
|
||||
{PROJETS.map((projet, index) => (
|
||||
{PROJETS.map((projet) => (
|
||||
<li key={projet.id} className="projets-grid__cell">
|
||||
<article className="projet-card" aria-labelledby={`projet-title-${projet.id}`}>
|
||||
<div className="projet-card__media">
|
||||
@@ -23,12 +22,10 @@ export function ProjetsGrid() {
|
||||
{projet.title}
|
||||
</h2>
|
||||
<ul className="projet-card__skills" aria-label="Compétences">
|
||||
{projet.skills.map((skill, i) => (
|
||||
{projet.skills.map((skill) => (
|
||||
<li key={skill}>
|
||||
<span
|
||||
className={`home-skill home-skill--${
|
||||
SKILL_CATS[(index + i) % SKILL_CATS.length]
|
||||
}`}
|
||||
className={`home-skill home-skill--${getSkillCategory(skill)}`}
|
||||
>
|
||||
{skill}
|
||||
</span>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createElement } from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { SITE_SOCIAL } from '../data/socialLinks.js'
|
||||
|
||||
function getNavLinkClassName({ isActive }) {
|
||||
return ['topnav__link', isActive ? 'is-active' : null].filter(Boolean).join(' ')
|
||||
@@ -21,6 +23,24 @@ export function TopNav() {
|
||||
<NavLink to="/projets" className={getNavLinkClassName}>
|
||||
Projets
|
||||
</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>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -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
@@ -1,21 +1,10 @@
|
||||
/** Cartes projet (page Projets) : titre, compétences, visuel, texte, lien externe. */
|
||||
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',
|
||||
title: 'androidpayload',
|
||||
skills: ['Linux', 'Bash', 'Java', 'Hardware'],
|
||||
imageSrc: 'https://www.thispersondoesnotexist.com/',
|
||||
imageSrc: 'https://picsum.photos/seed/pacmanc/640/360',
|
||||
imageAlt: 'Visuel du projet androidpayload',
|
||||
description:
|
||||
'Application pour récupérer tous les droits sur un appareil Android (ROOT).',
|
||||
@@ -23,92 +12,212 @@ export const PROJETS = [
|
||||
linkLabel: 'Magisk sur GitHub',
|
||||
},
|
||||
{
|
||||
id: 'homelab-stack',
|
||||
title: 'HomelabStack',
|
||||
skills: ['Docker', 'Linux', 'Ansible', 'Nginx'],
|
||||
imageSrc: 'https://picsum.photos/seed/homelab/640/360',
|
||||
imageAlt: 'Schéma de services conteneurisés',
|
||||
id: 'wikicyber',
|
||||
title: 'WIKICYBER',
|
||||
skills: ['Web', 'OSINT', 'Cybersécurité', 'Documentation'],
|
||||
imageSrc: '/images/wikicyber-preview.png',
|
||||
imageAlt: 'Aperçu du projet WIKICYBER',
|
||||
description:
|
||||
'Automatisation du déploiement de services auto-hébergés, reverse proxy et sauvegardes planifiées.',
|
||||
link: 'https://github.com/ansible/ansible',
|
||||
linkLabel: 'Ansible sur GitHub',
|
||||
'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/Foufure13/wiki.cyber',
|
||||
linkLabel: 'wiki.cyber sur GitHub',
|
||||
},
|
||||
{
|
||||
id: 'capteurs-maison',
|
||||
title: 'CapteursMaison',
|
||||
skills: ['C', 'MQTT', 'ESP32', 'Électronique'],
|
||||
imageSrc: 'https://picsum.photos/seed/iotcapteurs/640/360',
|
||||
imageAlt: 'Maquette capteurs connectés',
|
||||
id: 'lk-find-post',
|
||||
title: 'LK-Find-Post',
|
||||
skills: ['Scraping', 'API', 'Base de données', 'Python'],
|
||||
imageSrc: '/images/lk-find-post-preview.png',
|
||||
imageAlt: 'Aperçu du projet LK-Find-Post',
|
||||
description:
|
||||
'Firmware embarqué et passerelle MQTT pour relevés de température et humidité en temps réel.',
|
||||
link: 'https://mqtt.org/',
|
||||
linkLabel: 'Protocole MQTT',
|
||||
'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://github.com/Foufure13/Linkedin-find-post',
|
||||
linkLabel: 'Linkedin-find-post sur GitHub',
|
||||
},
|
||||
{
|
||||
id: 'veille-marche',
|
||||
title: 'VeilleMarché',
|
||||
skills: ['Python', 'REST', 'SQLite', 'CLI'],
|
||||
imageSrc: 'https://picsum.photos/seed/chartscli/640/360',
|
||||
imageAlt: 'Courbes et terminal',
|
||||
id: 'rotmg-like',
|
||||
title: 'ROTMG-LIKE',
|
||||
skills: ['C#', 'Multijoueur', 'Jeu vidéo'],
|
||||
imageSrc: '/images/rotmglike-preview.png',
|
||||
imageAlt: 'Aperçu du jeu ROTMG-LIKE',
|
||||
description:
|
||||
'Outil en ligne de commande pour agréger des cours, historiser en local et déclencher des alertes.',
|
||||
link: 'https://docs.python.org/3/library/argparse.html',
|
||||
linkLabel: 'argparse (Python)',
|
||||
'Jeu vidéo en C# multijoueur / solo.\nGraphisme et gameplay inspirés du jeu d’origine, 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 d’ennemis : à leur mort, gain d’XP et 1 drop d’arme par ennemi.\nProjet scolaire personnel — GitHub.',
|
||||
link: 'https://github.com/Foufure13/ROTMG-LIKE-Csharp',
|
||||
linkLabel: 'ROTMG-LIKE sur GitHub',
|
||||
},
|
||||
{
|
||||
id: 'photo-archiver',
|
||||
title: 'PhotoArchiver',
|
||||
skills: ['Rust', 'CLI', 'Filesystem', 'EXIF'],
|
||||
imageSrc: 'https://picsum.photos/seed/photoarch/640/360',
|
||||
imageAlt: 'Dossiers et vignettes photo',
|
||||
id: 'twitch-bot-interactif',
|
||||
title: 'Twitch Bot Interactif',
|
||||
skills: ['Twitch', 'API', 'STT', 'Bot'],
|
||||
imageSrc: '/images/twitch-bot-preview.png',
|
||||
imageAlt: 'Aperçu du bot Twitch',
|
||||
description:
|
||||
'Indexation d’une photothèque locale, détection de doublons par empreinte et renommage par date EXIF.',
|
||||
link: 'https://exiftool.org/',
|
||||
linkLabel: 'ExifTool',
|
||||
'Bot connecté au flux de chat et au streaming d’un 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://github.com/Foufure13/twitchBot-intelligent',
|
||||
linkLabel: 'twitchBot-intelligent sur GitHub',
|
||||
},
|
||||
{
|
||||
id: 'net-pulse',
|
||||
title: 'NetPulse',
|
||||
skills: ['Go', 'Prometheus', 'Grafana', 'HTTP'],
|
||||
imageSrc: 'https://picsum.photos/seed/netpulse/640/360',
|
||||
imageAlt: 'Graphiques de métriques réseau',
|
||||
id: 'billeterie-cinema',
|
||||
title: 'Billeterie cinéma',
|
||||
skills: ['Web', 'E-mail', 'Base de données'],
|
||||
imageSrc: '/images/billeterie-cinema-preview.png',
|
||||
imageAlt: 'Aperçu de la billetterie cinéma',
|
||||
description:
|
||||
'Sonde légère et tableaux de bord pour suivre latence, codes HTTP et disponibilité des services du lab.',
|
||||
link: 'https://prometheus.io/',
|
||||
linkLabel: 'Prometheus',
|
||||
'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://github.com/Foufure13/Billetterie-en-ligne',
|
||||
linkLabel: 'Billetterie-en-ligne sur GitHub',
|
||||
},
|
||||
{
|
||||
id: 'bench-auto',
|
||||
title: 'BenchAuto',
|
||||
skills: ['Bash', 'Jenkins', 'Git', 'curl'],
|
||||
imageSrc: 'https://picsum.photos/seed/benchauto/640/360',
|
||||
imageAlt: 'Pipeline et rapports de tests',
|
||||
id: 'realmaster',
|
||||
title: 'RealMaster',
|
||||
skills: ['Web', 'Social', 'Stats'],
|
||||
imageSrc: 'https://picsum.photos/seed/lkfindpost/640/360',
|
||||
imageAlt: 'Aperçu du projet RealMaster',
|
||||
description:
|
||||
'Jobs planifiés pour enchaîner scénarios de charge, collecter les métriques et publier un rapport HTML.',
|
||||
link: 'https://www.jenkins.io/',
|
||||
linkLabel: 'Jenkins',
|
||||
'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://github.com/Foufure13/realmaster',
|
||||
linkLabel: 'realmaster sur GitHub',
|
||||
},
|
||||
{
|
||||
id: 'mesh-relay',
|
||||
title: 'MeshRelay',
|
||||
skills: ['Zigbee', 'Node', 'MQTT', 'Home Assistant'],
|
||||
imageSrc: 'https://picsum.photos/seed/meshrelay/640/360',
|
||||
imageAlt: 'Passerelle et objets connectés',
|
||||
id: 'pacman-c',
|
||||
title: 'Pacman C',
|
||||
skills: ['C', 'Jeux vidéo', 'Procédural', 'Carte'],
|
||||
imageSrc: '/images/pacman-c-preview.png',
|
||||
imageAlt: 'Aperçu du Pacman en C',
|
||||
description:
|
||||
'Automatisations domotiques : capteurs Zigbee, scénarios jour/nuit et notifications sur événements.',
|
||||
link: 'https://www.home-assistant.io/',
|
||||
linkLabel: 'Home Assistant',
|
||||
'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://github.com/Foufure13/Pacman_C',
|
||||
linkLabel: 'Pacman_C sur GitHub',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'sync-vault',
|
||||
title: 'SyncVault',
|
||||
skills: ['rsync', 'S3', 'LUKS', 'Cron'],
|
||||
imageSrc: 'https://picsum.photos/seed/syncvault/640/360',
|
||||
imageAlt: 'Sauvegarde et chiffrement',
|
||||
id: 'airsoft-vitrine',
|
||||
title: 'Airsoft vitrine',
|
||||
skills: ['Web', 'Association', 'Vitrine'],
|
||||
imageSrc: '/images/airsoft-vitrine-preview.png',
|
||||
imageAlt: 'Aperçu du site Airsoft',
|
||||
description:
|
||||
'Volumes chiffrés, sauvegardes incrémentielles locales et miroir distant compatible stockage objet.',
|
||||
link: 'https://rclone.org/',
|
||||
linkLabel: 'rclone',
|
||||
'Site web créé pour une association.\nProjet personnel — GitHub.',
|
||||
link: 'https://github.com/Foufure13/Airsoft',
|
||||
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 d’investissement, 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 d’images générique dans un même style ou thème ; API pour communiquer avec un outil de génération d’image et l’administrer.\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 d’information',
|
||||
skills: ['Web', 'Forum', 'Light'],
|
||||
imageSrc: '/images/site-partage-preview.png',
|
||||
imageAlt: 'Aperçu du site de partage',
|
||||
description:
|
||||
'Forum léger : possibilité d’ajouter 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 d’une voiture ou autre véhicule pour faciliter le suivi des entretiens (un ou plusieurs véhicules).\nNotifications par e-mail lorsqu’une 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 d’un 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 d’utilisation 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 d’authentification et d’autorisation (SSO, 2FA, politiques d’accè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
@@ -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). */
|
||||
export const SKILLS = [
|
||||
{
|
||||
@@ -27,7 +77,7 @@ export const SKILLS = [
|
||||
},
|
||||
{
|
||||
label: 'Hardware',
|
||||
cat: 'yellow',
|
||||
cat: 'blue',
|
||||
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.',
|
||||
},
|
||||
]
|
||||
|
||||
/** 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]
|
||||
}
|
||||
|
||||
@@ -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
@@ -182,6 +182,40 @@ code {
|
||||
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 {
|
||||
font-size: 0.95rem;
|
||||
color: var(--text);
|
||||
@@ -214,10 +248,10 @@ code {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* Profil : bandeau type « box » pleine largeur au-dessus du contenu */
|
||||
/* Profil : pleine largeur, sans bandeau sidebar */
|
||||
.page--home-profil {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
grid-template-rows: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.page--stack-only {
|
||||
@@ -262,10 +296,6 @@ code {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.home-sidebar__inner--horizontal .home-sidebar__social {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.home-sidebar__inner--horizontal .home-sidebar__extra {
|
||||
width: auto;
|
||||
flex: 0 0 auto;
|
||||
@@ -292,11 +322,11 @@ code {
|
||||
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 {
|
||||
width: 100%;
|
||||
max-width: 260px;
|
||||
height: auto;
|
||||
width: min(100%, 300px);
|
||||
max-width: 300px;
|
||||
max-height: 300px;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
@@ -304,10 +334,14 @@ code {
|
||||
}
|
||||
|
||||
.home-sidebar__photo {
|
||||
width: 100%;
|
||||
max-width: 260px;
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
width: min(100%, 300px);
|
||||
max-width: 300px;
|
||||
max-height: 300px;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--border);
|
||||
@@ -322,40 +356,6 @@ code {
|
||||
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 {
|
||||
width: 100%;
|
||||
margin-top: 0.25rem;
|
||||
@@ -391,10 +391,6 @@ code {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.home-sidebar__inner--horizontal .home-sidebar__social {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.home-sidebar__inner--horizontal .home-sidebar__extra {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
@@ -816,6 +812,7 @@ code {
|
||||
line-height: 150%;
|
||||
color: var(--text);
|
||||
flex: 1 1 auto;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.projet-card__link {
|
||||
|
||||
+13
-18
@@ -1,13 +1,11 @@
|
||||
import { ContentStack } from '../components/ContentStack.jsx'
|
||||
import { HomeAutobiographiePanel } from '../components/HomeAutobiographiePanel.jsx'
|
||||
import { HomeEducationTimeline } from '../components/HomeEducationTimeline.jsx'
|
||||
import { HomeIntroPanel } from '../components/HomeIntroPanel.jsx'
|
||||
import { HomeProfilCompetencesPanel } from '../components/HomeProfilCompetencesPanel.jsx'
|
||||
import { HomeProfilQualitesPanel } from '../components/HomeProfilQualitesPanel.jsx'
|
||||
import { HomeSkillsBlock } from '../components/HomeSkillsBlock.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' }) {
|
||||
const isProfil = variant === 'profil'
|
||||
|
||||
@@ -17,25 +15,22 @@ export function HomePageView({ variant = 'home' }) {
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<aside
|
||||
className={['home-sidebar', isProfil ? 'home-sidebar--horizontal' : null]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
aria-label="Profil"
|
||||
>
|
||||
<HomeSidebar layout={isProfil ? 'horizontal' : 'vertical'} />
|
||||
</aside>
|
||||
{!isProfil ? (
|
||||
<aside className="home-sidebar" aria-label="Profil">
|
||||
<HomeSidebar layout="vertical" />
|
||||
</aside>
|
||||
) : null}
|
||||
<div className="home-main">
|
||||
<ContentStack
|
||||
panel1={
|
||||
isProfil ? <HomeAutobiographiePanel /> : <HomeIntroPanel />
|
||||
}
|
||||
panel1={isProfil ? null : <HomeSkillsBlock />}
|
||||
panel1b={
|
||||
isProfil ? <HomeProfilCompetencesPanel /> : <HomeSkillsBlock />
|
||||
}
|
||||
panel2={
|
||||
isProfil ? <HomeProfilQualitesPanel /> : <HomeEducationTimeline />
|
||||
isProfil ? (
|
||||
<HomeProfilCompetencesPanel />
|
||||
) : (
|
||||
<HomeEducationTimeline />
|
||||
)
|
||||
}
|
||||
panel2={isProfil ? null : <HomeIntroPanel />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user