@zevra/ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,566 @@
1
+ /* Zevra UI — retours d'état : bandeaux, étiquettes, pastilles, absence,
2
+ * travail en cours, curseur de flux.
3
+ *
4
+ * Dépend de tokens.css et de base.css (reset, fonte de corps, focus clavier
5
+ * global). Indépendant des autres modules.
6
+ *
7
+ * Tokens réellement employés ici, tous vérifiés dans tokens.css :
8
+ * couleurs — --ink, --ink-2, --muted, --label, --line, --hairline, --white,
9
+ * --accent, --accent-line, --accent-soft,
10
+ * --ok, --ok-soft, --ok-line,
11
+ * --warn, --warn-soft, --warn-line, --warn-ink,
12
+ * --err, --err-soft, --err-line ;
13
+ * typo — --font-display, --font-mono, --app, --app-sm,
14
+ * --app-h2, --mono, --mono-sm.
15
+ * Aucun autre. Il n'existe PAS de --ink-3…--ink-7, pas de --line-soft, pas de
16
+ * --line-firm, pas d'échelles --text-* ni --mono-xs/--mono-2xs : une version
17
+ * antérieure de cet en-tête les déclarait en dépendance, ce qui rendait le
18
+ * module inauditable (rien ne disait quelle valeur portait --ink-6) et l'aurait
19
+ * rendu sans couleur ni taille chez le consommateur. Les valeurs relevées qui
20
+ * tombaient sur ces noms sont rabattues sur les paliers du paquet, et chaque
21
+ * rabattement qui déplace le rendu est écrit en commentaire à l'endroit exact
22
+ * où il est fait.
23
+ *
24
+ * Relevé sur l'application Mémoire, en production. Une différence de méthode
25
+ * avec les modules de vitrine : Mémoire est écrite mobile-first et bascule à
26
+ * `lg` (1024px), alors que la charte ne connaît qu'UN point de rupture, à
27
+ * 768px. Les valeurs desktop sont donc posées en base et les valeurs mobiles
28
+ * regroupées en fin de fichier — le rendu desktop est identique au relevé, le
29
+ * rendu mobile arrive 256px plus tôt. C'est délibéré : deux ruptures dans un
30
+ * système qui en annonce une seule est une dette qui se paie sur chaque app.
31
+ *
32
+ * ÉCHELLE DE TEXTE — la production emploie des demi-pixels (11,5 / 12,5 /
33
+ * 13,5) que le paquet a rationalisés en trois paliers (12 / 13 / 14). Le
34
+ * rabattement retenu ici conserve les ÉCARTS du relevé plutôt que la valeur
35
+ * absolue : 11,5 et 12,5 → --app-sm (12), 13,5 → --app (13), 14 → --app-lg.
36
+ * Deux tailles distinctes d'un pixel dans la source restent donc distinctes
37
+ * d'un pixel ; c'est ce qui fait qu'un bandeau ample reste plus grand qu'un
38
+ * bandeau courant, des deux côtés du seuil.
39
+ *
40
+ * Rien ici n'est interactif : pas de bouton de fermeture, pas de survol, pas
41
+ * de champ. Les états :hover / :focus-visible / [disabled] / [aria-invalid]
42
+ * n'ont donc aucune cible dans ce module — ils appartiennent aux boutons
43
+ * (components.css) et aux champs (forms.css). Le focus clavier global de
44
+ * base.css suffit au bandeau qu'on rend focusable pour l'annoncer.
45
+ */
46
+
47
+ /* ─── Bandeaux d'état ───────────────────────────────────────────────────
48
+ * Filet + fond atténué + texte du même ton : la grammaire la plus
49
+ * systématique du relevé, appliquée à l'identique six fois. Elle tient
50
+ * parce que les trois valeurs viennent d'une même famille de tokens ; la
51
+ * recomposer à la main avec des opacités produit six bandeaux différents.
52
+ *
53
+ * Le ton est OBLIGATOIRE : `.zv-alert` seule hérite d'un filet en
54
+ * currentColor, c'est-à-dire un trait d'encre autour d'un fond blanc.
55
+ * Toujours l'accompagner de --info / --ok / --warn / --err / --dash. */
56
+
57
+ .zv-alert {
58
+ border: 1px solid;
59
+ padding: 12px 16px;
60
+ /* 12,5px relevé → palier 12. La valeur mobile (13,5 → 13) est en fin de
61
+ * fichier : les cinq bandeaux d'authentification GROSSISSENT sous le
62
+ * seuil, ils ne rétrécissent pas. */
63
+ font-size: var(--app-sm);
64
+ line-height: 1.55;
65
+ font-weight: 500;
66
+ /* Un bandeau porte souvent un message d'erreur brut, non maîtrisé :
67
+ * nom de fichier à rallonge, identifiant, chemin. Sans coupure, il pousse
68
+ * la page en défilement horizontal. */
69
+ overflow-wrap: break-word;
70
+ /* Angle vif, comme au relevé.
71
+ * ⚠️ NON ARBITRÉ EN DA, et le module ne tranche pas à sa place : le
72
+ * `border-radius: 0` posé ici et sur .zv-badge est la valeur relevée, pas
73
+ * une « signature de la couche applicative ». Il entre en contradiction
74
+ * directe avec .zv-empty__action, qui renvoie au .zv-btn de
75
+ * components.css et donc à --r-btn (5px) : un écran vide affichera un
76
+ * bouton arrondi au milieu de blocs à angle vif. La règle doit monter
77
+ * dans DECISIONS.md ou tomber — elle ne peut pas rester implicite dans
78
+ * quatre modules. */
79
+ border-radius: 0;
80
+ }
81
+
82
+ /* Information — le seul ton relevé en bandeau de bas de page (garanties,
83
+ * explication d'un écran). */
84
+ .zv-alert--info {
85
+ border-color: var(--accent-line);
86
+ background: var(--accent-soft);
87
+ color: var(--accent);
88
+ }
89
+
90
+ /* Avertissement — le texte est --warn-ink, JAMAIS --warn : l'ambre plein
91
+ * sur fond ambre atténué passe sous le seuil de contraste. C'est la seule
92
+ * famille asymétrique du système, et l'erreur la plus facile à commettre. */
93
+ .zv-alert--warn {
94
+ border-color: var(--warn-line);
95
+ background: var(--warn-soft);
96
+ color: var(--warn-ink);
97
+ }
98
+
99
+ /* Erreur — à porter avec role="alert" côté balisage, sinon le message
100
+ * apparaît sans être annoncé aux lecteurs d'écran : c'est précisément le
101
+ * seul contenu de l'écran qu'on ne peut pas se permettre de rater. */
102
+ .zv-alert--err {
103
+ border-color: var(--err-line);
104
+ background: var(--err-soft);
105
+ color: var(--err);
106
+ }
107
+
108
+ /* Succès — RELEVÉ, pleine largeur, deux fois : reset-password-card.tsx:90 et
109
+ * forgot-password-card.tsx:60, en `border-ok-line bg-ok-soft px-3 py-[10px]
110
+ * text-[13.5px]/[1.6] font-medium text-ok`. (Une version antérieure de ce
111
+ * commentaire le disait « extrapolé » : c'était faux, les deux écrans de
112
+ * réinitialisation n'avaient pas été relus. Le gabarit du relevé est celui
113
+ * de --sm, à composer avec.) */
114
+ .zv-alert--ok {
115
+ border-color: var(--ok-line);
116
+ background: var(--ok-soft);
117
+ color: var(--ok);
118
+ }
119
+
120
+ /* Variante « aveu d'ignorance » : ce que le système n'a PAS trouvé. Le
121
+ * pointillé sans fond dit qu'il n'y a rien derrière — un fond plein ferait
122
+ * lire une réponse là où il n'y en a pas. Avec la zone de dépôt, c'est le
123
+ * seul pointillé de toute la couche applicative : le garder rare, c'est ce
124
+ * qui lui laisse son sens.
125
+ *
126
+ * ⚠️ DEUX PERTES DE TEINTE, assumées et signalées plutôt que glissées :
127
+ * 1. le texte est `#6E6A61` en production (chat-client.tsx:265), un gris
128
+ * CHAUD — le seul du fil de réponse. Le paquet n'a pas de neutre chaud :
129
+ * --muted (#5a637a) est bleuté. Le rabattement est arrêté, mais il
130
+ * REFROIDIT ce texte, et il le range sous le même token que les gris
131
+ * froids du relevé (#6B7488 du pager, du bouton d'icône). Deux familles
132
+ * de teinte finissent donc sur --muted : c'est une fusion, pas une
133
+ * nuance. Le jour où la DA veut retrouver ce chaud, il faut un token
134
+ * dédié dans tokens.css — pas une valeur en dur ici.
135
+ * 2. le filet est `#C9CEDD` en production, un gris froid neutre ; --accent-line
136
+ * (#c9d6f5) est le seul filet du paquet à cette clarté, mais il est
137
+ * légèrement indigo. Écart perceptible sur un pointillé large. */
138
+ .zv-alert--dash {
139
+ border: 1px dashed var(--accent-line);
140
+ background: none;
141
+ padding: 10px 14px;
142
+ font-size: var(--app);
143
+ line-height: 1.55;
144
+ color: var(--muted);
145
+ }
146
+
147
+ /* Gabarit compact (`px-3 py-[9px]`) — relevé quatre fois : login-card:207,
148
+ * bibliotheque-client:210, branding-card:219, billing-card:164. Deux d'entre
149
+ * eux sont des alertes de PAGE, pas de panneau : ce gabarit n'est donc pas
150
+ * « le bandeau dans un cadre », c'est simplement le bandeau resserré. La
151
+ * carte de suppression (delete-org-card:39) n'a aucun bandeau — l'histoire
152
+ * qui justifiait ces valeurs dans une version antérieure était inventée, les
153
+ * valeurs, elles, sont bonnes. */
154
+ .zv-alert--sm {
155
+ padding: 9px 12px;
156
+ }
157
+
158
+ /* Gabarit ample : le message d'incident qui REMPLACE une réponse attendue
159
+ * (GPU en charge). Il occupe la place du contenu manquant, d'où le corps plus
160
+ * grand. Source chat-client.tsx:308 : `px-4 py-[14px] text-[13.5px]/[1.6]
161
+ * lg:px-5 lg:py-4` — le padding change au seuil, la TAILLE non (13,5px des
162
+ * deux côtés). D'où l'absence de font-size dans le bloc mobile. */
163
+ .zv-alert--lg {
164
+ padding: 16px 20px;
165
+ font-size: var(--app);
166
+ line-height: 1.6;
167
+ }
168
+
169
+ /* ⚠️ DEUX ARBITRAGES de couleur portés ici, pas glissés sous le tapis :
170
+ * 1. le filet vert est écrit `border-ok/30` en production (un color-mix à
171
+ * 30 %) alors que --ok-line existe dans le paquet et n'y sert jamais.
172
+ * Retenu : --ok-line. Le rendu diffère — le color-mix laissait
173
+ * transparaître --ok-soft, le token est un vert franc.
174
+ * 2. l'ambre de production est #FFF6E3 / #EBD9AE (teintes écrites en dur
175
+ * dans deux tables), le token du paquet #faf3e2 / #ebdcb4. Retenu : les
176
+ * tokens. Écart visible à l'œil sur les bandeaux et badges WARN. */
177
+
178
+ /* ─── Étiquettes d'état ─────────────────────────────────────────────────
179
+ * Rôle d'une entité, niveau d'un log, état d'un service, issue d'un envoi :
180
+ * un seul objet, trois gabarits, cinq tons. Le mono en petit corps est ce
181
+ * qui les distingue d'un texte — une étiquette se lit d'un coup d'œil, elle
182
+ * ne se lit pas en phrase.
183
+ *
184
+ * Pas de text-transform : les libellés sont écrits en capitales à la source.
185
+ * Le laisser au contenu évite qu'un libellé accentué (« INDEXÉ ») soit
186
+ * transformé différemment selon la locale du navigateur.
187
+ *
188
+ * Ne pas confondre avec .zv-pill / .zv-tag / .zv-src de components.css :
189
+ * ceux-là appartiennent à la vitrine (pastille pleine indigo, micro-label
190
+ * espacé, puce de source). Aucun nom n'est repris ici. */
191
+
192
+ .zv-badge {
193
+ display: inline-flex;
194
+ align-items: center;
195
+ gap: 7px;
196
+ /* En cellule de grille, une étiquette étirée sur toute la colonne cesse
197
+ * de se lire comme une étiquette : elle se cale à gauche sur sa largeur. */
198
+ justify-self: start;
199
+ border: 1px solid;
200
+ padding: 3px 8px;
201
+ font-family: var(--font-mono);
202
+ font-size: var(--mono); /* 10px relevé, palier exact du paquet */
203
+ font-weight: 600;
204
+ /* Un état ne se coupe jamais en deux lignes : « EN CHARGE » sur deux
205
+ * lignes se lit comme deux états. */
206
+ white-space: nowrap;
207
+ border-radius: 0; /* même arbitrage non tranché que .zv-alert, voir plus haut */
208
+ }
209
+
210
+ /* Gabarit micro : niveau de log en tête de ligne, badge de la coque. À
211
+ * cette taille la graisse monte et les lettres s'espacent, sinon les
212
+ * capitales se referment et INFO/WARN ne se distinguent plus en diagonale.
213
+ * 8,5px relevé → --mono-sm (9px), le plus petit palier du paquet. */
214
+ .zv-badge--micro {
215
+ padding: 2px 6px;
216
+ font-size: var(--mono-sm);
217
+ font-weight: 700;
218
+ letter-spacing: 0.06em;
219
+ }
220
+
221
+ /* Gabarit état de service (OK / CHARGE). ⚠️ 9,5px au relevé, rabattu sur
222
+ * --mono (10px) : il retombe donc sur la MÊME taille que .zv-badge, et ne
223
+ * s'en distingue plus que par l'interlettrage. C'est la conséquence assumée
224
+ * de l'échelle à trois paliers ; le gabarit est conservé pour garder un nom
225
+ * à cet usage, pas parce qu'il change encore de corps. */
226
+ .zv-badge--state {
227
+ padding: 3px 8px;
228
+ font-size: var(--mono);
229
+ letter-spacing: 0.06em;
230
+ }
231
+
232
+ /* Tons — triplets relevés tels quels dans les tables de teintes. */
233
+
234
+ /* Neutre : `border-line bg-hairline text-[#4A5468]` (repertoire-client:25).
235
+ * Le filet est bien --line, pas un filet adouci : une version antérieure y
236
+ * avait substitué un token intermédiaire sans le dire. #4A5468 est le neutre
237
+ * intermédiaire du paquet, --ink-2. */
238
+ .zv-badge--neutre {
239
+ background: var(--hairline);
240
+ border-color: var(--line);
241
+ color: var(--ink-2);
242
+ }
243
+ .zv-badge--accent {
244
+ background: var(--accent-soft);
245
+ border-color: var(--accent-line);
246
+ color: var(--accent);
247
+ }
248
+ .zv-badge--ok {
249
+ background: var(--ok-soft);
250
+ border-color: var(--ok-line);
251
+ color: var(--ok);
252
+ }
253
+ .zv-badge--warn {
254
+ background: var(--warn-soft);
255
+ border-color: var(--warn-line);
256
+ color: var(--warn-ink);
257
+ }
258
+ .zv-badge--err {
259
+ background: var(--err-soft);
260
+ border-color: var(--err-line);
261
+ color: var(--err);
262
+ }
263
+
264
+ /* Étiquette à pastille (« RÉSEAU PRIVÉ · IA DÉDIÉE », « TOUT
265
+ * OPÉRATIONNEL »). La pastille reprend la couleur du texte plutôt qu'un ton
266
+ * réglé à part : c'est ce que fait le relevé, et cela garantit que les deux
267
+ * ne divergent jamais quand on change de ton.
268
+ * ⚠️ Un jour où l'on posera une pastille sur .zv-badge--warn, currentColor
269
+ * donnera --warn-ink (l'ambre sombre) et non --warn : cas absent du relevé,
270
+ * à trancher avec la DA plutôt qu'à corriger ici en douce. */
271
+ .zv-badge--dot .zv-dot {
272
+ background: currentColor;
273
+ }
274
+
275
+ /* ─── Pastilles d'état ──────────────────────────────────────────────────
276
+ * Seule ou dans une étiquette. Ronde par défaut ; CARRÉE dans les bandeaux
277
+ * de provenance qui coiffent une réponse. La distinction est tenue sur cinq
278
+ * occurrences : ce n'est pas un oubli de `rounded-full`, c'est la marque
279
+ * que le point désigne une SOURCE et non un ÉTAT. Ne pas l'uniformiser. */
280
+
281
+ .zv-dot {
282
+ width: 6px;
283
+ height: 6px;
284
+ /* Sans flex: none la pastille s'écrase quand le libellé voisin est long —
285
+ * elle devient un ovale, et l'état n'est plus lisible. */
286
+ flex: none;
287
+ border-radius: 9999px;
288
+ /* Repli gris : un statut inconnu (nouvel état ajouté côté serveur, jamais
289
+ * déployé côté interface) doit rester neutre. Le teindre en vert par
290
+ * défaut ferait passer un échec pour un succès. */
291
+ background: var(--label);
292
+ }
293
+
294
+ /* Pastille de provenance : carrée. Seule dérogation au rond de tout le
295
+ * module. */
296
+ .zv-dot--square {
297
+ border-radius: 0;
298
+ }
299
+
300
+ /* Dans une étiquette de la coque ou une garantie : plus discrète. */
301
+ .zv-dot--sm {
302
+ width: 5px;
303
+ height: 5px;
304
+ }
305
+
306
+ /* Devant un libellé de service ou une phrase de travail en cours : la
307
+ * pastille porte seule l'information, elle grossit d'un pixel. */
308
+ .zv-dot--lg {
309
+ width: 7px;
310
+ height: 7px;
311
+ }
312
+
313
+ /* Correspondance statut → couleur, relevée telle quelle. Elle vit en CSS et
314
+ * pas dans chaque app : c'est la seule façon que « reçu » et « OCR en
315
+ * cours » restent du même ambre partout. */
316
+ .zv-dot--recu,
317
+ .zv-dot--ocr {
318
+ background: var(--warn);
319
+ }
320
+ .zv-dot--indexation {
321
+ background: var(--accent);
322
+ }
323
+ .zv-dot--indexe,
324
+ .zv-dot--ok {
325
+ background: var(--ok);
326
+ }
327
+ .zv-dot--erreur {
328
+ background: var(--err);
329
+ }
330
+
331
+ /* ─── Statut en clair ───────────────────────────────────────────────────
332
+ * Pastille + libellé, sans cadre — contrairement à l'étiquette. Le cadre
333
+ * est réservé à ce qui classe (rôle, niveau) ; le statut, lui, se lit dans
334
+ * le fil de la ligne. Le message d'erreur REMPLACE le libellé quand il y en
335
+ * a un : l'utilisateur veut lire la cause, pas le mot « Erreur ».
336
+ *
337
+ * Source : bibliotheque-client.tsx:259, `flex min-w-0 items-center
338
+ * gap-[6px] text-[11.5px] font-medium text-[#3A4256] lg:min-w-[auto]`, avec
339
+ * un `<span className="truncate">` à l'intérieur et AUCUNE variante `lg:`
340
+ * sur ce span. Autrement dit : le libellé tronque, et il tronque SOUS le
341
+ * seuil, là où min-width vaut 0 ; au-dessus, la colonne reprend sa largeur
342
+ * naturelle. Le fichier est desktop-first, donc `min-width: auto` est ici et
343
+ * `min-width: 0` dans le bloc mobile — l'inverse exact de ce qu'une version
344
+ * antérieure de ce module posait, qui faisait passer le libellé à la ligne
345
+ * sur téléphone et décalait la pastille de 5px vers le bas. Rien de tout
346
+ * cela n'existe en production. */
347
+
348
+ .zv-status {
349
+ display: flex;
350
+ align-items: center;
351
+ gap: 6px;
352
+ /* Valeur initiale, écrite pour être lue : au-dessus du seuil la colonne
353
+ * n'est pas contrainte, le libellé la pousse — c'est le `lg:min-w-[auto]`
354
+ * du relevé. La troncature n'opère réellement que sous le seuil. */
355
+ min-width: auto;
356
+ font-size: var(--app-sm); /* 11,5px relevé → palier 12 */
357
+ font-weight: 500;
358
+ color: var(--ink-2); /* #3A4256 relevé → neutre intermédiaire du paquet */
359
+ }
360
+
361
+ /* Le libellé tronque des DEUX côtés du seuil : la classe `truncate` de la
362
+ * source n'a pas de variante responsive. */
363
+ .zv-status__label {
364
+ overflow: hidden;
365
+ text-overflow: ellipsis;
366
+ white-space: nowrap;
367
+ }
368
+
369
+ /* ─── Absence ───────────────────────────────────────────────────────────
370
+ * DEUX objets distincts, à ne surtout pas fusionner :
371
+ * — la ligne d'absence, quand un filtre ne ramène rien : la collection
372
+ * existe, seul le filtre est trop étroit. Elle vit DANS le cadre, à la
373
+ * place des lignes, pour que l'en-tête de tableau reste visible ;
374
+ * — l'écran d'absence, quand la collection est vide : il remplace le
375
+ * tableau entier, porte un titre et l'action qui la remplira.
376
+ * Les confondre donne soit un écran de bienvenue affiché à chaque filtre
377
+ * trop strict, soit une collection vide qui n'offre aucune porte de sortie. */
378
+
379
+ /* ⚠️ Les quatre lignes d'absence relevées ne s'accordent pas : `p-[26px]`
380
+ * (repertoire-client:159), `px-[14px] py-6` (bibliotheque-client:230),
381
+ * `px-[18px] py-8` (import-client:263), `py-5` seul (logs-client:234).
382
+ * Retenue : la seule qui soit écrite telle quelle, 26px sur les quatre
383
+ * côtés. Une version antérieure posait « 26px 14px », c'est-à-dire le
384
+ * vertical de la première recomposé avec l'horizontal de la deuxième — une
385
+ * paire plausible qui n'existe dans aucune source. */
386
+ .zv-empty {
387
+ padding: 26px;
388
+ text-align: center;
389
+ font-size: var(--app-sm);
390
+ color: var(--muted);
391
+ }
392
+
393
+ .zv-empty--page {
394
+ border: 1px solid var(--line);
395
+ background: var(--white);
396
+ padding: 48px 32px;
397
+ text-align: center;
398
+ }
399
+
400
+ /* --app-h2 (20px) est le palier que tokens.css destine explicitement au
401
+ * « titre de fiche, d'état vide, d'interstice ». */
402
+ .zv-empty__title {
403
+ /* Posé explicitement : sur un <span>, le titre viendrait se coller au
404
+ * texte qui le suit au lieu de le surmonter, et l'état vide se lirait
405
+ * comme une seule phrase. */
406
+ display: block;
407
+ font-family: var(--font-display);
408
+ font-size: var(--app-h2);
409
+ font-weight: 300;
410
+ color: var(--ink);
411
+ }
412
+
413
+ .zv-empty__text {
414
+ /* La mesure bornée n'est pas décorative : centré et pleine largeur, ce
415
+ * paragraphe devient illisible dès 900px de cadre. */
416
+ margin: 8px auto 0;
417
+ max-width: 420px;
418
+ font-size: var(--app);
419
+ line-height: 1.6;
420
+ color: var(--muted);
421
+ }
422
+
423
+ /* L'action de l'écran vide (le bouton qui remplit la collection). Elle est
424
+ * ici pour l'écart seul : le bouton lui-même vient de components.css — et
425
+ * porte donc --r-btn, voir la contradiction signalée sur .zv-alert. */
426
+ .zv-empty__action {
427
+ margin-top: 20px;
428
+ }
429
+
430
+ /* Ni icône, ni illustration, ni « réinitialiser les filtres » : rien de tel
431
+ * n'existe dans les sources, et ce sont exactement les trois ajouts qu'un
432
+ * état vide attire. */
433
+
434
+ /* ─── Travail en cours ──────────────────────────────────────────────────
435
+ * En production ce n'est PAS une roue : c'est une pastille suivie d'une
436
+ * phrase qui dit le travail réel (« Recherche dans 4 812 documents
437
+ * anonymisés… »). Une roue dit « attendez » ; cette phrase dit ce qu'on
438
+ * attend, sur combien, et qu'il ne se passe rien d'anormal. */
439
+
440
+ .zv-loading {
441
+ display: flex;
442
+ align-items: center;
443
+ gap: 9px;
444
+ font-family: var(--font-mono);
445
+ font-size: var(--app-sm);
446
+ font-weight: 500;
447
+ /* #727C93 au relevé — un gris froid, rabattu sur --muted comme les autres
448
+ * neutres secondaires. Le token porte donc plusieurs gris du relevé : la
449
+ * hiérarchie interne des gris de Mémoire (#6B7488 / #727C93) n'existe plus
450
+ * dans le paquet, elle échouait de toute façon au seuil AA. */
451
+ color: var(--muted);
452
+ }
453
+
454
+ .zv-loading .zv-dot {
455
+ width: 7px;
456
+ height: 7px;
457
+ background: var(--accent);
458
+ }
459
+
460
+ /* ⚠️ STATIQUE, et c'est un choix. Aucune animation dans la source : ni
461
+ * @keyframes, ni classe d'animation, la pastille ne bat pas. Lui ajouter un
462
+ * pouls est l'invention la plus tentante de tout le relevé — et les
463
+ * @keyframes du paquet (motion.css) sont réservées à la vitrine, pilotées au
464
+ * défilement. Ne pas confondre `.zv-dot` avec `.zv-anim-dot-active` de
465
+ * motion.css : ce dernier anime un point de méthode, pas un état. */
466
+
467
+ /* ─── Curseur de flux ───────────────────────────────────────────────────
468
+ * Bloc collé à la fin du texte en cours de diffusion, sur le dernier
469
+ * message seulement. Il dit « la réponse n'est pas finie » — sans lui, une
470
+ * réponse coupée à mi-phrase se lit comme une réponse complète et fausse. */
471
+
472
+ .zv-caret {
473
+ margin-left: 2px;
474
+ color: var(--accent);
475
+ }
476
+
477
+ /* Le glyphe ▍ (U+258D) est un CARACTÈRE posé dans le texte, pas un
478
+ * pseudo-élément : il suit la dernière lettre où qu'elle tombe, y compris
479
+ * en fin de ligne justifiée. STATIQUE là encore, aucun clignotement.
480
+ *
481
+ * Le flux interrompu, lui, n'a AUCUN style : c'est la phrase
482
+ * « [Flux interrompu — la réponse est incomplète.] » ajoutée au corps du
483
+ * message, rendue par white-space: pre-wrap. La réponse partielle est
484
+ * conservée, jamais effacée — on n'efface pas ce que l'utilisateur a déjà lu. */
485
+
486
+ /* ─── Retour d'attente : ce que fait Mémoire, et ce qu'elle ne fait pas ──
487
+ * Le retour d'attente de cette application est TEXTUEL, et c'est cohérent
488
+ * avec .zv-loading ci-dessus : le libellé du bouton change pendant l'action
489
+ * (`{busy === d.id ? 'Suppression…' : 'Supprimer'}`, bibliotheque-client:283 ;
490
+ * `{pending ? 'Connexion…' : 'Se connecter'}` au login), en plus d'un
491
+ * `disabled:cursor-wait`. Ce n'est pas du CSS : c'est un motif de balisage à
492
+ * reprendre, et il ne demande aucune classe.
493
+ *
494
+ * Restent DEUX actions sans aucun retour : la déconnexion et l'envoi de
495
+ * message (le bouton reste plein et cliquable en apparence alors que la
496
+ * garde existe côté script). C'est un défaut de l'application, pas une
497
+ * intention de charte : ne pas le répliquer dans une app neuve.
498
+ *
499
+ * ─── Manques assumés : roue, squelette, infobulle ───────────────────────
500
+ * AUCUNE MATIÈRE PREMIÈRE dans les cinq écrans relevés. Ni .zv-spinner, ni
501
+ * .zv-skeleton, ni .zv-tooltip ne sont écrits ici : les livrer supposerait
502
+ * d'inventer une durée, une amplitude et une géométrie que personne n'a
503
+ * arbitrées, et un système ment moins en affichant un trou qu'une valeur
504
+ * plausible. Rien n'appelle d'infobulle et rien n'en propose : le nom de
505
+ * document tronqué n'a même pas d'attribut title, les barres des graphiques
506
+ * n'affichent aucune valeur au pointeur.
507
+ *
508
+ * Contraintes à respecter le jour où la DA les tranche : pas de dégradé
509
+ * spectre (réservé au filet de section, à la barre de progression et aux
510
+ * points de méthode) — donc un squelette en --hairline, pas un balayage
511
+ * spectral ; et une infobulle en petit panneau blanc, filet --line, ombre
512
+ * --shadow-menu, texte --app-sm / --ink-2, qui est la seule combinaison déjà
513
+ * présente ailleurs dans le système. */
514
+
515
+ /* ─── Mobile ────────────────────────────────────────────────────────────
516
+ * Un seul point de rupture, 768px, comme dans tous les modules du paquet.
517
+ * Ce bloc ne contient QUE des bascules attestées dans la source (variantes
518
+ * `lg:` de Mémoire, lues en sens inverse) — pas une seule règle de confort
519
+ * ajoutée en chemin. */
520
+
521
+ @media (max-width: 767px) {
522
+ /* Les cinq bandeaux d'authentification portent `text-[13.5px]
523
+ * lg:text-[12.5px]` (login-card:207, reset-password-card:78 et :137,
524
+ * forgot-password-card:60 et :96) : le texte GROSSIT d'un palier sous le
525
+ * seuil. C'est le motif le mieux attesté du module ; l'oublier laissait le
526
+ * bandeau à sa plus petite taille précisément sur l'écran le plus étroit. */
527
+ .zv-alert {
528
+ font-size: var(--app);
529
+ }
530
+
531
+ /* Le bandeau pointillé fait l'inverse — `text-[13px]` en desktop,
532
+ * `text-[12.5px]` sous le seuil — et resserre sa gouttière : 16px de
533
+ * chaque côté sur 375px déjà bordés par le padding de page, c'est la
534
+ * moitié de la largeur perdue en marges. Déclaré APRÈS .zv-alert pour
535
+ * l'emporter à spécificité égale. */
536
+ .zv-alert--dash {
537
+ padding: 10px 12px;
538
+ font-size: var(--app-sm);
539
+ }
540
+
541
+ /* Le message d'incident resserre son padding (`px-4 py-[14px]`) et GARDE
542
+ * sa taille : la source n'a pas de variante `lg:` sur le corps. Redéclaré
543
+ * ici uniquement pour n'être pas rétréci par la règle .zv-alert ci-dessus,
544
+ * à spécificité égale. */
545
+ .zv-alert--lg {
546
+ padding: 14px 16px;
547
+ font-size: var(--app);
548
+ }
549
+
550
+ /* C'est ici, et seulement ici, que la troncature du statut opère : le
551
+ * `min-w-0` de la source vaut sous le seuil, et sans lui l'ellipse ne se
552
+ * déclenche pas dans une cellule flex ou grid — le libellé pousse la
553
+ * colonne. C'est la cause n° 1 de débordement horizontal des tableaux. */
554
+ .zv-status {
555
+ min-width: 0;
556
+ }
557
+
558
+ /* ⚠️ NON RELEVÉ : aucune variante `lg:` sur le padding de l'écran vide.
559
+ * Ce resserrement est une décision de portage, prise parce que 32px de
560
+ * gouttière sur 375px laissent 311px de contenu à un bloc qui porte un
561
+ * titre et un bouton. Si la DA la refuse, c'est cette règle qui saute —
562
+ * pas le reste du bloc. */
563
+ .zv-empty--page {
564
+ padding: 40px 20px;
565
+ }
566
+ }