cool update
@@ -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>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 661 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 95 KiB |
@@ -1,6 +1,7 @@
|
|||||||
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">
|
||||||
|
{panel1 != null ? (
|
||||||
<section
|
<section
|
||||||
className={[
|
className={[
|
||||||
'content-stack__panel',
|
'content-stack__panel',
|
||||||
@@ -12,6 +13,7 @@ export function ContentStack({ panel1, panel1b, panel2, panel3 }) {
|
|||||||
>
|
>
|
||||||
{panel1}
|
{panel1}
|
||||||
</section>
|
</section>
|
||||||
|
) : null}
|
||||||
{panel1b != null ? (
|
{panel1b != null ? (
|
||||||
<section
|
<section
|
||||||
className={[
|
className={[
|
||||||
@@ -23,6 +25,7 @@ export function ContentStack({ panel1, panel1b, panel2, panel3 }) {
|
|||||||
{panel1b}
|
{panel1b}
|
||||||
</section>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
|
{panel2 != null ? (
|
||||||
<section
|
<section
|
||||||
className={[
|
className={[
|
||||||
'content-stack__panel',
|
'content-stack__panel',
|
||||||
@@ -36,6 +39,7 @@ export function ContentStack({ panel1, panel1b, panel2, panel3 }) {
|
|||||||
>
|
>
|
||||||
{panel2}
|
{panel2}
|
||||||
</section>
|
</section>
|
||||||
|
) : null}
|
||||||
{panel3 != null ? (
|
{panel3 != null ? (
|
||||||
<section
|
<section
|
||||||
className={[
|
className={[
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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.',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -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 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://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 d’une photothèque locale, détection de doublons par empreinte et renommage par date EXIF.',
|
'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://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 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',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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]
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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(' ')}
|
|
||||||
aria-label="Profil"
|
|
||||||
>
|
|
||||||
<HomeSidebar layout={isProfil ? 'horizontal' : 'vertical'} />
|
|
||||||
</aside>
|
</aside>
|
||||||
|
) : null}
|
||||||
<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>
|
||||||
|
|||||||