# DESIGN_SYSTEM.md — Expria Frontend > **Document de référence — Version 1.0 — Sprint 1** > Source de vérité unique pour l'identité visuelle, les tokens de design et les primitives UI. > Toute décision de DA doit être consignée ici avant d'être implémentée. --- ## 1. Direction artistique — verrouillée **Nom :** Boréal **Positionnement :** institutionnel chaleureux, premium sans flashy, sérieux sans austère. **Référence mentale :** Stripe Dashboard, Linear, Notion Desktop — mais réchauffé d'un cran. ### Parti pris fondateurs | Principe | Décision | |---|---| | Mode canonique Sprint 1 | **Clair uniquement** (light chaud) | | Mode sombre | Prévu Sprint 2+ (tokens écrits dual-theme-ready dès J1) | | Fond principal | `#F4F2EC` (off-white calibré, ni froid ni saturé) | | Surfaces élevées | Blanc pur `#FFFFFF` pour contraste subtil avec le fond | | Bleu de marque | `#1B4FD8` **sacro-saint** en mode clair — aucune variation | | Bleu mode sombre | `#7C9BFF` **prévu** pour Sprint 2+ (pattern Apple system colors) | | Accent chaleureux | Aucun en Sprint 1 — le bleu porte toute l'intentionnalité | | Angles | Rayons généreux mais retenus : 8 / 12 / 16 px | | Ombres | Minimales. 1 ombre-card unique, très subtile. Hairlines 1px privilégiées. | | Animations | 150–200 ms, `ease-out`, respect de `prefers-reduced-motion` | | Icônes | SVG inline dans `shared/ui/icons/` — aucune dépendance externe | | Typographie | Plus Jakarta Sans (via `font-family`, fallback système) | ### Ce qu'on refuse explicitement - Gradients criards (le seul acceptable : aucun). - Glassmorphism ou `backdrop-filter` généralisé — réservé à la bottom nav mobile si besoin. - Emojis dans les éléments interactifs ou les labels fonctionnels. - Ombres lourdes, "drop shadows" style Material Design 2. - Plus de 2 niveaux d'élévation visuelle (fond → card → modal). - Toute police de display fantaisiste, serif décorative ou condensée. - Les motifs SaaS génériques : illustrations 3D, dégradés violet-rose, glass blobs. --- ## 2. Tokens — `src/index.css` Remplacer intégralement le contenu actuel (`@import 'tailwindcss';`) par le bloc ci-dessous. Tailwind 4 lit automatiquement les tokens déclarés dans `@theme`. ```css @import 'tailwindcss'; @theme { /* ----- Brand ------------------------------------------------------- */ --color-brand: #1B4FD8; --color-brand-hover: #1744B8; --color-brand-active: #13379C; --color-brand-soft: #E7EDFC; --color-brand-ink: #FFFFFF; /* ----- Surfaces (light — Sprint 1) --------------------------------- */ --color-bg: #F4F2EC; --color-surface: #FBFAF6; --color-surface-raised: #FFFFFF; --color-surface-sunken: #EEECE4; /* ----- Ink (texte) ------------------------------------------------- */ --color-ink-primary: #0F1220; --color-ink-secondary: #4A4F5E; --color-ink-tertiary: #8A8F9E; --color-ink-inverse: #FBFAF6; /* ----- Borders & dividers ------------------------------------------ */ --color-border: #E3E0D6; --color-border-strong: #C9C5B7; --color-border-focus: #1B4FD8; /* ----- Feedback ---------------------------------------------------- */ --color-success: #1F7A4C; --color-success-soft: #E3F2EA; --color-warning: #B8741A; --color-warning-soft: #F7EEDF; --color-danger: #B8322D; --color-danger-soft: #F7E1DF; /* ----- Typographie ------------------------------------------------- */ --font-sans: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", sans-serif; --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace; /* Échelle : mobile-first, les tailles desktop se gèrent via utilities Tailwind */ --text-xs: 11px; --text-sm: 13px; --text-base: 14px; --text-md: 15px; --text-lg: 17px; --text-xl: 20px; --text-2xl: 24px; --text-3xl: 32px; --text-display: 40px; /* ----- Rayons ------------------------------------------------------ */ --radius-xs: 6px; --radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-xl: 20px; --radius-pill: 999px; /* ----- Ombres ------------------------------------------------------ */ --shadow-card: 0 1px 2px rgba(15, 18, 32, 0.04), 0 1px 8px rgba(15, 18, 32, 0.03); --shadow-raised: 0 4px 16px rgba(15, 18, 32, 0.06), 0 1px 2px rgba(15, 18, 32, 0.04); --shadow-focus: 0 0 0 3px rgba(27, 79, 216, 0.18); } /* --------------------------------------------------------------------- */ /* Globals — reset minimal, fond chaud par défaut */ /* --------------------------------------------------------------------- */ html, body { background: var(--color-bg); color: var(--color-ink-primary); font-family: var(--font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-variant-numeric: tabular-nums; } *:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--radius-xs); } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0ms !important; transition-duration: 0ms !important; } } /* --------------------------------------------------------------------- */ /* TODO Sprint 2+ — Dark theme (Cadence recalibré) */ /* --------------------------------------------------------------------- */ /* @theme { --color-bg-dark: #0F1320; --color-surface-dark: #171B2B; --color-surface-raised-dark: #1E2338; --color-ink-primary-dark: #E6E4DB; --color-ink-secondary-dark: #9CA0AC; --color-brand-dark: #7C9BFF; --color-brand-hover-dark: #9AB3FF; --color-border-dark: #2A3048; } */ ``` ### Règles d'usage des tokens 1. **Aucune valeur hexadécimale en dur** dans les composants. Toute couleur passe par un token. 2. **Nommage sémantique obligatoire.** On écrit `bg-surface`, pas `bg-gray-50`. 3. Si un cas d'usage exige une teinte hors charte, **le documenter ici avant de l'ajouter**. Pas de token orphelin. 4. Les tokens marqués `*-dark` ne sont **pas utilisés en Sprint 1**. Leur présence en commentaire est intentionnelle pour faciliter la reprise Sprint 2+. --- ## 3. Typographie | Usage | Taille | Poids | Tracking | Ligne | Token | |---|---|---|---|---|---| | Display (NCLC hero) | 40px | 700 | -0.02em | 1.0 | `text-display` | | H1 page | 32px | 700 | -0.02em | 1.1 | `text-3xl` | | H2 section | 24px | 700 | -0.015em | 1.2 | `text-2xl` | | H3 card title | 20px | 700 | -0.01em | 1.3 | `text-xl` | | Lead / intro | 17px | 500 | -0.005em | 1.5 | `text-lg` | | Body | 14px | 400 | 0 | 1.6 | `text-base` | | Body renforcé | 15px | 500 | 0 | 1.55 | `text-md` | | Small / meta | 13px | 400 | 0 | 1.5 | `text-sm` | | Eyebrow / label | 11px | 600 | 0.1em (uppercase) | 1.4 | `text-xs` | **Règles :** - Tout nombre métier (score 16/20, NCLC 7,5, compteur 3/5) est rendu en `font-variant-numeric: tabular-nums`. Hérité par le body, mais à re-spécifier explicitement sur les tables et listes. - `Plus Jakarta Sans` est déclarée en `font-family` avec fallback système. **Aucune webfont chargée** tant qu'on n'a pas validé la stratégie self-hosting (décision reportée). - Les chiffres français utilisent la **virgule** comme séparateur décimal (`7,5`, jamais `7.5`). --- ## 4. Primitives UI — Sprint 1 À créer dans `src/shared/ui/` en FSD, une primitive par dossier (`button/`, `card/`, etc.) avec `index.ts` pour l'export. ### Inventaire minimal | Composant | Variants | Usage dashboard | |---|---|---| | `Button` | `primary` / `secondary` / `ghost` / `upgrade` | CTA "Nouvelle simulation", "Passer au plan Standard", actions tertiaires | | `Card` | `default` / `raised` / `interactive` | Cadre métriques, item simulation, recommandation | | `MetricCard` | `default` / `hero` (pour le NCLC) | Bloc NCLC, compteur simulations, dernier score | | `ProgressBar` | `default` | Progression vers NCLC 9 | | `Badge` | `plan` / `nclc` / `neutral` | Plan actuel dans header, niveau NCLC par simulation | | `Sidebar` | — | Nav desktop (≥ 1024px) | | `BottomNav` | — | Nav mobile (< 1024px), 4 items max | | `PageHeader` | — | Greeting + plan pill | | `SectionHeader` | — | Titre de section + action optionnelle | ### Règles d'implémentation - Chaque primitive **accepte `className`** en plus de ses props typées, pour overrides ponctuels. - Chaque primitive **expose ses props via un type exporté** (`ButtonProps`, `CardProps`, etc.). - Aucune primitive ne contient de logique métier ou d'appel API. Elles reçoivent tout par props. - Les icônes sont passées par une prop `icon` acceptant un `ReactNode`, jamais par nom de string. --- ## 5. Layout dashboard — spécification Les primitives ci-dessus s'assemblent dans `src/features/dashboard/` et `src/pages/dashboard/`. ### Structure sémantique ``` (≥ 1024px)
(greeting + plan)
(NCLC estimé + progression) (simulations restantes) (dernier score)
(< 1024px) ``` ### Breakpoints | Breakpoint | Comportement | |---|---| | `< 1024px` | Mono-colonne, `BottomNav` fixe en bas, padding horizontal 20px | | `≥ 1024px` | Sidebar 240px + contenu centré 860px max, padding horizontal 32px | | `≥ 1440px` | Contenu centré 920px max (pas d'élargissement excessif) | ### Densité verticale - Padding vertical section : 24px mobile, 32px desktop. - Gap inter-cards : 12px mobile, 16px desktop. - Marge sous `PageHeader` : 20px mobile, 28px desktop. --- ## 6. Données mock — Sprint 1 Avant branchement API, fournir les données via `src/shared/api/mock/dashboard.ts`. Données crédibles, françaises, alignées sur l'audience réelle. ```typescript export const mockDashboard = { user: { firstName: 'Yacine', plan: 'decouverte' as const, planLabel: 'Plan Découverte', }, metrics: { nclcEstimated: 7.5, nclcTarget: 9, simulationsUsed: 2, simulationsQuota: 5, lastScore: { value: 16, max: 20, type: 'ecrit' as const }, }, recentSimulations: [ { id: 's-001', type: 'ecrit', relativeDate: 'il y a 2 jours', score: 16, max: 20, nclc: 8 }, { id: 's-002', type: 'oral', relativeDate: 'il y a 5 jours', score: 14, max: 20, nclc: 7 }, { id: 's-003', type: 'ecrit', relativeDate: 'il y a 1 semaine', score: 15, max: 20, nclc: 7 }, ], nextStep: { title: 'Cible une simulation orale cette semaine', body: 'Ton écrit est solide (NCLC 8). L\'oral reste à consolider pour sécuriser ton NCLC 9.', action: { label: 'Démarrer Expression Orale', to: '/simulation/orale' }, }, } as const; ``` **Règles contenu :** - Aucun "Lorem ipsum", aucune date absolue — relatif uniquement (`il y a X jours`). - Les prénoms mocks reflètent l'audience : Yacine, Aminata, Kenza, Bilal, Fatou, Kévin (Canada). - Les scores suivent une progression crédible (pas de 20/20 ni de 5/20). --- ## 7. Accessibilité — plancher Sprint 1 - Contraste minimum **WCAG AA** sur tous les couples texte/fond (vérifié pour la palette ci-dessus). - Tous les éléments interactifs ont un `:focus-visible` avec `--shadow-focus` (halo bleu 3px). - Les icônes purement décoratives portent `aria-hidden="true"`. - Les icônes fonctionnelles (sans label visible) portent `aria-label`. - Les landmarks sémantiques sont utilisés : `
`, `