@conduction/docusaurus-preset 3.36.1 → 3.37.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/README.md CHANGED
@@ -354,3 +354,86 @@ If a publish fails mid-run, fix the issue and push another commit — `semantic-
354
354
  ## License
355
355
 
356
356
  EUPL-1.2 + MIT.
357
+
358
+ ### Third-party assets
359
+
360
+ Not every file here is ours. The marks below keep their own terms.
361
+
362
+ **Font Awesome Free 6** — the `mail`, `phone`, `github` and `linkedin`
363
+ glyphs in `<EmployeeCard/>` are Font Awesome Free icons by Fonticons, Inc.,
364
+ used under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
365
+ Source: <https://fontawesome.com>. The paths are pasted into the component
366
+ rather than imported, so this notice is the attribution the licence asks
367
+ for — keep it with the code.
368
+
369
+ Font Awesome's own licence adds, of the brand glyphs among them: *"All
370
+ brand icons are trademarks of their respective owners… Please do not use
371
+ brand logos for any purpose except to represent the company, product, or
372
+ service to which they refer."* Linking to a profile on that service is
373
+ exactly that use.
374
+
375
+ **Bluesky and Mastodon marks** — the `bluesky` and `mastodon` glyphs are
376
+ not Font Awesome redraws but the brands' own artwork, byte-identical to
377
+ the source files, because both brands prohibit redrawing the mark:
378
+
379
+ - Bluesky butterfly — © Bluesky Social PBC, from their
380
+ [brand assets](https://bsky.social/about/support/icons)
381
+ (`bluesky_media_kit_logo_transparent_4.svg`). Used as a social-media icon
382
+ linking to a profile, which their
383
+ [Trademark Policy](https://bsky.social/about/support/trademarks) §4.1
384
+ permits without prior approval.
385
+ - Mastodon mark — trademark of Mastodon GmbH, from their
386
+ [Brand Toolkit](https://joinmastodon.org/branding): `/logos/logo-black.svg`
387
+ and `/logos/logo-white.svg`. Not the `logo-symbol-icon.svg` in the code
388
+ repo — that is the in-app UI icon and a different, simpler drawing.
389
+ Mastodon exports black and white as two separate paths rather than one
390
+ shape in two fills, so both are embedded and the stylesheet shows
391
+ whichever suits the ground. See their
392
+ [Trade Mark Policy](https://joinmastodon.org/trademark).
393
+
394
+ Two of their guidelines we cannot fully meet at this size, noted here so
395
+ nobody rediscovers them: the toolkit asks for 36px of clear space on all
396
+ sides (impossible for a 16px icon in a contact row), and says to use the
397
+ mark alone "only when the Mastodon brand has been clearly established in
398
+ the design". A labelled link to a Mastodon profile is the ordinary
399
+ reading of that, but it is a judgement call, not a written permission the
400
+ way Bluesky s section 4.1 is.
401
+
402
+ **GitHub and LinkedIn marks** — the Font Awesome glyphs for these two are
403
+ renditions of registered trademarks, and both owners publish colour rules
404
+ that bind us:
405
+
406
+ - GitHub — *"The Invertocat and our wordmark should only appear in white,
407
+ black, or in few cases grey or green."* <https://github.com/logos>
408
+ - LinkedIn — *"LinkedIn members may only use the [in] Logo in three color
409
+ variations: blue, black, and white."* Their social-icon clause covers
410
+ this case directly: *"When using the [in] Logo as a part of a line-up of
411
+ social media icons… use one of the three icon colors we provide."*
412
+ <https://brand.linkedin.com/in-logo>
413
+
414
+ ### Why every contact icon is black
415
+
416
+ Four of the six glyphs in `<EmployeeCard/>` are trademarks, and each owner
417
+ restricts the colours their mark may take:
418
+
419
+ | Mark | Colours the owner permits |
420
+ | --- | --- |
421
+ | GitHub | white, black, occasionally grey or green |
422
+ | LinkedIn | blue, black, white |
423
+ | Bluesky | official blue, black, white |
424
+ | Mastodon | the mark unmodified; black or white where contrast requires |
425
+
426
+ **Black is the only colour all four permit.** Cobalt — what this row used
427
+ to be — is permitted by none of them. So the row is black, and white on
428
+ the cobalt hover chip, which is the second variant every one of the four
429
+ brands names for that situation.
430
+
431
+ That settles the four marks. `mail` and `phone` are ordinary Font Awesome
432
+ icons under no such constraint; they are black so the row reads as one
433
+ decision rather than two colours by accident.
434
+
435
+ The fills are literal `#000`/`#fff`, never palette tokens, so a future
436
+ brand change cannot silently recolour someone else's trademark.
437
+ `[data-mark]` in `EmployeeCard.module.css` marks the glyphs where this is
438
+ a legal requirement rather than a design choice — if the row is ever
439
+ restyled, those must not follow. Do not "tidy" any of it into a token.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/docusaurus-preset",
3
- "version": "3.36.1",
3
+ "version": "3.37.0",
4
4
  "scripts": {
5
5
  "prepack": "node scripts/prepack-bundle-css.js",
6
6
  "test": "node --test"
@@ -25,16 +25,106 @@
25
25
  * ]}
26
26
  * />
27
27
  * </TeamGrid>
28
+ *
29
+ * Links.
30
+ * Every contact is one entry in `links`, and the entry decides all
31
+ * three things: `href` is the destination, `icon` picks the glyph
32
+ * from the ICONS map below, and `label` is the accessible name (it
33
+ * is what a screen reader announces, and what renders verbatim if
34
+ * the icon name is unknown). There is no separate email or phone
35
+ * prop — an address is a `mailto:` href with icon 'mail', a number
36
+ * is a `tel:` href with icon 'phone'. Adding a new network means
37
+ * adding one entry to ICONS; nothing else in the component knows
38
+ * which networks exist.
39
+ *
40
+ * Supported icons and the href each expects:
41
+ * mail mailto:naam@conduction.nl
42
+ * phone tel:+31612345678 (E.164, no spaces)
43
+ * github https://github.com/<user>
44
+ * linkedin https://www.linkedin.com/in/<slug>/
45
+ * bluesky https://bsky.app/profile/<handle>
46
+ * mastodon https://<instance>/@<handle>
47
+ *
48
+ * An http(s) href opens in a new tab, the same rule FeatureGrid,
49
+ * ContactCta, ExternalAppShelf, Footer and Navbar already follow: a
50
+ * contact link leaves the site for someone's profile, and sending a
51
+ * reader away mid-page loses where they were. `mailto:` and `tel:`
52
+ * are excluded — they hand off to another application, so a _blank
53
+ * on them opens a tab that never paints and stays behind empty.
28
54
  */
29
55
 
30
56
  import React from 'react';
31
57
  import styles from './EmployeeCard.module.css';
32
58
 
59
+ /* `rel` travels with every `target="_blank"`: without it the opened page
60
+ receives a `window.opener` handle back into ours. Modern browsers imply
61
+ `noopener` for _blank, but the preset states it rather than relying on
62
+ that, and `noreferrer` additionally withholds the Referer header. */
63
+ const externalProps = (href = '') =>
64
+ /^https?:\/\//.test(href) ? {target: '_blank', rel: 'noopener noreferrer'} : {};
65
+
33
66
  const ICONS = {
34
- mail: <svg viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>,
35
- github: <svg viewBox="0 0 24 24"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>,
36
- linkedin: <svg viewBox="0 0 24 24"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-4 0v7h-4v-7a6 6 0 0 1 6-6z"/><rect x="2" y="9" width="4" height="12"/><circle cx="4" cy="4" r="2"/></svg>,
37
- phone: <svg viewBox="0 0 24 24"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z"/></svg>,
67
+ /* Font Awesome Free 6, the SVG set CC BY 4.0, attributed in the
68
+ preset README. Each is a single solid path at Font Awesome's own
69
+ viewBox, pasted rather than imported: six glyphs do not justify a
70
+ runtime dependency on the icon packages for every site that
71
+ installs this preset. If the set ever grows past a dozen, swap to
72
+ @fortawesome/free-solid-svg-icons and free-brands-svg-icons.
73
+
74
+ Sourced from FortAwesome/Font-Awesome 6.x:
75
+ mail svgs/solid/envelope.svg
76
+ phone svgs/solid/phone.svg
77
+ github svgs/brands/github.svg
78
+ linkedin svgs/brands/linkedin.svg */
79
+ mail: <svg viewBox="0 0 512 512"><path d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48L48 64zM0 176L0 384c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-208L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"/></svg>,
80
+ github: <svg viewBox="0 0 496 512" data-mark="true"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>,
81
+ linkedin: <svg viewBox="0 0 448 512" data-mark="true"><path d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"/></svg>,
82
+ phone: <svg viewBox="0 0 512 512"><path d="M164.9 24.6c-7.7-18.6-28-28.5-47.4-23.2l-88 24C12.1 30.2 0 46 0 64C0 311.4 200.6 512 448 512c18 0 33.8-12.1 38.6-29.5l24-88c5.3-19.4-4.6-39.7-23.2-47.4l-96-40c-16.3-6.8-35.2-2.1-46.3 11.6L304.7 368C234.3 334.7 177.3 277.7 144 207.3L193.3 167c13.7-11.2 18.4-30 11.6-46.3l-40-96z"/></svg>,
83
+ /* The two fediverse marks are trademarks, not icons we may draw, so
84
+ they are the official artwork at its original geometry rather than
85
+ a redraw normalised to the 24x24 box the other four use.
86
+
87
+ bluesky bsky.social/about/brand-assets/butterfly/
88
+ bluesky_media_kit_logo_transparent_4.svg (568x501)
89
+ mastodon joinmastodon.org/logos/logo-black.svg and
90
+ logo-white.svg (74x79) — the brand page, not the
91
+ in-app logo-symbol-icon.svg in the code repo, which
92
+ is a different and simpler drawing
93
+
94
+ Every glyph in this map is a solid path now, so filling rather
95
+ than stroking is the default; what these two carry instead is
96
+ data-mark, the do-not-recolour lock.
97
+
98
+ Four of the six glyphs carry data-mark, in two tiers. Bluesky
99
+ permits the butterfly as a profile link with no permission needed
100
+ (Trademark Policy 4.1) but forbids recolouring outside the black
101
+ and white variants; Mastodon's policy says not to change or modify
102
+ the marks at all. GitHub allows white, black, and in few cases grey
103
+ or green; LinkedIn allows three colour options only. Black and
104
+ white are what all four permit, so all four render black on the
105
+ light chip and white on the cobalt hover chip instead of inheriting
106
+ currentColor.
107
+
108
+ mail and phone stay off the lock: they are Font Awesome utility
109
+ glyphs under CC BY 4.0, nobody's trademark, and free to take the
110
+ brand colour if the row is ever restyled.
111
+
112
+ Mastodon ships black and white as two separately drawn files —
113
+ the coordinates differ, they are not one path with two fills the
114
+ way Bluesky's are. So both are embedded and the stylesheet shows
115
+ whichever suits the ground, rather than filling one of them the
116
+ other colour. At 16px nobody could tell; the point is that each
117
+ variant is their file, used in the situation they drew it for.
118
+
119
+ Keep the paths byte-identical to the source files. Rescaling or
120
+ re-tracing them is the thing both policies prohibit. */
121
+ bluesky: <svg viewBox="0 0 568 501" data-mark="true"><path d="M123.121 33.6637C188.241 82.5526 258.281 181.681 284 234.873C309.719 181.681 379.759 82.5526 444.879 33.6637C491.866 -1.61183 568 -28.9064 568 57.9464C568 75.2916 558.055 203.659 552.222 224.501C531.947 296.954 458.067 315.434 392.347 304.249C507.222 323.8 536.444 388.56 473.333 453.32C353.473 576.312 301.061 422.461 287.631 383.039C285.169 375.812 284.017 372.431 284 375.306C283.983 372.431 282.831 375.812 280.369 383.039C266.939 422.461 214.527 576.312 94.6667 453.32C31.5556 388.56 60.7778 323.8 175.653 304.249C109.933 315.434 36.0535 296.954 15.7778 224.501C9.94525 203.659 0 75.2916 0 57.9464C0 -28.9064 76.1345 -1.61183 123.121 33.6637Z"/></svg>,
122
+ mastodon: (
123
+ <svg viewBox="0 0 74 79" data-mark="true">
124
+ <path className={styles.markOnLight} d="M73.7014 17.4323C72.5616 9.05152 65.1774 2.4469 56.424 1.1671C54.9472 0.950843 49.3518 0.163818 36.3901 0.163818H36.2933C23.3281 0.163818 20.5465 0.950843 19.0697 1.1671C10.56 2.41145 2.78877 8.34604 0.903306 16.826C-0.00357854 21.0022 -0.100361 25.6322 0.068112 29.8793C0.308275 35.9699 0.354874 42.0498 0.91406 48.1156C1.30064 52.1448 1.97502 56.1419 2.93215 60.0769C4.72441 67.3445 11.9795 73.3925 19.0876 75.86C26.6979 78.4332 34.8821 78.8603 42.724 77.0937C43.5866 76.8952 44.4398 76.6647 45.2833 76.4024C47.1867 75.8033 49.4199 75.1332 51.0616 73.9562C51.0841 73.9397 51.1026 73.9184 51.1156 73.8938C51.1286 73.8693 51.1359 73.8421 51.1368 73.8144V67.9366C51.1364 67.9107 51.1302 67.8852 51.1186 67.862C51.1069 67.8388 51.0902 67.8184 51.0695 67.8025C51.0489 67.7865 51.0249 67.7753 50.9994 67.7696C50.9738 67.764 50.9473 67.7641 50.9218 67.7699C45.8976 68.9569 40.7491 69.5519 35.5836 69.5425C26.694 69.5425 24.3031 65.3699 23.6184 63.6327C23.0681 62.1314 22.7186 60.5654 22.5789 58.9744C22.5775 58.9477 22.5825 58.921 22.5934 58.8965C22.6043 58.8721 22.621 58.8505 22.6419 58.8336C22.6629 58.8167 22.6876 58.8049 22.714 58.7992C22.7404 58.7934 22.7678 58.794 22.794 58.8007C27.7345 59.9796 32.799 60.5746 37.8813 60.5733C39.1036 60.5733 40.3223 60.5733 41.5447 60.5414C46.6562 60.3996 52.0437 60.1408 57.0728 59.1694C57.1983 59.1446 57.3237 59.1233 57.4313 59.0914C65.3638 57.5847 72.9128 52.8555 73.6799 40.8799C73.7086 40.4084 73.7803 35.9415 73.7803 35.4523C73.7839 33.7896 74.3216 23.6576 73.7014 17.4323ZM61.4925 47.3144H53.1514V27.107C53.1514 22.8528 51.3591 20.6832 47.7136 20.6832C43.7061 20.6832 41.6988 23.2499 41.6988 28.3194V39.3803H33.4078V28.3194C33.4078 23.2499 31.3969 20.6832 27.3894 20.6832C23.7654 20.6832 21.9552 22.8528 21.9516 27.107V47.3144H13.6176V26.4937C13.6176 22.2395 14.7157 18.8598 16.9118 16.3545C19.1772 13.8552 22.1488 12.5719 25.8373 12.5719C30.1064 12.5719 33.3325 14.1955 35.4832 17.4394L37.5587 20.8853L39.6377 17.4394C41.7884 14.1955 45.0145 12.5719 49.2765 12.5719C52.9614 12.5719 55.9329 13.8552 58.2055 16.3545C60.4017 18.8574 61.4997 22.2371 61.4997 26.4937L61.4925 47.3144Z"/>
125
+ <path className={styles.markOnDark} d="M73.7014 17.9592C72.5616 9.62034 65.1774 3.04876 56.424 1.77536C54.9472 1.56019 49.3517 0.7771 36.3901 0.7771H36.2933C23.3281 0.7771 20.5465 1.56019 19.0697 1.77536C10.56 3.01348 2.78877 8.91838 0.903306 17.356C-0.00357857 21.5113 -0.100361 26.1181 0.068112 30.3439C0.308275 36.404 0.354874 42.4535 0.91406 48.489C1.30064 52.498 1.97502 56.4751 2.93215 60.3905C4.72441 67.6217 11.9795 73.6395 19.0876 76.0945C26.6979 78.6548 34.8821 79.0799 42.724 77.3221C43.5866 77.1245 44.4398 76.8953 45.2833 76.6342C47.1867 76.0381 49.4199 75.3714 51.0616 74.2003C51.0841 74.1839 51.1026 74.1627 51.1156 74.1382C51.1286 74.1138 51.1359 74.0868 51.1368 74.0592V68.2108C51.1364 68.185 51.1302 68.1596 51.1185 68.1365C51.1069 68.1134 51.0902 68.0932 51.0695 68.0773C51.0489 68.0614 51.0249 68.0503 50.9994 68.0447C50.9738 68.0391 50.9473 68.0392 50.9218 68.045C45.8976 69.226 40.7491 69.818 35.5836 69.8087C26.694 69.8087 24.3031 65.6569 23.6184 63.9285C23.0681 62.4347 22.7186 60.8764 22.5789 59.2934C22.5775 59.2669 22.5825 59.2403 22.5934 59.216C22.6043 59.1916 22.621 59.1702 22.6419 59.1533C22.6629 59.1365 22.6876 59.1248 22.714 59.1191C22.7404 59.1134 22.7678 59.1139 22.794 59.1206C27.7345 60.2936 32.799 60.8856 37.8813 60.8843C39.1036 60.8843 40.3223 60.8843 41.5447 60.8526C46.6562 60.7115 52.0437 60.454 57.0728 59.4874C57.1983 59.4628 57.3237 59.4416 57.4313 59.4098C65.3638 57.9107 72.9128 53.2051 73.6799 41.2895C73.7086 40.8204 73.7803 36.3758 73.7803 35.889C73.7839 34.2347 74.3216 24.1533 73.7014 17.9592ZM61.4925 47.6918H53.1514V27.5855C53.1514 23.3526 51.3591 21.1938 47.7136 21.1938C43.7061 21.1938 41.6988 23.7476 41.6988 28.7919V39.7974H33.4078V28.7919C33.4078 23.7476 31.3969 21.1938 27.3894 21.1938C23.7654 21.1938 21.9552 23.3526 21.9516 27.5855V47.6918H13.6176V26.9752C13.6176 22.7423 14.7157 19.3795 16.9118 16.8868C19.1772 14.4 22.1488 13.1231 25.8373 13.1231C30.1064 13.1231 33.3325 14.7386 35.4832 17.9662L37.5587 21.3949L39.6377 17.9662C41.7884 14.7386 45.0145 13.1231 49.2765 13.1231C52.9614 13.1231 55.9329 14.4 58.2055 16.8868C60.4017 19.3772 61.4997 22.74 61.4997 26.9752L61.4925 47.6918Z"/>
126
+ </svg>
127
+ ),
38
128
  };
39
129
 
40
130
  export function TeamGrid({columns = 3, children, className}) {
@@ -66,7 +156,7 @@ export default function EmployeeCard({
66
156
  {links.length > 0 && (
67
157
  <div className={styles.contacts}>
68
158
  {links.map((l, i) => (
69
- <a key={i} href={l.href} aria-label={l.label}>{ICONS[l.icon] || l.label}</a>
159
+ <a key={i} href={l.href} aria-label={l.label} {...externalProps(l.href)}>{ICONS[l.icon] || l.label}</a>
70
160
  ))}
71
161
  </div>
72
162
  )}
@@ -96,7 +186,7 @@ export default function EmployeeCard({
96
186
  {links.length > 0 && (
97
187
  <div className={styles.contactsInline}>
98
188
  {links.map((l, i) => (
99
- <a key={i} href={l.href}>{ICONS[l.icon]}{l.label}</a>
189
+ <a key={i} href={l.href} {...externalProps(l.href)}>{ICONS[l.icon]}{l.label}</a>
100
190
  ))}
101
191
  </div>
102
192
  )}
@@ -107,7 +197,11 @@ export default function EmployeeCard({
107
197
  /* default: compact */
108
198
  const Tag = links.length > 0 && links[0].href ? 'a' : 'div';
109
199
  return (
110
- <Tag href={Tag === 'a' ? links[0].href : undefined} className={[styles.cardCompact, className].filter(Boolean).join(' ')}>
200
+ <Tag
201
+ href={Tag === 'a' ? links[0].href : undefined}
202
+ {...(Tag === 'a' ? externalProps(links[0].href) : {})}
203
+ className={[styles.cardCompact, className].filter(Boolean).join(' ')}
204
+ >
111
205
  <div className={styles.avatar} style={!photo ? {background: avatarColor || 'var(--c-blue-cobalt)'} : undefined}>
112
206
  {photo ? <img src={photo} alt={name} /> : initials}
113
207
  </div>
@@ -118,7 +212,7 @@ export default function EmployeeCard({
118
212
  {links.length > 0 && (
119
213
  <div className={styles.linksRow}>
120
214
  {links.map((l, i) => (
121
- <a key={i} href={l.href} aria-label={l.label}>{ICONS[l.icon]}</a>
215
+ <a key={i} href={l.href} aria-label={l.label} {...externalProps(l.href)}>{ICONS[l.icon]}</a>
122
216
  ))}
123
217
  </div>
124
218
  )}
@@ -60,7 +60,7 @@
60
60
  transition: all 140ms ease;
61
61
  }
62
62
  .linksRow a:hover { background: var(--c-blue-cobalt); color: white; }
63
- .linksRow svg { width: 13px; height: 13px; stroke: currentColor; strokeWidth: 2; fill: none; }
63
+ .linksRow svg { width: 13px; height: 13px; fill: #000000; stroke: none; }
64
64
 
65
65
  /* Photo variant */
66
66
  .cardPhoto {
@@ -90,7 +90,7 @@
90
90
  transition: all 140ms ease;
91
91
  }
92
92
  .contacts a:hover { background: var(--c-blue-cobalt); color: white; }
93
- .contacts svg { width: 16px; height: 16px; stroke: currentColor; stroke-width: 2; fill: none; }
93
+ .contacts svg { width: 16px; height: 16px; fill: #000000; stroke: none; }
94
94
 
95
95
  /* Detail variant */
96
96
  .cardDetail {
@@ -141,4 +141,74 @@
141
141
  text-decoration: none;
142
142
  }
143
143
  .contactsInline a:hover { color: var(--c-blue-cobalt); }
144
- .contactsInline svg { width: 14px; height: 14px; stroke: currentColor; stroke-width: 2; fill: none; }
144
+ .contactsInline svg { width: 14px; height: 14px; fill: #000000; stroke: none; }
145
+
146
+ /* ============================================================
147
+ Why every contact icon is black
148
+
149
+ Four of the six glyphs in this row are registered trademarks, and
150
+ every one of those four owners restricts the colours their mark may
151
+ take. Checked against each brand's own published guidance:
152
+
153
+ GitHub "The Invertocat and our wordmark should only appear in
154
+ white, black, or in few cases grey or green."
155
+ github.com/logos
156
+
157
+ LinkedIn "LinkedIn members may only use the [in] Logo in three
158
+ color variations: blue, black, and white." And, for this
159
+ exact case: "When using the [in] Logo as a part of a
160
+ line-up of social media icons... use one of the three
161
+ icon colors we provide." brand.linkedin.com/in-logo
162
+
163
+ Bluesky "Recolor the logo (other than the approved black and
164
+ white variants)" is listed under Don't.
165
+ bsky.social/about/support/branding
166
+
167
+ Mastodon "Do not change or modify the Mastodon marks."
168
+ joinmastodon.org/trademark
169
+
170
+ Black is the single colour all four permit. Cobalt is permitted by
171
+ none of them, and cobalt is what this row used to be.
172
+
173
+ That decides the four marks. mail and phone are plain Font Awesome
174
+ icons under no such constraint — they are black because a contact
175
+ row in two colours reads as a mistake rather than a decision, and
176
+ because one rule for the row is one fewer thing to get wrong the
177
+ next time somebody adds a network.
178
+
179
+ White on hover is not an exception: the chip turns cobalt
180
+ underneath, and white is the second variant all four brands name
181
+ for precisely that situation.
182
+
183
+ Literal #000/#fff on purpose. A palette token here would let a
184
+ future brand change silently recolour someone else's trademark,
185
+ which is the thing all four policies forbid.
186
+ ============================================================ */
187
+ .linksRow a:hover svg,
188
+ .contacts a:hover svg {
189
+ fill: #FFFFFF;
190
+ }
191
+
192
+ /* Mastodon publishes black and white as two separate drawings rather
193
+ than one shape in two fills, so both live in the SVG and we show the
194
+ one that suits the ground instead of recolouring either. Bluesky
195
+ needs none of this: its variants are one path, so a fill swap gives
196
+ the official black and white exactly. */
197
+ .markOnDark { display: none; }
198
+ .linksRow a:hover .markOnLight,
199
+ .contacts a:hover .markOnLight { display: none; }
200
+ .linksRow a:hover .markOnDark,
201
+ .contacts a:hover .markOnDark { display: inline; }
202
+ /* data-mark singles out the glyphs where the rule above is a legal
203
+ requirement and not a design choice, so a later decision to recolour
204
+ the row cannot quietly sweep up a trademark. Same values as the
205
+ default today; the point is that they hold when that changes. */
206
+ .linksRow svg[data-mark],
207
+ .contacts svg[data-mark],
208
+ .contactsInline svg[data-mark] {
209
+ fill: #000000;
210
+ }
211
+ .linksRow a:hover svg[data-mark],
212
+ .contacts a:hover svg[data-mark] {
213
+ fill: #FFFFFF;
214
+ }
package/src/css/brand.css CHANGED
@@ -23,15 +23,48 @@
23
23
  --ifm-color-primary-lighter: var(--c-cobalt-300);
24
24
  --ifm-color-primary-lightest: var(--c-cobalt-200);
25
25
 
26
- /* Accent KNVB orange (links, CTAs, focus ring) */
27
- --ifm-link-color: var(--c-orange-knvb);
28
- --ifm-link-hover-color: var(--c-cobalt-700);
26
+ /* Links follow the published rule: cobalt by default, orange on
27
+ hover. The inverse shipped for a long time, which put the accent
28
+ on the most repeated element of a documentation site — far past
29
+ the ~8% of page area the brand budgets for orange — and carried
30
+ every body link at 3.01:1, under the 4.5:1 WCAG asks of text.
31
+ Cobalt links measure 9.08:1. Hover takes the text-safe orange
32
+ (4.67:1) rather than KNVB, because hover is still a text role.
33
+ Both read from the tokens, so the rule lives in one place. */
34
+ --ifm-link-color: var(--conduction-color-link-default);
35
+ --ifm-link-hover-color: var(--conduction-color-link-hover);
29
36
  --ifm-button-color: white;
30
37
 
31
38
  /* Typography → Figtree body, Plex Mono code */
32
39
  --ifm-font-family-base: var(--conduction-typography-font-family-body);
33
40
  --ifm-font-family-monospace: var(--conduction-typography-font-family-code);
34
- --ifm-heading-color: var(--c-cobalt-900);
41
+
42
+ /* Headings are brand cobalt, not near-black. The site read as
43
+ black-on-white with a blue navbar; the token says the heading
44
+ colour is the brand colour. 9.08:1 on white, so nothing is lost
45
+ on contrast. */
46
+ --ifm-heading-color: var(--conduction-color-text-default);
47
+
48
+ /* The brand heading ramp. Infima ships 32/24/20/16/14/13.6, which
49
+ collapses h4 to body size and h5 below it; the published ramp is
50
+ 48/40/32/24/20/18 and it is the hierarchy every other Conduction
51
+ surface uses. Sizes come off the --fs-* scale so the ramp has one
52
+ source. */
53
+ --ifm-h1-font-size: var(--fs-5xl); /* 48px */
54
+ --ifm-h2-font-size: var(--fs-4xl); /* 40px */
55
+ --ifm-h3-font-size: var(--fs-3xl); /* 32px */
56
+ --ifm-h4-font-size: var(--fs-2xl); /* 24px */
57
+ --ifm-h5-font-size: var(--fs-xl); /* 20px */
58
+ --ifm-h6-font-size: var(--fs-lg); /* 18px */
59
+
60
+ /* 700 for h1/h2, 600 from h3 down (the rule below). Every heading
61
+ bold flattens the step between a section and its subsection. */
62
+ --ifm-heading-font-weight: var(--fw-bold);
63
+
64
+ /* The radius token, actually read. Infima defaults to 0.4rem
65
+ (6.4px) where the brand publishes 6px. Invisible, but it means
66
+ the token was present and unused. */
67
+ --ifm-global-radius: var(--radius-md);
35
68
 
36
69
  /* Code blocks */
37
70
  --ifm-code-font-size: 92%;
@@ -50,21 +83,38 @@
50
83
  --ifm-footer-link-color: white;
51
84
  --ifm-footer-title-color: var(--c-cobalt-200);
52
85
 
86
+ /* Status → the brand set, replacing Infima's stock #fa383e /
87
+ #00a400 / #ffba00. All three framework defaults fail as text on
88
+ white (3.68, 3.33 and 1.71:1) and none of them has any relation
89
+ to this palette — the direct cost of the design system having
90
+ published one status colour and no alert treatment. The alert
91
+ rules further down pair each base with its ink and tint. */
92
+ --ifm-color-info: var(--conduction-color-status-info);
93
+ --ifm-color-success: var(--conduction-color-status-success);
94
+ --ifm-color-warning: var(--conduction-color-status-warning);
95
+ --ifm-color-danger: var(--conduction-color-status-error);
96
+
53
97
  /* Spacing rhythm reused from tokens */
54
98
  --ifm-spacing-horizontal: var(--space-6);
55
99
  --ifm-spacing-vertical: var(--space-6);
56
- --ifm-line-height-base: 1.55;
100
+ --ifm-line-height-base: var(--lh-normal);
57
101
  }
58
102
 
103
+ /* Dark mode now comes from the tokens — tokens.css publishes the
104
+ whole semantic layer under [data-theme="dark"], so these are just
105
+ the Infima names pointed at it. Links go back to the bright KNVB
106
+ orange here (5.93:1 on cobalt-900): the text-safe orange exists
107
+ for white grounds and is the one that fails on a dark one. */
59
108
  [data-theme='dark'] {
60
- --ifm-background-color: var(--c-cobalt-900);
61
- --ifm-background-surface-color: var(--c-cobalt-800);
62
- --ifm-color-primary: var(--c-cobalt-300);
63
- --ifm-color-primary-light: var(--c-cobalt-200);
64
- --ifm-link-color: var(--c-orange-knvb);
65
- --ifm-heading-color: white;
66
- --ifm-font-color-base: var(--c-cobalt-100);
67
- --ifm-code-background: var(--c-cobalt-800);
109
+ --ifm-background-color: var(--conduction-color-background-default);
110
+ --ifm-background-surface-color: var(--conduction-color-background-surface);
111
+ --ifm-color-primary: var(--conduction-color-primary);
112
+ --ifm-color-primary-light: var(--conduction-color-primary-light);
113
+ --ifm-link-color: var(--conduction-color-link-default);
114
+ --ifm-link-hover-color: var(--conduction-color-link-hover);
115
+ --ifm-heading-color: var(--conduction-color-text-default);
116
+ --ifm-font-color-base: var(--conduction-color-text-default);
117
+ --ifm-code-background: var(--conduction-color-code-inline-background);
68
118
  }
69
119
 
70
120
  /* ============================================================
@@ -110,8 +160,16 @@
110
160
  outline-offset: 2px;
111
161
  }
112
162
 
113
- /* Headings keep the brand letter-spacing */
114
- h1, h2, h3 { letter-spacing: -0.02em; }
163
+ /* Letter-spacing per level, not one value for the top three. Large
164
+ Figtree sits loose without it; clearly visible at 48px, invisible
165
+ below about 24px, which is why h4-h6 get none. */
166
+ h1 { letter-spacing: var(--tracking-tight); }
167
+ h2 { letter-spacing: var(--tracking-snug); }
168
+ h3 { letter-spacing: var(--tracking-normal); }
169
+
170
+ /* The weight step. --ifm-heading-font-weight carries 700 for h1/h2;
171
+ from h3 down the brand drops to 600 so a subsection reads as one. */
172
+ h3, h4, h5, h6 { font-weight: var(--fw-semibold); }
115
173
 
116
174
  /* Marketing pages (frontmatter: hide_table_of_contents: true) get
117
175
  a flush top edge AND full-width sections. The MDXPage swizzle
@@ -157,6 +215,51 @@ a:not(.navbar__link):not(.footer__link-item):not(.button):hover {
157
215
  text-decoration-color: var(--c-orange-knvb);
158
216
  }
159
217
 
218
+ /* ============================================================
219
+ Alerts / admonitions
220
+
221
+ Each status is three values, not one: the base draws the rule and
222
+ the icon, the ink carries the text, the tint is the ground. Every
223
+ ink-on-tint pair below clears AAA, so the set survives a later
224
+ change to the tints.
225
+
226
+ Warning is the exception to "the base draws the rule": #F36C21 on
227
+ its own tint measures 2.76:1, under the 3:1 a component boundary
228
+ needs, so a warning border takes the ink instead.
229
+
230
+ Colour is never the only signal — Docusaurus renders an icon and a
231
+ label in every admonition, and that is what keeps this readable for
232
+ anyone who cannot separate the success green from the warning
233
+ orange (they sit 1.68:1 apart in luminance).
234
+ ============================================================ */
235
+ .alert--info {
236
+ --ifm-alert-background-color: var(--conduction-color-status-info-tint);
237
+ --ifm-alert-foreground-color: var(--conduction-color-status-info-ink);
238
+ --ifm-alert-border-color: var(--conduction-color-status-info);
239
+ }
240
+ .alert--success {
241
+ --ifm-alert-background-color: var(--conduction-color-status-success-tint);
242
+ --ifm-alert-foreground-color: var(--conduction-color-status-success-ink);
243
+ --ifm-alert-border-color: var(--conduction-color-status-success);
244
+ }
245
+ .alert--warning {
246
+ --ifm-alert-background-color: var(--conduction-color-status-warning-tint);
247
+ --ifm-alert-foreground-color: var(--conduction-color-status-warning-ink);
248
+ /* ink, not base — see the note above */
249
+ --ifm-alert-border-color: var(--conduction-color-status-warning-ink);
250
+ }
251
+ .alert--danger {
252
+ --ifm-alert-background-color: var(--conduction-color-status-error-tint);
253
+ --ifm-alert-foreground-color: var(--conduction-color-status-error-ink);
254
+ --ifm-alert-border-color: var(--conduction-color-status-error);
255
+ }
256
+
257
+ /* Blockquote — a cobalt rule and brand ink, from the tokens. */
258
+ blockquote {
259
+ border-left: var(--conduction-quote-rule-width) solid var(--conduction-color-quote-border);
260
+ color: var(--conduction-color-text-default);
261
+ }
262
+
160
263
  /* =========================================================================
161
264
  Docs-page styling — mirror preview/product-pages/_docs-shell.css
162
265
 
@@ -26,6 +26,16 @@
26
26
  --c-nextcloud-cyan: #1CAFFF; /* Nextcloud lighter cyan */
27
27
  --c-commonground-yellow: #F6AD00; /* Common Ground official, commonground.nl */
28
28
 
29
+ /* Vermillion tint + ink — the error pair. Derived, because the brand
30
+ publishes the base red only. */
31
+ --c-red-50: #FCE8EA; /* error tint — alert ground */
32
+ --c-red-950: #421115; /* error tint, dark mode. Luminance-matched
33
+ to cobalt-800 so the four alert grounds
34
+ read as one set; red-300 on it is 6.59:1 */
35
+ --c-red-300: #EF8A92; /* light vermillion — error text in dark
36
+ mode, 6.51:1 on cobalt-800 */
37
+ --c-red-700: #8D1720; /* error ink — 7.85:1 on red-50 */
38
+
29
39
  /* Cobalt tints — derived for surfaces, hex-prism faces, soft fills */
30
40
  --c-cobalt-50: #EEF2F8;
31
41
  --c-cobalt-100: #DCE3F0;
@@ -68,8 +78,11 @@
68
78
  /* — Component family 3: forest (data / trustworthy / NLDS-compliant)
69
79
  Distinct from mint — deeper, denser, more "official." Use this
70
80
  for hexes that handle data and registers. — */
81
+ --c-forest-50: #EEF7EE; /* success tint — alert ground */
82
+ --c-forest-950: #0B280A; /* success tint, dark mode — 5.98:1 with forest-300 */
71
83
  --c-forest-300: #7DAA7C;
72
- --c-forest-500: #3D7C3A;
84
+ --c-forest-500: #3D7C3A; /* = status success base */
85
+ --c-forest-700: #2C5A2A; /* success ink — 7.37:1 on forest-50 */
73
86
 
74
87
  /* — Component family 4: terracotta (documents / human work)
75
88
  Tuned around the brand vermillion (#AE1C28), so it ties to the
@@ -85,8 +98,15 @@
85
98
  Kept in tokens because they have specific jobs elsewhere. */
86
99
 
87
100
  /* Coral = KNVB orange accent only — focus rings, hover, occasional highlight. */
101
+ --c-coral-50: #FFF3EA; /* warning tint — alert ground */
102
+ --c-coral-950: #351B0D; /* warning tint, dark mode — 9.07:1 with coral-300 */
88
103
  --c-coral-300: #FAB29C;
89
104
  --c-coral-500: #F36C21; /* = KNVB */
105
+ --c-coral-600: #C64E0B; /* text-safe orange — 4.67:1 on white.
106
+ Same hue as KNVB, dark enough to carry
107
+ 16px text. Use for text and link-hover;
108
+ KNVB stays the fill / focus / icon accent. */
109
+ --c-coral-700: #833307; /* warning ink — 7.89:1 on coral-50 */
90
110
 
91
111
  /* Gold = Conduction Certified mark ONLY. Reserved for the cert avatar
92
112
  and trustmark badges. Do not use as a generic family color. */
@@ -109,28 +129,105 @@
109
129
  --conduction-color-brand-nextcloud: var(--c-nextcloud-blue);
110
130
  --conduction-color-brand-commonground: var(--c-commonground-yellow);
111
131
 
132
+ /* Primary ramp — the seven steps a consumer needs to build a UI on
133
+ cobalt: hover fills, pressed states, borders, muted chrome. The
134
+ primitives already carry the values; naming them semantically
135
+ stops every downstream product from picking its own. */
136
+ --conduction-color-primary-lightest: var(--c-cobalt-200);
137
+ --conduction-color-primary-lighter: var(--c-cobalt-300);
138
+ --conduction-color-primary-light: var(--c-cobalt-400);
139
+ --conduction-color-primary: var(--c-cobalt-500);
140
+ --conduction-color-primary-dark: var(--c-cobalt-600);
141
+ --conduction-color-primary-darker: var(--c-cobalt-700);
142
+ --conduction-color-primary-darkest: var(--c-cobalt-800);
143
+
112
144
  /* Background */
113
145
  --conduction-color-background-default: var(--c-white);
114
146
  --conduction-color-background-inverse: var(--c-blue-cobalt);
115
147
  --conduction-color-background-muted: var(--c-cobalt-50);
148
+ /* The deep ground every Conduction site actually paints for dark
149
+ panels, footers and dark mode. Distinct from -inverse, which is
150
+ the cobalt hero panel. Both are real, so name them separately
151
+ rather than leave anything downstream guessing which one
152
+ "inverse" was meant to be. */
153
+ --conduction-color-background-dark: var(--c-cobalt-900);
154
+ --conduction-color-background-surface: var(--c-cobalt-800);
116
155
 
117
156
  /* Text */
118
157
  --conduction-color-text-default: var(--c-blue-cobalt);
119
158
  --conduction-color-text-inverse: var(--c-white);
120
- --conduction-color-text-accent: var(--c-orange-knvb);
159
+ --conduction-color-text-accent: var(--c-coral-600);
121
160
  --conduction-color-text-muted: var(--c-cobalt-400);
122
161
 
123
162
  /* Link */
124
163
  --conduction-color-link-default: var(--c-blue-cobalt);
125
- --conduction-color-link-hover: var(--c-orange-knvb);
126
-
127
- /* Status */
128
- --conduction-color-status-error: var(--c-red-vermillion);
129
-
130
- /* Border */
131
- --conduction-color-border-default: var(--c-cobalt-200);
164
+ --conduction-color-link-hover: var(--c-coral-600);
165
+
166
+ /* Status — four states, three values each.
167
+ base = icon, mark, border · ink = text on the tint · tint = ground.
168
+ Publishing only the base is what left every downstream product
169
+ inventing the other two. Every ink-on-tint pair below clears AAA.
170
+
171
+ Success is forest, not mint: forest carries text on white at
172
+ 5.07:1 where mint reaches only 3.62:1, and it sits further from
173
+ the warning orange. Warning is the KNVB orange, the only
174
+ candidate that clears 3:1 on white — so orange now means brand
175
+ accent, focus ring and warning at once.
176
+
177
+ Two rules travel with this set:
178
+ 1. Never signal a status by colour alone — always an icon or a
179
+ word beside it (WCAG 1.4.1). Success and warning sit 1.68:1
180
+ apart in luminance, the classic red-green confusion pair.
181
+ 2. Warning is the exception to "the base draws the border":
182
+ #F36C21 on its own tint measures 2.76:1, under the 3:1 a
183
+ component boundary needs, so warning borders take the ink. */
184
+ --conduction-color-status-info: var(--c-blue-cobalt);
185
+ --conduction-color-status-info-ink: var(--c-cobalt-600);
186
+ --conduction-color-status-info-tint: var(--c-cobalt-50);
187
+
188
+ --conduction-color-status-success: var(--c-forest-500);
189
+ --conduction-color-status-success-ink: var(--c-forest-700);
190
+ --conduction-color-status-success-tint: var(--c-forest-50);
191
+
192
+ --conduction-color-status-warning: var(--c-orange-knvb);
193
+ --conduction-color-status-warning-ink: var(--c-coral-700);
194
+ --conduction-color-status-warning-tint: var(--c-coral-50);
195
+
196
+ --conduction-color-status-error: var(--c-red-vermillion);
197
+ --conduction-color-status-error-ink: var(--c-red-700);
198
+ --conduction-color-status-error-tint: var(--c-red-50);
199
+
200
+ /* Border
201
+ -default is cobalt-300, the lightest step that clears the 3:1
202
+ WCAG 1.4.11 asks of anything outlining a control. cobalt-200
203
+ (1.79:1) is still the right choice for decorative rules between
204
+ cards and table rows, where no minimum applies — reach for
205
+ --c-cobalt-200 directly there. */
206
+ --conduction-color-border-default: var(--c-cobalt-300);
132
207
  --conduction-color-border-strong: var(--c-blue-cobalt);
133
208
 
209
+ /* Footer — the dark panel conduction.nl has run in production for
210
+ years with no published counterpart. */
211
+ --conduction-color-footer-background: var(--c-cobalt-900);
212
+ --conduction-color-footer-text: var(--c-cobalt-200);
213
+ --conduction-color-footer-title: var(--c-cobalt-200);
214
+ --conduction-color-footer-link: var(--c-white);
215
+
216
+ /* Code — inline sits on a cobalt tint, blocks on the dark ground. */
217
+ --conduction-color-code-inline-background: var(--c-cobalt-50);
218
+ --conduction-color-code-inline-text: var(--c-blue-cobalt);
219
+ --conduction-color-code-block-background: var(--c-cobalt-900);
220
+ --conduction-color-code-block-text: var(--c-cobalt-100);
221
+
222
+ /* Alert, quote, table of contents — defined downstream twice over
223
+ (once by conduction.nl, once from the Utrecht side) and the two
224
+ sets disagreed. These are the conduction.nl values. */
225
+ --conduction-color-alert-border: var(--c-cobalt-100);
226
+ --conduction-color-quote-border: var(--c-blue-cobalt);
227
+ --conduction-color-toc-border: var(--c-cobalt-100);
228
+ --conduction-alert-rule-width: 5px;
229
+ --conduction-quote-rule-width: 3px;
230
+
134
231
  /* ---------- Typography primitives ---------- */
135
232
  --conduction-typography-font-family-body: 'Figtree', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
136
233
  --conduction-typography-font-family-heading: 'Figtree', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
@@ -155,6 +252,14 @@
155
252
  --lh-normal: 1.5;
156
253
  --lh-relaxed: 1.75;
157
254
 
255
+ /* Tracking — large Figtree needs negative letter-spacing to sit
256
+ the way the brand's own pages do. Visible from about 24px up,
257
+ invisible below it. Published as tokens so a consumer does not
258
+ have to read the numbers off a heading rule. */
259
+ --tracking-tight: -0.02em; /* h1 */
260
+ --tracking-snug: -0.015em; /* h2 */
261
+ --tracking-normal: -0.01em; /* h3 */
262
+
158
263
  /* ---------- Spacing (proposed scope-B) ---------- */
159
264
  --space-1: 4px;
160
265
  --space-2: 8px;
@@ -285,6 +390,154 @@
285
390
  --bleed: 0mm;
286
391
  }
287
392
 
393
+ /* ============================================================
394
+ Dark theme
395
+
396
+ Every Conduction site has run a dark mode for years with no
397
+ published counterpart, so each product invented its own. These
398
+ are the values conduction.nl actually paints, promoted to the
399
+ token layer: cobalt-900 ground, cobalt-800 surface, cobalt-300
400
+ primary, cobalt-100 text.
401
+
402
+ Only the semantic layer flips. The primitives never move — a
403
+ theme is a different mapping of the same palette, not a second
404
+ palette. Two rules hold only here:
405
+
406
+ 1. Text accent goes back to the bright KNVB orange (5.93:1 on
407
+ the ground). coral-600 exists because orange is too light
408
+ for white grounds; on a dark ground that reverses and
409
+ coral-600 becomes the one that fails.
410
+ 2. Status ink and status rule are the same -300 step. The 500
411
+ bases are far too dark to sit on cobalt-800 — cobalt-500
412
+ reaches 1.73:1 there — so in dark the light step does both
413
+ jobs and the tint is simply the surface.
414
+
415
+ The attribute selector is what Docusaurus sets on <html>. The
416
+ media query covers anything that only follows the OS, guarded
417
+ so an explicit light choice still wins.
418
+ ============================================================ */
419
+ :root[data-theme="dark"] {
420
+ /* Background */
421
+ --conduction-color-background-default: var(--c-cobalt-900);
422
+ --conduction-color-background-surface: var(--c-cobalt-800);
423
+ --conduction-color-background-muted: var(--c-cobalt-800);
424
+ --conduction-color-background-inverse: var(--c-cobalt-50);
425
+
426
+ /* Primary ramp inverts: the light steps carry, the dark recede */
427
+ --conduction-color-primary: var(--c-cobalt-300);
428
+ --conduction-color-primary-light: var(--c-cobalt-200);
429
+ --conduction-color-primary-lighter: var(--c-cobalt-100);
430
+ --conduction-color-primary-lightest: var(--c-white);
431
+ --conduction-color-primary-dark: var(--c-cobalt-400);
432
+ --conduction-color-primary-darker: var(--c-cobalt-500);
433
+ --conduction-color-primary-darkest: var(--c-cobalt-600);
434
+
435
+ /* Text — 13.85:1 for body, 5.91:1 for muted */
436
+ --conduction-color-text-default: var(--c-cobalt-100);
437
+ --conduction-color-text-inverse: var(--c-cobalt-900);
438
+ --conduction-color-text-muted: var(--c-cobalt-300);
439
+ --conduction-color-text-accent: var(--c-orange-knvb);
440
+
441
+ /* Link */
442
+ --conduction-color-link-default: var(--c-cobalt-300);
443
+ --conduction-color-link-hover: var(--c-orange-knvb);
444
+
445
+ /* Border — cobalt-300 clears 3:1 on both the ground and the
446
+ surface; cobalt-400 only manages it on the ground. */
447
+ --conduction-color-border-default: var(--c-cobalt-300);
448
+ --conduction-color-border-strong: var(--c-cobalt-200);
449
+
450
+ /* Status — the light step carries both ink and rule, and the
451
+ surface is the ground. */
452
+ --conduction-color-status-info: var(--c-cobalt-300);
453
+ --conduction-color-status-info-ink: var(--c-cobalt-300);
454
+ --conduction-color-status-info-tint: var(--c-cobalt-800);
455
+
456
+ --conduction-color-status-success: var(--c-forest-300);
457
+ --conduction-color-status-success-ink: var(--c-forest-300);
458
+ --conduction-color-status-success-tint: var(--c-forest-950);
459
+
460
+ --conduction-color-status-warning: var(--c-coral-300);
461
+ --conduction-color-status-warning-ink: var(--c-coral-300);
462
+ --conduction-color-status-warning-tint: var(--c-coral-950);
463
+
464
+ --conduction-color-status-error: var(--c-red-300);
465
+ --conduction-color-status-error-ink: var(--c-red-300);
466
+ --conduction-color-status-error-tint: var(--c-red-950);
467
+
468
+ /* Code — the block ground was already dark, so it stays; inline
469
+ moves off the light tint onto the surface. */
470
+ --conduction-color-code-inline-background: var(--c-cobalt-800);
471
+ --conduction-color-code-inline-text: var(--c-cobalt-100);
472
+
473
+ /* Alert, quote, toc */
474
+ --conduction-color-alert-border: var(--c-cobalt-700);
475
+ --conduction-color-quote-border: var(--c-cobalt-300);
476
+ --conduction-color-toc-border: var(--c-cobalt-700);
477
+ }
478
+
479
+ @media (prefers-color-scheme: dark) {
480
+ :root:not([data-theme="light"]) {
481
+ /* Background */
482
+ --conduction-color-background-default: var(--c-cobalt-900);
483
+ --conduction-color-background-surface: var(--c-cobalt-800);
484
+ --conduction-color-background-muted: var(--c-cobalt-800);
485
+ --conduction-color-background-inverse: var(--c-cobalt-50);
486
+
487
+ /* Primary ramp inverts: the light steps carry, the dark recede */
488
+ --conduction-color-primary: var(--c-cobalt-300);
489
+ --conduction-color-primary-light: var(--c-cobalt-200);
490
+ --conduction-color-primary-lighter: var(--c-cobalt-100);
491
+ --conduction-color-primary-lightest: var(--c-white);
492
+ --conduction-color-primary-dark: var(--c-cobalt-400);
493
+ --conduction-color-primary-darker: var(--c-cobalt-500);
494
+ --conduction-color-primary-darkest: var(--c-cobalt-600);
495
+
496
+ /* Text — 13.85:1 for body, 5.91:1 for muted */
497
+ --conduction-color-text-default: var(--c-cobalt-100);
498
+ --conduction-color-text-inverse: var(--c-cobalt-900);
499
+ --conduction-color-text-muted: var(--c-cobalt-300);
500
+ --conduction-color-text-accent: var(--c-orange-knvb);
501
+
502
+ /* Link */
503
+ --conduction-color-link-default: var(--c-cobalt-300);
504
+ --conduction-color-link-hover: var(--c-orange-knvb);
505
+
506
+ /* Border — cobalt-300 clears 3:1 on both the ground and the
507
+ surface; cobalt-400 only manages it on the ground. */
508
+ --conduction-color-border-default: var(--c-cobalt-300);
509
+ --conduction-color-border-strong: var(--c-cobalt-200);
510
+
511
+ /* Status — the light step carries both ink and rule, and the
512
+ surface is the ground. */
513
+ --conduction-color-status-info: var(--c-cobalt-300);
514
+ --conduction-color-status-info-ink: var(--c-cobalt-300);
515
+ --conduction-color-status-info-tint: var(--c-cobalt-800);
516
+
517
+ --conduction-color-status-success: var(--c-forest-300);
518
+ --conduction-color-status-success-ink: var(--c-forest-300);
519
+ --conduction-color-status-success-tint: var(--c-forest-950);
520
+
521
+ --conduction-color-status-warning: var(--c-coral-300);
522
+ --conduction-color-status-warning-ink: var(--c-coral-300);
523
+ --conduction-color-status-warning-tint: var(--c-coral-950);
524
+
525
+ --conduction-color-status-error: var(--c-red-300);
526
+ --conduction-color-status-error-ink: var(--c-red-300);
527
+ --conduction-color-status-error-tint: var(--c-red-950);
528
+
529
+ /* Code — the block ground was already dark, so it stays; inline
530
+ moves off the light tint onto the surface. */
531
+ --conduction-color-code-inline-background: var(--c-cobalt-800);
532
+ --conduction-color-code-inline-text: var(--c-cobalt-100);
533
+
534
+ /* Alert, quote, toc */
535
+ --conduction-color-alert-border: var(--c-cobalt-700);
536
+ --conduction-color-quote-border: var(--c-cobalt-300);
537
+ --conduction-color-toc-border: var(--c-cobalt-700);
538
+ }
539
+ }
540
+
288
541
  /* ============================================================
289
542
  Base typography
290
543
  ============================================================ */