# 19 — Style : Mantine et CSS modules

**Statut : fait · Phase : transverse · Mis à jour : 30/10/2026.**

Décidé le 29/10/2026. Tout le style de Récit passe sous **Mantine** avec une seule forme de style
dans le code : **un fichier `*.module.css` à côté de chaque composant**. Plus aucune classe
utilitaire écrite dans le JSX, plus aucun `style={…}` dans le JSX, plus de Tailwind, plus de
shadcn/ui, plus de Radix. Le plan commence par l'inventaire complet de ce qui existe (§1), fixe la
cible (§2) et la loi (§0.3), découpe le travail en lots exécutables par plusieurs agents (§10), et
encadre le tout par des captures d'écran avant/après de chaque page et de chaque composant (§7). Ce
plan **ne s'implémente pas** avant la demande explicite de l'utilisateur, et s'exécute sur une
branche à part (§11).

## 0. Décision, portée et lecture de la demande

### 0.1 La demande, telle qu'elle est lue

- « Aucune classe en inline, tout mon style doit être fait en Mantine » : les chaînes de classes
  utilitaires (`className="flex items-center gap-2"`) disparaissent du JSX ; les composants
  d'interface viennent de Mantine ; leur apparence est réglée par le thème Mantine et par des
  modules CSS.
- « Que du `module.css`, aucun style dans du JSX » : la seule façon de styler un composant est son
  module, importé en `classes` ; `style={{…}}`, `style={variable}`, `cn()`, `cva()`, et les props de
  style de Mantine (`p`, `m`, `c`, `bg`, `style`, `styles`) sont interdits.
- « Reprendre toutes les dépendances à _shiku_ » : lu comme **la pile d'interface héritée du
  framework d'origine et de shadcn/ui** (Tailwind, Radix, `cn`/`cva`, `tw-animate-css`,
  `next-themes`), à reprendre et à remplacer ; et, pour servir l'autre lecture possible du mot,
  **Shiki** est traité aussi : ses thèmes et ses variables CSS (`--shiki-light`, `--shiki-dark`…)
  doivent suivre le schéma de couleur de Mantine (§2.8).
- « Bien lister tous les usages avant, vérifier tous les screenshots pour avoir du comparatif
  avant/après » : §1 et §7.
- « Créer des SKILL, revoir les agents, la typo » : §9 et §2.6.
- « Sur une branche à part » : §11. La règle globale interdit de créer une branche sans demande
  explicite : c'est l'utilisateur qui la demande, le plan dit seulement comment elle vit.

### 0.2 Ce que « style » recouvre ici

| Domaine         | Aujourd'hui                                                                | Après                                                                        |
| --------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| CSS             | Tailwind v4 (`@apply`, `@theme`, utilitaires), CSS global, objets inline   | Modules CSS par composant, CSS global limité à `@recit/theme` et à Mantine   |
| Tokens          | Variables HSL de `@recit/theme/tokens.css`, variables oklch shadcn du site | Un seul jeu de variables, exposé à Mantine par `cssVariablesResolver` (§2.4) |
| Typographie     | `@tailwindcss/typography` (`prose`), `Heading` en `cva`, Geist, Inter      | `TypographyStylesProvider`, `theme.headings`, mêmes polices (§2.6)           |
| Icônes          | Lucide (`lucide-react`)                                                    | Lucide, inchangé : Mantine n'embarque pas d'icônes (§2.7)                    |
| Animation       | `motion/react` (deck, navigation), `tw-animate-css`, Remotion              | `motion/react` gardé ; `Transition` de Mantine pour l'interface ; Remotion   |
| Mode de couleur | `next-themes` + classe `.dark` sur `<html>`                                | `ColorSchemeScript` + `data-mantine-color-scheme` (§2.5)                     |
| Thème public    | Section `theme:` du `recit.config` (couleurs, polices, rayon)              | Même schéma, interprété par `createTheme` (§2.4)                             |

### 0.3 La loi

Une seule règle, applicable par un script (§8) et par les agents (§9). Elle porte sur tout fichier
`.ts`/`.tsx` de `packages/`, `sites/*/src`, `sites/storybook/.storybook` et `examples/*/components`.

**Permis.**

1. `className={classes.x}` où `classes` vient du module `X.module.css` placé à côté de `X.tsx` ;
   plusieurs classes se composent **dans le module** (`composes:`), jamais par concaténation dans le
   JSX. Un seul cas de concaténation est admis : une classe d'état choisie par condition,
   `className={open ? classes.open : classes.closed}`, sans chaîne littérale.
2. Un composant Mantine avec ses props **sémantiques** du thème : `variant`, `size`, `radius`,
   `color` (un nom de couleur du thème, jamais une valeur), `justify`, `align`, `gap` sur les
   composants de mise en page (`Group`, `Stack`, `Flex`, `Grid`, `SimpleGrid`) parce qu'ils sont
   l'équivalent sémantique d'un module de mise en page. Tout le reste passe par `classNames`.
3. L'API `classNames` de Mantine branchée sur un module :
   `classNames={{ root: classes.root, label: classes.label }}`.
4. Une valeur dynamique passe par une **variable CSS** lue dans le module, posée de l'une de ces
   trois façons et d'aucune autre : (a) un attribut `data-*` pour un ensemble fini de valeurs
   (`data-columns="3"`, `data-level="2"`), (b) le `vars` d'un composant Mantine réglé **dans le
   thème** (`Button.extend({ vars })` dans le fichier de thème, pas dans le JSX), (c) pour une
   valeur continue calculée au vol (échelle d'une diapositive, position d'un indicateur), le hook
   `useCssVariable(ref, "--name", value)` fourni par `@recit/site/hooks` et par `@recit/components`,
   qui écrit la propriété sur l'élément dans un effet. Ce hook est le **seul** chemin impératif. Sa
   limite : un effet s'exécute après l'hydratation, donc une valeur nécessaire au **premier rendu**
   ne peut pas passer par lui. Les sites inline de §1 sensibles au premier rendu et leur chemin :
   l'échelle et la taille de `SlideStage` → calcul CSS (`container-type: size` sur le viewport du
   deck, `aspect-ratio` depuis `data-aspect`, ensemble fini `16:9`/`4:3`, et
   `scale: min(tan(atan2(100cqw, var(--deck-design-width))), tan(atan2(100cqh, var(--deck-design-height))))`,
   les dimensions de design étant émises par `buildThemeCss` dans le `<style>` de données ; ou, plus
   simple, une diapositive dimensionnée en `cqw`/`cqh` sans mise à l'échelle) ; le fond de
   `SlideFrame` → variable par scène émise par `buildThemeCss`
   (`[data-scene=\"…\"] { --slide-bg: … }`, lue par le module) ; les `{width, height}` du mode
   impression → les mêmes variables de design ; `--columns` du `Footer` → `data-columns` (1 à 6) ;
   la barre de progression → le prop `value` de `Progress` (Mantine écrit sa propre variable) ;
   l'indicateur de `Toc` → `useCssVariable`, parce qu'il n'existe qu'après le défilement. Les autres
   (`Header`, `Banner`, `BrowserWindow`, `LanguageIcon`, `SidebarToggle`, `NavigationWrapper`,
   `Callout`) sont classés à la lecture par STY4 : fini → `data-*`, continu → calcul CSS, après
   interaction → hook ; aucun ne reste inline.
5. Le CSS global n'existe qu'à trois endroits : les feuilles de Mantine
   (`@mantine/core/styles.layer.css`, `@mantine/spotlight/styles.layer.css`), `@recit/theme`
   (`tokens.css`, `fonts.css`, `deck.css`, `page.css`, `print.css` : les tokens et le contrat de
   classes sémantiques du HTML compilé, §5.4 ; `reset.css`, le preflight que compilait Tailwind,
   partagé par les sites, les decks et Storybook depuis STY5c), les deux feuilles de base du
   framework (`packages/site/src/app/base.css` : reset, variables de mise en page, `html`/`body`,
   barre de défilement, STY5b et STY5c ; `app/defaultTheme.css` : les jetons par défaut que saute
   `theme.noDefaultTheme`) et une feuille de base par site (`sites/*/styles.css`) réservée aux
   classes qu'une page MDX écrit elle-même.
6. Le `<style>` qui injecte `story.themeCss`
   (`packages/integrations/site-stories/src/StoryViewer.tsx:138`, `StoryDocument.tsx:49`,
   `mdx-components.tsx:101`) et le `<style id="recit-theme">` du gabarit de deck
   (`packages/cli/src/render/html-template.ts`) restent : ce sont des **données** (les variables
   d'un récit), pas du style de composant.

**Interdit.**

1. Toute chaîne littérale dans `className` (guillemets ou gabarit de chaîne), y compris une classe
   sémantique de `deck.css` : un composant qui veut la classe `slide-card` écrit dans son module
   `.card { composes: slide-card from global; }` et utilise `classes.card`.
2. `cn`, `clsx`, `twMerge`, `cva`, `class-variance-authority`, `tailwind-merge`.
3. `style={{…}}` et `style={variable}` dans le JSX, `style:` dans un `createElement`, le prop
   `style`, `styles`, `sx`, `vars` et les props de style de Mantine (`p`, `px`, `m`, `mt`, `c`,
   `bg`, `fz`, `fw`, `w`, `h`, `maw`, `pos`, `display`, `ta`, `lh`, `bd`, `opacity`…).
4. `@apply`, `@theme`, `@custom-variant`, `@source`, `@plugin`, `@utility` et tout import de
   `tailwindcss`.
5. `<style>` inline et `dangerouslySetInnerHTML` portant du CSS, hors les deux cas de données
   ci-dessus.

**Exceptions, nommées et limitées.**

| Exception                                                                                                   | Pourquoi                                                                                                                             | Où                                                                                                                                                                                                                                                                                                                          |
| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| La couche d'animation des blocs motion : un objet `style` **calculé par image** est fusionné dans l'élément | Une valeur par image (Remotion) ou par pas (Framer) ne peut pas vivre dans un module statique                                        | `packages/formats/motion-blocks/src/engine.tsx` (`StaticAnim`, `FramerAnim`, `AnimContext`), `packages/formats/motion-render/src/RemotionAnim.tsx`, `packages/formats/motion-blocks/src/player.tsx:158-226`, `src/ui/render.tsx` (valeurs échantillonnées par temps de scène). Liste d'exclusion de §8, fichier par fichier |
| Les variables de thème d'un film posées sur la racine de la scène                                           | Le thème d'un film arrive par `inputProps` (Remotion), pas par une feuille : il doit devenir des variables CSS une fois, à la racine | Un composant `ThemeVars` unique dans `motion-blocks` (§5.6), dans la même liste d'exclusion                                                                                                                                                                                                                                 |
| Les deux `<style>` de données                                                                               | Variables d'un récit, générées par `buildThemeCss`                                                                                   | §0.3 permis 6                                                                                                                                                                                                                                                                                                               |
| Ce que Mantine écrit lui-même (`style` interne de `Progress`, `Grid`, `Transition`)                         | C'est le code de Mantine, pas le nôtre ; la loi porte sur notre JSX                                                                  | —                                                                                                                                                                                                                                                                                                                           |

Tout ce qui n'est pas dans ce tableau est un module.

### 0.4 Non-objectifs

- Aucun changement de contenu, de navigation, de routes ni de comportement : la migration est
  **iso-fonctionnelle** ; une différence visuelle est acceptée consciemment dans la galerie (§7),
  jamais subie.
- Pas de refonte graphique (nouvelle palette, nouveaux espacements) : les tokens actuels sont
  reportés tels quels dans le thème Mantine. Une refonte, si on la veut, est un plan suivant.
- Pas de changement du schéma public `theme:` du `recit.config` ni des classes sémantiques que
  `@recit/core` compile dans le HTML des diapositives
  (`packages/core/src/compile/rehype-plugins.ts`, `src/parser/unified-pipeline.ts`) : les récits
  existants restent valides.
- Le rendu MP4 (`recit render`, Remotion) garde son moteur ; seule la partie statique de ses styles
  change (§5.6).

## 1. Inventaire avant

Relevés du 29/10/2026 sur `main` (commit « Claude limit » et suivants), par trois agents en lecture
seule. Les chiffres sont la référence « avant » ; §8 les fait descendre à zéro, lot par lot.

### 1.1 Le framework de site (`packages/site`)

| Mesure                                                            | Valeur                                                                                                                                                                                                                                                                                           |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Occurrences de `className`                                        | **947**, dans 110 des 171 fichiers `.tsx` hors tests                                                                                                                                                                                                                                             |
| `className={cn(…)}` / appels `cn(` / `cva(`                       | 219 / 227 / 14 (9 fichiers `ui/`, `Heading.tsx`, `Framed.tsx`)                                                                                                                                                                                                                                   |
| `style={{…}}` et `style={var}`                                    | 17 au total (PageProgress, LanguageIcon ×2, Header ×2, Banner, BrowserWindow ×2, Footer ×3 `--columns`, SidebarToggle, Toc, NavigationWrapper, Callout ×4 `--callout-color`, ToggleGroup `--gap`, Progress)                                                                                      |
| Variantes `dark:` / `prose` / classes `animate-*` et `data-state` | 62 (21 fichiers) / 18 / 36                                                                                                                                                                                                                                                                       |
| Modules CSS                                                       | **0**                                                                                                                                                                                                                                                                                            |
| Chaînes de classes hors JSX                                       | `HIGHLIGHT_CODE_BLOCK_CLASS` (`src/lib/shiki-constants.ts`), `ProseClasses` (`src/lib/components/Typography.tsx:5`)                                                                                                                                                                              |
| Composants `ui/` (export `./ui/*`, `src/lib/ui/*.tsx`)            | **54** + `util.tsx` ; **27 sans aucun importeur**                                                                                                                                                                                                                                                |
| Paquets Radix déclarés                                            | 21 (`react-slot` importé par 7 fichiers, `react-dialog` 3, `react-visually-hidden` 3, `collapsible`/`label`/`tabs` 2, 15 autres par 1 ; `react-toggle` et `react-toggle-group` déclarés, jamais importés)                                                                                        |
| Autres bibliothèques d'interface                                  | `@base-ui/react` (Toggle, ToggleGroup), `cmdk`, `vaul`, `embla-carousel-react`, `next-themes`, `@tanem/react-nprogress`, `react-hook-form`, `motion` (navigation)                                                                                                                                |
| Tailwind                                                          | `tailwindcss`, `@tailwindcss/vite`, `@tailwindcss/typography`, `tw-animate-css` ; configuré en CSS seul (`src/app/main.css`, 471 lignes : `@theme inline`, `@utility typography`, `@utility prose`, `@layer base`, `@layer utilities`, 40 `@apply`) ; `defaultTheme.css` (99 lignes, zinc oklch) |
| Thème                                                             | `src/vite/plugin-theme.ts` (394 lignes) émet `virtual:recit-theme.css` : polices Google (liste `GOOGLE_FONTS`, 25 familles), registre shadcn (`src/vite/shadcn-registry.ts`), `:root`/`.dark` depuis `theme.light`/`theme.dark` (20 clés), `--font-*`, `--shiki-*`                               |
| Mode sombre                                                       | `next-themes` `<ThemeProvider attribute="class">` (`src/lib/components/SiteRoot.tsx:75`) ; aucun script de tête maison ; `ThemeSwitch.tsx`                                                                                                                                                       |
| SSR                                                               | `src/vite/css/plugin.ts` + `collect.ts` : parcourt le graphe SSR et garde `isCSSRequest(url)` ; `src/vite/build.ts` `getEagerCssEntries` ; aucun CSS critique                                                                                                                                    |
| Polices                                                           | `@fontsource-variable/geist` et `geist-mono` dans `main.css:1-2` ; défauts `"Geist Variable"` / `"Geist Mono Variable"`                                                                                                                                                                          |

Les quinze fichiers les plus chargés : `LandingPage` 34, `ui/DropdownMenu` 31, `ui/Command` 30,
`plugins/markdown/MdxPage` 27, `ui/Select` 25, `ui/NavigationMenu` 25, `ui/Item` 25,
`ui/AlertDialog` 25, `ui/Pagination` 19, `ui/Dialog` 19, `ui/Carousel` 19, `ui/Card` 19,
`components/Header` 19, `navigation/NavigationItem` 18, `MobileTopNavigationDrawer` 18.

Par dossier (`className` littéral / `cn(` / `style`) : `lib/ui` 79 / 173 / 6, `lib/components` 166 /
41 / 11 (dont `navigation/` 41 / 15 / 2), `lib/plugins/markdown` 20 / 4 / 0,
`lib/plugins/search-pagefind` 19 / 0 / 0, `lib/errors` 3 / 1 / 0, `app` 1 / 0 / 0. Les dossiers
`cli`, `vite`, `config`, `lib/core`, `lib/hooks`, `lib/navigation` n'ont rien.

Le seul consommateur de `@recit/site/ui` hors du framework est
`packages/tools/site-kit/src/components/SourceFile.tsx` (`SyntaxHighlight`). Aucun site ni exemple
ne l'importe.

### 1.2 Les paquets Récit

| Paquet                           | Mécanisme                                                                                                                                                                                                                                                                                                                                                                                             | `className="` | `className={` | `style={{` | `style={v}` | `style: {` | CSS        |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------- | ---------- | ----------- | ---------- | ---------- |
| `components` (11 tsx, 9 stories) | Tailwind dans le JSX + classes sémantiques de `deck.css` (`slide-card`, `slide-callout-*`…)                                                                                                                                                                                                                                                                                                           | 26            | 21            | 2          | 0           | 0          | 0          |
| `theme`                          | CSS global : `tokens.css` 67, `deck.css` 951, `page.css` 17, `print.css` 51, `fonts.css` 39, `tailwind.css` 22 (pont `@theme inline`)                                                                                                                                                                                                                                                                 | –             | –             | –          | –           | –          | 6 fichiers |
| `formats/deck` (13 tsx)          | Sémantique (`deck-*`, `slide-*`, `print-*`) + Tailwind dans `PresenterView` (26/28 chaînes, palette slate codée en dur), `MobileRemoteView`, `RemoteQrCodeModal`, `AnnotationCanvas` ; inline pour échelle et tailles                                                                                                                                                                                 | 75            | 8             | 12         | 1           | 0          | 0          |
| `formats/motion-blocks` (13 tsx) | Inline seulement : `src/styles.ts` `makeStyles(theme)` → `CSSProperties` ; `src/ui/render.tsx` 1 490 lignes                                                                                                                                                                                                                                                                                           | 0             | 0             | 27         | 31          | 60         | 0          |
| `formats/motion-render` (5 tsx)  | Inline (`Film.tsx:30`) ; le bundle Remotion n'importe que `@recit/theme/fonts.css` (`src/entry.tsx:4`)                                                                                                                                                                                                                                                                                                | 0             | 0             | 1          | 0           | 1          | 0          |
| `integrations/site-stories` (8)  | `src/site-stories.css` 293 lignes (classes `recit-wrapper`, `recit-embed`, `recit-gallery`…) compilée par `scripts/build-css.mjs` avec `@tailwindcss/node` et un plugin PostCSS qui préfixe tout par `.recit-wrapper` ; `<style>{story.themeCss}</style>`                                                                                                                                             | 25            | 6             | 1          | 1           | 0          | 1          |
| `tools/site-kit` (6, privé)      | Tailwind dans le JSX, compilé par le site consommateur ; `Tutorial.tsx` utilise `prose prose-sm dark:prose-invert`                                                                                                                                                                                                                                                                                    | 24            | 1             | 0          | 0           | 0          | 0          |
| `cli`                            | `src/bundle/css.ts` : `@import "tailwindcss"` + `@recit/theme/{tokens,tailwind,page,deck,print}.css` + `@source` sur `@recit/components` et `@recit/deck` + `customCss`, puis KaTeX et polices ; `src/bundle/js.ts` : **esbuild**, `loader` pour médias seulement ; `src/render/html-template.ts` : script de démarrage `.dark`, `<style id=THEME_STYLE_ID>` ; `src/motion/client.tsx` objet `styles` | 0             | 0             | 2          | 8           | 0          | 0          |
| `core`                           | Pas d'interface ; 30 `className:` sont des nœuds hast des plugins rehype qui **émettent des classes dans le HTML compilé**                                                                                                                                                                                                                                                                            | –             | –             | –          | –           | –          | –          |

Aucun module CSS, aucun CSS-in-JS, aucun `cn`/`cva` hors du framework. `@recit/theme` déclare
`tailwindcss` en peer optionnel (`packages/theme/package.json:51-57`). Le thème est un objet
`RecitTheme` (`packages/theme/src/types.ts` : 11 tokens HSL `COLOR_TOKENS`, polices, `data`,
`motion`), deux thèmes nommés `warm-editorial` et `saas-product` ; `buildThemeCss`
(`packages/core/src/config/theme-css.ts`) produit `story.themeCss` ; le deck lit les variables, le
motion passe par `toMotionTheme()` et les `inputProps` de Remotion.

### 1.3 Sites et exemples

Tous les sites passent par `createSiteConfig` (`packages/tools/site-kit/src/config.tsx`), qui fixe
`theme.light.primary #7c3aed` et le dark ; aucun site ne déclare de polices ; tous sauf storybook
importent `@recit/site-stories/styles.css`.

| Site / exemple    | Composants `.tsx` et classes                                                                                             | CSS propre                                                  | MDX avec classes                 |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | -------------------------------- |
| `www`             | `Hero` 9, `FormatGrid` 11 (`[&_pre]:max-h-[460px]`, `text-[10px]`), `OneSource` 2                                        | –                                                           | 0                                |
| `docs`            | `StatusBadge` 1                                                                                                          | –                                                           | 0                                |
| `motion`          | `VideoDownloads` 1                                                                                                       | `styles.css` 24 lignes (`.motion-grid`, `.typography`)      | 3 (`motion-grid`)                |
| `components`      | `FormatSupport` 1 (emerald, `dark:`)                                                                                     | `src/stories.css` 6 lignes (`.metric-value`)                | 1 (`media-demo`, sans règle CSS) |
| `presentation`    | –                                                                                                                        | `styles.css` 8 lignes                                       | 0                                |
| `plans`, `vision` | –                                                                                                                        | –                                                           | 0                                |
| `storybook`       | `.storybook/preview.tsx` : 2 classes, 2 `style` (décorateur `withSlide`)                                                 | `preview.css` : `@import "tailwindcss"` + thème + `@source` | –                                |
| `site-kit`        | `Cards` 9, `ExampleHeader` 9, `Tutorial` 6, `SourceFile` 1                                                               | –                                                           | –                                |
| `deck-mdx`        | `components/index.tsx` : `Counter`, `Timeline`, 4 classes                                                                | –                                                           | –                                |
| `deck-showcase`   | `components/index.tsx` : `Metric`, 4 classes (emerald) ; réutilisé par `docs`, `components`, `presentation`, `site-i18n` | `styles.css` 10 lignes (`customCss`)                        | –                                |
| `deck-pitch`      | –                                                                                                                        | – (police de titre Georgia)                                 | –                                |

Aucun `public/*.css`, aucun `style=` dans une page MDX.

### 1.4 Typographie et mode sombre

- Sites : `@tailwindcss/typography` et `--tw-prose-*` surchargés (`main.css:57-108`), `.prose` en
  `@utility` (`main.css:110-164`), `Typography.tsx` (`prose dark:prose-invert typography`),
  `Heading.tsx` en `cva` (`text-4xl font-extrabold` … `text-md`), `MdxComponents.tsx` : h1–h6 →
  `Heading`, `a` → `AnchorLink`, `img` `rounded-lg`, `table` → `ScrollableTable`, `pre`
  `not-prose my-4`, `code` → `InlineCode`/`CodeBlock`, douze alias de `Callout` ; **pas de `p`**.
- Decks : `deck.css` `.slide h1` 3.5rem, h2 2.5rem, h3 1.5rem (L81-97, surcharges L565, L582) ;
  polices `fonts.css` (Inter, JetBrains Mono, Space Grotesk, Space Mono, Yeseva One, Josefin Sans).
- Mode sombre : la classe `.dark` sur `<html>` est lue par 20 fichiers, dont
  `packages/site/src/vite/plugin-theme.ts`, `ThemeSwitch.tsx`, `Header.tsx`, `Footer.tsx`,
  `Mermaid.tsx`, `search-inkeep/index.tsx`, `packages/formats/deck/src/hooks/useThemeMode.ts:61`,
  `Presentation.tsx`, `packages/cli/src/render/html-template.ts:34-38` (script de démarrage),
  `packages/cli/src/commands/export-pdf.ts`,
  `packages/components/src/slides/{CodeBlock,Mermaid}.tsx`, `tools/mermaid-render.ts`,
  `packages/core/src/config/{schema,validate,theme-css}.ts`, `compile/compile-story.ts:177-255`
  (`colorMode` → `defaultMode`), `sites/storybook` (`withThemeByClassName`).

### 1.5 Captures et tests

- `playwright.config.ts` : un projet `chromium` 1280×720, `maxDiffPixelRatio: 0.02`,
  `animations: "disabled"`, baselines `e2e/__screenshots__/<spec>/<nom>-<platform>.png` ; **213**
  `-linux.png` et 198 `-darwin.png` dans git ; Docker via `scripts/e2e-linux.{js,sh}`
  (`mcr.microsoft.com/playwright:v1.64.1-noble`, `linux/amd64`) ; `test:e2e:update:linux` seul à
  écrire une baseline Linux.
- Couverture : `deck-render` 91 (six exemples, clair et sombre), `site-docs` 38, `site-components`
  29, `site-presentation` 19, `motion-dev` 11, `site-motion` 11, `site-vision` 6, `site-www` 3,
  `site-embed` 3, `site-i18n` 1, `deck-features` 1 (console présentateur) ; `site-plans`,
  `site-app-bundle` et `accessibility` (axe sur 3 URL) sans capture.
- **Non couverts** : les trois pages `app*` de la doc, les 28 pages de `plans`, `/present/bridge`,
  l'index et le récit anglais de `site-i18n`, le bundle Tauri, les 9 stories de Storybook, **toutes
  les pages de site en mode sombre**, tout viewport mobile, la page 404, la boîte de recherche
  ouverte, l'en-tête et le pied de page isolés, `vision` sur darwin.
- Storybook (`sites/storybook`) : addons docs, a11y (`test: "error"`), themes ; aucun test-runner ni
  capture. Goldens motion : `packages/formats/motion-render` `goldens:check`.

### 1.6 Outillage des agents et conventions

- `.claude/skills/` : `e2e-baselines`, `recit-import`. Pas d'`agents/`.
- `CLAUDE.md` (= `AGENTS.md`) L108 « Core tech : … TailwindCSS … Radix UI », L133-137 « UI : from
  `@recit/site/ui` (shadcn/ui) and icons from `@recit/site/icons` (Lucide) », L177-197 « Bundle Size
  ». Rien sur le CSS, `className` ou le style inline.
- `biome.jsonc` : `css.parser.tailwindDirectives`, `noUnknownAtRules` ignore `plugin`, `theme`,
  `custom-variant`, `apply` ; `noImportantStyles` coupé pour `main.css` ; `noRestrictedImports`
  interdit `@recit/site` sauf `site`, `site-stories`, `site-kit` ; aucune règle sur les classes.
- `.oxfmtrc.json` : les `.css` sont formatés, un `.module.css` le sera aussi ; `lefthook.yml` :
  `biome lint --write` et `pnpm fmt` sur les fichiers indexés.
- `NOTICE.md` : `packages/site/` « site framework » d'origine, sans mention de shadcn, Radix ni
  Lucide ; polices en « Third-party assets ».
- `sites/docs` : `theme/overview.mdx:38` et `theme/custom-css.mdx:4,25` parlent de Tailwind.

## 2. Cible

### 2.1 Mantine, version et paquets

- `@mantine/core` et `@mantine/hooks` **9.7.4** (relevé le 29/10 sur le registre ; peer
  `react ^19.2.0`, satisfait). Mantine est sans Emotion depuis la v7 : feuille CSS statique
  (`@mantine/core/styles.css`), variables CSS, `MantineProvider` sans contexte de style, rendu
  serveur par `ColorSchemeScript`. STY0 vérifie la surface d'API de la v9 (noms de `classNames`,
  `cssVariablesResolver`, `Transition`, `TypographyStylesProvider`) avant d'écrire une ligne.
- `postcss-preset-mantine` 1.18.0 et `postcss-simple-vars` 7.0.1 : `rem()`, `light-dark()`, les
  mixins `light`/`dark`/`smaller-than`/`larger-than`, dans chaque bundler qui compile un module
  (§2.2).
- `@mantine/spotlight` 9.7.4 **remplace `cmdk`** pour la boîte de recherche (`Command.tsx`,
  `PagefindSearch.tsx`) : même rôle, même paquet éditeur, une dépendance en moins. `@mantine/modals`
  n'est pas pris : un `Dialog` existe déjà dans `core` (`Modal`), et le registre de modales n'est
  pas un besoin.
- Rien d'autre. La règle « une seule stack » (`CLAUDE.md`) tient : `motion`, `lucide-react`,
  `react-router`, `@tanstack/react-query`, `@mdx-js/react` restent ; Mantine n'apporte ni icônes, ni
  routeur, ni requêtes.

### 2.2 PostCSS et bundlers

Quatre compilateurs touchent du CSS ; chacun reçoit la même configuration de modules.

| Où                                                              | Compilateur                                                                | Après                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| --------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Site (`packages/site/src/vite/plugin.ts`)                       | Vite ; `tailwindcss()` en dernier plugin (`plugin.ts:32`)                  | `css.postcss` avec `postcss-preset-mantine` et `postcss-simple-vars` ; `css.modules.generateScopedName: "[name]__[local]"` (lisible, sans hachage : le framework est un seul bundle, et un HTML prérendu stable se compare d'une build à l'autre) ; plus de plugin Tailwind                                                                                                                                                                                             |
| Deck seul (`packages/cli/src/bundle/css.ts`, `js.ts`)           | `@tailwindcss/node` + `@tailwindcss/oxide` pour le CSS, esbuild pour le JS | `css.ts` concatène `@recit/theme/*.css`, `@recit/components/styles.css`, `@recit/deck/styles.css` et `customCss`, sans Tailwind ; esbuild compile nativement les `.module.css` des composants **utilisateur** (loader `local-css`, actif par défaut sur ce suffixe) et `js.ts` récupère le CSS émis dans `outputFiles` pour l'ajouter au bundle ; ces composants utilisateur gardent le motif de noms d'esbuild (ils ne sont pas prérendus, aucune capture n'en dépend) |
| `site-stories` (`scripts/build-css.mjs`)                        | `@tailwindcss/node` + PostCSS de préfixage `.recit-wrapper`                | PostCSS seul : tokens préfixés (déjà fait L17-22), `deck.css`, `@recit/components/styles.css`, `@recit/deck/styles.css`, puis son propre `site-stories.module.css` compilé en classes stables ; plus de scan `@source`                                                                                                                                                                                                                                                  |
| Storybook (`sites/storybook/.storybook/main.ts`, `preview.css`) | Vite + `@tailwindcss/vite` dans `viteFinal`                                | Vite + la même configuration PostCSS ; `preview.css` importe la feuille Mantine, `@recit/theme/*` et les feuilles des paquets                                                                                                                                                                                                                                                                                                                                           |
| Remotion (`motion-render/src/render.ts:134`)                    | webpack de Remotion                                                        | Inchangé : l'entrée importe `@recit/motion-blocks/styles.css` à côté de `@recit/theme/fonts.css`                                                                                                                                                                                                                                                                                                                                                                        |

**Modules dans un paquet publié.** `@recit/components`, `@recit/deck`, `@recit/motion-blocks` et
`@recit/site-stories` sont construits par `tsdown` et consommés par Vite (sites), esbuild (deck
seul) et webpack (Remotion). Pour que chaque consommateur voie la même chose, les modules sont
compilés **à la construction du paquet** par `postcss-modules` avec un nom de classe **déterministe
et préfixé** (`recit-<paquet>-<fichier>__<classe>`, pas de hachage), en une feuille par paquet
(`dist/styles.css`, exportée `./styles.css`) et une table `classes` importée par le code
(`import classes from "./X.module.css"` est réécrit vers la table générée). En dev, la condition
`@recit/source` fait lire les `.module.css` par Vite avec le même `generateScopedName`, donc les
mêmes noms : pas d'écart entre dev et build, pas de collision entre paquets, pas de hachage qui
bouge d'une version à l'autre. C'est ce que STY0 établit par un spike (§10), avec deux options de
repli si `tsdown` sait le faire seul (plugin rolldown CSS) ou si `postcss-modules` ne suffit pas.

**Décision (STY0b, 29/10).** Le spike retient **Lightning CSS**, ni `postcss-modules` ni le plugin
rolldown `@tsdown/css` : c'est le compilateur CSS que Vite 8 embarque déjà, donc une ligne de
catalogue à la version installée (`lightningcss: 1.33.0`) en `devDependency` du paquet qui compile
ses modules, et aucune dépendance d'exécution. `scripts/build-css-modules.js` compile chaque
`src/**/*.module.css` du paquet avec le motif `recit-<paquet>-<Fichier>__[local]`
(`cssModules.pattern`, un préfixe littéral par fichier, pas de hachage ; `dashedIdents: false`, les
`--variables` restent globales), lit ses `exports` et écrit deux choses : la table de classes **à
côté du module**, `X.module.css.ts`
(`const classes = { root: "recit-components-Kbd__root slide-kbd" } as const;`, le
`composes: … from global` joint au nom local, clés triées, déjà au format oxfmt), et la feuille du
paquet `dist/styles.css` (exportée `./styles.css`, `sideEffects: ["**/*.css"]`). Le composant
importe la **table**, pas le CSS : `import classes from "./Kbd.module.css.js"`. C'est ce qui donne
les mêmes noms en dev et en build sans rien demander au bundler du consommateur : sous
`@recit/source`, Vite lit `Kbd.tsx` puis `Kbd.module.css.ts`, un objet JS ordinaire ; tsdown,
esbuild et webpack voient la même chose dans `dist` ; aucun `generateScopedName` à aligner dans les
configurations Vite (le mécanisme imaginé ci-dessus n'existe d'ailleurs pas avec le transformateur
Lightning CSS de Vite, qui n'accepte qu'un motif chaîne, sans nom de paquet). Le typage vient de la
table elle-même (`as const`), il n'y a pas de `.d.ts` à part : `classes.inexistante` échoue `tsgo`.
Câblage : `"build": "node ../../scripts/build-css-modules.js && tsdown"`, le même préfixe pour
`typecheck` (la table doit exister avant `tsgo`), et `tsdown.config.ts` nettoie `dist/**` sauf
`dist/styles.css`. Preuves : `.recit-components-Kbd__root` dans
`storybook-static/assets/iframe-*.css` (`preview.css` importe `@recit/components/styles.css`) et
dans la feuille d'un deck qui charge la feuille par `theme.customCss` ; le deck seul l'aura quand
`packages/cli/src/bundle/css.ts` importera `@recit/components/styles.css` (STY6.1, hors des fichiers
de STY0b). À généraliser et à trancher en STY6.1 : les quatre paquets câblés de la même façon ; pas
de `rem()` ni de mixin `postcss-preset-mantine` dans les paquets de format (ils ne chargent pas
Mantine, §3.3, et Lightning CSS ne les connaît pas) ; `composes` depuis un autre fichier refusé ; la
feuille n'est pas en `@layer recit` tant que `deck.css` n'est pas en couche `theme` (§2.3, STY9) ;
les tables générées vivent dans `src/` (à committer comme un `.d.ts`, ou à ignorer et à régénérer
par `build` et `typecheck`) ; un mode `--watch` pour le dev ; le relais d'un `className` d'auteur
sur un élément nu (`[classes.root, className].join(" ")`, que la loi ne couvre pas).

**Tables générées (STY6.1).** Les tables `src/**/*.module.css.ts` sont **committées** avec leurs
modules, comme un `.d.ts` écrit à la main le serait : `build` et `typecheck` de chaque paquet les
régénèrent avant `tsdown` et `tsgo`, et `node scripts/build-css-modules.js --check --all` dans
`pnpm check` (`check:other`) refuse une table absente, périmée ou orpheline (module disparu) et une
feuille `dist/` construite qui ne correspond plus à ses modules ; une feuille pas encore construite
n'est pas une faute (`pnpm check` précède `pnpm build` en CI). Un relecteur voit les noms de classes
dans le diff, l'IDE et `tsgo` typent sans construction préalable, et `@recit/source` lit la table
sans que Vite compile le module. Les quatre paquets (`@recit/components`, `@recit/deck`,
`@recit/motion-blocks`, `@recit/site-stories`) sont câblés de la même façon ; `site-stories`, dont
`./styles.css` est la feuille d'intégration préfixée `.recit-wrapper`, écrit ses modules dans
`dist/modules.css` (`"recit": { "cssModulesSheet" }` de son `package.json`) que `build-css.mjs`
replie dans `styles.css` avec celles de `@recit/components`, `@recit/deck` et
`@recit/motion-blocks`. `--watch` recompile à la volée en dev. L'utilisateur committe les tables
avec le lot qui les change.

**SSR.** `packages/site/src/vite/css/collect.ts:43` garde toute URL pour laquelle
`isCSSRequest(url)` est vrai : un `.module.css` l'est. Le `transform(code, id)` de
`css/plugin.ts:56-60` mémorise le code transformé ; pour un module, l'ordre des hooks décide s'il
voit le CSS ou la table JS : STY0 le vérifie en dev avec un module dans `SiteRoot` et corrige le
plugin si besoin (lecture directe via `?direct`). En build, `getEagerCssEntries`
(`src/vite/build.ts`) ne change pas : Vite range les modules dans les mêmes chunks CSS.

### 2.3 Les couches

```css
@layer mantine, theme, recit, site;
```

- `mantine` : `@mantine/core/styles.layer.css` et `@mantine/spotlight/styles.layer.css` (les
  feuilles en couche, fournies par Mantine).
- `theme` : `@recit/theme/tokens.css`, `fonts.css`, `page.css`, `deck.css`, `print.css`, et le
  `virtual:recit-theme.css` du site (variables d'un site, §2.4).
- `recit` : les modules des paquets et du framework.
- `site` : la feuille d'un site (`sites/*/styles.css`) et `theme.customCss`.

Une règle d'un module gagne sur Mantine sans `!important`, et la feuille d'un site gagne sur tout.
`noImportantStyles` de biome redevient actif partout (`main.css` disparaît).

### 2.4 Thème : du `recit.config` à `createTheme`

Le schéma public ne change pas (`packages/site/src/config/validators/SiteConfig.ts:361-373`
`ThemeConfigSchema` : `light`, `dark`, `fonts`, `customCss`, `noDefaultTheme`, `registryUrl`). Ce
qui change, c'est son interprète.

| Clé du config                                                                                                | Aujourd'hui (`plugin-theme.ts`)                           | Après                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `light.*`, `dark.*` (20 clés shadcn : `background`, `primary`, `card`, `muted`, `border`, `ring`, `radius`…) | `:root {…}` et `.dark {…}` en variables shadcn            | Les mêmes variables, émises par `virtual:recit-theme.css` sous `:root` et `[data-mantine-color-scheme="dark"]`, **et** lues par un `cssVariablesResolver` qui en dérive `--mantine-color-*`, `--mantine-radius-*`, `--mantine-primary-color-*` ; `primary` devient `theme.primaryColor` avec une échelle de 10 teintes générée (`generateColors` n'est pas dans `@mantine/core` mais dans le paquet séparé `@mantine/colors-generator` : STY2 écrit une génération HSL de dix nuances autour de la teinte donnée, trente lignes testées, et ne prend ce paquet qu'en repli si les nuances ne conviennent pas) |
| `fonts.sans/serif/mono`                                                                                      | `@import` Google Fonts (liste `GOOGLE_FONTS`), `--font-*` | Même import, posé dans `theme.fontFamily`, `fontFamilyMonospace`, `headings.fontFamily` ; Geist reste la valeur par défaut via `@fontsource-variable`                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `registryUrl`                                                                                                | `fetchShadcnRegistryItem` (`shadcn-registry.ts`)          | **Retiré** : diagnostic d'erreur « `theme.registryUrl` is gone with shadcn/ui: set `theme.light` and `theme.dark` », schéma marqué `@deprecated` une version puis supprimé ; `shadcn-registry.ts` et son test supprimés                                                                                                                                                                                                                                                                                                                                                                                       |
| `customCss`                                                                                                  | Injecté dans `main.css`                                   | Émis en couche `site`, inchangé pour l'utilisateur                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `noDefaultTheme`                                                                                             | Saute `defaultTheme.css`                                  | Saute les valeurs par défaut du thème (le `createTheme` de base) ; les tokens de `@recit/theme` restent                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |

`defaultTheme.css` (zinc oklch) disparaît : les défauts du site deviennent ceux de
`@recit/theme/tokens.css` (violet) **ou** restent zinc par un jeu de valeurs dans `createTheme` ;
STY2 choisit après comparaison dans la galerie (§7), le but étant l'iso-rendu des sites. Les
variables shadcn (`--card`, `--popover`, `--sidebar-*`) que personne ne lira plus une fois les
composants migrés sont retirées à STY9.

### 2.5 Mode de couleur

- `ColorSchemeScript` dans `<head>` (via `PluginHeads`/`Meta`, `@unhead/react`) et
  `localStorageColorSchemeManager({ key: "recit-color-scheme" })` dans `MantineProvider` ;
  `defaultColorScheme` depuis le config (`"auto"` quand rien n'est dit). Plus de `next-themes`, plus
  de `ThemeProvider` dans `SiteRoot.tsx:75`, `useTheme` de `@recit/site/components` et `hooks`
  réimplémenté sur `useMantineColorScheme()` (même signature de retour : `theme`, `resolvedTheme`,
  `setTheme`) pour ne pas casser les sites.
- Le mot du glossaire est **colorMode** (`light`, `dark`, `system`) : le site l'adopte enfin
  (`site.colorMode` dans le config, défaut `system`), et `compile-story.ts:177-255` le mappe déjà
  pour les decks.
- **Le pont `.dark` pendant la migration** : vingt fichiers lisent la classe `.dark` (§1.4). Jusqu'à
  STY9, un effet dans `SiteRoot` recopie `data-mantine-color-scheme` vers la classe `.dark` de
  `<html>` ; le deck (`useThemeMode.ts:61`), `site-stories` (`.dark .recit-wrapper`), Mermaid
  (`mermaid.initialize({theme})`), Inkeep (`colorMode`), Storybook (`withThemeByClassName`) et le
  gabarit de deck (`html-template.ts:34-38`) continuent de marcher. STY9 retourne chacun vers
  l'attribut (ou vers `light-dark()` dans les modules) et retire le pont ; le deck seul et le
  gabarit CLI, qui n'ont pas Mantine, lisent `data-mantine-color-scheme` par un script de démarrage
  équivalent à celui d'aujourd'hui, pour que `deck.css` ait une seule condition de mode.
- Dans les modules, le mode s'écrit `light-dark(a, b)` ou avec les mixins `@mixin dark` : plus de
  variante `dark:`.

### 2.6 Typographie

- `TypographyStylesProvider` de Mantine remplace `@tailwindcss/typography` et `ProseClasses`
  (`Typography.tsx`, `MdxPage.tsx`, `Markdown.tsx`) : `Typography.module.css` reporte les règles de
  `@utility prose` et `@utility typography` (`main.css:57-164` : liens, code, citations, `hr`,
  listes de tâches, `h2` `mt-8 mb-4`) sur le sélecteur du provider.
- `theme.headings` : les six niveaux de `Heading.tsx` (`cva`) deviennent `theme.headings.sizes` (h1
  `2.25rem/800`, h2 `1.5rem/700`, h3 `1.25rem/600`, h4 `1.25rem`, h5 `1.125rem`, h6 `1rem`), et
  `Heading` devient `Title` + `Heading.module.css` (ancre `.anchor`, `.link`).
- `MdxComponents.tsx` réécrit élément par élément : `h1`–`h6` → `Heading` (`Title`), `a` →
  `AnchorLink` (module), `img`/vidéo → `.media` (module), `table` → `ScrollableTable` (module +
  `Table` de Mantine), `pre` → `.pre` (module), `code` → `InlineCode` (`Code` de Mantine) ou
  `CodeBlock`, les douze alias de `Callout` → `Alert` de Mantine avec `classNames`, et un `p`
  explicite (il manque aujourd'hui) pour que `TypographyStylesProvider` et le MDX s'accordent.
- Polices : Geist et Geist Mono gardées par `@fontsource-variable` ; polices Google du config
  gardées ; polices de deck inchangées (`@recit/theme/fonts.css`).
- Échelle, avant → après, à vérifier pixel près dans la galerie :

| Surface | Élément  | Avant                                   | Après (Mantine `theme.headings` ou `deck.css`)   |
| ------- | -------- | --------------------------------------- | ------------------------------------------------ |
| Site    | h1       | `text-4xl font-extrabold` (2.25rem/800) | `2.25rem/800`, `lineHeight 1.2`                  |
| Site    | h2       | `text-2xl font-bold` (1.5rem/700)       | `1.5rem/700`, marges `2rem 0 1rem`               |
| Site    | h3       | `text-xl font-semibold` (1.25rem/600)   | `1.25rem/600`                                    |
| Site    | corps    | Tailwind `prose` (1rem/1.75)            | `theme.fontSizes.md` 1rem, `lineHeights.md` 1.75 |
| Deck    | h1/h2/h3 | `deck.css` 3.5/2.5/1.5rem               | inchangé (`deck.css` reste)                      |

### 2.7 Icônes et animation

- `lucide-react` reste la seule bibliothèque d'icônes ; `@recit/site/icons` inchangé ; les
  composants Mantine reçoivent une icône Lucide par `leftSection`/`rightSection`.
- `motion/react` reste pour les transitions de diapositives (`transitions.ts`, `AnimatePresence`) et
  la navigation du site (`motionFeatures.ts`, règle `LazyMotion`/`m.*` du `CLAUDE.md`) ; les
  entrées/sorties d'interface (menus, dialogues, drawers) utilisent `Transition` et les transitions
  intégrées des composants Mantine ; `tw-animate-css` et les classes `animate-*`/`data-state`
  disparaissent.

### 2.8 Shiki, Mermaid, KaTeX

- Les variables `--shiki-light`, `--shiki-dark`, `--shiki-*-bg` (`plugin-theme.ts:323-326`) sont
  toujours émises ; la règle qui choisit la paire (`main.css` `@layer base`, Shiki rules) passe dans
  `CodeBlock.module.css` avec `light-dark(var(--shiki-light), var(--shiki-dark))` ;
  `defaultColor: false` de `shiki.ts:81` reste. Les thèmes Shiki restent configurables par
  `syntaxHighlighting.themes`.
- Mermaid : `initialize({ theme })` lit le schéma Mantine (`useMantineColorScheme`) ; les libellés
  HTML gardent la règle de `deck.css:528`.
- KaTeX : rien dans le site (aucune feuille), inchangé dans la CLI (`buildKatexCss`).

## 3. Conventions des modules

1. **Un module par composant**, `Button.module.css` à côté de `Button.tsx`, importé
   `import classes from "./Button.module.css"`. Un fichier qui exporte plusieurs composants a un
   module par composant exporté, ou se découpe.
2. **Nommage** : classes en camelCase (`.root`, `.label`, `.isOpen`), une classe racine `.root`, des
   états par classe ou par `data-*` (`&[data-active]`), des variantes par `data-variant` ; pas de
   BEM, le module isole déjà.
3. **Valeurs** : `rem()` pour toute longueur (`postcss-preset-mantine`), `light-dark()` pour les
   couleurs qui changent de mode, `var(--mantine-*)` pour tout ce que le thème définit, les tokens
   `--primary`, `--background`… de `@recit/theme` seulement dans les paquets de format (deck,
   components) qui ne chargent pas Mantine.
4. **Points de rupture** : les mixins `@mixin smaller-than $mantine-breakpoint-md` ; jamais une
   valeur en pixels écrite deux fois.
5. **Imbrication** : deux niveaux au plus ; au-delà, une classe.
6. **Typage** : `declare module "*.module.css"` de Vite (`vite/client`) dans `packages/site`, et un
   `css-modules.d.ts` généré avec la table de classes dans les paquets publiés (STY0), pour que
   `classes.inexistante` soit une erreur de typecheck.
7. **Sous-éléments Mantine** : `classNames={{ root: classes.root, section: classes.section }}` ; un
   composant qui n'a pas de sous-élément reçoit `className={classes.root}`.
8. **Dynamique** : §0.3 permis 4 ; un `data-*` quand les valeurs sont finies, `useCssVariable` sinon
   ; le module lit `var(--x, repli)`.
9. **SSR et HTML prérendu** : noms déterministes (§2.2), donc un HTML prérendu identique d'une build
   à l'autre à CSS constant ; les captures de §7 et les tests de `site-app-bundle` ne voient aucun
   bruit de hachage.
10. **Storybook** : chaque composant de `@recit/components` garde sa story ; les stories n'ont ni
    classe ni style (le décorateur `withSlide` passe en module `preview.module.css`).
11. **Tests** : un test unitaire qui lit une classe lit `classes.x`, jamais une chaîne.

## 4. Table de correspondance

### 4.1 Les 54 composants `ui/`

Export `@recit/site/ui/*` : les noms de fichiers exportés **restent** pour les importeurs
(`site-kit`, `MdxComponents`), leur implémentation change. « Mantine » = réécrit sur le composant
Mantine nommé avec `classNames` ; « module » = sans composant Mantine, une balise et un module ; «
supprimé » = sans importeur, retiré avec son export.

| Fichier `ui/`                                                                                                                                                                                                                    | Dépendance retirée                                                                                                                                                                     | Importeurs (int / `@ui`) | Après                                                                                                     |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------- |
| Button, ButtonGroup                                                                                                                                                                                                              | `react-slot`, `cva`                                                                                                                                                                    | 12 / 5 ; 0 / 1           | Mantine `Button`, `Button.Group` ; `asChild` → `component`/`renderRoot`                                   |
| Alert, DismissibleAlert                                                                                                                                                                                                          | `cva`                                                                                                                                                                                  | 1 / 3 ; 0                | Mantine `Alert` (`withCloseButton`) ; DismissibleAlert supprimé                                           |
| Callout                                                                                                                                                                                                                          | –                                                                                                                                                                                      | 2-3                      | Mantine `Alert` + `Callout.module.css` (12 types par `data-type`, `--callout-color` → variable de module) |
| Badge                                                                                                                                                                                                                            | `react-slot`, `cva`                                                                                                                                                                    | 2 / 0                    | Mantine `Badge`                                                                                           |
| Separator                                                                                                                                                                                                                        | `react-separator`                                                                                                                                                                      | 2 / 3                    | Mantine `Divider`                                                                                         |
| Dialog                                                                                                                                                                                                                           | `react-dialog`, `react-visually-hidden`                                                                                                                                                | 0 / 3                    | Mantine `Modal` (+ `VisuallyHidden`)                                                                      |
| Drawer                                                                                                                                                                                                                           | `vaul`                                                                                                                                                                                 | 2-3                      | Mantine `Drawer`                                                                                          |
| Command                                                                                                                                                                                                                          | `cmdk`, `react-dialog`                                                                                                                                                                 | 0 / 2                    | `@mantine/spotlight`                                                                                      |
| DropdownMenu                                                                                                                                                                                                                     | `react-dropdown-menu`                                                                                                                                                                  | 0 / 2                    | Mantine `Menu`                                                                                            |
| Popover, HoverCard                                                                                                                                                                                                               | `react-popover`, `react-hover-card`                                                                                                                                                    | 0 / 1 ; 0                | Mantine `Popover`, `HoverCard` ; HoverCard supprimé                                                       |
| Tooltip                                                                                                                                                                                                                          | `react-tooltip`                                                                                                                                                                        | 1 / 2                    | Mantine `Tooltip`                                                                                         |
| Kbd                                                                                                                                                                                                                              | –                                                                                                                                                                                      | 0 / 1                    | Mantine `Kbd`                                                                                             |
| Input, InputGroup, Label, Textarea, NativeSelect, Select                                                                                                                                                                         | `react-label`, `cva`, `react-select`                                                                                                                                                   | 1 ; 0/1 ; 1 ; 1 ; 0 ; 0  | Mantine `TextInput`, `Input.Wrapper`, `Textarea`, `NativeSelect` ; `Select` et `NativeSelect` supprimés   |
| Card                                                                                                                                                                                                                             | –                                                                                                                                                                                      | 1                        | Mantine `Card`                                                                                            |
| Collapsible                                                                                                                                                                                                                      | `react-collapsible`                                                                                                                                                                    | 1                        | Mantine `Collapse` (les keyframes `.CollapsibleContent` de `main.css` disparaissent)                      |
| NavigationMenu                                                                                                                                                                                                                   | `react-navigation-menu`, `cva`                                                                                                                                                         | 1                        | module (`HeaderNavigation`) + `Menu` pour les sous-menus                                                  |
| Stepper                                                                                                                                                                                                                          | –                                                                                                                                                                                      | 1                        | module (`@layer utilities .stepper` de `main.css:423-470` → `Stepper.module.css`)                         |
| Toggle, ToggleGroup                                                                                                                                                                                                              | `@base-ui/react`, `cva`                                                                                                                                                                | 1 ; 0                    | Mantine `SegmentedControl` (ToggleGroup supprimé) ou `ActionIcon`                                         |
| CodeBlock, CodeTabPanel, CodeTabs, EmbeddedCodeBlock, SyntaxHighlight                                                                                                                                                            | `react-tabs` (CodeTabs)                                                                                                                                                                | 2-3 ; 1 (site-kit)       | modules ; `CodeTabs` sur Mantine `Tabs` ; `SyntaxHighlight` reste paresseux (règle Bundle Size)           |
| Accordion, ActionButton, AlertDialog, AspectRatio, Breadcrumb, Carousel, Checkbox, Form, Frame, Item, Pagination, Progress, RadioGroup, ReactComponentDoc, ScrollArea, Secret, SidecarBox, Skeleton, Slider, Switch, Tabs, Value | `accordion`, `alert-dialog`, `aspect-ratio`, `embla-carousel-react`, `checkbox`, `react-hook-form`, `slot`+`cva`, `progress`, `radio-group`, `scroll-area`, `slider`, `switch`, `tabs` | 0                        | **supprimés** (27 avec ceux ci-dessus) ; le `Pagination` de `lib/components` n'est pas celui-ci           |
| `util.tsx` (`cn`, `focusRing`)                                                                                                                                                                                                   | `clsx`, `tailwind-merge`                                                                                                                                                               | partout                  | supprimé ; `focusRing` devient `:focus-visible` dans chaque module                                        |

### 4.2 Le framework hors `ui/`

| Fichier(s)                                                                                                                                                                                                                                                                                                                                   | `className`                                            | Après                                                                                                                                                                                                                                       |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LandingPage.tsx`                                                                                                                                                                                                                                                                                                                            | 34                                                     | `Container`, `Title`, `Text`, `SimpleGrid`, `Card` + module                                                                                                                                                                                 |
| `plugins/markdown/MdxPage.tsx`                                                                                                                                                                                                                                                                                                               | 27                                                     | `TypographyStylesProvider`, `Group`, `Menu` + module ; `ButtonGroup`, `DropdownMenu`, `Popover` déjà migrés en §4.1                                                                                                                         |
| `Header.tsx`, `HeaderNavigation.tsx`, `TopNavigation.tsx`, `MobileTopNavigation.tsx`, `MobileTopNavigationDrawer.tsx`                                                                                                                                                                                                                        | 19, 13, 6, 2, 18                                       | `AppShell.Header`, `Burger`, `Drawer` + modules ; les variables de mise en page (`--top-header-height`, `--header-height`, `--banner-height`, `main.css:166-220`) deviennent `AppShell` `header.height` et des variables du module `Layout` |
| `Footer.tsx`                                                                                                                                                                                                                                                                                                                                 | 17                                                     | module ; `--columns` → `data-columns` (1 à 6)                                                                                                                                                                                               |
| `Layout.tsx`, `Main.tsx`, `Banner.tsx`, `PageProgress.tsx`, `Spinner.tsx`                                                                                                                                                                                                                                                                    | 2, 5, 3, 1, 1                                          | `AppShell`, `Container` + modules ; `PageProgress` sur `Progress` de Mantine et son module → `@tanem/react-nprogress` retiré                                                                                                                |
| `navigation/NavigationItem.tsx`, `NavigationCategory.tsx`, `Toc.tsx`, `NavigationFrames.tsx`, `StackRows.tsx`, `NavigationWrapper.tsx`, `SidebarToggle.tsx`, `NavigationBadge.tsx`, `NavigationFilterInput.tsx`, `MobileNavigationDrawer.tsx`                                                                                                | 18, 14, 12, 9, 8, 6, 3, 4, 3, 2                        | `AppShell.Navbar`, `NavLink`, `ScrollArea`, `TextInput`, `Badge` + modules ; `Toc` `indicatorStyle` → `useCssVariable` ; `--side-nav-width` → `AppShell` `navbar.width`                                                                     |
| `BrowserWindow.tsx`, `Framed.tsx`, `Anchor.tsx`, `Pagination.tsx`, `Search.tsx`, `DeveloperHint.tsx`, `CategoryHeading.tsx`, `AiAssistantMenuItems.tsx`, `ThemeSwitch.tsx`, `Markdown.tsx`, `InlineCode.tsx`, `ScrollableTable.tsx`, `NotFoundPage.tsx`, `ErrorPage.tsx`, `Mermaid.tsx`, `LanguageIcon.tsx`, `Heading.tsx`, `Typography.tsx` | 15, 6, 6, 10, 6, 4, 3, 3, 3, 3, 3, 1, 2, 2, 2, 1, 5, 2 | modules ; `ThemeSwitch` sur `useMantineColorScheme` + `ActionIcon` ; `Heading`/`Typography` §2.6                                                                                                                                            |
| `errors/ErrorAlert.tsx`, `RouterError.tsx`                                                                                                                                                                                                                                                                                                   | 3, 2                                                   | `Alert` + module                                                                                                                                                                                                                            |
| `plugins/search-pagefind/PagefindSearch.tsx`, `ResultList.tsx`, `IndexingDialog.tsx`                                                                                                                                                                                                                                                         | 9, 8, 5                                                | `Spotlight` + modules ; l'extrait HTML (`dangerouslySetInnerHTML`) garde une classe de module pour ses `<mark>`                                                                                                                             |
| `app/main.tsx`                                                                                                                                                                                                                                                                                                                               | 1                                                      | import de `@mantine/core/styles.layer.css` et des feuilles de thème ; `main.css` réduit aux `@import` et à la déclaration `@layer`                                                                                                          |
| `lib/shiki-constants.ts` (`HIGHLIGHT_CODE_BLOCK_CLASS`)                                                                                                                                                                                                                                                                                      | –                                                      | une classe du module `CodeBlock` exportée, consommée par `plugin-shiki.ts`                                                                                                                                                                  |

### 4.3 Les paquets Récit, les sites, les exemples

| Surface                                                                            | Fichiers                                                                                                                                                                                  | Après                                                                                                                                                                                                                                                                                                                                                      |
| ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@recit/components`                                                                | `Cover`, `Points`, `Grid`, `Quote`, `Callout`, `Media`, `CodeBlock`, `CodeGroup`, `Mermaid`, `mdx/builtins.tsx` (Card, Columns, Col, Left, Right, Image, Badge, Kbd, Step), `SlideMotion` | un module par composant, `composes: slide-* from global` pour le contrat de `deck.css` ; `CodeBlock` `style={{fontSize}}` ×2 → `data-size` ; `Columns` → `data-columns` ; stories inchangées en API                                                                                                                                                        |
| `@recit/deck`                                                                      | `PresenterView` (28), `Presentation` (13), `MobileRemoteView` (12), `RemoteQrCodeModal` (11), `SlideFrame` (8), `AnnotationCanvas`, `SlideStage`, `HtmlSlide`, `DevHud`, `MotionViewer`   | modules ; **`PresenterView` passe de la palette slate codée en dur aux tokens** (différence visuelle attendue, acceptée en galerie) ; `SlideStage` échelle et taille → calcul CSS, `SlideFrame` fond → variable par scène du `<style>` de données (§0.3 permis 4) ; `HtmlSlide` `display: contents` → classe ; `.deck-print` `{width, height}` → variables |
| `@recit/theme`                                                                     | `tailwind.css` (pont)                                                                                                                                                                     | **supprimé** avec le peer `tailwindcss` ; `tokens.css`, `deck.css`, `page.css`, `print.css`, `fonts.css` restent globaux (§5.4) ; `deck.css` perd ses sélecteurs `.dark` au profit de `[data-mantine-color-scheme="dark"]` à STY9                                                                                                                          |
| `@recit/motion-blocks`                                                             | `styles.ts`, 8 blocs, `player.tsx`, `ui/render.tsx`, `uiMock.tsx`, `compareBars.tsx`…                                                                                                     | §5.6 : `makeStyles` → `blocks.module.css` + `ThemeVars` ; la couche d'animation reste en liste d'exclusion                                                                                                                                                                                                                                                 |
| `@recit/motion-render`                                                             | `Film.tsx:30`                                                                                                                                                                             | `AbsoluteFill` avec `className` de module ; fond par variable posée par `ThemeVars`                                                                                                                                                                                                                                                                        |
| `@recit/site-stories`                                                              | `mdx-components.tsx` (10), `MotionViewer` (7), `i18n.tsx` (4), `StoryViewer`, `StoryDocument`, `plugin.tsx`                                                                               | `site-stories.css` → `site-stories.module.css` compilé en classes stables, préfixage `.recit-wrapper` conservé en PostCSS ; `not-prose` disparaît (le provider de typographie s'arrête à `.recit-wrapper` par `:not()`)                                                                                                                                    |
| `@recit/site-kit`                                                                  | `Cards` (9), `ExampleHeader` (9), `Tutorial` (6), `SourceFile` (1)                                                                                                                        | modules ; `Tutorial` `prose prose-sm` → `TypographyStylesProvider` + module (`counter-increment` en CSS)                                                                                                                                                                                                                                                   |
| `sites/www`                                                                        | `Hero`, `FormatGrid`, `OneSource`                                                                                                                                                         | modules (les valeurs arbitraires `max-h-[460px]`, `text-[10px]` deviennent des règles)                                                                                                                                                                                                                                                                     |
| `sites/docs`, `motion`, `components`                                               | `StatusBadge`, `VideoDownloads`, `FormatSupport`                                                                                                                                          | `Badge`, module, module                                                                                                                                                                                                                                                                                                                                    |
| `sites/motion/styles.css`, `presentation/styles.css`, `components/src/stories.css` | classes de contenu MDX                                                                                                                                                                    | restent (couche `site`), `.typography` renommé vers le sélecteur du provider ; `media-demo` reçoit enfin une règle ou disparaît du MDX                                                                                                                                                                                                                     |
| `examples/deck-mdx`, `deck-showcase`                                               | `Counter`, `Timeline`, `Metric`                                                                                                                                                           | modules compilés par esbuild (§2.2) ; `deck-showcase/styles.css` reste (`customCss`)                                                                                                                                                                                                                                                                       |
| `sites/storybook`                                                                  | `preview.tsx`, `preview.css`, `main.ts`                                                                                                                                                   | `preview.module.css`, feuille Mantine + thème + feuilles des paquets, PostCSS ; `withThemeByDataAttribute` (`data-mantine-color-scheme`)                                                                                                                                                                                                                   |
| `packages/cli/src/motion/client.tsx`, `commands/motion-dev.ts`                     | objet `styles`, `<style>` inline de la page de dev                                                                                                                                        | `client.module.css` bundlé par esbuild ; la page de dev charge la feuille                                                                                                                                                                                                                                                                                  |

## 5. Chaque surface, le chemin technique

### 5.1 Le framework de site

1. **Socle** (STY0) : `@mantine/core`, `@mantine/hooks`, PostCSS ; `MantineProvider` dans `SiteRoot`
   à côté du `ThemeProvider` de `next-themes` (coexistence le temps de la migration : les deux
   posent leur attribut, le pont §2.5 les accorde) ; `@mantine/core/styles.layer.css` importé dans
   `main.tsx` avant `main.css` ; `@layer` déclaré en tête de `main.css` ; `css.modules` configuré ;
   un premier module (`Spinner.module.css`) prouve la chaîne dev, SSR, build, prérendu, capture.
2. **Thème** (STY2) : `plugin-theme.ts` garde l'émission des variables et des polices, perd le
   registre shadcn ; un nouveau `src/lib/theme/createRecitTheme.ts` lit `virtual:recit-config` et
   construit `createTheme` + `cssVariablesResolver` ; `ColorSchemeScript` dans la tête.
3. **Primitives** (STY3) puis **composants** (STY4) : fichier par fichier, dans l'ordre des
   importeurs (un `ui/` d'abord, ses consommateurs ensuite), chaque fichier perdant son `cn` et ses
   chaînes le jour où il reçoit son module ; `pnpm check` compte les violations qui restent (§8).
4. **Retrait** (STY5) : `tailwindcss()` retiré de `plugin.ts`, `@source` et `__pluginDirs` retirés
   de `plugin-theme.ts:349`, `main.css` réduit (reset, `@layer`, variables de mise en page qui
   restent globales), dépendances retirées, taille du bundle mesurée.

### 5.2 `site-stories`

Le préfixage `.recit-wrapper` protège aujourd'hui les tokens et `deck.css` du CSS du site. Il reste
: `build-css.mjs` garde sa passe PostCSS de préfixage sur les tokens et sur `deck.css`, y ajoute
`@recit/components/styles.css` et `@recit/deck/styles.css` (classes déjà préfixées `recit-…`, donc
sans collision, mais préfixées quand même par `.recit-wrapper` pour l'héritage des variables), et
compile `site-stories.module.css` en classes stables. Le site qui l'importe le place en couche
`recit`. `not-prose` disparaît : `TypographyStylesProvider` exclut `.recit-wrapper` par son propre
module (`:where(.recit-wrapper) *` hors portée).

### 5.3 Le deck seul et la CLI

- `css.ts` : plus de `compile()`/`Scanner` ; la feuille est la concaténation de
  `@recit/theme/{tokens,page,deck,print}.css`, `@recit/components/styles.css`,
  `@recit/deck/styles.css`, du CSS que `js.ts` a émis pour les modules des composants utilisateur,
  de `customCss`, puis KaTeX et polices comme aujourd'hui ; `@tailwindcss/node` et `oxide` quittent
  `packages/cli/package.json`.
- `js.ts` : `write: false` et `outputFiles` existent déjà ; un `.module.css` importé par un
  composant utilisateur produit un fichier `.css` dans `outputFiles` (loader `local-css` natif
  d'esbuild) ; `js.ts` le rend dans `JsBundle.css`, `build-deck.ts` le passe à `css.ts`. Les noms
  locaux suivent le même motif que Vite (option `localIdentName`… si esbuild ne l'expose pas, les
  composants utilisateur gardent le hachage d'esbuild : ils ne sont pas prérendus, aucune capture ne
  dépend du nom).
- `html-template.ts` : le script de démarrage pose `data-mantine-color-scheme` **et** la classe
  `.dark` jusqu'à STY9, puis l'attribut seul.
- `export-pdf.ts` et `?print=1` : `print.css` inchangé ; captures de `deck-features` avant/après.

### 5.4 `@recit/theme` : ce qui reste global, et pourquoi

`packages/core` compile des classes dans le HTML des diapositives (`slide-*`, `grid-cols-n`,
`deck-*`, 30 nœuds hast dans `rehype-plugins.ts` et `unified-pipeline.ts`) et les récits des
utilisateurs les écrivent dans leur Markdown. Ces classes sont un **contrat public** : elles ne
peuvent pas être hachées. `deck.css` (951 lignes) reste donc la feuille globale du format, en couche
`theme`, et les composants la rejoignent par `composes: … from global`. Ce qui change :
`tailwind.css` disparaît, les sélecteurs `.dark` deviennent `[data-mantine-color-scheme="dark"]`
(STY9), et les quelques règles de `deck.css` qui ne servent qu'à un composant React
(`.slide-progress-fill`, `.deck-hud`…) migrent dans le module du composant, une par une, avec la
capture qui le prouve. `tokens.css` reste la source des tokens HSL ; le site les lit par le
résolveur (§2.4).

### 5.5 `@recit/components` et Storybook

Chaque composant reçoit son module ; les classes sémantiques passent par `composes`. Storybook
compile les mêmes modules par Vite et montre le résultat dans la grille 16:9 du décorateur, en clair
et en sombre (`withThemeByDataAttribute`). Les 9 stories deviennent des captures (§7.2).

### 5.6 `@recit/motion-blocks`

- `makeStyles(theme)` : la partie **statique** (`frame`, `eyebrow`, `display`, `statement`,
  `statValue`, `rowLabel`, `mask`, `styles.ts:15-60`) devient `blocks.module.css`, dont les valeurs
  de thème sont lues en `var(--mb-bg)`, `var(--mb-text)`, `var(--mb-font-body)`,
  `var(--mb-type-display)`… ; les unités `cqw` et `containerType: size` passent telles quelles en
  CSS.
- `ThemeVars` : un composant racine (dans `engine.tsx` ou `themes/`) qui pose les variables `--mb-*`
  depuis le `Theme` reçu par `inputProps`. C'est la seconde exception de §0.3, dans la liste
  d'exclusion.
- La couche d'animation (`StaticAnim`, `FramerAnim`, `RemotionAnim`, `player.tsx:158-226`,
  `ui/render.tsx` `sampleSpec`) reste : elle fusionne un objet par image. Ce qui est statique dans
  `render.tsx` (57 `style: {`) est trié : constant → module, par temps → couche.
- Preuve : les goldens de `motion-render` (`pnpm -F @recit/motion-render goldens:check`) avant et
  après, plus les captures `motion-dev` de `e2e`.

### 5.7 Sites, exemples, site-kit

Composants de sites et d'exemples en modules (§4.3) ; les feuilles `styles.css` des sites restent
pour les classes écrites dans le MDX ; `site-kit` prend des modules et perd `prose`.

## 6. Dépendances

### 6.1 Retirées

| Paquet                                                                                                                                                                                                                                                                                    | Déclaré dans                                                    | Importeurs avant                 |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | -------------------------------- |
| `tailwindcss`, `@tailwindcss/vite`, `@tailwindcss/typography`, `tw-animate-css`                                                                                                                                                                                                           | `packages/site`                                                 | `plugin.ts`, `main.css`          |
| `tailwindcss`, `@tailwindcss/node`, `@tailwindcss/oxide`                                                                                                                                                                                                                                  | `packages/cli`, `packages/integrations/site-stories`, catalogue | `bundle/css.ts`, `build-css.mjs` |
| `tailwindcss` (peer optionnel)                                                                                                                                                                                                                                                            | `packages/theme`                                                | `tailwind.css`                   |
| `@tailwindcss/vite`, `tailwindcss` (catalogue)                                                                                                                                                                                                                                            | `sites/storybook`                                               | `main.ts`, `preview.css`         |
| `tailwind-merge`, `clsx`, `class-variance-authority`                                                                                                                                                                                                                                      | `packages/site`                                                 | `cn.ts`, 14 `cva`                |
| `@radix-ui/react-{slot,dialog,visually-hidden,collapsible,label,tabs,accordion,alert-dialog,aspect-ratio,checkbox,dropdown-menu,hover-card,navigation-menu,popover,progress,radio-group,scroll-area,select,separator,slider,switch,tooltip,toggle,toggle-group}` (21 + 2 jamais importés) | `packages/site`                                                 | 7, 3, 3, 2, 2, 2, puis 1 ou 0    |
| `@base-ui/react`, `cmdk`, `vaul`, `embla-carousel-react`, `next-themes`, `react-hook-form`, `@tanem/react-nprogress`                                                                                                                                                                      | `packages/site`                                                 | 2, 1, 1, 1, 4, 1, 1              |

### 6.2 Ajoutées

| Paquet                                                              | Où                                                                                                          | Rôle                                |
| ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| `@mantine/core`, `@mantine/hooks` 9.7.4                             | `packages/site` (dépendance), `sites/storybook` (dev), catalogue                                            | composants, thème, hooks            |
| `@mantine/spotlight` 9.7.4                                          | `packages/site`                                                                                             | recherche                           |
| `postcss-preset-mantine`, `postcss-simple-vars`                     | `packages/site`, `packages/cli`, `site-stories`, `sites/storybook`, et le script de build des modules (dev) | mixins, `rem()`, `light-dark()`     |
| `postcss-modules` (ou le plugin rolldown de tsdown, décidé en STY0) | racine (dev)                                                                                                | classes stables des paquets publiés |

Aucune de ces dépendances ne duplique une existante. `NOTICE.md` reçoit une ligne pour Mantine (MIT)
et perd la mention implicite de shadcn (§9.4).

### 6.3 Taille des bundles

Règles du `CLAUDE.md` « Bundle Size » inchangées : rien de lourd dans le chemin d'entrée,
`SyntaxHighlight`, `CodeTabs`, `Mermaid`, `Markdown` paresseux, `domAnimation` par `LazyMotion`.
Mesure avant/après (STY0 pose la mesure, STY5 et STY9 la refont) : taille gzip du chunk d'entrée de
`sites/docs` et de `sites/www` (`dist/assets/entry-*.js` et le CSS qui l'accompagne), notée au
journal. Attendu : le CSS du site **grossit** (feuille Mantine, ~30 ko gzip pour `styles.css`
complète, à mesurer ; l'import par composant `@mantine/core/styles/Button.css` est l'option si la
feuille entière pèse trop) et le JS d'entrée **baisse** (21 paquets Radix, `cmdk`, `vaul`,
`next-themes`, `tailwind-merge` en moins, Mantine étant tree-shakable composant par composant). Le
deck seul (`recit build`) perd le CSS utilitaire et gagne les feuilles des paquets : mesuré sur
`examples/deck-starter`.

### 6.4 Rupture pour les utilisateurs de `@recit/site`

Aujourd'hui, le Tailwind du site compile aussi les classes utilitaires écrites par l'utilisateur
d'un site : ses pages MDX, ses `mdx.components`, ses slots et ses composants, scannés par `@source`
(`plugin-theme.ts:349`, `__pluginDirs`). Une fois Tailwind parti, **ces classes ne produisent plus
rien**. C'est une rupture, annoncée dans le changelog (§10.2) et documentée (STY10.2) :

- `theme.customCss` continue de marcher (couche `site`), et c'est le chemin : une feuille CSS propre
  au site, ou un `*.module.css` à côté de chaque composant du site, compilé par Vite comme ceux du
  framework.
- `theme.registryUrl` part avec un diagnostic (§2.4).
- Les composants de `@recit/site/ui/*` gardent leurs noms et leurs props (§4.1) ; `cn` de
  `@recit/site/ui/util` disparaît.
- La page `sites/docs/pages/theme/mantine.mdx` (STY10.2) porte une section « Migrating a site from
  Tailwind utilities » avec la table Tailwind → CSS du skill (§9.1).

## 7. Captures avant et après

L'identité au pixel n'est pas le but : le but est **aucune régression non vue**, et l'acceptation
consciente de chaque différence. Le protocole en trois temps.

### 7.1 Phase 0 : étendre la couverture avant de toucher au style (STY1)

Tout ce que §1.5 liste comme non couvert reçoit une capture **avant** le premier module :

| Ajout                                                                                       | Spec                                                          | Détail                                                                                                  |
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Les 28 pages de `plans`                                                                     | `site-plans.spec.ts`                                          | pleine page, comme `vision`                                                                             |
| Les 3 pages `app*` de la doc, `/present/bridge`, l'index et le récit anglais de `site-i18n` | `site-docs`, `site-motion`, `site-i18n`                       | ajout aux listes `PAGES`                                                                                |
| **Mode sombre** de chaque page de site                                                      | un projet Playwright `chromium-dark` (`colorScheme: "dark"`)  | double le nombre de captures de site ; le nom reçoit `-dark`                                            |
| **Mobile** : un projet `mobile` (390×844, `isMobile`, `hasTouch`)                           | pages d'accueil, une page de doc, une story, le drawer ouvert | la navigation mobile et les drawers sont les composants les plus réécrits                               |
| 404, recherche ouverte (avec un résultat), en-tête seul, pied de page seul, bannière        | `site-docs`                                                   | `page.goto("/nulle-part")`, `openSearch()` puis saisie, `locator("header").screenshot()`                |
| Le bundle Tauri (`site-app-bundle`)                                                         | 3 captures (accueil, récit, 404)                              | sert de témoin « sans CDN »                                                                             |
| **Storybook** : les 9 stories, clair et sombre                                              | `storybook.spec.ts` (nouveau)                                 | `pnpm -F recit-storybook build` puis un `webServer` statique sur `storybook-static`, `iframe.html?id=…` |
| Console présentateur en sombre, `?print=1` d'un deck                                        | `deck-features`                                               | `PresenterView` change de palette : il faut la voir                                                     |
| Les goldens motion                                                                          | `motion-render` `goldens`                                     | déjà là ; `goldens:check` entre dans la vérification de STY7                                            |

Puis `pnpm test:e2e:update:linux` **une fois**, sur la branche, au commit de départ : ce jeu de
`-linux.png` est le « avant » (environ 213 + 250 fichiers). `e2e-linux.sh` n'écrit que des
`-linux.png` : rien d'autre ne bouge.

### 7.2 La galerie

`scripts/screenshot-gallery.js` (nouveau, STY1) lit `test-results/linux/**` après un
`pnpm test:e2e:linux` et produit `test-results/gallery/index.html` : une ligne par capture qui a
changé, trois colonnes **avant / après / diff** (Playwright écrit `-expected`, `-actual`, `-diff`),
le pourcentage de pixels, un filtre par spec, un bouton « accepté » qui note la décision dans
`test-results/gallery/decisions.json`. Aucune dépendance : HTML et JS inline, lus depuis le disque.
La machine de travail est distante et sans écran :
`node scripts/screenshot-gallery.js --serve <port>` sert `test-results/gallery` sur toutes les
interfaces (comme le serveur de dev du site depuis `allowedHosts: true`), et le bouton « accepté »
envoie un `POST` au même serveur, qui écrit `decisions.json` ; sans `--serve`, le script n'écrit que
les fichiers. L'humain parcourt la galerie à la fin de chaque vague ; ce qu'il accepte est régénéré
avec `pnpm test:e2e:update:linux <spec>` (la seule commande qui écrit une `-linux.png`), le reste
est un bug du lot.

### 7.3 Réglages

- `maxDiffPixelRatio` reste 0.02 pendant la migration, pour voir ; `animations: "disabled"` ;
  `mermaidReady`, `filmsReady`, `decksReady`, `heightStable` de `e2e/support.ts` inchangés ; les
  polices sont embarquées (Geist, Inter) donc stables dans le conteneur.
- Chaque **vague** de §10 se termine par `pnpm test:e2e:linux` **deux fois** (règle du skill
  `e2e-baselines`), la galerie, la liste des images acceptées dans la note de journal avec la raison
  de chaque différence. Pas de capture locale sur cette machine Linux : le skill rappelle qu'un
  poste de dev ne rend pas comme l'image officielle ; les agents d'un lot vérifient par leurs tests
  et leurs builds, les captures se font dans le conteneur, une fois l'arbre stable.
- `accessibility.spec.ts` (axe, WCAG A/AA) reste vert à chaque lot ; Mantine est accessible par
  défaut, mais un `classNames` peut casser un contraste : c'est le test qui le dit.
- Le coût d'un passage en conteneur : `scripts/e2e-linux.sh` copie l'arbre, réinstalle
  (`pnpm install --frozen-lockfile`, magasin en cache), reconstruit les 47 projets (cache nx dans le
  volume, `NX_CACHE_DIRECTORY=/cache/nx`) puis lance la suite ; APP3 l'a vu dépasser dix minutes.
  STY1.1 le **mesure** (temps total, part de l'installation, de la construction, des captures) et
  écrit le chiffre ici. Mesuré le 29/10 (STY1, 8 cœurs, caches chauds) : un passage complet dure 7 à
  8 min, dont 1 s de copie, 25 à 45 s d'installation depuis le magasin, 2 à 2 min 30 de construction
  (cache nx) et 4 min 20 à 5 min 20 pour les 444 tests des trois projets ; l'installation et la
  construction ne pèsent qu'un tiers, le saut d'installation ne vaudrait que 30 s. Budget : environ
  460 captures, sept vagues, deux passages par vague ; le script accepte déjà un filtre de specs
  (`playwright test "$@"`), donc une vague ne rejoue que les specs des surfaces qu'elle a touchées
  (ligne « Vérification » de chaque lot), et la suite complète ne tourne qu'à STY1 et à STY9. STY1.5
  essaie un saut d'installation dans `e2e-linux.sh` quand le hachage de `pnpm-lock.yaml` n'a pas
  changé (`node_modules` gardé dans le volume de cache) ; la construction est déjà couverte par le
  cache nx ; si le saut n'est pas l'affaire d'une demi-journée, il est hors périmètre et la mesure
  reste la règle.
- Les captures darwin ne sont pas régénérées pendant la migration (le skill les réserve au poste
  local) ; elles le sont une fois, après STY9, par l'utilisateur.

## 8. Application de la règle

### 8.1 `scripts/check-style-rule.js` (STY0, en avertissement ; STY9, en erreur)

Entré dans `check:other` de `package.json`. Sur tout `.ts`/`.tsx` de `packages/`, `sites/*/src`,
`sites/storybook/.storybook`, `examples/*/components`, hors tests, il signale :

1. `className=` suivi d'une chaîne ou d'un gabarit (`"…"`, `'…'`, `` `…` ``) ; toléré :
   `className={classes.x}`, `className={cond ? classes.a : classes.b}`,
   `className={props.className}` relayé tel quel.
2. `style={` et `style:` dans un `createElement`, hors fichiers de la liste d'exclusion.
3. Les props de style Mantine (`p`, `px`, `py`, `m`, `mt`, `mb`, `ml`, `mr`, `c`, `bg`, `fz`, `fw`,
   `w`, `h`, `maw`, `mah`, `miw`, `mih`, `pos`, `display`, `ta`, `lh`, `bd`, `opacity`, `styles`,
   `sx`, `vars`) sur un élément JSX.
4. Un import de `clsx`, `tailwind-merge`, `class-variance-authority`, `tailwindcss`, `cmdk`, `vaul`,
   `next-themes`, `@radix-ui/*`, `@base-ui/*`.
5. Dans un `.css` hors `.module.css` et hors `@recit/theme` et `sites/*/styles.css` : toute règle.
6. Dans un `.css` : `@apply`, `@theme`, `@custom-variant`, `@source`, `@plugin`, `@utility`,
   `dark:`.
7. Un `.tsx` qui n'importe aucun module et contient un `className` (signal : composant pas encore
   migré).

Il imprime le compte par dossier et le total ; en mode avertissement (jusqu'à STY9) il ne fait pas
échouer `pnpm check`, mais **refuse une hausse** par rapport à `scripts/style-rule-baseline.json`
(le compte de §1), ce qui gèle `main` (§11). La liste d'exclusion
`scripts/style-rule-allowlist.json` nomme fichier par fichier la couche d'animation (§0.3) et le
hook `useCssVariable`. Un test `scripts/__tests__/check-style-rule.spec.ts` couvre chaque motif.

### 8.2 Lint et format

- `biome.jsonc` : `noRestrictedImports` gagne les paquets de §8.1-4 avec un message qui renvoie au
  skill ; `css.parser.tailwindDirectives` et les `noUnknownAtRules` ignorés (`plugin`, `theme`,
  `custom-variant`, `apply`) retirés à STY9 ; `noImportantStyles` partout ; les `.module.css` sont
  lintés comme les `.css` (déjà dans `includes`).
- `.oxfmtrc.json` : rien à faire, `.module.css` est un `.css`.
- `lefthook.yml` : rien à faire.
- Typage : `pnpm typecheck` attrape `classes.inexistante` grâce aux déclarations de §3.6.

## 9. Skills, agents, documentation

### 9.1 `.claude/skills/mantine-style/SKILL.md` (nouveau, STY10)

Déclenché pour tout composant, style, CSS, Mantine, thème, mode sombre, classe, Tailwind. Plan de la
fiche :

1. Quand l'utiliser ; la loi de §0.3 recopiée (permis, interdit, exceptions).
2. Écrire un composant : le module à côté, `classes.root`, `classNames` Mantine, `data-*`,
   `useCssVariable`, `composes … from global` pour `deck.css`, `light-dark()`, `rem()`, mixins ; un
   exemple complet (`Callout.tsx` + `Callout.module.css`).
3. Table **Tailwind → CSS** des utilitaires rencontrés dans l'inventaire (`flex items-center gap-2`
   → `display:flex; align-items:center; gap: rem(8px)`, `text-muted-foreground` →
   `color: var(--mantine-color-dimmed)`, `rounded-lg` → `border-radius: var(--mantine-radius-lg)`,
   `dark:…` → `light-dark()`, `sm:` → `@mixin larger-than $mantine-breakpoint-sm`, `prose` →
   `TypographyStylesProvider`, `not-prose` → sortir du provider, `size-4` →
   `width/height: rem(16px)`, `truncate`, `sr-only` → `VisuallyHidden`…), une centaine de lignes.
4. Vérifier : `node scripts/check-style-rule.js <dossier>`, `pnpm check`, le lot de captures, la
   galerie, la note de journal.
5. Ce qu'on ne fait pas : props de style Mantine, `style`, chaîne de classes, CSS global hors
   `@recit/theme`, nouvelle dépendance.

### 9.2 Skill `e2e-baselines` (mis à jour, STY1)

Ajoute la galerie (§7.2) à l'étape 5, les projets `chromium-dark` et `mobile`, et le
`storybook.spec.ts`.

### 9.3 `CLAUDE.md` et `AGENTS.md` (STY10)

- L108 « Core tech » : `TailwindCSS` et `Radix UI` remplacés par `Mantine 9, CSS modules`.
- « UI » (L133-137) : « UI from `@recit/site/ui` (Mantine components wrapped with their modules) and
  icons from `@recit/site/icons` (Lucide) ».
- Nouvelle section « Styling » : la loi de §0.3 en anglais, en dix lignes, et le renvoi au skill.
- « Bundle Size » : Mantine ajouté à la liste des modules qui ne s'importent pas statiquement en
  entier (`import { Button } from "@mantine/core"` est tree-shakable ; `@mantine/spotlight`
  paresseux derrière `Search`).
- « Code Style » : `.module.css` importé en `classes`, type-only pour `CSSProperties` interdit hors
  liste d'exclusion.
- Un agent `.claude/agents/style-reviewer.md` (optionnel) : relit un diff pour les violations de la
  loi et les différences de la galerie non justifiées.

### 9.4 `CONTRIBUTING.md`, `NOTICE.md`, docs (STY10)

- `CONTRIBUTING.md` §4 : paragraphe « Style » (modules, loi, skill, galerie).
- `NOTICE.md` : ligne « Mantine 9 (`@mantine/*`, MIT) » dans les dépendances notables ; la ligne
  `packages/site/` précise « UI rewritten on Mantine on <date>, the shadcn/ui and Radix code of the
  fork is gone ».
- `sites/docs/pages/theme/overview.mdx:38` et `theme/custom-css.mdx:4,25` : « compiled with Tailwind
  » → « a plain stylesheet, loaded after the theme », plus une page `theme/mantine.mdx` qui montre
  comment un site écrit son `recit.config` `theme:` et sa feuille en couche `site`.
- `03-packaging.md` : la ligne des dépendances de `@recit/theme` (peer `tailwindcss` retiré).

## 10. Lots

Chaque lot nomme ses **fichiers possédés**, ce dont il **dépend**, ce qu'il **fournit** et sa
**vérification**. Tailles S, M, L. Chaque lot se termine par ses tests unitaires, les builds de ses
paquets, `node scripts/check-style-rule.js` sur ses dossiers et sa note de journal ; **chaque
vague** se termine par la séquence de §7.3 (e2e dans le conteneur deux fois, galerie, revue), lancée
par le coordinateur quand plus aucun lot n'est à mi-chemin, parce que le conteneur copie l'arbre
entier (`scripts/e2e-linux.sh`) et qu'un manifeste changé sans son lockfile le ferait échouer. Les
lignes « e2e » des vérifications ci-dessous se lisent « en fin de vague ».

### État au 29/10/2026

Exécution par vagues sur `feature/mantine`, découpage de `20-multi-agents.md` §4 (notes détaillées
dans `journal.md`) :

| Vague   | Lots                                           | État                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 0       | STY1, STY10.1                                  | Faits : 443 références Linux, galerie, skill `mantine-style`, section « Styling »                                                                                                                                                                                                                                                                                                                                                                      |
| 1       | STY0a, STY0b, STY0c, STY0d                     | Faits : Mantine 9.7.4 et PostCSS dans le framework, `Spinner.module.css`, modules compilés par Lightning CSS en table typée (`@recit/components`), module utilisateur dans un deck par esbuild, compteur à 1 217 en avertissement ; `site-docs` 106/106, galerie vide                                                                                                                                                                                  |
| 2       | STY2, STY6.1, STY7a, TST-A                     | Faits : thème `createRecitTheme` et mode de couleur Mantine (`.dark` reflété), outillage des quatre paquets publiés et feuilles branchées (CLI, `site-stories`, Storybook, Remotion, `recit dev`), blocs motion sans style inline (32 goldens identiques), 15 tests e2e comportementaux ; `site-docs` 106/106, galerie vide                                                                                                                            |
| 3       | STY3a, STY3b, STY3c, STY6.2, STY7b, TST0       | Faits : primitives `ui/` sur Mantine (13 composants réécrits, 27 supprimés, surcouches et Spotlight, code et Shiki), `@recit/components` en modules, couche d'animation triée (goldens identiques), Storybook testé par Vitest ; ordre des couches posé en tête de chaque feuille émise ; e2e 459/459, galerie vide                                                                                                                                    |
| 4       | STY4a-b, STY6.3a-b, STY8, TST1, TST2a-b        | Faits : `packages/site` hors typographie et `main.css`, `@recit/deck` (échelle en CSS), `site-kit`, `site-stories`, sites et exemples, ≈ 300 stories ; porte : e2e 447 passés, 306 références Storybook à créer, 8 pages à −1 px (`FeatureCard`, héros) corrigées par GATE4/TST4b                                                                                                                                                                      |
| 5       | STY5a-b, TST3a-b, TST4a-b, GATE4, INFRA, SBFIX | Faits : typographie (`Typography`, `Heading`, `MdxComponents`) et `main.css` démonté (`base.css`, `Layout.module.css`), icônes de langage, composants des sites dans `site-kit` (sites en Markdown seulement), ≈ 460 stories ; compteur de style 17, stories 18. Vague de corrections en cours avant la porte : STORYFIX2, LH, A11Y-SITE, A11Y-DECK, HYDRA                                                                                             |
| 5' et 6 | STY5c, STY9, STY10.2, TST5, FINALFIX           | Faits (30/10) : Tailwind, shadcn/ui, Radix et `next-themes` retirés, un seul attribut de mode, compteur de style et compteur de stories à 0 en erreur, `style-law.grit` en erreur, version plan unique, documentation finale ; porte finale : deux passages e2e complets verts consécutifs (1 480 tests, 10,6 min), 1 659 références. Reste à l'utilisateur : captures darwin (STY9.3) et décision sur les couleurs de `warm-editorial`/`saas-product` |

### STY0 — Socle et spike (M)

Dépend de : STY1 (le « avant » est capturé sur l'arbre intact). Parallèle avec : STY10.1.

Fichiers : `packages/site/package.json`, `pnpm-workspace.yaml` (catalogue), `pnpm-lock.yaml`,
`packages/site/src/vite/plugin.ts` (PostCSS, `css.modules`), `packages/site/src/vite/css/plugin.ts`
et `collect.ts` (si la collecte SSR des modules le demande), `packages/site/src/app/main.tsx`,
`main.css` (en-tête `@layer` seulement), `packages/site/src/lib/components/SiteRoot.tsx`
(`MantineProvider` à côté de `ThemeProvider`), `packages/site/src/lib/components/Spinner.tsx` +
`Spinner.module.css` (le premier module), `packages/site/src/lib/hooks/useCssVariable.ts`,
`packages/site/src/lib/theme/` (nouveau, squelette), `scripts/check-style-rule.js`,
`scripts/style-rule-baseline.json`, `scripts/style-rule-allowlist.json`,
`scripts/__tests__/check-style-rule.spec.ts`, `package.json` (`check:other`), le spike dans
`scripts/build-css-modules.mjs` (nouveau) et un paquet d'essai (`packages/components`, un seul
module `Kbd.module.css` + `dist/styles.css`), `sites/plans/pages/19-style.md` §2.2 mis à jour avec
la décision.

- [ ] STY0.1 — Mantine 9 et PostCSS installés dans `packages/site` ; `MantineProvider`,
      `ColorSchemeScript`, feuille en couche ; le site se construit et se prérend sans différence
      visuelle (galerie vide).
- [ ] STY0.2 — `Spinner.module.css` : dev, SSR dev (`virtual:ssr-css.css`), build, prérendu ; le nom
      de classe `Spinner__root` apparaît dans `dist/index.html`.
- [ ] STY0.3 — Spike des modules dans un paquet publié : `Kbd.module.css` dans `@recit/components`,
      compilé en `recit-components-Kbd__root` par `scripts/build-css-modules.mjs` (postcss-modules),
      `dist/styles.css` exporté, importé par `sites/storybook` et par le deck seul
      (`recit build examples/deck-starter/slides.md`) ; même nom en dev par `@recit/source`. La
      décision (postcss-modules, plugin rolldown ou autre) écrite dans §2.2 de ce plan.
- [ ] STY0.4 — esbuild : un `.module.css` dans `examples/deck-mdx/components` produit son CSS dans
      `outputFiles` et arrive dans la feuille du deck ; test dans `packages/cli/src/__tests__`.
- [ ] STY0.5 — `check-style-rule.js` en avertissement avec la baseline de §1 ; test ; `pnpm check`
      vert.
- [ ] STY0.6 — Mesure des bundles (§6.3) notée au journal.

Fournit : la chaîne de compilation, `useCssVariable`, le squelette du thème, le compteur.

Vérification :

```bash
pnpm exec nx run docs:build && grep -c "Spinner__root" sites/docs/dist/index.html
pnpm exec vitest run packages/site scripts/__tests__/check-style-rule.spec.ts packages/cli
pnpm check && pnpm test:e2e:linux e2e/site-docs.spec.ts
```

### STY1 — Captures avant (M)

Dépend de : rien. Parallèle avec : rien, seul en vague 0 : le conteneur e2e copie l'arbre entier, un
autre lot en cours fausserait le « avant ».

Fichiers : `playwright.config.ts` (projets `chromium-dark`, `mobile`, `webServer` Storybook),
`e2e/site-plans.spec.ts`, `e2e/site-docs.spec.ts`, `e2e/site-motion.spec.ts`,
`e2e/site-i18n.spec.ts`, `e2e/site-app-bundle.spec.ts`, `e2e/deck-features.spec.ts`,
`e2e/storybook.spec.ts` (nouveau), `e2e/support.ts` (helpers `openSearch` existant, `darkMode`),
`e2e/ports.ts` (port Storybook), `scripts/screenshot-gallery.js` (nouveau),
`scripts/__tests__/screenshot-gallery.spec.ts`, `.claude/skills/e2e-baselines/SKILL.md`,
`e2e/__screenshots__/**/*-linux.png` (générées, jamais éditées à la main).

- [ ] STY1.1 — Les ajouts de §7.1, projet par projet ; `pnpm test:e2e:linux` deux fois vert avant
      toute génération.
- [ ] STY1.2 — `pnpm test:e2e:update:linux` sur tout : le « avant ». Compte des fichiers noté.
- [ ] STY1.3 — La galerie (§7.2) sur un diff fabriqué (une capture volontairement modifiée) ;
      `--serve <port>` sur toutes les interfaces et le `POST` de `decisions.json` ; test du script :
      serveur sur un port libre, `GET` de la galerie, `POST` d'une décision, lecture de
      `decisions.json`.
- [ ] STY1.4 — Skill `e2e-baselines` mis à jour.
- [ ] STY1.5 — Mesure du passage en conteneur (§7.3) écrite dans ce plan et au journal ; essai du
      saut d'installation de `e2e-linux.sh` (fichier ajouté à ce lot : `scripts/e2e-linux.sh`),
      gardé s'il est sûr et court, sinon noté hors périmètre.

Fournit : le jeu « avant », la galerie.

Vérification : `pnpm test:e2e:linux` deux fois ; `node scripts/screenshot-gallery.js` ouvre une
galerie vide ; `pnpm exec vitest run scripts/__tests__`.

### STY2 — Thème et mode de couleur (M)

Dépend de : STY0. Parallèle avec : STY6.1.

Fichiers : `packages/site/src/vite/plugin-theme.ts` (+ test),
`packages/site/src/vite/shadcn-registry.ts` (supprimé) et son test,
`packages/site/src/lib/theme/createRecitTheme.ts` (+ test),
`packages/site/src/lib/theme/colorScale.ts` (+ test),
`packages/site/src/config/validators/SiteConfig.ts` (`registryUrl` déprécié, `colorMode`),
`packages/site/src/lib/components/SiteRoot.tsx`, `Meta.tsx` ou `PluginHeads.tsx`
(`ColorSchemeScript`), `ThemeSwitch.tsx` + module, `packages/site/src/lib/hooks/useTheme.ts`
(nouveau : remplace la réexportation de `next-themes`), `packages/site/src/lib/components/index.ts`
et `hooks/index.ts` (exports), `packages/site/src/app/defaultTheme.css`, le pont `.dark` dans
`SiteRoot`, `packages/site/src/lib/components/Mermaid.tsx`, `plugins/search-inkeep/index.tsx`,
`e2e/support.ts` (le helper `darkMode` étendu à la clé Mantine, question 14).

- [ ] STY2.1 — `createRecitTheme(config)` : couleurs, échelle de 10 nuances, rayon, polices,
      `headings`, `cssVariablesResolver` ; test d'instantané du thème pour `sites/docs`.
- [ ] STY2.2 — `ColorSchemeScript`, `localStorageColorSchemeManager`, `colorMode` du config ; le
      pont `.dark` ; `next-themes` ne fait plus rien mais reste installé jusqu'à STY9.
- [ ] STY2.3 — `registryUrl` retiré avec diagnostic ; `GOOGLE_FONTS` conservé.
- [ ] STY2.4 — `ThemeSwitch` sur Mantine ; `useTheme` compatible.
- [ ] STY2.5 — Galerie : les sites doivent être **identiques** (le thème est posé mais rien ne le
      lit encore, hors `ThemeSwitch`).

Fournit : le thème, le hook de mode, `[data-mantine-color-scheme]`.

Vérification : `pnpm exec vitest run packages/site` ; `pnpm check` ; e2e deux fois, galerie vide
sauf `ThemeSwitch`.

### STY3 — Primitives `ui/` (L, en trois agents)

Dépend de : STY2. Parallèle entre eux et avec STY6.2, STY7.

Fichiers, par agent, dans `packages/site/src/lib/ui/` (chaque `.tsx` et son `.module.css`) :

- **STY3a, actions et texte** : `Button`, `ButtonGroup`, `Badge`, `Kbd`, `Toggle`, `Input`,
  `InputGroup`, `Label`, `Textarea`, `Separator`, `Card`, `Alert`, `Callout`, et la suppression des
  27 fichiers sans importeur (§4.1) avec leurs exports dans `packages/site/package.json`.
- **STY3b, surcouches** : `Dialog`, `Drawer`, `Popover`, `Tooltip`, `DropdownMenu`, `Command` (→
  Spotlight), `Collapsible`, `NavigationMenu`.
- **STY3c, code** : `CodeBlock` (+ test), `CodeTabs`, `CodeTabPanel`, `EmbeddedCodeBlock`,
  `SyntaxHighlight`, `Stepper` (+ test), `packages/site/src/lib/shiki-constants.ts`,
  `packages/site/src/vite/plugin-shiki.ts` (la classe du module).

Chaque agent possède aussi `util.tsx` **en lecture** : il est supprimé par STY3a à la fin, quand
`grep -r "ui/util" packages/site/src` ne rend plus rien. `lib/util/cn.ts`, lui, part avec STY5.

- [ ] STY3.1 — Chaque composant réécrit selon §4.1, même API de props qu'avant pour ses importeurs
      (ou l'importeur adapté dans le même lot s'il est dans `lib/components`, auquel cas le fichier
      est **demandé au coordinateur**, STY4 le possède).
- [ ] STY3.2 — Les tests existants (`CodeBlock.test.tsx`, `Stepper.test.tsx`) verts, lisant
      `classes.*`.
- [ ] STY3.3 — Galerie : différences attendues listées par composant (focus ring, ombres de Mantine)
      et acceptées ou corrigées pour revenir à l'avant.

Vérification : `pnpm exec vitest run packages/site` ;
`node scripts/check-style-rule.js packages/site/src/lib/ui` à zéro pour les fichiers de l'agent ;
e2e deux fois ; galerie.

### STY4 — Composants du framework (L, en deux agents)

Dépend de : STY3. Parallèle entre eux et avec STY6.3, STY8.

- **STY4a, pages et mise en page** : `LandingPage`, `Layout`, `Main`, `Header`, `HeaderNavigation`,
  `TopNavigation`, `MobileTopNavigation`, `MobileTopNavigationDrawer`, `Footer`, `Banner`,
  `PageProgress`, `BrowserWindow`, `Framed`, `Anchor`, `Pagination`, `Search`, `DeveloperHint`,
  `CategoryHeading`, `AiAssistantMenuItems`, `NotFoundPage`, `ErrorPage`, `LanguageIcon`,
  `ScrollableTable`, `InlineCode`, `Markdown`, `Mermaid` (module), `errors/*`, `app/main.tsx` ;
  `Layout.module.css` est créé mais lit encore les variables globales de `main.css:166-220`, que
  STY5 déplace. `Heading`, `Typography` et `MdxComponents` sont à STY5.
- **STY4b, navigation et plugins** : `lib/components/navigation/*` (12 fichiers),
  `plugins/markdown/MdxPage.tsx`, `plugins/search-pagefind/*`, `plugins/search-inkeep/index.tsx`
  (attribut de mode), `lib/components/Slot.tsx` si besoin.

- [ ] STY4.1 — Chaque fichier selon §4.2 ; les valeurs dynamiques selon §0.3 permis 4 : `--columns`
      en `data-columns`, `Toc` par `useCssVariable` (après interaction), le reste classé fini ou
      continu à la lecture ; rien d'inline, et jamais le hook pour une valeur du premier rendu.
- [ ] STY4.2 — Captures des pages de doc **identiques ou acceptées ligne à ligne** (c'est le lot le
      plus visible) ; `Typography` et `Heading` gardent leur API, leur intérieur change en STY5.
- [ ] STY4.3 — `node scripts/check-style-rule.js packages/site` à zéro hors `ui/` (STY3) et hors
      `Typography`, `Heading`, `MdxComponents`, `main.css` (STY5).

Vérification : comme STY3, plus `pnpm exec nx run-many -t build -p docs www presentation` et
`accessibility.spec.ts`.

### STY5 — Typographie, `main.css` et retrait de Tailwind (L)

Dépend de : STY3, STY4. Parallèle avec : rien (il touche le manifeste et `main.css`, que tout le
monde lit).

Fichiers : `packages/site/src/lib/components/Typography.tsx` + `Typography.module.css`,
`Heading.tsx` + `Heading.module.css`, `packages/site/src/lib/util/MdxComponents.tsx`,
`packages/site/src/lib/components/Layout.module.css` (créé par STY4a, reçoit les variables de mise
en page), `packages/site/src/app/main.css`, `packages/site/src/lib/util/cn.ts` (supprimé),
`packages/site/package.json`, `pnpm-lock.yaml`, `packages/site/src/vite/plugin.ts`,
`plugin-theme.ts` (`@source`, `__pluginDirs`), `biome.jsonc` (directives Tailwind),
`packages/site/src/lib/core/plugins.ts` (`__pluginDirs` reste pour les routes, perd son rôle CSS ;
documenté).

- [ ] STY5.0 — Typographie (§2.6) : `TypographyStylesProvider` partout où `ProseClasses` l'était,
      `Typography.module.css` reporte règle à règle `main.css:57-164`, `Heading` sur `Title` et
      `theme.headings`, `MdxComponents` réécrit élément par élément avec un `p` ; captures des pages
      de doc en clair et en sombre acceptées ligne à ligne.
- [ ] STY5.1 — `main.css` démonté : les variables de mise en page (`main.css:166-220`) dans
      `Layout.module.css`, le reset et `html`/`body`/scrollbar/`.wavy-line` dans `app/base.css`
      (couche `theme`, le seul CSS global du framework), les règles Shiki déjà parties en STY3c ; à
      la fin, `main.css` n'a plus que les `@import` et la déclaration `@layer`.
- [x] STY5.2 — `tailwindcss()` retiré de `plugin.ts` ; le site se construit ; aucune classe
      utilitaire orpheline (galerie : rien ne doit bouger, puisque tout est en module) ; `cn.ts`
      supprimé.
- [x] STY5.3 — Dépendances de §6.1 lignes 1 et 5 à 7 retirées, sauf `next-themes` (STY9).
- [x] STY5.4 — Mesure des bundles (§6.3) comparée à STY0.6.

Vérification : `pnpm exec nx run-many -t build typecheck -p @recit/site docs www` ; `pnpm check` ;
e2e deux fois ; galerie vide.

### STY6 — Paquets Récit (L, en trois temps)

- **STY6.1, outillage des paquets publiés (M)** — dépend de STY0 ; parallèle avec STY2. Fichiers :
  `scripts/build-css-modules.mjs` (sortie du spike, généralisé), `packages/components`,
  `packages/formats/deck`, `packages/formats/motion-blocks`, `packages/integrations/site-stories`
  (`package.json` : script `build`, export `./styles.css`, `tsdown.config.ts`),
  `packages/cli/src/bundle/css.ts`, `js.ts`, `packages/cli/src/render/build-deck.ts`,
  `packages/cli/package.json`, `packages/integrations/site-stories/scripts/build-css.mjs`,
  `packages/theme/package.json` (peer `tailwindcss`), `packages/theme/src/tailwind.css` (supprimé),
  `sites/storybook/.storybook/main.ts` et `preview.css`, `preview.tsx` + `preview.module.css`.
  Fournit : `@recit/<paquet>/styles.css`, la CLI sans Tailwind, Storybook sans Tailwind.
  Vérification : `pnpm build` ; `recit build` de chaque `examples/deck-*` ;
  `pnpm -F recit-storybook build` ; captures `deck-render` identiques.
- **STY6.2, `@recit/components` (M)** — dépend de STY6.1 ; parallèle avec STY3, STY7. Fichiers :
  `packages/components/src/**` (11 composants, 9 stories, modules). Vérification : tests du paquet,
  `storybook.spec.ts`, `deck-render` (les composants sont dans chaque deck).
- **STY6.3, `@recit/deck` et `site-kit` (M)** — dépend de STY6.2 ; parallèle avec STY4, STY8.
  Fichiers : `packages/formats/deck/src/**` (13 composants, modules),
  `packages/tools/site-kit/src/components/*` (+ modules),
  `packages/integrations/site-stories/src/**` (`site-stories.module.css`, `mdx-components`,
  `MotionViewer`, `i18n`), `packages/cli/src/motion/client.tsx` + module,
  `packages/cli/src/commands/motion-dev.ts`. Les valeurs du premier rendu du deck suivent §0.3
  permis 4 : `data-aspect`, dimensions de design et fond par scène émis par `buildThemeCss`
  (`packages/core/src/config/theme-css.ts`, ajouté aux fichiers de ce lot), échelle calculée en CSS
  ; aucun `useCssVariable` dans le deck. Vérification : `deck-features` (console présentateur,
  différence attendue et acceptée), `deck-render`, `site-components`, `site-embed`, `motion-dev`.

### STY7 — Motion blocks (M)

Dépend de : STY6.1. Parallèle avec : STY3, STY6.2.

Fichiers : `packages/formats/motion-blocks/src/**` (`styles.ts` → `blocks.module.css`, `ThemeVars`,
les 8 blocs, `player.tsx`, `ui/render.tsx`, `uiMock.tsx`, `compareBars.tsx`),
`packages/formats/motion-render/src/{Film,entry,RemotionAnim}.tsx`,
`scripts/style-rule-allowlist.json` (les fichiers de la couche d'animation, et rien d'autre).

- [ ] STY7.1 — `blocks.module.css` et `ThemeVars` ; les blocs n'ont plus que `className` et la
      couche `A`.
- [ ] STY7.2 — `render.tsx` trié : constant → module, par temps → couche ; le compte de `style:`
      hors couche tombe à zéro.
- [ ] STY7.3 — `goldens:check` vert **sans régénération** (le rendu est identique), sinon chaque
      golden qui change est justifié et régénéré par `goldens`.

Vérification : `pnpm exec vitest run packages/formats/motion-blocks packages/formats/motion-render`
; `pnpm -F @recit/motion-render goldens:check` ; `motion-dev` et `site-motion` en e2e.

### STY8 — Sites et exemples (S)

Dépend de : STY2 (le thème) et STY6.1 (modules dans un deck). Parallèle avec : STY4, STY6.3.

Fichiers : `sites/www/src/*`, `sites/docs/src/StatusBadge.tsx`,
`sites/motion/src/VideoDownloads.tsx`, `sites/components/src/FormatSupport.tsx`, leurs modules,
`sites/{motion,presentation}/styles.css`, `sites/components/src/stories.css`,
`sites/components/stories/media.mdx` (`media-demo`), `examples/deck-mdx/components/*`,
`examples/deck-showcase/components/*`, `packages/tools/site-kit/src/config.tsx` (rien à changer,
vérifié).

Vérification : `site-www`, `site-docs`, `site-motion`, `site-components`, `deck-render` ; galerie.

### STY9 — Bascule finale (M)

Dépend de : STY5, STY6, STY7, STY8. Parallèle avec : STY10.2.

Fichiers : `packages/site/src/lib/components/SiteRoot.tsx` (pont `.dark` retiré, `next-themes`
retiré), `packages/site/package.json`, `pnpm-lock.yaml`,
`packages/theme/src/{tokens,deck,page,print}.css` (`.dark` → `[data-mantine-color-scheme="dark"]`),
`packages/formats/deck/src/hooks/useThemeMode.ts`,
`packages/formats/deck/src/deck/Presentation.tsx`, `packages/cli/src/render/html-template.ts`,
`packages/cli/src/commands/export-pdf.ts`, `packages/components/src/slides/{CodeBlock,Mermaid}.tsx`,
`tools/mermaid-render.ts`, `packages/core/src/config/theme-css.ts` (le bloc sombre),
`packages/integrations/site-stories/scripts/build-css.mjs` (`.dark .recit-wrapper`),
`sites/storybook/.storybook/preview.tsx` (`withThemeByDataAttribute`), `scripts/check-style-rule.js`
(mode erreur, baseline supprimée), `biome.jsonc`, `.nx/version-plans/version-plan-style.md`.

- [x] STY9.1 — Un seul attribut de mode partout ; `next-themes` parti.
- [x] STY9.2 — Le compteur à zéro, en erreur dans `pnpm check`.
- [ ] STY9.3 — Mesure finale des bundles ; `pnpm verify` ; e2e complet deux fois ; galerie passée en
      revue avec l'utilisateur ; captures darwin régénérées par lui.
- [x] STY9.4 — Version plan (§10.2).

Vérification : `pnpm verify && pnpm verify:e2e` (local) et `pnpm test:e2e:linux` deux fois.

### STY10 — Skills, agents, documentation (S, en deux temps)

- **STY10.1 (S)** — dépend de STY1 ; parallèle avec STY0 (le skill décrit la loi et la chaîne telles
  que ce plan les fixe). Fichiers : `.claude/skills/mantine-style/SKILL.md`, `CLAUDE.md` et
  `AGENTS.md` (section « Styling » en avertissement « in progress on branch style/mantine »). Pour
  que les agents des vagues suivantes écrivent déjà selon la loi.
- **STY10.2 (S)** — dépend de STY5 ; parallèle avec STY9 (aucun fichier commun, la forme finale est
  celle de ce plan). Fichiers : `CLAUDE.md`, `AGENTS.md` (versions finales), `CONTRIBUTING.md`,
  `NOTICE.md`, `sites/docs/pages/theme/{overview,custom-css,mantine}.mdx` (`mantine.mdx` avec sa
  section « Migrating a site from Tailwind utilities », §6.4), `sites/docs/sidebar.ts`,
  `sites/plans/pages/03-packaging.md`, `.claude/agents/style-reviewer.md` (optionnel).

- [x] STY10.2 — Fait le 30/10 : `AGENTS.md` final (« UI », « Styling », « Tests »),
      `CONTRIBUTING.md`, `NOTICE.md`, `theme/mantine.mdx` et les pages qui citaient Tailwind,
      `03-packaging.md` ; pas d'agent `style-reviewer` (journal, note STY10.2).

Vérification : `pnpm check` (frontmatter, liens) ; `pnpm exec nx run docs:build`.

### 10.1 Les vagues

| Vague   | Lots en parallèle                    | Condition d'entrée                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 0       | STY1                                 | la branche existe (§11)                                                                                                                                                                                                                                                                                                                                                                                                                                |
| 1       | STY0, STY10.1                        | le « avant » de STY1 généré et relu                                                                                                                                                                                                                                                                                                                                                                                                                    |
| 2       | STY2, STY6.1                         | STY0 vérifié                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| 3       | STY3a, STY3b, STY3c, STY6.2, STY7    | STY2 et STY6.1 vérifiés                                                                                                                                                                                                                                                                                                                                                                                                                                |
| 4       | STY4a, STY4b, STY6.3, STY8           | STY3 vérifié (les trois), STY6.2 vérifié                                                                                                                                                                                                                                                                                                                                                                                                               |
| 5       | STY5                                 | STY4 vérifié (les deux)                                                                                                                                                                                                                                                                                                                                                                                                                                |
| 5' et 6 | STY5c, STY9, STY10.2, TST5, FINALFIX | Faits (30/10) : Tailwind, shadcn/ui, Radix et `next-themes` retirés, un seul attribut de mode, compteur de style et compteur de stories à 0 en erreur, `style-law.grit` en erreur, version plan unique, documentation finale ; porte finale : deux passages e2e complets verts consécutifs (1 480 tests, 10,6 min), 1 659 références. Reste à l'utilisateur : captures darwin (STY9.3) et décision sur les couleurs de `warm-editorial`/`saas-product` |

Règles d'exécution, les mêmes que `18-mobile-desktop.md` §6 : relire avant d'écrire, un lot = un
agent = ses fichiers, un fichier manquant se demande au coordinateur, jamais de git, jamais de
formatage global, une note `### STYn — <titre> — <date>` en fin de `journal.md` avec les commandes,
les comptes du compteur, la liste des captures acceptées et pourquoi. **Chaque vague se termine par
un passage e2e dans le conteneur, lancé par le coordinateur sur un arbre stable, puis par une revue
de galerie par l'utilisateur** avant la suivante : c'est la seule porte qui demande un humain.

### 10.2 Version plan

Tous les paquets publiés changent de contrat CSS : `@recit/theme` perd `tailwind.css` et son peer,
`@recit/components`, `@recit/deck`, `@recit/motion-blocks`, `@recit/site-stories` gagnent
`./styles.css`, `@recit/cli` ne compile plus Tailwind. En 0.x (`CONTRIBUTING.md` §5), c'est un
`prerelease` : la migration n'ouvre pas de phase de la feuille de route (`06-roadmap.md`, P5 à P12),
elle traverse les phases en cours. Un seul plan, `.nx/version-plans/version-plan-style.md`, écrit
par STY9, `prerelease` pour `@recit/core` (`theme-css.ts` change le bloc sombre), `@recit/theme`,
`@recit/components`, `@recit/deck`, `@recit/motion-blocks`, `@recit/motion-render`,
`@recit/site-stories`, `@recit/cli`, avec la ligne de changelog « Styling: Mantine and CSS modules
replace Tailwind, shadcn/ui and Radix; `@recit/theme/tailwind.css` is gone, each package ships
`styles.css`; a site's own MDX, `mdx.components`, slots and components can no longer use Tailwind
utility classes: write a stylesheet (`theme.customCss`) or a CSS module; `theme.registryUrl` is
removed ». Si, au moment de fusionner, l'utilisateur veut en faire le premier jalon d'une phase,
`preminor` ; question ouverte Q6.

### 10.3 Effort

| Vague   | Lots                                 | Agents                                                                                                                                                                                                                                                                                                                                                                                                                                                 | Taille cumulée |
| ------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- |
| 0       | STY1                                 | 1                                                                                                                                                                                                                                                                                                                                                                                                                                                      | M              |
| 1       | STY0, STY10.1                        | 2                                                                                                                                                                                                                                                                                                                                                                                                                                                      | M + S          |
| 2       | STY2, STY6.1                         | 2                                                                                                                                                                                                                                                                                                                                                                                                                                                      | M + M          |
| 3       | STY3a-c, STY6.2, STY7                | 5                                                                                                                                                                                                                                                                                                                                                                                                                                                      | L + M + M      |
| 4       | STY4a-b, STY6.3, STY8                | 4                                                                                                                                                                                                                                                                                                                                                                                                                                                      | L + M + S      |
| 5       | STY5                                 | 1                                                                                                                                                                                                                                                                                                                                                                                                                                                      | L              |
| 5' et 6 | STY5c, STY9, STY10.2, TST5, FINALFIX | Faits (30/10) : Tailwind, shadcn/ui, Radix et `next-themes` retirés, un seul attribut de mode, compteur de style et compteur de stories à 0 en erreur, `style-law.grit` en erreur, version plan unique, documentation finale ; porte finale : deux passages e2e complets verts consécutifs (1 480 tests, 10,6 min), 1 659 références. Reste à l'utilisateur : captures darwin (STY9.3) et décision sur les couleurs de `warm-editorial`/`saas-product` |

Dix-sept lots d'agent, sept vagues, sept revues de galerie (la première, après STY1, ne fait que
relire le « avant »). Environ 950 `className` du framework, 160 des paquets, 60 des sites et
exemples, 120 `style` des blocs motion à trier, 54 composants `ui/` à réécrire ou supprimer, 55 + 12
composants du framework, 11 + 13 des paquets, 4 de `site-kit`, 7 de sites et d'exemples. Une vague
par journée de travail d'agents est l'ordre de grandeur, les revues humaines en plus.

## 11. La branche

- **Nom** : `feature/mantine`, créée par l'utilisateur depuis `main` le 29/10/2026 ; les agents n'en
  créent aucune (règle globale).
- **Gel de `main`** : STY0 est le seul lot dont une partie peut aller sur `main` en avance, si
  l'utilisateur le veut : `scripts/check-style-rule.js` en mode avertissement avec sa baseline, qui
  refuse toute hausse ; ainsi aucun nouveau `className` utilitaire n'entre dans `main` pendant la
  migration. Le reste du plan ne touche pas `main`.
- **Rebase** : à la fin de chaque vague, l'utilisateur rebase `style/mantine` sur `main` (les agents
  ne le font pas) ; les conflits attendus sont dans `journal.md` (ajouts des deux côtés, triviaux)
  et dans les fichiers que le plan 18 touche encore (`packages/tools/app/**`, `.github/`,
  `sites/docs/pages/documentation/configuration/app*.mdx`) : aucun d'eux n'a de style, la
  cohabitation est sans conflit par construction. Une nouvelle page de doc arrivée sur `main` reçoit
  sa capture au rebase suivant (STY1 la liste).
- **Fusion** : une seule fois, après STY9 et la revue finale, avec les `-linux.png` acceptées ; la
  CI (`build.yaml`, `test:e2e`) doit être verte sur la branche avant. Pas de fusion partielle : un
  site à moitié migré a deux systèmes de style, et c'est ce que le plan interdit.
- **Tauri (plan 18)** : APP9 et APP10.2 touchent la coquille Rust et `window-open.js` ; ni l'un ni
  l'autre n'a de CSS. Le bundle Tauri (`site-app-bundle.spec.ts`) suit `dist/` du site, donc il
  change de style avec le site : ses trois captures sont dans STY1.

- **Abandon** : la branche peut être abandonnée à n'importe quelle vague sans effet sur `main`, qui
  n'a rien reçu d'elle. Ce qui vaut d'être repris seul dans ce cas, chacun dans un commit propre sur
  `main` à la demande de l'utilisateur : la couverture de captures et la galerie de STY1 (utiles
  quel que soit le style), le compteur `check-style-rule.js` en avertissement avec sa baseline, et
  le skill `mantine-style` réduit à sa table Tailwind → CSS si Mantine n'est pas retenu. Une branche
  à moitié migrée n'est **jamais** fusionnée : deux systèmes de style dans un site, c'est ce que ce
  plan interdit.

## 12. Risques

| Risque                                                                                       | Parade                                                                                                                                                         |
| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Les modules d'un paquet publié : trois bundlers, trois comportements                         | STY0.3 et STY0.4 tranchent avant tout le reste par la compilation à la construction (noms stables, une feuille) ; repli : modules copiés en source dans `dist` |
| La collecte SSR en dev ne voit pas le CSS d'un module (`css/plugin.ts` hook `transform`)     | STY0.2 le prouve ou corrige ; en build, Vite range les modules dans les chunks CSS comme tout CSS                                                              |
| Différences visuelles massives d'un coup (ombres, focus, rayons de Mantine)                  | Thème calé sur les tokens actuels (STY2.5 : galerie vide) ; un composant à la fois ; `classNames` ramène chaque écart ; la galerie rend chaque écart visible   |
| `PresenterView` change de palette (slate codée en dur → tokens)                              | Différence **voulue**, annoncée ici, acceptée en galerie                                                                                                       |
| Les classes compilées par `@recit/core` dans le HTML (`slide-*`) ne peuvent pas être hachées | `deck.css` reste global (§5.4), `composes … from global` fait le pont                                                                                          |
| Remotion : un module CSS dans le bundle webpack                                              | Le paquet livre `styles.css` déjà compilé ; l'entrée l'importe comme `fonts.css` ; goldens avant/après                                                         |
| `prose` disparaît : la typographie du MDX bouge                                              | `Typography.module.css` reporte règle à règle `main.css:57-164` ; captures des 38 pages de doc en clair et en sombre                                           |
| Taille du CSS du site (feuille Mantine)                                                      | Mesurée en STY0.6 et STY5.3 ; import par composant si besoin ; le JS d'entrée baisse en face                                                                   |
| Les props de style de Mantine sont tentantes et partout dans sa doc                          | Interdites par le compteur (§8.1-3) et par le skill ; la revue de diff les attrape                                                                             |
| Le pont `.dark` oublié quelque part après STY9                                               | La liste des vingt fichiers de §1.4 est la check-list de STY9.1 ; `grep -rn '"dark"' packages sites` doit ne rendre que les valeurs de config                  |
| Deux sessions sur la même branche                                                            | Les règles de `18-mobile-desktop.md` §6 (relire avant d'écrire, un fichier un lot)                                                                             |
| Une baseline `-linux` régénérée par erreur masque une régression                             | Seul `test:e2e:update:linux <spec>` écrit, après galerie, avec la raison dans le journal ; le diff git des PNG est relu avant fusion                           |
| `vite/client` et le typage des modules dans les paquets publiés                              | Déclaration générée par `build-css-modules.mjs` (STY0.3), typecheck de chaque paquet dans sa vérification                                                      |

## 13. Questions ouvertes

1. **Props de style de Mantine** (`p`, `m`, `c`, `bg`…) : interdites ici par cohérence avec « aucun
   style dans le JSX ». Les composants de mise en page (`Group`, `Stack`, `Grid`) gardent `gap`,
   `justify`, `align`, qui sont leur raison d'être. Confirmer ces deux lignes avant STY3.
2. **Recherche** : `@mantine/spotlight` (proposé) ou une boîte en module sur `Modal` sans paquet de
   plus ?
3. **`deck.css` global** (proposé, §5.4) ou découpé en modules avec un contrat de classes publiques
   maintenu à la main ? Le global coûte moins et ne casse aucun récit.
4. **`motion/react`** gardé pour les transitions de diapositives (proposé) : Mantine n'a pas
   d'équivalent de `AnimatePresence`.
5. **Défauts du site** : rester zinc (shadcn) ou passer au violet de `@recit/theme` ? La galerie de
   STY2 montre les deux ; iso-rendu proposé (zinc) pour cette migration, violet pour le plan de
   refonte.
6. **Version** : `prerelease` (proposé) ou `preminor` au moment de fusionner ?
7. **Les pages héritées du framework d'origine dans `sites/docs`** : elles n'ont ni classe ni style
   (§1.3), donc rien à faire ; mais leur capture change avec le framework : est-ce que l'utilisateur
   veut les relire une à une en galerie, ou accepter la spec `site-docs` en bloc après revue
   d'échantillon ?
8. **Export PDF** (`export-pdf.ts`, `?print=1`, `print.css`) : accepter des différences de
   pagination dues aux polices ou aux marges de Mantine, ou viser l'identité ? Proposé : identité,
   `print.css` ne change pas.
9. **Captures mobiles** : un seul viewport (390×844) ou deux (plus 768×1024) ?
10. **Le nom `@recit/site/ui/*`** : garder ces exports (proposé, pour `site-kit` et les sites qui
    les prendraient) ou exposer Mantine directement et supprimer la couche ? Garder évite une
    rupture et permet des `classNames` par défaut cohérents.
11. **Le projet Playwright en mode sombre** : `colorScheme: "dark"` émule `prefers-color-scheme`,
    que `next-themes` n'honore que si le choix mémorisé est `system` (et Mantine, que si
    `defaultColorScheme` est `auto` et qu'aucune valeur n'est mémorisée). Le helper `darkMode` de
    `e2e/support.ts` doit donc poser le choix mémorisé : la clé de `next-themes` (`theme`)
    aujourd'hui, la clé de Mantine (`recit-color-scheme`, §2.5) après STY2, **les deux** jusqu'à
    STY9. STY1 crée le helper, STY2 l'étend (fichier ajouté à STY2). Faut-il garder le projet
    `chromium-dark` par émulation, ou capturer chaque page en sombre par le helper seul ?
