@starterlib/tokens 0.8.0 → 0.9.1

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.
@@ -1,256 +1,598 @@
1
- /**
2
- * Niveau 1 — primitives. Palette brute, sans intention d'usage.
3
- * Aucun composant ne consomme ce niveau directement : il passe par le
4
- * niveau semantique, sinon un changement de nuance se propage a l'aveugle.
5
- */
1
+ const DEGRES_EN_RADIANS = Math.PI / 180;
2
+ function versSrgbLineaire({ l, c, h }) {
3
+ const a = c * Math.cos(h * DEGRES_EN_RADIANS);
4
+ const b = c * Math.sin(h * DEGRES_EN_RADIANS);
5
+ const l_ = l + .3963377774 * a + .2158037573 * b;
6
+ const m_ = l - .1055613458 * a - .0638541728 * b;
7
+ const s_ = l - .0894841775 * a - 1.291485548 * b;
8
+ const lc = l_ ** 3;
9
+ const mc = m_ ** 3;
10
+ const sc = s_ ** 3;
11
+ return [
12
+ 4.0767416621 * lc - 3.3077115913 * mc + .2309699292 * sc,
13
+ -1.2684380046 * lc + 2.6097574011 * mc - .3413193965 * sc,
14
+ -.0041960863 * lc - .7034186147 * mc + 1.707614701 * sc
15
+ ];
16
+ }
17
+ function dansLeGamut(lineaire) {
18
+ return lineaire.every((v) => v >= -1e-7 && v <= 1.0000001);
19
+ }
20
+ function encoderGamma(lineaire) {
21
+ const v = Math.min(1, Math.max(0, lineaire));
22
+ return v <= .0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - .055;
23
+ }
24
+ function decoderGamma(canal8bits) {
25
+ const c = canal8bits / 255;
26
+ return c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
27
+ }
28
+ function enHex$2(lineaire) {
29
+ return `#${lineaire.map((v) => Math.round(encoderGamma(v) * 255).toString(16).padStart(2, "0")).join("")}`;
30
+ }
31
+ function oklchVersSrgb(couleur) {
32
+ if (!Number.isFinite(couleur.l) || !Number.isFinite(couleur.c) || !Number.isFinite(couleur.h)) throw new Error(`Couleur OKLCH invalide : ${JSON.stringify(couleur)}`);
33
+ const demandee = versSrgbLineaire(couleur);
34
+ if (dansLeGamut(demandee)) return {
35
+ hex: enHex$2(demandee),
36
+ rabattue: false,
37
+ chroma: couleur.c
38
+ };
39
+ let dedans = 0;
40
+ let dehors = couleur.c;
41
+ for (let pas = 0; pas < 40; pas++) {
42
+ const milieu = (dedans + dehors) / 2;
43
+ if (dansLeGamut(versSrgbLineaire({
44
+ ...couleur,
45
+ c: milieu
46
+ }))) dedans = milieu;
47
+ else dehors = milieu;
48
+ }
49
+ return {
50
+ hex: enHex$2(versSrgbLineaire({
51
+ ...couleur,
52
+ c: dedans
53
+ })),
54
+ rabattue: true,
55
+ chroma: dedans
56
+ };
57
+ }
58
+ function oklchVersHex(couleur) {
59
+ return oklchVersSrgb(couleur).hex;
60
+ }
61
+ function hexVersOklch(hex) {
62
+ const v = hex.trim().replace(/^#/, "");
63
+ const complet = v.length === 3 ? v.split("").map((c) => c + c).join("") : v;
64
+ if (!/^[0-9a-fA-F]{6}$/.test(complet)) throw new Error(`Couleur hexadecimale invalide : ${hex}`);
65
+ const [r, g, b] = [
66
+ 0,
67
+ 2,
68
+ 4
69
+ ].map((i) => decoderGamma(parseInt(complet.slice(i, i + 2), 16)));
70
+ const l_ = Math.cbrt(.4122214708 * r + .5363325363 * g + .0514459929 * b);
71
+ const m_ = Math.cbrt(.2119034982 * r + .6806995451 * g + .1073969566 * b);
72
+ const s_ = Math.cbrt(.0883024619 * r + .2817188376 * g + .6299787005 * b);
73
+ const l = .2104542553 * l_ + .793617785 * m_ - .0040720468 * s_;
74
+ const a = 1.9779984951 * l_ - 2.428592205 * m_ + .4505937099 * s_;
75
+ const bb = .0259040371 * l_ + .7827717662 * m_ - .808675766 * s_;
76
+ const c = Math.hypot(a, bb);
77
+ return {
78
+ l,
79
+ c,
80
+ h: c < 1e-4 ? 0 : (Math.atan2(bb, a) / DEGRES_EN_RADIANS + 360) % 360
81
+ };
82
+ }
83
+ function oklchEnCss({ l, c, h }) {
84
+ const arrondir = (v, chiffres) => Number(v.toFixed(chiffres)).toString();
85
+ return `oklch(${arrondir(l, 3)} ${arrondir(c, 3)} ${arrondir(h, 0)})`;
86
+ }
87
+ const PAS_DE_RAMPE = [
88
+ 50,
89
+ 100,
90
+ 200,
91
+ 300,
92
+ 400,
93
+ 500,
94
+ 600,
95
+ 700,
96
+ 800,
97
+ 900,
98
+ 950
99
+ ];
100
+ const PAS_NEUTRES = [
101
+ 0,
102
+ 50,
103
+ 100,
104
+ 150,
105
+ 200,
106
+ 300,
107
+ 400,
108
+ 500,
109
+ 600,
110
+ 700,
111
+ 800,
112
+ 900,
113
+ 950
114
+ ];
115
+ const RANGS_DE_SERIE = [
116
+ 1,
117
+ 2,
118
+ 3,
119
+ 4,
120
+ 5,
121
+ 6
122
+ ];
123
+ const L_PAS = {
124
+ 50: .97,
125
+ 100: .935,
126
+ 200: .88,
127
+ 300: .8,
128
+ 400: .7,
129
+ 500: .6,
130
+ 600: .5,
131
+ 700: .43,
132
+ 800: .36,
133
+ 900: .28,
134
+ 950: .2
135
+ };
136
+ const C_PAS = {
137
+ 50: .07,
138
+ 100: .15,
139
+ 200: .29,
140
+ 300: .5,
141
+ 400: .77,
142
+ 500: .95,
143
+ 600: 1,
144
+ 700: .88,
145
+ 800: .74,
146
+ 900: .58,
147
+ 950: .38
148
+ };
149
+ const FAMILLES = {
150
+ bleu: {
151
+ teinte: 262,
152
+ chromaMax: .18,
153
+ vocation: "marque, focus, selection"
154
+ },
155
+ azur: {
156
+ teinte: 232,
157
+ chromaMax: .095,
158
+ vocation: "information"
159
+ },
160
+ or: {
161
+ teinte: 85,
162
+ chromaMax: .098,
163
+ vocation: "accent (jamais un statut, jamais une action)"
164
+ },
165
+ vert: {
166
+ teinte: 152,
167
+ chromaMax: .125,
168
+ vocation: "succes et variation positive"
169
+ },
170
+ ambre: {
171
+ teinte: 58,
172
+ chromaMax: .115,
173
+ exception: { 600: .52 },
174
+ vocation: "avertissement"
175
+ },
176
+ rouge: {
177
+ teinte: 25,
178
+ chromaMax: .19,
179
+ vocation: "danger, erreur, action destructrice"
180
+ }
181
+ };
182
+ const NEUTRES = {
183
+ 0: {
184
+ l: 1,
185
+ c: 0,
186
+ h: 0
187
+ },
188
+ 50: {
189
+ l: .98,
190
+ c: .003,
191
+ h: 255
192
+ },
193
+ 100: {
194
+ l: .965,
195
+ c: .004,
196
+ h: 255
197
+ },
198
+ 150: {
199
+ l: .94,
200
+ c: .005,
201
+ h: 255
202
+ },
203
+ 200: {
204
+ l: .888,
205
+ c: .007,
206
+ h: 255
207
+ },
208
+ 300: {
209
+ l: .81,
210
+ c: .009,
211
+ h: 255
212
+ },
213
+ 400: {
214
+ l: .71,
215
+ c: .011,
216
+ h: 255
217
+ },
218
+ 500: {
219
+ l: .61,
220
+ c: .013,
221
+ h: 255
222
+ },
223
+ 600: {
224
+ l: .51,
225
+ c: .015,
226
+ h: 255
227
+ },
228
+ 700: {
229
+ l: .45,
230
+ c: .016,
231
+ h: 255
232
+ },
233
+ 800: {
234
+ l: .31,
235
+ c: .017,
236
+ h: 255
237
+ },
238
+ 900: {
239
+ l: .22,
240
+ c: .018,
241
+ h: 255
242
+ },
243
+ 950: {
244
+ l: .155,
245
+ c: .018,
246
+ h: 255
247
+ }
248
+ };
249
+ const SERIE = {
250
+ 1: {
251
+ l: .55,
252
+ c: .15,
253
+ h: 262
254
+ },
255
+ 2: {
256
+ l: .55,
257
+ c: .13,
258
+ h: 85
259
+ },
260
+ 3: {
261
+ l: .55,
262
+ c: .12,
263
+ h: 195
264
+ },
265
+ 4: {
266
+ l: .55,
267
+ c: .15,
268
+ h: 310
269
+ },
270
+ 5: {
271
+ l: .55,
272
+ c: .14,
273
+ h: 152
274
+ },
275
+ 6: {
276
+ l: .55,
277
+ c: .16,
278
+ h: 25
279
+ }
280
+ };
281
+ function definir(oklch) {
282
+ const { hex, rabattue } = oklchVersSrgb(oklch);
283
+ return {
284
+ oklch,
285
+ hex,
286
+ rabattue
287
+ };
288
+ }
289
+ function definitionDuPas(famille, pas) {
290
+ const f = FAMILLES[famille];
291
+ return {
292
+ l: f.exception?.[pas] ?? L_PAS[pas],
293
+ c: Number((f.chromaMax * C_PAS[pas]).toFixed(6)),
294
+ h: f.teinte
295
+ };
296
+ }
297
+ function engendrer(cles, definition) {
298
+ return Object.fromEntries(cles.map((cle) => [cle, definir(definition(cle))]));
299
+ }
300
+ function rampe(famille) {
301
+ return engendrer(PAS_DE_RAMPE, (pas) => definitionDuPas(famille, pas));
302
+ }
303
+ const definitionsDesPrimitives = {
304
+ neutre: engendrer(PAS_NEUTRES, (pas) => NEUTRES[pas]),
305
+ bleu: rampe("bleu"),
306
+ azur: rampe("azur"),
307
+ or: rampe("or"),
308
+ vert: rampe("vert"),
309
+ ambre: rampe("ambre"),
310
+ rouge: rampe("rouge"),
311
+ serie: engendrer(RANGS_DE_SERIE, (rang) => SERIE[rang])
312
+ };
313
+ function enHex$1(rampeDefinie) {
314
+ return Object.fromEntries(Object.entries(rampeDefinie).map(([cle, d]) => [cle, d.hex]));
315
+ }
6
316
  const primitives = {
7
- neutre: {
8
- 0: '#ffffff',
9
- 50: '#f7f8fa',
10
- 100: '#eef1f5',
11
- 200: '#dde2e9',
12
- 300: '#c3ccd7',
13
- 400: '#94a1b2',
14
- 500: '#6b7889',
15
- 600: '#4d5868',
16
- 700: '#39414e',
17
- 800: '#232a34',
18
- 900: '#141920',
19
- 950: '#0b0e13',
20
- },
21
- bleu: {
22
- 50: '#eff5ff',
23
- 100: '#dbe8fe',
24
- 200: '#bfd6fe',
25
- 300: '#93bbfd',
26
- 400: '#609afa',
27
- 500: '#3b7df6',
28
- 600: '#255eeb',
29
- 700: '#1d4ed8',
30
- 800: '#1e40af',
31
- 900: '#1e3a8a',
32
- },
33
- vert: {
34
- 100: '#d7f5e3',
35
- 300: '#7fd6a4',
36
- 500: '#1a9d54',
37
- 600: '#15803d',
38
- 700: '#116932',
39
- 900: '#0a3d1e',
40
- },
41
- ambre: {
42
- 100: '#fdf0d5',
43
- 300: '#f3c969',
44
- 500: '#b4730b',
45
- 600: '#a16207',
46
- 700: '#854d0e',
47
- 900: '#432a06',
48
- },
49
- rouge: {
50
- 100: '#fde2e2',
51
- 300: '#f3a3a3',
52
- 500: '#d92d2d',
53
- 600: '#b91c1c',
54
- 700: '#991b1b',
55
- 900: '#4c1010',
56
- },
57
- };
58
-
59
- /**
60
- * Echelles non chromatiques. Elles ne changent pas entre le theme clair et le
61
- * theme sombre : une distance ni une graisse ne dependent de la luminosite.
62
- */
63
- /** Pas de 4 px. Une valeur hors echelle est un defaut de conception. */
317
+ neutre: enHex$1(definitionsDesPrimitives.neutre),
318
+ bleu: enHex$1(definitionsDesPrimitives.bleu),
319
+ azur: enHex$1(definitionsDesPrimitives.azur),
320
+ or: enHex$1(definitionsDesPrimitives.or),
321
+ vert: enHex$1(definitionsDesPrimitives.vert),
322
+ ambre: enHex$1(definitionsDesPrimitives.ambre),
323
+ rouge: enHex$1(definitionsDesPrimitives.rouge),
324
+ serie: enHex$1(definitionsDesPrimitives.serie)
325
+ };
64
326
  const espacement = {
65
- '0': '0',
66
- '1': '0.25rem',
67
- '2': '0.5rem',
68
- '3': '0.75rem',
69
- '4': '1rem',
70
- '5': '1.25rem',
71
- '6': '1.5rem',
72
- '8': '2rem',
73
- '10': '2.5rem',
74
- '12': '3rem',
75
- '16': '4rem',
76
- '20': '5rem',
77
- '24': '6rem',
327
+ "0": "0",
328
+ "1": "0.25rem",
329
+ "2": "0.5rem",
330
+ "3": "0.75rem",
331
+ "4": "1rem",
332
+ "5": "1.25rem",
333
+ "6": "1.5rem",
334
+ "8": "2rem",
335
+ "10": "2.5rem",
336
+ "12": "3rem",
337
+ "16": "4rem",
338
+ "20": "5rem",
339
+ "24": "6rem",
340
+ demi: "0.125rem",
341
+ "1-5": "0.375rem",
342
+ "2-5": "0.625rem",
343
+ "3-5": "0.875rem"
78
344
  };
79
345
  const rayon = {
80
- none: '0',
81
- xs: '0.125rem',
82
- sm: '0.25rem',
83
- md: '0.5rem',
84
- lg: '0.75rem',
85
- xl: '1rem',
86
- pleine: '9999px',
346
+ none: "0",
347
+ xs: "0.125rem",
348
+ sm: "0.25rem",
349
+ md: "0.5rem",
350
+ lg: "0.75rem",
351
+ xl: "1rem",
352
+ pleine: "9999px"
87
353
  };
354
+ const CLES_DE_TAILLE = [
355
+ "2xs",
356
+ "xs",
357
+ "sm",
358
+ "md",
359
+ "lg",
360
+ "xl",
361
+ "2xl",
362
+ "3xl",
363
+ "4xl",
364
+ "5xl",
365
+ "6xl"
366
+ ];
88
367
  const typographie = {
89
- famille: {
90
- base: "system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
91
- mono: "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace",
92
- },
93
- taille: {
94
- xs: '0.75rem',
95
- sm: '0.875rem',
96
- md: '1rem',
97
- lg: '1.125rem',
98
- xl: '1.25rem',
99
- '2xl': '1.5rem',
100
- '3xl': '1.875rem',
101
- '4xl': '2.25rem',
102
- },
103
- graisse: { normale: '400', moyenne: '500', semi: '600', grasse: '700' },
104
- interligne: { serre: '1.25', normal: '1.5', large: '1.75' },
105
- };
106
- /**
107
- * Les ombres restent discretes : une elevation forte sur fond sombre se lit
108
- * comme une salissure, jamais comme une hauteur.
109
- */
110
- const ombre = {
111
- none: 'none',
112
- sm: '0 1px 2px 0 rgb(0 0 0 / 0.06)',
113
- md: '0 2px 6px -1px rgb(0 0 0 / 0.10), 0 1px 3px -1px rgb(0 0 0 / 0.06)',
114
- lg: '0 8px 20px -4px rgb(0 0 0 / 0.12), 0 3px 8px -3px rgb(0 0 0 / 0.07)',
115
- xl: '0 20px 40px -8px rgb(0 0 0 / 0.16)',
116
- };
117
- /**
118
- * Durees courtes : au-dela de 250 ms une interface bancaire parait lente.
119
- * Toute animation doit se desactiver sous `prefers-reduced-motion`.
120
- */
368
+ famille: {
369
+ base: "'Inter Variable', 'Inter Repli', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
370
+ mono: "'JetBrains Mono Variable', 'JetBrains Mono Repli', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace"
371
+ },
372
+ taille: {
373
+ "2xs": "0.6875rem",
374
+ xs: "0.75rem",
375
+ sm: "0.875rem",
376
+ md: "1rem",
377
+ lg: "1.125rem",
378
+ xl: "1.25rem",
379
+ "2xl": "1.5rem",
380
+ "3xl": "2rem",
381
+ "4xl": "2.5rem",
382
+ "5xl": "3rem",
383
+ "6xl": "3.5rem"
384
+ },
385
+ interligne: {
386
+ "2xs": "0.875rem",
387
+ xs: "1rem",
388
+ sm: "1.25rem",
389
+ md: "1.5rem",
390
+ lg: "1.75rem",
391
+ xl: "1.75rem",
392
+ "2xl": "2rem",
393
+ "3xl": "2.5rem",
394
+ "4xl": "3rem",
395
+ "5xl": "3.5rem",
396
+ "6xl": "4rem"
397
+ },
398
+ graisse: {
399
+ normale: "400",
400
+ moyenne: "500",
401
+ semi: "600",
402
+ grasse: "700"
403
+ },
404
+ interlettrage: {
405
+ serre: "-0.01em",
406
+ normal: "normal",
407
+ large: "0.02em",
408
+ capitales: "0.05em",
409
+ etiquette: "0.04em",
410
+ surtitre: "0.1em"
411
+ },
412
+ approche: {
413
+ lg: "-0.005em",
414
+ xl: "-0.01em",
415
+ "2xl": "-0.015em",
416
+ "3xl": "-0.02em",
417
+ "4xl": "-0.025em",
418
+ "6xl": "-0.03em"
419
+ },
420
+ chiffres: {
421
+ alignes: "tabular-nums lining-nums",
422
+ references: "tabular-nums lining-nums slashed-zero"
423
+ }
424
+ };
121
425
  const duree = {
122
- instantane: '0ms',
123
- rapide: '120ms',
124
- normale: '200ms',
125
- lente: '320ms',
126
- };
127
- /**
128
- * Rythme d'une attente qui dure : respiration d'un squelette sous mouvement
129
- * reduit, va-et-vient d'une progression indeterminee.
130
- *
131
- * ⚠️ **Hors de l'echelle des durees, et c'est le point.** `--st-duree-*` passe a
132
- * zero sous `prefers-reduced-motion` : un indicateur d'activite cable dessus se
133
- * figerait, et l'ecran passerait pour bloque. Les feuilles le lisent par
134
- * `var(--st-anim-respiration, …)`, avec un repli litteral — une application peut
135
- * ralentir la respiration, jamais l'eteindre.
136
- */
137
- const respiration = '1600ms';
426
+ instantane: "0ms",
427
+ rapide: "120ms",
428
+ normale: "200ms",
429
+ lente: "320ms",
430
+ longue: "480ms"
431
+ };
432
+ const deplacement = {
433
+ court: "0.25rem",
434
+ moyen: "0.5rem",
435
+ long: "1rem"
436
+ };
437
+ const cadence = {
438
+ vive: "900ms",
439
+ normale: "1400ms",
440
+ respiration: "1600ms"
441
+ };
442
+ const cadenceMinimale = "600ms";
138
443
  const courbe = {
139
- standard: 'cubic-bezier(0.2, 0, 0, 1)',
140
- entrant: 'cubic-bezier(0, 0, 0, 1)',
141
- sortant: 'cubic-bezier(0.3, 0, 1, 1)',
142
- };
143
- /**
144
- * Plans d'empilement nommes. Un `z-index` ecrit a la main dans un composant
145
- * est un defaut : il finira par passer devant ou derriere sans qu'on sache
146
- * pourquoi.
147
- */
444
+ standard: "cubic-bezier(0.2, 0, 0, 1)",
445
+ entrant: "cubic-bezier(0, 0, 0, 1)",
446
+ sortant: "cubic-bezier(0.3, 0, 1, 1)"
447
+ };
148
448
  const plan = {
149
- base: '0',
150
- contenuEleve: '10',
151
- entete: '100',
152
- tiroir: '200',
153
- superposition: '300',
154
- dialogue: '400',
155
- infobulle: '500',
156
- notification: '600',
157
- };
158
- /** Points de rupture. Mobile d'abord : ces valeurs sont des minimums. */
449
+ base: "0",
450
+ contenuEleve: "10",
451
+ entete: "100",
452
+ superposition: "300",
453
+ notification: "600",
454
+ evitement: "700"
455
+ };
159
456
  const rupture = {
160
- sm: '480px',
161
- md: '768px',
162
- lg: '1024px',
163
- xl: '1280px',
164
- '2xl': '1536px',
165
- };
166
- /** Cible tactile minimale. Sous 44 px, l'echec de pointage devient courant. */
167
- const cibleTactileMinimale = '44px';
168
- /**
169
- * Cible de pointage minimale (WCAG 2.5.8, niveau AA). C'est un PLANCHER, pas un
170
- * confort : la cible tactile reste la regle pour ce qui se touche.
171
- */
172
- const cibleMinimale = '24px';
173
- /**
174
- * Epaisseurs de trait. Trois suffisent : un filet qui separe, un accent qui
175
- * signale, un marqueur qui designe. Une quatrieme valeur serait un arbitraire de
176
- * plus a tenir.
177
- */
457
+ sm: "480px",
458
+ md: "768px",
459
+ lg: "1024px",
460
+ xl: "1280px",
461
+ "2xl": "1536px"
462
+ };
463
+ const cibleTactileMinimale = "44px";
464
+ const cibleMinimale = "24px";
178
465
  const epaisseur = {
179
- fine: '1px',
180
- moyenne: '2px',
181
- forte: '3px',
182
- };
183
- /**
184
- * Largeur de lecture. Au-dela de soixante-quinze caracteres, l'oeil perd le
185
- * debut de la ligne suivante ; en deca de quarante-cinq, il saute trop souvent.
186
- */
187
- const mesure = {
188
- etroite: '45ch',
189
- normale: '70ch',
190
- };
191
-
192
- /**
193
- * ⚠️ 16 px par rem, et non la taille de police de `html` : une requete de media
194
- * se resout contre la taille PAR DEFAUT du navigateur, celle que la personne
195
- * regle dans ses preferences, jamais contre une valeur posee par une feuille.
196
- * Une personne qui lit a 20 px voit donc la rupture `lg` a 1280 px reels : la
197
- * mise en page suit son choix, la ou des px l'ignoreraient.
198
- */
466
+ fine: "1px",
467
+ intermediaire: "1.5px",
468
+ moyenne: "2px",
469
+ forte: "3px",
470
+ epaisse: "4px"
471
+ };
472
+ const epaisseurCapillaire = {
473
+ base: "1px",
474
+ hauteDensite: "0.5px"
475
+ };
476
+ const largeur = {
477
+ etroite: "45ch",
478
+ normale: "70ch",
479
+ page: "90rem",
480
+ navigation: "16rem",
481
+ rail: "4.5rem",
482
+ "carte-auth": "28.125rem",
483
+ "dialogue-sm": "24rem",
484
+ "dialogue-md": "32rem",
485
+ "dialogue-lg": "48rem",
486
+ tiroir: "28rem",
487
+ menu: "12rem",
488
+ couche: "28rem",
489
+ "champ-sm": "8rem",
490
+ "champ-md": "12rem",
491
+ "champ-lg": "16rem",
492
+ colonne: "18rem"
493
+ };
494
+ const hauteurDeCommande = {
495
+ sm: "2rem",
496
+ md: "2.25rem",
497
+ lg: "2.5rem",
498
+ xl: "2.75rem",
499
+ "2xl": "3.25rem"
500
+ };
501
+ const MODES_DE_DENSITE = [
502
+ "confortable",
503
+ "dense",
504
+ "compacte"
505
+ ];
506
+ const densite = {
507
+ confortable: {
508
+ "hauteur-commande-defaut": "var(--st-hauteur-commande-xl)",
509
+ "densite-cellule-bloc": "var(--st-espacement-3)",
510
+ "densite-cellule-ligne": "var(--st-espacement-3)",
511
+ "densite-texte-taille": "var(--st-taille-md)",
512
+ "densite-texte-interligne": "var(--st-interligne-md)",
513
+ "densite-ligne": "var(--st-hauteur-commande-2xl)",
514
+ "calendrier-jour-defaut": "var(--st-hauteur-commande-xl)"
515
+ },
516
+ dense: {
517
+ "hauteur-commande-defaut": "var(--st-hauteur-commande-md)",
518
+ "densite-cellule-bloc": "var(--st-espacement-2)",
519
+ "densite-cellule-ligne": "var(--st-espacement-3)",
520
+ "densite-texte-taille": "var(--st-taille-sm)",
521
+ "densite-texte-interligne": "var(--st-interligne-sm)",
522
+ "densite-ligne": "var(--st-hauteur-commande-xl)",
523
+ "calendrier-jour-defaut": "var(--st-hauteur-commande-md)"
524
+ },
525
+ compacte: {
526
+ "hauteur-commande-defaut": "var(--st-hauteur-commande-sm)",
527
+ "densite-cellule-bloc": "var(--st-espacement-1)",
528
+ "densite-cellule-ligne": "var(--st-espacement-2)",
529
+ "densite-texte-taille": "var(--st-taille-xs)",
530
+ "densite-texte-interligne": "var(--st-interligne-xs)",
531
+ "densite-ligne": "var(--st-hauteur-commande-md)",
532
+ "calendrier-jour-defaut": "var(--st-hauteur-commande-sm)"
533
+ }
534
+ };
535
+ const focus = {
536
+ epaisseur: "var(--st-epaisseur-moyenne)",
537
+ decalage: "var(--st-epaisseur-moyenne)",
538
+ lisere: "var(--st-epaisseur-moyenne)"
539
+ };
540
+ const delai = {
541
+ annonce: 120,
542
+ grace: 120,
543
+ attente: 200,
544
+ survol: 300,
545
+ repli: 50,
546
+ gel: 250
547
+ };
548
+ const tenue = {
549
+ affichageMinimal: 400,
550
+ confirmation: 2e3,
551
+ toast: 5e3,
552
+ rechargementAnnonce: 2e3,
553
+ revelation: 3e4
554
+ };
555
+ const rythme = {
556
+ delai,
557
+ tenue
558
+ };
199
559
  const PIXELS_PAR_REM = 16;
200
560
  function enRem(px) {
201
- return `${(px / PIXELS_PAR_REM).toString()}rem`;
561
+ return `${(px / PIXELS_PAR_REM).toString()}rem`;
202
562
  }
203
563
  function lire(nom) {
204
- const px = Number.parseInt(rupture[nom], 10);
205
- const rem = enRem(px);
206
- return { px, rem, requete: `(min-width: ${rem})` };
207
- }
208
- /**
209
- * Les points de rupture de l'echelle, DERIVES de `rupture` : une valeur ecrite
210
- * ici a la main pourrait diverger de celle du CSS, et c'est precisement le
211
- * defaut F7 du plan de la coquille (`64rem` en dur la ou `rupture.lg` existait).
212
- *
213
- * ```ts
214
- * inject(BreakpointObserver).observe(RUPTURES.lg.requete);
215
- * ```
216
- */
564
+ const px = Number.parseInt(rupture[nom], 10);
565
+ const rem = enRem(px);
566
+ return {
567
+ px,
568
+ rem,
569
+ requete: `(min-width: ${rem})`
570
+ };
571
+ }
217
572
  const RUPTURES = {
218
- sm: lire('sm'),
219
- md: lire('md'),
220
- lg: lire('lg'),
221
- xl: lire('xl'),
222
- '2xl': lire('2xl'),
223
- };
224
- /**
225
- * Le partiel Sass `_ruptures.scss`, ENGENDRE comme la feuille de jetons : un
226
- * fichier ecrit a la main a cote du TypeScript finirait par ne plus lui
227
- * correspondre. Meme garde de non-derive (`pnpm tokens:css:check`).
228
- *
229
- * Il expose une variable par rupture, la carte complete, et un mixin qui ecrit
230
- * la requete de media, mobile d'abord :
231
- *
232
- * ```scss
233
- * @use '@starterlib/tokens/styles/ruptures' as rupture;
234
- *
235
- * @include rupture.depuis('lg') { … }
236
- * ```
237
- */
573
+ sm: lire("sm"),
574
+ md: lire("md"),
575
+ lg: lire("lg"),
576
+ xl: lire("xl"),
577
+ "2xl": lire("2xl")
578
+ };
238
579
  function partielDesRuptures() {
239
- const noms = Object.keys(RUPTURES);
240
- const variables = noms.map((nom) => `$rupture-${nom}: ${RUPTURES[nom].rem}; // ${RUPTURES[nom].px.toString()}px`);
241
- const entrees = noms.map((nom) => ` '${nom}': $rupture-${nom},`);
242
- return `// Engendre par @starterlib/tokens — ne pas modifier a la main.
580
+ const noms = Object.keys(RUPTURES);
581
+ const variables = noms.map((nom) => `$rupture-${nom}: ${RUPTURES[nom].rem}; // ${RUPTURES[nom].px.toString()}px`);
582
+ const entrees = noms.map((nom) => ` '${nom}': $rupture-${nom},`);
583
+ return `// Engendre par @starterlib/tokens \u2014 ne pas modifier a la main.
243
584
  // Regenerer : pnpm tokens:css
244
585
  //
245
586
  // Points de rupture de l'echelle \`rupture\`, en rem : une requete de media en
246
587
  // rem suit la taille de police choisie par la personne, la ou des px
247
588
  // l'ignoreraient. Mobile d'abord : chaque valeur est un MINIMUM.
248
589
  @use 'sass:map';
590
+ @use 'sass:string';
249
591
 
250
- ${variables.join('\n')}
592
+ ${variables.join("\n")}
251
593
 
252
594
  $ruptures: (
253
- ${entrees.join('\n')}
595
+ ${entrees.join("\n")}
254
596
  );
255
597
 
256
598
  // Applique les regles a partir d'une rupture : \`@include depuis('lg') { … }\`.
@@ -263,376 +605,1457 @@ ${entrees.join('\n')}
263
605
  @content;
264
606
  }
265
607
  }
266
- `;
608
+
609
+ // Meme echelle, mesuree sur le CONTENEUR et non sur la fenetre : pour un composant
610
+ // qui s'adapte a sa boite (carte, tableau, formulaire en colonne). L'ancetre
611
+ // declare \`container-type: inline-size\`.
612
+ //
613
+ // ⚠️ La borne est INTERPOLEE : Sass evalue une requete \`@media\`, mais recopie le
614
+ // prelude d'un \`@container\` tel quel. Sans \`#{}\`, la feuille recevait
615
+ // \`(min-width: map.get(...))\`, que le navigateur ignore : la requete ne
616
+ // s'appliquait jamais, et rien ne le signalait (lot 205, barre d'actions).
617
+ @mixin depuis-conteneur($nom) {
618
+ @if not map.has-key($ruptures, $nom) {
619
+ @error "Rupture inconnue : #{$nom}. Admises : #{map.keys($ruptures)}.";
620
+ }
621
+
622
+ @container (min-width: #{map.get($ruptures, $nom)}) {
623
+ @content;
624
+ }
267
625
  }
268
626
 
269
- /**
270
- * Calcul de contraste WCAG 2.1. Il vit dans les jetons, et non dans les tests,
271
- * parce qu'une SPA peut avoir a verifier une couleur fournie a l'execution
272
- * (theme d'un client, statut colore par une donnee).
273
- */
274
- /** Accepte `#rgb` et `#rrggbb`. Leve sur toute autre forme : une couleur
275
- * illisible ne doit pas se transformer en noir par defaut. */
276
- function versRvb(hex) {
277
- const v = hex.trim().replace(/^#/, '');
278
- const complet = v.length === 3
279
- ? v
280
- .split('')
281
- .map((c) => c + c)
282
- .join('')
283
- : v;
284
- if (!/^[0-9a-fA-F]{6}$/.test(complet)) {
285
- throw new Error(`Couleur hexadecimale invalide : ${hex}`);
627
+ // Ce qui remplace une BOUCLE d'attente sous mouvement reduit : la preference du
628
+ // systeme (sauf mouvement complet choisi), ou l'attribut \`data-mouvement='reduit'\`
629
+ // pose par StMouvement sur <html> (ou par une zone). A poser DANS la regle de la boucle :
630
+ // \`.barre { animation: … ; @include sous-mouvement-reduit { animation: … } }\`.
631
+ //
632
+ // ⚠️ Reserve aux boucles infinies. Une TRANSITION n'en a pas besoin : elle lit
633
+ // \`--st-duree-*\`, que la feuille de jetons met deja a zero (03 P5).
634
+ //
635
+ // ⚠️ Dans une feuille de composant, la regle doit partir de \`:host\` : sans lui,
636
+ // l'encapsulation d'Angular colle l'attribut de portee sur \`:root\`, et la regle
637
+ // ne s'applique jamais — la boucle tournait encore sous mouvement reduit.
638
+ @mixin sous-mouvement-reduit($composant: true) {
639
+ @if not & {
640
+ @error 'sous-mouvement-reduit se pose dans une regle : il complete son selecteur.';
641
+ }
642
+ @if $composant and not string.index('#{&}', ':host') {
643
+ @error 'sous-mouvement-reduit : la regle doit partir de :host (encapsulation Angular).';
644
+ }
645
+
646
+ @media (prefers-reduced-motion: reduce) {
647
+ :root:not([data-mouvement='complet']) & {
648
+ @content;
286
649
  }
287
- return {
288
- r: parseInt(complet.slice(0, 2), 16),
289
- g: parseInt(complet.slice(2, 4), 16),
290
- b: parseInt(complet.slice(4, 6), 16),
291
- };
650
+ }
651
+
652
+ [data-mouvement='reduit'] & {
653
+ @content;
654
+ }
655
+ }
656
+ `;
657
+ }
658
+ function lireHex(texte) {
659
+ const v = texte.trim().replace(/^#/, "");
660
+ const complet = v.length === 3 ? v.split("").map((c) => c + c).join("") : v;
661
+ if (!/^[0-9a-fA-F]{6}$/.test(complet)) return null;
662
+ return {
663
+ r: parseInt(complet.slice(0, 2), 16),
664
+ g: parseInt(complet.slice(2, 4), 16),
665
+ b: parseInt(complet.slice(4, 6), 16)
666
+ };
667
+ }
668
+ function lireOpacite(texte) {
669
+ const v = texte.trim();
670
+ return v.endsWith("%") ? Number(v.slice(0, -1)) / 100 : Number(v);
671
+ }
672
+ function lireFonctionRgb(texte) {
673
+ const trouve = /^rgba?\(([^)]*)\)$/i.exec(texte.trim());
674
+ if (trouve === null) return null;
675
+ const [canaux = "", opaciteModerne] = (trouve[1] ?? "").split("/");
676
+ const morceaux = canaux.split(/[\s,]+/).map((m) => m.trim()).filter((m) => m !== "");
677
+ const opacite = opaciteModerne ?? (morceaux.length === 4 ? morceaux.pop() : void 0);
678
+ if (morceaux.length !== 3) return null;
679
+ const canal = (m) => Math.round(m.endsWith("%") ? Number(m.slice(0, -1)) * 255 / 100 : Number(m));
680
+ const [r, g, b] = morceaux.map(canal);
681
+ const a = opacite === void 0 ? 1 : lireOpacite(opacite);
682
+ if (![
683
+ r,
684
+ g,
685
+ b
686
+ ].every((c) => Number.isFinite(c) && c >= 0 && c <= 255) || !Number.isFinite(a) || a < 0 || a > 1) return null;
687
+ return {
688
+ r,
689
+ g,
690
+ b,
691
+ a
692
+ };
693
+ }
694
+ function lireCouleur(texte) {
695
+ const hex = lireHex(texte);
696
+ if (hex !== null) return {
697
+ ...hex,
698
+ a: 1
699
+ };
700
+ const fonction = lireFonctionRgb(texte);
701
+ if (fonction !== null) return fonction;
702
+ throw new Error(`Couleur invalide : ${texte}`);
703
+ }
704
+ function versRvb(couleur) {
705
+ const lue = lireCouleur(couleur);
706
+ if (lue.a < 1) throw new Error(`Couleur translucide (${couleur}) : la composer sur son fond avant d'en mesurer le contraste.`);
707
+ return {
708
+ r: lue.r,
709
+ g: lue.g,
710
+ b: lue.b
711
+ };
712
+ }
713
+ function enHex({ r, g, b }) {
714
+ return `#${[
715
+ r,
716
+ g,
717
+ b
718
+ ].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
719
+ }
720
+ function composer(premierPlan, fond) {
721
+ const dessus = lireCouleur(premierPlan);
722
+ const dessous = versRvb(fond);
723
+ const melange = (c, d) => Math.round(c * dessus.a + d * (1 - dessus.a));
724
+ return enHex({
725
+ r: melange(dessus.r, dessous.r),
726
+ g: melange(dessus.g, dessous.g),
727
+ b: melange(dessus.b, dessous.b)
728
+ });
292
729
  }
293
730
  function canalLineaire(valeur8bits) {
294
- const c = valeur8bits / 255;
295
- return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
731
+ const c = valeur8bits / 255;
732
+ return c <= .04045 ? c / 12.92 : Math.pow((c + .055) / 1.055, 2.4);
296
733
  }
297
- /** Luminance relative WCAG, entre 0 (noir) et 1 (blanc). */
298
734
  function luminanceRelative(couleur) {
299
- const { r, g, b } = typeof couleur === 'string' ? versRvb(couleur) : couleur;
300
- return 0.2126 * canalLineaire(r) + 0.7152 * canalLineaire(g) + 0.0722 * canalLineaire(b);
735
+ const { r, g, b } = typeof couleur === "string" ? versRvb(couleur) : couleur;
736
+ return .2126 * canalLineaire(r) + .7152 * canalLineaire(g) + .0722 * canalLineaire(b);
301
737
  }
302
- /** Rapport de contraste, entre 1 et 21. L'ordre des arguments est indifferent. */
303
738
  function rapportDeContraste(a, b) {
304
- const la = luminanceRelative(a);
305
- const lb = luminanceRelative(b);
306
- const clair = Math.max(la, lb);
307
- const sombre = Math.min(la, lb);
308
- return (clair + 0.05) / (sombre + 0.05);
309
- }
310
- /**
311
- * Seuils WCAG. `composant` couvre les bordures, icones et limites de champ :
312
- * la norme 1.4.11 leur demande 3:1, pas 4.5:1.
313
- */
739
+ const la = luminanceRelative(a);
740
+ const lb = luminanceRelative(b);
741
+ const clair = Math.max(la, lb);
742
+ const sombre = Math.min(la, lb);
743
+ return (clair + .05) / (sombre + .05);
744
+ }
314
745
  function seuilExige(niveau, nature) {
315
- if (nature === 'composant')
316
- return 3;
317
- if (niveau === 'AAA')
318
- return nature === 'grand' ? 4.5 : 7;
319
- return nature === 'grand' ? 3 : 4.5;
746
+ if (nature === "composant") return 3;
747
+ if (niveau === "AAA") return nature === "grand" ? 4.5 : 7;
748
+ return nature === "grand" ? 3 : 4.5;
320
749
  }
321
- function respecteLeContraste(premierPlan, arrierePlan, niveau = 'AA', nature = 'courant') {
322
- return rapportDeContraste(premierPlan, arrierePlan) >= seuilExige(niveau, nature);
750
+ function respecteLeContraste(premierPlan, arrierePlan, niveau = "AA", nature = "courant") {
751
+ return rapportDeContraste(premierPlan, arrierePlan) >= seuilExige(niveau, nature);
323
752
  }
324
-
753
+ const APCA = {
754
+ trc: 2.4,
755
+ rouge: .2126729,
756
+ vert: .7151522,
757
+ bleu: .072175,
758
+ normFond: .56,
759
+ normTexte: .57,
760
+ inverseTexte: .62,
761
+ inverseFond: .65,
762
+ seuilNoir: .022,
763
+ serrageNoir: 1.414,
764
+ echelle: 1.14,
765
+ decalage: .027,
766
+ deltaMin: 5e-4,
767
+ plancher: .1
768
+ };
769
+ function luminanceApca(couleur) {
770
+ const { r, g, b } = versRvb(couleur);
771
+ const y = APCA.rouge * (r / 255) ** APCA.trc + APCA.vert * (g / 255) ** APCA.trc + APCA.bleu * (b / 255) ** APCA.trc;
772
+ return y > APCA.seuilNoir ? y : y + (APCA.seuilNoir - y) ** APCA.serrageNoir;
773
+ }
774
+ function apca(texte, fond) {
775
+ const yTexte = luminanceApca(texte);
776
+ const yFond = luminanceApca(fond);
777
+ if (Math.abs(yFond - yTexte) < APCA.deltaMin) return 0;
778
+ if (yFond > yTexte) {
779
+ const brut = (yFond ** APCA.normFond - yTexte ** APCA.normTexte) * APCA.echelle;
780
+ return brut < APCA.plancher ? 0 : (brut - APCA.decalage) * 100;
781
+ }
782
+ const brut = (yFond ** APCA.inverseFond - yTexte ** APCA.inverseTexte) * APCA.echelle;
783
+ return brut > -APCA.plancher ? 0 : (brut + APCA.decalage) * 100;
784
+ }
785
+ const RENDU = {
786
+ fond: "#f8f9ff",
787
+ texte: "#161c24",
788
+ texteDiscret: "#434652",
789
+ texteAttenue: "#656874",
790
+ primaire: "#003181",
791
+ surfaceConteneurBas: "#eff4ff",
792
+ surfaceConteneur: "#e9eefa",
793
+ surfaceConteneurHaut: "#e3e8f4",
794
+ surfaceConteneurPlusHaut: "#dde3ee",
795
+ positif: "#005a2b",
796
+ primaireAttenue: "#dae2ff",
797
+ surPrimaireAttenue: "#001848"
798
+ };
799
+ const SOMBRE_CALCULE = {
800
+ surfaceConteneurBas: "#0e141c",
801
+ surfaceConteneurHaut: "#323941",
802
+ surfaceSurvolElevee: "#383f49",
803
+ surfaceActive: "#424851"
804
+ };
805
+ const { neutre, bleu, azur, or, vert, ambre, rouge } = primitives;
325
806
  const themeClair = {
326
- fond: primitives.neutre[50],
327
- surface: primitives.neutre[0],
328
- surfaceElevee: primitives.neutre[0],
329
- surfaceEnfoncee: primitives.neutre[100],
330
- surfaceSurvol: primitives.neutre[100],
331
- texte: primitives.neutre[900],
332
- texteDiscret: primitives.neutre[600],
333
- texteDesactive: primitives.neutre[400],
334
- texteInverse: primitives.neutre[0],
335
- bordure: primitives.neutre[200],
336
- // 4.49:1 sur `surface` : c'est la bordure qui IDENTIFIE un champ,
337
- // elle est soumise a WCAG 1.4.11. La neutre 400 n'atteignait que 2.63:1.
338
- bordureForte: primitives.neutre[500],
339
- anneauFocus: primitives.bleu[600],
340
- primaire: primitives.bleu[700],
341
- primaireSurvol: primitives.bleu[800],
342
- surPrimaire: primitives.neutre[0],
343
- primaireDiscret: primitives.bleu[50],
344
- surPrimaireDiscret: primitives.bleu[900],
345
- succes: primitives.vert[600],
346
- surSucces: primitives.neutre[0],
347
- succesDiscret: primitives.vert[100],
348
- surSuccesDiscret: primitives.vert[900],
349
- avertissement: primitives.ambre[600],
350
- surAvertissement: primitives.neutre[0],
351
- avertissementDiscret: primitives.ambre[100],
352
- surAvertissementDiscret: primitives.ambre[900],
353
- danger: primitives.rouge[600],
354
- dangerSurvol: primitives.rouge[700],
355
- surDanger: primitives.neutre[0],
356
- dangerDiscret: primitives.rouge[100],
357
- surDangerDiscret: primitives.rouge[900],
807
+ fond: RENDU.fond,
808
+ surface: neutre[0],
809
+ surfaceElevee: neutre[0],
810
+ surfaceEnfoncee: RENDU.surfaceConteneur,
811
+ surfaceConteneurBas: RENDU.surfaceConteneurBas,
812
+ surfaceConteneurHaut: RENDU.surfaceConteneurHaut,
813
+ surfaceSurvol: RENDU.surfaceConteneur,
814
+ surfaceSurvolElevee: RENDU.surfaceConteneur,
815
+ surfaceCouranteElevee: RENDU.surfaceConteneur,
816
+ surfaceActive: RENDU.surfaceConteneurPlusHaut,
817
+ surfaceSelection: bleu[50],
818
+ surfaceInverse: RENDU.texte,
819
+ surfaceMarque: bleu[950],
820
+ texte: RENDU.texte,
821
+ texteDiscret: RENDU.texteDiscret,
822
+ texteAttenue: RENDU.texteAttenue,
823
+ texteDesactive: neutre[400],
824
+ texteInverse: neutre[0],
825
+ textePrimaire: RENDU.primaire,
826
+ texteSurMarque: neutre[0],
827
+ texteSurMarqueDiscret: bleu[200],
828
+ bordure: neutre[200],
829
+ bordureForte: neutre[500],
830
+ bordureSelection: bleu[700],
831
+ anneauFocus: bleu[600],
832
+ lisereFocus: neutre[0],
833
+ selection: bleu[200],
834
+ surSelection: neutre[950],
835
+ primaire: RENDU.primaire,
836
+ primaireSurvol: bleu[700],
837
+ primaireActif: bleu[900],
838
+ surPrimaire: neutre[0],
839
+ primaireDiscret: bleu[50],
840
+ surPrimaireDiscret: bleu[900],
841
+ primaireAttenue: RENDU.primaireAttenue,
842
+ surPrimaireAttenue: RENDU.surPrimaireAttenue,
843
+ accent: or[600],
844
+ accentSurvol: or[700],
845
+ surAccent: neutre[0],
846
+ accentDiscret: or[100],
847
+ surAccentDiscret: or[900],
848
+ information: azur[600],
849
+ surInformation: neutre[0],
850
+ informationDiscret: azur[50],
851
+ surInformationDiscret: azur[900],
852
+ succes: vert[600],
853
+ surSucces: neutre[0],
854
+ succesDiscret: vert[50],
855
+ surSuccesDiscret: vert[900],
856
+ avertissement: ambre[600],
857
+ surAvertissement: neutre[0],
858
+ avertissementDiscret: ambre[50],
859
+ surAvertissementDiscret: ambre[900],
860
+ danger: rouge[600],
861
+ dangerSurvol: rouge[700],
862
+ dangerActif: rouge[800],
863
+ surDanger: neutre[0],
864
+ dangerDiscret: rouge[50],
865
+ surDangerDiscret: rouge[900],
866
+ dangerDiscretCourant: rouge[100],
867
+ positif: RENDU.positif,
868
+ negatif: RENDU.texte
358
869
  };
359
870
  const themeSombre = {
360
- fond: primitives.neutre[950],
361
- surface: primitives.neutre[900],
362
- surfaceElevee: primitives.neutre[800],
363
- surfaceEnfoncee: primitives.neutre[950],
364
- surfaceSurvol: primitives.neutre[800],
365
- texte: primitives.neutre[100],
366
- texteDiscret: primitives.neutre[300],
367
- texteDesactive: primitives.neutre[500],
368
- texteInverse: primitives.neutre[950],
369
- bordure: primitives.neutre[700],
370
- bordureForte: primitives.neutre[500],
371
- anneauFocus: primitives.bleu[300],
372
- primaire: primitives.bleu[300],
373
- primaireSurvol: primitives.bleu[200],
374
- surPrimaire: primitives.neutre[950],
375
- primaireDiscret: primitives.bleu[900],
376
- surPrimaireDiscret: primitives.bleu[100],
377
- succes: primitives.vert[300],
378
- surSucces: primitives.neutre[950],
379
- succesDiscret: primitives.vert[900],
380
- surSuccesDiscret: primitives.vert[100],
381
- avertissement: primitives.ambre[300],
382
- surAvertissement: primitives.neutre[950],
383
- avertissementDiscret: primitives.ambre[900],
384
- surAvertissementDiscret: primitives.ambre[100],
385
- danger: primitives.rouge[300],
386
- dangerSurvol: primitives.rouge[100],
387
- surDanger: primitives.neutre[950],
388
- dangerDiscret: primitives.rouge[900],
389
- surDangerDiscret: primitives.rouge[100],
871
+ fond: neutre[950],
872
+ surface: neutre[900],
873
+ surfaceElevee: neutre[800],
874
+ surfaceEnfoncee: neutre[950],
875
+ surfaceConteneurBas: SOMBRE_CALCULE.surfaceConteneurBas,
876
+ surfaceConteneurHaut: SOMBRE_CALCULE.surfaceConteneurHaut,
877
+ surfaceSurvol: neutre[800],
878
+ surfaceSurvolElevee: SOMBRE_CALCULE.surfaceSurvolElevee,
879
+ surfaceCouranteElevee: neutre[700],
880
+ surfaceActive: SOMBRE_CALCULE.surfaceActive,
881
+ surfaceSelection: bleu[900],
882
+ surfaceInverse: neutre[100],
883
+ surfaceMarque: neutre[900],
884
+ texte: neutre[100],
885
+ texteDiscret: neutre[300],
886
+ texteAttenue: neutre[400],
887
+ texteDesactive: neutre[500],
888
+ texteInverse: neutre[950],
889
+ textePrimaire: bleu[300],
890
+ texteSurMarque: neutre[100],
891
+ texteSurMarqueDiscret: neutre[300],
892
+ bordure: neutre[700],
893
+ bordureForte: neutre[500],
894
+ bordureSelection: bleu[300],
895
+ anneauFocus: bleu[200],
896
+ lisereFocus: neutre[900],
897
+ selection: bleu[800],
898
+ surSelection: neutre[0],
899
+ primaire: bleu[300],
900
+ primaireSurvol: bleu[200],
901
+ primaireActif: bleu[100],
902
+ surPrimaire: neutre[950],
903
+ primaireDiscret: bleu[900],
904
+ surPrimaireDiscret: bleu[100],
905
+ primaireAttenue: bleu[800],
906
+ surPrimaireAttenue: bleu[100],
907
+ accent: or[300],
908
+ accentSurvol: or[200],
909
+ surAccent: neutre[950],
910
+ accentDiscret: or[900],
911
+ surAccentDiscret: or[100],
912
+ information: azur[300],
913
+ surInformation: neutre[950],
914
+ informationDiscret: azur[900],
915
+ surInformationDiscret: azur[100],
916
+ succes: vert[300],
917
+ surSucces: neutre[950],
918
+ succesDiscret: vert[900],
919
+ surSuccesDiscret: vert[100],
920
+ avertissement: ambre[300],
921
+ surAvertissement: neutre[950],
922
+ avertissementDiscret: ambre[900],
923
+ surAvertissementDiscret: ambre[100],
924
+ danger: rouge[300],
925
+ dangerSurvol: rouge[200],
926
+ dangerActif: rouge[100],
927
+ surDanger: neutre[950],
928
+ dangerDiscret: rouge[900],
929
+ surDangerDiscret: rouge[100],
930
+ dangerDiscretCourant: rouge[950],
931
+ positif: vert[300],
932
+ negatif: neutre[100]
390
933
  };
391
934
  const themes = {
392
- clair: themeClair,
393
- sombre: themeSombre,
935
+ clair: themeClair,
936
+ sombre: themeSombre
394
937
  };
938
+ function surChacune(premierPlan, arrierePlans, nature = "courant") {
939
+ return arrierePlans.map((arrierePlan) => ({
940
+ premierPlan,
941
+ arrierePlan,
942
+ nature
943
+ }));
944
+ }
945
+ const SURFACES_DE_LECTURE = [
946
+ "fond",
947
+ "surface",
948
+ "surfaceElevee",
949
+ "surfaceEnfoncee",
950
+ "surfaceConteneurBas",
951
+ "surfaceConteneurHaut",
952
+ "surfaceSurvol",
953
+ "surfaceSelection"
954
+ ];
955
+ const SURFACES_DE_STATUT = [
956
+ "fond",
957
+ "surface",
958
+ "surfaceElevee",
959
+ "surfaceEnfoncee",
960
+ "surfaceSurvol"
961
+ ];
395
962
  const pairesDeContraste = [
396
- { premierPlan: 'texte', arrierePlan: 'surface', nature: 'courant' },
397
- { premierPlan: 'texte', arrierePlan: 'fond', nature: 'courant' },
398
- { premierPlan: 'texte', arrierePlan: 'surfaceElevee', nature: 'courant' },
399
- { premierPlan: 'texte', arrierePlan: 'surfaceEnfoncee', nature: 'courant' },
400
- { premierPlan: 'texteDiscret', arrierePlan: 'surface', nature: 'courant' },
401
- { premierPlan: 'texteDiscret', arrierePlan: 'fond', nature: 'courant' },
402
- { premierPlan: 'texteInverse', arrierePlan: 'primaire', nature: 'courant' },
403
- { premierPlan: 'bordureForte', arrierePlan: 'surface', nature: 'composant' },
404
- { premierPlan: 'anneauFocus', arrierePlan: 'surface', nature: 'composant' },
405
- { premierPlan: 'anneauFocus', arrierePlan: 'fond', nature: 'composant' },
406
- { premierPlan: 'surPrimaire', arrierePlan: 'primaire', nature: 'courant' },
407
- { premierPlan: 'surPrimaire', arrierePlan: 'primaireSurvol', nature: 'courant' },
408
- { premierPlan: 'surPrimaireDiscret', arrierePlan: 'primaireDiscret', nature: 'courant' },
409
- { premierPlan: 'primaire', arrierePlan: 'surface', nature: 'courant' },
410
- { premierPlan: 'surSucces', arrierePlan: 'succes', nature: 'courant' },
411
- { premierPlan: 'surSuccesDiscret', arrierePlan: 'succesDiscret', nature: 'courant' },
412
- // ⚠️ `courant` et non `composant` : `st-inline-message` emploie ces couleurs
413
- // comme TEXTE. Declarees « composant », elles auraient pu descendre a 3:1 sans
414
- // que la garde echoue (audit de la vague 2, UX-7). La nature se declare selon
415
- // l'USAGE le plus exigeant, jamais selon la vocation premiere de la couleur.
416
- { premierPlan: 'succes', arrierePlan: 'surface', nature: 'courant' },
417
- { premierPlan: 'succes', arrierePlan: 'fond', nature: 'courant' },
418
- { premierPlan: 'succes', arrierePlan: 'surfaceElevee', nature: 'courant' },
419
- { premierPlan: 'surAvertissement', arrierePlan: 'avertissement', nature: 'courant' },
420
- {
421
- premierPlan: 'surAvertissementDiscret',
422
- arrierePlan: 'avertissementDiscret',
423
- nature: 'courant',
424
- },
425
- { premierPlan: 'avertissement', arrierePlan: 'surface', nature: 'courant' },
426
- { premierPlan: 'avertissement', arrierePlan: 'fond', nature: 'courant' },
427
- { premierPlan: 'avertissement', arrierePlan: 'surfaceElevee', nature: 'courant' },
428
- { premierPlan: 'surDanger', arrierePlan: 'danger', nature: 'courant' },
429
- { premierPlan: 'surDanger', arrierePlan: 'dangerSurvol', nature: 'courant' },
430
- { premierPlan: 'surDangerDiscret', arrierePlan: 'dangerDiscret', nature: 'courant' },
431
- { premierPlan: 'danger', arrierePlan: 'surface', nature: 'courant' },
432
- { premierPlan: 'danger', arrierePlan: 'fond', nature: 'courant' },
433
- { premierPlan: 'danger', arrierePlan: 'surfaceElevee', nature: 'courant' },
434
- // Les surfaces flottantes — dialogues, messages ephemeres — sont
435
- // `surfaceElevee` : ce qui s'y lit se verifie sur elle aussi.
436
- { premierPlan: 'texteDiscret', arrierePlan: 'surfaceElevee', nature: 'courant' },
437
- { premierPlan: 'primaire', arrierePlan: 'fond', nature: 'courant' },
438
- { premierPlan: 'primaire', arrierePlan: 'surfaceElevee', nature: 'courant' },
963
+ ...surChacune("texte", [
964
+ ...SURFACES_DE_LECTURE,
965
+ "surfaceSurvolElevee",
966
+ "surfaceCouranteElevee",
967
+ "surfaceActive"
968
+ ]),
969
+ ...surChacune("texteDiscret", [
970
+ ...SURFACES_DE_LECTURE,
971
+ "surfaceSurvolElevee",
972
+ "surfaceActive"
973
+ ]),
974
+ ...surChacune("texteAttenue", SURFACES_DE_LECTURE),
975
+ ...surChacune("textePrimaire", [
976
+ "fond",
977
+ "surface",
978
+ "surfaceElevee",
979
+ "surfaceConteneurBas",
980
+ "surfaceSelection",
981
+ "primaireDiscret"
982
+ ]),
983
+ {
984
+ premierPlan: "texteInverse",
985
+ arrierePlan: "surfaceInverse",
986
+ nature: "courant"
987
+ },
988
+ {
989
+ premierPlan: "texteInverse",
990
+ arrierePlan: "primaire",
991
+ nature: "courant"
992
+ },
993
+ {
994
+ premierPlan: "texteSurMarque",
995
+ arrierePlan: "surfaceMarque",
996
+ nature: "courant"
997
+ },
998
+ {
999
+ premierPlan: "texteSurMarqueDiscret",
1000
+ arrierePlan: "surfaceMarque",
1001
+ nature: "courant"
1002
+ },
1003
+ ...surChacune("bordureForte", [
1004
+ "surface",
1005
+ "fond",
1006
+ "surfaceEnfoncee",
1007
+ "surfaceConteneurBas"
1008
+ ], "composant"),
1009
+ ...surChacune("bordureSelection", ["surfaceSelection", "surface"], "composant"),
1010
+ ...surChacune("anneauFocus", [
1011
+ "surface",
1012
+ "fond",
1013
+ "surfaceElevee",
1014
+ "lisereFocus",
1015
+ "surfaceCouranteElevee",
1016
+ "dangerDiscretCourant"
1017
+ ], "composant"),
1018
+ {
1019
+ premierPlan: "surSelection",
1020
+ arrierePlan: "selection",
1021
+ nature: "courant"
1022
+ },
1023
+ ...surChacune("surPrimaire", [
1024
+ "primaire",
1025
+ "primaireSurvol",
1026
+ "primaireActif"
1027
+ ]),
1028
+ {
1029
+ premierPlan: "surPrimaireDiscret",
1030
+ arrierePlan: "primaireDiscret",
1031
+ nature: "courant"
1032
+ },
1033
+ {
1034
+ premierPlan: "surPrimaireAttenue",
1035
+ arrierePlan: "primaireAttenue",
1036
+ nature: "courant"
1037
+ },
1038
+ ...surChacune("primaire", [
1039
+ "surface",
1040
+ "fond",
1041
+ "surfaceElevee",
1042
+ "primaireDiscret",
1043
+ "surfaceSelection"
1044
+ ]),
1045
+ {
1046
+ premierPlan: "primaire",
1047
+ arrierePlan: "surfaceCouranteElevee",
1048
+ nature: "composant"
1049
+ },
1050
+ ...surChacune("surAccent", ["accent", "accentSurvol"]),
1051
+ {
1052
+ premierPlan: "surAccentDiscret",
1053
+ arrierePlan: "accentDiscret",
1054
+ nature: "courant"
1055
+ },
1056
+ ...surChacune("accent", [
1057
+ "surface",
1058
+ "fond",
1059
+ "accentDiscret"
1060
+ ]),
1061
+ {
1062
+ premierPlan: "surInformation",
1063
+ arrierePlan: "information",
1064
+ nature: "courant"
1065
+ },
1066
+ {
1067
+ premierPlan: "surInformationDiscret",
1068
+ arrierePlan: "informationDiscret",
1069
+ nature: "courant"
1070
+ },
1071
+ ...surChacune("information", [...SURFACES_DE_STATUT, "informationDiscret"]),
1072
+ {
1073
+ premierPlan: "surSucces",
1074
+ arrierePlan: "succes",
1075
+ nature: "courant"
1076
+ },
1077
+ {
1078
+ premierPlan: "surSuccesDiscret",
1079
+ arrierePlan: "succesDiscret",
1080
+ nature: "courant"
1081
+ },
1082
+ ...surChacune("succes", [...SURFACES_DE_STATUT, "succesDiscret"]),
1083
+ {
1084
+ premierPlan: "surAvertissement",
1085
+ arrierePlan: "avertissement",
1086
+ nature: "courant"
1087
+ },
1088
+ {
1089
+ premierPlan: "surAvertissementDiscret",
1090
+ arrierePlan: "avertissementDiscret",
1091
+ nature: "courant"
1092
+ },
1093
+ ...surChacune("avertissement", [...SURFACES_DE_STATUT, "avertissementDiscret"]),
1094
+ ...surChacune("surDanger", [
1095
+ "danger",
1096
+ "dangerSurvol",
1097
+ "dangerActif"
1098
+ ]),
1099
+ {
1100
+ premierPlan: "surDangerDiscret",
1101
+ arrierePlan: "dangerDiscret",
1102
+ nature: "courant"
1103
+ },
1104
+ ...surChacune("danger", [
1105
+ ...SURFACES_DE_STATUT,
1106
+ "dangerDiscret",
1107
+ "dangerDiscretCourant"
1108
+ ]),
1109
+ ...surChacune("positif", [
1110
+ "surface",
1111
+ "fond",
1112
+ "surfaceConteneurBas"
1113
+ ]),
1114
+ ...surChacune("negatif", ["surface", "fond"])
439
1115
  ];
440
- const exemptesDeContraste = [
441
- {
442
- jeton: 'bordure',
443
- raison: 'Separateur decoratif (bord de carte, filet de tableau). WCAG 1.4.11 ne ' +
444
- "s'applique qu'aux bordures porteuses d'information ; celle qui identifie " +
445
- 'un champ est `bordureForte`, elle, controlee a 3:1.',
446
- },
447
- {
448
- jeton: 'texteDesactive',
449
- raison: 'WCAG 1.4.3 exempte explicitement les commandes desactivees. Le statut ' +
450
- "desactive n'est jamais porte par la seule couleur : l'attribut aria le dit.",
451
- },
1116
+ const exemptesDeContraste = [{
1117
+ jeton: "bordure",
1118
+ raison: "Separateur decoratif (bord de carte, filet de tableau). WCAG 1.4.11 ne s'applique qu'aux bordures porteuses d'information ; celle qui identifie un champ est `bordureForte`, elle, controlee a 3:1."
1119
+ }, {
1120
+ jeton: "texteDesactive",
1121
+ raison: "WCAG 1.4.3 exempte explicitement les commandes desactivees. Le statut desactive n'est jamais porte par la seule couleur : l'attribut aria le dit."
1122
+ }];
1123
+ const pairesExclues = [
1124
+ {
1125
+ premierPlan: "texteAttenue",
1126
+ arrierePlan: "surfaceActive",
1127
+ raison: "4,30:1 en clair (#656874 sur #dde3ee, D17), 3,59:1 en sombre. Sur une surface active, une aide se compose en `texteDiscret` (audit Claude Design §3.2 #3)."
1128
+ },
1129
+ {
1130
+ premierPlan: "texteAttenue",
1131
+ arrierePlan: "surfaceSurvolElevee",
1132
+ raison: "4,13:1 en sombre (#9da2a8 sur #383f49). Une entree de menu survolee garde son texte en `texte` ou `texteDiscret`."
1133
+ },
1134
+ {
1135
+ premierPlan: "texteDiscret",
1136
+ arrierePlan: "surfaceCouranteElevee",
1137
+ raison: "4,11:1 en sombre (#bdc1c7 sur #4f565e). L entree courante d un menu passe son texte en `texte` (6,74:1), son suffixe aussi en sombre."
1138
+ }
452
1139
  ];
453
- /** Seuil de perceptibilite des jetons exemptes : ils n'ont pas de minimum
454
- * normatif, mais un separateur invisible ne separe rien. */
455
1140
  const SEUIL_DE_PERCEPTIBILITE = 1.2;
1141
+ const ecartsDeSurface = [
1142
+ {
1143
+ devant: "surfaceEnfoncee",
1144
+ derriere: "surface",
1145
+ minimum: 1.12,
1146
+ pourquoi: "le creux (champ en lecture seule, puits) se voit sur une carte"
1147
+ },
1148
+ {
1149
+ devant: "surfaceSurvol",
1150
+ derriere: "surface",
1151
+ minimum: 1.12,
1152
+ pourquoi: "une ligne survolee se distingue de ses voisines"
1153
+ },
1154
+ {
1155
+ devant: "surfaceSurvolElevee",
1156
+ derriere: "surfaceElevee",
1157
+ minimum: 1.15,
1158
+ pourquoi: "une entree de menu survolee se voit dans les deux themes (D1)"
1159
+ },
1160
+ {
1161
+ devant: "surfaceCouranteElevee",
1162
+ derriere: "surfaceElevee",
1163
+ minimum: 1.15,
1164
+ pourquoi: "l'entree courante d'un menu se voit dans les deux themes (lot 231)"
1165
+ },
1166
+ {
1167
+ devant: "dangerDiscretCourant",
1168
+ derriere: "surfaceElevee",
1169
+ minimum: 1.2,
1170
+ pourquoi: "l'entree danger courante d'un menu se voit dans les deux themes (lot 231)"
1171
+ },
1172
+ {
1173
+ devant: "surfaceActive",
1174
+ derriere: "surfaceSurvol",
1175
+ minimum: 1.1,
1176
+ pourquoi: "l'etat enfonce differe du survol"
1177
+ },
1178
+ {
1179
+ devant: "surfaceConteneurHaut",
1180
+ derriere: "surfaceConteneurBas",
1181
+ minimum: 1.1,
1182
+ pourquoi: "le survol d un lien de navigation se voit sur son panneau"
1183
+ },
1184
+ {
1185
+ devant: "surfaceConteneurBas",
1186
+ derriere: "surface",
1187
+ minimum: 1.05,
1188
+ pourquoi: "un en-tete de tableau ou un pied se detache de la carte qui le porte"
1189
+ }
1190
+ ];
1191
+ const surfacesDuFocus = [
1192
+ "surface",
1193
+ "fond",
1194
+ "surfaceElevee",
1195
+ "surfaceEnfoncee",
1196
+ "surfaceConteneurBas",
1197
+ "surfaceConteneurHaut",
1198
+ "surfaceSelection",
1199
+ "surfaceActive",
1200
+ "surfaceMarque",
1201
+ "surfaceInverse",
1202
+ "primaire",
1203
+ "primaireSurvol",
1204
+ "danger",
1205
+ "accent",
1206
+ "succes",
1207
+ "information",
1208
+ "avertissement"
1209
+ ];
1210
+ const COUCHES = [
1211
+ "repos",
1212
+ "survol",
1213
+ "surelevee",
1214
+ "flottante",
1215
+ "modale"
1216
+ ];
1217
+ function memeCouleur(couleur) {
1218
+ return {
1219
+ clair: couleur,
1220
+ sombre: couleur
1221
+ };
1222
+ }
1223
+ const ombres = {
1224
+ repos: [{
1225
+ geometrie: "0 1px 2px 0",
1226
+ couleur: memeCouleur("rgb(0 0 0 / 0.05)")
1227
+ }],
1228
+ survol: [{
1229
+ geometrie: "0 4px 6px -1px",
1230
+ couleur: memeCouleur("rgb(0 0 0 / 0.1)")
1231
+ }, {
1232
+ geometrie: "0 2px 4px -2px",
1233
+ couleur: memeCouleur("rgb(0 0 0 / 0.1)")
1234
+ }],
1235
+ surelevee: [{
1236
+ geometrie: "0 10px 15px -3px",
1237
+ couleur: memeCouleur("rgb(0 0 0 / 0.1)")
1238
+ }, {
1239
+ geometrie: "0 4px 6px -4px",
1240
+ couleur: memeCouleur("rgb(0 0 0 / 0.1)")
1241
+ }],
1242
+ flottante: [{
1243
+ geometrie: "0 20px 25px -5px",
1244
+ couleur: memeCouleur("rgb(0 0 0 / 0.1)")
1245
+ }, {
1246
+ geometrie: "0 8px 10px -6px",
1247
+ couleur: memeCouleur("rgb(0 0 0 / 0.1)")
1248
+ }],
1249
+ modale: [{
1250
+ geometrie: "0 16px 32px -8px",
1251
+ couleur: {
1252
+ clair: "rgb(7 13 20 / 0.14)",
1253
+ sombre: "rgb(0 0 0 / 0.48)"
1254
+ }
1255
+ }, {
1256
+ geometrie: "0 32px 64px -16px",
1257
+ couleur: {
1258
+ clair: "rgb(7 13 20 / 0.2)",
1259
+ sombre: "rgb(0 0 0 / 0.56)"
1260
+ }
1261
+ }]
1262
+ };
1263
+ const voile = {
1264
+ clair: "rgb(7 13 20 / 0.48)",
1265
+ sombre: "rgb(0 0 0 / 0.64)"
1266
+ };
1267
+ const couleurDOmbre = {
1268
+ clair: "rgb(0 0 0 / 0.2)",
1269
+ sombre: "rgb(0 0 0 / 0.6)"
1270
+ };
1271
+ function ombreDuTheme(couche, theme) {
1272
+ return ombres[couche].map((c) => `${c.geometrie} ${c.couleur[theme]}`).join(", ");
1273
+ }
1274
+ function rolesDesPrimitives() {
1275
+ const roles = /* @__PURE__ */ new Map();
1276
+ for (const nomDeTheme of ["clair", "sombre"]) {
1277
+ const entrees = Object.entries(themes[nomDeTheme]);
1278
+ for (const [jeton, valeur] of entrees) {
1279
+ const liste = roles.get(valeur) ?? [];
1280
+ liste.push(`${jeton} (${nomDeTheme})`);
1281
+ roles.set(valeur, liste);
1282
+ }
1283
+ }
1284
+ return roles;
1285
+ }
1286
+ const stylesTypographiques = {
1287
+ corps: {
1288
+ taille: "md",
1289
+ graisse: "normale",
1290
+ interlettrage: "normal",
1291
+ usage: "texte courant"
1292
+ },
1293
+ "corps-petit": {
1294
+ taille: "sm",
1295
+ graisse: "normale",
1296
+ interlettrage: "normal",
1297
+ usage: "texte secondaire"
1298
+ },
1299
+ "corps-fort": {
1300
+ taille: "md",
1301
+ graisse: "semi",
1302
+ interlettrage: "normal",
1303
+ usage: "intitule de ligne, valeur secondaire (D5)"
1304
+ },
1305
+ "corps-grand": {
1306
+ taille: "lg",
1307
+ graisse: "normale",
1308
+ interlettrage: "normal",
1309
+ usage: "texte d introduction, mention juridique (18/28, DESIGN.md body-lg)"
1310
+ },
1311
+ legende: {
1312
+ taille: "xs",
1313
+ graisse: "moyenne",
1314
+ interlettrage: "large",
1315
+ usage: "legende, etiquette de badge"
1316
+ },
1317
+ libelle: {
1318
+ taille: "sm",
1319
+ graisse: "moyenne",
1320
+ interlettrage: "normal",
1321
+ usage: "libelle de champ, onglet (D18 : 14/500)"
1322
+ },
1323
+ commande: {
1324
+ taille: "sm",
1325
+ graisse: "semi",
1326
+ interlettrage: "normal",
1327
+ usage: "libelle de bouton de 44 px (14/20 600, majorite des planches)"
1328
+ },
1329
+ etiquette: {
1330
+ taille: "xs",
1331
+ graisse: "semi",
1332
+ interlettrage: "etiquette",
1333
+ usage: "en-tete de colonne, etiquette de statut (12/16 600, DESIGN.md label-md)"
1334
+ },
1335
+ "micro-capitales": {
1336
+ taille: "2xs",
1337
+ graisse: "semi",
1338
+ interlettrage: "capitales",
1339
+ capitales: true,
1340
+ usage: "en-tete de colonne, terme de liste, statut en capitales (seul lecteur de 2xs)"
1341
+ },
1342
+ surtitre: {
1343
+ taille: "2xs",
1344
+ graisse: "semi",
1345
+ interlettrage: "surtitre",
1346
+ capitales: true,
1347
+ usage: "surtitre de page ou de section, en capitales espacees"
1348
+ },
1349
+ "titre-4": {
1350
+ taille: "lg",
1351
+ interligne: "md",
1352
+ graisse: "semi",
1353
+ interlettrage: "approche",
1354
+ usage: "intertitre, titre de dialogue et de carte (18/24)"
1355
+ },
1356
+ "titre-3": {
1357
+ taille: "xl",
1358
+ graisse: "semi",
1359
+ interlettrage: "approche",
1360
+ usage: "titre de section"
1361
+ },
1362
+ "titre-2": {
1363
+ taille: "2xl",
1364
+ graisse: "semi",
1365
+ interlettrage: "approche",
1366
+ usage: "titre de bloc majeur"
1367
+ },
1368
+ "titre-1": {
1369
+ taille: "3xl",
1370
+ graisse: "semi",
1371
+ interlettrage: "approche",
1372
+ usage: "titre de page (h1 de 32 px, D18)"
1373
+ },
1374
+ affichage: {
1375
+ taille: "4xl",
1376
+ graisse: "semi",
1377
+ interlettrage: "approche",
1378
+ usage: "annonce, titre d ecran de marque"
1379
+ },
1380
+ "affichage-grand": {
1381
+ taille: "6xl",
1382
+ graisse: "semi",
1383
+ interlettrage: "approche",
1384
+ usage: "grand chiffre de tableau de bord (56/64, DESIGN.md display-lg)"
1385
+ },
1386
+ "chiffre-cle": {
1387
+ taille: "3xl",
1388
+ graisse: "semi",
1389
+ interlettrage: "approche",
1390
+ chiffres: "alignes",
1391
+ usage: "valeur de st-stat"
1392
+ },
1393
+ montant: {
1394
+ taille: "md",
1395
+ graisse: "moyenne",
1396
+ interlettrage: "normal",
1397
+ chiffres: "alignes",
1398
+ usage: "st-money, cellule numerique"
1399
+ },
1400
+ "montant-hero": {
1401
+ taille: "4xl",
1402
+ graisse: "semi",
1403
+ interlettrage: "approche",
1404
+ chiffres: "alignes",
1405
+ usage: "solde de compte (40/48, planche 2)"
1406
+ },
1407
+ reference: {
1408
+ taille: "sm",
1409
+ graisse: "normale",
1410
+ interlettrage: "large",
1411
+ chiffres: "references",
1412
+ famille: "mono",
1413
+ usage: "IBAN, identifiant, reference de virement"
1414
+ },
1415
+ code: {
1416
+ taille: "sm",
1417
+ graisse: "normale",
1418
+ interlettrage: "normal",
1419
+ chiffres: "references",
1420
+ famille: "mono",
1421
+ usage: "st-code"
1422
+ }
1423
+ };
1424
+ function interlettrageEnCss(style) {
1425
+ if (style.interlettrage !== "approche") return `var(--st-interlettrage-${style.interlettrage})`;
1426
+ if (!(style.taille in typographie.approche)) throw new Error(`Aucune approche de titre pour la taille ${style.taille}.`);
1427
+ return `var(--st-approche-${style.taille})`;
1428
+ }
1429
+ function interlettrageDuStyle(nom) {
1430
+ const style = stylesTypographiques[nom];
1431
+ if (style.interlettrage !== "approche") return typographie.interlettrage[style.interlettrage];
1432
+ return typographie.approche[style.taille] ?? "normal";
1433
+ }
1434
+ function declarationsDuStyle(nom) {
1435
+ const style = stylesTypographiques[nom];
1436
+ return [
1437
+ ...style.famille === "mono" ? ["font-family: var(--st-police-mono);"] : [],
1438
+ `font-size: var(--st-taille-${style.taille});`,
1439
+ `line-height: var(--st-interligne-${style.interligne ?? style.taille});`,
1440
+ `font-weight: var(--st-graisse-${style.graisse});`,
1441
+ `letter-spacing: ${interlettrageEnCss(style)};`,
1442
+ ...style.chiffres === void 0 ? [] : [`font-variant-numeric: var(--st-chiffres-${style.chiffres});`],
1443
+ ...style.capitales === true ? ["text-transform: uppercase;"] : []
1444
+ ];
1445
+ }
1446
+ function partielDeTypographie() {
1447
+ const noms = Object.keys(stylesTypographiques);
1448
+ const branches = noms.map((nom, i) => {
1449
+ return ` ${i === 0 ? "@if" : "} @else if"} $nom == '${nom}' {\n${declarationsDuStyle(nom).map((d) => ` ${d}`).join("\n")}\n`;
1450
+ });
1451
+ return `// Engendre par @starterlib/tokens \u2014 ne pas modifier a la main.
1452
+ // Regenerer : pnpm tokens:css
1453
+ //
1454
+ // Styles typographiques composites : taille, interligne, graisse, interlettrage et
1455
+ // chiffres, jamais de couleur. Une seule notation : \`@include style('titre-2')\`.
1456
+ // Les valeurs sont LUES dans la feuille de jetons (var(--st-*)) : l'impression, qui
1457
+ // passe les tailles en points, et la densite s'appliquent aux styles aussi.
1458
+
1459
+ $styles: (${noms.map((n) => `'${n}'`).join(", ")});
456
1460
 
1461
+ @mixin style($nom) {
1462
+ ${branches.join("")} } @else {
1463
+ @error "Style typographique inconnu : #{$nom}. Admis : #{$styles}.";
1464
+ }
1465
+ }
1466
+ `;
1467
+ }
1468
+ const LATIN = "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD";
1469
+ const LATIN_ETENDU = "U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF";
1470
+ const policesDuSocle = [{
1471
+ famille: "Inter Variable",
1472
+ graisses: "100 900",
1473
+ paquet: "@fontsource-variable/inter",
1474
+ fichiers: [{
1475
+ fichier: "inter-latin-ext-wght-normal.woff2",
1476
+ unicodeRange: LATIN_ETENDU,
1477
+ aPrecharger: false
1478
+ }, {
1479
+ fichier: "inter-latin-wght-normal.woff2",
1480
+ unicodeRange: LATIN,
1481
+ aPrecharger: true
1482
+ }],
1483
+ metriques: {
1484
+ unitesParEm: 2048,
1485
+ ascendante: 1984,
1486
+ descendante: 494,
1487
+ interligneAjoute: 0,
1488
+ chasseMoyenne: 969.74
1489
+ },
1490
+ repli: {
1491
+ famille: "Inter Repli",
1492
+ locales: [
1493
+ "Arial",
1494
+ "ArialMT",
1495
+ "Liberation Sans",
1496
+ "Arimo"
1497
+ ],
1498
+ metriques: {
1499
+ unitesParEm: 2048,
1500
+ ascendante: 1854,
1501
+ descendante: 434,
1502
+ interligneAjoute: 67,
1503
+ chasseMoyenne: 904
1504
+ }
1505
+ }
1506
+ }, {
1507
+ famille: "JetBrains Mono Variable",
1508
+ graisses: "100 800",
1509
+ paquet: "@fontsource-variable/jetbrains-mono",
1510
+ fichiers: [{
1511
+ fichier: "jetbrains-mono-latin-ext-wght-normal.woff2",
1512
+ unicodeRange: LATIN_ETENDU,
1513
+ aPrecharger: false
1514
+ }, {
1515
+ fichier: "jetbrains-mono-latin-wght-normal.woff2",
1516
+ unicodeRange: LATIN,
1517
+ aPrecharger: false
1518
+ }],
1519
+ metriques: {
1520
+ unitesParEm: 1e3,
1521
+ ascendante: 1020,
1522
+ descendante: 300,
1523
+ interligneAjoute: 0,
1524
+ chasseMoyenne: 600
1525
+ },
1526
+ repli: {
1527
+ famille: "JetBrains Mono Repli",
1528
+ locales: [
1529
+ "Courier New",
1530
+ "CourierNewPSMT",
1531
+ "Liberation Mono",
1532
+ "Cousine"
1533
+ ],
1534
+ metriques: {
1535
+ unitesParEm: 2048,
1536
+ ascendante: 1705,
1537
+ descendante: 615,
1538
+ interligneAjoute: 0,
1539
+ chasseMoyenne: 1229
1540
+ }
1541
+ }
1542
+ }];
1543
+ function pourcent(valeur) {
1544
+ return `${(Math.round(valeur * 1e4) / 100).toFixed(2)}%`;
1545
+ }
1546
+ function surchargesDuRepli(police) {
1547
+ const p = police.metriques;
1548
+ const r = police.repli.metriques;
1549
+ const agrandissement = p.chasseMoyenne / p.unitesParEm / (r.chasseMoyenne / r.unitesParEm);
1550
+ return {
1551
+ sizeAdjust: pourcent(agrandissement),
1552
+ ascentOverride: pourcent(p.ascendante / p.unitesParEm / agrandissement),
1553
+ descentOverride: pourcent(p.descendante / p.unitesParEm / agrandissement),
1554
+ lineGapOverride: pourcent(p.interligneAjoute / p.unitesParEm / agrandissement)
1555
+ };
1556
+ }
1557
+ function fichiersAPrecharger() {
1558
+ return policesDuSocle.flatMap((p) => p.fichiers.filter((f) => f.aPrecharger).map((f) => f.fichier));
1559
+ }
1560
+ function feuilleDesPolices() {
1561
+ const blocs = policesDuSocle.flatMap((police) => {
1562
+ const faces = police.fichiers.map((f) => `@font-face {
1563
+ font-family: '${police.famille}';
1564
+ font-style: normal;
1565
+ font-weight: ${police.graisses};
1566
+ font-display: optional;
1567
+ src: url('./polices/${f.fichier}') format('woff2');
1568
+ unicode-range: ${f.unicodeRange};
1569
+ }`);
1570
+ const s = surchargesDuRepli(police);
1571
+ const repli = `@font-face {
1572
+ font-family: '${police.repli.famille}';
1573
+ src: ${police.repli.locales.map((l) => `local('${l}')`).join(", ")};
1574
+ size-adjust: ${s.sizeAdjust};
1575
+ ascent-override: ${s.ascentOverride};
1576
+ descent-override: ${s.descentOverride};
1577
+ line-gap-override: ${s.lineGapOverride};
1578
+ }`;
1579
+ return [...faces, repli];
1580
+ });
1581
+ return `/* Engendre par @starterlib/tokens \u2014 ne pas modifier a la main.
1582
+ Regenerer : pnpm tokens:css
1583
+
1584
+ Polices du socle, auto-hebergees (SIL OFL 1.1, voir polices/LICENSE-*.txt).
1585
+ Recette : docs/recettes/police.md. A precharger par l'hote :
1586
+ ${fichiersAPrecharger().map((f) => ` <link rel="preload" href="\u2026/polices/${f}" as="font" type="font/woff2" crossorigin>`).join("\n")}
1587
+ Repli METRISE : memes hauteur de ligne et chasse moyenne que la police, calculees
1588
+ sur le fichier livre (tools/verifier-police.mjs). */
1589
+
1590
+ ${blocs.join("\n\n")}
1591
+ `;
1592
+ }
457
1593
  const jetonsDeComposant = [
458
- {
459
- nom: '--st-commande-hauteur',
460
- repli: 'var(--st-cible-tactile-min)',
461
- role: 'Hauteur MINIMALE commune des boutons, champs et listes deroulantes. Une ' +
462
- 'seule valeur pour tous : une ligne ou le champ fait 44 px et le bouton 40 ' +
463
- 'se voit tout de suite. Sous la hauteur du contenu, la commande grandit ' +
464
- 'plutot que de rogner son texte.',
465
- },
466
- {
467
- nom: '--st-commande-rayon',
468
- repli: 'var(--st-rayon-md)',
469
- role: "Rayon des commandes. C'est le reglage d'identite le plus demande.",
470
- },
471
- {
472
- nom: '--st-surface-rayon',
473
- repli: 'var(--st-rayon-md)',
474
- role: 'Rayon des surfaces qui portent du contenu : carte, dialogue, menu, popover.',
475
- },
476
- {
477
- nom: '--st-tableau-cellule-bloc',
478
- repli: 'var(--st-espacement-3)',
479
- role: "Respiration verticale d'une cellule : c'est par elle que passe la densite.",
480
- },
481
- {
482
- nom: '--st-tableau-cellule-ligne',
483
- repli: 'var(--st-espacement-3)',
484
- role: 'Respiration horizontale d une cellule.',
485
- },
486
- {
487
- nom: '--st-tableau-largeur-case',
488
- repli: '3rem',
489
- role: 'Largeur de la colonne des cases a cocher ; la colonne figee suivante se decale d autant.',
490
- },
491
- {
492
- nom: '--st-calendrier-jour-taille',
493
- repli: 'var(--st-cible-tactile-min)',
494
- role: 'Cote d une case de jour. Un calendrier dense reste au-dessus de la cible minimale.',
495
- },
496
- {
497
- nom: '--st-anim-chargement',
498
- repli: 'une duree propre a chaque indicateur',
499
- role: "Rythme d'un indicateur d'activite. ⚠️ Hors de l'echelle des durees, qui " +
500
- 'passe a zero sous mouvement reduit : un indicateur fige ferait croire a un ecran bloque.',
501
- },
502
- {
503
- nom: '--st-anim-respiration',
504
- repli: '1600ms',
505
- role: 'Rythme de la respiration qui remplace le mouvement sous `prefers-reduced-motion`.',
506
- },
1594
+ {
1595
+ nom: "--st-commande-hauteur",
1596
+ repli: "var(--st-hauteur-commande-defaut)",
1597
+ role: "Hauteur MINIMALE commune des boutons, champs et listes deroulantes. Une seule valeur pour tous : une ligne ou le champ fait 44 px et le bouton 40 se voit tout de suite. Le repli suit la densite (44 / 36 / 32, 44 au doigt). Un reglage plus court ne descend jamais sous le plancher des commandes (D4) : 32 px au pointeur fin, 44 au doigt.",
1598
+ lecteurs: [
1599
+ "st-button",
1600
+ "st-icon-button",
1601
+ "_champ-natif"
1602
+ ]
1603
+ },
1604
+ {
1605
+ nom: "--st-commande-rayon",
1606
+ repli: "var(--st-rayon-md)",
1607
+ role: "Rayon des commandes. C'est le reglage d'identite le plus demande.",
1608
+ lecteurs: [
1609
+ "st-button",
1610
+ "st-icon-button",
1611
+ "_champ-natif"
1612
+ ]
1613
+ },
1614
+ {
1615
+ nom: "--st-surface-rayon",
1616
+ repli: "var(--st-rayon-md)",
1617
+ role: "Rayon des surfaces flottantes qui portent du contenu : menu, popover, dialogue, message.",
1618
+ lecteurs: [
1619
+ "st-menu",
1620
+ "st-popover",
1621
+ "st-dialog-container",
1622
+ "st-toast-message"
1623
+ ]
1624
+ },
1625
+ {
1626
+ nom: "--st-carte-rayon",
1627
+ repli: "var(--st-rayon-lg)",
1628
+ role: "Rayon de la carte, la surface la plus chartee : 12 px, distinct des surfaces flottantes.",
1629
+ lecteurs: ["st-card"]
1630
+ },
1631
+ {
1632
+ nom: "--st-tableau-cellule-bloc",
1633
+ repli: "var(--st-espacement-0)",
1634
+ role: "Respiration verticale d'une cellule ; aucune par defaut : la hauteur de ligne du mode fait le rythme (D29).",
1635
+ lecteurs: ["st-data-table"]
1636
+ },
1637
+ {
1638
+ nom: "--st-tableau-cellule-ligne",
1639
+ repli: "calc(var(--st-densite-cellule-ligne) + var(--st-espacement-1))",
1640
+ role: "Respiration horizontale d'une cellule ; le repli suit la densite (16 / 16 / 12 px).",
1641
+ lecteurs: ["st-data-table"]
1642
+ },
1643
+ {
1644
+ nom: "--st-tableau-largeur-case",
1645
+ repli: "var(--st-espacement-12)",
1646
+ role: "Largeur de la colonne des cases a cocher ; la colonne figee suivante se decale d autant.",
1647
+ lecteurs: ["st-data-table"]
1648
+ },
1649
+ {
1650
+ nom: "--st-calendrier-jour-taille",
1651
+ repli: "var(--st-calendrier-jour-defaut)",
1652
+ role: "Cote d une case de jour. Un calendrier dense reste au-dessus de la cible minimale.",
1653
+ lecteurs: ["st-calendar"]
1654
+ },
1655
+ {
1656
+ nom: "--st-anim-chargement",
1657
+ repli: "var(--st-cadence-vive) ou var(--st-cadence-normale)",
1658
+ role: "Rythme d'un indicateur d'activite. ⚠️ Hors de l'echelle des durees, qui passe a zero sous mouvement reduit : un indicateur fige ferait croire a un ecran bloque. Borne par --st-cadence-min : on ralentit une boucle, on ne l eteint pas.",
1659
+ lecteurs: [
1660
+ "st-spinner",
1661
+ "st-progress",
1662
+ "st-skeleton"
1663
+ ]
1664
+ },
1665
+ {
1666
+ nom: "--st-anim-respiration",
1667
+ repli: "var(--st-cadence-respiration)",
1668
+ role: "Rythme de la respiration qui remplace le mouvement sous mouvement reduit. Borne par --st-cadence-min.",
1669
+ lecteurs: [
1670
+ "st-spinner",
1671
+ "st-progress",
1672
+ "st-skeleton"
1673
+ ]
1674
+ },
1675
+ {
1676
+ nom: "--st-coquille-largeur-navigation",
1677
+ repli: "var(--st-largeur-navigation)",
1678
+ role: "Largeur de la navigation laterale de la coquille : un reglage de coquille legitime.",
1679
+ lecteurs: ["st-app-shell"]
1680
+ },
1681
+ {
1682
+ nom: "--st-separateur-marge",
1683
+ repli: "var(--st-espacement-4)",
1684
+ role: "Marge d'un separateur : le reglage de rythme vertical le plus pose par une SPA.",
1685
+ lecteurs: ["st-divider"]
1686
+ },
1687
+ {
1688
+ nom: "--st-champ-largeur-libelle",
1689
+ repli: "var(--st-largeur-champ-md)",
1690
+ role: "Largeur de la colonne des libelles d un formulaire en ligne (libelle a gauche du champ).",
1691
+ lecteurs: ["st-form-field"]
1692
+ },
1693
+ {
1694
+ nom: "--st-liste-hauteur-max",
1695
+ repli: "18rem",
1696
+ role: "Hauteur maximale d une liste deroulante avant defilement (combobox, choix multiple).",
1697
+ lecteurs: ["_liste"]
1698
+ }
1699
+ ];
1700
+ const contratsDHote = [
1701
+ "--st-hauteur-entete",
1702
+ "--st-form-actions-hauteur",
1703
+ "--st-choix-interligne",
1704
+ "--st-choix-graisse",
1705
+ "--st-choix-couleur",
1706
+ "--st-menu-curseur",
1707
+ "--st-switch-rail",
1708
+ "--st-switch-curseur"
1709
+ ];
1710
+ const variablesInternes = [
1711
+ "--st-icone-taille",
1712
+ "--st-avatar-taille",
1713
+ "--st-spinner-taille",
1714
+ "--st-logo-hauteur",
1715
+ "--st-progress-part",
1716
+ "--st-text-lignes",
1717
+ "--st-arbre-niveau",
1718
+ "--st-tableau-plan-figee",
1719
+ "--st-tableau-plan-entete",
1720
+ "--st-tableau-plan-coin",
1721
+ "--st-skeleton-derniere"
507
1722
  ];
508
- /** Vrai si la propriete fait partie du catalogue public. */
509
1723
  function estJetonDeComposant(nom) {
510
- return jetonsDeComposant.some((jeton) => jeton.nom === nom);
1724
+ return jetonsDeComposant.some((jeton) => jeton.nom === nom);
1725
+ }
1726
+ const PREFIXE = "--st";
1727
+ function enKebab(valeur) {
1728
+ return valeur.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
511
1729
  }
512
-
513
- /**
514
- * Les proprietes CSS personnalisees sont ENGENDREES depuis les jetons
515
- * TypeScript, jamais ecrites a la main en parallele. Deux sources de verite
516
- * pour une meme couleur finissent toujours par diverger ; ici la derive est
517
- * impossible, et la CI verifie que le fichier livre correspond au code.
518
- */
519
- const PREFIXE = '--st';
520
1730
  function aplatir(prefixe, valeurs) {
521
- const entrees = Object.entries(valeurs);
522
- return entrees.map(([cle, valeur]) => ` ${prefixe}-${cle}: ${valeur};`);
1731
+ return Object.entries(valeurs).map(([cle, valeur]) => ` ${prefixe}-${enKebab(cle)}: ${valeur};`);
523
1732
  }
524
- function couleursEnCss(jetons) {
525
- const entrees = Object.entries(jetons);
526
- return entrees.map(([cle, valeur]) => ` ${PREFIXE}-couleur-${enKebab(cle)}: ${valeur};`);
1733
+ function selonLeTheme(clair, sombre) {
1734
+ return clair === sombre ? clair : `light-dark(${clair}, ${sombre})`;
527
1735
  }
528
- function enKebab(valeur) {
529
- return valeur.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
1736
+ function nomDeCouleur(jeton) {
1737
+ return `${PREFIXE}-couleur-${enKebab(jeton)}`;
1738
+ }
1739
+ function couleursEnCss() {
1740
+ return Object.keys(themeClair).map((jeton) => ` ${nomDeCouleur(jeton)}: ${selonLeTheme(themeClair[jeton], themeSombre[jeton])};`);
1741
+ }
1742
+ function ombresEnCss() {
1743
+ return [
1744
+ ` ${PREFIXE}-ombre-none: none;`,
1745
+ ...COUCHES.map((couche) => {
1746
+ const valeur = ombres[couche].map((c) => `${c.geometrie} ${selonLeTheme(c.couleur.clair, c.couleur.sombre)}`).join(", ");
1747
+ return ` ${PREFIXE}-ombre-${couche}: ${valeur};`;
1748
+ }),
1749
+ ` ${PREFIXE}-voile: ${selonLeTheme(voile.clair, voile.sombre)};`,
1750
+ ` ${PREFIXE}-couleur-ombre: ${selonLeTheme(couleurDOmbre.clair, couleurDOmbre.sombre)};`
1751
+ ];
530
1752
  }
531
- /** Jetons invariants : ils ne dependent pas du theme. */
532
1753
  function cssDesEchelles() {
533
- return [
534
- ...aplatir(`${PREFIXE}-espacement`, espacement),
535
- ...aplatir(`${PREFIXE}-rayon`, rayon),
536
- ...aplatir(`${PREFIXE}-ombre`, ombre),
537
- ...aplatir(`${PREFIXE}-duree`, duree),
538
- ...aplatir(`${PREFIXE}-courbe`, courbe),
539
- ...aplatir(`${PREFIXE}-plan`, plan),
540
- ...aplatir(`${PREFIXE}-rupture`, rupture),
541
- ...aplatir(`${PREFIXE}-police`, typographie.famille),
542
- ...aplatir(`${PREFIXE}-taille`, typographie.taille),
543
- ...aplatir(`${PREFIXE}-graisse`, typographie.graisse),
544
- ...aplatir(`${PREFIXE}-interligne`, typographie.interligne),
545
- ...aplatir(`${PREFIXE}-epaisseur`, epaisseur),
546
- ...aplatir(`${PREFIXE}-mesure`, mesure),
547
- ` ${PREFIXE}-cible-tactile-min: ${cibleTactileMinimale};`,
548
- ` ${PREFIXE}-cible-min: ${cibleMinimale};`,
549
- ].join('\n');
550
- }
551
- function cssDuTheme(theme) {
552
- return couleursEnCss(themes[theme]).join('\n');
553
- }
554
- /**
555
- * Feuille complete. Le theme clair est la valeur par defaut ; le sombre
556
- * s'applique soit par preference systeme, soit par attribut explicite, et
557
- * l'attribut l'emporte dans les deux sens (un utilisateur qui choisit le
558
- * clair sur un systeme sombre doit obtenir le clair).
559
- *
560
- * Elle porte aussi les trois derogations qui obeissent a la PERSONNE et non au
561
- * theme : mouvement reduit (durees a zero), contraste renforce (filet fort,
562
- * texte discret rendu comme texte), impression (jetons clairs forces) — et le
563
- * decalage de defilement qui garde un focus hors de l'en-tete collant.
564
- *
565
- * ⚠️ `color-scheme` suit le theme EFFECTIF, il n'est pas pose une fois pour
566
- * toutes a `light dark` : ce dernier laisse le navigateur decider seul, si bien
567
- * qu'un theme clair force sur un systeme sombre gardait des barres de
568
- * defilement, des champs natifs et un remplissage automatique sombres.
569
- */
570
- /**
571
- * Catalogue des reglages de composant, en COMMENTAIRE.
572
- *
573
- * ⚠️ Ils ne sont pas declares : une declaration a la racine serait masquee par
574
- * celle d'un composant, et surtout elle laisserait croire qu'on les change ici.
575
- * Ils se posent la ou on veut regler — racine, zone, instance.
576
- */
1754
+ return [
1755
+ ...aplatir(`${PREFIXE}-espacement`, espacement),
1756
+ ...aplatir(`${PREFIXE}-rayon`, rayon),
1757
+ ...aplatir(`${PREFIXE}-duree`, duree),
1758
+ ...aplatir(`${PREFIXE}-courbe`, courbe),
1759
+ ...aplatir(`${PREFIXE}-deplacement`, deplacement),
1760
+ ...aplatir(`${PREFIXE}-cadence`, cadence),
1761
+ ` ${PREFIXE}-cadence-min: ${cadenceMinimale};`,
1762
+ ...aplatir(`${PREFIXE}-plan`, plan),
1763
+ ...aplatir(`${PREFIXE}-police`, typographie.famille),
1764
+ ...aplatir(`${PREFIXE}-taille`, typographie.taille),
1765
+ ...aplatir(`${PREFIXE}-interligne`, typographie.interligne),
1766
+ ...aplatir(`${PREFIXE}-graisse`, typographie.graisse),
1767
+ ...aplatir(`${PREFIXE}-interlettrage`, typographie.interlettrage),
1768
+ ...aplatir(`${PREFIXE}-approche`, typographie.approche),
1769
+ ...aplatir(`${PREFIXE}-chiffres`, typographie.chiffres),
1770
+ ...aplatir(`${PREFIXE}-epaisseur`, epaisseur),
1771
+ ` ${PREFIXE}-epaisseur-capillaire: ${epaisseurCapillaire.base};`,
1772
+ ...aplatir(`${PREFIXE}-largeur`, largeur),
1773
+ ...aplatir(`${PREFIXE}-hauteur-commande`, hauteurDeCommande),
1774
+ ...aplatir(`${PREFIXE}-focus`, focus),
1775
+ ` ${PREFIXE}-cible-tactile-min: ${cibleTactileMinimale};`,
1776
+ ` ${PREFIXE}-cible-min: ${cibleMinimale};`
1777
+ ].join("\n");
1778
+ }
1779
+ function cssDuTheme() {
1780
+ return [...couleursEnCss(), ...ombresEnCss()].join("\n");
1781
+ }
1782
+ function variablesDeDensite(mode, retrait = " ") {
1783
+ return Object.entries(densite[mode]).map(([nom, valeur]) => `${retrait}${PREFIXE}-${nom}: ${valeur};`).join("\n");
1784
+ }
577
1785
  function catalogueDesComposants() {
578
- const lignes = jetonsDeComposant.map((jeton) => ` ${jeton.nom} (defaut : ${jeton.repli})`);
579
- return `/* Reglages de composant, a poser par l'application la ou elle veut :
580
- ${lignes.join('\n')}
1786
+ return `/* Reglages de composant, a poser par l'application la ou elle veut :
1787
+ ${jetonsDeComposant.map((jeton) => ` ${jeton.nom} (defaut : ${jeton.repli})`).join("\n")}
581
1788
  Voir le README de @starterlib/tokens. */`;
582
1789
  }
583
- /**
584
- * Selecteur des DEROGATIONS de la racine : impression, contraste renforce.
585
- *
586
- * ⚠️ `:root` seul ne suffirait pas. Le theme sombre s'ecrit sur
587
- * `:root[data-theme='sombre']` et sur `:root:not([data-theme='clair'])`, deux
588
- * selecteurs de specificite (0,2,0) ; un `:root` nu ne pese que (0,1,0), et une
589
- * derogation ecrite dessus perdrait contre eux, meme placee apres. Les deux
590
- * formes ci-dessous couvrent chaque document — avec ou sans attribut — a
591
- * specificite egale, et l'emportent par l'ordre.
592
- */
593
- const RACINE_QUEL_QUE_SOIT_LE_THEME = `:root[data-theme],
594
- :root:not([data-theme])`;
595
- function indenter(bloc) {
596
- return bloc
597
- .split('\n')
598
- .map((ligne) => ` ${ligne}`)
599
- .join('\n');
1790
+ function dureesAEteindre() {
1791
+ return Object.fromEntries(Object.entries(duree).filter(([, valeur]) => Number.parseFloat(valeur) !== 0).map(([cle]) => [`${PREFIXE}-duree-${enKebab(cle)}`, "0ms"]));
1792
+ }
1793
+ const mouvementReduit = {
1794
+ ...dureesAEteindre(),
1795
+ ...Object.fromEntries(Object.keys(deplacement).map((cle) => [`${PREFIXE}-deplacement-${enKebab(cle)}`, "0rem"]))
1796
+ };
1797
+ const mouvementGele = dureesAEteindre();
1798
+ const aliasDeContrasteRenforceParJeton = {
1799
+ bordure: "bordureForte",
1800
+ bordureForte: "texteDiscret",
1801
+ texteDiscret: "texte",
1802
+ texteAttenue: "texteDiscret",
1803
+ texteDesactive: "texteAttenue",
1804
+ surfaceSurvol: "surfaceActive",
1805
+ surfaceSurvolElevee: "surfaceActive",
1806
+ surfaceCouranteElevee: "bordure",
1807
+ surfaceConteneurHaut: "surfaceActive"
1808
+ };
1809
+ const encresDeContrasteRenforce = {
1810
+ succes: {
1811
+ clair: primitives.vert[700],
1812
+ sombre: themeSombre.succes
1813
+ },
1814
+ avertissement: {
1815
+ clair: primitives.ambre[700],
1816
+ sombre: themeSombre.avertissement
1817
+ }
1818
+ };
1819
+ function themeSousContrasteRenforce(theme) {
1820
+ const base = theme === "clair" ? themeClair : themeSombre;
1821
+ const rendu = { ...base };
1822
+ for (const [jeton, cible] of Object.entries(aliasDeContrasteRenforceParJeton)) rendu[jeton] = base[cible];
1823
+ for (const [jeton, encres] of Object.entries(encresDeContrasteRenforce)) rendu[jeton] = encres[theme];
1824
+ return rendu;
1825
+ }
1826
+ const aliasDeContrasteRenforce = {
1827
+ ...Object.fromEntries(Object.entries(aliasDeContrasteRenforceParJeton).map(([jeton, cible]) => [nomDeCouleur(jeton), selonLeTheme(themeClair[cible], themeSombre[cible])])),
1828
+ ...Object.fromEntries(Object.entries(encresDeContrasteRenforce).map(([jeton, encres]) => [nomDeCouleur(jeton), selonLeTheme(encres.clair, encres.sombre)])),
1829
+ [`${PREFIXE}-focus-epaisseur`]: `var(${PREFIXE}-epaisseur-forte)`
1830
+ };
1831
+ const couleursSysteme = {
1832
+ Canvas: [
1833
+ "fond",
1834
+ "surface",
1835
+ "surfaceElevee",
1836
+ "surfaceEnfoncee",
1837
+ "surfaceConteneurBas",
1838
+ "surfaceConteneurHaut",
1839
+ "surfaceSurvol",
1840
+ "surfaceSurvolElevee",
1841
+ "surfaceCouranteElevee",
1842
+ "surfaceActive",
1843
+ "surfaceSelection",
1844
+ "surfaceInverse",
1845
+ "surfaceMarque",
1846
+ "lisereFocus",
1847
+ "primaireDiscret",
1848
+ "primaireAttenue",
1849
+ "accentDiscret",
1850
+ "informationDiscret",
1851
+ "succesDiscret",
1852
+ "avertissementDiscret",
1853
+ "dangerDiscret",
1854
+ "dangerDiscretCourant"
1855
+ ],
1856
+ CanvasText: [
1857
+ "texte",
1858
+ "texteDiscret",
1859
+ "texteAttenue",
1860
+ "texteInverse",
1861
+ "texteSurMarque",
1862
+ "texteSurMarqueDiscret",
1863
+ "negatif",
1864
+ "bordure",
1865
+ "bordureForte",
1866
+ "accent",
1867
+ "accentSurvol",
1868
+ "information",
1869
+ "succes",
1870
+ "avertissement",
1871
+ "danger",
1872
+ "dangerSurvol",
1873
+ "dangerActif",
1874
+ "positif",
1875
+ "surPrimaireDiscret",
1876
+ "surPrimaireAttenue",
1877
+ "surAccentDiscret",
1878
+ "surInformationDiscret",
1879
+ "surSuccesDiscret",
1880
+ "surAvertissementDiscret",
1881
+ "surDangerDiscret"
1882
+ ],
1883
+ GrayText: ["texteDesactive"],
1884
+ LinkText: [
1885
+ "primaire",
1886
+ "primaireSurvol",
1887
+ "primaireActif",
1888
+ "textePrimaire"
1889
+ ],
1890
+ Highlight: [
1891
+ "selection",
1892
+ "bordureSelection",
1893
+ "anneauFocus"
1894
+ ],
1895
+ HighlightText: ["surSelection"]
1896
+ };
1897
+ const encresDAplat = [
1898
+ "surPrimaire",
1899
+ "surAccent",
1900
+ "surInformation",
1901
+ "surSucces",
1902
+ "surAvertissement",
1903
+ "surDanger"
1904
+ ];
1905
+ const derogationsDImpression = {
1906
+ fond: "#ffffff",
1907
+ surfaceSurvol: "#ffffff",
1908
+ surfaceActive: "#ffffff",
1909
+ surfaceSelection: "#ffffff",
1910
+ surfaceConteneurBas: "#ffffff",
1911
+ surfaceConteneurHaut: "#ffffff",
1912
+ texte: "#070d14",
1913
+ bordure: "#9da2a8",
1914
+ bordureForte: "#60676f",
1915
+ succes: "#07602f",
1916
+ avertissement: "#783f03",
1917
+ danger: "#960c19",
1918
+ information: "#0b5776",
1919
+ primaireDiscret: "#ffffff",
1920
+ primaireAttenue: "#ffffff",
1921
+ accentDiscret: "#ffffff",
1922
+ informationDiscret: "#ffffff",
1923
+ succesDiscret: "#ffffff",
1924
+ avertissementDiscret: "#ffffff",
1925
+ dangerDiscret: "#ffffff"
1926
+ };
1927
+ const taillesDImpression = {
1928
+ "2xs": "8pt",
1929
+ xs: "8pt",
1930
+ sm: "9pt",
1931
+ md: "10.5pt",
1932
+ lg: "12pt",
1933
+ xl: "13pt",
1934
+ "2xl": "15pt",
1935
+ "3xl": "18pt",
1936
+ "4xl": "22pt",
1937
+ "5xl": "28pt",
1938
+ "6xl": "32pt"
1939
+ };
1940
+ function declarations(valeurs, retrait) {
1941
+ return Object.entries(valeurs).map(([nom, valeur]) => `${retrait}${nom}: ${valeur};`).join("\n");
1942
+ }
1943
+ function blocContrasteForce() {
1944
+ const alias = {};
1945
+ for (const [systeme, jetons] of Object.entries(couleursSysteme)) for (const jeton of jetons) alias[nomDeCouleur(jeton)] = systeme;
1946
+ for (const couche of ["none", ...COUCHES]) alias[`${PREFIXE}-ombre-${couche}`] = "none";
1947
+ alias[`${PREFIXE}-epaisseur-capillaire`] = epaisseurCapillaire.base;
1948
+ return declarations(alias, " ");
1949
+ }
1950
+ function blocImpression() {
1951
+ const valeurs = {};
1952
+ for (const [jeton, valeur] of Object.entries(derogationsDImpression)) valeurs[nomDeCouleur(jeton)] = valeur;
1953
+ for (const couche of COUCHES) valeurs[`${PREFIXE}-ombre-${couche}`] = "none";
1954
+ valeurs[`${PREFIXE}-voile`] = "transparent";
1955
+ valeurs[`${PREFIXE}-epaisseur-capillaire`] = epaisseurCapillaire.base;
1956
+ for (const [cle, taille] of Object.entries(taillesDImpression)) valeurs[`${PREFIXE}-taille-${cle}`] = taille;
1957
+ return declarations(valeurs, " ");
1958
+ }
1959
+ function affichageSelonLeTheme(theme, retrait = " ") {
1960
+ const visible = "inline";
1961
+ return [`${retrait}${PREFIXE}-affichage-si-clair: ${theme === "clair" ? visible : "none"};`, `${retrait}${PREFIXE}-affichage-si-sombre: ${theme === "sombre" ? visible : "none"};`].join("\n");
600
1962
  }
601
1963
  function feuilleDeJetons() {
602
- return `/* Engendre par @starterlib/tokens — ne pas modifier a la main.
1964
+ return `/* Engendre par @starterlib/tokens \u2014 ne pas modifier a la main.
603
1965
  Regenerer : pnpm tokens:css */
604
1966
 
605
1967
  ${catalogueDesComposants()}
606
1968
 
607
1969
  :root {
608
1970
  color-scheme: light;
1971
+ ${affichageSelonLeTheme("clair")}
609
1972
  ${cssDesEchelles()}
610
- ${cssDuTheme('clair')}
1973
+ ${cssDuTheme()}
1974
+ ${variablesDeDensite("confortable")}
1975
+ }
1976
+
1977
+ /* Theme : la preference du systeme, puis l'attribut, qui l'emporte dans les deux
1978
+ sens. Sur la racine comme sur un conteneur : un ilot [data-theme] rend ses
1979
+ descendants dans son theme. */
1980
+ @media (prefers-color-scheme: dark) {
1981
+ :root {
1982
+ color-scheme: dark;
1983
+ ${affichageSelonLeTheme("sombre", " ")}
1984
+ }
611
1985
  }
612
1986
 
613
- :root[data-theme='sombre'] {
1987
+ [data-theme='sombre'] {
614
1988
  color-scheme: dark;
615
- ${cssDuTheme('sombre')}
1989
+ ${affichageSelonLeTheme("sombre")}
616
1990
  }
617
1991
 
618
- @media (prefers-color-scheme: dark) {
619
- :root:not([data-theme='clair']) {
620
- color-scheme: dark;
621
- ${cssDuTheme('sombre')
622
- .split('\n')
623
- .map((l) => ` ${l}`)
624
- .join('\n')}
1992
+ [data-theme='clair'] {
1993
+ color-scheme: light;
1994
+ ${affichageSelonLeTheme("clair")}
1995
+ }
1996
+
1997
+ /* Un ilot re-pose la couleur du texte : \`color\` s'herite RESOLUE, et le texte d'un
1998
+ ilot sombre heriterait sinon l'encre claire de la page. */
1999
+ :where([data-theme]):not(:root) {
2000
+ color: var(${PREFIXE}-couleur-texte);
2001
+ }
2002
+
2003
+ ::selection {
2004
+ color: var(${nomDeCouleur("surSelection")});
2005
+ background: var(${nomDeCouleur("selection")});
2006
+ }
2007
+
2008
+ /* Densite : un MODE, sur la racine ou sur une zone. Aucune densite ne descend
2009
+ sous la cible tactile sur un ecran tactile. */
2010
+ [data-densite='confortable'] {
2011
+ ${variablesDeDensite("confortable")}
2012
+ }
2013
+
2014
+ [data-densite='dense'] {
2015
+ ${variablesDeDensite("dense")}
2016
+ }
2017
+
2018
+ [data-densite='compacte'] {
2019
+ ${variablesDeDensite("compacte")}
2020
+ }
2021
+
2022
+ @media (pointer: coarse) {
2023
+ :root,
2024
+ [data-densite] {
2025
+ ${PREFIXE}-hauteur-commande-defaut: var(${PREFIXE}-hauteur-commande-xl);
2026
+ ${PREFIXE}-calendrier-jour-defaut: var(${PREFIXE}-cible-tactile-min);
625
2027
  }
626
2028
  }
627
2029
 
628
- @media (prefers-reduced-motion: reduce) {
2030
+ /* Un filet d'un vrai pixel d'ecran sur les dalles haute densite. */
2031
+ @media (min-resolution: 2dppx) {
629
2032
  :root {
630
- ${PREFIXE}-duree-rapide: 0ms;
631
- ${PREFIXE}-duree-normale: 0ms;
632
- ${PREFIXE}-duree-lente: 0ms;
2033
+ ${PREFIXE}-epaisseur-capillaire: ${epaisseurCapillaire.hauteDensite};
2034
+ }
2035
+ }
2036
+
2037
+ /* Mouvement reduit (01 \u00a76, 03 P5) : la preference du systeme, sauf quand la
2038
+ personne a choisi le mouvement complet (data-mouvement sur <html>, pose par
2039
+ StMouvement) ; puis l'attribut, qui l'impose \u2014 sur la racine ou sur une zone.
2040
+ Durees et deplacements a zero ; les cadences restent, un indicateur d'attente
2041
+ ne se fige jamais. \`:where(:root)\` : la regle pese (0,1,0), comme les autres. */
2042
+ @media (prefers-reduced-motion: reduce) {
2043
+ :where(:root):not([data-mouvement='complet']) {
2044
+ ${declarations(mouvementReduit, " ")}
633
2045
  }
634
2046
  }
635
2047
 
2048
+ [data-mouvement='reduit'] {
2049
+ ${declarations(mouvementReduit, " ")}
2050
+ }
2051
+
2052
+ /* Gel d'une image : au changement de theme ou de langue, rien ne transite le temps
2053
+ d'un rendu (StTheme et StLangue posent data-gel sur <html>). Place apres le
2054
+ mouvement : un mouvement complet choisi ne leve pas le gel. */
2055
+ [data-gel] {
2056
+ ${declarations(mouvementGele, " ")}
2057
+ }
2058
+
636
2059
  /* WCAG 2.4.11 : un arret de tabulation ne remonte jamais sous l'en-tete collant.
637
2060
  \`${PREFIXE}-hauteur-entete\` est posee sur <html> par st-app-shell, qui mesure
638
2061
  son en-tete ; sans coquille, le repli vaut zero et rien ne change. */
@@ -640,37 +2063,166 @@ html {
640
2063
  scroll-padding-block-start: var(${PREFIXE}-hauteur-entete, 0px);
641
2064
  }
642
2065
 
643
- /* Contraste renforce : le filet decoratif devient un filet fort, et le texte
644
- discret redevient du texte. Les deux jetons restent des ALIAS du theme
645
- courant, clair ou sombre. */
2066
+ /* Rythme vertical d'un formulaire, porte par le CONTENEUR (lot 200) : les champs du
2067
+ socle n'ont plus de marge externe, pour qu'un champ pose dans une ligne de
2068
+ reglage ou un popover ne traine pas 20 px qu'il n'a pas choisis. */
2069
+ .st-formulaire {
2070
+ display: grid;
2071
+ gap: var(${PREFIXE}-espacement-5);
2072
+ }
2073
+
2074
+ /* Texte pour les seuls lecteurs d'ecran (\u00ab , 5 non lus \u00bb d'un lien de navigation) : la classe
2075
+ est PUBLIEE ici, dans la feuille que l'hote charge deja. Emise dans la portee emulee de chaque
2076
+ composant, elle n'atteignait jamais un element ecrit dans le gabarit de l'APPELANT, dont le
2077
+ texte s'affichait en clair (constats A1099, A1082, lot 156). \`.st-vh-focalisable\` : masquee
2078
+ tant qu'elle ne contient pas le focus (un lien d'evitement) ; \`:where()\` garde le poids d'une
2079
+ classe, comme toute la feuille. */
2080
+ .st-vh,
2081
+ .st-vh-focalisable:where(:not(:focus-within)) {
2082
+ position: absolute;
2083
+ width: 1px;
2084
+ height: 1px;
2085
+ padding: 0;
2086
+ margin: -1px;
2087
+ overflow: hidden;
2088
+ clip-path: inset(50%);
2089
+ white-space: nowrap;
2090
+ border: 0;
2091
+ }
2092
+
2093
+ /* Contrat avec le CDK : ses surfaces (menus, popovers, infobulles, tiroirs,
2094
+ dialogues) s'empilent par l'ordre du DOM dans ce conteneur, qui lit le plan. */
2095
+ .cdk-overlay-container {
2096
+ z-index: var(${PREFIXE}-plan-superposition);
2097
+ }
2098
+
2099
+ /* Contraste renforce : alias du theme courant, clair ou sombre (01 \u00a77.1). */
646
2100
  @media (prefers-contrast: more) {
647
- ${indenter(RACINE_QUEL_QUE_SOIT_LE_THEME)} {
648
- ${PREFIXE}-couleur-bordure: var(${PREFIXE}-couleur-bordure-forte);
649
- ${PREFIXE}-couleur-texte-discret: var(${PREFIXE}-couleur-texte);
2101
+ :root {
2102
+ ${declarations(aliasDeContrasteRenforce, " ")}
2103
+ }
2104
+ }
2105
+
2106
+ /* Contraste force : les roles vers les couleurs systeme, aucune ombre (01 \u00a77.2). */
2107
+ @media (forced-colors: active) {
2108
+ :root {
2109
+ ${blocContrasteForce()}
650
2110
  }
651
2111
  }
652
2112
 
653
- /* Impression : les jetons clairs, quel que soit le theme choisi a l'ecran. Un
654
- fond sombre imprime est une page noire. */
2113
+ /* Impression : le theme clair et son encre, quel que soit le theme a l'ecran ou
2114
+ dans un ilot (01 \u00a77.3). Un fond sombre imprime est une page noire. */
655
2115
  @media print {
656
- ${indenter(RACINE_QUEL_QUE_SOIT_LE_THEME)} {
2116
+ :root,
2117
+ [data-theme] {
657
2118
  color-scheme: light;
658
- ${indenter(cssDuTheme('clair'))}
2119
+ ${affichageSelonLeTheme("clair", " ")}
2120
+ }
2121
+
2122
+ :root {
2123
+ ${blocImpression()}
2124
+ }
2125
+
2126
+ .st-hors-impression {
2127
+ display: none !important;
2128
+ }
2129
+
2130
+ /* Impression EXCLUSIVE (lot 263) : un composant qui la demande (les codes de secours) pose
2131
+ l'attribut sur <html> le temps du dialogue, et seule sa region s'imprime ; tout ce qui n'est
2132
+ ni elle, ni dedans, ni l'un de ses ancetres disparait. Poids nul (:where) comme le reste de
2133
+ la feuille ; !important l'emporte sur l'affichage des composants. */
2134
+ :where(
2135
+ :root[data-st-impression-exclusive]
2136
+ body
2137
+ *:not([data-st-imprimable], [data-st-imprimable] *, :has([data-st-imprimable]))
2138
+ ) {
2139
+ display: none !important;
659
2140
  }
660
- }
661
- `;
662
2141
  }
663
2142
 
664
- /**
665
- * @starterlib/tokens — niveau 1 et 2 du systeme de design.
666
- *
667
- * Ce paquet ne depend d'AUCUNE bibliotheque, Angular compris : il doit rester
668
- * consommable par un rendu de courriel, un export PDF ou un outil de design.
669
- */
2143
+ @media not print {
2144
+ .st-impression-seulement {
2145
+ display: none !important;
2146
+ }
2147
+ }
670
2148
 
671
- /**
672
- * Generated bundle index. Do not edit.
673
- */
2149
+ @page {
2150
+ margin: 16mm 14mm;
2151
+ }
2152
+ `;
2153
+ }
2154
+ function nombreDeJetonsDeCouleur(jetons = themeClair) {
2155
+ return Object.keys(jetons).length;
2156
+ }
2157
+ function jetonsDtcg() {
2158
+ const jetons = {
2159
+ $description: "Jetons de @starterlib/tokens au format DTCG 2025.10. Engendre par tools/generer-css.mjs : ne pas modifier.",
2160
+ couleur: {
2161
+ clair: couleurs(themeClair),
2162
+ sombre: couleurs(themeSombre)
2163
+ },
2164
+ espacement: dimensions(espacement),
2165
+ rayon: dimensions(rayon),
2166
+ taille: dimensions(typographie.taille),
2167
+ interligne: dimensions(typographie.interligne),
2168
+ graisse: Object.fromEntries(Object.entries(typographie.graisse).map(([cle, valeur]) => [cle, {
2169
+ $type: "fontWeight",
2170
+ $value: Number(valeur)
2171
+ }])),
2172
+ police: Object.fromEntries(Object.entries(typographie.famille).map(([cle, pile]) => [cle, {
2173
+ $type: "fontFamily",
2174
+ $value: familles(pile)
2175
+ }])),
2176
+ duree: Object.fromEntries(Object.entries(duree).map(([cle, valeur]) => [cle, {
2177
+ $type: "duration",
2178
+ $value: {
2179
+ value: Number.parseFloat(valeur),
2180
+ unit: "ms"
2181
+ }
2182
+ }]))
2183
+ };
2184
+ return `${JSON.stringify(jetons, null, 2)}\n`;
2185
+ }
2186
+ function couleurs(theme) {
2187
+ const noms = Object.keys(theme);
2188
+ return Object.fromEntries(noms.map((nom) => [nom, {
2189
+ $type: "color",
2190
+ $value: couleur(theme[nom])
2191
+ }]));
2192
+ }
2193
+ function couleur(hex) {
2194
+ return {
2195
+ colorSpace: "srgb",
2196
+ components: [
2197
+ 1,
2198
+ 3,
2199
+ 5
2200
+ ].map((debut) => Number.parseInt(hex.slice(debut, debut + 2), 16) / 255).map((canal) => Number(canal.toFixed(4))),
2201
+ alpha: 1,
2202
+ hex
2203
+ };
2204
+ }
2205
+ function dimensions(valeurs) {
2206
+ return Object.fromEntries(Object.entries(valeurs).map(([cle, valeur]) => [cle, {
2207
+ $type: "dimension",
2208
+ $value: dimension(valeur)
2209
+ }]));
2210
+ }
2211
+ function dimension(valeur) {
2212
+ if (valeur === "0") return {
2213
+ value: 0,
2214
+ unit: "px"
2215
+ };
2216
+ const lu = /^(-?\d*\.?\d+)(px|rem)$/.exec(valeur);
2217
+ if (lu === null) throw new Error(`Dimension hors du format DTCG (px ou rem) : ${valeur}`);
2218
+ return {
2219
+ value: Number(lu[1]),
2220
+ unit: lu[2]
2221
+ };
2222
+ }
2223
+ function familles(pile) {
2224
+ return pile.split(",").map((famille) => famille.trim().replace(/^'(.*)'$/, "$1"));
2225
+ }
2226
+ export { CLES_DE_TAILLE, COUCHES, C_PAS, FAMILLES, L_PAS, MODES_DE_DENSITE, PAS_DE_RAMPE, PAS_NEUTRES, PREFIXE, RANGS_DE_SERIE, RUPTURES, SEUIL_DE_PERCEPTIBILITE, aliasDeContrasteRenforce, aliasDeContrasteRenforceParJeton, apca, cadence, cadenceMinimale, cibleMinimale, cibleTactileMinimale, composer, contratsDHote, couleurDOmbre, couleursSysteme, courbe, cssDesEchelles, cssDuTheme, declarationsDuStyle, definitionDuPas, definitionsDesPrimitives, delai, densite, deplacement, derogationsDImpression, duree, ecartsDeSurface, enKebab, encresDAplat, encresDeContrasteRenforce, epaisseur, epaisseurCapillaire, espacement, estJetonDeComposant, exemptesDeContraste, feuilleDeJetons, feuilleDesPolices, fichiersAPrecharger, focus, hauteurDeCommande, hexVersOklch, interlettrageDuStyle, jetonsDeComposant, jetonsDtcg, largeur, lireCouleur, luminanceRelative, mouvementGele, mouvementReduit, nomDeCouleur, nombreDeJetonsDeCouleur, oklchEnCss, oklchVersHex, oklchVersSrgb, ombreDuTheme, ombres, pairesDeContraste, pairesExclues, partielDeTypographie, partielDesRuptures, plan, policesDuSocle, primitives, rapportDeContraste, rayon, respecteLeContraste, rolesDesPrimitives, rupture, rythme, seuilExige, stylesTypographiques, surchargesDuRepli, surfacesDuFocus, taillesDImpression, tenue, themeClair, themeSombre, themeSousContrasteRenforce, themes, typographie, variablesInternes, versRvb, voile };
674
2227
 
675
- export { PREFIXE, RUPTURES, SEUIL_DE_PERCEPTIBILITE, cibleMinimale, cibleTactileMinimale, courbe, cssDesEchelles, cssDuTheme, duree, epaisseur, espacement, estJetonDeComposant, exemptesDeContraste, feuilleDeJetons, jetonsDeComposant, luminanceRelative, mesure, ombre, pairesDeContraste, partielDesRuptures, plan, primitives, rapportDeContraste, rayon, respecteLeContraste, respiration, rupture, seuilExige, themeClair, themeSombre, themes, typographie, versRvb };
676
- //# sourceMappingURL=starterlib-tokens.mjs.map
2228
+ //# sourceMappingURL=starterlib-tokens.mjs.map