From 9356c6e3f45de939e8d9bf216d510ca621579d5f Mon Sep 17 00:00:00 2001 From: R0m1k3 Date: Tue, 11 Aug 2026 15:37:11 +0200 Subject: [PATCH] =?UTF-8?q?Refondre=20le=20th=C3=A8me=20autour=20du=20regi?= =?UTF-8?q?stre=20et=20du=20chiffre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'ancien thème était crème et vert forêt, corps à 14 px, libellés secondaires à 11 px. Deux défauts s'y cumulaient : ça se lisait mal huit heures par jour, et le fond chaud décalait la perception des aplats — un jaune de poste virait au vert, un rouge d'alerte s'éteignait. Le nouveau part de ce que l'écran est vraiment : un registre qu'on lit en chiffres. Papier légèrement bleuté, encre bleu-ardoise plus dense, et un seul bleu d'acte réservé à trois emplois — le filet actif, les liens, le bouton qui engage. Tout le reste est en gris et en état, ce qui rend les alertes visibles parce qu'elles sont les seules choses colorées de la page. Lisibilité d'abord. Le corps monte à 15 px, rien ne descend sous 12, et `ink-3` passe de 0,545 à 0,50 de clarté : à l'ancienne valeur, les libellés secondaires tombaient sous 4,5:1 sur ce papier. Typographie : IBM Plex Sans, et Plex Mono pour les codes. Le choix vient du contenu — cet écran affiche des identifiants, AB-300, HS-HS25, un matricule, un département 2A. Plex a été dessiné pour la documentation technique : le 1 porte un empattement, le l est courbé, le 0 se distingue du O, et ces trois détails décident si un gestionnaire recopie le bon code. Le zéro barré est activé partout. Les fichiers sont téléchargés à la compilation et servis par l'application : `font-src 'self'` tient, et aucune requête ne part vers un tiers. La structure est portée par un **filet de marge** de 2 px plutôt que par des bordures et des aplats gris — la marge du cahier, appliquée à une interface. Il marque l'onglet ouvert, l'en-tête de carte, la section active. Il ne décore jamais : un filet dit une appartenance. Le dimanche a le sien, doublé, et une colonne teintée. C'est la seule colonne du planning qui porte une conséquence de droit — L3132-27 impose une rémunération au moins double **et** un repos compensateur équivalent. La grille le montre avant que la paie ne le découvre. Le marquage ne remplace aucune alerte : le moteur de règles reste seul juge. La barre d'application devient collante, et les boutons passent de 26 à 32 px de haut : sur une grille de quatre-vingts lignes, remonter changer de semaine est le geste le plus répété, et 26 px était sous le seuil confortable au pointeur. Co-Authored-By: Claude Opus 5 --- src/app/globals.css | 228 +++++++++++++++++++++------- src/app/layout.tsx | 43 +++++- src/components/shell/AppShell.tsx | 60 ++++++-- src/components/shell/PageHeader.tsx | 12 +- src/components/ui/Button.tsx | 14 +- src/components/ui/Card.tsx | 8 +- 6 files changed, 279 insertions(+), 86 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 35bc503..8e9a41f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -14,74 +14,98 @@ ========================================================================== */ @theme { - --font-ui: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, - 'Helvetica Neue', Arial, sans-serif; - --font-mono: ui-monospace, SFMono-Regular, 'Cascadia Mono', 'Segoe UI Mono', - 'Liberation Mono', Menlo, monospace; + /* IBM Plex — servi depuis l'application, jamais depuis un CDN (voir + src/app/layout.tsx). Le repli système suit en cas d'échec de chargement : + un planning doit rester lisible même quand la fonte ne l'est pas. */ + --font-ui: var(--font-plex-sans), ui-sans-serif, system-ui, -apple-system, + 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + --font-mono: var(--font-plex-mono), ui-monospace, SFMono-Regular, + 'Cascadia Mono', 'Segoe UI Mono', 'Liberation Mono', Menlo, monospace; - --text-micro: 11px; - --text-xs: 12px; - --text-sm: 13px; - --text-md: 14px; - --text-lg: 16px; - --text-xl: 20px; - --text-2xl: 26px; - --text-3xl: 32px; + /* Échelle relevée d'un cran : l'ancienne partait de 14 px de corps et 11 px + de micro-libellé, ce qui se lit mal huit heures par jour sur un écran de + back-office. Le corps est à 15 px, et rien ne descend sous 12. */ + --text-micro: 12px; + --text-xs: 13px; + --text-sm: 14px; + --text-md: 15px; + --text-lg: 17px; + --text-xl: 21px; + --text-2xl: 27px; + --text-3xl: 34px; - --radius-1: 3px; - --radius-2: 6px; - --radius-3: 10px; - --radius-4: 14px; + /* Rayons courts. Un planning est un tableau de bord, pas une carte de + visite : au-delà de 8 px les cellules perdent leur alignement optique. */ + --radius-1: 2px; + --radius-2: 5px; + --radius-3: 8px; + --radius-4: 12px; --radius-full: 999px; --ease-organic: cubic-bezier(0.2, 0.7, 0.3, 1); --ease-organic-out: cubic-bezier(0, 0.6, 0.35, 1); - /* Neutres chauds — la base « organique » du thème. */ - --color-canvas: oklch(0.976 0.008 85); - --color-surface: oklch(0.996 0.005 85); - --color-surface-2: oklch(0.958 0.01 85); - --color-surface-3: oklch(0.932 0.012 85); - --color-line-1: oklch(0.905 0.012 85); - --color-line-2: oklch(0.852 0.014 85); - --color-line-3: oklch(0.735 0.016 85); - --color-ink-1: oklch(0.255 0.02 140); - --color-ink-2: oklch(0.445 0.016 135); - --color-ink-3: oklch(0.545 0.014 120); + /* ---- Neutres froids --------------------------------------------------- + Papier légèrement bleuté plutôt que crème. Le crème réchauffe et fatigue + moins sur un texte long ; ici l'écran porte des tableaux de chiffres et des + aplats colorés, et un fond chaud décale la perception de ces aplats — un + jaune de poste vire au vert, un rouge d'alerte s'éteint. Le neutre froid + laisse la couleur dire ce qu'elle dit. + --------------------------------------------------------------------- */ + --color-canvas: oklch(0.979 0.004 250); + --color-surface: oklch(1 0 0); + --color-surface-2: oklch(0.965 0.005 250); + --color-surface-3: oklch(0.938 0.008 250); + --color-line-1: oklch(0.912 0.008 250); + --color-line-2: oklch(0.858 0.011 250); + --color-line-3: oklch(0.72 0.015 250); - --color-accent: oklch(0.44 0.078 158); - --color-accent-hover: oklch(0.385 0.08 158); - --color-accent-press: oklch(0.335 0.072 158); - --color-accent-ink: oklch(0.985 0.008 158); - --color-accent-soft: oklch(0.935 0.03 158); - --color-accent-soft-ink: oklch(0.315 0.06 158); + /* Encre plus dense qu'avant, et franchement bleutée. `ink-3` monte à 0.50 : + à 0.545 sur ce papier, les libellés secondaires passaient sous 4,5:1. */ + --color-ink-1: oklch(0.215 0.018 258); + --color-ink-2: oklch(0.405 0.018 256); + --color-ink-3: oklch(0.50 0.017 254); - --color-focus: oklch(0.52 0.16 252); + /* Bleu d'acte. Il ne sert qu'à trois choses : le filet actif, les liens, et + le bouton qui engage. Tout le reste du produit est en gris et en état. */ + --color-accent: oklch(0.435 0.135 258); + --color-accent-hover: oklch(0.375 0.135 258); + --color-accent-press: oklch(0.325 0.12 258); + --color-accent-ink: oklch(0.99 0.004 258); + --color-accent-soft: oklch(0.945 0.028 258); + --color-accent-soft-ink: oklch(0.335 0.11 258); - --color-danger: oklch(0.475 0.145 30); - --color-danger-hover: oklch(0.415 0.145 30); - --color-danger-ink: oklch(0.985 0.006 30); - --color-danger-soft: oklch(0.94 0.035 30); - --color-danger-soft-ink: oklch(0.355 0.105 30); + /* Focus en cyan : distinct du bleu d'accent, sinon l'anneau disparaît sur le + bouton primaire, c'est-à-dire là où il compte le plus. */ + --color-focus: oklch(0.58 0.155 218); - --color-warn: oklch(0.745 0.125 78); - --color-warn-ink: oklch(0.255 0.055 60); - --color-warn-soft: oklch(0.945 0.045 82); - --color-warn-soft-ink: oklch(0.375 0.085 60); + --color-danger: oklch(0.505 0.175 27); + --color-danger-hover: oklch(0.445 0.175 27); + --color-danger-ink: oklch(0.99 0.005 27); + --color-danger-soft: oklch(0.955 0.03 27); + --color-danger-soft-ink: oklch(0.395 0.13 27); - --color-ok: oklch(0.475 0.095 148); - --color-ok-soft: oklch(0.935 0.035 148); - --color-ok-soft-ink: oklch(0.315 0.07 148); + --color-warn: oklch(0.70 0.145 65); + --color-warn-ink: oklch(0.24 0.06 55); + --color-warn-soft: oklch(0.958 0.045 78); + --color-warn-soft-ink: oklch(0.415 0.10 55); - --color-info: oklch(0.475 0.105 245); - --color-info-soft: oklch(0.935 0.035 245); - --color-info-soft-ink: oklch(0.325 0.08 245); + --color-ok: oklch(0.505 0.115 158); + --color-ok-soft: oklch(0.955 0.033 158); + --color-ok-soft-ink: oklch(0.365 0.085 158); - --shadow-e1: 0 1px 2px oklch(0.3 0.02 85 / 0.08); + --color-info: oklch(0.505 0.115 240); + --color-info-soft: oklch(0.955 0.03 240); + --color-info-soft-ink: oklch(0.365 0.09 240); + + /* Ombres froides et courtes. Une ombre longue sur un tableau dense fait + flotter les cartes les unes au-dessus des autres et brouille la lecture + des colonnes. */ + --shadow-e1: 0 1px 1px oklch(0.35 0.03 258 / 0.06); --shadow-e2: - 0 1px 2px oklch(0.3 0.02 85 / 0.06), 0 6px 14px oklch(0.3 0.02 85 / 0.07); + 0 1px 2px oklch(0.35 0.03 258 / 0.07), 0 4px 10px oklch(0.35 0.03 258 / 0.06); --shadow-e3: - 0 2px 6px oklch(0.3 0.02 85 / 0.08), 0 16px 40px oklch(0.3 0.02 85 / 0.14); + 0 2px 4px oklch(0.35 0.03 258 / 0.08), 0 14px 34px oklch(0.35 0.03 258 / 0.12); } :root { @@ -111,12 +135,16 @@ toujours écrit dans le bloc (voir ShiftChip). --------------------------------------------------------------------- */ --tier-dir: -1; - --chip-bg-l: 0.905; - --chip-bg-c: 0.042; - --chip-fg-l: 0.315; - --chip-fg-c: 0.055; - --chip-edge-l: 0.52; - --chip-edge-c: 0.12; + /* Retendus pour le papier froid : fonds un cran plus clairs et moins + chargés, encre un cran plus sombre. Sur l'ancien fond crème, les aplats + tiraient tous vers le même beige moyen et la grille devenait un patchwork ; + ici ils restent des repères, et le texte du bloc passe 7:1. */ + --chip-bg-l: 0.928; + --chip-bg-c: 0.038; + --chip-fg-l: 0.29; + --chip-fg-c: 0.062; + --chip-edge-l: 0.545; + --chip-edge-c: 0.115; --post-cai-h: 250; --post-cai-t: 0; @@ -336,6 +364,30 @@ body { font-size: var(--text-md); line-height: var(--lh-ui); -webkit-font-smoothing: antialiased; + /* Zéro barré partout. Sur un écran couvert de codes — AB-300, HS-HS25, un + matricule — la confusion entre 0 et O se paie en ligne de paie fausse. */ + font-variant-numeric: slashed-zero; +} + +/* Les titres se resserrent, les micro-libellés s'écartent. Cette respiration + inverse est ce qui distingue une hiérarchie d'un simple changement de corps : + à 27 px, l'espacement par défaut d'une grotesque paraît lâche ; à 12 px, il + colle. */ +h1, +h2, +h3 { + letter-spacing: -0.02em; + line-height: var(--lh-tight); +} + +/* Les codes se lisent en chasse fixe : ils se recopient caractère par + caractère, souvent à la main, dans un autre logiciel. */ +code, +kbd, +samp { + font-family: var(--font-mono); + font-size: 0.95em; + font-variant-numeric: slashed-zero; } *, @@ -357,6 +409,66 @@ body { font-variant-numeric: tabular-nums; } +/* ========================================================================== + Le filet — élément de structure du produit + + Un planning est lu comme un registre : des lignes, des colonnes, et des + filets qui disent où commence quoi. Plutôt que d'entourer chaque bloc d'une + bordure complète, la structure est portée par un **filet de marge** de 2 px + à gauche de ce qui est actif ou de ce qui compte. C'est la marge rouge du + cahier, appliquée à une interface. + + Deux règles, pour que ce ne soit pas un ornement : + - un filet marque une **appartenance** (cette section, cet onglet), jamais + une décoration ; + - il ne double jamais une bordure existante. + ========================================================================== */ +.spine { + border-left: 2px solid var(--color-accent); + padding-left: 0.875rem; +} + +.spine-quiet { + border-left: 2px solid var(--color-line-2); + padding-left: 0.875rem; +} + +/* Sur-titre : le petit libellé qui nomme une zone sans lui voler la vedette. + Interlettré large et gradé haut — à cette taille, la graisse porte mieux la + lisibilité que le corps. */ +.eyebrow { + font-size: var(--text-micro); + font-weight: 650; + letter-spacing: 0.085em; + text-transform: uppercase; + color: var(--color-ink-3); +} + +/* Un chiffre qu'on vient lire, par opposition à un chiffre dans une phrase : + compteur d'heures, effectif, écart. Tabulaire, dense, sans ambiguïté entre + le zéro et le O. */ +.figure { + font-variant-numeric: tabular-nums lining-nums slashed-zero; + letter-spacing: -0.01em; + font-weight: 600; + color: var(--color-ink-1); +} + +/* ---- Le dimanche --------------------------------------------------------- + La seule colonne du planning qui porte une conséquence de droit : L3132-27 + impose une rémunération au moins double **et** un repos compensateur + équivalent. Elle est donc marquée dans la grille — fond très légèrement + teinté, et un filet double à sa gauche, celui qu'on trace dans un registre + pour séparer ce qui se totalise à part. + + Le marquage ne remplace aucune alerte : le moteur de règles reste seul juge. + Il évite de découvrir un dimanche planifié au moment de la paie. + ------------------------------------------------------------------------ */ +.col-sunday { + background: var(--color-warn-soft); + box-shadow: inset 2px 0 0 var(--color-warn), inset 5px 0 0 var(--color-surface); +} + @media (prefers-reduced-motion: reduce) { *, *::before, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1b19f29..3e233de 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,10 +1,46 @@ import type { Metadata } from 'next'; +import { IBM_Plex_Mono, IBM_Plex_Sans } from 'next/font/google'; import { headers } from 'next/headers'; import { THEME_INIT_SCRIPT } from '@/components/shell/ThemeToggle'; import './globals.css'; +/** + * IBM Plex Sans, et Plex Mono pour les codes. + * + * Le choix vient de ce que l'écran affiche vraiment : des **identifiants**. + * `AB-300`, `HS-HS25`, un matricule `E0004`, un code de poste, un SIRET, un + * département `2A`. Plex a été dessiné pour la documentation technique — le + * 1 porte un empattement, le l est courbé, le 0 se distingue du O — et ces + * trois détails décident si un gestionnaire de paie recopie le bon code. + * + * Ses formes légèrement carrées tiennent aussi dans une cellule de tableau + * dense, là où une grotesque géométrique se referme et où deux lignes de + * chiffres cessent de s'aligner à l'œil. + * + * `next/font` télécharge les fichiers **à la compilation** et les sert depuis + * l'application : la directive `font-src 'self'` reste satisfaite, et aucune + * requête ne part vers un tiers à l'exécution — ce que la charte de télémétrie + * interdit de toute façon. + */ +const plexSans = IBM_Plex_Sans({ + subsets: ['latin'], + weight: ['400', '500', '600', '700'], + variable: '--font-plex-sans', + display: 'swap', + // Métriques ajustées sur la fonte de repli : sans cela, la bascule au + // chargement décale toute la grille d'un demi-caractère. + adjustFontFallback: true, +}); + +const plexMono = IBM_Plex_Mono({ + subsets: ['latin'], + weight: ['400', '500'], + variable: '--font-plex-mono', + display: 'swap', +}); + export const metadata: Metadata = { title: 'PlanFlow', description: 'Gestion du personnel, des plannings et des temps.', @@ -19,7 +55,12 @@ export default async function RootLayout({ const nonce = (await headers()).get('x-nonce') ?? undefined; return ( - +