@medquantis/design-system 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/DESIGN.md ADDED
@@ -0,0 +1,1059 @@
1
+ ---
2
+ name: MedQuantis
3
+ description: The MedQuantis design system 1.2.0. Generated from tokens/ and guidelines/ by the design-system build. Do not edit. Names are the homemade-claude-kit role names where the kit has a role, else the tokens.css name without --mq-; the tables below map each to its --mq-* token.
4
+ colors:
5
+ primary: "{colors.accent}"
6
+ ink: "#1a1a1a"
7
+ muted: "#555555"
8
+ tertiary: "#707070"
9
+ canvas-bg: "#fafaf8"
10
+ surface: "#ffffff"
11
+ surface-2: "#f5f4f1"
12
+ border: "#dddbd6"
13
+ accent: "#0a2463"
14
+ accent-ink: "#ffffff"
15
+ success: "#2e7d32"
16
+ warning: "#a06400"
17
+ danger: "#d8232a"
18
+ info: "#1565c0"
19
+ chart-1: "#0a2463"
20
+ chart-2: "#5d6f91"
21
+ chart-3: "#8795b3"
22
+ chart-4: "#c3cbdc"
23
+ chart-rest: "#dde3ee"
24
+ chart-axis: "#8c8c8c"
25
+ chart-grid: "#dddbd6"
26
+ chart-highlight: "#0a2463"
27
+ accent-hover: "#061a4d"
28
+ accent-pale: "#eaeef7"
29
+ accent-pale-2: "#f2f4f9"
30
+ accent-highlight: "#0a2463"
31
+ decoration: "#8c8c8c"
32
+ desk: "#e9e8e4"
33
+ border-light: "#ebeae6"
34
+ border-strong: "#5d6f91"
35
+ panel-start: "#0f2a5e"
36
+ panel-end: "#0a2047"
37
+ on-navy-title: "#ffffff"
38
+ on-navy-secondary: rgba(255, 255, 255, 0.68)
39
+ on-navy-second-level: "#c3cbdc"
40
+ on-navy-accent: "#8795b3"
41
+ on-navy-rule: rgba(255, 255, 255, 0.16)
42
+ on-navy-rule-strong: rgba(255, 255, 255, 0.18)
43
+ on-navy-pill: rgba(255, 255, 255, 0.12)
44
+ on-navy-discreet: rgba(255, 255, 255, 0.4)
45
+ on-warning-fill: "#1a1a1a"
46
+ warning-fill: "#d4850a"
47
+ warning-tint: "#fbf3e7"
48
+ dim: "#707070"
49
+ accent-on-dark: "#8795b3"
50
+ focus-ring-color: "#0a2463"
51
+ focus-ring-color-on-navy: "#ffffff"
52
+ dark-ink: "#f5f4f1"
53
+ dark-muted: "#c3cbdc"
54
+ dark-tertiary: "#8f98aa"
55
+ dark-canvas-bg: "#11161f"
56
+ dark-surface: "#1a2131"
57
+ dark-surface-2: "#141a26"
58
+ dark-border: rgba(255, 255, 255, 0.14)
59
+ dark-accent: "#6f88c9"
60
+ dark-accent-ink: "#11161f"
61
+ dark-success: "#6fbf73"
62
+ dark-warning: "#e0a040"
63
+ dark-danger: "#ff6b6b"
64
+ dark-info: "#64a8f0"
65
+ dark-chart-1: "#9fb3e8"
66
+ dark-chart-2: "#6f88c9"
67
+ dark-chart-3: "#5d6f91"
68
+ dark-chart-4: "#3a4560"
69
+ dark-chart-rest: "#232b3d"
70
+ dark-chart-axis: "#6b7385"
71
+ dark-chart-grid: rgba(255, 255, 255, 0.1)
72
+ dark-chart-highlight: "#9fb3e8"
73
+ dark-accent-hover: "#8aa0d6"
74
+ dark-accent-pale: rgba(111, 136, 201, 0.18)
75
+ dark-accent-pale-2: rgba(111, 136, 201, 0.1)
76
+ dark-accent-highlight: "#9fb3e8"
77
+ dark-decoration: "#6b7385"
78
+ dark-desk: "#0b0f16"
79
+ dark-border-light: rgba(255, 255, 255, 0.1)
80
+ dark-border-strong: "#8795b3"
81
+ dark-panel-start: "#2f4c8f"
82
+ dark-panel-end: "#0f2a5e"
83
+ dark-on-navy-accent: "#b2c2ed"
84
+ dark-on-navy-discreet: rgba(255, 255, 255, 0.5)
85
+ dark-on-warning-fill: "#11161f"
86
+ dark-warning-fill: "#d4850a"
87
+ dark-warning-tint: rgba(212, 133, 10, 0.16)
88
+ dark-dim: "#8f98aa"
89
+ dark-accent-on-dark: "#b2c2ed"
90
+ dark-focus-ring-color: "#9fb3e8"
91
+ typography:
92
+ body:
93
+ fontFamily: Newsreader, Georgia, 'Times New Roman', serif
94
+ fontSize: 16.5px
95
+ fontWeight: 400
96
+ lineHeight: 1.55
97
+ heading:
98
+ fontFamily: Outfit, system-ui, -apple-system, 'Segoe UI', sans-serif
99
+ fontSize: 32px
100
+ fontWeight: 500
101
+ lineHeight: 1.15
102
+ mono:
103
+ fontFamily: "'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace"
104
+ text-xs:
105
+ fontSize: 11px
106
+ text-sm:
107
+ fontSize: 13px
108
+ text-lg:
109
+ fontSize: 20px
110
+ text-xl:
111
+ fontSize: 56px
112
+ rounded:
113
+ card-radius: 6px
114
+ btn-radius: 3px
115
+ node-radius: 6px
116
+ radius-sm: 3px
117
+ radius-pill: 6px
118
+ radius-overlay: 12px
119
+ spacing:
120
+ space-sm: 8px
121
+ space-md: 16px
122
+ space-lg: 32px
123
+ space-xs: 4px
124
+ space-xl: 48px
125
+ space-3: 12px
126
+ space-5: 24px
127
+ space-8: 72px
128
+ ---
129
+
130
+ # MedQuantis design
131
+
132
+ Generated from tokens/ and guidelines/ by the build of @medquantis/design-system 1.2.0. Do not edit. Each section opens with a table that maps the front matter names to the --mq-* custom properties of tokens.css, the names the guideline text uses; the rules are the MedQuantis guidelines, section by section.
133
+
134
+ ## Overview
135
+
136
+ ### Principles: Introduction
137
+
138
+ These principles decide the cases the other guidelines do not cover. When two rules seem to conflict, the earlier principle wins.
139
+
140
+ 1. **Clinical clarity.** Sober and high-contrast. Nothing is decorative unless it carries meaning: colour communicates a role, a state or an emphasis, never an ornament. Every coloured element must earn its place.
141
+ 2. **One accent.** Navy (`--mq-accent`, `#0a2463`) is the only brand accent, on every surface (ADR-0003). Everything else is a warm neutral or the blue-grey family. Interactive apps add status colours for genuine system status only (ADR-0004).
142
+ 3. **The figure before the adjective.** A claim is backed by a number, and every number can be traced to its source. Figures are the hero of a page: they get the largest type, the accent colour and room around them. Any fictitious figure says so on the page.
143
+ 4. **Calm confidence.** Pale neutral surfaces, generous whitespace, one idea per block and one hero figure per column. When a page is full, cut content before shrinking text.
144
+ 5. **Document-grade.** Every document reads as well printed on A4 or exported to PDF as on a screen. A design that only works on a screen is not finished.
145
+ 6. **Sober motion, never required.** Motion is optional, short and carries meaning (a bar grows, a line draws). Nothing loops, and print always shows the final state.
146
+ 7. **Three families, three roles.** Outfit for headings, labels and app UI; Newsreader for reading text; Geist Mono for codes, table figures and dates (typography).
147
+ 8. **One system, many media.** Slides, A4 reports, papers, the website, newsletters and apps share the same tokens. Media differ in what they may use (documents never carry status colours), never in the values themselves.
148
+
149
+ ### Brand: Name
150
+
151
+ - **MedQuantis** is the company and the public brand. It is the name on every slide, report, paper, page and email a reader outside the company sees.
152
+ - It is written as one word with a capital M and a capital Q: MedQuantis. Never Medquantis, MEDQUANTIS in running text, or Med Quantis.
153
+ - **Paracel** names the technology (the engine and its data), never the company. Use it only when the sentence is about the technology itself; to a client, the company that speaks is MedQuantis.
154
+
155
+ ### Brand: Tagline
156
+
157
+ | Language | Tagline |
158
+ | :-- | :-- |
159
+ | French | L'IA suisse au service des systèmes de santé |
160
+ | German | Schweizer KI für das Gesundheitswesen |
161
+ | English | Swiss AI for healthcare systems |
162
+ | Italian | Not yet defined, owner to provide |
163
+
164
+ The French line is the reference; the German and English lines are its approved translations. Do not write an Italian tagline until the owner provides one.
165
+
166
+ ### Voice: Tone
167
+
168
+ - **Expert to expert.** Write to a professional who knows the field: confident, never boastful. The work is shown, not promised.
169
+ - **Partnership.** "We" is MedQuantis, "you" is the reader's organisation. Write with the reader, not at them.
170
+ - **Numbers before adjectives.** Replace "fast", "easy" or "significant" with the figure that proves it. A claim of ease or speed without a number is cut.
171
+ - **No hype.** Do not oversell artificial intelligence; describe what the system does and how it can be checked.
172
+ - **No humour.** The material is about health insurance, public money and patients.
173
+ - **No jargon for clients.** No query languages, table names or internal system names in client material.
174
+ - **Respect the client.** In a document written for a client, do not judge the client's past choices and do not estimate their internal costs. The only figures put forward are the ones our work establishes.
175
+
176
+ ### Voice: Words
177
+
178
+ Prefer wording a reader can verify: traced to the exact clause, executable, auditable, reproducible.
179
+
180
+ Avoid: AI-powered, revolutionary, seamless, easy, simply, just. In French, also révolutionnaire, simplement, facile, juste; in German, revolutionär, einfach, nahtlos, nur; in Italian, rivoluzionario, semplicemente, facile, solo.
181
+
182
+ ### Voice: Languages
183
+
184
+ - English is the source language of shared material; French, German and Italian versions are translations of it and must read as if written in that language.
185
+ - German is formal Swiss Standard German: the reader is addressed as Sie, and the sharp s is always written ss.
186
+ - French follows French typography, including non-breaking spaces before high punctuation (writing).
187
+ - One term per concept across all four languages, taken from the table below. Product names and the names of regulatory lists keep their capitals.
188
+
189
+ ### Voice: Terminology
190
+
191
+ | Concept | French | German | Italian | English |
192
+ | :-- | :-- | :-- | :-- | :-- |
193
+ | The list of reimbursed medicines | Liste des spécialités (LS) | Spezialitätenliste (SL) | Elenco delle specialità (ES) | Specialities List (SL) |
194
+ | Federal Office of Public Health, the regulator | Office fédéral de la santé publique (OFSP) | Bundesamt für Gesundheit (BAG) | Ufficio federale della sanità pubblica (UFSP) | Federal Office of Public Health (FOPH) |
195
+ | Mandatory health insurance | assurance obligatoire des soins (AOS) | obligatorische Krankenpflegeversicherung (OKP) | assicurazione obbligatoria delle cure medico-sanitarie (AOMS) | mandatory health insurance |
196
+ | Health insurer | assureur-maladie | Krankenversicherer | assicuratore malattia | health insurer |
197
+ | Price model | modèle de prix | Preismodell | modello di prezzo | price model |
198
+ | Refund owed under a price model | restitution | Rückerstattung | restituzione | refund |
199
+ | Limitation (conditions of reimbursement) | limitation | Limitatio | limitazione | limitation |
200
+ | Prior cost approval | garantie de prise en charge | Kostengutsprache | garanzia di assunzione dei costi | cost approval |
201
+ | Health insurance ordinance, cited by article | art. 71 OAMal | Art. 71 KVV | art. 71 OAMal | Art. 71 KVV |
202
+ | The insurer's executive management | direction | Geschäftsleitung | direzione | executive management |
203
+ | Fictitious figures | chiffres illustratifs | illustrative Zahlen | cifre illustrative | illustrative figures |
204
+
205
+ The regulator is never "the board": the board, or executive management, belongs to the client.
206
+
207
+ ### Writing: Titles
208
+
209
+ - Sentence case: capital on the first word and on proper nouns only.
210
+ - No full stop at the end of a title, even a title of two sentences (drop only the final one).
211
+ - Eyebrows and section labels are typed in normal case; CSS sets them in capitals (typography).
212
+
213
+ ### Writing: Sentences and lists
214
+
215
+ - Write full sentences with a subject and a verb. An idea that matters gets two or three sentences, not a bullet.
216
+ - Distrust triads and mechanical bullet lists. A list is for items that are genuinely parallel.
217
+ - Body pages: short sentences, one idea per bullet.
218
+ - Summary and agenda pages: one line per point, readable in three seconds. The detail lives in the body pages.
219
+
220
+ ### Writing: French typography
221
+
222
+ - A non-breaking space (U+00A0) before `:`, `;`, `?`, `!` and `%`.
223
+ - French quotation marks with non-breaking spaces inside: « exemple ».
224
+ - Regulatory abbreviations as listed in voice.
225
+
226
+ ### Writing: Source files
227
+
228
+ Markdown is written one line per paragraph or list item, never hard-wrapped at a fixed width. Editors wrap the display; the file keeps one logical line per paragraph.
229
+
230
+ ## Colors
231
+
232
+ | DESIGN.md | tokens.css | Value |
233
+ | :-- | :-- | :-- |
234
+ | `colors.primary` | `--mq-accent` | `{colors.accent}` |
235
+ | `colors.ink` | `--mq-ink` | `#1a1a1a` |
236
+ | `colors.muted` | `--mq-muted` | `#555555` |
237
+ | `colors.tertiary` | `--mq-tertiary` | `#707070` |
238
+ | `colors.canvas-bg` | `--mq-canvas` | `#fafaf8` |
239
+ | `colors.surface` | `--mq-surface` | `#ffffff` |
240
+ | `colors.surface-2` | `--mq-surface-alt` | `#f5f4f1` |
241
+ | `colors.border` | `--mq-border` | `#dddbd6` |
242
+ | `colors.accent` | `--mq-accent` | `#0a2463` |
243
+ | `colors.accent-ink` | `--mq-on-accent` | `#ffffff` |
244
+ | `colors.success` | `--mq-success` | `#2e7d32` |
245
+ | `colors.warning` | `--mq-warning` | `#a06400` |
246
+ | `colors.danger` | `--mq-danger` | `#d8232a` |
247
+ | `colors.info` | `--mq-info` | `#1565c0` |
248
+ | `colors.chart-1` | `--mq-chart-series-1` | `#0a2463` |
249
+ | `colors.chart-2` | `--mq-chart-series-2` | `#5d6f91` |
250
+ | `colors.chart-3` | `--mq-chart-series-3` | `#8795b3` |
251
+ | `colors.chart-4` | `--mq-chart-series-4` | `#c3cbdc` |
252
+ | `colors.chart-rest` | `--mq-chart-rest` | `#dde3ee` |
253
+ | `colors.chart-axis` | `--mq-chart-axis` | `#8c8c8c` |
254
+ | `colors.chart-grid` | `--mq-chart-baseline` | `#dddbd6` |
255
+ | `colors.chart-highlight` | `--mq-chart-series-1` | `#0a2463` |
256
+ | `colors.accent-hover` | `--mq-accent-hover` | `#061a4d` |
257
+ | `colors.accent-pale` | `--mq-accent-pale` | `#eaeef7` |
258
+ | `colors.accent-pale-2` | `--mq-accent-pale-2` | `#f2f4f9` |
259
+ | `colors.accent-highlight` | `--mq-accent-highlight` | `#0a2463` |
260
+ | `colors.decoration` | `--mq-decoration` | `#8c8c8c` |
261
+ | `colors.desk` | `--mq-desk` | `#e9e8e4` |
262
+ | `colors.border-light` | `--mq-border-light` | `#ebeae6` |
263
+ | `colors.border-strong` | `--mq-border-strong` | `#5d6f91` |
264
+ | `colors.panel-start` | `--mq-panel-start` | `#0f2a5e` |
265
+ | `colors.panel-end` | `--mq-panel-end` | `#0a2047` |
266
+ | `colors.on-navy-title` | `--mq-on-navy-title` | `#ffffff` |
267
+ | `colors.on-navy-secondary` | `--mq-on-navy-secondary` | `rgba(255, 255, 255, 0.68)` |
268
+ | `colors.on-navy-second-level` | `--mq-on-navy-second-level` | `#c3cbdc` |
269
+ | `colors.on-navy-accent` | `--mq-on-navy-accent` | `#8795b3` |
270
+ | `colors.on-navy-rule` | `--mq-on-navy-rule` | `rgba(255, 255, 255, 0.16)` |
271
+ | `colors.on-navy-rule-strong` | `--mq-on-navy-rule-strong` | `rgba(255, 255, 255, 0.18)` |
272
+ | `colors.on-navy-pill` | `--mq-on-navy-pill` | `rgba(255, 255, 255, 0.12)` |
273
+ | `colors.on-navy-discreet` | `--mq-on-navy-discreet` | `rgba(255, 255, 255, 0.4)` |
274
+ | `colors.on-warning-fill` | `--mq-on-warning-fill` | `#1a1a1a` |
275
+ | `colors.warning-fill` | `--mq-warning-fill` | `#d4850a` |
276
+ | `colors.warning-tint` | `--mq-warning-tint` | `#fbf3e7` |
277
+ | `colors.dim` | `--mq-dim` | `#707070` |
278
+ | `colors.accent-on-dark` | `--mq-accent-on-dark` | `#8795b3` |
279
+ | `colors.focus-ring-color` | `--mq-focus-ring-color` | `#0a2463` |
280
+ | `colors.focus-ring-color-on-navy` | `--mq-focus-ring-color-on-navy` | `#ffffff` |
281
+ | `colors.dark-ink` | `--mq-ink` in `.dark` | `#f5f4f1` |
282
+ | `colors.dark-muted` | `--mq-muted` in `.dark` | `#c3cbdc` |
283
+ | `colors.dark-tertiary` | `--mq-tertiary` in `.dark` | `#8f98aa` |
284
+ | `colors.dark-canvas-bg` | `--mq-canvas` in `.dark` | `#11161f` |
285
+ | `colors.dark-surface` | `--mq-surface` in `.dark` | `#1a2131` |
286
+ | `colors.dark-surface-2` | `--mq-surface-alt` in `.dark` | `#141a26` |
287
+ | `colors.dark-border` | `--mq-border` in `.dark` | `rgba(255, 255, 255, 0.14)` |
288
+ | `colors.dark-accent` | `--mq-accent` in `.dark` | `#6f88c9` |
289
+ | `colors.dark-accent-ink` | `--mq-on-accent` in `.dark` | `#11161f` |
290
+ | `colors.dark-success` | `--mq-success` in `.dark` | `#6fbf73` |
291
+ | `colors.dark-warning` | `--mq-warning` in `.dark` | `#e0a040` |
292
+ | `colors.dark-danger` | `--mq-danger` in `.dark` | `#ff6b6b` |
293
+ | `colors.dark-info` | `--mq-info` in `.dark` | `#64a8f0` |
294
+ | `colors.dark-chart-1` | `--mq-chart-series-1` in `.dark` | `#9fb3e8` |
295
+ | `colors.dark-chart-2` | `--mq-chart-series-2` in `.dark` | `#6f88c9` |
296
+ | `colors.dark-chart-3` | `--mq-chart-series-3` in `.dark` | `#5d6f91` |
297
+ | `colors.dark-chart-4` | `--mq-chart-series-4` in `.dark` | `#3a4560` |
298
+ | `colors.dark-chart-rest` | `--mq-chart-rest` in `.dark` | `#232b3d` |
299
+ | `colors.dark-chart-axis` | `--mq-chart-axis` in `.dark` | `#6b7385` |
300
+ | `colors.dark-chart-grid` | `--mq-chart-baseline` in `.dark` | `rgba(255, 255, 255, 0.1)` |
301
+ | `colors.dark-chart-highlight` | `--mq-chart-series-1` in `.dark` | `#9fb3e8` |
302
+ | `colors.dark-accent-hover` | `--mq-accent-hover` in `.dark` | `#8aa0d6` |
303
+ | `colors.dark-accent-pale` | `--mq-accent-pale` in `.dark` | `rgba(111, 136, 201, 0.18)` |
304
+ | `colors.dark-accent-pale-2` | `--mq-accent-pale-2` in `.dark` | `rgba(111, 136, 201, 0.1)` |
305
+ | `colors.dark-accent-highlight` | `--mq-accent-highlight` in `.dark` | `#9fb3e8` |
306
+ | `colors.dark-decoration` | `--mq-decoration` in `.dark` | `#6b7385` |
307
+ | `colors.dark-desk` | `--mq-desk` in `.dark` | `#0b0f16` |
308
+ | `colors.dark-border-light` | `--mq-border-light` in `.dark` | `rgba(255, 255, 255, 0.1)` |
309
+ | `colors.dark-border-strong` | `--mq-border-strong` in `.dark` | `#8795b3` |
310
+ | `colors.dark-panel-start` | `--mq-panel-start` in `.dark` | `#2f4c8f` |
311
+ | `colors.dark-panel-end` | `--mq-panel-end` in `.dark` | `#0f2a5e` |
312
+ | `colors.dark-on-navy-accent` | `--mq-on-navy-accent` in `.dark` | `#b2c2ed` |
313
+ | `colors.dark-on-navy-discreet` | `--mq-on-navy-discreet` in `.dark` | `rgba(255, 255, 255, 0.5)` |
314
+ | `colors.dark-on-warning-fill` | `--mq-on-warning-fill` in `.dark` | `#11161f` |
315
+ | `colors.dark-warning-fill` | `--mq-warning-fill` in `.dark` | `#d4850a` |
316
+ | `colors.dark-warning-tint` | `--mq-warning-tint` in `.dark` | `rgba(212, 133, 10, 0.16)` |
317
+ | `colors.dark-dim` | `--mq-dim` in `.dark` | `#8f98aa` |
318
+ | `colors.dark-accent-on-dark` | `--mq-accent-on-dark` in `.dark` | `#b2c2ed` |
319
+ | `colors.dark-focus-ring-color` | `--mq-focus-ring-color` in `.dark` | `#9fb3e8` |
320
+
321
+ ### Colour: Introduction
322
+
323
+ Use semantic tokens only. The `--mq-core-*` names hold raw values and exist so semantic tokens can point at them; a consumer never names them. Every value below is generated in `dist/tokens.css`, and every contrast ratio comes from `tokens/contrast-pairs.json`, checked by the token test and shown in the specimen.
324
+
325
+ ### Colour: The medium split
326
+
327
+ | Medium | Colours allowed | How a state is shown |
328
+ | :-- | :-- | :-- |
329
+ | Documents: slides, A4 reports, papers, newsletters, the website | Navy, the neutrals, the blue-grey family, the chart tokens | By mark style: solid, ring, pale, outline, quiet, dashed, struck through |
330
+ | Interactive apps | Everything documents use, plus `--mq-success`, `--mq-warning`, `--mq-danger`, `--mq-info` | Status colour for genuine system status only (errors, validation, alerts, confirmations), always with an icon or a word |
331
+
332
+ Documents never carry green, orange, red, sky blue, pink, purple or turquoise, not even to say good or bad (ADR-0004). They never load `css/app.css`. A callout in a document is always navy; there is no warning-orange or success-green callout.
333
+
334
+ ### Colour: Accent
335
+
336
+ | Token | Value | Use |
337
+ | :-- | :-- | :-- |
338
+ | `--mq-accent` | `#0a2463` | The single accent: key figures, links, active states, the key column of a table, top and edge rules, series 1, navy panels |
339
+ | `--mq-accent-hover` | `#061a4d` | Hover and pressed state of a link or an accent control, nothing else |
340
+ | `--mq-accent-pale` | `#eaeef7` | Icon pills and chips, a deviating value, the highlighted table row, the pale state |
341
+ | `--mq-accent-pale-2` | `#f2f4f9` | Progress tracks, the quiet state, callout background |
342
+ | `--mq-on-accent` | `#ffffff` | Text and icons on an accent fill (14.53:1) |
343
+ | `--mq-accent-highlight` | `#0a2463` | Accent text on the pale fills (the pale KPI value, the pale badge, the callout title) and large highlighted title text: the accent itself in light, the light navy on dark (owner ruling 18) |
344
+
345
+ Navy carries emphasis on its own. Status colours never replace it for emphasis, in any medium.
346
+
347
+ ### Colour: Neutrals
348
+
349
+ In the light theme the neutrals are warm greys tuned to the warm canvas, never blue-tinted greys; the neutrals of the dark theme carry the navy cast of its canvas (Dark theme).
350
+
351
+ | Token | Value | Use | Contrast |
352
+ | :-- | :-- | :-- | :-- |
353
+ | `--mq-ink` | `#1a1a1a` | Primary text and values | 16.65:1 on canvas |
354
+ | `--mq-muted` | `#555555` | Secondary text, leads, captions | 7.13:1 on canvas |
355
+ | `--mq-tertiary` | `#707070` | Tertiary text: notes, footers, axis labels, placeholders (owner ruling 1) | 4.74:1 on canvas, 4.95:1 on surface |
356
+ | `--mq-decoration` | `#8c8c8c` | Non-text decoration only: axis lines, ticks, an icon beside its label. Never text (owner ruling 1) | 3.22:1 on canvas |
357
+ | `--mq-canvas` | `#fafaf8` | Background of every document page: A4 report, paper, slide (owner ruling 9) | |
358
+ | `--mq-surface` | `#ffffff` | Cards, panels, tables, figures, inputs | |
359
+ | `--mq-surface-alt` | `#f5f4f1` | Recessed areas: table headers, tab bars, grouped areas, hover | |
360
+ | `--mq-desk` | `#e9e8e4` | On screen only, around a page preview; never inside a document, so a printed page is never grey (owner ruling 9) | |
361
+ | `--mq-border` | `#dddbd6` | Borders, dividers, chart baselines | decorative |
362
+ | `--mq-border-light` | `#ebeae6` | Hairlines between rows and columns, card borders | decorative |
363
+ | `--mq-border-strong` | `#5d6f91` | Outline of outlined and dashed state marks | 4.84:1 on canvas, non-text |
364
+
365
+ Why a warm canvas: pure white reads clinical and generic; the warm off-white reads institutional while white cards still stand out on it. Tertiary text sits exactly at 4.50:1 on `--mq-surface-alt`, so put it on canvas or surface, not on the recessed grey.
366
+
367
+ ### Colour: Navy panels and gradients
368
+
369
+ Navy panels, the report header and the navy KPI tile use a gradient between `--mq-panel-start` (`#0f2a5e`) and `--mq-panel-end` (`#0a2047`) at 160 degrees. Contrast is always checked against `--mq-panel-start`, the lighter stop and the worst case.
370
+
371
+ Text and marks on navy follow owner ruling 2. There is no sky blue anywhere.
372
+
373
+ | Token | Value | Use | On navy | On panel start | Allowed for |
374
+ | :-- | :-- | :-- | :-- | :-- | :-- |
375
+ | `--mq-on-navy-title` | `#ffffff` | Titles, figures, icons | 14.53:1 | 13.88:1 | Text |
376
+ | `--mq-on-navy-secondary` | white at 68 % | Secondary text, kickers, labels | 7.40:1 | 7.18:1 | Text |
377
+ | `--mq-on-navy-second-level` | `#c3cbdc` | Second-level text, descriptions | 8.92:1 | 8.52:1 | Text |
378
+ | `--mq-on-navy-accent` | `#8795b3` | A one-off accent: one word, one dot | 4.83:1 | 4.61:1 | Text, narrow margin |
379
+ | `--mq-on-navy-discreet` | white at 40 % | Discreet numbers | 3.48:1 | 3.44:1 | Large text or non-text only |
380
+ | `--mq-on-navy-rule`, `--mq-on-navy-rule-strong` | white at 16 and 18 % | Rules and separators | 1.61:1 | 1.62:1 | Decorative only |
381
+ | `--mq-on-navy-pill` | white at 12 % | Icon pill fill; the icon on it stays white (10.31:1) | 1.41:1 | | Decorative only |
382
+
383
+ Use white icons on navy, including Lucide icons (set the SVG stroke). One large navy block per screen or page area (layout).
384
+
385
+ ### Colour: States by mark style (documents)
386
+
387
+ | Mark | Look | Meaning |
388
+ | :-- | :-- | :-- |
389
+ | Solid | Navy fill, white text | Acquired, paid, confirmed |
390
+ | Ring | White fill, navy outline and text | Approved, validated, in progress |
391
+ | Pale | Pale navy fill, navy text | Corrected, for information |
392
+ | Outline | White fill, blue-grey outline, ink text | Neutral, pending |
393
+ | Quiet | Quiet pale fill, muted text | Withdrawn, excluded, not applicable |
394
+ | Dashed | Dashed blue-grey outline, muted text | Upcoming, not yet claimed, on the roadmap |
395
+ | Struck through | Text struck through in blue-grey 2 | Excluded from the total |
396
+
397
+ The first six are the `mq-badge` states (`is-solid`, `is-ring`, `is-pale`, `is-outline`, `is-quiet`, `is-dashed`); the struck-through row is a table pattern without a shared class yet (layout). A page that uses them gives their key once (accessibility).
398
+
399
+ ### Colour: Status colours (apps only)
400
+
401
+ | Token | Value | Use | Contrast |
402
+ | :-- | :-- | :-- | :-- |
403
+ | `--mq-success` | `#2e7d32` | Confirmation, published, valid | 5.13:1 on surface, 4.91:1 on canvas |
404
+ | `--mq-danger` | `#d8232a` | Errors, destructive actions | 5.01:1 on surface, 4.79:1 on canvas |
405
+ | `--mq-info` | `#1565c0` | Information | 5.75:1 on surface, 5.50:1 on canvas |
406
+ | `--mq-warning` | `#a06400` | Warning text, icons and borders | 4.86:1 on surface, 4.65:1 on canvas |
407
+ | `--mq-warning-fill` | `#d4850a` | Warning badge or highlight fill, always with its text role `--mq-on-warning-fill`, ink in light (5.93:1) | Fill only |
408
+ | `--mq-warning-tint` | `#fbf3e7` | Warning background behind ink text (15.81:1) | Fill only |
409
+
410
+ The warning split (owner ruling 5): the orange `#d4850a` is a fill and never text, an icon or a border (2.81:1 on canvas). Text on it is ink; white on it is forbidden (2.93:1). On the warning tint, the text stays ink and `--mq-warning` is used only for the icon and the border (4.42:1, non-text).
411
+
412
+ Every status colour comes with an icon or a word (WCAG 1.4.1):
413
+
414
+ - a status badge carries its word, an urgent flag adds an icon and a border (`mq-status` in `css/app.css`);
415
+ - a status column shows a small coloured dot followed by the plain word, not a badge;
416
+ - a flag is a coloured word, and only on the rows that need attention, so normal rows stay quiet;
417
+ - light badge backgrounds come from the 50 and 100 steps of the status scales in `dist/tailwind.css`, never a hand-picked tint.
418
+
419
+ The red of the old Paracel theme survives only as `--mq-danger` (ADR-0003). App-specific hues, such as the annotation colours of regulatory text, stay local in the app under its own prefix until a second consumer needs them (specialising).
420
+
421
+ ### Colour: Chart colours
422
+
423
+ The chart tokens (`--mq-chart-series-1` to `--mq-chart-series-4`, `--mq-chart-rest`, `--mq-chart-axis`, `--mq-chart-baseline`) and their order are in charts.
424
+
425
+ ### Colour: Dark theme
426
+
427
+ MedQuantis has one dark theme (ADR-0007, owner ruling 12). It re-values the semantic roles above under the `.dark` class: a role it does not name keeps its light value, and no component needs a second name. A consumer opts in by loading `themes/dark.css` of the package after `tokens.css` and setting `.dark` on `<html>` or on one slide. The design system never switches on `prefers-color-scheme` by itself.
428
+
429
+ - **Apps and the website:** light and dark; the app decides how the viewer chooses, and may follow the viewer's setting by toggling `.dark`.
430
+ - **Documents** (A4 reports, papers, PDFs): light or dark, chosen by the author per document at build time, never from the reader's screen (owner ruling 15). A dark document stays dark in its PDF; light stays the default for anything likely to be printed on paper.
431
+ - **Slides:** a whole deck, or a single slide carrying `.dark`.
432
+
433
+ Every ratio below is the WCAG 2.x contrast with the dark values, on the dark canvas `#11161f` and surface `#1a2131`; the token test checks every declared pair in both themes.
434
+
435
+ | Token | Dark value | Use | Contrast |
436
+ | :-- | :-- | :-- | :-- |
437
+ | `--mq-canvas` | `#11161f`, a near-black with a navy cast | Background of every dark page and slide | Background |
438
+ | `--mq-surface` | `#1a2131` | Cards, panels, tables, figures, inputs | Background |
439
+ | `--mq-surface-alt` | `#141a26` | Recessed areas: table headers, grouped areas | Background |
440
+ | `--mq-desk` | `#0b0f16` | On screen only, around a dark page preview | Background |
441
+ | `--mq-border` | white at 14 % | Borders, dividers | Decorative |
442
+ | `--mq-border-light` | white at 10 % | Hairlines, card borders | Decorative |
443
+ | `--mq-border-strong` | `#8795b3` | Outline of outlined and dashed state marks | 6.03:1 on canvas, 5.35:1 on surface, non-text |
444
+ | `--mq-ink` | `#f5f4f1`, warm white | Primary text and values | 16.48:1 on canvas, 14.62:1 on surface |
445
+ | `--mq-muted` | `#c3cbdc` | Secondary text, leads, captions | 11.13:1 on canvas, 9.88:1 on surface |
446
+ | `--mq-tertiary` | `#8f98aa` | Tertiary text: notes, footers, axis labels | 6.25:1 on canvas, 5.54:1 on surface |
447
+ | `--mq-decoration` | `#6b7385` | Non-text decoration only, never text | 3.81:1 on canvas, 3.38:1 on surface, non-text |
448
+ | `--mq-accent` | `#6f88c9`, the lifted navy | Links, labels, key figures, active states | 5.22:1 on canvas, 4.63:1 on surface, 5.01:1 on the recessed surface |
449
+ | `--mq-accent-hover` | `#8aa0d6` | Hover and pressed state of a link or an accent control | 6.98:1 on canvas |
450
+ | `--mq-accent-pale` | the lifted navy at 18 % | Icon pills and chips, the highlighted row, the pale state | Fill |
451
+ | `--mq-accent-pale-2` | the lifted navy at 10 % | Progress tracks, the quiet state, callout background | Fill |
452
+ | `--mq-accent-highlight` | `#9fb3e8`, the light navy | Large highlighted title text, and accent text on the pale fills: the pale KPI value, the pale badge and the callout title (owner ruling 18) | 8.71:1 on canvas, 7.73:1 on surface, 5.97:1 on the pale fill, 6.77:1 on the quiet pale fill |
453
+ | `--mq-on-accent` | `#11161f` | Text and icons on an accent fill, never white (white 3.48:1 on the accent) | 5.22:1 on the accent |
454
+ | `--mq-focus-ring-color` | `#9fb3e8`, the light navy | Focus ring on dark backgrounds | 8.71:1 on canvas |
455
+ | `--mq-panel-start` | `#2f4c8f` | Light end of the navy panel gradient | Background, white 8.23:1 on the panel start |
456
+ | `--mq-panel-end` | `#0f2a5e` | Dark end of the navy panel gradient | Background |
457
+ | `--mq-shadow-panel` | a 1 px outline, light navy at 35 %, no glow (owner ruling 13) | Navy panels, the report header and the navy KPI tile on dark | Edge of a panel |
458
+ | `--mq-shadow-edge` | the same 1 px outline, light navy at 35 %; none in light | The monogram (`mq-monogram`), the navy KPI tile and a navy brand or app tile, which draw no shadow in light (owner ruling 14) | Edge of a navy mark |
459
+ | `--mq-shadow-small` | black at 40 %, 1 px down with a 2 px blur | Cards, figures and tables on dark, where the light 5 % shadow does not show | Elevation |
460
+ | `--mq-shadow-medium` | black at 50 %, 3 px down with a 12 px blur | A slide or page previewed on the dark desk, raised panels and menus | Elevation |
461
+
462
+ Accent text on the pale fills reads `--mq-accent-highlight` in both themes (owner ruling 18): in light it is the accent itself, while on dark the lifted navy would be only 3.57:1 on the pale fill and 4.05:1 on the quiet pale fill. The fills are measured laid over the canvas and over the surface, the worse result counting. The callout tab keeps `--mq-accent` on the quiet pale fill as a non-text mark.
463
+
464
+ The retired `--mq-dim` and `--mq-accent-on-dark` follow the roles they alias on dark too: `#8f98aa` (tertiary) and `#b2c2ed` (the on-navy accent).
465
+
466
+ **The navy panel on dark.** Navy itself fails on a dark canvas (`#0a2463` is 1.25:1 on `#11161f`), so on dark the brand shows as the lifted navy, the light navy and the panel gradient, with no new hue (ADR-0003). Navy panels, the report header and the navy KPI tile keep the 160 degree gradient between `--mq-panel-start` and `--mq-panel-end`, with the dark values above, and draw `--mq-shadow-panel`: a 1 px light-navy outline, never a glow (owner ruling 13). A navy tile on dark takes the panel gradient, never the lifted accent. Contrast on navy is still checked against the panel start, the lighter stop.
467
+
468
+ The on-navy tier on the dark panel (owner ruling 19): two roles take dark values, the others keep their light values and are checked again on the lighter dark panel start.
469
+
470
+ | Token | Dark value | Contrast | Allowed for |
471
+ | :-- | :-- | :-- | :-- |
472
+ | `--mq-on-navy-title` | `#ffffff`, unchanged | 8.23:1 on the panel start | Text |
473
+ | `--mq-on-navy-secondary` | white at 68 %, unchanged | 4.80:1 on the panel start | Text |
474
+ | `--mq-on-navy-second-level` | `#c3cbdc`, unchanged | 5.05:1 on the panel start | Text |
475
+ | `--mq-on-navy-accent` | `#b2c2ed`, a lighter navy tint | 4.64:1 on the panel start | Text, narrow margin |
476
+ | `--mq-on-navy-discreet` | white at 50 % | 3.37:1 on the panel start | Large text or non-text only |
477
+ | `--mq-on-navy-rule`, `--mq-on-navy-rule-strong`, `--mq-on-navy-pill` | white at 16, 18 and 12 %, unchanged | Decorative | Decorative only |
478
+
479
+ Light keeps `#8795b3` (4.61:1) and white at 40 % (3.44:1) for those two roles; on the dark panel start they would fall to 2.74:1 and 2.71:1.
480
+
481
+ **The light navy is a navy tint.** `#9fb3e8` (`--mq-accent-highlight`, `--mq-focus-ring-color`, dark series 1) is not the retired sky blue: its LCH hue 280.4 (chroma 29.2) sits in the navy family with the lifted navy `#6f88c9` (hue 282.8), against navy 293.7 and sky blue 269.9 (chroma 36.1). Sky blue stays retired in both themes (owner ruling 2).
482
+
483
+ Status colours on dark, apps only (ADR-0004, owner ruling 16), each still with an icon or a word; `--mq-warning-fill` keeps its value and stays a fill:
484
+
485
+ - `--mq-success` `#6fbf73`: 8.09:1 on canvas, 7.18:1 on surface.
486
+ - `--mq-warning` `#e0a040`: 8.00:1 on canvas, 7.10:1 on surface.
487
+ - `--mq-danger` `#ff6b6b`: 6.53:1 on canvas, 5.80:1 on surface.
488
+ - `--mq-info` `#64a8f0`: 7.25:1 on canvas, 6.43:1 on surface.
489
+ - `--mq-warning-fill` `#d4850a`, unchanged: a fill, never text.
490
+ - `--mq-warning-tint`, the warning fill at 16 %: ink 11.63:1 on the warning tint.
491
+ - `--mq-on-warning-fill` `#11161f`, text on `--mq-warning-fill`: 6.18:1 on the warning fill, where the warm white ink 2.67:1 on the warning fill would fail.
492
+
493
+ Chart colours on dark (owner ruling 16): the four series run in reverse order, the lightest first, under the same rules as light (charts): series 4 for context bars, the remainder drawn with a series 4 outline. Each dark series meets or beats its light counterpart against its canvas.
494
+
495
+ | Token | Dark value | Contrast |
496
+ | :-- | :-- | :-- |
497
+ | `--mq-chart-series-1` | `#9fb3e8` | 8.71:1 on canvas |
498
+ | `--mq-chart-series-2` | `#6f88c9` | 5.22:1 on canvas |
499
+ | `--mq-chart-series-3` | `#5d6f91` | 3.58:1 on canvas |
500
+ | `--mq-chart-series-4` | `#3a4560` | 1.90:1 on canvas, context bars only |
501
+ | `--mq-chart-rest` | `#232b3d` | 1.28:1 on canvas, with a series 4 outline |
502
+ | `--mq-chart-axis` | `#6b7385` | 3.81:1 on canvas, non-text |
503
+ | `--mq-chart-baseline` | white at 10 % | Decorative |
504
+
505
+ ### Charts: Introduction
506
+
507
+ Charts use the navy and blue-grey family only, in every medium. Status colours never colour a chart series, not even in an app; a chart that shows system status labels it in words.
508
+
509
+ ### Charts: The ordered palette
510
+
511
+ Four categorical series plus a remainder fill (owner, 2026-10-10), always used in this order, darkest first:
512
+
513
+ | Order | Token | Value | Contrast on canvas | Typical role |
514
+ | :-- | :-- | :-- | :-- | :-- |
515
+ | 1 | `--mq-chart-series-1` | `#0a2463` | 13.90:1 | The main series, the current period, the value the text discusses |
516
+ | 2 | `--mq-chart-series-2` | `#5d6f91` | 4.84:1 | The second share of a whole |
517
+ | 3 | `--mq-chart-series-3` | `#8795b3` | 2.88:1 | Other values, projections, "upcoming" dashed marks, struck values |
518
+ | 4 | `--mq-chart-series-4` | `#c3cbdc` | 1.56:1 | Previous period, reference, neutral outline |
519
+ | Remainder | `--mq-chart-rest` | `#dde3ee` | | What is left, empty or not yet: never a categorical series |
520
+
521
+ The pairs between the four series stay at least CIEDE2000 10 apart under normal vision and under protan, deutan, tritan and achromatic simulation; the remainder fill is too close to series 4, so it is drawn with a series 4 outline.
522
+
523
+ **Series 3 and 4 are below 3:1 against the page** (WCAG 1.4.11). A chart that uses them must not rely on their colour alone: label those marks directly, give them a separating outline or stroke, or add a pattern. Series 1 and 2 meet 3:1 on their own.
524
+
525
+ Rules:
526
+
527
+ - Never more than four series at once. Group the rest as "Other" in the remainder fill.
528
+ - Fix colours per series when the order means something (reference before current); never let a charting library rotate through its palette.
529
+ - Only one element is navy when the others are there for comparison.
530
+
531
+ ### Charts: Common cases
532
+
533
+ | Case | Colours |
534
+ | :-- | :-- |
535
+ | One series | Series 1 |
536
+ | Two series: reference or previous period, then current | Series 4, then series 1 |
537
+ | Parts of a whole | Series 1, series 2, remainder fill; in the legend the remainder swatch is outlined |
538
+ | States of an amount (paid, approved, claimed, not yet claimed) | Series 1, series 3, series 4 with its outline, and the upcoming part dashed in series 3; or the document mark styles of colour |
539
+ | Ranking with more than four items | Highlight on grey, below |
540
+ | Workflow stages in an app | Series 1 to 4 with the stage name as a label or legend, never status hues, never white text on series 3 or 4 |
541
+
542
+ ### Charts: Highlight on grey
543
+
544
+ To point at one item among many (a ranking, one insurer among peers), draw the item the text discusses in series 1 and every other item in series 3. When the context should recede further, such as a reference period behind the current one, use series 4. Label the highlighted item directly; the others need a label or an outline because series 3 and 4 are below 3:1.
545
+
546
+ ### Charts: Sequential and diverging use
547
+
548
+ There are no sequential or diverging ramp tokens yet.
549
+
550
+ - **Sequential:** for an ordered quantity (low to high), use the existing tokens as a light-to-dark ramp of up to five steps: `--mq-chart-rest`, `--mq-chart-series-4`, `--mq-chart-series-3`, `--mq-chart-series-2`, `--mq-chart-series-1`. Their lightness falls monotonically. Do not interpolate new values by hand; if a chart needs more steps, bin the data into five classes or ask for a ramp token.
551
+ - **Diverging:** documents have a single hue, so there is no second hue for the opposite side, and status colours are not allowed in charts. Until a diverging ramp is ruled, show the magnitude with the sequential ramp and the direction with the sign of the value and a mark style (filled for above the reference, outlined or dashed for below), with the reference line drawn as the baseline. This is an open point for the owner.
552
+
553
+ ### Accessibility: Colour is never alone
554
+
555
+ Colour never carries meaning on its own (WCAG 1.4.1):
556
+
557
+ - documents show states by mark style (solid, ring, pale, outline, quiet, dashed, struck through) and a page that uses these marks gives their key once, in a legend or a note;
558
+ - apps pair every status colour with an icon or a word;
559
+ - charts label the marks that use series 3 or 4, or give them an outline, since those colours are below 3:1 on the page (charts);
560
+ - a change in a figure is shown by its sign, not by a colour or an arrow alone.
561
+
562
+ ### Accessibility: Contrast pairs
563
+
564
+ Every foreground and background pair the system uses is declared in `tokens/contrast-pairs.json` with its allowed use: text (4.5:1), large text (3:1), non-text (3:1, WCAG 1.4.11) or decorative (no minimum, for marks that carry no information). The token test checks every declared pair and fails on any pairing used in `css/` without a declaration; the specimen shows each pair with its ratio. A consumer that needs a new pair asks for it here rather than eyeballing it.
565
+
566
+ Pairs to remember:
567
+
568
+ | Pair | Ratio | Allowed for |
569
+ | :-- | :-- | :-- |
570
+ | Ink on canvas | 16.65:1 | Text |
571
+ | Muted on canvas | 7.13:1 | Text |
572
+ | Tertiary on canvas, on surface | 4.74:1, 4.95:1 | Text. Not on the recessed surface (exactly 4.50:1, not declared) |
573
+ | Decoration grey on canvas | 3.22:1 | Non-text only, never text |
574
+ | Navy on canvas | 13.90:1 | Text |
575
+ | White on navy | 14.53:1 | Text |
576
+ | On-navy accent on the lighter panel stop | 4.61:1 | Text, narrow margin |
577
+ | On-navy discreet (white at 40 %) on navy | 3.48:1 | Large text or non-text only |
578
+ | Warning text on surface | 4.86:1 | Text |
579
+ | Ink on the warning fill | 5.93:1 | Text; white on the fill is forbidden (2.93:1) |
580
+ | Blue-grey outline on canvas | 4.84:1 | Non-text |
581
+
582
+ Placeholder text in inputs uses the tertiary text colour, never a lighter grey.
583
+
584
+ ## Typography
585
+
586
+ | DESIGN.md | tokens.css | Value |
587
+ | :-- | :-- | :-- |
588
+ | `typography.body.fontFamily` | `--mq-font-family-body` | `Newsreader, Georgia, 'Times New Roman', serif` |
589
+ | `typography.body.fontSize` | `--mq-font-size-body` | `16.5px` |
590
+ | `typography.body.fontWeight` | `--mq-font-weight-body` | `400` |
591
+ | `typography.body.lineHeight` | `--mq-font-line-height-body` | `1.55` |
592
+ | `typography.heading.fontFamily` | `--mq-font-family-heading` | `Outfit, system-ui, -apple-system, 'Segoe UI', sans-serif` |
593
+ | `typography.heading.fontSize` | `--mq-font-size-h2` | `32px` |
594
+ | `typography.heading.fontWeight` | `--mq-font-weight-heading` | `500` |
595
+ | `typography.heading.lineHeight` | `--mq-font-line-height-tight` | `1.15` |
596
+ | `typography.mono.fontFamily` | `--mq-font-family-mono` | `'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace` |
597
+ | `typography.text-xs.fontSize` | `--mq-font-size-eyebrow` | `11px` |
598
+ | `typography.text-sm.fontSize` | `--mq-font-size-label` | `13px` |
599
+ | `typography.text-lg.fontSize` | `--mq-font-size-h3` | `20px` |
600
+ | `typography.text-xl.fontSize` | `--mq-font-size-hero-max` | `56px` |
601
+
602
+ ### Typography: Three families, three roles
603
+
604
+ | Family | Token | Role |
605
+ | :-- | :-- | :-- |
606
+ | Outfit | `--mq-font-family-heading`, `--mq-font-family-ui` | Headings, column titles, hero figures, labels, table text, takeaways; the whole UI of apps (navigation, buttons, badges, UI body text) |
607
+ | Newsreader | `--mq-font-family-body` | Reading text: body of documents and slides, leads, captions, notes, quoted official text, and regulatory text in apps. The only serif (owner ruling 10) |
608
+ | Geist Mono | `--mq-font-family-mono` | Codes, figures in tables, dates in tables, eyebrows, axis labels, identifiers |
609
+
610
+ Georgia and Times New Roman appear only as fallbacks. The three families are self-hosted (`fonts/fonts.css`, variable woff2 with latin and latin-ext); no page loads a font from a remote service. Newsreader ships its true italic, so leads and notes are never a slanted roman.
611
+
612
+ Outfit was chosen because it reads cleanly from a display title down to an 11 px badge, with a geometric, contemporary character that stays distinct from the generic interface sans-serifs.
613
+
614
+ ### Typography: Weights
615
+
616
+ | Weight | Token | Use |
617
+ | :-- | :-- | :-- |
618
+ | Outfit 500 | `--mq-font-weight-heading` | Every heading, column title, hero figure, KPI value, label, button label and badge (owner ruling 4) |
619
+ | Outfit 600 | `--mq-font-weight-emphasis` | Only a short highlighted word inside body text, as the "Med" of the wordmark. Bold inside Newsreader prose switches to Outfit 600 at about 0.94 em |
620
+ | Outfit 400 | `--mq-font-weight-body` | App UI body text |
621
+ | Outfit 300 | `--mq-font-weight-light` | Only the "Quantis" half of the wordmark |
622
+ | Newsreader 400 | `--mq-font-weight-body` | Reading text, with its italic for leads and notes |
623
+ | Geist Mono 400 and 500 | | Table figures and codes 400, eyebrows 500 |
624
+
625
+ Weights 700 and 800 appear only inside the monogram files; no heading, button or table uses them.
626
+
627
+ ### Typography: Roles and sizes
628
+
629
+ Every size is a token. The print column gives the A4 scale for reports printed from a browser; it is not yet a token (open point).
630
+
631
+ | Role | Family and style | Screen token | Screen | Print (A4) |
632
+ | :-- | :-- | :-- | :-- | :-- |
633
+ | Hero title (`mq-display`) | Outfit 500, line height 1.04 to 1.08, letter spacing about -0.025 em | `--mq-font-size-hero-min` to `--mq-font-size-hero-max` | 34 to 56 px | 26 pt |
634
+ | Slide title (`mq-slide-title`) | Outfit 500, line height 1.15 | `--mq-font-size-slide-title` | 36 px | |
635
+ | Section title (`mq-title`) | Outfit 500, line height 1.15, about -0.015 em | `--mq-font-size-h2` | 32 px | 17 pt |
636
+ | Subsection, card and column title (`mq-subtitle`, `mq-card-title`) | Outfit 500, about -0.01 em | `--mq-font-size-h3` | 20 px | 12.5 pt |
637
+ | Lead (`mq-lead`) | Newsreader italic, muted, line height 1.5 | `--mq-font-size-lead` | 18 px | 11.5 pt |
638
+ | Body (`mq-doc`, `mq-prose`) | Newsreader 400, line height 1.6 | `--mq-font-size-body` | 16.5 px | 10 pt |
639
+ | Small text, figure caption | Newsreader | `--mq-font-size-small` | 15 px | 9.5 pt |
640
+ | Label (`mq-label`) | Outfit 500, muted | `--mq-font-size-label` | 13 px | 8.5 pt |
641
+ | Eyebrow (`mq-eyebrow`) | Geist Mono 500, capitals, letter spacing 0.16 to 0.18 em, navy | `--mq-font-size-eyebrow` | 11 px | 7.5 pt |
642
+ | Hero figure (`mq-figure`) | Outfit 500, tabular figures, navy | `--mq-font-size-figure` | 50 px | 28 pt |
643
+ | KPI value (`mq-kpi-value`) | Outfit 500, tabular figures | `--mq-font-size-kpi` | 30 px | 17 pt |
644
+ | Counter value | Outfit 500, tabular figures, navy | `--mq-font-size-stat` | 24 px | 14 pt |
645
+ | Table header | Geist Mono, capitals, small letter spacing | `--mq-font-size-table-header` | 11 px | 7 pt |
646
+ | Table figure (`mq-num`) | Geist Mono 400, tabular figures, right-aligned | `--mq-font-size-table-cell` | 13 px | 8.5 pt |
647
+ | Table text | Outfit 500 (first column) or Newsreader | `--mq-font-size-table-text` | 14 px | 8.5 pt |
648
+ | Takeaway (`mq-takeaway`) | Outfit 500 | `--mq-font-size-takeaway` | 16 px | 10.5 pt |
649
+ | Note, source, illustrative mention (`mq-note`, `mq-foot`) | Newsreader italic, tertiary | `--mq-font-size-note` | 12.5 px | 8 pt |
650
+ | Chart axis label | Geist Mono, tertiary | `--mq-font-size-axis` | 11 px | about 8 pt on paper |
651
+
652
+ Rules:
653
+
654
+ - **Floor:** no text below 11 px on screen, in any medium, including table headers and badges.
655
+ - **Letter spacing** is negative on titles and tightens as the size grows: about -0.01 em near 20 px, -0.015 em near 32 to 38 px, -0.025 em at 56 px and above. Body text keeps the font's own spacing.
656
+ - **Figures** always use tabular figures (`font-variant-numeric: tabular-nums`, or `mq-mono`), so columns of numbers align.
657
+ - **Eyebrows and section labels** are typed in normal case and set in capitals by CSS; a middle dot separates their parts: "02 · Amounts by year".
658
+
659
+ ### Typography: Line height
660
+
661
+ | Context | Line height |
662
+ | :-- | :-- |
663
+ | Hero titles | 1.04 to 1.08 |
664
+ | Headings, counters, stat figures | 1.15 to 1.3 |
665
+ | Leads | 1.5 |
666
+ | App UI text, labels, descriptions | 1.55 |
667
+ | Reading text in documents | 1.6 |
668
+ | Help text and longer descriptions in apps | 1.75 |
669
+ | Regulatory text in apps (dense legal prose read under time pressure) | 2.2, and 2.0 on phones |
670
+
671
+ Long regulatory text is set in Newsreader at about 15 px with that generous line height, so long German compounds stay separable line by line. Codes inside it are set in Geist Mono at 12 px on the recessed surface with the small radius. Dense tables keep tight padding; the breathing room goes to the text people read word by word.
672
+
673
+ ### Typography: App type scale
674
+
675
+ Apps use the same families and weights at UI sizes: UI body Outfit 400 at 14 px, help text 13 to 14 px at line height 1.75, labels and navigation Outfit 500 at 13 px, badges Outfit 500 at 11 px, data values Geist Mono at 12 to 13 px, page title Outfit 500. Heavier weights (600 to 800) and sizes below 11 px from older app designs are retired.
676
+
677
+ ## Layout
678
+
679
+ | DESIGN.md | tokens.css | Value |
680
+ | :-- | :-- | :-- |
681
+ | `spacing.space-sm` | `--mq-space-2` | `8px` |
682
+ | `spacing.space-md` | `--mq-space-4` | `16px` |
683
+ | `spacing.space-lg` | `--mq-space-6` | `32px` |
684
+ | `spacing.space-xs` | `--mq-space-1` | `4px` |
685
+ | `spacing.space-xl` | `--mq-space-7` | `48px` |
686
+ | `spacing.space-3` | `--mq-space-3` | `12px` |
687
+ | `spacing.space-5` | `--mq-space-5` | `24px` |
688
+ | `spacing.space-8` | `--mq-space-8` | `72px` |
689
+
690
+ ### Layout: Media
691
+
692
+ | Medium | Canvas | Layout |
693
+ | :-- | :-- | :-- |
694
+ | Slide | 1280 by 720 px (`--mq-slide-width`, `--mq-slide-height`, `css/slide.css`) | Fixed canvas, 72 px side margins (`--mq-space-8`), section label top left, title under it, and a footer row (`mq-slide-foot`) with the legal line bottom left and the slide number bottom right, both in tertiary mono (owner ruling 11); a classified deck adds its classification label at the right of the footer, before the slide number (owner ruling 17) |
695
+ | Screen report or paper | A page that scrolls, centred, `--mq-page-width` (1040 px) wide | Flow layout: no absolute positions, no fixed heights. It is not a deck: no slide canvas inside a report |
696
+ | A4 print | 210 by 297 mm (`--mq-a4-width`, `--mq-a4-height`, `css/print.css`) | One `mq-page` per sheet on the canvas colour; on screen the pages sit on the desk |
697
+
698
+ ### Layout: Report skeleton
699
+
700
+ 1. A navy header: navy gradient, the on-navy wordmark, a mono kicker preceded by a short rule, the title in white Outfit 500, one sentence that carries the message (its first clause in white, the rest in on-navy secondary), and a row of metadata (author, scope, date) separated by on-navy rules. The header is a pattern, not yet a shared component.
701
+ 2. "In brief": three or four self-contained sentences with the figure that matters, under a 2 px navy rule.
702
+ 3. Sections. Each opens with a head (numbered eyebrow such as "02 · Amounts by year", title, one-sentence lead), then its blocks, then, when useful, one takeaway line (`mq-takeaway`).
703
+ 4. The footer (`mq-foot`): a rule, the numbered sources, then the legal line and the date (brand).
704
+
705
+ A source is cited in the text with a small superscript number in Geist Mono, navy, pointing to the numbered source list.
706
+
707
+ ### Layout: Widths and spacing
708
+
709
+ All spacing comes from the 4 and 8 px scale (`--mq-space-1` to `--mq-space-8`: 4, 8, 12, 16, 24, 32, 48, 72 px). No other spacing value.
710
+
711
+ | Measure | Token | Value |
712
+ | :-- | :-- | :-- |
713
+ | Page width | `--mq-page-width` | 1040 px |
714
+ | Reading measure: prose, leads, in-brief, callouts stop here | `--mq-page-measure` | 760 px |
715
+ | Side gutter | `--mq-space-6` | 32 px, 16 px (`--mq-space-4`) on phones |
716
+ | Between cards | `--mq-space-5` | 24 px |
717
+ | Between calm columns | `--mq-space-7` | 48 px |
718
+ | From a section head to its content | `--mq-space-6` | 32 px |
719
+ | Between sections | `--mq-space-8` | 72 px, 48 px on phones |
720
+
721
+ Figures, tables and grids take the full page width; running text never does.
722
+
723
+ Grids: calm columns (three columns separated by thin rules, no boxes, one hero figure each), two-column and three-column grids of cards or figures. On phones every grid becomes one column, and a band of four tiles becomes two by two.
724
+
725
+ ### Layout: Print and PDF
726
+
727
+ - `css/print.css` prints colours exactly (`print-color-adjust: exact`), so navy headers and panels do not print white, and stops all animation.
728
+ - Every document page uses the canvas colour (owner ruling 9); the desk never reaches paper.
729
+ - Figures, cards, panels, KPI bands, callouts, takeaways, table rows and source items never split across pages, and a heading is never left alone at the foot of a page.
730
+ - Before sending a PDF, check it page by page.
731
+
732
+ ### Layout: Phones
733
+
734
+ One column, a 16 px gutter, and no horizontal scrolling of the page. Only tables and wide charts scroll, inside their own frame.
735
+
736
+ ### Accessibility: Target
737
+
738
+ WCAG 2.2 level AA for every medium: apps, the website, HTML reports, slides exported to PDF and newsletters. APCA is not normative and is not used; contrast is measured with the WCAG 2.x formula.
739
+
740
+ ### Accessibility: Size and targets
741
+
742
+ - No text below 11 px on screen (typography).
743
+ - Touch targets in apps are at least 44 px high on mobile (buttons, tabs, navigation links), above the 24 px minimum of WCAG 2.5.8.
744
+ - Text reflows: pages work at 320 px wide and at 200 % zoom without horizontal page scrolling; only tables and wide charts scroll inside their frame.
745
+
746
+ ### Accessibility: Structure
747
+
748
+ - One `h1` per page and headings in order; the eyebrow is not a heading.
749
+ - Set the page language (`lang`) and mark passages in another language.
750
+ - An SVG chart has `role="img"` and an accessible name and description (`<title>` and `<desc>`, or `aria-labelledby`); the figure's key values also appear in text or in a table.
751
+ - Icons that carry meaning have a label; decorative icons are hidden (`aria-hidden="true"`).
752
+ - PDFs exported from HTML are tagged and carry an outline (Playwright `tagged` and `outline`); the specimen test exports one.
753
+
754
+ ### Accessibility: Motion
755
+
756
+ - Motion is optional, short and meaningful. Interactive elements may use short transitions (about 120 ms) on colour and background; documents do not animate on hover.
757
+ - Never blur, zoom, reflections, spotlight effects or looping animation.
758
+ - Everything respects `prefers-reduced-motion: reduce` (no animation at all) and print shows the final state.
759
+
760
+ ## Elevation & Depth
761
+
762
+ ### Layout: Elevation and navy blocks
763
+
764
+ - Cards, figures and tables: `--mq-shadow-small`. Raised panels, menus and a page or slide previewed on the desk: `--mq-shadow-medium`. Navy panels: `--mq-shadow-panel`, which on dark is a 1 px light-navy outline and never a glow (owner ruling 13). No shadow in print and none on chart marks.
765
+ - One large navy block per screen, slide or page area. In a report, keep the header, a navy KPI band and a navy panel apart with light content.
766
+
767
+ ## Shapes
768
+
769
+ | DESIGN.md | tokens.css | Value |
770
+ | :-- | :-- | :-- |
771
+ | `rounded.card-radius` | `--mq-radius-medium` | `6px` |
772
+ | `rounded.btn-radius` | `--mq-radius-small` | `3px` |
773
+ | `rounded.node-radius` | `--mq-radius-medium` | `6px` |
774
+ | `rounded.radius-sm` | `--mq-radius-small` | `3px` |
775
+ | `rounded.radius-pill` | `--mq-radius-medium` | `6px` |
776
+ | `rounded.radius-overlay` | `--mq-radius-overlay` | `12px` |
777
+
778
+ ### Layout: Radius
779
+
780
+ Owner ruling 3, as amended on 2026-10-10:
781
+
782
+ | Token | Value | For |
783
+ | :-- | :-- | :-- |
784
+ | `--mq-radius-small` | 3 px | Small items: tags, chips, badges, inputs, buttons, progress tracks, bar ends, tooltips |
785
+ | `--mq-radius-medium` | 6 px | Cards, panels, KPI tiles, figures, icon pills, slide frames. The maximum anywhere else |
786
+ | `--mq-radius-overlay` | 12 px | Apps only: dialogs and modals, side drawers and command palettes. Never in documents, slides, cards or website media |
787
+
788
+ - No pill shapes, ever: no fully rounded buttons, tags, chips or tabs.
789
+ - A circle is allowed only for something that is genuinely round: a status dot, a numbered marker, an avatar. In Tailwind that is `rounded-full`, and only there.
790
+ - In `dist/tailwind.css` the steps `rounded`, `rounded-xs` and `rounded-sm` resolve to 3 px, `rounded-md` to `rounded-4xl` to 6 px, and `rounded-overlay` to 12 px, so no utility class can exceed the ruling. The shared stylelint config rejects any other radius.
791
+
792
+ Squared-off corners read professional and institutional; pills read consumer.
793
+
794
+ ## Components
795
+
796
+ ### Layout: Document components
797
+
798
+ | Pattern | Shared class | Rules |
799
+ | :-- | :-- | :-- |
800
+ | Card | `mq-card` | White, light border, small shadow, 6 px radius. An optional title bar (blue-grey icon, navy title, mono note on the right on the canvas colour) is a pattern, not yet a class |
801
+ | Navy panel | `mq-panel` | Navy gradient, white title, on-navy secondary and second-level text, white icons in translucent pills |
802
+ | KPI tiles | `mq-kpis`, `mq-kpi` | Three forms: calm columns with one hero figure each; ruled counters (a navy left rule, value, muted label) in rows of three or four; a navy band (`is-navy`) or pale tiles (`is-pale`). Value above its caption, never side by side |
803
+ | Callout | `mq-callout` | A reservation or a point of attention: navy left tab, quiet pale fill, mono navy title. Always navy |
804
+ | Takeaway | `mq-takeaway` | The one sentence to remember, under a navy tab, Outfit 500 |
805
+ | Numbered rows | (pattern) | A navy circle with the number, a heading column, a body column: one line per point |
806
+ | Footer | `mq-foot` | Sources and legal line in tertiary italic, in reports and papers; never inside a slide |
807
+ | Slide footer | `mq-slide-foot` | On every slide (owner ruling 11): the legal line bottom left, the slide number bottom right, small, tertiary, Geist Mono. A classified deck shows its classification at the right of the footer, before the slide number: the label in the deck's language, never the C0 to C3 code (owner ruling 17). Without a classification: `<footer class="mq-slide-foot"><p>MedQuantis SA · CH-1095 Lutry · medquantis.ch</p><p class="mq-slide-number">03</p></footer>` |
808
+
809
+ ### Layout: Tables
810
+
811
+ Document tables (`mq-table`):
812
+
813
+ - Horizontal hairlines only, never vertical rules and never zebra stripes.
814
+ - Headers in Geist Mono capitals on the recessed surface; the unit goes on a second header line ("Gap" then "CHF"), never repeated in every cell.
815
+ - Figures in Geist Mono, right-aligned (`mq-num`); the first column in Outfit 500 or Newsreader.
816
+ - The key column in navy (`is-key`).
817
+ - A deviating value may sit in a pale navy pill with the small radius.
818
+ - At most one highlighted row (`tr.is-highlight`): a pale navy fill. The source pattern adds a 3 px navy edge on its first cell; the shared class does not draw it yet.
819
+ - An excluded row is struck through in blue-grey 2 and left out of the total (pattern, not yet a class).
820
+ - The total row (`tr.is-total`) sits under a 2 px navy rule; its key amount is navy.
821
+
822
+ App tables (sober tables):
823
+
824
+ - A white wrapper with a light border, the small shadow and the 6 px radius; recessed header row; row hover on the recessed surface.
825
+ - Below a minimum width (about 640 px) the table scrolls horizontally inside its wrapper, never the page.
826
+ - Do not put a badge in every column. Use three levels of visual weight so the eye rests on normal rows: small classification marks explained by a legend row under the table, a status dot followed by its word, and flags as coloured words only on the rows that need attention (colour).
827
+
828
+ ### Layout: App patterns
829
+
830
+ - **Navigation:** links in muted Outfit 500; hover in ink on the recessed surface; the active link in navy on pale navy. On tablets the navigation collapses into a menu.
831
+ - **Tabs:** underline tabs, not segmented pills. The active tab is navy with a 2 px navy underline (`--mq-stroke-strong`); hover in ink on the recessed surface. Underline tabs stay light in dense tools.
832
+ - **Breadcrumb:** inside a light bordered card; links muted, navy on hover; the separator is a single right angle quote in tertiary; the current page in ink.
833
+ - **Buttons:** primary (navy fill, white text, darker navy on hover), secondary (white, border, recessed on hover), danger outline (danger border and text, with an icon or a destructive verb), ghost (no border, muted text). One size, Outfit 500, the small radius. On phones a button row stretches to full width.
834
+ - **Empty state:** centred, a dimmed icon, an Outfit 500 title, one muted sentence, and a hint in Geist Mono tertiary.
835
+ - **Stat cards:** stable. No hover lift, no animation: reference data must not move under the pointer.
836
+ - **Breakpoints:** documents switch to one column below 720 px. Apps use 768 px (tablet: navigation menu, narrower gutters) and 480 px (phone: full-width buttons, two stat cards per row, keys of key-value rows above their values).
837
+
838
+ ### Accessibility: Focus
839
+
840
+ - Every interactive element shows a visible focus ring on keyboard focus (WCAG 2.4.7 and 1.4.11): `--mq-focus-ring-width` (2 px) of `--mq-focus-ring-color` (navy, 13.90:1 on canvas) at `--mq-focus-ring-offset` (2 px) from the element.
841
+ - On navy, the ring is white (`--mq-focus-ring-color-on-navy`, 14.53:1). `css/base.css` and `css/components.css` draw both.
842
+ - Never remove the outline without drawing this ring. A focused input may also change its border to navy, but the ring stays.
843
+
844
+ ### Charts: Marks
845
+
846
+ - **Grid and axes:** no background grid. One baseline in `--mq-chart-baseline`, 1 px. No drawn vertical axis: at most three value labels on the left, without lines. Axis lines and ticks, where drawn, use `--mq-chart-axis` (non-text).
847
+ - **Axis labels:** Geist Mono at `--mq-font-size-axis`, in tertiary text (`--mq-tertiary`), three to five ticks. Dates as `2024` or `10.2026` (data patterns).
848
+ - **Value labels:** write only the value the text discusses (the peak, the latest value), in navy; leave about 18 % headroom above the tallest mark for it.
849
+ - **Vertical bars:** rounded at the top with the small radius (3 px), square at the base, with a narrow gap between bars of a monthly series.
850
+ - **Horizontal bars:** about 8 px thick, the far end rounded with the small radius, the value in Geist Mono at the end of the bar.
851
+ - **Ring:** for parts of a whole, a thick stroke starting at the top, small gaps between arcs, no text in the centre, the legend beside it. Never a pie of more than four parts.
852
+ - **Lines:** the main series in series 1 at 2 px with round caps; a reference in series 4 at 1.5 px; a projection in series 3 at 1.5 px dashed; an end dot in navy with its value above it. No area fill under a line and no smoothing.
853
+ - **Annotations:** a dotted vertical marker in series 3 with a short sentence in Newsreader italic, muted; or a numeric note under the chart (the number in navy, then a sentence).
854
+ - **Progress bar** (`mq-progress`): a quiet pale track (`--mq-accent-pale-2`), a navy fill, the value in Geist Mono beside it; a secondary value in series 3.
855
+ - **Distribution bar** (`mq-distribution`, `mq-segment`): segments in palette order, the remainder dashed or in the remainder fill.
856
+ - **Tooltips**, where a library draws them: navy background, white text, title in Outfit, values in Geist Mono, the small radius.
857
+
858
+ Never: background grids, zebra bands, 3D, shadows on marks, area fills under lines, pies of more than four parts.
859
+
860
+ ### Charts: Ring construction
861
+
862
+ Build a ring in SVG from one circle per part, so the arcs need no path arithmetic:
863
+
864
+ 1. Every circle has the same centre and radius, `fill="none"`, `pathLength="100"` (so lengths are percentages) and a thick stroke; wrap them in a group with `transform="rotate(-90 cx cy)"` so the first arc starts at the top.
865
+ 2. Check that the values sum to 100 before drawing.
866
+ 3. Each arc is drawn with `stroke-dasharray="L G"`, where the arc length L is its value minus the gap (1 unit) and G is 100 minus L. The arc is therefore one unit shorter than its value on purpose: that unit is the gap between arcs.
867
+ 4. Each arc starts where the previous ones end: `stroke-dashoffset` is minus the running total of the previous values (0, then minus the first value, and so on).
868
+ 5. The parts take series 1, 2, 3 in order. The remainder is two circles with the same dash: a `--mq-chart-series-4` arc 2 px wider, then a `--mq-chart-rest` arc over it, so the series 4 edge shows as its outline.
869
+ 6. Set the stroke colour with `style="stroke: var(--mq-chart-series-1)"`, never a literal value. No text in the centre; the legend sits beside the ring (`mq-legend is-column`).
870
+
871
+ ### Charts: Legends
872
+
873
+ The legend is HTML (`mq-legend`, `mq-legend-item`, `mq-swatch`) placed above or beside the plot, never inside it. Beside a ring, use `mq-legend is-column`: the items in a column, each value in `mq-legend-value` at the right edge in Geist Mono, and the remainder item (`is-rest`) separated by a rule. Above a bar or line chart, use the plain `mq-legend`, one inline row. Swatches: `is-1` to `is-4`, `is-rest`, `is-outline` and `is-dashed`.
874
+
875
+ ### Charts: Size and motion
876
+
877
+ - An SVG chart takes the full width of its figure, and its text sizes are in viewBox units. Because the SVG shrinks with its figure in print, size the labels for the printed width: they should read about 8 pt on paper.
878
+ - On phones a wide chart keeps a minimum width and scrolls inside its frame.
879
+ - Motion is optional and carries meaning: bars grow from the base, a line draws, an arc sweeps, all within two seconds. It stops under `prefers-reduced-motion` and never runs in print.
880
+
881
+ ### Brand: Legal line
882
+
883
+ The footer of every document sent outside carries the legal line and the date of the document:
884
+
885
+ `MedQuantis SA · CH-1095 Lutry · medquantis.ch`
886
+
887
+ Set it in Geist Mono at the note size, tertiary text colour (`--mq-tertiary`), with the date at the right end of the same line.
888
+
889
+ On slides the legal line appears on every slide, bottom left, small and in tertiary grey, with the slide number bottom right (owner ruling 11). Use the slide footer row `mq-slide-foot` of `css/slide.css`, never `mq-foot`, which flows from the top of a slide canvas.
890
+
891
+ A deck that carries a classification shows it at the right of the footer, before the slide number, in the same small tertiary mono (owner ruling 17, amending ruling 11's two-cell footer). It is the label only, in the deck's language (Confidential in an English deck, Confidentiel in a French one), never the C0 to C3 code. A kit deck takes it from its `classification:` and `lang:`.
892
+
893
+ Material left with a reader (a deck after a meeting, a report sent outside) also carries a confidentiality mention and its date, in the document's language:
894
+
895
+ | Medium | French | German | English | Italian |
896
+ | :-- | :-- | :-- | :-- | :-- |
897
+ | Presentation | Présentation confidentielle | Präsentation vertraulich | Confidential presentation | Not yet defined, owner to provide |
898
+ | Report | Rapport confidentiel | Vertraulicher Bericht (proposed, owner to confirm) | Confidential report (proposed, owner to confirm) | Not yet defined, owner to provide |
899
+
900
+ ### Brand: Wordmark
901
+
902
+ The wordmark is the logo; no icon is required next to it. "Med" is set in Outfit 600 and "Quantis" in Outfit 300, in navy (`#0a2463`) on light backgrounds and in white on navy and on every dark surface, at every size (owner rulings 6 and 14).
903
+
904
+ Each background has two files in `brand/`:
905
+
906
+ | File | Use |
907
+ | :-- | :-- |
908
+ | `wordmark-on-light.svg`, `wordmark-on-navy.svg` | Live text. Inline the SVG in a page that loads `fonts/fonts.css`, so the letters render in Outfit and stay selectable. |
909
+ | `wordmark-on-light-print.svg`, `wordmark-on-navy-print.svg` | Outlined letters. Use for images (`<img>` cannot load web fonts), documents, PDFs, email and anything sent to a printer. |
910
+
911
+ Rules:
912
+
913
+ - Use the on-navy file on navy and the on-light file on light backgrounds. Never recolour, stretch, outline, shadow or rearrange the wordmark.
914
+ - On dark (the dark theme, a dark slide or page), use the white `wordmark-on-navy` files on every dark surface: 18.13:1 on the dark canvas, 16.08:1 on the dark surface. The navy wordmark is never used on dark: it is 1.25:1 there (owner ruling 14).
915
+ - Inline the SVG rather than linking an external image, and remove its `width` and `height` so the page sizes it. On a report header the wordmark sits at about 198 by 44 px on screen and 162 by 36 px in print.
916
+ - A full stop in the on-navy accent (`--mq-on-navy-accent`) after a name is reserved for a one-word name on a navy cover. It is never added to the wordmark.
917
+
918
+ ### Brand: Monogram and favicon
919
+
920
+ Where the wordmark does not fit (favicon, app icon, avatar, app tile), use the monogram: a white M and Q on a navy (`#0a2463`) square with rounded corners of 8/64 of its side, M in Outfit 700 and Q in Outfit 400, centred on the cap height (owner ruling 7). The letters are outlined in every file.
921
+
922
+ | File | Use |
923
+ | :-- | :-- |
924
+ | `brand/monogram.svg` | The monogram with its rounded corners |
925
+ | `brand/monogram-square.svg` | For platforms that round icons themselves |
926
+ | `brand/monogram-small.svg` | 16 px and smaller: larger letters, M 800 and Q 600 |
927
+ | `brand/favicon/` | `favicon.svg`, `favicon.ico` (16, 32, 48), `apple-touch-icon.png` (180), `icon-192.png`, `icon-512.png` |
928
+
929
+ On dark (owner ruling 14) the monogram keeps its file and takes the navy panel's edge, the 1 px light-navy outline that `--mq-shadow-panel` draws in the dark theme, since its navy square is only 1.11:1 against the dark surface; in light it has no outline. A navy brand tile or app tile on dark takes the panel gradient (`--mq-panel-start` to `--mq-panel-end`), never the lifted accent, on which white is 3.48:1. No new brand file is needed.
930
+
931
+ ### Brand: No combined logo yet
932
+
933
+ There is no lockup of the monogram and the wordmark in v1, and no on-navy variant of the monogram. Do not compose one locally: the combined logo is deferred until a real surface needs it ([issue #2](https://github.com/MedQuantis/design-system/issues/2)). Older lockups with a strapline under the wordmark are retired.
934
+
935
+ ### Brand: Icons
936
+
937
+ - Lucide only, from the pinned build in `icons/` (version and hash in `icons/manifest.json`). No second icon library, and never an emoji in place of an icon.
938
+ - Icons are offline: the pinned build or an inline SVG, never a CDN or any remote request.
939
+ - Monochrome, stroke 2, round caps and joins, colour inherited from the text. Navy on a pale navy pill (`--mq-accent-pale`), white on a navy panel inside a translucent pill (`--mq-on-navy-pill`), blue-grey (`--mq-border-strong`) in a card title bar.
940
+ - The icon pill is a small square with the 6 px radius (`--mq-radius-medium`), never a circle or a pill shape.
941
+ - An icon that carries meaning has a text label or an accessible name; a decorative icon is hidden from assistive technology.
942
+
943
+ ## Do's and Don'ts
944
+
945
+ ### Writing: No em dash, no en dash
946
+
947
+ Never use the em dash or the en dash, in any language, in any copy: slides, reports, pages, emails, app strings, translations and these guidelines. A guideline test fails on either character in `guidelines/`.
948
+
949
+ Choose the punctuation that matches how the two parts relate:
950
+
951
+ - a second clause with its own subject becomes a new sentence;
952
+ - an aside goes between commas or in parentheses;
953
+ - a definition, an explanation or a list lead-in takes a colon;
954
+ - two balanced independent clauses take a semicolon;
955
+ - a short label pairing (a section number and its name, a scope and its period) takes a middle dot: "04 · Analytics".
956
+
957
+ A spaced hyphen used as an aside is a disguised dash and follows the same rule. Hyphens inside compound words stay ("Swiss-hosted", "nDSG-konform"). The middle dot and the arrow (→) are allowed.
958
+
959
+ When translating, never carry a dash over from the source language: restructure the sentence in the target language.
960
+
961
+ ### Data patterns: Introduction
962
+
963
+ Every example on this page is fictitious. Where a rule asks for a non-breaking space, the example contains the real character (U+00A0), so it can be copied as is.
964
+
965
+ ### Data patterns: Numbers (owner ruling 8, as amended on 2026-10-10)
966
+
967
+ | Case | Write | Never |
968
+ | :-- | :-- | :-- |
969
+ | Thousands | `2'460'000`: the straight apostrophe (U+0027) | the curly apostrophe, a space, a comma, a full stop |
970
+ | Decimals | `12.5`: a decimal point, in every language | a decimal comma |
971
+ | Percentage | `12.5 %`: a non-breaking space before `%`, in every language | `12.5%`, a breaking space |
972
+ | Amount | `CHF 2'460'000`: the currency first, joined to the amount by a non-breaking space | `2'460'000 CHF`, `Fr.`, a breaking space |
973
+ | Cents | `CHF 6'826.20`; in a report, round to the franc whenever the cents add nothing | |
974
+ | Millions | `CHF 1.2 mio` (French), `CHF 1.2 Mio.` (German), `CHF 1.2 M` (English); in a sentence the word may be written out | a decimal comma |
975
+ | Negative | `−150'000`: the minus sign U+2212 | a hyphen |
976
+ | Change | `+14 %`, `−3 %`: the sign is enough | a colour or an arrow to show direction |
977
+ | Approximation | `~84`, `≈178`: approximate figures stay approximate | false precision |
978
+
979
+ The Italian abbreviation for millions is not yet defined; the owner is to provide it.
980
+
981
+ - One number format per document. Never mix two thousands separators, even when quoting a source that uses another one.
982
+ - A hero figure stays short, about seven to nine characters. The unit goes in its caption ("CHF of net gap"), not in the figure.
983
+ - A table's unit goes in its header, not in every cell (layout).
984
+ - Shares of a whole are rounded with the largest-remainder method, so the displayed percentages add up to exactly 100.
985
+ - In code, format through one function per pattern (amount, number, percentage) rather than by hand; the specimen script exports such functions.
986
+
987
+ ### Data patterns: Dates and periods
988
+
989
+ | Case | Write |
990
+ | :-- | :-- |
991
+ | Date | `08.10.2026`: day, month, year with full stops |
992
+ | Month (axes, tables) | `10.2026` |
993
+ | Year (axes) | `2024` |
994
+ | Period | `2019 → 2025`, with the arrow |
995
+ | Range | `2019-2025` with a plain hyphen, or in words: "3 to 6", « 3 à 6 » |
996
+
997
+ Never an en dash in a range.
998
+
999
+ ### Data patterns: Null versus zero
1000
+
1001
+ A zero is a measurement; a missing value is not. Keep them apart everywhere:
1002
+
1003
+ - **Zero** is written `0` (or `CHF 0`) when the value was measured and is zero.
1004
+ - **Missing** (not measured, not applicable, not yet available) is never written as `0` and never left as an empty cell. Write the language's "not available" abbreviation: `n.d.` (French and Italian), `k.A.` (German), `n/a` (English), and explain it once in a note under the table or chart.
1005
+ - A dash is not used for a missing value: it reads as a minus sign or a range.
1006
+ - An aggregate over values that are all missing is itself missing, never a total of `0`.
1007
+ - A chart leaves a gap for a missing point; it never draws it at zero.
1008
+
1009
+ ### Data patterns: Confidential values
1010
+
1011
+ A confidential rate, price, refund scale or commercial term is never invented and never shown, not even as an example. Show a mask instead: a lock icon followed by `••••` in Geist Mono, navy.
1012
+
1013
+ ### Data patterns: Illustrative data
1014
+
1015
+ - Fictitious figures are flagged on the page itself, in a note (`mq-note`): "Illustrative figures · insurers and medicines anonymised".
1016
+ - Fictitious names follow ADR-0005: Assureur A, Assureur B (A being the largest), Médicament X, Médicament Y, round amounts such as `CHF 300'000`.
1017
+ - Never name a real client, partner or person, never present a fictitious figure as real, and never cite a figure or a legal article that cannot be traced to its source.
1018
+ - In this repository, a line holding a fictitious amount or percentage carries the marker `mq:illustrative`, on the same line or, for a whole file, in its first five lines (as this file does). Inside `examples/`, every amount and every percentage needs it; elsewhere, every currency amount does. The confidentiality check fails on an unmarked figure.
1019
+
1020
+ ### Specialising the design system: Introduction
1021
+
1022
+ The design system is the single source of the MedQuantis visual identity (ADR-0002). A consumer uses it as released and adds only what it alone needs.
1023
+
1024
+ ### Specialising the design system: Framework neutrality
1025
+
1026
+ The design system ships framework-neutral outputs only: CSS custom properties (`dist/tokens.css`), resolved values (`dist/tokens.json`), a Tailwind theme (`dist/tailwind.css`), the kit contract for kit renderers (`dist/kit-contract.css`), the shared `mq-` CSS, fonts, icons and brand files. It ships no framework component.
1027
+
1028
+ - Wiring the tokens into a component library (Nuxt UI, shadcn/ui, Radix, MUI) belongs to the consumer. For example, a component library that names its error colour `error` maps it to `--mq-danger` in the app, not here.
1029
+ - When two consumers need the same wiring, it may ship here as a separate optional export generated from the tokens, never as part of the core outputs.
1030
+ - Apps with a component library take the tokens and the Tailwind theme only. They do not load the `mq-` component CSS and never load `kit-contract.css`, whose unprefixed names collide with framework variables.
1031
+
1032
+ ### Specialising the design system: Promotion tests
1033
+
1034
+ A local component is promoted only if it passes all four tests:
1035
+
1036
+ | Test | Question |
1037
+ | :-- | :-- |
1038
+ | Useful | Is it needed in more than one place? |
1039
+ | Unique | Does it avoid duplicating an existing component? |
1040
+ | Consistent | Is it built from existing tokens and styles only? |
1041
+ | Versatile | Does it fit uses beyond the two that produced it? |
1042
+
1043
+ ### Specialising the design system: The one-off log
1044
+
1045
+ Every local one-off (a component or a token a consumer adds under its own prefix) is logged in `docs/one-offs.md`, one line each: the consumer, what was added, the date, and the outcome. The outcome is one of: promoted, kept local (with the reason), or replaced by an existing component. The log is how two independent one-offs are noticed and become a promotion candidate. Proposals follow the two contribution tiers of GOVERNANCE.md.
1046
+
1047
+ ### Specialising the design system: Use what exists first
1048
+
1049
+ - Build documents from the shared `mq-` classes with their documented markup. Every shared class carries the `mq-` prefix so it never collides with a consumer's classes.
1050
+ - Add no colour, font, shadow, radius or spacing that is not a token. When a need is not covered, combine existing components before inventing one.
1051
+ - The specimen (`make specimen`, then `dist/specimen/index.html`) is the visual reference; when a rendering is in doubt, it decides.
1052
+ - Pin an exact release; never a range or a branch.
1053
+
1054
+ ### Specialising the design system: The four rules
1055
+
1056
+ 1. **Core and semantic tokens are never overridden in a consumer.** A consumer does not redefine `--mq-accent` or any other `--mq-*` name.
1057
+ 2. **A product-level variation is a theme in this repository**, never a local override. There is no theme in v1 (ADR-0003).
1058
+ 3. **Local additions use the consumer's own prefix** (`--deck-*`, `--app-*`) and reference `var(--mq-*)` only. The shared stylelint config (`@medquantis/design-system/stylelint`) rejects literal colours, fonts and shadows, `!important`, unknown custom properties and any radius other than the three radius tokens. A repository whose dense views need a sans body may override its own body family locally under its prefix.
1059
+ 4. **A component invented locally in two consumers is a candidate for promotion** into this repository.