This commit is contained in:
Ladebeze66 2026-04-22 14:40:03 +02:00
parent 7a42786648
commit 20d4c78df8
6 changed files with 268 additions and 130 deletions

View File

@ -1,18 +1,24 @@
"use client" "use client";
import { useState } from "react"; import { useEffect, useState } from "react";
export default function Footer() { export default function Footer() {
const [count, setCount] = useState(0); const [visitCount, setVisitCount] = useState(0);
function handleClick() { useEffect(() => {
setCount(count + 1); const visits = localStorage.getItem("visitCount");
} const newVisitCount = visits ? parseInt(visits, 10) + 1 : 1;
localStorage.setItem("visitCount", newVisitCount.toString());
setVisitCount(newVisitCount);
}, []);
return ( return (
<footer className="min-h-[80px] w-full min-w-0 rounded-lg bg-white/50 backdrop-blur"> <footer className="min-h-[80px] w-full min-w-0 rounded-lg bg-white/50 backdrop-blur">
<div className="mx-auto flex max-w-4xl min-w-0 flex-col items-center px-4 py-6 font-headline text-sm text-gray-700"> <div className="mx-auto flex max-w-4xl min-w-0 flex-col items-center gap-1 px-4 py-6 font-headline text-sm text-gray-700">
<p>&copy; {new Date().getFullYear()} Gras-Calvet Fernand</p> <p>&copy; {new Date().getFullYear()} Gras-Calvet Fernand</p>
<p className="text-[10px] uppercase tracking-[0.3em] text-outline">
Visite n° {visitCount}
</p>
</div> </div>
</footer> </footer>
); );

View File

@ -1,3 +1,5 @@
"use client";
import { useEffect } from "react"; import { useEffect } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import CarouselCompetences from "./CarouselCompetences"; import CarouselCompetences from "./CarouselCompetences";
@ -27,37 +29,67 @@ interface ModalGlossaireProps {
export default function ModalGlossaire({ mot, onClose }: ModalGlossaireProps) { export default function ModalGlossaire({ mot, onClose }: ModalGlossaireProps) {
const apiUrl = getApiUrl(); const apiUrl = getApiUrl();
// Verrouille le scroll du body + ferme sur Esc.
useEffect(() => { useEffect(() => {
document.body.classList.add("overflow-hidden"); document.body.classList.add("overflow-hidden");
const handleKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", handleKey);
return () => { return () => {
document.body.classList.remove("overflow-hidden"); document.body.classList.remove("overflow-hidden");
window.removeEventListener("keydown", handleKey);
}; };
}, []); }, [onClose]);
const images = mot.images?.map((img) => ({ const images =
url: `${apiUrl}${img.formats?.large?.url || img.url}`, mot.images?.map((img) => ({
alt: img.name || "Illustration", url: `${apiUrl}${img.formats?.large?.url || img.url}`,
})) || []; alt: img.name || "Illustration",
})) || [];
return createPortal( return createPortal(
<div className="fixed inset-0 w-screen h-screen bg-black bg-opacity-75 flex items-center justify-center z-[1000]"> <div
<div className="bg-white/60 p-6 rounded-lg shadow-lg w-[114vw] max-w-6xl relative h-[72vh]"> className="fixed inset-0 z-[1000] flex items-center justify-center bg-on-surface/75 backdrop-blur-sm p-4"
<button className="absolute top-2 right-2 text-gray-700 text-sm p-1" onClick={onClose}> role="dialog"
aria-modal="true"
aria-label={`Glossaire : ${mot.mot_clef}`}
onClick={onClose}
>
{/* Carte interne : largeur fluide avec marge, hauteur capée, scroll interne si besoin.
stopPropagation pour ne pas fermer la modale quand on interagit à l'intérieur. */}
<div
className="relative flex w-full max-w-4xl max-h-[90vh] flex-col overflow-hidden rounded-sheet bg-surface-container-lowest/95 backdrop-blur-vellum p-6 shadow-ambient"
onClick={(e) => e.stopPropagation()}
>
<button
type="button"
className="absolute right-3 top-3 z-10 flex h-10 w-10 items-center justify-center rounded-full text-on-surface-variant transition-colors hover:bg-surface-container hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"
onClick={onClose}
aria-label="Fermer la fenêtre du glossaire"
>
<span className="material-symbols-outlined" aria-hidden="true">
close
</span>
</button> </button>
<div className="flex flex-col md:flex-row gap-6 h-full"> <div className="flex flex-col md:flex-row gap-6 overflow-y-auto pr-1">
{/* Description */}
<div className="md:w-1/2"> <div className="md:w-1/2">
<h2 className="text-3xl font-headline font-bold mb-4">{mot.mot_clef}</h2> <h2 className="mb-4 pr-10 text-2xl font-headline font-extrabold tracking-tight text-primary md:text-3xl">
<p className="font-headline font-bold text-xs text-gray-700 mb-6">{mot.description}</p> {mot.mot_clef}
</h2>
<p className="font-body text-base leading-relaxed text-on-surface-variant">
{mot.description}
</p>
</div> </div>
<div className="md:w-1/2 h-full"> <div className="md:w-1/2 min-h-[200px] md:min-h-[320px]">
{images.length > 0 ? ( {images.length > 0 ? (
<CarouselCompetences images={images} className="w-full h-full" /> <CarouselCompetences images={images} className="h-full w-full" />
) : ( ) : (
<p className="text-gray-500">Aucune image disponible.</p> <p className="text-sm text-on-surface-variant italic">
Aucune image disponible.
</p>
)} )}
</div> </div>
</div> </div>

View File

@ -1,18 +1,47 @@
"use client" "use client";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
/**
* NavLink lien de navigation avec état actif.
*
* Props :
* - text (string) : libellé affiché.
* - path (string) : URL cible.
* - onClick (fn, optionnel) : handler (ex. fermeture du drawer mobile).
* - className (string, optionnel) : classes utilitaires communes (fond, padding).
* - activeClassName (string, optionnel) : classes appliquées quand la route courante == path.
* - inactiveClassName (string, optionnel) : classes appliquées quand la route courante != path.
*
* Sans activeClassName / inactiveClassName, on retombe sur le comportement
* historique `opacity-100 / opacity-50 hover:opacity-65` pour ne rien casser
* sur les NavLink du header desktop qui n'en fournissent pas.
*/
export default function NavLink(props) { export default function NavLink(props) {
const {
text,
path,
onClick,
className = "",
activeClassName,
inactiveClassName,
} = props;
const pathname = usePathname(); const pathname = usePathname();
const active = pathname === props.path; const active = pathname === path;
const stateClass = active
? activeClassName ?? "opacity-100"
: inactiveClassName ?? "opacity-50 hover:opacity-65";
return ( return (
<Link <Link
className={active ? "opacity-100" : "opacity-50 hover:opacity-65"} className={`${className} ${stateClass}`.trim()}
href={props.path} href={path}
onClick={onClick}
> >
{props.text} {/* Texte du lien */} {text}
</Link> </Link>
); );
} }

View File

@ -1,14 +1,12 @@
"use client"; "use client";
import React, { useEffect, useState, useRef } from "react"; import React, { useEffect, useRef, useState } from "react";
import Footer from "./components/Footer"; import Footer from "./components/Footer";
import "./assets/main.css"; import "./assets/main.css";
import "./globals.css"; import "./globals.css";
import NavLink from "./components/NavLink"; import NavLink from "./components/NavLink";
export default function RootLayout({ children }: { children: React.ReactNode }) { export default function RootLayout({ children }: { children: React.ReactNode }) {
const [visitCount, setVisitCount] = useState(0);
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
const menuRef = useRef<HTMLElement | null>(null); const menuRef = useRef<HTMLElement | null>(null);
const burgerRef = useRef<HTMLButtonElement | null>(null); const burgerRef = useRef<HTMLButtonElement | null>(null);
@ -28,17 +26,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
closeTimerRef.current = setTimeout(() => setIsMenuOpen(false), AUTO_CLOSE_MS); closeTimerRef.current = setTimeout(() => setIsMenuOpen(false), AUTO_CLOSE_MS);
}; };
const openMenu = () => setIsMenuOpen(true);
const closeMenu = () => setIsMenuOpen(false); const closeMenu = () => setIsMenuOpen(false);
const toggleMenu = () => setIsMenuOpen((v) => !v); const toggleMenu = () => setIsMenuOpen((v) => !v);
useEffect(() => {
const visits = localStorage.getItem("visitCount");
const newVisitCount = visits ? parseInt(visits) + 1 : 1;
localStorage.setItem("visitCount", newVisitCount.toString());
setVisitCount(newVisitCount);
}, []);
useEffect(() => { useEffect(() => {
if (!isMenuOpen) { if (!isMenuOpen) {
clearAutoClose(); clearAutoClose();
@ -70,86 +60,146 @@ export default function RootLayout({ children }: { children: React.ReactNode })
} }
}, [isMenuOpen]); }, [isMenuOpen]);
// Classes communes pour les liens du drawer mobile : fond container primaire,
// radius "tile", hover qui inverse vers fond clair + texte primaire (palette Stitch).
const drawerLinkClass =
"block px-4 py-2 rounded-tile bg-primary-container/60 text-white transition-colors duration-200 hover:bg-primary-fixed hover:text-primary";
const drawerLinkActive = "bg-primary-fixed text-primary";
// NavLink desktop : état actif souligné, inactif discret (palette Stitch).
const desktopLinkActive = "text-primary border-b-2 border-primary-fixed pb-0.5";
const desktopLinkInactive =
"text-on-surface-variant hover:text-primary transition-colors";
return ( return (
<html lang="fr"> <html lang="fr">
<body className="min-w-0 overflow-x-hidden antialiased"> <body className="min-w-0 overflow-x-hidden antialiased">
<div className="relative grid min-h-[100dvh] w-full min-w-0 grid-rows-[auto_1fr_auto]"> <div className="relative grid min-h-[100dvh] w-full min-w-0 grid-rows-[auto_1fr_auto]">
{/* Conserve le fond en plein écran */} {/* Wallpaper plein écran (fondation, ne change pas avec la refonte). */}
<div className="absolute inset-0 bg-wallpaper"></div> <div className="absolute inset-0 bg-wallpaper"></div>
{/* Contenu centré avec largeur contrôlée */} {/* Cercles animés : repalette en ton indigo-ardoise (Stitch "Digital Atelier"). */}
<div className="relative z-10 max-w-5xl w-full mx-auto"></div> <div className="absolute z-0 inset-0 overflow-hidden pointer-events-none">
<div className="circle-one blur-3xl w-40 md:w-64 h-40 md:h-64 rounded-full bg-primary/40 top-0 right-10 md:right-28 absolute"></div>
{/* Cercles animés */} <div className="circle-two blur-3xl w-40 md:w-64 h-40 md:h-64 rounded-full bg-primary-container/30 bottom-0 left-10 md:left-28 absolute"></div>
<div className="absolute z-0 inset-0 overflow-hidden">
<div className="circle-one blur-3xl w-40 md:w-64 h-40 md:h-64 rounded-full bg-rose-400/60 top-0 right-10 md:right-28 absolute"></div>
<div className="circle-two blur-3xl w-40 md:w-64 h-40 md:h-64 rounded-full bg-indigo-400/60 bottom-0 left-10 md:left-28 absolute"></div>
</div> </div>
{/* Header */} {/* Header "No-Line" : pas de bordure pleine, juste un shift tonal + ombre ambient diffuse. */}
<header className="fixed left-0 top-0 z-20 h-16 w-full min-w-0 border-b-2 border-gray-500 bg-white/50 px-4 py-2 shadow-md backdrop-blur-md md:h-16 md:px-6"> <header className="fixed left-0 top-0 z-20 h-16 w-full min-w-0 bg-surface/80 px-4 py-2 shadow-ambient-sm backdrop-blur-vellum md:h-16 md:px-6">
<div className="mx-auto flex max-w-4xl min-w-0 items-center justify-between gap-2"> <div className="mx-auto flex max-w-4xl min-w-0 items-center justify-between gap-2">
<h2 className="min-w-0 truncate pr-1 text-xl font-headline font-extrabold italic tracking-tight md:text-2xl"> <h2 className="min-w-0 truncate pr-1 text-xl font-headline font-extrabold italic tracking-tight text-primary md:text-2xl">
Portfolio Gras-Calvet Fernand Portfolio Gras-Calvet Fernand
</h2> </h2>
{/* Bouton menu burger */} {/* Burger ghost (Material Symbols) : plus sobre, couleur primaire, hover tonal. */}
<button <button
ref={burgerRef} ref={burgerRef}
type="button" type="button"
className="md:hidden p-2 bg-gray-300 rounded" className="md:hidden flex h-10 w-10 items-center justify-center rounded-full text-primary transition-colors hover:bg-surface-container focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"
onClick={toggleMenu} onClick={toggleMenu}
aria-label={isMenuOpen ? "Fermer le menu" : "Ouvrir le menu"} aria-label={isMenuOpen ? "Fermer le menu" : "Ouvrir le menu"}
aria-expanded={isMenuOpen} aria-expanded={isMenuOpen}
aria-controls="mobile-drawer" aria-controls="mobile-drawer"
> >
{isMenuOpen ? "✕" : "☰"} <span className="material-symbols-outlined" aria-hidden="true">
{isMenuOpen ? "close" : "menu"}
</span>
</button> </button>
{/* Menu desktop */} {/* Menu desktop : NavLink avec états actif/inactif éditoriaux. */}
<nav className="hidden md:flex"> <nav className="hidden md:flex">
<ul className="flex gap-x-4 text-gray-700 font-headline font-bold"> <ul className="flex gap-x-6 font-headline text-sm font-bold uppercase tracking-widest">
<li><NavLink text="Accueil" path="/" /></li> <li>
<li><NavLink text="Portfolio" path="/portfolio" /></li> <NavLink
<li><NavLink text="Compétences" path="/competences" /></li> text="Accueil"
<li><NavLink text="Contact" path="/contact" /></li> path="/"
activeClassName={desktopLinkActive}
inactiveClassName={desktopLinkInactive}
/>
</li>
<li>
<NavLink
text="Portfolio"
path="/portfolio"
activeClassName={desktopLinkActive}
inactiveClassName={desktopLinkInactive}
/>
</li>
<li>
<NavLink
text="Compétences"
path="/competences"
activeClassName={desktopLinkActive}
inactiveClassName={desktopLinkInactive}
/>
</li>
<li>
<NavLink
text="Contact"
path="/contact"
activeClassName={desktopLinkActive}
inactiveClassName={desktopLinkInactive}
/>
</li>
</ul> </ul>
</nav> </nav>
</div> </div>
</header> </header>
{/* Drawer mobile (tiroir gauche, 70%, fond sombre translucide) */} {/* Drawer mobile (tiroir gauche, 70 %, fond primaire translucide). */}
<div <div
className={`mobile-drawer-root fixed inset-0 z-40 md:hidden transition-opacity duration-300 ease-out ${ className={`mobile-drawer-root fixed inset-0 z-40 md:hidden transition-opacity duration-300 ease-out ${
isMenuOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none" isMenuOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
}`} }`}
aria-hidden={!isMenuOpen} aria-hidden={!isMenuOpen}
> >
{/* Voile : tap pour fermer */}
<div <div
className="absolute inset-0 bg-black/40 backdrop-blur-sm" className="absolute inset-0 bg-on-surface/40 backdrop-blur-sm"
onClick={closeMenu} onClick={closeMenu}
aria-hidden="true" aria-hidden="true"
/> />
{/* Colonne tiroir */}
<nav <nav
id="mobile-drawer" id="mobile-drawer"
ref={menuRef} ref={menuRef}
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-label="Menu de navigation" aria-label="Menu de navigation"
className={`mobile-drawer-panel relative z-10 h-full w-[70%] max-w-sm bg-gray-900/70 backdrop-blur-md border-r border-white/10 shadow-2xl flex flex-col gap-3 px-6 pt-20 pb-8 text-white font-headline font-extrabold text-2xl tracking-tight transition-transform duration-300 ease-out ${ className={`mobile-drawer-panel relative z-10 h-full w-[70%] max-w-sm bg-primary/90 backdrop-blur-vellum shadow-ambient flex flex-col gap-3 px-6 pt-20 pb-8 font-headline text-lg font-bold tracking-tight transition-transform duration-300 ease-out ${
isMenuOpen ? "translate-x-0" : "-translate-x-full" isMenuOpen ? "translate-x-0" : "-translate-x-full"
}`} }`}
onClick={scheduleAutoClose} onClick={scheduleAutoClose}
onTouchStart={scheduleAutoClose} onTouchStart={scheduleAutoClose}
onTouchMove={scheduleAutoClose} onTouchMove={scheduleAutoClose}
> >
<NavLink text="Accueil" path="/" onClick={closeMenu} className="text-lg text-white hover:text-gray-900 px-4 py-2 bg-gray-700/80 rounded-lg transition-all duration-300 hover:bg-gray-500" /> <NavLink
<NavLink text="Portfolio" path="/portfolio" onClick={closeMenu} className="text-lg text-white hover:text-gray-900 px-4 py-2 bg-gray-700/80 rounded-lg transition-all duration-300 hover:bg-gray-500" /> text="Accueil"
<NavLink text="Compétences" path="/competences" onClick={closeMenu} className="text-lg text-white hover:text-gray-900 px-4 py-2 bg-gray-700/80 rounded-lg transition-all duration-300 hover:bg-gray-500" /> path="/"
<NavLink text="Contact" path="/contact" onClick={closeMenu} className="text-lg text-white hover:text-gray-900 px-4 py-2 bg-gray-700/80 rounded-lg transition-all duration-300 hover:bg-gray-500" /> onClick={closeMenu}
className={drawerLinkClass}
activeClassName={drawerLinkActive}
/>
<NavLink
text="Portfolio"
path="/portfolio"
onClick={closeMenu}
className={drawerLinkClass}
activeClassName={drawerLinkActive}
/>
<NavLink
text="Compétences"
path="/competences"
onClick={closeMenu}
className={drawerLinkClass}
activeClassName={drawerLinkActive}
/>
<NavLink
text="Contact"
path="/contact"
onClick={closeMenu}
className={drawerLinkClass}
activeClassName={drawerLinkActive}
/>
</nav> </nav>
</div> </div>
@ -160,13 +210,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<div className="relative z-10 w-full min-w-0 shrink-0"> <div className="relative z-10 w-full min-w-0 shrink-0">
<Footer /> <Footer />
</div> </div>
<div className="absolute bottom-0 right-0 p-4 text-sm text-gray-500">
NV : {visitCount}
</div>
</div> </div>
</body> </body>
</html> </html>
); );
} }

View File

@ -1,7 +1,7 @@
# Refonte visuelle — Direction "Digital Atelier" # Refonte visuelle — Direction "Digital Atelier"
**Créé :** 2026-04-22 **Créé :** 2026-04-22
**Statut :** en cours (étapes 1-3/8 terminées) **Statut :** en cours (étapes 1-4/8 terminées)
**Source d'inspiration :** `stitch_V1/` (design newsletter Stitch — `DESIGN.md` et `code.html`). **Source d'inspiration :** `stitch_V1/` (design newsletter Stitch — `DESIGN.md` et `code.html`).
**Audit préalable :** [`captures/AUDIT-VISUEL.md`](./captures/AUDIT-VISUEL.md). **Audit préalable :** [`captures/AUDIT-VISUEL.md`](./captures/AUDIT-VISUEL.md).
@ -57,7 +57,7 @@ Chaque étape = un lot cohérent + éventuelle mise à jour de `captures/AUDIT-V
| 1 | Fondations : tokens Tailwind + import polices + icônes | `tailwind.config.ts`, `app/globals.css` | **fait** (2026-04-22) | | 1 | Fondations : tokens Tailwind + import polices + icônes | `tailwind.config.ts`, `app/globals.css` | **fait** (2026-04-22) |
| 2 | Garde-fou doc + mise à jour feuille de route | `docs-site-interne/REFONTE-VISUELLE.md`, `docs-site-interne/feuille-de-route.md` | **fait** (2026-04-22) | | 2 | Garde-fou doc + mise à jour feuille de route | `docs-site-interne/REFONTE-VISUELLE.md`, `docs-site-interne/feuille-de-route.md` | **fait** (2026-04-22) |
| 3 | Migration typographique globale (Orbitron → Manrope / Newsreader) | `app/**/*.{tsx,jsx,js}`, `app/assets/main.css` | **fait** (2026-04-22) | | 3 | Migration typographique globale (Orbitron → Manrope / Newsreader) | `app/**/*.{tsx,jsx,js}`, `app/assets/main.css` | **fait** (2026-04-22) |
| 4 | Layout racine : header No-Line, burger ghost, palette cercles, compteur migré, drawer | `app/layout.tsx`, `app/globals.css` | à faire | | 4 | Layout racine : header No-Line, burger ghost, palette cercles, compteur migré, drawer | `app/layout.tsx`, `app/components/NavLink.jsx`, `app/components/Footer.jsx` | **fait** (2026-04-22) |
| 5 | Home : hero vellum, portrait frame, takeaways, pull-quote, CTAs | `app/page.tsx` | à faire | | 5 | Home : hero vellum, portrait frame, takeaways, pull-quote, CTAs | `app/page.tsx` | à faire |
| 6 | Listes portfolio + compétences : grille asymétrique, cartes éditoriales | `app/portfolio/page.jsx`, `app/competences/page.jsx`, composants `Carousel*` | à faire | | 6 | Listes portfolio + compétences : grille asymétrique, cartes éditoriales | `app/portfolio/page.jsx`, `app/competences/page.jsx`, composants `Carousel*` | à faire |
| 7 | Fiches détail + modale glossaire + GrasBot (jewel flottant) | `app/portfolio/[slug]/page.tsx`, `app/competences/[slug]/page.tsx`, `app/components/ModalGlossaire.tsx`, `app/components/ChatBot.js` | à faire | | 7 | Fiches détail + modale glossaire + GrasBot (jewel flottant) | `app/portfolio/[slug]/page.tsx`, `app/competences/[slug]/page.tsx`, `app/components/ModalGlossaire.tsx`, `app/components/ChatBot.js` | à faire |
@ -78,6 +78,30 @@ Après l'étape 3, retour utilisateur : **couleurs de texte différentes** entre
**Leçon retenue (à appliquer aux étapes suivantes)** : quand on supprime un "masque" CSS (comme la couleur forcée d'Orbitron), toujours vérifier que la valeur qui va ré-émerger par héritage est bien la valeur attendue, pas une variable dépendante du contexte d'exécution. **Leçon retenue (à appliquer aux étapes suivantes)** : quand on supprime un "masque" CSS (comme la couleur forcée d'Orbitron), toujours vérifier que la valeur qui va ré-émerger par héritage est bien la valeur attendue, pas une variable dépendante du contexte d'exécution.
## 4 ter. Correctif urgent modale glossaire (2026-04-22) — blocage mobile
Après l'étape 4, retour utilisateur sur Samsung S25 Ultra : les mots-clés du glossaire (compétences) ouvrent bien la modale mais **la modale déborde de l'écran**, **la croix de fermeture est hors champ**, impossible de refermer sans recharger la page.
**Causes identifiées** dans `app/components/ModalGlossaire.tsx` (pré-existantes avant la refonte) :
- Carte interne en `w-[114vw] max-w-6xl` : force une largeur > viewport sur mobile (114 % de 400 px = 456 px dans une fenêtre de 400 px), et sur desktop la contrainte est masquée par `max-w-6xl`.
- Hauteur figée `h-[72vh]` sans scroll interne : le contenu est simplement tronqué quand il dépasse.
- Aucune fermeture au tap sur le voile, ni à Esc. Seule issue = bouton `✖` en haut à droite, hors champ sur mobile.
- Bouton de fermeture en `text-sm p-1` : zone tactile < 44 px, sous le seuil Material Design pour le tactile.
**Fix** (anticipe les besoins de l'étape 7) :
- Carte interne : `w-full max-w-4xl max-h-[90vh]` + padding 4 sur le voile pour la marge latérale sur mobile.
- Contenu intérieur en `overflow-y-auto` pour scroll interne si nécessaire.
- Voile cliquable pour fermer, `stopPropagation` sur la carte pour ne pas fermer en interagissant avec.
- Fermeture `Escape` via `keydown` global.
- Bouton de fermeture rond `h-10 w-10` avec Material Symbol `close`, focus-visible, position `absolute top-3 right-3`.
- Alignement palette Stitch : voile `bg-on-surface/75 backdrop-blur-sm`, carte `bg-surface-container-lowest/95 backdrop-blur-vellum shadow-ambient rounded-sheet`, titre `text-primary`, description en `font-body` serif (Newsreader) pour lisibilité, texte `text-on-surface-variant`.
- Ajout de `"use client"` (manquant).
- `role="dialog" aria-modal="true" aria-label={...}` sur le conteneur, `aria-label` explicite sur le bouton de fermeture.
Ce correctif concerne uniquement le composant `ModalGlossaire`. L'étape 7 reprendra la refonte globale de cette zone (cohérence visuelle avec les fiches détail) mais le blocage UX mobile est levé dès maintenant.
## 5. Checklist relecture (à passer à la fin de chaque étape) ## 5. Checklist relecture (à passer à la fin de chaque étape)
- [ ] Aucune colonne unique globale `max-w-xl` (c'est le format newsletter). - [ ] Aucune colonne unique globale `max-w-xl` (c'est le format newsletter).

View File

@ -8,7 +8,7 @@ Document vivant : ajuster les statuts et dates au fil du travail.
| ID | Sujet | Statut | Notes | | ID | Sujet | Statut | Notes |
|----|--------|--------|--------| |----|--------|--------|--------|
| R1 | Moderniser lUI (design system, cohérence typo/couleurs) | En cours | Direction "Digital Atelier" inspirée de `stitch_V1/` ; cadrage et plan dans [`REFONTE-VISUELLE.md`](./REFONTE-VISUELLE.md). Étapes 1-3 (tokens + garde-fou + migration typo globale) faites le 2026-04-22. | | R1 | Moderniser lUI (design system, cohérence typo/couleurs) | En cours | Direction "Digital Atelier" inspirée de `stitch_V1/` ; cadrage et plan dans [`REFONTE-VISUELLE.md`](./REFONTE-VISUELLE.md). Étapes 1-4 (tokens + garde-fou + migration typo globale + layout racine) faites le 2026-04-22. |
| R2 | Homogénéiser TS vs JS dans `app/` | À faire | Migrer progressivement les `.jsx`/`.js` | | R2 | Homogénéiser TS vs JS dans `app/` | À faire | Migrer progressivement les `.jsx`/`.js` |
| R3 | Centraliser config API (Strapi + LLM) via `.env` | À faire | Remplacer URLs en dur où pertinent | | R3 | Centraliser config API (Strapi + LLM) via `.env` | À faire | Remplacer URLs en dur où pertinent |
| R4 | Revoir `layout.tsx` (server vs client, perf SEO) | À faire | Évaluer extraction header/footer | | R4 | Revoir `layout.tsx` (server vs client, perf SEO) | À faire | Évaluer extraction header/footer |
@ -40,3 +40,5 @@ Document vivant : ajuster les statuts et dates au fil du travail.
| 2026-04-22 | Refonte visuelle "Digital Atelier" — étape 1 (tokens Tailwind : palette Stitch, `font-headline` Manrope + `font-body` Newsreader, `rounded-sheet/tile`, `shadow-ambient/jewel`) et étape 2 (garde-fou + plan dans [`REFONTE-VISUELLE.md`](./REFONTE-VISUELLE.md)). | | 2026-04-22 | Refonte visuelle "Digital Atelier" — étape 1 (tokens Tailwind : palette Stitch, `font-headline` Manrope + `font-body` Newsreader, `rounded-sheet/tile`, `shadow-ambient/jewel`) et étape 2 (garde-fou + plan dans [`REFONTE-VISUELLE.md`](./REFONTE-VISUELLE.md)). |
| 2026-04-22 | Refonte visuelle — étape 3 : migration typographique globale. Toutes les classes `font-orbitron-*` (12 définitions CSS, 29 occurrences dans 11 fichiers) remplacées par `font-headline` Manrope + tailles/poids Tailwind explicites. Import Google Fonts Orbitron supprimé de `app/assets/main.css`. | | 2026-04-22 | Refonte visuelle — étape 3 : migration typographique globale. Toutes les classes `font-orbitron-*` (12 définitions CSS, 29 occurrences dans 11 fichiers) remplacées par `font-headline` Manrope + tailles/poids Tailwind explicites. Import Google Fonts Orbitron supprimé de `app/assets/main.css`. |
| 2026-04-22 | Refonte visuelle — correctif post-étape 3 : régression de couleurs texte entre desktop/mobile. Retrait du `@media (prefers-color-scheme: dark)` hérité du template Next (incohérent avec l'arbitrage "light-only"), `--foreground` fixé à `#191c1d` (on-surface Stitch), `body` avec couleur non-dépendante du thème système. 3 classes Tailwind invalides `text-black-500/700` remplacées par `text-gray-700` (`app/layout.tsx`, `app/page.tsx`, `app/components/ContentSectionCompetences.tsx`). | | 2026-04-22 | Refonte visuelle — correctif post-étape 3 : régression de couleurs texte entre desktop/mobile. Retrait du `@media (prefers-color-scheme: dark)` hérité du template Next (incohérent avec l'arbitrage "light-only"), `--foreground` fixé à `#191c1d` (on-surface Stitch), `body` avec couleur non-dépendante du thème système. 3 classes Tailwind invalides `text-black-500/700` remplacées par `text-gray-700` (`app/layout.tsx`, `app/page.tsx`, `app/components/ContentSectionCompetences.tsx`). |
| 2026-04-22 | Refonte visuelle — étape 4 : layout racine. Header "No-Line" (bordure pleine supprimée, `shadow-ambient-sm` + `backdrop-blur-vellum`, titre en `text-primary`). Burger refait en ghost button (Material Symbols `menu`/`close` au lieu des caractères `☰`/`✕`). Cercles animés repeints en `bg-primary/40` + `bg-primary-container/30`. Drawer mobile en `bg-primary/90 backdrop-blur-vellum` + liens éditoriaux (`bg-primary-container/60` → hover `bg-primary-fixed text-primary`). Bug préexistant **corrigé** : `NavLink` ignorait `className` et `onClick` fournis par le drawer mobile → refait avec support `className` / `onClick` / `activeClassName` / `inactiveClassName`, comportement desktop historique préservé. Compteur de visites migré de `layout.tsx` (bloc orphelin `absolute bottom-0 right-0`) vers `Footer.jsx` (ligne discrète `text-[10px] uppercase tracking-[0.3em]`). Nettoyage : state `visitCount` + useEffect déplacés, `div.max-w-5xl` vide retirée, state `count` inutilisé retiré de `Footer.jsx`. |
| 2026-04-22 | Refonte visuelle — correctif urgent `ModalGlossaire` (blocage mobile signalé sur Samsung S25 Ultra). `w-[114vw] max-w-6xl h-[72vh]``w-full max-w-4xl max-h-[90vh]` + `overflow-y-auto`. Fermeture ajoutée sur tap-voile et `Escape`. Bouton fermeture rond 40 px Material Symbol `close`. Alignement palette Stitch (`bg-on-surface/75`, `bg-surface-container-lowest/95`, `rounded-sheet`, `text-primary`, description `font-body` Newsreader). `"use client"` + `role="dialog" aria-modal` ajoutés. |