refonte: portfolio statique Astro (design moderne développeur)
Some checks failed
Deploy production / build-deploy (push) Failing after 56s

- Remplace l'ancien Astro hybrid + admin par un site one-page statique (sortie static)
- Thème clair/sombre sans flash, contenu typé (src/data/content.ts), zéro admin
- Icônes simple-icons (SVG monochrome), polices auto-hébergées (RGPD)
- SEO complet : canonical, Open Graph, Twitter Card, JSON-LD Person, robots.txt, sitemap.xml, image OG 1200x630
- CI Gitea de déploiement FTPS vers Plesk (.gitea/workflows/prod.yml)
This commit is contained in:
Johan LEROY
2026-06-19 16:03:09 +02:00
parent dc4e3820c4
commit ce17bf590b
189 changed files with 1836 additions and 9872 deletions

View File

@@ -1,7 +0,0 @@
# Admin credentials — générer via `npm run hash:password -- 'monMotDePasse'`
ADMIN_PASSWORD_HASH=
ADMIN_JWT_SECRET=
# Port de production (Plesk Node.js)
PORT=3100
HOST=0.0.0.0

46
.gitea/workflows/prod.yml Normal file
View File

@@ -0,0 +1,46 @@
# Déploiement production — johanleroy.fr
# Push sur `main` → contrôle de types → build statique Astro → mirror FTP vers Plesk.
# Secrets requis dans Gitea (Settings → Actions → Secrets) :
# FTP_HOST, FTP_USER, FTP_PASSWORD, FTP_TARGET_DIR (chemin docroot, ex. /httpdocs/johanleroy/public)
name: Deploy production
on:
push:
branches:
- main
jobs:
build-deploy:
runs-on: ubuntu-latest
container: node:22-bullseye
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: npm install --no-audit --no-fund
- name: Type check
run: npm run check
- name: Build static site
run: npm run build
- name: Verify build output
run: ls -la dist
- name: Deploy via FTPS (Plesk)
run: |
apt-get update && apt-get install -y lftp
# FTPS forcé (jamais de fallback en clair) + vérification du certificat (CA système).
# Si le déploiement échoue sur une erreur de certificat, c'est que le cert FTP ne
# correspond pas à FTP_HOST → préférer SFTP (clé SSH) plutôt que de désactiver la vérif.
lftp -e "
set ftp:ssl-force true;
set ftp:ssl-protect-data true;
set ssl:verify-certificate yes;
open ${{ secrets.FTP_HOST }};
user ${{ secrets.FTP_USER }} ${{ secrets.FTP_PASSWORD }};
mirror --reverse --delete --verbose dist/ ${{ secrets.FTP_TARGET_DIR }};
bye
"

View File

@@ -1,26 +0,0 @@
name: Tests
on:
push:
branches: [main, dev]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:22-bullseye
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci --no-audit --no-fund
- name: Astro check (types + lint hints)
run: npm run check
- name: Vitest
run: npm test
- name: Astro build (vérifie que le build prod passe)
run: npm run build

32
.gitignore vendored
View File

@@ -1,35 +1,21 @@
# build output
# build
dist/
.astro/
# deps
# dépendances
node_modules/
# env
# env / OS / éditeurs
.env
.env.production
.env.local
.env.*
.DS_Store
.idea/
.vscode/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# editors
.vscode/*
!.vscode/extensions.json
.idea/
# macOS
.DS_Store
# temp
*.log
# runtime data (uploads + JSON content edited via /admin)
# kept on the server, not deployed via FTP
data/
# test artifacts (coverage)
coverage/
# instructions IA locales (gardées en local, hors repo public)
CLAUDE.md

View File

@@ -1,4 +0,0 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

170
README.md
View File

@@ -1,161 +1,37 @@
# Portfolio — Johan Leroy
# Portfolio — johanleroy.fr
Site vitrine personnel en **Astro 6 hybrid** (SSG public + SSR admin), déployé sur Plesk Node.js.
🎧 Dev le jour, DJ la nuit — stack cyberpunk dark, contenu éditable sans recompilation.
---
## Stack
- **Astro 6** (`output: 'static'` + adapter Node standalone — hybrid via `prerender: false` sur `/admin/*` et `/api/*`)
- **React 19** (îlots interactifs)
- **Tailwind CSS 4** + custom design system cyberpunk
- **Framer Motion** + **GSAP** + **Lenis** (animations)
- **@react-three/fiber** (hero 3D disco ball)
- **Zod** (validation schémas de contenu)
- **jose** + **bcryptjs** (auth admin JWT cookie)
---
Site vitrine **one-page** de Johan Leroy, développeur fullstack.
Stack : **Astro 6** (sortie 100 % statique), TypeScript, CSS (variables de thème), zéro îlot
framework. Thème **clair/sombre** persistant, polices **auto-hébergées** (RGPD-friendly), icônes
de technos via **simple-icons** (SVG inline monochromes).
## Développement
```bash
npm install
npm run dev
npm run dev # serveur de dev (http://localhost:4321)
npm run check # astro check (typage)
npm run build # build statique → dist/
npm run preview # prévisualise le build
```
→ http://localhost:3100
## Modifier le contenu
## Édition du contenu
Tout le contenu (profil, projets, expériences, formation, stack, centres d'intérêt) vit dans
**un seul fichier typé** : [`src/data/content.ts`](src/data/content.ts).
L'URL du bouton « café » s'y trouve aussi (`coffeeUrl`).
Tout le contenu éditable est dans `public/content/*.json` :
## Assets
| Fichier | Rôle |
|---|---|
| `profile.json` | Profil, photo, CTA hero, réseaux sociaux |
| `skills.json` | Compétences techniques (primary + secondary) |
| `experiences.json` | Parcours pro + compétences techniques & transverses |
| `formations.json` | Diplômes et formations |
| `projects.json` | Portfolio de projets |
| `interests.json` | Centres d'intérêt |
| `site.json` | Meta SEO + flags features |
- `public/images/` — logo, photos de profil, logos d'entreprises, visuels de projets.
- `public/fonts/` — Inter + JetBrains Mono (`.woff2`, auto-hébergées).
- `public/cv.pdf` — CV téléchargeable depuis le hero.
Tu peux éditer :
- **Via `/admin`** (interface web protégée par mot de passe) — recommandé en prod
- **Manuellement** en éditant les JSON et en rechargeant la page
## Déploiement
**Aucune recompilation n'est nécessaire** : les fichiers sont servis statiquement côté site public, et relus au runtime côté admin.
**Automatique via Gitea Actions** (`.gitea/workflows/prod.yml`) : tout push sur `main` lance
`astro check` + `npm run build`, puis envoie `dist/` en FTP vers Plesk.
Secrets à définir dans Gitea (Settings → Actions → Secrets) :
`FTP_HOST`, `FTP_USER`, `FTP_PASSWORD`, `FTP_TARGET_DIR` (chemin docroot, ex. `/httpdocs/johanleroy/public`).
---
## Admin
### Configuration initiale
1. Générer un hash de mot de passe :
```bash
npm run hash:password -- monMotDePasse
```
2. Copier les deux lignes générées (`ADMIN_PASSWORD_HASH` et `ADMIN_JWT_SECRET`) dans un fichier `.env` (local) ou dans les variables d'environnement Plesk (prod).
### Accès
→ http://localhost:3100/admin (ou https://johanleroy.fr/admin en prod)
Login via mot de passe, cookie httpOnly 7 jours.
---
## Déploiement Plesk Node.js
### Variables d'environnement à définir sur Plesk
| Variable | Valeur |
|---|---|
| `ADMIN_PASSWORD_HASH` | hash bcrypt généré |
| `ADMIN_JWT_SECRET` | secret JWT aléatoire |
| `PORT` | `3100` (ou ce que Plesk impose) |
| `HOST` | `0.0.0.0` |
| `NODE_ENV` | `production` |
### Paramètres Plesk Node.js
- **Node.js version** : 22+ (24.x OK)
- **Application root** : `/httpdocs/johanleroy`
- **Document root Apache** : `/httpdocs/johanleroy/dist/client` (Apache sert les statiques buildés, le reste passe en proxy vers Node)
- **Application startup file** : `dist/server/entry.mjs`
- **Application URL** : https://johanleroy.fr (SSL Let's Encrypt obligatoire pour les cookies admin)
### Déploiement manuel
1. **Build local** : `npm run build` (vérifier que `dist/server/entry.mjs` est généré)
2. **Upload FTP/SFTP** vers le répertoire de l'application Plesk (ex : `/httpdocs/johanleroy/`) :
- `dist/` (écrase l'existant)
- `public/` (écrase l'existant)
- `package.json`
- `package-lock.json`
3. **Plesk → Node.js → NPM install** (à faire si `package.json` a changé)
4. **Plesk → Node.js → Restart App** pour recharger le nouveau bundle
**Ne JAMAIS écraser** : `data/`, `.env`, `node_modules/` côté serveur.
---
## Structure
```
src/
├── components/
│ ├── Header.astro, Footer.astro, SEO.astro
│ ├── ProjectCard.astro
│ └── islands/ # composants React hydratés
│ ├── Hero3D.tsx
│ ├── RolesMorph.tsx
│ ├── Reveal.tsx
│ ├── SkillCard.tsx
│ ├── SmoothScroll.tsx
│ └── CustomCursor.tsx
├── content/schemas/ # schémas Zod (typages + validation)
├── lib/
│ ├── auth.ts # JWT cookie admin
│ └── content.ts # lecture/écriture JSON
├── layouts/BaseLayout.astro
├── middleware.ts # protection routes /admin et /api
├── pages/
│ ├── index.astro
│ ├── formations.astro
│ ├── experience.astro
│ ├── projets.astro
│ ├── 404.astro
│ ├── admin/
│ │ ├── login.astro
│ │ ├── index.astro
│ │ └── edit/[section].astro
│ └── api/
│ ├── auth/login.ts, logout.ts
│ ├── content/[section].ts
│ └── upload.ts
└── styles/global.css
public/
├── content/*.json # données éditables
├── img/ # images (icônes, entreprises, projets, photos)
└── assets/cv.pdf
```
---
## Direction artistique
**Cyberpunk Dark** — noir profond + cyan/magenta néon.
- Palette : `#0A0A0F / #00F0FF / #FF2A6D / #D1F7FF`
- Typographies : Space Grotesk (display), Inter (body), JetBrains Mono (mono)
- Effets : disco ball 3D, smooth scroll Lenis, curseur custom, glow néon, scanlines, glitch
---
## Licence
© 2026 Johan Leroy.
Déploiement manuel possible : `npm run build` → uploader le contenu de `dist/` sur l'hébergement statique.

View File

@@ -1,19 +1,7 @@
// @ts-check
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwindcss from '@tailwindcss/vite';
import node from '@astrojs/node';
// Site vitrine statique (one-page). Sortie 100 % statique : `astro build` → dist/.
export default defineConfig({
site: 'https://johanleroy.fr',
output: 'static',
adapter: node({ mode: 'standalone' }),
integrations: [react()],
vite: {
plugins: [tailwindcss()],
},
server: {
port: 3100,
host: true,
},
});

View File

@@ -1,513 +0,0 @@
# Portfolio v2 — Analyse & plan d'action
**Projet source :** `/home/johan/WebstormProjects/Portfolio_angular`
**URL live :** https://johanleroy.fr
**Date :** 20 avril 2026
---
## 1. Analyse de l'existant
### Stack actuelle
| Élément | Valeur |
|---|---|
| Framework | Angular 19.2 |
| Styling | TailwindCSS 4 |
| State / Data | Aucun (tout en dur dans les templates) |
| Animations | Particules vanilla JS maison (`home.component.ts`) |
| Icônes | PNG dans `/public/img/icon/` (pas de SVG, pas HiDPI) |
| Icônes UI | Classes FontAwesome (`fa-gamepad`, `fa-film`) **sans lib chargée → cassées** |
| Dark mode | Toggle manuel via `localStorage` |
| SEO | Service custom `seo.service.ts` (title, description, OG, canonical) |
| Router | Lazy-load `PublicModule`, view transitions activées |
| Hébergement | Statique (`.htaccess` Apache dans `/public/`) |
| Tests | Karma/Jasmine (0 test écrit) |
### Structure
```
src/app/
├── _models/ particule.ts, seo.ts
├── _services/ seo.service.ts
└── public/
├── public-layout/ header + dark toggle + nav
├── public-routing.module.ts
├── public.module.ts
└── pages/
├── home/ (448 lignes HTML — accueil + skills + centres d'intérêt)
├── formations/ (169 lignes)
├── experience/ (417 lignes — 6 entreprises Actemium, SNCF, Cerema, Novoferm, PivotPoint, Almeria)
├── projets/ (104 lignes — 3 projets MonVoisinGeek, Techos, LPLV)
└── not-found/
```
### Palette actuelle (`src/styles.css`)
```css
--color-primary-dark: #181818
--color-primary-blue: #0A2463
--color-primary-light: #3E92CC
--color-primary-white: #FFFAFF
--color-primary-accent: #D8315B
```
### Problèmes identifiés
1. **Stack disproportionnée** — Angular + Zone.js pour un portfolio statique = bundle lourd (warning 500 kB dans `angular.json`), overkill pour du contenu vitrine.
2. **Décalage avec ta stack actuelle** — tu bosses maintenant sur Next.js/Nuxt/NestJS, Angular n'est plus dans aucun de tes projets en mémoire.
3. **Contenu 100 % hardcodé dans les templates** — chaque modif de texte, techno, projet ou date d'expérience impose de recompiler + redéployer.
4. **Animations minimalistes** — juste des particules en `requestAnimationFrame`, pas d'effets wow (scroll reveal, parallax, 3D, text morphing, cursors custom…).
5. **Icônes PNG** — flou sur écrans HiDPI, poids réseau élevé, pas de teinte dynamique possible.
6. **FontAwesome référencé mais jamais chargé** → les 4 icônes des centres d'intérêt sont invisibles.
7. **Tests inexistants** malgré la config Karma — bruit dans `package.json`.
8. **Pas de PWA** (manifest.json, service worker) alors que c'est trivial sur du statique.
### Ce qui marche bien et à conserver
- La structure de navigation (Accueil / Formations / Expérience / Projets).
- La palette existante (tu peux la reprendre ou l'enrichir).
- Le `SeoService` et ses meta tags dynamiques.
- Dark mode par défaut selon préférence système.
- Export statique déployé sur Plesk via Apache.
- Le CV PDF téléchargeable.
---
## 2. Choix techno recommandé
### Option A — **Astro 5 + React islands** ⭐ recommandé
**Pourquoi :**
- 0 JS par défaut → Lighthouse 100 presque gratuit.
- Islands pattern : tu charges React + Framer Motion uniquement sur les sections animées.
- Content Collections typées (Zod) → ton JSON devient un contrat validé au build.
- SSG pur, déploiement Plesk identique à l'actuel (export statique).
- Supporte nativement les endpoints SSR si tu veux un mini-admin.
**Stack complète :**
- Astro 5 (router fichiers, layouts)
- React 19 (îlots interactifs)
- TailwindCSS 4 (continuité palette)
- Framer Motion (micro-interactions déclaratives)
- GSAP + ScrollTrigger (timelines paillettes)
- Lenis (smooth scroll)
- @react-three/fiber + @react-three/drei (effets 3D type scène accueil)
- Lucide React (icônes UI) + Simple Icons via iconify (logos techno vectoriels)
- TypeScript strict
### Option B — **Next.js 15 + App Router**
Pertinent si tu veux un vrai mini-back-office plus tard (auth JWT, API routes, ISR). Plus lourd qu'Astro pour un simple portfolio.
### Option C — **Nuxt 4**
Si tu préfères rester côté Vue. Excellent mais tu connais déjà Vue 3 (Amarea, TrackSniff), donc pas de challenge.
### Recommandation
**Astro 5** — le meilleur rapport puissance/légèreté pour un portfolio. Tu ajoutes Next.js à ton CV avec Techos, Astro te donne un 3e framework frontend moderne à afficher, et tu gagnes une killer app de perf.
---
## 3. Système de contenu éditable sans rebuild
### Architecture proposée
```
public/
├── content/
│ ├── profile.json (nom, titre, accroche, photo, CV)
│ ├── skills.json (catégories + items avec niveau, logo iconify)
│ ├── experiences.json (tableau d'expériences pro)
│ ├── formations.json (tableau de formations)
│ ├── projects.json (tableau de projets + images + liens)
│ ├── interests.json (centres d'intérêt)
│ └── site.json (meta SEO, couleurs, switches features)
└── img/ (assets éditables)
```
### Principe
- Les fichiers JSON sont servis **en statique** par ton Plesk (pas dans le bundle).
- Côté client, un `ContentService` fait un `fetch('/content/profile.json')` au mount.
- Les composants React affichent les données via Suspense.
- **Modif en production = éditer le JSON sur le serveur → rechargement navigateur = MAJ immédiate. Zéro rebuild.**
### Schémas typés (Zod)
Les mêmes schémas servent :
- au build (validation SSG des données si tu veux injecter au build)
- au runtime (parsing des JSON fetchés)
- à l'admin (génération de formulaires)
Exemple `content/profile.json` :
```json
{
"name": "Johan Leroy",
"title": "Développeur Fullstack",
"tagline": "Dev le jour, DJ la nuit",
"hero": {
"photo": "/img/pp/pp3.png",
"cta": [
{ "label": "Télécharger mon CV", "href": "/assets/cv.pdf" },
{ "label": "Voir mes projets", "href": "/projets" }
]
},
"socials": {
"email": "leroyjohan3@gmail.com",
"github": "https://github.com/JohanLeroy",
"gitea": "https://git.lidge.fr/johanleroy",
"website": "https://johanleroy.fr"
}
}
```
### 3 niveaux d'édition (à choisir)
| Niveau | Outil | Usage |
|---|---|---|
| **1. Terminal / SFTP** | FTP Plesk + éditeur texte | MAJ rapide, zéro surcouche. Le plus simple. |
| **2. Mini-admin web** | `/admin` protégé par mot de passe (cookie httpOnly côté endpoint Astro SSR) | Formulaire qui édite `content/*.json` en live sur le serveur |
| **3. Gitea as CMS** | Un repo `johan-portfolio-content` dédié. L'admin web pousse via l'API Gitea. | Historisation automatique, rollback possible, build-free par cron `git pull` côté Plesk. |
**Reco pour toi :** niveau 2 ou 3. Le niveau 3 est le plus élégant car tu gardes un vrai git history sans polluer le repo code.
### Ce que l'admin web permettrait
- Édition inline du profil (nom, titre, bio)
- Ajout/édition/suppression de projets (drag-drop pour réordonner)
- Ajout d'une expérience pro
- Changement du thème de couleur (live preview)
- Toggle du mode "embauche disponible" (badge vert sur le site)
- Upload de nouvelles images (vers `/public/img/` via endpoint SSR)
- Export / import du JSON (backup)
---
## 4. UX "paillettes dans les yeux" — direction créative
### Accueil
- **Hero 3D** avec @react-three/fiber : scène minimaliste type "disco ball" qui tourne lentement, sensible au mouvement de souris (parallax). Nod subtil à ton univers DJ.
- **Texte morphing** (GSAP SplitText) — "Dev" se transforme en "DJ" puis en "Dev" en boucle lente.
- **Curseur custom** qui suit la souris avec un léger lag, change de forme sur les liens.
- **Gradient animé** en arrière-plan type aurore boréale (shader ou CSS `conic-gradient` animé).
### Scroll
- **Lenis smooth scroll** partout.
- **Scroll-triggered animations** : sections qui révèlent leur contenu en cascade (fade + translate-y + blur-out).
- **Pin + horizontal scroll** sur la section Projets (GSAP ScrollTrigger, type showcase Awwwards).
- **Progress bar** en haut de page.
### Section skills
- Cards avec effet **hover 3D tilt** (vanilla-tilt ou React équivalent).
- Icônes SVG des technos avec **glow au survol**.
- Compteur de maîtrise animé (ring progress) au scroll.
### Timeline expérience
- Layout type **métro parisien** (une ligne verticale animée, stations = expériences).
- Cards qui s'ouvrent en accordéon au scroll.
### Projets
- Carousel horizontal pinned avec images **zoom parallax**.
- Badge techno avec micro-logos simple-icons colorés.
- Hover : overlay gradient + CTA "voir projet".
### Detail bonus
- **Mode "party"** (easter egg) : un bouton discret qui active un strobe + particles + glitch texts → clin d'œil DJ.
- **Konami code** → affiche ton set DJ en fond sonore discret.
- **Switch 3 thèmes** : Day, Night, Neon (le mode neon = pleine paillettes).
---
## 5. Plan d'exécution
### Phase 0 — Prérequis (décision utilisateur)
- [ ] Valider le choix Astro vs Next.js vs Nuxt.
- [ ] Valider le niveau d'édition (SFTP / admin web / Gitea).
- [ ] Choisir nom de projet (`portfolio-v2`, `johanleroy-fr`, `portfolio-astro`, …).
- [ ] Palette : garder l'actuelle ou refaire ? (je peux proposer 3 palettes).
### Phase 1 — Bootstrap (1 jour)
- Scaffold Astro + TS strict + Tailwind 4 + React integration.
- Setup ESLint + Prettier + configs héritées de Techos (cohérence).
- Créer les 6 schémas Zod + JSON de contenu initial (repris du site actuel).
- Layout global + router fichier (`/`, `/formations`, `/experience`, `/projets`).
- Migrer `SeoService` vers un composant `<SEO>` Astro.
### Phase 2 — Contenu & structure (12 jours)
- Extraire tout le contenu des templates actuels vers les JSON.
- Migrer les PNG d'icônes → Iconify (Simple Icons + Lucide).
- Migrer les images projets/entreprises (optimisation avec `astro:assets`).
- Pages statiques sans animations pour valider la structure.
### Phase 3 — Animations & paillettes (23 jours)
- Intégration Lenis + configuration smooth scroll globale.
- Installation Framer Motion + GSAP + ScrollTrigger.
- Hero 3D @react-three/fiber.
- Scroll-triggered reveals sur chaque section.
- Curseur custom + cards 3D tilt + timeline métro.
- Carousel projets pinned horizontal.
- Thèmes Day / Night / Neon.
### Phase 4 — Admin d'édition (12 jours, optionnel selon niveau choisi)
- Endpoint Astro SSR `/api/content/:file` (GET/PUT).
- Protection par mot de passe + cookie httpOnly.
- Page `/admin` : formulaires dynamiques générés depuis les schémas Zod.
- Upload d'images.
- Export / import JSON.
- (Variante Gitea) : intégration Gitea API pour push auto.
### Phase 5 — Polish & deploy (1 jour)
- PWA (manifest + service worker).
- Audit Lighthouse (objectif 100/100/100/100).
- Sitemap auto + robots.txt.
- Meta tags Open Graph + Twitter Cards.
- Build production.
- Déploiement Plesk (remplacement `/dist` existant).
- Redirections 301 si URLs changent.
### Phase 6 — Extras plus tard
- Blog MDX (écriture d'articles techniques).
- Page contact avec vrai formulaire (via endpoint SSR ou Formspree).
- Section "Now" (what I'm up to, mis à jour mensuel).
- Stats GitHub/Gitea live (commits, streaks).
- Page `/jeux` ou `/bacs` pour expériences DJ.
---
## 6. Estimation totale
| Phase | Charge |
|---|---|
| 0 — Décisions | 30 min |
| 1 — Bootstrap | 1 jour |
| 2 — Contenu & structure | 12 jours |
| 3 — Animations | 23 jours |
| 4 — Admin (optionnel) | 12 jours |
| 5 — Polish & deploy | 1 jour |
| **Total sans admin** | **57 jours** |
| **Total avec admin** | **69 jours** |
---
## 7. Questions initiales — réponses validées ✅
| # | Question | Réponse |
|---|---|---|
| 1 | Framework | ✅ **Astro 5** |
| 2 | Système d'édition | ✅ **Admin web** (option 2) |
| 3 | Palette / identité | ✅ **Refonte DEV/DJ/Neon** |
| 4 | Hébergement | ✅ **Plesk** (stay put) — ISR à creuser selon contraintes |
| 5 | Niveau "paillettes" | ✅ **Wow mais pro** (pas d'effets dans tous les sens) |
| 6 | Contenu à rafraîchir | ✅ **Ajouter projets récents** (sauf Beehelp = pro/NDA) |
| 7 | Nom du repo | ✅ `portfolio` |
---
## 8. Setup technique dérivé des choix
| Élément | Valeur |
|---|---|
| Mode Astro | **Hybrid** — SSG pour pages publiques, SSR pour `/admin` et `/api` |
| Déploiement | **Plesk Node.js hosting** (même pattern que `techos_api`) |
| Persistance contenu | `/public/content/*.json` édités par endpoints SSR protégés |
| Auth admin | Mot de passe + cookie httpOnly (JWT ou session simple) |
| MAJ contenu en prod | Admin web → PUT JSON → **fetch runtime côté client, zéro rebuild** |
| ISR — note Plesk | Plesk = serveur Node permanent, on a l'équivalent d'ISR gratuit. Pas d'ISR Next.js-style mais re-fetch JSON runtime = effet identique |
---
## 9. Direction artistique — 3 palettes néon à trancher
### Option A — **Cyberpunk Dark** (blade-runner / tekno industrielle)
```
bg: #0A0A0F (noir profond)
accent 1: #00F0FF (cyan électrique)
accent 2: #FF2A6D (rose magenta)
accent 3: #D1F7FF (blanc bleuté)
texte: #E0E0FF
```
### Option B — **Rave / Miami** (90's, fun, coloré)
```
bg: #0D0221 (violet nuit)
accent 1: #FF00C8 (pink néon)
accent 2: #00FFC8 (mint laser)
accent 3: #FFD600 (jaune flash)
texte: #FFF8F0
```
### Option C — **Tech Minimal + 1 néon** ⭐ reco pro
```
bg: #0B0B0E (charbon)
surface: #16161D
accent: #39FF14 (vert néon strobe) — UN SEUL pop
texte: #EAEAEA
secondaire: #8A8F98
```
**Reco :** Option C pour recruteurs (le plus pro), Option A si on assume la vibe DJ.
---
## 10. Liste projets à afficher — proposition
| # | Projet | Statut | Action |
|---|---|---|---|
| 1 | **Techos** (asso hébergement) | Live `techos-asso.fr` | ✅ Inclure |
| 2 | **TrackSniff** | Privé (invitation) | ⚠️ À trancher |
| 3 | **Amarea Tattoo** | Client (studio tatouage) | ✅ Inclure |
| 4 | **Lidge** | Perso, auto-hébergé | ✅ Inclure |
| 5 | **Portfolio v2** (méta) | En cours | ✅ Classique circulaire |
| 6 | **MonVoisinGeek / LPLV** (anciens) | Live actuellement | ⚠️ Keep or drop ? |
| ❌ | **Beehelp** | Pro / NDA | ❌ Exclu (validé) |
---
## 11. Questions finales — réponses validées ✅
| # | Question | Réponse |
|---|---|---|
| 1 | **Palette** | ✅ **Option A — Cyberpunk Dark** (`#0A0A0F / #00F0FF / #FF2A6D / #D1F7FF / #E0E0FF`) |
| 2 | TrackSniff affiché ? | ❌ **Non** (privé, on n'affiche pas) |
| 3 | Portfolio v2 (self-reference) | ❌ **Non** |
| 4 | MonVoisinGeek / LPLV | ✅ **Garder** — mais marqués **"Projet fermé / archivé"**, infos présentes, liens morts |
| 5 | Projet supplémentaire | ✅ **Ajouter "Bot Discord KSauce"** |
| 6 | Nouvelle expérience/formation | — (à compléter au moment de migrer le contenu) |
### Liste finale des projets affichés
| # | Projet | État | Lien |
|---|---|---|---|
| 1 | **Techos** | Live | techos-asso.fr |
| 2 | **Amarea Tattoo** | Live | (à confirmer) |
| 3 | **Lidge** | Live perso | (à confirmer) |
| 4 | **Bot Discord KSauce** | (à préciser) | — |
| 5 | **MonVoisinGeek** | 🔒 Archivé | — |
| 6 | **LPLV** | 🔒 Archivé | — |
---
## 12. Plan d'implémentation détaillé — GO
**Répertoire cible :** `/home/johan/WebstormProjects/portfolio` (nouveau, à côté de l'ancien `Portfolio_angular`)
### Étape 1 — Bootstrap Astro hybrid ⬜
- [ ] `npm create astro@latest portfolio` (template `minimal`, TS strict)
- [ ] Installer intégrations : `@astrojs/react`, `@astrojs/tailwind`, `@astrojs/node` (adapter SSR)
- [ ] Configurer `astro.config.mjs` mode `hybrid` + adapter Node standalone (Plesk)
- [ ] Setup Prettier + ESLint (aligné sur Techos)
- [ ] `.gitignore`, `.editorconfig`, `README.md`
### Étape 2 — Design system Cyberpunk Dark ⬜
- [ ] `tailwind.config` + CSS custom properties (palette Option A)
- [ ] Typographies : **heading** = Space Grotesk / **body** = Inter / **mono accent** = JetBrains Mono
- [ ] Utilitaires `glow-cyan`, `glow-pink`, `neon-border`, `scanlines`, `noise-overlay`
- [ ] Cursor custom (composant React island)
- [ ] Dark only (pas de light mode — la vibe cyberpunk ne s'y prête pas)
### Étape 3 — Schémas Zod + contenu JSON ⬜
- [ ] `src/content/schemas/` : `profile.ts`, `skills.ts`, `experiences.ts`, `formations.ts`, `projects.ts`, `interests.ts`, `site.ts`
- [ ] `public/content/*.json` : contenu extrait des templates Angular actuels + MAJ
- [ ] `ContentService` (fetch runtime) côté client
- [ ] Validation Zod au parsing (garde-fou si JSON mal formé)
### Étape 4 — Pages publiques + layout ⬜
- [ ] Layout global : `Header` (nav + logo glitch), `Footer`, `CursorCustom`, `LenisProvider`
- [ ] `/` — Hero 3D disco-ball (R3F), scroll reveals, skills, centres d'intérêt
- [ ] `/formations` — Timeline métro verticale
- [ ] `/experience` — Timeline métro + cards accordéon (6 entreprises)
- [ ] `/projets` — Carousel pinned horizontal + badges techno Iconify
- [ ] `/404` — Page glitch 404
### Étape 5 — Animations paillettes ⬜
- [ ] Lenis smooth scroll global
- [ ] Framer Motion : reveals + micro-interactions
- [ ] GSAP + ScrollTrigger : timelines pinned
- [ ] Hero R3F : disco ball parallax souris
- [ ] SplitText : morphing "Dev / DJ / Dev"
- [ ] Cards skills : 3D tilt (hover)
- [ ] Gradient conic animé en bg
### Étape 6 — Admin SSR ⬜
- [ ] `/admin/login` — form password → cookie httpOnly
- [ ] Middleware `src/middleware.ts` : protection route `/admin/*`
- [ ] `/admin` — dashboard, liste des sections éditables
- [ ] `/admin/edit/[section]` — formulaires générés depuis Zod schemas
- [ ] `/api/content/[section]` — GET/PUT (lecture + écriture JSON serveur)
- [ ] `/api/upload` — upload images vers `public/img/`
- [ ] `/api/export` + `/api/import` — backup/restore JSON
### Étape 7 — SEO + PWA + déploiement ⬜
- [ ] Composant `<SEO />` (title, description, OG, canonical, Twitter)
- [ ] `sitemap.xml` auto
- [ ] `manifest.json` + service worker (PWA)
- [ ] Redirections 301 depuis anciennes URLs si nécessaire
- [ ] Build prod + test local
- [ ] Script déploiement Plesk Node (équivalent Techos API)
- [ ] Remplacement sur `johanleroy.fr`
### Étape 8 — Post-déploiement ⬜
- [ ] Audit Lighthouse (objectif 95+)
- [ ] Test admin web en prod
- [ ] Rédiger `docs/ADMIN.md` (comment éditer le contenu)
- [ ] Snapshot initial dans git du contenu actuel
- [ ] Mettre à jour `CLAUDE.md` du projet
---
**GO ✅** — scaffold démarré.
---
## 13. État d'implémentation (suivi)
| Étape | Status |
|---|---|
| 1 — Bootstrap Astro hybrid (React + Tailwind 4 + Node adapter) | ✅ |
| 2 — Design system Cyberpunk Dark (palette, glow, néon, fonts) | ✅ |
| 3 — Schémas Zod + JSON contenu extrait | ✅ |
| 4 — Layout + pages publiques (/, /formations, /experience, /projets, /404) | ✅ |
| 5 — Animations (Lenis, Framer Motion, R3F hero disco, morph, curseur) | ✅ |
| 6 — Admin SSR protégé + API CRUD JSON + upload | ✅ |
| 7 — SEO + build prod + workflow Gitea + README déploiement | ✅ |
### Livrables
- `/home/johan/WebstormProjects/portfolio/` (projet Astro 6 + React 19 + Tailwind 4)
- Build production OK → `dist/server/entry.mjs` (startup Plesk Node)
- Dev testé sur **http://localhost:3100**
- Workflow `.gitea/workflows/deploy.yml` déclenché sur push `main`
- Contenu projets mis à jour : Techos, Amarea Tattoo, Lidge, Bot Discord KSauce, LPLV (archivé), MonVoisinGeek (archivé)
### Prochaines étapes côté utilisateur
1. `cd ~/WebstormProjects/portfolio && npm run dev` → vérifier sur http://localhost:3100
2. `npm run hash:password -- monMotDePasse` → copier `ADMIN_PASSWORD_HASH` + `ADMIN_JWT_SECRET` dans un `.env` local
3. Tester `/admin/login` puis `/admin`
4. Initialiser git et pousser sur `main` vers Gitea
5. Configurer les secrets Gitea (`FTP_HOST`, `FTP_USER`, `FTP_PASSWORD`, `FTP_TARGET_DIR`)
6. Configurer Plesk Node.js : startup file `dist/server/entry.mjs`, variables d'env admin, Node 22
7. Pousser sur `main` → CI déploie → redémarrer l'app Node dans Plesk

View File

@@ -1,374 +0,0 @@
# Portfolio v2 — Moodboard "Studio Live / Free Party"
> Direction artistique : **tekno free party**. Vibe sound system / squat / rave illégale. Palette cyberpunk conservée mais **poussée en acid**, typographies plus brutes, vocabulaire underground, textures warehouse.
---
## 1. Vocabulaire / mots-clés du site
On oublie "cv", "profil", "vinyles", on skinne tout :
| Concept générique | Version free party |
|---|---|
| Portfolio | **Sound system** |
| Mes projets | **Crate / Skeuds** |
| Téléchargez mon CV | **Dubplate `.pdf`** |
| Expérience pro | **Setlist** / **Warehouse log** |
| Formations | **Learning stack** / **BPM log** |
| Compétences | **Rack** / **Patch bay** |
| Centres d'intérêt | **Off-decks** |
| Contact | **Open mic** / **Frequencies** |
| Chargement | `BOOTING SOUND SYSTEM…` |
| 404 | `SIGNAL LOST` / `NO SKEUD FOUND` |
| Dispo pour boulot | `ON AIR` (LED verte clignotante) |
Tu gardes le style "code + tekno" : classes `.mono`, tags `[BPM:174]`, `>> mix`, `//` partout.
---
## 2. Palette ajustée (acid free party)
On garde le cyberpunk mais on pousse **l'acid** et on ajoute du **jaune strobe** pour l'énergie rave.
```
bg: #0A0A0F (noir warehouse)
surface: #111118
accent_cyan: #00F0FF (UV light principal)
accent_acid: #C6FF00 (⭐ NOUVEAU — jaune acid strobe pour accents forts)
accent_pink: #FF2A6D (magenta dubplate)
accent_red: #FF0040 (⭐ NOUVEAU — rouge LED clip)
texte: #E0E0FF
mute: #8A8FA8
```
**Usages :**
- cyan = base UI (nav, borders)
- acid yellow = **CTA principal + "ON AIR"** (très rave flyer)
- pink/magenta = accents secondaires
- rouge LED = warnings, statut live, status en cours
- violet/ice → on réduit l'usage
---
## 3. Typographies
On pousse plus loin le contraste entre mono tech et display brut :
| Usage | Font | Pourquoi |
|---|---|---|
| Display / titres | **Space Grotesk** (gardé) | Sharp, tech, se tient |
| Body | **Inter** (gardé) | Lisible |
| Mono | **JetBrains Mono** (gardé) | Code |
| ⭐ Accent "rave" | **Array** ou **Neue Haas Grotesk Display Black** | Stencil bâton, façon flyer free party / affiche underground |
| ⭐ Étiquettes stickées | **Monument Extended** ou équivalent | Giga bold pour les dates, BPM, codes |
Suggestion alternative open source sur Google Fonts : **Bebas Neue** (stencil), **Anton** (poster), **Oswald** (condensé).
---
## 4. Éléments communs à tout le site
### 4.1 Header / nav = **console sound system**
- En haut, fine barre noire avec :
- **Logo** : "JL" stylisé rave (stencil) avec **LED verte qui clignote** à côté = "ON AIR"
- **Nav** : les items `/crate`, `/setlist`, `/bpm-log`, `/open-mic` (ou versions FR : `/skeuds`, `/setlist`, `/formations`, `/contact`)
- **Scroll progress bar** fine en néon cyan en bas du header (indique où on en est)
- Au clic d'un item → **LED s'allume**, petit son *clic* de fader (togglable, off par défaut)
### 4.2 Background ambient
Derrière tout le site, couches superposées très subtiles :
1. **Gradient noir → surface** de base
2. **Grain / noise** fin (bruit vidéo warehouse)
3. **2-3 spots UV** (radial cyan + magenta) qui se **déplacent lentement** selon le scroll (fake parallax light)
4. **Optional :** **strobe flash** très bref (20ms, opacité 10%) toutes les 30-60s pour la vibe rave. **Respecte `prefers-reduced-motion`**
### 4.3 Transition entre pages = **crossfade EQ**
Quand tu cliques sur un lien de nav :
1. L'écran actuel reçoit un **léger blur + fade out** (simule un low-pass EQ qui ferme)
2. Le nouveau page **slide in** avec son contenu qui reveal (fade in + text split)
3. Durée totale : ~600ms
4. Réalisation : **Astro View Transitions API** (native)
### 4.4 Scroll smooth = Lenis (déjà fait)
### 4.5 Curseur
- Curseur natif conservé
- **Sur les liens/boutons** : petit halo néon cyan/acid qui apparaît (au lieu d'un curseur custom qui dérange)
- **Boutons magnétiques** : le bouton se déplace légèrement vers le curseur à l'approche (spring physics, comme Vercel / Rauno)
### 4.6 Text reveals
- Tous les gros titres : **char-by-char reveal** avec effet blur → clear (Framer Motion SplitText)
- Sous-titres : **word-by-word** fade-up
- Trigger : `whileInView`
---
## 5. Page / (Home)
### 5.1 Hero
**Couche 1 (background fixe)** :
- Gradient mesh animé noir → charbon
- 3 spots UV lents
- **Ligne de waveform audio** centrée en bas du hero, qui pulse doucement (fake audio reactive avec math `sin`). Couleur cyan/acid, épaisseur fine.
**Couche 2 (texte)** :
- `> frequency: 174 BPM` (petit mono cyan tout en haut, stylé tag radio)
- **Titre** : `Johan Leroy` en **ultra large**, style poster free party. Le "Leroy" en **acid yellow** avec glow (au lieu de magenta). Effet **char-by-char reveal** au load.
- Sous-titre : `[ ` + **mot qui morph `dev → DJ → skeud-head → dev`** + ` ]` dans des brackets mono
- **Tagline acid** : _"code propre, sono sale"_ en italique mono
- **CTA principal** en **jaune acid** néon : `// grab the dubplate (cv.pdf)`
- **CTA ghost** : `>> browse the crate`
**Couche 3 (stickers / details) :**
- En haut à droite, petit **badge "ON AIR"** avec LED verte (animée)
- En bas à gauche, petit tag style flyer `NANTES FR · 2019→now`
- Stickers collés style "free party sticker pack" éparpillés (logos fictifs en SVG) — très légers, opacité 20%
### 5.2 Section Stack = **Sound system rack**
**Métaphore : un rack de studio / sono.** Un panneau sombre façon plaque acier brossé, avec **modules empilés** verticalement (comme des modules Eurorack).
Chaque module = une catégorie :
- En haut du module : **label LED** (LANGUAGES, FRAMEWORKS, DATABASES…)
- **VU meter vertical** à gauche qui bouge doucement (fake)
- **Pads / knobs** : chaque techno = un carré avec son icône + label, stylé "bouton pressoir"
- Au hover du pad → **pad s'enfonce** légèrement, LED cyan s'allume, **tooltip** apparaît `[ MASTERED ]` ou `[ KNOWS ]`
- **Indicateur BPM fictif** en haut à droite du rack : `BPM 174 · HARDTEK`
**Deux racks empilés :** primary (plus gros) et secondary (plus compact).
Effet paillette : le rack a une **jauge power** qui s'allume au scroll reveal.
### 5.3 Section Off-decks (centres d'intérêt)
Cards format **bande magnétique / cassette tape** :
- Chaque card = une mini-cassette stylisée avec étiquette
- Icône au centre, label en dessous façon marker
- Au hover : la bande **tourne** légèrement (CSS transform rotateY)
### 5.4 Footer
- Séparateur = **forme d'onde audio**
- 3 colonnes : Nav, Open mic (contact), Credits
- En bas, sticker collé `"// code propre, sono sale — JL 2026"` + **icônes sound system** (enceinte, platine, casque) en SVG
---
## 6. Page /projets = **Crate / skeuds**
**Métaphore : un bac à disques.** Les projets sont des **skeuds (records 12")** qu'on feuillette.
### 6.1 Layout
- **Horizontal pinned scroll** (GSAP ScrollTrigger) : quand tu scrolles verticalement, les skeuds **défilent horizontalement** comme si tu feuilletais un bac
- Chaque skeud = une **pochette vinyle** carrée, grand format (300-400px)
### 6.2 Skeud / card projet
- **Face A** par défaut visible :
- Image projet en center crop
- Cercle noir de vinyle qui **dépasse** à droite (comme un disque qui sort de sa pochette)
- Étiquette centrale du vinyle (label fictif) avec nom du projet
- Stickers overlay :
- `[LIVE]` en acid yellow si actif
- `[ARCHIVED]` en gris si fermé
- `[DUBPLATE]` si privé
- BPM fictif `[180 BPM]`
- Au hover :
- Le disque **sort complètement** de la pochette (translation X)
- Le disque **tourne** sur son axe (33⅓ rpm fake, lent)
- La pochette s'incline légèrement (3D tilt)
- Overlay apparaît avec stack + lien "play"
- Au clic :
- Transition fluide vers site externe, ou vers page détail
### 6.3 Détails graphiques
- Le bac a une texture **bois / plastique** (SVG pattern)
- **Numéro d'ordre** en gros chiffres stencil derrière chaque card `#01`, `#02`
- Filtres en haut : `[ ALL ]` `[ LIVE ]` `[ ARCHIVED ]` en petits pads
- **Compteur** en bas : `05 / 06 skeuds`
---
## 7. Page /experience = **Setlist**
**Métaphore : la tracklist d'un set DJ.** Chaque job = une track dans le set, avec durée, BPM, style.
### 7.1 Layout : tracklist verticale stylisée
Header de la page :
```
> setlist.current = "10_years_of_code"
> duration: 10y 0m 0d
> tracks: 6
> status: ON AIR
```
Ensuite, liste verticale où chaque expérience est **une ligne de tracklist** :
```
01 [NOW PLAYING] Almeria ████████████ Dev fullstack · 3y · [174 BPM]
02 CEREMA ████░░░░░░░░ Technicien · 2y · [160 BPM]
03 SNCF Réseau ████░░░░░░░░ Télécom · 2y · [140 BPM]
04 [INTRO] Novoferm █░░░░░░░░░░░ Stage · 7w · [120 BPM]
...
```
Au clic/hover d'une ligne → elle **s'expand** comme un accordéon, révèle missions + stack. Animation : expansion + le "BPM" pulse.
### 7.2 Détails
- Police mono partout dans la liste
- Ligne "NOW PLAYING" avec **fond jaune acid subtil** et **LED rouge clignotante**
- Progress bars = **waveforms** ASCII stylisées
- **Timeline header** : ligne horizontale avec années `2018 — 2026`
### 7.3 Après la tracklist, 2 blocs :
- **Patch bay** = compétences techniques (pattern rack, avec câbles SVG animés qui relient les modules) — visuellement unique
- **Soft skills** = liste simple en mono
---
## 8. Page /formations = **BPM Log**
**Métaphore : un journal de bord de crate-digging.** Tu as appris comme un DJ apprend — tu creuses, tu collectionnes, tu stockes.
### 8.1 Layout
Timeline verticale simple mais **affichage type carte d'entrée de rave** :
Chaque formation = une "carte" façon **entrance ticket** :
- Fond carte papier beige/gris (distinct des autres sections, ambiance "ticket froissé")
- En-tête : `FREE PARTY INVITATION — EDU SYSTEM`
- Date / lieu : `2023 — NANTES — CAMPUS ENI`
- Titre : `CONCEPTEUR DEV. D'APPLICATIONS`
- Description
- Footer sticker : `ADMIT ONE`
- Card **rotationnée légèrement** (tilt 2-3°) pour l'effet "jeté"
Au scroll, les tickets **glissent** depuis les côtés avec une légère rotation.
---
## 9. Page 404 = **SIGNAL LOST**
- Fond noir profond, scanlines CRT visibles
- Gros texte central : `SIGNAL LOST` + glitch chromatic aberration
- Sous-texte mono : `frequency not found — rejoin the main feed`
- CTA acid : `[<<] back to the sound system`
- En fond, petit **static noise** animé (SVG avec noise turbulence)
---
## 10. Micro-détails / easter eggs
- **Konami code** (↑↑↓↓←→←→BA) → active le **party mode** : strobe 1s + glitch généralisé + un émoji skeud qui traverse l'écran
- **Clic logo 5x** → affiche un **DJ set secret** (player audio caché, ton son si tu veux)
- **Boot sequence** au premier load : brève overlay `BOOTING SOUND SYSTEM… [OK]` avant de révéler le hero
- **Hover sur un lien** : léger *tick* (clic platine, togglable)
---
## 11. Plan d'implémentation ordonné
1. **Design system v2** — ajouter palette acid (`--color-acid: #C6FF00`, `--color-red: #FF0040`), ajouter font stencil (Anton ou Bebas Neue)
2. **View Transitions** activer côté Astro + transitions personnalisées entre pages
3. **Header skinné** — LED ON AIR, scroll progress bar, logo stencil
4. **Hero v2** — waveform SVG animée, text reveal char-by-char, tagline, stickers
5. **Sound system rack** (nouvelle UI skills) — remplace la grille actuelle
6. **Magnetic buttons + text reveals** — utilitaires globaux
7. **Crate / skeuds** (nouvelle UI projets) — horizontal pinned scroll, vinyle qui sort
8. **Setlist** (nouvelle UI experience) — tracklist, now playing, accordéons
9. **Patch bay** (nouveau bloc expérience) — rack + câbles SVG animés
10. **BPM log** (nouvelle UI formations) — tickets rave tiltés
11. **Footer skinné** — forme d'onde, stickers
12. **404 signal lost**
13. **Micro-interactions** — sons togglables, Konami, boot sequence
14. **Reduced motion** — vérifier tout se dégrade bien
15. **Perf** — audit Lighthouse
**Effort total :** 4-5 jours de dev concentré. Livrable par blocs testables.
---
## 12. Risques & arbitrages
- **Risque kitsch :** si on en met trop, ça devient caricatural. Ligne de crête : **1 gimmick fort par section max**, le reste sobre.
- **Risque perf :** shader + vinyles 3D + scroll pinned = lourd. On reste sur du CSS/SVG/Canvas 2D léger, pas de WebGL sauf si nécessaire.
- **Risque recruteur :** certains vieux recruteurs ne comprendront pas la métaphore. Contre-mesure : **vocabulaire bilingue** (techos + jargon), CV toujours accessible en 1 clic.
- **Risque audio :** si on ajoute vraiment des sons, **off par défaut**, toggle visible. Jamais auto-play.
---
## 13. Avant le GO : questions pour toi
1. **Acid yellow `#C6FF00`** comme accent principal CTA, tu valides ? (tu peux aussi choisir `#D6FF00` plus doux)
2. **Font stencil** : Bebas Neue (gratuite, moderne) / Anton / Array (payante mais ultime) ?
3. **Sons** : on les implémente dès le début (togglables, off) ou on les laisse pour plus tard ?
4. **Boot sequence** au premier load : oui ou cringe ?
5. **Vocabulaire** : on garde bilingue (FR grand public + jargon en sous-texte), ou full jargon free party ?
6. **Horizontal pinned scroll** pour les projets : oui (plus wow, mais mobile = fallback grid) / non (grid partout) ?
7. Tu veux **rester sur cette doc** comme référence pendant l'implémentation ou tu veux qu'on épure avant ?
---
Dis-moi tes choix + "GO" et je commence à skinner, par étapes (je te montre après chaque bloc).
---
## 14. Décisions validées ✅
| # | Question | Réponse validée |
|---|---|---|
| 1 | Acid yellow `#C6FF00` en accent principal | ✅ Validé |
| 2 | Font stencil | ✅ **Bebas Neue** (Google Fonts, gratuite, moderne) |
| 3 | Sons (clic fader, tick platine) togglables off par défaut | ✅ Intégrés dès le début, **OFF par défaut**, toggle visible |
| 4 | Boot sequence au premier load | ✅ **On tente** — ajustable si cringe après retour |
| 5 | Vocabulaire | ✅ **FR grand public****rester pro**. Nav et URLs standards (`/projets`, `/formations`, `/experience`). Les skins free party **dressent** le site en accents déco (sous-titres mono `// crate v1.0`, stickers, BPM fictifs, `[NOW PLAYING]`, etc.) sans bloquer le recruteur |
| 6 | Horizontal pinned scroll projets | ✅ **OUI** — avec **fallback grid verticale sur mobile** (breakpoint `< md`) |
| 7 | Doc de référence | ✅ On la garde en référence |
### Ligne éditoriale affinée
- **Structure UX** = pro et accessible (nav FR, URLs lisibles, CTA clairs)
- **Habillage** = free party / studio (palette acid, stencil, stickers, BPM, waveforms, vinyles)
- **Sons** = **toggle** visible dans le header (icône speaker), off à l'arrivée
- **Boot sequence** = rapide (1.2s max), skippable en cliquant, n'apparaît **qu'au premier load** (stocké en `sessionStorage`), plus jamais en retour de page
- **Mobile** = fallback propre pour chaque wow-feature :
- Horizontal pinned scroll → grid verticale
- Magnetic buttons → classic hover
- Cursor halos → désactivés
- Spots UV parallax → statiques
### Plan d'exécution — ordre de livraison
| Bloc | Livrables | Effort |
|---|---|---|
| **0. Design system v2** | Palette acid (`--color-acid`, `--color-red`) + Bebas Neue chargée + utilities `glow-acid`, `stencil`, `sticker` | 0.5j |
| **1. View Transitions + transitions de page** | Crossfade EQ global entre pages | 0.25j |
| **2. Header v2** | Logo stencil "JL" + LED ON AIR + scroll progress bar + toggle sons | 0.5j |
| **3. Background ambient** | Gradient mesh + grain + 2 spots UV suivant le scroll | 0.5j |
| **4. Hero v2 + boot sequence** | Waveform, text reveal, tagline, stickers, CTA acid + boot 1re visite | 1j |
| **5. Sound system rack (skills)** | Remplace les grilles SkillCard actuelles | 1j |
| **6. Magnetic buttons + text reveals globaux** | Utilitaires réutilisables | 0.5j |
| **7. Crate (projets)** | Pinned horizontal scroll + skeuds qui sortent + fallback grid mobile | 1j |
| **8. Setlist (expérience)** | Tracklist, NOW PLAYING, accordéons, BPM | 1j |
| **9. Patch bay (compétences pro)** | Rack + câbles SVG animés | 0.5j |
| **10. BPM log (formations)** | Tickets rave tiltés | 0.5j |
| **11. Footer skinné + 404 Signal Lost** | Waveform séparateur, stickers, page 404 | 0.5j |
| **12. Easter eggs** | Konami, 5× logo, sons | 0.25j |
| **13. Audit perf + reduced motion** | Lighthouse, a11y | 0.5j |
**Total :** ~8 jours de dev concentré, livrables testables bloc par bloc.
---
## 15. GO ✅ — implémentation démarre au bloc 0

View File

@@ -1,32 +0,0 @@
# Docs — Portfolio v2
Documents de conception et décisions prises pendant la refonte du portfolio.
## Sommaire
| # | Fichier | Rôle |
|---|---|---|
| 01 | [Analyse & plan](./01-analyse-et-plan.md) | Audit de l'ancien site Angular + choix Astro hybrid + système d'édition JSON + stack déploiement Plesk Node.js |
| 02 | [Moodboard Free Party](./02-moodboard-freeparty.md) | Direction artistique "Studio Live / tekno free party" : palette acid, typos, vocabulaire, déclinaison page par page, plan d'implémentation 14 blocs |
## Choix principaux validés
- **Framework** : Astro 6 hybrid (SSG public + SSR `/admin` et `/api`)
- **Hébergement** : Plesk Node.js (startup `dist/server/entry.mjs`)
- **Contenu** : `public/content/*.json` éditables via `/admin`, runtime fetch (zéro rebuild)
- **Direction artistique** : Cyberpunk Dark + skin Free Party
- Palette : `#0A0A0F` / `#00F0FF` cyan / `#C6FF00` acid / `#FF2A6D` magenta / `#FF0040` LED red
- Typos : Inter, Space Grotesk, JetBrains Mono, Bebas Neue (stencil)
- **Animations** : Lenis smooth scroll, Framer Motion, View Transitions API, GSAP scroll-pinned manuel, islands React
## Structure des 14 blocs implémentés
Voir [02-moodboard-freeparty.md §11](./02-moodboard-freeparty.md) pour la liste ordonnée et [14](./02-moodboard-freeparty.md) pour le plan d'exécution détaillé.
## Racines & contraintes
- **Pro avant tout** : nav FR grand public, URLs standards, CV 1 clic
- **Jargon free party = déco** : stickers, BPM fictifs, LED, stencil — jamais bloquant
- **Responsive** : fallbacks propres pour chaque effet wow (pinned horizontal, magnetic, UV parallax)
- **A11y** : respect de `prefers-reduced-motion` partout
- **Sécurité** : admin JWT cookie httpOnly, middleware protégeant `/admin/*` et `/api/content/*`

2778
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +1,23 @@
{
"name": "portfolio",
"type": "module",
"version": "1.0.0",
"version": "2.0.0",
"engines": {
"node": ">=22.12.0"
"node": ">=18.20.8"
},
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"start": "HOST=0.0.0.0 PORT=3100 node ./dist/server/entry.mjs",
"astro": "astro",
"check": "astro check",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"hash:password": "node scripts/hash-password.mjs"
"astro": "astro"
},
"dependencies": {
"@astrojs/node": "^10.0.5",
"@astrojs/react": "^5.0.3",
"@hookform/resolvers": "^5.2.2",
"@iconify-icon/react": "^3.0.3",
"@tailwindcss/vite": "^4.2.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"astro": "^6.1.8",
"bcryptjs": "^3.0.3",
"framer-motion": "^12.38.0",
"jose": "^6.2.2",
"lenis": "^1.3.23",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-hook-form": "^7.75.0",
"tailwindcss": "^4.2.2",
"zod": "^4.3.6"
"simple-icons": "^13.16.0"
},
"devDependencies": {
"@astrojs/check": "^0.9.8",
"@types/bcryptjs": "^2.4.6",
"@types/node": "^25.6.0",
"@vitest/coverage-v8": "^4.1.6",
"typescript": "^5.9.3",
"vitest": "^4.1.6"
"typescript": "^5.9.3"
}
}

View File

@@ -1,116 +0,0 @@
{
"items": [
{
"id": "almeria",
"company": "Almeria",
"logo": "/api/files/img/company/almeria.png",
"role": "Développeur",
"period": "Depuis 2023",
"duration": "3 ans",
"description": "Conception et développement d'applications web et de logiciels d'intégration ERP.",
"missions": [
"Analyse, conception et développement d'applications web",
"Création de logiciels d'intégration ERP",
"Maintenance et évolution des applications existantes"
],
"stack": ["Angular", "Express.js", "NestJS", "SQL Server", "MariaDB"],
"type": "cdi",
"current": true
},
{
"id": "cerema",
"company": "CEREMA",
"logo": "/api/files/img/company/cerema.png",
"role": "Technicien évaluation mesure trafic & mobilité",
"period": "2021 - 2023",
"duration": "2 ans",
"description": "Spécialisé dans l'installation de capteurs et l'analyse de données de mobilité.",
"missions": [
"Installation de capteurs provisoires dans le secteur mobilité",
"Optimisation des données dans l'innovation d'outils numériques",
"Analyse et traitement des données de trafic et mobilité"
],
"stack": ["Analyse de données", "PHP", "MariaDB"],
"type": "cdd",
"current": false
},
{
"id": "sncf",
"company": "Réseau SNCF",
"logo": "/api/files/img/company/sncf.png",
"role": "Agent télécom",
"period": "2019 - 2021",
"duration": "2 ans",
"description": "Maintenance des équipements de sonorisation, d'affichage et de téléphonie sur le réseau ferroviaire.",
"missions": [
"Maintenance préventive et corrective des équipements",
"Installation et réparation des systèmes d'affichage en gare",
"Gestion des équipements téléphoniques sur les voies ferrées"
],
"stack": ["Télécommunications", "Maintenance", "Équipements audio", "Affichage dynamique"],
"type": "cdi",
"current": false
},
{
"id": "novoferm",
"company": "Novoferm",
"logo": "/api/files/img/company/novoferm.png",
"role": "Stage développeur",
"period": "2019",
"duration": "7 semaines",
"description": "Stage de développement d'une application web qui permet de récupérer des fichiers stockés dans SharePoint. Réparation et configuration de PC, imprimantes et téléphones.",
"missions": [],
"stack": [],
"type": "stage",
"current": false
},
{
"id": "pivotpoint",
"company": "Pivot Point",
"logo": "/api/files/img/company/pivotpoint.png",
"role": "Stage développeur",
"period": "2018",
"duration": "1 semaine",
"description": "Stage de développement d'un site internet en PHP.",
"missions": [],
"stack": ["PHP"],
"type": "stage",
"current": false
},
{
"id": "actemium",
"company": "Actemium",
"logo": "/api/files/img/company/actemium.png",
"role": "Stage développeur",
"period": "2018",
"duration": "1 semaine",
"description": "Stage de développement d'applications Windows d'automatisation.",
"missions": [],
"stack": [],
"type": "stage",
"current": false
}
],
"skills": {
"technical": [
"Développement Frontend",
"Développement Backend",
"Analyse et traitement de données",
"Mise en production",
"Analyse de base de données",
"Modélisation UML",
"Maquettage",
"Sécurité"
],
"soft": [
"Gestion de projets",
"Travail en équipe",
"Analyse de projets",
"Communication",
"Autonomie",
"Organisation",
"Adaptation",
"Veille technologique"
]
}
}

View File

@@ -1,35 +0,0 @@
{
"items": [
{
"id": "cda-eni",
"title": "Concepteur Développeur d'Applications",
"school": "Campus ENI Nantes",
"schoolUrl": "https://www.eni-ecole.fr",
"period": "Depuis 2023",
"description": "Formation sur la conception et le développement d'applications web et mobiles. Acquisition des compétences en architecture logicielle et gestion de projets informatiques."
},
{
"id": "bts-sn",
"title": "BTS Systèmes Numériques",
"school": "Campus Saint Félix La Salle Nantes",
"schoolUrl": "https://stfelixlasalle.fr",
"period": "2021 - 2023",
"description": "Formation technique supérieure en systèmes électroniques et informatiques, avec une spécialisation en informatique industrielle et en réseaux."
},
{
"id": "bac-pro-sn",
"title": "BAC PRO Systèmes Numériques",
"school": "Campus Saint Félix La Salle Nantes",
"schoolUrl": "https://stfelixlasalle.fr",
"period": "2018 - 2021",
"description": "Formation en systèmes électroniques et réseaux informatiques. Apprentissage des bases de l'installation et de la maintenance de systèmes numériques."
},
{
"id": "brevet",
"title": "Diplôme National du Brevet",
"school": "Ensemble scolaire Saint Père en Retz",
"schoolUrl": "https://www.ensemble-scolaire-saint-pere.fr",
"period": "Avant 2018"
}
]
}

View File

@@ -1,8 +0,0 @@
{
"items": [
{ "id": "gaming", "label": "Jeux vidéo", "icon": "lucide:gamepad-2", "color": "violet" },
{ "id": "movies", "label": "Films & Séries", "icon": "lucide:film", "color": "cyan" },
{ "id": "dj", "label": "DJing / Tekno", "icon": "lucide:disc-3", "color": "magenta" },
{ "id": "side", "label": "Projets personnels", "icon": "lucide:rocket", "color": "ice" }
]
}

View File

@@ -1,43 +0,0 @@
{
"name": "Johan Leroy",
"title": "Développeur",
"tagline": "Dev le jour, DJ la nuit",
"bio": "Développeur fullstack passionné par les technos modernes. J'aime créer des expériences web performantes, propres et scalables — et accessoirement mixer de la tekno quand l'écran s'éteint.",
"available": true,
"availableLabel": "Ouvert aux opportunités",
"hero": {
"photo": "/api/files/img/pp/pp3.png",
"photos": [
"/api/files/img/pp/pp1.png",
"/api/files/img/pp/pp2.png",
"/api/files/img/pp/pp3.png"
],
"roles": [
"Dev",
"DJ",
"Dev"
],
"cta": [
{
"label": "Télécharger mon CV",
"href": "/api/files/assets/cv.pdf",
"variant": "primary",
"external": true
},
{
"label": "Voir mes projets",
"href": "/projets",
"variant": "ghost",
"external": false
}
]
},
"socials": {
"email": "contact@johanleroy.fr",
"github": "https://github.com/JohanLeroy",
"gitea": "https://git.lidge.fr/johanleroy",
"linkedin": "https://www.linkedin.com/in/johan-leroy-472409180/",
"website": "https://johanleroy.fr"
},
"cv": "/api/files/assets/cv.pdf"
}

View File

@@ -1,71 +0,0 @@
{
"items": [
{
"id": "techos",
"title": "Techos",
"subtitle": "Plateforme hébergement & domaines",
"description": "Plateforme client + admin de gestion d'hébergement web et de domaines pour une association, construite par-dessus WHMCS. Design system Liquid Glass dark + light mode.",
"image": "/api/files/img/projet/techos.png",
"category": "web",
"status": "live",
"url": "https://techos-asso.fr",
"stack": ["Next.js 16", "React 19", "NestJS 11", "TailwindCSS 4", "shadcn/ui", "Redis", "WHMCS API"],
"featured": true
},
{
"id": "amarea",
"title": "Amarea Tattoo",
"subtitle": "Site du studio de tatouage Amarea",
"description": "Site vitrine et gestion pour un studio de tatouage. Architecture découplée SPA + REST API avec gestion de médias.",
"image": "",
"category": "web",
"status": "live",
"stack": ["Vue 3", "Vite", "NestJS 11", "TypeORM", "MariaDB", "Tailwind 4"],
"featured": true
},
{
"id": "lidge",
"title": "Lidge",
"subtitle": "Portail abonnements services auto-hébergés",
"description": "Plateforme personnelle pour gérer les abonnements à mes services auto-hébergés (Jellyfin, Cloud, Vault, Mail, Messenger, Git…) avec SSO, tickets et workflow d'accès.",
"image": "",
"category": "web",
"status": "live",
"stack": ["Nuxt 3", "NestJS 11", "TypeORM", "MariaDB", "JWT", "Nodemailer"],
"featured": true
},
{
"id": "ksauce-bot",
"title": "Bot Discord KSauce",
"subtitle": "Bot communautaire Discord",
"description": "Bot Discord développé pour la communauté KSauce. Gestion de commandes, modération et fonctionnalités sur-mesure.",
"image": "",
"category": "bot",
"status": "live",
"stack": ["Node.js", "TypeScript", "Discord.js"],
"featured": false
},
{
"id": "lplv",
"title": "Liberty Pour La Vie",
"subtitle": "Site associatif (archivé)",
"description": "Développement d'un site web complet pour l'association Liberty Pour La Vie. Projet fermé, plus en ligne.",
"image": "/api/files/img/projet/lplv.png",
"category": "web",
"status": "archived",
"stack": ["Java Spring Boot", "Thymeleaf", "Bootstrap", "MariaDB"],
"featured": false
},
{
"id": "monvoisingeek",
"title": "Mon Voisin Geek",
"subtitle": "Site vitrine (archivé)",
"description": "Développement d'un site web vitrine simple pour Mon Voisin Geek. Projet fermé, plus en ligne.",
"image": "/api/files/img/projet/monvoisingeek.png",
"category": "web",
"status": "archived",
"stack": ["Angular", "Tailwind CSS", "Express.js", "MariaDB"],
"featured": false
}
]
}

View File

@@ -1,26 +0,0 @@
{
"title": "Johan Leroy — Portfolio",
"description": "Développeur fullstack — NestJS, Nuxt, Next.js, Vue. Dev le jour, DJ la nuit.",
"url": "https://johanleroy.fr",
"keywords": [
"johan leroy",
"développeur fullstack",
"nestjs",
"nuxt",
"nextjs",
"vue",
"angular",
"tailwindcss",
"portfolio",
"développeur web",
"freelance",
"nantes"
],
"ogImage": "/api/files/img/logo/android-chrome-512x512.png",
"features": {
"hero3D": false,
"smoothScroll": true,
"customCursor": false,
"partyMode": true
}
}

View File

@@ -1,113 +0,0 @@
{
"primary": [
{
"id": "languages",
"label": "Langages",
"items": [
{ "name": "TypeScript", "icon": "simple-icons:typescript", "level": "primary" },
{ "name": "JavaScript", "icon": "simple-icons:javascript", "level": "primary" },
{ "name": "Java", "icon": "simple-icons:openjdk", "level": "primary" },
{ "name": "Python", "icon": "simple-icons:python", "level": "primary" },
{ "name": "PHP", "icon": "simple-icons:php", "level": "primary" }
]
},
{
"id": "frameworks",
"label": "Frameworks",
"items": [
{ "name": "NestJS", "icon": "simple-icons:nestjs", "level": "primary" },
{ "name": "Nuxt", "icon": "simple-icons:nuxt", "level": "primary" },
{ "name": "Next.js", "icon": "simple-icons:nextdotjs", "level": "primary" },
{ "name": "Vue.js", "icon": "simple-icons:vuedotjs", "level": "primary" },
{ "name": "Angular", "icon": "simple-icons:angular", "level": "primary" },
{ "name": "Tailwind CSS", "icon": "simple-icons:tailwindcss", "level": "primary" },
{ "name": "Astro", "icon": "simple-icons:astro", "level": "primary" }
]
},
{
"id": "databases",
"label": "Bases de données",
"items": [
{ "name": "MariaDB", "icon": "simple-icons:mariadb", "level": "primary" },
{ "name": "MySQL", "icon": "simple-icons:mysql", "level": "primary" },
{ "name": "PostgreSQL", "icon": "simple-icons:postgresql", "level": "primary" },
{ "name": "Redis", "icon": "simple-icons:redis", "level": "primary" }
]
},
{
"id": "tools",
"label": "Outils & DevOps",
"items": [
{ "name": "Docker", "icon": "simple-icons:docker", "level": "primary" },
{ "name": "Git", "icon": "simple-icons:git", "level": "primary" },
{ "name": "AWS", "icon": "simple-icons:amazonwebservices", "level": "primary" },
{ "name": "Terraform", "icon": "simple-icons:terraform", "level": "primary" },
{ "name": "NGINX", "icon": "simple-icons:nginx", "level": "primary" },
{ "name": "Plesk", "icon": "simple-icons:plesk", "level": "primary" }
]
},
{
"id": "ide",
"label": "IDE & Éditeurs",
"items": [
{ "name": "WebStorm", "icon": "simple-icons:webstorm", "level": "primary" },
{ "name": "PhpStorm", "icon": "simple-icons:phpstorm", "level": "primary" },
{ "name": "PyCharm", "icon": "simple-icons:pycharm", "level": "primary" },
{ "name": "Claude Code", "icon": "simple-icons:claude", "level": "primary" }
]
},
{
"id": "os",
"label": "Systèmes",
"items": [
{ "name": "Debian", "icon": "simple-icons:debian", "level": "primary" },
{ "name": "Ubuntu", "icon": "simple-icons:ubuntu", "level": "primary" },
{ "name": "macOS", "icon": "simple-icons:macos", "level": "primary" }
]
}
],
"secondary": [
{
"id": "languages-2",
"label": "Autres langages",
"items": [
{ "name": "C#", "icon": "simple-icons:dotnet", "level": "secondary" },
{ "name": "C++", "icon": "simple-icons:cplusplus", "level": "secondary" },
{ "name": "Dart", "icon": "simple-icons:dart", "level": "secondary" },
{ "name": "Kotlin", "icon": "simple-icons:kotlin", "level": "secondary" }
]
},
{
"id": "frameworks-2",
"label": "Autres frameworks",
"items": [
{ "name": "Spring", "icon": "simple-icons:spring", "level": "secondary" },
{ "name": "Symfony", "icon": "simple-icons:symfony", "level": "secondary" },
{ "name": "Express.js", "icon": "simple-icons:express", "level": "secondary" },
{ "name": "FastAPI", "icon": "simple-icons:fastapi", "level": "secondary" },
{ "name": "Flutter", "icon": "simple-icons:flutter", "level": "secondary" },
{ "name": "GraphQL", "icon": "simple-icons:graphql", "level": "secondary" },
{ "name": "Vuetify", "icon": "simple-icons:vuetify", "level": "secondary" },
{ "name": "Bootstrap", "icon": "simple-icons:bootstrap", "level": "secondary" }
]
},
{
"id": "databases-2",
"label": "Autres bases",
"items": [
{ "name": "MongoDB", "icon": "simple-icons:mongodb", "level": "secondary" },
{ "name": "SQLite", "icon": "simple-icons:sqlite", "level": "secondary" }
]
},
{
"id": "cloud",
"label": "Cloud & services",
"items": [
{ "name": "Firebase", "icon": "simple-icons:firebase", "level": "secondary" },
{ "name": "Swagger", "icon": "simple-icons:swagger", "level": "secondary" },
{ "name": "Postman", "icon": "simple-icons:postman", "level": "secondary" },
{ "name": "Stripe", "icon": "simple-icons:stripe", "level": "secondary" }
]
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,36 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
<defs>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.2" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<linearGradient id="grad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#00F0FF" />
<stop offset="100%" stop-color="#B967FF" />
</linearGradient>
</defs>
<rect
x="4" y="4" width="56" height="56" rx="10"
fill="#0A0A0F"
stroke="url(#grad)"
stroke-width="2"
/>
<g filter="url(#glow)">
<path
d="M38 14 L38 40 Q38 48 30 48 Q22 48 22 40"
stroke="#00F0FF"
stroke-width="4"
stroke-linecap="round"
fill="none"
/>
<circle cx="38" cy="14" r="3" fill="#FF2A6D" />
</g>
<line x1="8" y1="52" x2="56" y2="52" stroke="#00F0FF" stroke-width="0.5" opacity="0.3" />
<line x1="8" y1="54" x2="56" y2="54" stroke="#FF2A6D" stroke-width="0.5" opacity="0.3" />
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -0,0 +1,14 @@
KIT LOGO — Johan Leroy (prêt pour le web)
Logo détouré (vraie transparence) à partir de ../../logo.png, puis décliné aux bons formats.
- logo.png Logo du header — JL vert, fond TRANSPARENT (392x320). OK thème clair ET sombre.
- logo-master.png Master carré 1024x1024 transparent (pour régénérer d'autres tailles).
- favicon-16x16.png Favicon 16 (transparent)
- favicon-32x32.png Favicon 32 (transparent)
- favicon.ico Favicon multi-tailles 16/32/48 (transparent)
- apple-touch-icon.png 180x180, fond sombre #09090b (iOS ignore la transparence)
- android-chrome-192x192.png 192, fond sombre #09090b (maskable, zone de sécurité)
- android-chrome-512x512.png 512, fond sombre #09090b (maskable)
Source d'origine (fond damier non transparent) conservée à la racine du kit : ../../logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

BIN
public/images/logo/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
public/images/og.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

Before

Width:  |  Height:  |  Size: 862 KiB

After

Width:  |  Height:  |  Size: 862 KiB

View File

Before

Width:  |  Height:  |  Size: 801 KiB

After

Width:  |  Height:  |  Size: 801 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 221 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 B

4
public/robots.txt Normal file
View File

@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://johanleroy.fr/sitemap.xml

9
public/sitemap.xml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://johanleroy.fr/</loc>
<lastmod>2026-06-19</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
</urlset>

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env node
import bcrypt from 'bcryptjs';
import { randomBytes } from 'node:crypto';
const password = process.argv[2];
if (!password) {
console.error('Usage: npm run hash:password -- <mot_de_passe>');
process.exit(1);
}
const hash = bcrypt.hashSync(password, 12);
const jwtSecret = randomBytes(48).toString('base64');
console.log('\n✅ Copier ce bloc dans ton .env ou variables Plesk :\n');
console.log(`ADMIN_PASSWORD_HASH=${hash}`);
console.log(`ADMIN_JWT_SECRET=${jwtSecret}\n`);

Some files were not shown because too many files have changed in this diff Show More