@medquantis/design-system 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,281 @@
1
+ # App patterns
2
+
3
+ Patterns for dense interactive apps (internal review and operations tools) built on the tokens. They describe anatomy, states and the tokens each part reads; the framework components that draw them belong to the app ([specialising](specialising.md)). Every pattern here is for the app medium only.
4
+
5
+ ## App shell
6
+
7
+ One frame for every screen of the app: a left sidebar, then a main panel that stacks an optional environment banner, a navbar, a toolbar and the content. The content scrolls inside the panel; the sidebar, banner, navbar and toolbar stay in place.
8
+
9
+ **Sidebar.** On the surface (`--mq-surface`) with a `--mq-border-light` hairline on its right edge, `--mq-sidebar-width` wide.
10
+
11
+ - Brand block at the top: the wordmark, and under it the product name in muted Outfit 500 at the label size. The block keeps one height expanded or collapsed, so the toggle below it never moves.
12
+ - Navigation in groups. A group opens with an eyebrow in tertiary text; the first group may have none. Each link carries a Lucide icon in `--mq-decoration`, its label in muted Outfit 500, and an optional count at the right in Geist Mono, tertiary. Hover: ink on `--mq-surface-alt`. The current page (`aria-current="page"`): navy text and icon on `--mq-accent-pale`, its count navy.
13
+ - A count shows only when it is above zero. A count that asks for the person's action (what waits for them) is a filled badge, `--mq-on-accent` on `--mq-accent`, Geist Mono 600, with the small radius: never a pill. Every other count stays plain text.
14
+ - The signed-in person at the foot, in a block on the canvas with a `--mq-border-light` border and the medium radius: a round avatar with initials (`--mq-on-accent` on `--mq-accent`), the name, the roles as small mono chips (a role not held is struck through in tertiary), and the session line in muted text, such as "Session open · expires after 8 h of inactivity". Sign-out opens from the name.
15
+ - A toggle on the sidebar's edge collapses and expands it. It carries `aria-expanded` and an accessible name that says the action (expand the navigation, collapse the navigation).
16
+
17
+ **Rail.** The collapsed sidebar, `--mq-sidebar-rail-width` wide. The monogram replaces the wordmark; links become icons alone, centred in 40 by 36 px targets, each label moving to the link's tooltip and accessible name; groups are separated by a hairline instead of their eyebrow; the person shows as the avatar alone. The only count kept is the action badge, at the icon's top right.
18
+
19
+ **Environment banner.** Above the navbar on every environment except production, an unknown environment included, never on production. One line of ink text at the label size, Outfit 500, on `--mq-warning-tint`, with a `--mq-warning-fill` bottom border and a `--mq-warning` icon, as a `status` region. It is genuine system status, so it takes the warning colours ([colour](colour.md)).
20
+
21
+ **Navbar.** On the surface with a hairline below: a screen icon (a white Lucide icon on a 32 px navy tile with the medium radius), the page title as the page's `h1` in Outfit 500 at the h3 size, and a one-line subtitle in muted text at the label size. Global actions, such as search, sit at the right.
22
+
23
+ **Toolbar.** On the canvas with a hairline below: the screen's filters in one row that wraps, a flexible gap, then right-aligned controls. Each screen fills it; a screen without filters leaves it out.
24
+
25
+ **Spacing.** Side padding `--mq-space-6`, `--mq-space-4` on phones, plus the device's safe-area inset.
26
+
27
+ | State | What changes |
28
+ | :-- | :-- |
29
+ | Expanded | The full sidebar |
30
+ | Rail | As above; the app may start on the rail and keep the person's choice |
31
+ | Drawer, below 768 px | The sidebar leaves the layout. A menu button at the left of the navbar opens it in full over the content, with `--mq-shadow-medium`; it closes on a link, on Escape and on a tap outside. The edge toggle is hidden |
32
+ | Phone, below 480 px | The navbar's subtitle and screen icon are hidden |
33
+ | Service unavailable | The shell stays. Counts are hidden, never shown as 0, since a count that could not be measured is missing, not zero ([data patterns](data-patterns.md)); the content shows the error state |
34
+ | Not production | The environment banner shows |
35
+
36
+ The drawer slides in within 200 ms and appears at once under `prefers-reduced-motion`.
37
+
38
+ Stays in the app: the routes and their grouping, the count values and which count asks for action, the session and its timeout, the environment and where it comes from, and the search.
39
+
40
+ ## Sign-in, access refused and error pages
41
+
42
+ Three ways the app answers a person it cannot serve yet. None of them ever falls back to a framework's default page.
43
+
44
+ **Sign-in page.** The only page served without a session, so it carries no app bundle: a self-contained page built from `tokens.css` and `fonts.css` alone, on the canvas, centred both ways.
45
+
46
+ - The wordmark above one card: the surface, a `--mq-border-light` border, the medium radius, `--mq-shadow-small`, `--mq-space-6` padding, at most 420 px wide. The wordmark is not a link, since nothing behind it is reachable yet.
47
+ - In the card, in order: an eyebrow in tertiary mono ("Restricted access"), the product name as the `h1`, one muted sentence on what the tool is for, an optional notice, then the one action: a full-width primary button with a sign-in icon ("Sign in with MedQuantis"), and under it a muted hint at the label size naming the account to use and the role the app asks for.
48
+ - The legal line under the card, in tertiary Geist Mono ([brand](brand.md)).
49
+ - One action only: no second link, no password reset, no sign-up, which belong to the identity provider.
50
+
51
+ | Arrival | Notice above the button |
52
+ | :-- | :-- |
53
+ | First visit | None |
54
+ | After the session expired | An info notice as a `status` region: an `--mq-info` icon and border, the sentence in ink, such as "Your session expired. Sign in to pick up where you left off." |
55
+ | After sign-out | A success notice the same way with `--mq-success`, such as "You are signed out." |
56
+
57
+ After sign-in the person lands on the page they asked for, never on a default home.
58
+
59
+ **Access refused page.** For a signed-in person whose account lacks the role the app needs. It replaces the shell, since every page behind it would refuse again: a navbar with the monogram and the product name only (no sidebar, no link into the app), then one card at most 560 px wide.
60
+
61
+ - A navy icon pill (`--mq-accent-pale`, the medium radius) with a lock icon, the title as the `h1` ("Access refused"), a lead sentence in Outfit 500 naming the missing role in bold, and one muted sentence explaining it.
62
+ - A key-value block on the canvas with a `--mq-border-light` border and the medium radius: the signed-in account (avatar, name, address in mono), then the role asked for as a struck-through role chip, beside "Not assigned to this account" in `--mq-danger` with an icon.
63
+ - How to get the role, in muted text: who assigns it, and that the person signs in again once it is assigned.
64
+ - One primary action, "Switch account": it signs out of the app and of the identity provider, then opens the sign-in page in its signed-out state, and a note under the button says so. Never a redirect back into the app, which would loop.
65
+
66
+ **Error states inside the shell.** When a screen cannot show its content, the shell stays (sidebar, and the navbar with the screen's own title) and one card takes the content area: centred, at most 560 px wide, on the surface with `--mq-shadow-small`, its text centred. In order: a 40 px icon tile with the medium radius, a status word in Geist Mono capitals at the eyebrow size, the title as an `h2` at the h3 size, one muted sentence no wider than 50 characters, and the actions.
67
+
68
+ | State | Icon tile | Status word | Body and actions |
69
+ | :-- | :-- | :-- | :-- |
70
+ | Not found (404) | `--mq-decoration` icon on `--mq-surface-alt` | "Not found", tertiary | What was not found and why that may be, the address asked for in tertiary mono, and a primary action back to the list |
71
+ | Service unavailable (503) | `--mq-warning` icon on `--mq-warning-tint` | "Unavailable", `--mq-warning` | That the service does not answer for now, and a primary "Retry". While it retries, the button is disabled with a turning icon; if the service is still down, a muted line under it gives the time of the last attempt ("Still unavailable at the last attempt, at 14:32") in a polite live region. The sidebar's counts are hidden (app shell, above) |
72
+ | Unexpected error (500) | `--mq-danger` icon on `--mq-surface-alt` | "Error", `--mq-danger` | That the request failed and what to do if it happens again; the request ID as selectable mono text in a bordered row labelled `X-Request-Id`, with a "Copy" button that confirms once ("Copied"); a secondary action back to the list |
73
+
74
+ The status word and the icon carry each state together with its colour, never the colour alone ([accessibility](accessibility.md)). Under `prefers-reduced-motion` the icon does not turn and the button reads "Retrying".
75
+
76
+ Stays in the app: the sign-in address and its return path, the role name and who assigns it, the request ID value, and the wording of each state.
77
+
78
+ ## Overview: queue boxes and coverage cards
79
+
80
+ The overview lands a person on what waits for them, then shows how much of each area is covered, in one grammar for every area.
81
+
82
+ **Queue boxes.** A row of boxes, one per queue, three across and one column on phones. Each box is a link to its list: the surface, a `--mq-border-light` border, the medium radius and `--mq-shadow-small`, `--mq-space-4` padding; on hover the border turns `--mq-border-strong`. In order:
83
+
84
+ - the queue's name in Outfit 600, with its total at the right in navy Outfit 500 at the figure size, in tabular figures;
85
+ - optionally one line per area, its name in ink and its count in navy at the right;
86
+ - the age line in tertiary text at the note size: "Oldest waiting for 3 days", the duration in ink.
87
+
88
+ | State | Shows |
89
+ | :-- | :-- |
90
+ | Zero | The box stays in place, quiet: the total in tertiary, no area lines, and the age line reads "Nothing waits" |
91
+ | The person's own queue, above zero | A navy border and the pale navy fill (`--mq-accent-pale`); the same box at zero is quiet like the others |
92
+ | Overdue | The age turns `--mq-warning` and takes a warning icon, so the state never rests on colour alone. The threshold belongs to the app |
93
+ | Loading | The box keeps its size with skeleton bars on `--mq-surface-alt`; their shimmer stops under `prefers-reduced-motion` |
94
+
95
+ **Coverage cards.** One card per area, three across, one column below 1100 px; the same card as a queue box but not a link itself. In order:
96
+
97
+ - the area's title in Outfit 600, with an "Open →" link at the right in navy;
98
+ - the scope in Geist Mono capitals at the eyebrow size, tertiary: what is counted and over which editions ("Coverage of 1'248 texts · every edition served");
99
+ - a segmented bar, 12 px high, its ends rounded with the small radius, never a pill, with 2 px gaps between segments ([charts](charts.md), distribution bar);
100
+ - the legend under it: per segment a 10 px swatch with the small radius, the label (a link when the segment opens a list), the count in Geist Mono, muted, and the percentage in Geist Mono, tertiary, both right-aligned;
101
+ - optionally one take-away line in muted text at the note size.
102
+
103
+ Rules for the bar:
104
+
105
+ - The segments take the ordered palette in order, `--mq-chart-series-1` to `--mq-chart-series-4`, and what is left (not yet detected, not yet extracted) takes the remainder fill `--mq-chart-rest`, with a series 4 outline in the bar and the swatch. Five segments therefore need no new hue. A status colour never colours a segment: a segment that needs attention says so in its label.
106
+ - A segment below 1 % keeps a 2 px minimum width in the bar, and its legend line reads "< 1 %"; every other percentage is rounded with the largest-remainder method ([data patterns](data-patterns.md)).
107
+ - A segment that opens a list is a link in both the bar and the legend, with the same accessible name; one that opens nothing is plain text, never a dead link.
108
+ - The bar has an accessible name that states the scope; the legend carries every value in text.
109
+ - One edition or every edition served: the scope line says which; the card does not change shape.
110
+
111
+ Stays in the app: the segment keys, their counts and labels, which segments link where, the overdue threshold, and the queues themselves.
112
+
113
+ ## Review table: filters, condition tile, status pill, code chips
114
+
115
+ The list behind every review screen: a filter toolbar, then a table whose rows carry a condition tile, a status pill and code chips. It extends the app table of [layout](layout.md), Tables.
116
+
117
+ **Filters**, in the shell's toolbar (app shell, above):
118
+
119
+ - **Select with its label inside**: 32 px high, the surface, a `--mq-border` border, the small radius; the label in muted text, the value in ink Outfit 500. Each option ends with its count, and an option whose count is zero is muted but still listed.
120
+ - **Segmented control** for a filter in two steps: a track on `--mq-surface-alt` with a `--mq-border` border and the medium radius, its segments with the small radius, each with its count in Geist Mono, tertiary. The selected segment is navy Outfit 500 on the surface with `--mq-shadow-small`; an empty segment is tertiary. A 1 px `--mq-border-strong` rule, 16 px high, separates the two steps. It is a radio group: arrow keys move, and the selection is announced.
121
+ - **Clear filters**: a ghost text button that appears once any filter differs from its default, and puts every filter back.
122
+
123
+ **Table.** The app table wrapper: the surface, a `--mq-border-light` border, the medium radius, `--mq-shadow-small`, scrolling sideways inside itself below its minimum width. Header row on `--mq-surface-alt`, never a navy band; cells with `--mq-space-3` padding and a `--mq-border-light` hairline between rows; text at the table text size.
124
+
125
+ | State | Shows |
126
+ | :-- | :-- |
127
+ | Loading | Skeleton rows of the same height, bars on `--mq-surface-alt`; the shimmer stops under `prefers-reduced-motion` |
128
+ | Sorted | The sorted header is underlined and carries `aria-sort`; the sort reads in the header's accessible name |
129
+ | Hover | The row on `--mq-surface-alt` |
130
+ | Keyboard | Rows are reachable with the keyboard (Arrow or J and K keys move, Enter opens); the focused row shows the focus ring, and moving never opens |
131
+ | Selected (the open item) | `--mq-accent-pale`, with a 3 px navy edge (`--mq-stroke-tab`) on its first cell |
132
+ | Waiting for you | The quiet pale fill `--mq-accent-pale-2`, on that row only, so the others stay quiet |
133
+ | Empty | The app empty state ([layout](layout.md), App patterns) in the table's place: "No open item", or, under filters, "No item matches these filters" with a clear-filters action |
134
+
135
+ **Condition tile.** A short code that identifies a review condition (R1, R2): a navy square (`--mq-on-accent` on `--mq-accent`), the medium radius, the code in Geist Mono 600, 30 px in the list and 32 px in a dialog header, followed by the condition's name in Outfit 600. A tooltip on the tile gives what raised it. It is an identifier, not a state, so every tile is the same navy.
136
+
137
+ **Status pill.** The item's step, a word in Outfit 500 at the label size with the small radius (never a pill shape despite its name), and under it a detail line in tertiary text at the note size (who, since when). The four states differ by mark style, and each carries its word:
138
+
139
+ | State | Mark |
140
+ | :-- | :-- |
141
+ | To decide | Outline: `--mq-border-strong` border, muted text on the surface |
142
+ | Confirmation required | The warning mark: `--mq-warning` text and a `--mq-warning-fill` border on `--mq-warning-tint`, since it asks the person to act |
143
+ | Under review by a colleague | Pale: navy text and border on `--mq-accent-pale`; the detail line names the colleague |
144
+ | Under review by you | Pale, plus a 2 px `--mq-accent-pale` ring around it, and its row takes the waiting-for-you fill |
145
+
146
+ **Code chip.** A mono code with its name: the code in Geist Mono at the note size on `--mq-surface-alt` with the small radius, then the name in ink. A name too long for its cell is cut with an ellipsis and given in full in a tooltip and the accessible name; a code without a name shows the code alone, never an empty name.
147
+
148
+ | Variant | Mark |
149
+ | :-- | :-- |
150
+ | Plain | As above |
151
+ | Key code (the one the row is about) | `--mq-accent-pale` with navy text |
152
+ | Added | A leading "+" and the code in `--mq-change-added`, with a 1 px ring of the same colour |
153
+ | Removed | Struck through, the code in `--mq-change-removed` |
154
+
155
+ The change colours are apps only and never carry the change alone: the "+" and the strike say it in every theme and in greyscale ([charts](charts.md), App categorical palette).
156
+
157
+ Stays in the app: the conditions and their codes, the wording of each state, the sort keys, the keyboard shortcuts beyond Arrow and Enter, and which code is the key code.
158
+
159
+ ## Decision modal
160
+
161
+ A large dialog where one decision is taken: context on the left, process on the right, actions at the foot. Its proportions and pane heights stay fixed from item to item, so nothing jumps while a person works through a list.
162
+
163
+ **Frame.** On the surface with the overlay radius (`--mq-radius-overlay`) and `--mq-shadow-medium`, inset `--mq-space-5` from the viewport plus the safe-area insets, at most 1320 px wide. Escape closes it, focus stays inside while it is open and returns to the row it came from. Below 768 px it fills the screen with no radius, and its panes stack and scroll as one.
164
+
165
+ **Header**, with a hairline below:
166
+
167
+ - at the left, the condition tile (32 px) and the condition's name as the dialog title in Outfit 500 at the h3 size; under it the subject (the product) and an edition badge in the outline mark ("Edition 01.10.2026");
168
+ - in the centre, the progress line at a fixed width;
169
+ - at the right, the position in mono ("3 / 12"), previous and next buttons, and close.
170
+
171
+ **Progress line.** Three steps (detected, review, decided): a 22 px circle and a label each, joined by 1.5 px lines.
172
+
173
+ | Step | Mark |
174
+ | :-- | :-- |
175
+ | Done | `--mq-accent-pale` circle with a navy ring and a tick; the line after it navy |
176
+ | Current | A navy circle with its number in `--mq-on-accent` and a 3 px `--mq-accent-pale` ring; the label in navy Outfit 600 |
177
+ | To come | A `--mq-border-strong` ring on the surface, the label tertiary; the line after it `--mq-border` |
178
+ | Optional review | The circle dashed, with "optional" in its detail line |
179
+ | Required review | The detail line in `--mq-warning` with a warning icon: "required: your confirmation" |
180
+
181
+ Labels keep one line, cut with an ellipsis and given in full in a tooltip. Where the header is too narrow for title, line and navigation side by side, the line takes its own row under the title.
182
+
183
+ **Left pane: context**, on the canvas, a hairline on its right edge:
184
+
185
+ - "The problem": a card with a fixed height of four lines, what the condition means for this item;
186
+ - underline tabs ([layout](layout.md), App patterns): the source text ([limitation-text marks](#limitation-text-marks)) and the decision history with its count ([history timeline](#history-timeline)). The chosen tab stays chosen from item to item.
187
+
188
+ **Right pane: process**, on the surface, in numbered steps (a 22 px navy circle with the number in `--mq-on-accent`, then the step's name in Outfit 500):
189
+
190
+ 1. **Evidence**: one row per code in play, the code chip (review table, above), its name, and its source tag; a reason or quote an agent gave sits under its row in Newsreader italic, muted. The list has a fixed height and scrolls inside.
191
+ 2. **Decision**: the choice cards, then the form fields.
192
+ 3. **Result**: what is served today and what will be served after.
193
+
194
+ **Source tags**, one mark style per source ([colour](colour.md), States by mark style), each with its word:
195
+
196
+ | Source | Mark |
197
+ | :-- | :-- |
198
+ | Attached by the regulator | Solid |
199
+ | Printed in the text | Pale, like the printed-code mark in the text |
200
+ | Proposed by the agent | Dashed: not yet confirmed |
201
+ | Candidate from the title | Outline |
202
+ | Close code | Quiet |
203
+ | Removed by the regulator | Struck through |
204
+
205
+ **Choice cards.** A radio group of cards, one per answer: a `--mq-border-light` border and the small radius; the title in Outfit 500, one muted line of explanation, and at the right the codes the answer would serve, or "no code" in tertiary italic. Hover on `--mq-surface-alt`; selected: a navy border on `--mq-accent-pale-2`; invalid: a `--mq-danger` border with the error in words under the group. A card that restates what is served today says so in its explanation.
206
+
207
+ **Result card.** A dashed `--mq-border-strong` border with the medium radius, two rows labelled in Geist Mono capitals, "Today" and "After": the code chips of each, an added code with its "+", a removed one struck through, both in the change-type colours, and "unchanged" in tertiary when nothing moves. Its height is fixed; long names are cut with a tooltip.
208
+
209
+ **Held decision card.** While a colleague's review is pending it replaces the decision form: `--mq-warning-tint` with a `--mq-warning-fill` border and the medium radius, its title ("Decision to review") in `--mq-warning`, then rows of label and value: the held answer, its reason, the comment, who asked and when. The reviewer sees approve and send back, where send back asks for a comment; the person who asked sees withdraw. Everyone else sees it read-only.
210
+
211
+ **Alerts**, above the form, each with an icon and a sentence:
212
+
213
+ - a refused write: a `--mq-danger` border, what was refused and why; the form keeps what the person entered;
214
+ - a stale write: an `--mq-info` border, "Decided meanwhile by a colleague, at 14:20"; the item reloads with that decision at the top of its history;
215
+ - after a decision: a notice that the change reaches the public site after the next refresh.
216
+
217
+ **Footer** on the canvas with a hairline above: facts at the left (muted label, ink value), actions at the right, secondary first and the primary last, such as "Confirm and go to next". An action the person cannot take is disabled with its reason shown in words beside it ("Role required: Maintainer"), never refused after a click on an enabled button.
218
+
219
+ **Closed item.** It opens in its last state, read-only: no form, the result card showing what is served, and only navigation and close in the footer.
220
+
221
+ Stays in the app: the choices and what each serves, which actions are allowed and why not, the wording of refusals and alerts, the sources and the data.
222
+
223
+ ## Limitation-text marks
224
+
225
+ How a verbatim source text shows the passages a decision rests on. The text stays the official text: a mark adds a background or an underline, and never changes a word, its case or its order.
226
+
227
+ **Text box.** Newsreader at a reading size (about 16.5 px) with a loose line height (about 2.2), so a mark never touches the line above it; on the canvas with a `--mq-border-light` border, the medium radius, and `--mq-space-3` by `--mq-space-4` padding. A long text scrolls inside its box (in the decision modal, inside its tab), never the page.
228
+
229
+ | Mark | Look | Means |
230
+ | :-- | :-- | :-- |
231
+ | Printed code | Geist Mono, navy text on `--mq-accent-pale`, the small radius | A code the text itself prints |
232
+ | Accepted quote | `--mq-accent-pale-2` with a 2 px navy underline | A passage accepted as evidence for a decision |
233
+ | Contested printed code | The printed mark with a 2 px `--mq-warning` underline | The printed code this item calls into question |
234
+ | Quote pointed at | The accepted quote on `--mq-accent-pale` | While the person points at that quote elsewhere, such as its evidence row |
235
+
236
+ The contested mark differs from the printed one by its underline, not by colour alone, and the legend names it.
237
+
238
+ **Legend.** Under the box at the note size in muted text: a swatch for each mark this item uses (the contested mark only where the item has one), then, in tertiary, the editions the text belongs to in mono ("Binding in editions 01.03.2024 to 01.10.2026").
239
+
240
+ **Quoting a passage.** The text is plain text, so it can be selected with a pointer or the keyboard; the selection shows on `--mq-accent-pale`. When the selection could prove a decision, a popover opens beside it (the surface, a `--mq-border` border, the medium radius, `--mq-shadow-medium`): an eyebrow, "Quote as evidence for", then one small button per code that still needs a quote, its code in mono and its name. When nothing needs a quote, no popover opens. Once quoted, the passage takes the accepted-quote mark, and the decision form lists it as a quote row: a 3 px navy left edge (`--mq-stroke-tab`) on `--mq-accent-pale-2`, the passage in Newsreader italic, and a remove action.
241
+
242
+ | State | Shows |
243
+ | :-- | :-- |
244
+ | Printed, quoted, contested | The marks above, each in the legend |
245
+ | Selection in progress | The selection on `--mq-accent-pale`; the popover only when a code needs a quote |
246
+ | Long text | It scrolls inside its box; the legend stays under it |
247
+
248
+ Stays in the app: the passages and where they fall in the text, which codes need a quote, matching a quote to the text, and the popover's actions.
249
+
250
+ ## History timeline
251
+
252
+ The decisions on one item, newest first: who did what, with which values, and which row is in force now. It is read on every item and later as an audit view.
253
+
254
+ **Filters** above it: a row of toggle buttons, "All" then one per source, each with its count in Geist Mono, tertiary; a `--mq-border` border with the small radius, never a pill. A filter that is on is navy (`--mq-on-accent` on `--mq-accent`), its count in `--mq-accent-pale`, and carries `aria-pressed`.
255
+
256
+ **Rows.** A vertical list joined by a 1 px `--mq-border` line through the dots. Each row is one line that opens to its details:
257
+
258
+ - the dot, by origin: a person's decision a filled navy dot, an agent's a dashed `--mq-border-strong` ring, the system's a plain ring, a publication a small square in `--mq-decoration`;
259
+ - the date in Geist Mono, muted;
260
+ - the source as an icon with its name (in a narrow pane the icon alone, its name in the tooltip and the accessible name);
261
+ - the event as a chip with the small radius, one mark style per outcome: accepted or applied in the ring mark (navy border and text), rejected dashed, a person's decision pale (`--mq-accent-pale`), a publication quiet (`--mq-surface-alt`);
262
+ - the person, muted; a person the app cannot name reads "Unknown identity" in tertiary italic, never an empty cell or an internal identifier;
263
+ - the values in Geist Mono at the right, or "none" in tertiary italic;
264
+ - the in-force column, then a chevron that turns when the row opens.
265
+
266
+ Hover on `--mq-surface-alt`. A row opens with a click or Enter: its details sit under it, aligned with the event, in tertiary text at the note size: the reason code in mono, the note, and the run that produced it as a mono link.
267
+
268
+ **The row in force.** Exactly one row is live: its in-force column shows a 10 px `--mq-success` dot with the live halo (`--mq-shadow-live`), its name ("in force") in the tooltip and the accessible name. Superseded rows leave the column empty.
269
+
270
+ **Key.** Under the list, after a hairline: each event mark and each dot with its meaning, in tertiary text at the note size, so no mark is read by colour alone.
271
+
272
+ | State | Shows |
273
+ | :-- | :-- |
274
+ | Collapsed, expanded | One line; the line with its details under it |
275
+ | Live, superseded | The live dot with its halo; an empty column |
276
+ | Filter on, off | The navy toggle; the outlined toggle. "All" turns the others off |
277
+ | Loading | Skeleton rows of the same height; the shimmer stops under `prefers-reduced-motion` |
278
+ | Empty | "No decision yet" in muted text, in the list's place |
279
+ | Unknown person | "Unknown identity", tertiary italic |
280
+
281
+ Stays in the app: the event types and their wording, the sources, the data, and which row is in force.
@@ -27,6 +27,8 @@ Set it in Geist Mono at the note size, tertiary text colour (`--mq-tertiary`), w
27
27
 
28
28
  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.
29
29
 
30
+ 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:`.
31
+
30
32
  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:
31
33
 
32
34
  | Medium | French | German | English | Italian |
@@ -36,7 +38,7 @@ Material left with a reader (a deck after a meeting, a report sent outside) also
36
38
 
37
39
  ## Wordmark
38
40
 
39
- 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, at every size (owner ruling 6).
41
+ 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).
40
42
 
41
43
  Each background has two files in `brand/`:
42
44
 
@@ -48,6 +50,7 @@ Each background has two files in `brand/`:
48
50
  Rules:
49
51
 
50
52
  - Use the on-navy file on navy and the on-light file on light backgrounds. Never recolour, stretch, outline, shadow or rearrange the wordmark.
53
+ - 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).
51
54
  - 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.
52
55
  - 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.
53
56
 
@@ -62,6 +65,8 @@ Where the wordmark does not fit (favicon, app icon, avatar, app tile), use the m
62
65
  | `brand/monogram-small.svg` | 16 px and smaller: larger letters, M 800 and Q 600 |
63
66
  | `brand/favicon/` | `favicon.svg`, `favicon.ico` (16, 32, 48), `apple-touch-icon.png` (180), `icon-192.png`, `icon-512.png` |
64
67
 
68
+ 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.
69
+
65
70
  ## No combined logo yet
66
71
 
67
72
  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.
@@ -1,6 +1,6 @@
1
1
  # Charts
2
2
 
3
- 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.
3
+ Charts use the navy and blue-grey family, in every medium. An interactive app may also use the app categorical palette, for a chart of more categories than the ordered palette carries and for data categories such as change types; documents never do ([ADR-0008](../docs/architecture/decisions/adr-0008-apps-have-a-categorical-palette.md)). Status colours never colour a chart series, not even in an app; a chart that shows system status labels it in words.
4
4
 
5
5
  ## The ordered palette
6
6
 
@@ -35,6 +35,38 @@ Rules:
35
35
  | Ranking with more than four items | Highlight on grey, below |
36
36
  | 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 |
37
37
 
38
+ ## App categorical palette (apps only)
39
+
40
+ Six hues for interactive apps whose charts or lists show more categories than the four ordered series can carry, and three change types built on them (owner rulings 20 and 21, [ADR-0008](../docs/architecture/decisions/adr-0008-apps-have-a-categorical-palette.md)). They are light only until their dark values are ruled, and they never appear in documents, which keep the ordered palette.
41
+
42
+ | Token | Value | Contrast on surface | Allowed for |
43
+ | :-- | :-- | :-- | :-- |
44
+ | `--mq-app-cat-green` | `#1c7e3f` | 5.12:1 | Marks, fills and text |
45
+ | `--mq-app-cat-orange` | `#d78000` | 3.01:1 | Marks and fills only |
46
+ | `--mq-app-cat-orange-text` | `#a35f00` | 5.01:1 | Text in the orange hue: counts and labels |
47
+ | `--mq-app-cat-vermillion` | `#cf2a00` | 5.27:1 | Marks, fills and text |
48
+ | `--mq-app-cat-blue` | `#3a8ff5` | 3.27:1 | Marks and fills only |
49
+ | `--mq-app-cat-violet` | `#5a2fbe` | 8.12:1 | Marks, fills and text |
50
+ | `--mq-app-cat-teal` | `#178a91` | 4.13:1 | Marks and fills only |
51
+
52
+ | Change type | Token | Reads |
53
+ | :-- | :-- | :-- |
54
+ | Added | `--mq-change-added` | The green |
55
+ | Modified | `--mq-change-modified`; its text `--mq-change-modified-text` | The orange; the orange text |
56
+ | Removed | `--mq-change-removed` | The vermillion |
57
+
58
+ Between the six hues and navy series 1, the smallest CIEDE2000 distance is 17.4 under normal vision, 11.9 under protanopia, 12.9 under deuteranopia and 8.4 under tritanopia (green and teal); the token test holds those floors.
59
+
60
+ Rules:
61
+
62
+ - A one-series chart stays navy (`--mq-chart-series-1`). Up to four series, the ordered palette applies in apps too; the categorical hues are for charts of five or more categories and for data categories.
63
+ - Fix each hue to its category across every view of the app; never let a charting library rotate through the palette.
64
+ - No series uses a status token. The vermillion is a data category ("removed from the list"), not an error: `--mq-danger` stays for errors. The green, the orange, the orange text and the vermillion sit CIEDE2000 3.3, 2.2, 2.8 and 7.9 from `--mq-success`, `--mq-warning-fill`, `--mq-warning` and `--mq-danger`, so code tells them apart by token name and the screen by the word or sign each carries, never by hue.
65
+ - Marks sit on the surface (`--mq-surface`), where every hue meets 3:1 and needs no outline. On the canvas the orange falls to 2.88:1, and on the recessed surface the orange and the blue fall below 3:1, so a chart in these hues sits in a white card.
66
+ - Text in a hue meets 4.5:1: the green, the vermillion and the violet may be text, orange text reads `--mq-app-cat-orange-text`, and the blue and the teal are never text.
67
+ - Never colour alone: the green and the vermillion have almost the same lightness, so in greyscale they cannot be told apart. Every mark carries a direct label or a legend entry, and a change type its sign (+, −) or its word; label the green and the teal directly where they touch.
68
+ - A change type keeps its hue wherever a change is listed (badge, dot, filter, count) and keeps its mark style on top: an added value with +, a removed value struck through.
69
+
38
70
  ## Highlight on grey
39
71
 
40
72
  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.
@@ -7,7 +7,7 @@ Use semantic tokens only. The `--mq-core-*` names hold raw values and exist so s
7
7
  | Medium | Colours allowed | How a state is shown |
8
8
  | :-- | :-- | :-- |
9
9
  | 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 |
10
- | 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 |
10
+ | Interactive apps | Everything documents use, plus `--mq-success`, `--mq-warning`, `--mq-danger`, `--mq-info`, and the app categorical palette with its change types ([charts](charts.md)) | Status colour for genuine system status only (errors, validation, alerts, confirmations), always with an icon or a word; a categorical hue for a data category only, always with a label, a sign or a word |
11
11
 
12
12
  Documents never carry green, orange, red, sky blue, pink, purple or turquoise, not even to say good or bad ([ADR-0004](../docs/architecture/decisions/adr-0004-status-colours-in-apps-only.md)). They never load `css/app.css`. A callout in a document is always navy; there is no warning-orange or success-green callout.
13
13
 
@@ -20,12 +20,13 @@ Documents never carry green, orange, red, sky blue, pink, purple or turquoise, n
20
20
  | `--mq-accent-pale` | `#eaeef7` | Icon pills and chips, a deviating value, the highlighted table row, the pale state |
21
21
  | `--mq-accent-pale-2` | `#f2f4f9` | Progress tracks, the quiet state, callout background |
22
22
  | `--mq-on-accent` | `#ffffff` | Text and icons on an accent fill (14.53:1) |
23
+ | `--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) |
23
24
 
24
25
  Navy carries emphasis on its own. Status colours never replace it for emphasis, in any medium.
25
26
 
26
27
  ## Neutrals
27
28
 
28
- The neutrals are warm greys tuned to the warm canvas, never blue-tinted greys.
29
+ 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](#dark-theme)).
29
30
 
30
31
  | Token | Value | Use | Contrast |
31
32
  | :-- | :-- | :-- | :-- |
@@ -83,7 +84,7 @@ The first six are the `mq-badge` states (`is-solid`, `is-ring`, `is-pale`, `is-o
83
84
  | `--mq-danger` | `#d8232a` | Errors, destructive actions | 5.01:1 on surface, 4.79:1 on canvas |
84
85
  | `--mq-info` | `#1565c0` | Information | 5.75:1 on surface, 5.50:1 on canvas |
85
86
  | `--mq-warning` | `#a06400` | Warning text, icons and borders | 4.86:1 on surface, 4.65:1 on canvas |
86
- | `--mq-warning-fill` | `#d4850a` | Warning badge or highlight fill, always with ink text (5.93:1) | Fill only |
87
+ | `--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 |
87
88
  | `--mq-warning-tint` | `#fbf3e7` | Warning background behind ink text (15.81:1) | Fill only |
88
89
 
89
90
  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).
@@ -95,8 +96,89 @@ Every status colour comes with an icon or a word (WCAG 1.4.1):
95
96
  - a flag is a coloured word, and only on the rows that need attention, so normal rows stay quiet;
96
97
  - light badge backgrounds come from the 50 and 100 steps of the status scales in `dist/tailwind.css`, never a hand-picked tint.
97
98
 
98
- The red of the old Paracel theme survives only as `--mq-danger` ([ADR-0003](../docs/architecture/decisions/adr-0003-navy-is-the-single-brand-accent.md)). 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](specialising.md)).
99
+ The red of the old Paracel theme survives only as `--mq-danger` ([ADR-0003](../docs/architecture/decisions/adr-0003-navy-is-the-single-brand-accent.md)). The app categorical palette and the change types (`--mq-app-cat-*`, `--mq-change-*`) are data categories, never statuses; their values and rules are in [charts](charts.md) ([ADR-0008](../docs/architecture/decisions/adr-0008-apps-have-a-categorical-palette.md)). Other 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](specialising.md)).
99
100
 
100
101
  ## Chart colours
101
102
 
102
103
  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](charts.md).
104
+
105
+ ## Dark theme
106
+
107
+ MedQuantis has one dark theme ([ADR-0007](../docs/architecture/decisions/adr-0007-medquantis-has-a-dark-theme.md), 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.
108
+
109
+ - **Apps and the website:** light and dark; the app decides how the viewer chooses, and may follow the viewer's setting by toggling `.dark`.
110
+ - **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.
111
+ - **Slides:** a whole deck, or a single slide carrying `.dark`.
112
+
113
+ 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.
114
+
115
+ | Token | Dark value | Use | Contrast |
116
+ | :-- | :-- | :-- | :-- |
117
+ | `--mq-canvas` | `#11161f`, a near-black with a navy cast | Background of every dark page and slide | Background |
118
+ | `--mq-surface` | `#1a2131` | Cards, panels, tables, figures, inputs | Background |
119
+ | `--mq-surface-alt` | `#141a26` | Recessed areas: table headers, grouped areas | Background |
120
+ | `--mq-desk` | `#0b0f16` | On screen only, around a dark page preview | Background |
121
+ | `--mq-border` | white at 14 % | Borders, dividers | Decorative |
122
+ | `--mq-border-light` | white at 10 % | Hairlines, card borders | Decorative |
123
+ | `--mq-border-strong` | `#8795b3` | Outline of outlined and dashed state marks | 6.03:1 on canvas, 5.35:1 on surface, non-text |
124
+ | `--mq-ink` | `#f5f4f1`, warm white | Primary text and values | 16.48:1 on canvas, 14.62:1 on surface |
125
+ | `--mq-muted` | `#c3cbdc` | Secondary text, leads, captions | 11.13:1 on canvas, 9.88:1 on surface |
126
+ | `--mq-tertiary` | `#8f98aa` | Tertiary text: notes, footers, axis labels | 6.25:1 on canvas, 5.54:1 on surface |
127
+ | `--mq-decoration` | `#6b7385` | Non-text decoration only, never text | 3.81:1 on canvas, 3.38:1 on surface, non-text |
128
+ | `--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 |
129
+ | `--mq-accent-hover` | `#8aa0d6` | Hover and pressed state of a link or an accent control | 6.98:1 on canvas |
130
+ | `--mq-accent-pale` | the lifted navy at 18 % | Icon pills and chips, the highlighted row, the pale state | Fill |
131
+ | `--mq-accent-pale-2` | the lifted navy at 10 % | Progress tracks, the quiet state, callout background | Fill |
132
+ | `--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 |
133
+ | `--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 |
134
+ | `--mq-focus-ring-color` | `#9fb3e8`, the light navy | Focus ring on dark backgrounds | 8.71:1 on canvas |
135
+ | `--mq-panel-start` | `#2f4c8f` | Light end of the navy panel gradient | Background, white 8.23:1 on the panel start |
136
+ | `--mq-panel-end` | `#0f2a5e` | Dark end of the navy panel gradient | Background |
137
+ | `--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 |
138
+ | `--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 |
139
+ | `--mq-shadow-live` | a 3 px ring of the dark success green `#6fbf73` at 17 %; in light the success green at 17 % | Apps only: the halo of a live dot, the row in force on a timeline ([app patterns](app-patterns.md)) | Decorative |
140
+ | `--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 |
141
+ | `--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 |
142
+
143
+ 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.
144
+
145
+ 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).
146
+
147
+ **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](../docs/architecture/decisions/adr-0003-navy-is-the-single-brand-accent.md)). 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.
148
+
149
+ 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.
150
+
151
+ | Token | Dark value | Contrast | Allowed for |
152
+ | :-- | :-- | :-- | :-- |
153
+ | `--mq-on-navy-title` | `#ffffff`, unchanged | 8.23:1 on the panel start | Text |
154
+ | `--mq-on-navy-secondary` | white at 68 %, unchanged | 4.80:1 on the panel start | Text |
155
+ | `--mq-on-navy-second-level` | `#c3cbdc`, unchanged | 5.05:1 on the panel start | Text |
156
+ | `--mq-on-navy-accent` | `#b2c2ed`, a lighter navy tint | 4.64:1 on the panel start | Text, narrow margin |
157
+ | `--mq-on-navy-discreet` | white at 50 % | 3.37:1 on the panel start | Large text or non-text only |
158
+ | `--mq-on-navy-rule`, `--mq-on-navy-rule-strong`, `--mq-on-navy-pill` | white at 16, 18 and 12 %, unchanged | Decorative | Decorative only |
159
+
160
+ 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.
161
+
162
+ **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).
163
+
164
+ 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:
165
+
166
+ - `--mq-success` `#6fbf73`: 8.09:1 on canvas, 7.18:1 on surface.
167
+ - `--mq-warning` `#e0a040`: 8.00:1 on canvas, 7.10:1 on surface.
168
+ - `--mq-danger` `#ff6b6b`: 6.53:1 on canvas, 5.80:1 on surface.
169
+ - `--mq-info` `#64a8f0`: 7.25:1 on canvas, 6.43:1 on surface.
170
+ - `--mq-warning-fill` `#d4850a`, unchanged: a fill, never text.
171
+ - `--mq-warning-tint`, the warning fill at 16 %: ink 11.63:1 on the warning tint.
172
+ - `--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.
173
+
174
+ Chart colours on dark (owner ruling 16): the four series run in reverse order, the lightest first, under the same rules as light ([charts](charts.md)): 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.
175
+
176
+ | Token | Dark value | Contrast |
177
+ | :-- | :-- | :-- |
178
+ | `--mq-chart-series-1` | `#9fb3e8` | 8.71:1 on canvas |
179
+ | `--mq-chart-series-2` | `#6f88c9` | 5.22:1 on canvas |
180
+ | `--mq-chart-series-3` | `#5d6f91` | 3.58:1 on canvas |
181
+ | `--mq-chart-series-4` | `#3a4560` | 1.90:1 on canvas, context bars only |
182
+ | `--mq-chart-rest` | `#232b3d` | 1.28:1 on canvas, with a series 4 outline |
183
+ | `--mq-chart-axis` | `#6b7385` | 3.81:1 on canvas, non-text |
184
+ | `--mq-chart-baseline` | white at 10 % | Decorative |
@@ -4,7 +4,7 @@
4
4
 
5
5
  | Medium | Canvas | Layout |
6
6
  | :-- | :-- | :-- |
7
- | 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) |
7
+ | 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) |
8
8
  | 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 |
9
9
  | 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 |
10
10
 
@@ -53,7 +53,7 @@ Squared-off corners read professional and institutional; pills read consumer.
53
53
 
54
54
  ## Elevation and navy blocks
55
55
 
56
- - 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`. No shadow in print and none on chart marks.
56
+ - 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.
57
57
  - 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.
58
58
 
59
59
  ## Document components
@@ -67,7 +67,7 @@ Squared-off corners read professional and institutional; pills read consumer.
67
67
  | Takeaway | `mq-takeaway` | The one sentence to remember, under a navy tab, Outfit 500 |
68
68
  | Numbered rows | (pattern) | A navy circle with the number, a heading column, a body column: one line per point |
69
69
  | Footer | `mq-foot` | Sources and legal line in tertiary italic, in reports and papers; never inside a slide |
70
- | 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: `<footer class="mq-slide-foot"><p>MedQuantis SA · CH-1095 Lutry · medquantis.ch</p><p class="mq-slide-number">03</p></footer>` |
70
+ | 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>` |
71
71
 
72
72
  ## Tables
73
73