Documentation
Comment reprendre cette base pour un projet suivant. Ce document décrit ce qui existe réellement dans le dépôt, y compris les pièges qui ont coûté un cycle de correction — ils sont marqués comme tels.
Installation
npm install
npm run dev # développement
npm run build # build de production
npm run start # sert le buildUne seule variable d'environnement est lue : NEXT_PUBLIC_SITE_URL. Tant qu'elle est absente, le site refuse d'être indexé. Voir .env.example.
Les trois contrôles se lancent séparément :
npm run typecheck # TypeScript strict
npm run lint # ESLint, doit sortir sans erreur NI avertissement
npm run verify:tokens # motion.css et motion.ts sont-ils identiques ?
npm run verify # navigateur : serveur devant déjà tournerRègle
npm run verify charge chaque page aux neuf largeurs du §11, dans les deux thèmes, et échoue sur un débordement, un texte tronqué, une requête en 4xx, une cible tactile sous 24 px, ou un texte laissé transparent alors qu'il est visible. Un build vert ne remplace pas ce contrôle.Architecture
Le dépôt sert deux produits sur un seul déploiement. Les groupes de routes portent le chrome, la racine ne porte que les providers.
src/
├── app/
│ ├── layout.tsx polices, thème, providers — rien d'autre
│ ├── page.tsx / redirige vers /fr
│ ├── (sentis)/[locale]/ site de l'agence : /fr et /en
│ ├── (showcase)/nexus/ vitrine : /nexus et ses sous-pages
│ └── (app)/nexus/ enveloppe applicative : /nexus/dashboard
├── components/
│ ├── ui/ primitives vendues (shadcn + Magic UI)
│ ├── layout/ Container, Section, en-têtes, pieds de page
│ ├── motion/ bibliothèque d'animations
│ ├── sections/ blocs composés de la vitrine
│ └── sentis/ chrome propre au site de l'agence
├── lib/ nav, i18n, site, motion, stats, utils
└── styles/
├── tokens.css couleur, espacement, rayon, ombre, grille
├── motion.css durées, courbes, reduced-motion
├── scroll-driven.css animations natives liées au scroll
└── sentis.css peau de la marque Studio SentisRègle
sections/ consomme ui/ et layout/, jamais l'inverse. Une primitive qui importe un bloc composé devient impossible à réutiliser ailleurs.Piège
./demo), pas aliasés. Un alias @/app/(showcase)/… casse dès qu'on renomme le groupe, ce qui est arrivé.Design tokens
Toute valeur visuelle vit dans src/styles/tokens.css. Un composant ne contient jamais de couleur, de rayon ni d'ombre en dur.
Règle
.claude/skills/impeccable/scripts/impeccable detect <fichier>. Trois couleurs candidates ont été rejetées à ce stade.Règle
dataviz, qui vérifie la bande de clarté, le plancher de chroma, la séparation daltonienne et le contraste. La palette d'origine du projet échouait trois de ces cinq contrôles.Piège
--content-max est en ch : l'unité se résout sur la police de l'élément qui porte la classe. À poser sur le texte lui-même, jamais sur un conteneur dont la taille de police diffère.Ajouter une marque
Le site de l'agence est la démonstration que la base sert à autre chose qu'elle-même : même architecture de tokens, mêmes composants, autre identité. Trois étapes.
/* 1. src/styles/<marque>.css — redéfinir les MÊMES noms de tokens */
[data-brand="sentis"] {
--paper: #fcfaf6;
--ink: #1a1714;
--radius: 0.625rem;
--content-max: 58ch;
}
.dark [data-brand="sentis"] { /* … */ }
/* 2. l'importer dans src/app/globals.css */
@import "../styles/sentis.css";// 3. poser l'attribut sur le layout de la marque
<div data-brand="sentis" className={`${police.variable} bg-paper text-ink`}>
{children}
</div>Règle
Composants
src/components/ui/ n'est pas écrit à la main : il est peuplé par npx shadcn@latest add. Le modifier signifie qu'une mise à jour du registre écrasera le travail.
Règle
setState dans un effet, et un compteur rendu à zéro côté serveur.Règle
Math.random() pendant le rendu, setState dans un effet), deux parce que rien ne s'en servait.Animations
Durées, courbes, décalages et distances viennent de motion.css, dont src/lib/motion.ts est le miroir pour Motion, qui a besoin de nombres. npm run verify:tokens échoue si les deux divergent.
Règle
prefers-reduced-motion est traité une fois, globalement. Un composant ne rend jamais une version raccourcie de son animation : il rend l'état final.Piège
useTransform liée au scroll doit tenir dans [0, 1]. Motion confie ces valeurs à l'API d'animation du navigateur, qui refuse un offset négatif ou supérieur à 1 et lève une erreur à chaque chargement.Piège
opacity: 0.35 fait tomber son texte à 1,8:1. Le signal passe par un filet, une couleur d'accent ou une bordure.Responsive
Neuf largeurs sont vérifiées : 320, 375, 390, 430, 768, 834, 1024, 1280 et 1440 px, dans les deux thèmes. Container porte la gouttière unique du projet ; c'est lui qui garantit que rien ne touche le bord de l'écran.
Piège
min-width: auto : un tableau imposait sa largeur minimale à toute la piste, donc à toutes les cartes de la même rangée. min-w-0 sur l'élément de grille est le correctif.Piège
Accessibilité
Ce qui est vérifié automatiquement : contraste des tokens, cibles tactiles d'au moins 24 px, absence de texte transparent dans le viewport, comportement sous mouvement réduit, absence d'erreur de console.
Règle
Règle
Performance
Toutes les pages sont statiques. GSAP, Lenis et Three.js ne sont pas installés : les sections épinglées reposent sur position: sticky et le scroll horizontal sur scroll-snap natif.
Piège
public/ compris, dans le bundle serveur.Conventions de code
TypeScript strict, aucun any. Composants serveur par défaut ; "use client" seulement pour un état, un écouteur ou une API navigateur.
Règle
npm run lint doit sortir sans erreur ni avertissement. La règle react-hooks interdit setState dans un effet : le remplaçant est useSyncExternalStore, utilisé à deux endroits du dépôt.Règle
Créer une page
1. src/app/(showcase)/nexus/<nom>/page.tsx
export const metadata = { title, description }
2. Structure : <Section index title lead> pour chaque bloc.
La gouttière vient de Container, jamais d'un padding local.
3. Partie interactive dans un fichier voisin en "use client",
importé relativement (./panels), pas via un alias.
4. src/lib/nav.ts : ajouter l'entrée, status "planned" puis "live".
Le site affiche l'état réel — une page annoncée finie ne l'est pas
tant que l'entrée n'est pas passée à "live".
5. tests/responsive-check.mjs : ajouter la route à PAGES.
6. npm run lint && npm run typecheck && npm run build
npm run start & puis npm run verifyRègle
Contribution
L'état réel du projet, ce qui reste ouvert et ce qui a été assumé sont dans la vitrine et dans les documents du dépôt : docs/audit.md, docs/architecture.md et docs/quality-checklist.md.
Règle