# Design system Novelia

Document de référence pour les contributeurs. Tout ce qui n'est pas ici est de la latitude — tout ce qui est ici doit être respecté.

## Direction art

**Palette claire « papier crème + bronze »**, signature « atelier français ». Décidée le 2026-05-21, post-pivot de l'ancien dark. PAS de mode dark prévu.

- Background global crème `#faf7f2`, cards blanches `#ffffff`.
- Accent bronze profond `#92400e` (amber-800), lisible AA sur crème.
- Polices : Cabinet Grotesk (body, Fontshare CDN) + Fraunces (display, Google Fonts).
- Signatures visuelles : `SectionStamp` (cartouche stone-900 + bordure bronze) + `TornEdge` (séparateur papier déchiré) + grain texture `.grain-overlay`.

## Architecture tokens · 3 couches

Tout dans [app/globals.css](app/globals.css) sous `@theme`. Commentaires de blocs explicites.

### Layer 1 · Primitives (palette brute)

**Usage strictement réservé à `globals.css`.** Ne jamais consommer directement en feature code.

- `--color-stone-50` → `--color-stone-950` : 11 paliers warm neutres.
- `--color-amber-50` → `--color-amber-900` : 10 paliers signature.
- `--color-paper`, `--color-paper-2`, `--color-paper-3` : crème déclinée.

### Layer 2 · Semantic (alias métiers)

**Couche normale de consommation en feature code.** C'est l'API.

| Token | Usage | Valeur |
|---|---|---|
| `--color-background` | Fond global | crème |
| `--color-surface` | Cards, panels | blanc |
| `--color-surface-2` | Zones secondaires, sidebars | crème foncé |
| `--color-surface-3` | Hover, sections appuyées | crème encore plus foncé |
| `--color-text` | Titres + body principal | stone-900 |
| `--color-text-muted` | Sous-titres, labels | stone-700 |
| `--color-text-dim` | Texte secondaire | stone-500 |
| `--color-text-faint` | Placeholders, meta | `#8d847e` |
| `--color-border` | Hairlines | rgba(28,25,23,0.08) |
| `--color-border-strong` | Bordures plus visibles | rgba(28,25,23,0.18) |
| `--color-accent` | Bronze actif | amber-800 |
| `--color-accent-hi` | Hover accent | amber-700 |
| `--color-accent-lo` | Active/pressed | amber-900 |
| `--color-accent-soft` | Fond accent doux | rgba(146,64,14,0.08) |
| `--color-accent-border` | Bordure accent visible | rgba(146,64,14,0.40) |
| `--color-success`/`-soft` | Confirmation | vert |
| `--color-warning`/`-soft` | Alerte | bronze |
| `--color-danger`/`-soft` | Erreur | rouge |
| `--color-info`/`-soft` | Information | bleu |

### Layer 3 · Component (tokens spécifiques composants)

Pour les patterns qui se répètent. Consommés via `shadow-[var(--shadow-X)]`.

- `--shadow-accent-soft` / `--shadow-accent-hi` : Button primary 2 états.
- `--shadow-navbar-float` : Navbar pill flottante.
- `--shadow-phone-mockup` : Mockups smartphone expertises.
- `--shadow-card-hover` : Hover cards interactives.

## Échelle typographique stricte

| Token | Pixels | Usage |
|---|---|---|
| `text-micro` | 10 | Meta, dot-labels, status-bar mockups |
| `text-xxs` | 11 | Eyebrow uppercase, deltas |
| `text-xs` | 12 | Labels, mentions légales |
| `text-sm` | 14 | Body sm, captions |
| `text-base` | 16 | Body |
| `text-lg` | 18 | Body lg |
| `text-xl` | 20 | Sub-h |
| `text-2xl` | 24 | h3 |
| `text-3xl` | 30 | h2 minor |
| `text-4xl` | 36 | h2 |
| `text-5xl` | 48 | h1 minor / display sm |
| `text-6xl` | 60 | display md |
| `text-7xl` | 72 | display lg |
| `text-8xl` | 96 | Hero display (accueil réservé) |

**Interdit :** `text-[10px]`, `text-[11px]`, `text-[13px]`, `text-[15px]`, `text-[19px]` etc. Si un cas réel demande une taille hors échelle, étendre l'échelle dans `globals.css`.

## Shadows

5 paliers warm allégés (Layer 2) :
`--shadow-xs` → `--shadow-xl`, teintés `rgba(28, 25, 23, X)`.

**Interdit :** `shadow-[0_X_Y_rgba(...)]` inline dans le feature code (sauf cas isolé qui révèle un pattern nouveau — auquel cas ajouter un token Layer 3).

## Radii

`--radius-xs` 4 / `sm` 6 / `md` 8 / `lg` 12 / `xl` 16 / `2xl` 20 / `card` 12 / `pill` ∞.

## Touch targets

`--touch-min: 2.75rem` (44px, WCAG). Garanti par les primitives Button/Input/Select/Textarea via `min-h-11 md:min-h-X`.

## Primitives UI

Toutes dans [components/ui/](components/ui/). API alignée sur la même structure.

| Primitive | Variants | Sizes | Props loading | Props invalid |
|---|---|---|---|---|
| `Button` | primary, secondary, ghost, outline, danger | sm, md, lg | ✓ | — |
| `Input` | (n/a) | sm, md, lg | — | ✓ |
| `Textarea` | (n/a) | sm, md, lg | — | ✓ |
| `Select` | (n/a) | sm, md, lg | — | ✓ |
| `Badge` | neutral, accent, success, warning, danger, muted | — | — | — |
| `Avatar` | corelia, client | sm, md, lg | — | — |
| `Panel` | (className `.panel` ou `.panel-elev`) | — | — | — |
| `StatCard` | (n/a) | — | — | — |
| `Skeleton` | (n/a) | — | — | — |
| `Field` | (orchestration label + hint + error + aria-describedby) | — | — | ✓ |

**Règle d'or :** toute nouvelle primitive doit suivre le schéma : `forwardRef` + `cn(...)` + props alignées sur ce tableau.

## Composants marketing dédiés signature « atelier »

| Composant | Rôle |
|---|---|
| `SectionStamp` | Cartouche stone-900 + bordure bronze. Remplace `Badge` sur les ouvertures de section éditoriales. |
| `TornEdge` | Séparateur SVG « papier déchiré » entre 2 sections. |
| `ExpertiseCard` | Card avec 3 variantes (text, numbered, media) pour casser la répétition entre pages expertises. |

## Animations

- `<Reveal>` (fade + translateY 12px sur 700ms ease-out) sur les pages marketing.
- **AUCUNE animation sur portail/admin** (outils du quotidien, jamais d'attente perçue).
- Respect `prefers-reduced-motion` partout (Reveal, grain, Process scroll-linked).
- Easing custom : `cubic-bezier(0.23, 1, 0.32, 1)` pour Button + Input + Select + Textarea (emil-design-eng baseline).

## A11y

- Focus visible global : `outline: 2px solid var(--color-accent); outline-offset: 2px`.
- Skip-link `.skip-link` dans root layout.
- `text-wrap: balance` sur h1-h4, `text-wrap: pretty` sur body.
- `tabular-nums` automatique sur `.tabular-nums`, `[data-tabular]`, tables.
- Toast `<ToastRegion>` dans root layout, `aria-live="polite"` (success/info) + `assertive` (error).
- Recharts wrappers exposent `aria-label` + `<details><table>` fallback données.

## SEO + GEO

- Sitemap dynamique [app/sitemap.ts](app/sitemap.ts) : auth pages exclues.
- Robots [app/robots.ts](app/robots.ts) : `disallow /portail /atelier-novelia /api/` + AI crawlers explicitement autorisés (GPTBot, ClaudeBot, PerplexityBot, etc.).
- `public/llms.txt` : manifeste GEO pour AI search.
- Schema.org : Organization + ProfessionalService + WebSite + FAQPage + OfferCatalog + BreadcrumbList + ItemList.
- OG image dynamique : [app/opengraph-image.tsx](app/opengraph-image.tsx).
- CSP : Report-Only dans [next.config.ts](next.config.ts) (à enforcer quand stable).

## Règles d'écriture

Cf. CLAUDE.md section « Préférences d'écriture ». Résumé :

- Pas d'emojis dans code/copy/commits.
- Pas d'em-dash (—) — utiliser virgules, deux-points, middle-dot `·`.
- Pas de chiffres précis dans promesses marketing vitrine.
- Tech-agnostique côté vitrine (pas de mention « Next.js », « React », « Tailwind »).
- `BRAND_NAME` / `BRAND_DOMAIN` toujours depuis [lib/brand.ts](lib/brand.ts).
- Icônes via [components/ui/icons.tsx](components/ui/icons.tsx), jamais directement Phosphor/Lucide.
- Vouvoiement systématique côté client.

## Anti-patterns interdits (revue audit 2026-05-23)

| Anti-pattern | À faire à la place |
|---|---|
| `text-[10px]` / `text-[11px]` etc. | `text-micro` / `text-xxs` / token approprié |
| `shadow-[0_X_Y_rgba(...)]` inline | Token shadow Layer 2 ou ajouter Layer 3 |
| `bg-stone-900/[0.04]` en feature code | `bg-surface-2` ou alias sémantique |
| Halos décoratifs `blur-[80px]+` | Suppression — ce sont des reliquats dark-SaaS |
| Mockups gradient `from-stone-900 to-stone-950` | Aplat `bg-stone-900` + `--shadow-phone-mockup` |
| Card identique répétée 4 fois | Varier avec `ExpertiseCard variant=numbered/media` |
| `<span className="italic text-accent-hi">` | Réservé à 1 occurrence sur tout le site (accueil) |
| `themeColor: "#0c0a09"` / `colorScheme: "dark"` | `"#faf7f2"` / `"light"` (palette est claire) |
| `/connexion` dans sitemap | Auth pages restent hors sitemap |
| `<br className="hidden md:block" />` décoratif | Laisser `text-wrap: balance` faire son travail |

## Quand étendre le système

Si tu as besoin d'un token qui n'existe pas :
1. Vérifier qu'il ne dérive pas d'un existant (`bg-accent/40` au lieu de nouveau token).
2. S'il représente un pattern récurrent (≥ 3 utilisations), créer un token Layer 3 dans `globals.css`.
3. Documenter ici.
4. Pas de réservation « peut-être un jour » — on ajoute quand le besoin se manifeste.
