@rtkelly-labs/design-system 0.0.0-bootstrap.0 → 0.16.0-dev.0.37dedac

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.
Files changed (284) hide show
  1. package/CHANGELOG.md +347 -0
  2. package/LICENSE +21 -0
  3. package/README.md +317 -2
  4. package/dist/components/AlertDialog.js +95 -0
  5. package/dist/components/AlertDialog.js.map +1 -0
  6. package/dist/components/AppShell.js +261 -0
  7. package/dist/components/AppShell.js.map +1 -0
  8. package/dist/components/AsciiDivider.js +8 -0
  9. package/dist/components/AsciiDivider.js.map +1 -0
  10. package/dist/components/AsciiGauge.js +130 -0
  11. package/dist/components/AsciiGauge.js.map +1 -0
  12. package/dist/components/Avatar.js +87 -0
  13. package/dist/components/Avatar.js.map +1 -0
  14. package/dist/components/Badge.js +25 -0
  15. package/dist/components/Badge.js.map +1 -0
  16. package/dist/components/BarChart.js +288 -0
  17. package/dist/components/BarChart.js.map +1 -0
  18. package/dist/components/BracketText.js +23 -0
  19. package/dist/components/BracketText.js.map +1 -0
  20. package/dist/components/BulletChart.js +50 -0
  21. package/dist/components/BulletChart.js.map +1 -0
  22. package/dist/components/Button.js +130 -0
  23. package/dist/components/Button.js.map +1 -0
  24. package/dist/components/Card.js +181 -0
  25. package/dist/components/Card.js.map +1 -0
  26. package/dist/components/ChartTooltip.js +54 -0
  27. package/dist/components/ChartTooltip.js.map +1 -0
  28. package/dist/components/Checkbox.js +55 -0
  29. package/dist/components/Checkbox.js.map +1 -0
  30. package/dist/components/ColophonCard.js +122 -0
  31. package/dist/components/ColophonCard.js.map +1 -0
  32. package/dist/components/DataTable.js +299 -0
  33. package/dist/components/DataTable.js.map +1 -0
  34. package/dist/components/Divider.js +42 -0
  35. package/dist/components/Divider.js.map +1 -0
  36. package/dist/components/Drawer.js +81 -0
  37. package/dist/components/Drawer.js.map +1 -0
  38. package/dist/components/EmptyState.js +39 -0
  39. package/dist/components/EmptyState.js.map +1 -0
  40. package/dist/components/ErrorSummary.js +122 -0
  41. package/dist/components/ErrorSummary.js.map +1 -0
  42. package/dist/components/Fieldset.js +37 -0
  43. package/dist/components/Fieldset.js.map +1 -0
  44. package/dist/components/Glyph.js +60 -0
  45. package/dist/components/Glyph.js.map +1 -0
  46. package/dist/components/Input.js +111 -0
  47. package/dist/components/Input.js.map +1 -0
  48. package/dist/components/LinkProvider.js +76 -0
  49. package/dist/components/LinkProvider.js.map +1 -0
  50. package/dist/components/Menu.js +144 -0
  51. package/dist/components/Menu.js.map +1 -0
  52. package/dist/components/MetadataGrid.js +184 -0
  53. package/dist/components/MetadataGrid.js.map +1 -0
  54. package/dist/components/MobileNav.js +120 -0
  55. package/dist/components/MobileNav.js.map +1 -0
  56. package/dist/components/Modal.js +79 -0
  57. package/dist/components/Modal.js.map +1 -0
  58. package/dist/components/NerdIcon.js +177 -0
  59. package/dist/components/NerdIcon.js.map +1 -0
  60. package/dist/components/NoteBlock.js +84 -0
  61. package/dist/components/NoteBlock.js.map +1 -0
  62. package/dist/components/PageHeader.js +34 -0
  63. package/dist/components/PageHeader.js.map +1 -0
  64. package/dist/components/PageTitle.js +21 -0
  65. package/dist/components/PageTitle.js.map +1 -0
  66. package/dist/components/Pagination.js +96 -0
  67. package/dist/components/Pagination.js.map +1 -0
  68. package/dist/components/Popover.js +87 -0
  69. package/dist/components/Popover.js.map +1 -0
  70. package/dist/components/Progress.js +83 -0
  71. package/dist/components/Progress.js.map +1 -0
  72. package/dist/components/RadioGroup.js +74 -0
  73. package/dist/components/RadioGroup.js.map +1 -0
  74. package/dist/components/ReportDocument.js +75 -0
  75. package/dist/components/ReportDocument.js.map +1 -0
  76. package/dist/components/SectionContainer.js +18 -0
  77. package/dist/components/SectionContainer.js.map +1 -0
  78. package/dist/components/Select.js +199 -0
  79. package/dist/components/Select.js.map +1 -0
  80. package/dist/components/SiteFooter.js +29 -0
  81. package/dist/components/SiteFooter.js.map +1 -0
  82. package/dist/components/SiteHeader.js +59 -0
  83. package/dist/components/SiteHeader.js.map +1 -0
  84. package/dist/components/SiteNav.js +141 -0
  85. package/dist/components/SiteNav.js.map +1 -0
  86. package/dist/components/Skeleton.js +51 -0
  87. package/dist/components/Skeleton.js.map +1 -0
  88. package/dist/components/SocialIcon.js +77 -0
  89. package/dist/components/SocialIcon.js.map +1 -0
  90. package/dist/components/Sparkline.js +104 -0
  91. package/dist/components/Sparkline.js.map +1 -0
  92. package/dist/components/Spinner.js +65 -0
  93. package/dist/components/Spinner.js.map +1 -0
  94. package/dist/components/StatCard.js +53 -0
  95. package/dist/components/StatCard.js.map +1 -0
  96. package/dist/components/StatusPage.js +99 -0
  97. package/dist/components/StatusPage.js.map +1 -0
  98. package/dist/components/Swatch.js +55 -0
  99. package/dist/components/Swatch.js.map +1 -0
  100. package/dist/components/Switch.js +53 -0
  101. package/dist/components/Switch.js.map +1 -0
  102. package/dist/components/TLDR.js +53 -0
  103. package/dist/components/TLDR.js.map +1 -0
  104. package/dist/components/Table.js +155 -0
  105. package/dist/components/Table.js.map +1 -0
  106. package/dist/components/Tabs.js +325 -0
  107. package/dist/components/Tabs.js.map +1 -0
  108. package/dist/components/Tag.js +19 -0
  109. package/dist/components/Tag.js.map +1 -0
  110. package/dist/components/TelemetryGauge.js +112 -0
  111. package/dist/components/TelemetryGauge.js.map +1 -0
  112. package/dist/components/ThemeProvider.js +87 -0
  113. package/dist/components/ThemeProvider.js.map +1 -0
  114. package/dist/components/TickerTape.js +196 -0
  115. package/dist/components/TickerTape.js.map +1 -0
  116. package/dist/components/Toast.js +200 -0
  117. package/dist/components/Toast.js.map +1 -0
  118. package/dist/components/Tooltip.js +69 -0
  119. package/dist/components/Tooltip.js.map +1 -0
  120. package/dist/components/TrackCard.js +206 -0
  121. package/dist/components/TrackCard.js.map +1 -0
  122. package/dist/components/blog/BlogPost.js +116 -0
  123. package/dist/components/blog/BlogPost.js.map +1 -0
  124. package/dist/components/blog/LoremIpsumPost.js +62 -0
  125. package/dist/components/blog/LoremIpsumPost.js.map +1 -0
  126. package/dist/components/blog/author.js +25 -0
  127. package/dist/components/blog/author.js.map +1 -0
  128. package/dist/components/booleanControl.js +50 -0
  129. package/dist/components/booleanControl.js.map +1 -0
  130. package/dist/components/dialogPopupRef.js +28 -0
  131. package/dist/components/dialogPopupRef.js.map +1 -0
  132. package/dist/components/dialogSurface.js +75 -0
  133. package/dist/components/dialogSurface.js.map +1 -0
  134. package/dist/components/docs/AnchorHeading.js +80 -0
  135. package/dist/components/docs/AnchorHeading.js.map +1 -0
  136. package/dist/components/docs/Breadcrumbs.js +26 -0
  137. package/dist/components/docs/Breadcrumbs.js.map +1 -0
  138. package/dist/components/docs/CodeBlock.js +63 -0
  139. package/dist/components/docs/CodeBlock.js.map +1 -0
  140. package/dist/components/docs/CodeTabs.js +71 -0
  141. package/dist/components/docs/CodeTabs.js.map +1 -0
  142. package/dist/components/docs/DocPager.js +28 -0
  143. package/dist/components/docs/DocPager.js.map +1 -0
  144. package/dist/components/docs/DocsHeader.js +124 -0
  145. package/dist/components/docs/DocsHeader.js.map +1 -0
  146. package/dist/components/docs/DocsLayout.js +61 -0
  147. package/dist/components/docs/DocsLayout.js.map +1 -0
  148. package/dist/components/docs/DocsLinkProvider.js +25 -0
  149. package/dist/components/docs/DocsLinkProvider.js.map +1 -0
  150. package/dist/components/docs/DocsSidebar.js +147 -0
  151. package/dist/components/docs/DocsSidebar.js.map +1 -0
  152. package/dist/components/docs/Prose.js +56 -0
  153. package/dist/components/docs/Prose.js.map +1 -0
  154. package/dist/components/docs/TableOfContents.js +71 -0
  155. package/dist/components/docs/TableOfContents.js.map +1 -0
  156. package/dist/components/docs/codeTabsStore.js +75 -0
  157. package/dist/components/docs/codeTabsStore.js.map +1 -0
  158. package/dist/components/docs/figures/ActivityGrid.js +116 -0
  159. package/dist/components/docs/figures/ActivityGrid.js.map +1 -0
  160. package/dist/components/docs/figures/BeforeAfter.js +64 -0
  161. package/dist/components/docs/figures/BeforeAfter.js.map +1 -0
  162. package/dist/components/docs/figures/ChangeSummary.js +74 -0
  163. package/dist/components/docs/figures/ChangeSummary.js.map +1 -0
  164. package/dist/components/docs/figures/Decision.js +34 -0
  165. package/dist/components/docs/figures/Decision.js.map +1 -0
  166. package/dist/components/docs/figures/DrawingSheet.js +37 -0
  167. package/dist/components/docs/figures/DrawingSheet.js.map +1 -0
  168. package/dist/components/docs/figures/FigureFrame.js +226 -0
  169. package/dist/components/docs/figures/FigureFrame.js.map +1 -0
  170. package/dist/components/docs/figures/FileChangeTree.js +134 -0
  171. package/dist/components/docs/figures/FileChangeTree.js.map +1 -0
  172. package/dist/components/docs/figures/FlowDiagram.js +82 -0
  173. package/dist/components/docs/figures/FlowDiagram.js.map +1 -0
  174. package/dist/components/docs/figures/GanttChart.js +79 -0
  175. package/dist/components/docs/figures/GanttChart.js.map +1 -0
  176. package/dist/components/docs/figures/IndexedSection.js +45 -0
  177. package/dist/components/docs/figures/IndexedSection.js.map +1 -0
  178. package/dist/components/docs/figures/MetricStrip.js +23 -0
  179. package/dist/components/docs/figures/MetricStrip.js.map +1 -0
  180. package/dist/components/docs/figures/Steps.js +58 -0
  181. package/dist/components/docs/figures/Steps.js.map +1 -0
  182. package/dist/components/docs/figures/SwatchCompare.js +42 -0
  183. package/dist/components/docs/figures/SwatchCompare.js.map +1 -0
  184. package/dist/components/docs/figures/TermGrid.js +22 -0
  185. package/dist/components/docs/figures/TermGrid.js.map +1 -0
  186. package/dist/components/docs/figures/Terminal.js +37 -0
  187. package/dist/components/docs/figures/Terminal.js.map +1 -0
  188. package/dist/components/docs/figures/Timeline.js +61 -0
  189. package/dist/components/docs/figures/Timeline.js.map +1 -0
  190. package/dist/components/docs/figures/TokenTable.js +42 -0
  191. package/dist/components/docs/figures/TokenTable.js.map +1 -0
  192. package/dist/components/docs/figures/TreeDiagram.js +76 -0
  193. package/dist/components/docs/figures/TreeDiagram.js.map +1 -0
  194. package/dist/components/docs/figures/UptimeStrip.js +68 -0
  195. package/dist/components/docs/figures/UptimeStrip.js.map +1 -0
  196. package/dist/components/docs/figures/index.js +24 -0
  197. package/dist/components/docs/figures/index.js.map +1 -0
  198. package/dist/components/docs/index.js +15 -0
  199. package/dist/components/docs/index.js.map +1 -0
  200. package/dist/components/docs/mdxComponents.js +108 -0
  201. package/dist/components/docs/mdxComponents.js.map +1 -0
  202. package/dist/components/experiments/DesignSandbox.js +206 -0
  203. package/dist/components/experiments/DesignSandbox.js.map +1 -0
  204. package/dist/components/experiments/ExperimentsView.js +138 -0
  205. package/dist/components/experiments/ExperimentsView.js.map +1 -0
  206. package/dist/components/fieldFrame.js +200 -0
  207. package/dist/components/fieldFrame.js.map +1 -0
  208. package/dist/components/floatingSurface.js +35 -0
  209. package/dist/components/floatingSurface.js.map +1 -0
  210. package/dist/components/marketing/CTASection.js +47 -0
  211. package/dist/components/marketing/CTASection.js.map +1 -0
  212. package/dist/components/marketing/FeatureGrid.js +49 -0
  213. package/dist/components/marketing/FeatureGrid.js.map +1 -0
  214. package/dist/components/marketing/Hero.js +44 -0
  215. package/dist/components/marketing/Hero.js.map +1 -0
  216. package/dist/components/marketing/PricingGrid.js +65 -0
  217. package/dist/components/marketing/PricingGrid.js.map +1 -0
  218. package/dist/components/marketing/SectionGrid.js +51 -0
  219. package/dist/components/marketing/SectionGrid.js.map +1 -0
  220. package/dist/components/paginationRange.js +28 -0
  221. package/dist/components/paginationRange.js.map +1 -0
  222. package/dist/components/portalTheme.js +14 -0
  223. package/dist/components/portalTheme.js.map +1 -0
  224. package/dist/components/saas/SaasLandingPage.js +201 -0
  225. package/dist/components/saas/SaasLandingPage.js.map +1 -0
  226. package/dist/components/siteNavContext.js +24 -0
  227. package/dist/components/siteNavContext.js.map +1 -0
  228. package/dist/components/slides/Slide.js +84 -0
  229. package/dist/components/slides/Slide.js.map +1 -0
  230. package/dist/components/slides/SlideDeck.js +228 -0
  231. package/dist/components/slides/SlideDeck.js.map +1 -0
  232. package/dist/components/themeInitScript.js +16 -0
  233. package/dist/components/themeInitScript.js.map +1 -0
  234. package/dist/components/themeScope.js +9 -0
  235. package/dist/components/themeScope.js.map +1 -0
  236. package/dist/components/toastLifetime.js +21 -0
  237. package/dist/components/toastLifetime.js.map +1 -0
  238. package/dist/fonts/DSSymbols-OFL.txt +98 -0
  239. package/dist/fonts/DSSymbols-Regular.woff2 +0 -0
  240. package/dist/fonts/SymbolsNerdFontMono-Regular.woff2 +0 -0
  241. package/dist/hooks/useActiveHeading.js +59 -0
  242. package/dist/hooks/useActiveHeading.js.map +1 -0
  243. package/dist/hooks/useCopyToClipboard.js +58 -0
  244. package/dist/hooks/useCopyToClipboard.js.map +1 -0
  245. package/dist/index.d.ts +6394 -0
  246. package/dist/index.js +117 -0
  247. package/dist/index.js.map +1 -0
  248. package/dist/lib/accentClasses.js +87 -0
  249. package/dist/lib/accentClasses.js.map +1 -0
  250. package/dist/lib/recipe.js +33 -0
  251. package/dist/lib/recipe.js.map +1 -0
  252. package/dist/lib/slug.js +39 -0
  253. package/dist/lib/slug.js.map +1 -0
  254. package/dist/lib/theme.js +76 -0
  255. package/dist/lib/theme.js.map +1 -0
  256. package/dist/lib/tokenRules.js +51 -0
  257. package/dist/lib/tokenRules.js.map +1 -0
  258. package/dist/prose.css +766 -0
  259. package/dist/styles.css +203 -0
  260. package/dist/theme/ansi.js +77 -0
  261. package/dist/theme/ansi.js.map +1 -0
  262. package/dist/theme/contrast.js +342 -0
  263. package/dist/theme/contrast.js.map +1 -0
  264. package/dist/theme/levels.js +234 -0
  265. package/dist/theme/levels.js.map +1 -0
  266. package/dist/theme/media.js +115 -0
  267. package/dist/theme/media.js.map +1 -0
  268. package/dist/theme/recommended.js +105 -0
  269. package/dist/theme/recommended.js.map +1 -0
  270. package/dist/theme.css +538 -0
  271. package/dist/tokens.js +1 -0
  272. package/dist/tokens.js.map +1 -0
  273. package/package.json +209 -6
  274. package/skills/design-system-usage/SKILL.md +75 -0
  275. package/terminal/rtk-midnight.alacritty.toml +33 -0
  276. package/terminal/rtk-midnight.ghostty.conf +24 -0
  277. package/terminal/rtk-midnight.itermcolors +167 -0
  278. package/terminal/rtk-midnight.windows-terminal.json +23 -0
  279. package/terminal/rtk-sketch.alacritty.toml +33 -0
  280. package/terminal/rtk-sketch.ghostty.conf +24 -0
  281. package/terminal/rtk-sketch.itermcolors +167 -0
  282. package/terminal/rtk-sketch.windows-terminal.json +23 -0
  283. package/tokens/palette.midnight.tokens.json +477 -0
  284. package/tokens/palette.sketch.tokens.json +477 -0
@@ -0,0 +1,203 @@
1
+ /* @rtkelly-labs/design-system — the full opinionated stylesheet.
2
+ *
3
+ * theme.css alone ships the tokens and nothing else, for consumers who want to
4
+ * bring their own resets and fonts. This file layers on the web fonts and the
5
+ * global resets — and nothing more.
6
+ *
7
+ * ============================================================================
8
+ * Why there are no component classes here
9
+ * ============================================================================
10
+ *
11
+ * Styling lives in the TSX, as Tailwind utilities on the element. What remains
12
+ * in this file is only what has no element in a component to hang a `className`
13
+ * on: the document itself, and bare tags arriving from a consumer's own markup.
14
+ *
15
+ * `pnpm check:css` enforces that. It counts every declaration whose selector
16
+ * names a class this repo authors, and fails when the number rises.
17
+ *
18
+ * The classes that used to live here — `.brutalist-card`, `.brutalist-btn`,
19
+ * `.brutalist-badge`, `.ascii-divider` — are now utilities inside `Card`,
20
+ * `Badge` and `AsciiDivider`. `.brutalist-btn` and `.brutalist-btn-pink` had no
21
+ * caller anywhere in this package and are gone; use `<Button />`.
22
+ */
23
+ /* Fonts are self-hosted, from `@fontsource`, and that is a correctness choice
24
+ * before it is a performance one.
25
+ *
26
+ * These lines replace a remote `@import` of the Google Fonts CSS API. Four
27
+ * things were wrong with that:
28
+ *
29
+ * 1. **It transmits every visitor's IP to a third party.** The Munich
30
+ * Regional Court held that doing so without consent breaches the GDPR,
31
+ * specifically because self-hosting was available and so the transfer was
32
+ * not necessary. A design system should not hand that decision — or that
33
+ * liability — to whoever installs it.
34
+ * 2. **The performance argument no longer exists.** It rested on a shared
35
+ * browser cache across every site using Google Fonts; cache partitioning
36
+ * ended that, so each origin downloads its own copy regardless.
37
+ * 3. **`@import` of a remote stylesheet is the slowest possible delivery.**
38
+ * It serialises: fetch this file, parse it, discover the import, fetch
39
+ * Google's CSS, parse that, and only then fetch the WOFF2s. Three round
40
+ * trips of render-blocking work before text can paint.
41
+ * 4. **The rendering was not pinned to anything.** Google can change a font
42
+ * binary with no commit here, which moves both consumer rendering and this
43
+ * repo's visual baselines with nothing to explain why. Now the exact bytes
44
+ * are in the lockfile, pinned by the same mechanism as every other
45
+ * dependency.
46
+ *
47
+ * Variable where a variable build exists — one file spans the whole weight axis
48
+ * instead of four to seven static files. `ibm-plex-mono` has no variable build,
49
+ * so its four weights are imported individually.
50
+ *
51
+ * Note `space-grotesk` really is a 300-700 family, while the old Google request
52
+ * asked for 800. Whatever resolved that gap before — Google clamping to 700
53
+ * server-side, or the browser synthesising — the outcome is unchanged: the
54
+ * visual suite compares all 38 baselines at `maxDiffPixels: 0` and every one of
55
+ * them is byte-identical across this change. Worth knowing that
56
+ * `font-extrabold` on a display face is not drawn at 800 by this family, but it
57
+ * was not before either.
58
+ *
59
+ * `theme.css` remains the bring-your-own-fonts path: it declares only the
60
+ * `--ds-font-*` roles, each indirecting through `var(--font-inter, ...)` so a
61
+ * consumer can substitute their own faces without touching this file. */
62
+ @import "@fontsource-variable/inter";
63
+ @import "@fontsource-variable/space-grotesk";
64
+ @import "@fontsource/ibm-plex-mono/400.css";
65
+ @import "@fontsource/ibm-plex-mono/500.css";
66
+ @import "@fontsource/ibm-plex-mono/600.css";
67
+ @import "@fontsource/ibm-plex-mono/700.css";
68
+ @import "@fontsource/vt323";
69
+
70
+ @font-face {
71
+ font-family: "Symbols Nerd Font Mono";
72
+ src: url("./fonts/SymbolsNerdFontMono-Regular.woff2") format("woff2");
73
+ font-weight: 400 700;
74
+ font-style: normal;
75
+ font-display: swap;
76
+ unicode-range: U+E000-F8FF, U+F0000-FFFFF, U+23FB-23FE, U+2B58;
77
+ }
78
+
79
+ /* Every symbol a story renders, drawn by a font this package ships.
80
+ *
81
+ * None of the text faces above carry arrows, box drawing, blocks, geometric
82
+ * shapes, dingbats or `⌘`; IBM Plex Mono is latin-only and Nerd Fonts' symbol
83
+ * set covers none of U+2500-259F. So `→`, `✓`, `⌘` and every ASCII-art rule
84
+ * were drawn by whatever the machine had installed — Courier New on one runner,
85
+ * nothing at all for `⌘` there, a different font in the Playwright image. The
86
+ * visual suite could not tell a regression from a different operating system.
87
+ *
88
+ * A 21 KB subset of JetBrains Mono (OFL-1.1, `fonts/DSSymbols-OFL.txt`), cut to
89
+ * these blocks only, so it never replaces a Latin glyph. Every advance is 600 of
90
+ * 1000 units, the same as IBM Plex Mono, so box drawing keeps the mono stack's
91
+ * column width. One weight declared across the whole axis, so bold text never
92
+ * gets a synthesised, fattened box-drawing line. `check:fonts` fails when a
93
+ * rendered character reaches no shipped face. */
94
+ @font-face {
95
+ font-family: "DS Symbols";
96
+ src: url("./fonts/DSSymbols-Regular.woff2") format("woff2");
97
+ font-weight: 100 900;
98
+ font-style: normal;
99
+ font-display: swap;
100
+ unicode-range: U+0370-03FF, U+2190-21FF, U+2300-23FF, U+2500-259F, U+25A0-25FF, U+2600-26FF, U+2700-27BF;
101
+ }
102
+
103
+ @import "tailwindcss";
104
+ @import "./theme.css";
105
+ @import "./prose.css";
106
+
107
+ /* The `--animate-ds-*` loops and their keyframes used to be declared here,
108
+ * which left a consumer on the documented `theme.css` contract with a Spinner
109
+ * that did not spin (#302). They are generated into `theme.css` now, from
110
+ * `LOOPS` in `src/theme/media.ts`, and reach this sheet through the import
111
+ * above. */
112
+
113
+ body, html {
114
+ font-family: var(--ds-font-body);
115
+ background-color: var(--ds-surface-base);
116
+ color: var(--ds-text-primary);
117
+ }
118
+
119
+ /* Typography defaults for bare tags. A consumer's own markup and a Markdown
120
+ * pipeline both emit these with no class to target, so this is the one place
121
+ * the type roles can reach them. Components state their own font.
122
+ *
123
+ * `!important` is inherited from the rule these replace, and is load-bearing
124
+ * while prose.css still sets fonts on `.docs-prose h1` and friends: a component
125
+ * carrying `font-mono` inside an article would otherwise lose to the more
126
+ * specific descendant selector. It comes out with those prose rules. */
127
+ h1, h2, h3, h4, h5, h6 {
128
+ font-family: var(--ds-font-display) !important;
129
+ }
130
+
131
+ code, pre {
132
+ font-family: var(--ds-font-mono) !important;
133
+ }
134
+
135
+ /* Radius, as a token rather than a reset.
136
+ *
137
+ * This was `*, *::before, *::after { border-radius: 0px !important }` — a
138
+ * universal selector with `!important`, reaching every element in a consumer's
139
+ * document including ones this package has never heard of. A consumer wanting a
140
+ * rounded avatar in their own markup could not have one, and the only way out
141
+ * was a second `!important`. That is #54.
142
+ *
143
+ * Zero is a value on the radius scale, not the absence of one. Declared per
144
+ * Medium in `src/theme/media.ts` and emitted as `--ds-radius-none`, it stops
145
+ * being a reset: scoped to this package's own elements, and overridable by
146
+ * ordinary specificity.
147
+ *
148
+ * There is no rule here at all, and that is the fix.
149
+ *
150
+ * `theme.css` redefines Tailwind's whole radius scale to the Medium's value, so
151
+ * every `rounded-*` utility — including `rounded-lg` on a component this
152
+ * package ships — resolves to zero without any selector. Nothing else needs
153
+ * saying.
154
+ *
155
+ * A rule for bare `<input>` / `<button>` tags arriving from a consumer's own
156
+ * markup was the obvious next thought, and `check:css` was right to refuse it:
157
+ * `src/styles.css` has a budget of zero, and reaching into a consumer's
158
+ * document is precisely what #54 is about. A consumer's bare tags are theirs. */
159
+
160
+ /* Keyboard focus must stay visible even where a component replaces the ring
161
+ * with a border colour — a border swap is invisible under forced-colors.
162
+ *
163
+ * The geometry is the Medium's and the colour is the Level's, which is the two
164
+ * axes in one declaration. `media.ts` has declared `focusRing` since the axis
165
+ * landed and this rule kept its own hardcoded `2px` — a token with no reader,
166
+ * which is the same fault the palette had before `accentHue` gave the Roles
167
+ * something to point at. A ring that has to be thicker on a 1080p frame now
168
+ * changes in one place. */
169
+ :focus-visible {
170
+ outline: var(--ds-focus-width) solid var(--ds-accent-primary);
171
+ outline-offset: var(--ds-focus-offset);
172
+ }
173
+
174
+ /* Paper.
175
+
176
+ * The document-level half of the PDF flow: what a report or a docs page does
177
+ * when it leaves the screen. Element selectors and at-rules only — `check:css`
178
+ * keeps its budget at zero here, and the per-component decisions (hide the
179
+ * header, drop the sidebar) are `print:` utilities on the element, which is
180
+ * where all styling lives.
181
+ *
182
+ * The margin is declared once, on `@page`, and `ds-report --pdf` reads it with
183
+ * `preferCSSPageSize` rather than restating it at the driver. The motion kill
184
+ * is switch 3 of `docs/deterministic-rendering.md` applied to the one
185
+ * consumer that has no capture harness: the browser's own print pipeline. A
186
+ * stylesheet is where it belongs because it is wrong on screen and right
187
+ * exactly once — on paper. Colour-exact is what keeps a border that took the
188
+ * contrast gate onto the page instead of into a driver's ink-saving guess. */
189
+ @media print {
190
+ @page { margin: 16mm 14mm; }
191
+
192
+ *,
193
+ *::before,
194
+ *::after {
195
+ transition: none !important;
196
+ animation: none !important;
197
+ -webkit-print-color-adjust: exact;
198
+ print-color-adjust: exact;
199
+ }
200
+
201
+ h1, h2, h3, h4 { break-after: avoid; }
202
+ p { orphans: 3; widows: 3; }
203
+ }
@@ -0,0 +1,77 @@
1
+ // src/theme/ansi.ts
2
+ import { LEVELS } from "./levels.js";
3
+ function achromatic(level, step) {
4
+ const dark = level.polarity === "dark";
5
+ switch (step) {
6
+ // The terminal's own black: the theme's deepest ground on a dark Level, its
7
+ // ink on a light one.
8
+ case "darkest":
9
+ return dark ? level.surface.sunken : level.text.primary;
10
+ case "dark":
11
+ return dark ? level.surface.raised : level.text.secondary;
12
+ case "light":
13
+ return dark ? level.text.secondary : level.surface.sunken;
14
+ case "lightest":
15
+ return dark ? level.text.primary : level.surface.raised;
16
+ }
17
+ }
18
+ var ANSI_SLOTS = [
19
+ "black",
20
+ "red",
21
+ "green",
22
+ "yellow",
23
+ "blue",
24
+ "magenta",
25
+ "cyan",
26
+ "white",
27
+ "brightBlack",
28
+ "brightRed",
29
+ "brightGreen",
30
+ "brightYellow",
31
+ "brightBlue",
32
+ "brightMagenta",
33
+ "brightCyan",
34
+ "brightWhite"
35
+ ];
36
+ var SLOTS = {
37
+ black: { kind: "role", read: (l) => achromatic(l, "darkest") },
38
+ red: { kind: "hue", hue: "red", bright: false },
39
+ green: { kind: "hue", hue: "green", bright: false },
40
+ yellow: { kind: "hue", hue: "yellow", bright: false },
41
+ blue: { kind: "hue", hue: "blue", bright: false },
42
+ magenta: { kind: "hue", hue: "magenta", bright: false },
43
+ cyan: { kind: "hue", hue: "cyan", bright: false },
44
+ white: { kind: "role", read: (l) => achromatic(l, "light") },
45
+ brightBlack: { kind: "role", read: (l) => achromatic(l, "dark") },
46
+ brightRed: { kind: "hue", hue: "red", bright: true },
47
+ brightGreen: { kind: "hue", hue: "green", bright: true },
48
+ brightYellow: { kind: "hue", hue: "yellow", bright: true },
49
+ brightBlue: { kind: "hue", hue: "blue", bright: true },
50
+ brightMagenta: { kind: "hue", hue: "magenta", bright: true },
51
+ brightCyan: { kind: "hue", hue: "cyan", bright: true },
52
+ brightWhite: { kind: "role", read: (l) => achromatic(l, "lightest") }
53
+ };
54
+ function ansiScheme(level) {
55
+ const def = LEVELS[level];
56
+ const slots = {};
57
+ for (const slot of ANSI_SLOTS) {
58
+ const source = SLOTS[slot];
59
+ slots[slot] = source.kind === "hue" ? (source.bright ? def.paletteBright : def.palette)[source.hue] : source.kind === "role" ? source.read(def) : source.read();
60
+ }
61
+ return {
62
+ level,
63
+ slots,
64
+ chrome: {
65
+ background: def.surface.base,
66
+ foreground: def.text.primary,
67
+ cursor: def.accent.primary,
68
+ selectionBackground: def.surface.raised
69
+ }
70
+ };
71
+ }
72
+ export {
73
+ ANSI_SLOTS,
74
+ SLOTS,
75
+ ansiScheme
76
+ };
77
+ //# sourceMappingURL=ansi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/theme/ansi.ts"],"sourcesContent":["import { LEVELS } from './levels.ts';\nimport type { LevelDefinition, PALETTE_HUES, ThemeLevel } from './levels.ts';\n\n/**\n * The sixteen ANSI slots, and the fan-out that fills them from ten Hues.\n *\n * ## Why a terminal is the cheapest Target on the list\n *\n * Sixteen Slots, a foreground, a background, a cursor and a selection. No\n * chrome, no scopes, no semantic map. And four encodings — iTerm2, Windows\n * Terminal, Alacritty, Ghostty — are the same sixteen values written four\n * ways, so one fan-out unlocks all of them.\n *\n * It was also, until `palette` existed, completely impossible. A terminal has\n * sixteen positions *named by colour* and no concept of a keyword, so a Role\n * vocabulary could not address it at all. Measured before the Hue layer\n * landed: 5 of 16 slots fillable, with `accent.tertiary` stretched across both\n * `red` and `magenta` — 30° from each — and `blue` uncovered at 52°.\n *\n * ## Why the map lives here and not in a Level\n *\n * ADR 0001: *\"Slot maps are Derived, and their fan-out lives in the Emitter.\"*\n * It is data, so it is unit-testable, and keeping it out of the Levels means\n * adding a Target can never change a colour. `SLOTS` below is the whole of the\n * decision; the values are the Level's.\n *\n * ## The one honest compromise\n *\n * `blue` resolves to `palette.blue`, which sits at **233°** — a sky blue, not\n * a true blue — because that is where six years of authored colour put the\n * dark Level's primary accent. `violet` is at 295°, leaving a 62° gap where a\n * true blue would be. Recorded rather than papered over: an eleventh Hue would\n * close it, and ten is already the ceiling, with `teal`/`cyan` on the light\n * Level at ΔE 0.049.\n */\n\n/**\n * The four achromatic slots, resolved by *lightness* rather than by name.\n *\n * The first version of this map read `surface.sunken` for `black` and\n * `text.secondary` for `white` — correct on a dark Level and wrong on a light\n * one, where it gave the terminal a `black` of `#efeadf` (nearly white) and\n * collided `brightBlack` and `brightWhite` on `#ffffff`. A terminal's ramp runs\n * dark to light regardless of which way the theme runs, so the slots have to\n * ask about lightness, not about which Group a value lives in.\n *\n * `fixed.white` is deliberately not used for `lightest`. A terminal cell\n * painted pure white inside a `#f5f3ec` window reads as a hole in the same way\n * true black does inside `#121316` — the extreme should be the theme's own\n * extreme, and on the dark Level `text.primary` already is.\n */\nfunction achromatic(\n level: LevelDefinition,\n step: 'darkest' | 'dark' | 'light' | 'lightest',\n): string {\n const dark = level.polarity === 'dark';\n switch (step) {\n // The terminal's own black: the theme's deepest ground on a dark Level, its\n // ink on a light one.\n case 'darkest':\n return dark ? level.surface.sunken : level.text.primary;\n case 'dark':\n return dark ? level.surface.raised : level.text.secondary;\n case 'light':\n return dark ? level.text.secondary : level.surface.sunken;\n case 'lightest':\n return dark ? level.text.primary : level.surface.raised;\n }\n}\n\n/** The sixteen slots, in the order every terminal format lists them. */\nexport const ANSI_SLOTS = [\n 'black',\n 'red',\n 'green',\n 'yellow',\n 'blue',\n 'magenta',\n 'cyan',\n 'white',\n 'brightBlack',\n 'brightRed',\n 'brightGreen',\n 'brightYellow',\n 'brightBlue',\n 'brightMagenta',\n 'brightCyan',\n 'brightWhite',\n] as const;\n\nexport type AnsiSlot = (typeof ANSI_SLOTS)[number];\n\n/**\n * What fills each slot.\n *\n * `hue` reads `palette` (or `paletteBright` when `bright`); `role` reads a\n * named Role path for the four achromatic slots, which are grounds and inks\n * rather than hues. A slot is never a literal — that would be a second origin,\n * which ADR 0002 forbids.\n */\ntype SlotSource =\n | { readonly kind: 'hue'; readonly hue: (typeof PALETTE_HUES)[number]; readonly bright: boolean }\n | { readonly kind: 'role'; readonly read: (level: LevelDefinition) => string }\n | { readonly kind: 'fixed'; readonly read: () => string };\n\n/**\n * The fan-out map. **This is the whole decision**, and it is data.\n *\n * The four achromatic slots are the interesting ones. A terminal's `black` is\n * not `fixed.black` — it is the darkest ground the theme *has*, because a\n * terminal cell painted true black inside a `#121316` window reads as a hole.\n * Likewise `white` is the theme's ink, not `#ffffff`. Only `brightWhite`\n * reaches for the invariant, because \"brighter than the ink\" has nowhere else\n * to go.\n */\nexport const SLOTS: Readonly<Record<AnsiSlot, SlotSource>> = {\n black: { kind: 'role', read: (l) => achromatic(l, 'darkest') },\n red: { kind: 'hue', hue: 'red', bright: false },\n green: { kind: 'hue', hue: 'green', bright: false },\n yellow: { kind: 'hue', hue: 'yellow', bright: false },\n blue: { kind: 'hue', hue: 'blue', bright: false },\n magenta: { kind: 'hue', hue: 'magenta', bright: false },\n cyan: { kind: 'hue', hue: 'cyan', bright: false },\n white: { kind: 'role', read: (l) => achromatic(l, 'light') },\n\n brightBlack: { kind: 'role', read: (l) => achromatic(l, 'dark') },\n brightRed: { kind: 'hue', hue: 'red', bright: true },\n brightGreen: { kind: 'hue', hue: 'green', bright: true },\n brightYellow: { kind: 'hue', hue: 'yellow', bright: true },\n brightBlue: { kind: 'hue', hue: 'blue', bright: true },\n brightMagenta: { kind: 'hue', hue: 'magenta', bright: true },\n brightCyan: { kind: 'hue', hue: 'cyan', bright: true },\n brightWhite: { kind: 'role', read: (l) => achromatic(l, 'lightest') },\n};\n\n/**\n * The four values a terminal needs beyond the sixteen.\n *\n * `selectionBackground` is the one that would be wrong to derive. A terminal\n * has no alpha, so a selection band must be an opaque colour that every\n * foreground still reads against — which is why it is `surface.raised` and not\n * a tinted `surface.base`. Composited contrast, resolved at declaration time\n * because the Target cannot resolve it at paint time.\n */\nexport interface AnsiChrome {\n readonly background: string;\n readonly foreground: string;\n readonly cursor: string;\n readonly selectionBackground: string;\n}\n\nexport interface AnsiScheme {\n readonly level: ThemeLevel;\n readonly slots: Readonly<Record<AnsiSlot, string>>;\n readonly chrome: AnsiChrome;\n}\n\n/** Resolve one Level into a complete sixteen-colour scheme. */\nexport function ansiScheme(level: ThemeLevel): AnsiScheme {\n const def = LEVELS[level];\n const slots = {} as Record<AnsiSlot, string>;\n\n for (const slot of ANSI_SLOTS) {\n const source = SLOTS[slot];\n slots[slot] =\n source.kind === 'hue'\n ? (source.bright ? def.paletteBright : def.palette)[source.hue]\n : source.kind === 'role'\n ? source.read(def)\n : source.read();\n }\n\n return {\n level,\n slots,\n chrome: {\n background: def.surface.base,\n foreground: def.text.primary,\n cursor: def.accent.primary,\n selectionBackground: def.surface.raised,\n },\n };\n}\n"],"mappings":";AAAA,SAAS,cAAc;AAmDvB,SAAS,WACP,OACA,MACQ;AACR,QAAM,OAAO,MAAM,aAAa;AAChC,UAAQ,MAAM;AAAA;AAAA;AAAA,IAGZ,KAAK;AACH,aAAO,OAAO,MAAM,QAAQ,SAAS,MAAM,KAAK;AAAA,IAClD,KAAK;AACH,aAAO,OAAO,MAAM,QAAQ,SAAS,MAAM,KAAK;AAAA,IAClD,KAAK;AACH,aAAO,OAAO,MAAM,KAAK,YAAY,MAAM,QAAQ;AAAA,IACrD,KAAK;AACH,aAAO,OAAO,MAAM,KAAK,UAAU,MAAM,QAAQ;AAAA,EACrD;AACF;AAGO,IAAM,aAAa;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA2BO,IAAM,QAAgD;AAAA,EAC3D,OAAO,EAAE,MAAM,QAAQ,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,EAAE;AAAA,EAC7D,KAAK,EAAE,MAAM,OAAO,KAAK,OAAO,QAAQ,MAAM;AAAA,EAC9C,OAAO,EAAE,MAAM,OAAO,KAAK,SAAS,QAAQ,MAAM;AAAA,EAClD,QAAQ,EAAE,MAAM,OAAO,KAAK,UAAU,QAAQ,MAAM;AAAA,EACpD,MAAM,EAAE,MAAM,OAAO,KAAK,QAAQ,QAAQ,MAAM;AAAA,EAChD,SAAS,EAAE,MAAM,OAAO,KAAK,WAAW,QAAQ,MAAM;AAAA,EACtD,MAAM,EAAE,MAAM,OAAO,KAAK,QAAQ,QAAQ,MAAM;AAAA,EAChD,OAAO,EAAE,MAAM,QAAQ,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,EAAE;AAAA,EAE3D,aAAa,EAAE,MAAM,QAAQ,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,EAAE;AAAA,EAChE,WAAW,EAAE,MAAM,OAAO,KAAK,OAAO,QAAQ,KAAK;AAAA,EACnD,aAAa,EAAE,MAAM,OAAO,KAAK,SAAS,QAAQ,KAAK;AAAA,EACvD,cAAc,EAAE,MAAM,OAAO,KAAK,UAAU,QAAQ,KAAK;AAAA,EACzD,YAAY,EAAE,MAAM,OAAO,KAAK,QAAQ,QAAQ,KAAK;AAAA,EACrD,eAAe,EAAE,MAAM,OAAO,KAAK,WAAW,QAAQ,KAAK;AAAA,EAC3D,YAAY,EAAE,MAAM,OAAO,KAAK,QAAQ,QAAQ,KAAK;AAAA,EACrD,aAAa,EAAE,MAAM,QAAQ,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,EAAE;AACtE;AAyBO,SAAS,WAAW,OAA+B;AACxD,QAAM,MAAM,OAAO,KAAK;AACxB,QAAM,QAAQ,CAAC;AAEf,aAAW,QAAQ,YAAY;AAC7B,UAAM,SAAS,MAAM,IAAI;AACzB,UAAM,IAAI,IACR,OAAO,SAAS,SACX,OAAO,SAAS,IAAI,gBAAgB,IAAI,SAAS,OAAO,GAAG,IAC5D,OAAO,SAAS,SACd,OAAO,KAAK,GAAG,IACf,OAAO,KAAK;AAAA,EACtB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,MACN,YAAY,IAAI,QAAQ;AAAA,MACxB,YAAY,IAAI,KAAK;AAAA,MACrB,QAAQ,IAAI,OAAO;AAAA,MACnB,qBAAqB,IAAI,QAAQ;AAAA,IACnC;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,342 @@
1
+ // src/theme/contrast.ts
2
+ var HEX = /^#([0-9a-f]{3,8})$/i;
3
+ var RGB_FN = /^rgba?\(\s*([0-9.]+)[\s,]+([0-9.]+)[\s,]+([0-9.]+)(?:[\s,/]+([0-9.]+))?\s*\)$/i;
4
+ function parseColor(value) {
5
+ const input = value.trim();
6
+ const hex = HEX.exec(input);
7
+ if (hex) {
8
+ const digits = hex[1];
9
+ const expand = (s) => Number.parseInt(s.length === 1 ? s + s : s, 16);
10
+ if (digits.length === 3 || digits.length === 4) {
11
+ return {
12
+ r: expand(digits[0]),
13
+ g: expand(digits[1]),
14
+ b: expand(digits[2]),
15
+ a: digits.length === 4 ? expand(digits[3]) / 255 : 1
16
+ };
17
+ }
18
+ if (digits.length === 6 || digits.length === 8) {
19
+ return {
20
+ r: expand(digits.slice(0, 2)),
21
+ g: expand(digits.slice(2, 4)),
22
+ b: expand(digits.slice(4, 6)),
23
+ a: digits.length === 8 ? expand(digits.slice(6, 8)) / 255 : 1
24
+ };
25
+ }
26
+ }
27
+ const fn = RGB_FN.exec(input);
28
+ if (fn) {
29
+ return {
30
+ r: Number(fn[1]),
31
+ g: Number(fn[2]),
32
+ b: Number(fn[3]),
33
+ a: fn[4] === void 0 ? 1 : Number(fn[4])
34
+ };
35
+ }
36
+ throw new Error(`Unparseable colour: ${value}`);
37
+ }
38
+ function composite(foreground, backdrop) {
39
+ if (foreground.a >= 1) return foreground;
40
+ const a = foreground.a;
41
+ return {
42
+ r: foreground.r * a + backdrop.r * (1 - a),
43
+ g: foreground.g * a + backdrop.g * (1 - a),
44
+ b: foreground.b * a + backdrop.b * (1 - a),
45
+ a: 1
46
+ };
47
+ }
48
+ function relativeLuminance(color) {
49
+ const channel = (value) => {
50
+ const c = value / 255;
51
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
52
+ };
53
+ return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
54
+ }
55
+ function contrastRatio(foreground, background) {
56
+ const bg = parseColor(background);
57
+ const fg = composite(parseColor(foreground), bg);
58
+ const lighter = Math.max(relativeLuminance(fg), relativeLuminance(bg));
59
+ const darker = Math.min(relativeLuminance(fg), relativeLuminance(bg));
60
+ return (lighter + 0.05) / (darker + 0.05);
61
+ }
62
+ function apcaContrast(foreground, background) {
63
+ const bg = parseColor(background);
64
+ const fg = composite(parseColor(foreground), bg);
65
+ const y = (c) => 0.2126729 * (c.r / 255) ** 2.4 + 0.7151522 * (c.g / 255) ** 2.4 + 0.072175 * (c.b / 255) ** 2.4;
66
+ const clamp = (lum) => lum > 0.022 ? lum : lum + (0.022 - lum) ** 1.414;
67
+ const text = clamp(y(fg));
68
+ const ground = clamp(y(bg));
69
+ if (Math.abs(ground - text) < 5e-4) return 0;
70
+ if (ground > text) {
71
+ const sapc2 = (ground ** 0.56 - text ** 0.57) * 1.14;
72
+ return sapc2 < 0.1 ? 0 : (sapc2 - 0.027) * 100;
73
+ }
74
+ const sapc = (ground ** 0.65 - text ** 0.62) * 1.14;
75
+ return sapc > -0.1 ? 0 : (sapc + 0.027) * 100;
76
+ }
77
+ var MAXIMUM_NEUTRAL_CHROMA = 0.045;
78
+ function oklabChroma(hex) {
79
+ const decode = (v) => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
80
+ const [r, g, b] = [1, 3, 5].map((i) => decode(Number.parseInt(hex.slice(i, i + 2), 16) / 255));
81
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
82
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
83
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
84
+ const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
85
+ const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
86
+ return Math.hypot(a, bb);
87
+ }
88
+ var PALETTE_HUES_ORDER = [
89
+ "red",
90
+ "orange",
91
+ "yellow",
92
+ "green",
93
+ "teal",
94
+ "cyan",
95
+ "blue",
96
+ "violet",
97
+ "magenta",
98
+ "pink"
99
+ ];
100
+ var MINIMUM_RATIO = {
101
+ text: 4.5,
102
+ /** `text.inverse` sits on an accent fill, not on a surface. */
103
+ textInverse: 4.5,
104
+ accent: 4.5,
105
+ intent: 4.5,
106
+ /**
107
+ * A Hue, above WCAG AA deliberately. An editor draws selection, current-line,
108
+ * find-match and diff backgrounds *behind* the same tokens, so a palette
109
+ * solved to exactly 4.5:1 has no room left to tint the ground. At 4.5 the
110
+ * light palette has one sRGB step of headroom; at 5.5 it has twenty-two.
111
+ * See `docs/palette-provenance.md` and #78.
112
+ */
113
+ palette: 5.5,
114
+ /**
115
+ * These numbers are the **web** Medium's, and that indexing is the point.
116
+ *
117
+ * ADR 0004 names this the asymmetry most likely to be got wrong later:
118
+ * *colour values do not vary by Medium, but the floors they must clear do.* A
119
+ * projected 1080p frame, a compression-damaged video and an unantialiased
120
+ * terminal cell are not the browser's legibility problem, and until now this
121
+ * was one set of numbers for every surface the system emits to.
122
+ *
123
+ * `MEDIA_DEFINITIONS[medium].contrastFloor` carries the per-Medium values;
124
+ * `auditContrast` takes a Medium and reads them. The constants here remain as
125
+ * the web Medium's, so nothing moves on merge and a caller that does not care
126
+ * about Media keeps working.
127
+ */
128
+ /** `bright` Hues carry emphasis, not body text, so AA is the right bar. */
129
+ paletteBright: 4.5,
130
+ borderStrong: 3,
131
+ borderDefault: 3,
132
+ borderSubtle: 1.4,
133
+ /**
134
+ * A modal's dialog against the scrimmed page behind it. Not an accessibility
135
+ * threshold — an assertion that the scrim does the one job it exists for.
136
+ */
137
+ overlaySeparation: 3,
138
+ /**
139
+ * A selection device — the accent `fill` behind a chosen tab, or the 4px
140
+ * accent `edge` beside it — against the surface it sits on. WCAG 1.4.11's
141
+ * non-text bar: the device is a UI component boundary, not text.
142
+ */
143
+ stateDevice: 3
144
+ };
145
+ function auditHueAgreement(ladder) {
146
+ const results = [];
147
+ for (const level of Object.keys(ladder)) {
148
+ const def = ladder[level];
149
+ const check = (role, value, ref) => {
150
+ if (ref === "neutral") {
151
+ const chroma = oklabChroma(value);
152
+ results.push({
153
+ level,
154
+ role,
155
+ declared: ref,
156
+ value,
157
+ expected: null,
158
+ passes: chroma <= MAXIMUM_NEUTRAL_CHROMA,
159
+ detail: `chroma ${chroma.toFixed(3)} (max ${MAXIMUM_NEUTRAL_CHROMA})`
160
+ });
161
+ return;
162
+ }
163
+ const expected = def.palette[ref];
164
+ results.push({
165
+ level,
166
+ role,
167
+ declared: ref,
168
+ value,
169
+ expected,
170
+ passes: value.toLowerCase() === expected.toLowerCase(),
171
+ detail: `palette.${ref} is ${expected}`
172
+ });
173
+ };
174
+ for (const [role, ref] of Object.entries(def.accentHue)) {
175
+ check(`accent.${role}`, def.accent[role], ref);
176
+ }
177
+ for (const [role, ref] of Object.entries(def.intentHue)) {
178
+ check(`intent.${role}`, def.intent[role], ref);
179
+ }
180
+ }
181
+ return results;
182
+ }
183
+ var WEB_FLOOR = {
184
+ role: MINIMUM_RATIO.text,
185
+ hue: MINIMUM_RATIO.palette,
186
+ hueBright: MINIMUM_RATIO.paletteBright
187
+ };
188
+ function auditContrast(ladder, floor = WEB_FLOOR) {
189
+ const results = [];
190
+ for (const [level, def] of Object.entries(ladder)) {
191
+ const grounds = [
192
+ ["surface.base", def.surface.base],
193
+ ["surface.raised", def.surface.raised],
194
+ ["surface.sunken", def.surface.sunken]
195
+ ];
196
+ const check = (pair, foreground, background, minimum) => {
197
+ const ratio = contrastRatio(foreground, background);
198
+ results.push({
199
+ level,
200
+ pair,
201
+ foreground,
202
+ background,
203
+ ratio,
204
+ apca: apcaContrast(foreground, background),
205
+ minimum,
206
+ passes: ratio >= minimum
207
+ });
208
+ };
209
+ for (const [groundName, ground] of grounds) {
210
+ for (const tone of ["primary", "secondary", "muted"]) {
211
+ check(`text.${tone} on ${groundName}`, def.text[tone], ground, floor.role);
212
+ }
213
+ for (const tone of ["primary", "secondary", "tertiary", "quiet"]) {
214
+ check(`accent.${tone} on ${groundName}`, def.accent[tone], ground, floor.role);
215
+ }
216
+ for (const tone of ["info", "success", "warning", "danger"]) {
217
+ check(`intent.${tone} on ${groundName}`, def.intent[tone], ground, floor.role);
218
+ }
219
+ for (const hue of PALETTE_HUES_ORDER) {
220
+ check(`palette.${hue} on ${groundName}`, def.palette[hue], ground, floor.hue);
221
+ check(
222
+ `palette.bright.${hue} on ${groundName}`,
223
+ def.paletteBright[hue],
224
+ ground,
225
+ floor.hueBright
226
+ );
227
+ }
228
+ check(`border.strong on ${groundName}`, def.border.strong, ground, MINIMUM_RATIO.borderStrong);
229
+ check(`border.default on ${groundName}`, def.border.default, ground, MINIMUM_RATIO.borderDefault);
230
+ check(`border.subtle on ${groundName}`, def.border.subtle, ground, MINIMUM_RATIO.borderSubtle);
231
+ }
232
+ for (const tone of ["primary", "secondary", "tertiary", "quiet"]) {
233
+ check(
234
+ `text.inverse on accent.${tone}`,
235
+ def.text.inverse,
236
+ def.accent[tone],
237
+ floor.role
238
+ );
239
+ }
240
+ for (const tone of ["info", "success", "warning", "danger"]) {
241
+ check(
242
+ `text.inverse on intent.${tone}`,
243
+ def.text.inverse,
244
+ def.intent[tone],
245
+ floor.role
246
+ );
247
+ }
248
+ check("select.option: text.primary on surface.raised", def.text.primary, def.surface.raised, floor.role);
249
+ check("select.option disabled: text.muted on surface.raised", def.text.muted, def.surface.raised, floor.role);
250
+ check("select.placeholder: text.muted on surface.base", def.text.muted, def.surface.base, floor.role);
251
+ const listAccents = [
252
+ ...["primary", "secondary", "tertiary", "quiet"].map(
253
+ (tone) => [`accent.${tone}`, def.accent[tone]]
254
+ ),
255
+ ...["info", "success", "warning", "danger"].map(
256
+ (tone) => [`intent.${tone}`, def.intent[tone]]
257
+ )
258
+ ];
259
+ for (const [name, fill] of listAccents) {
260
+ check(`select.option highlighted: text.inverse on ${name}`, def.text.inverse, fill, floor.role);
261
+ check(`select.option selected mark: ${name} on surface.raised`, fill, def.surface.raised, floor.role);
262
+ }
263
+ const scrimOverBase = composite(
264
+ parseColor(def.surface.overlay),
265
+ parseColor(def.surface.base)
266
+ );
267
+ const scrim = `rgb(${Math.round(scrimOverBase.r)}, ${Math.round(scrimOverBase.g)}, ${Math.round(scrimOverBase.b)})`;
268
+ const separation = Math.max(
269
+ contrastRatio(def.surface.raised, scrim),
270
+ contrastRatio(def.border.strong, scrim)
271
+ );
272
+ results.push({
273
+ level,
274
+ pair: "dialog (fill or border) against scrimmed page",
275
+ foreground: `${def.surface.raised} / ${def.border.strong}`,
276
+ background: scrim,
277
+ ratio: separation,
278
+ apca: null,
279
+ minimum: MINIMUM_RATIO.overlaySeparation,
280
+ passes: separation >= MINIMUM_RATIO.overlaySeparation
281
+ });
282
+ }
283
+ return results;
284
+ }
285
+ function auditTextHierarchy(ladder) {
286
+ const steps = [];
287
+ for (const [level, def] of Object.entries(ladder)) {
288
+ for (const ground of ["base", "raised", "sunken"]) {
289
+ const lc = (tone) => Math.abs(apcaContrast(def.text[tone], def.surface[ground]));
290
+ const tones = { primary: lc("primary"), secondary: lc("secondary"), muted: lc("muted") };
291
+ steps.push({ level, ground: `surface.${ground}`, lc: tones, mutedStep: tones.secondary - tones.muted });
292
+ }
293
+ }
294
+ return steps;
295
+ }
296
+ function auditSelectionDevices(ladder) {
297
+ const results = [];
298
+ for (const [level, def] of Object.entries(ladder)) {
299
+ const grounds = [
300
+ ["surface.base", def.surface.base],
301
+ ["surface.raised", def.surface.raised]
302
+ ];
303
+ const check = (device, pair, foreground, background) => {
304
+ const ratio = contrastRatio(foreground, background);
305
+ results.push({
306
+ level,
307
+ device,
308
+ pair,
309
+ foreground,
310
+ background,
311
+ ratio,
312
+ minimum: MINIMUM_RATIO.stateDevice,
313
+ passes: ratio >= MINIMUM_RATIO.stateDevice
314
+ });
315
+ };
316
+ for (const [groundName, ground] of grounds) {
317
+ for (const tone of ["primary", "secondary", "tertiary"]) {
318
+ check("fill", `accent.${tone} fill on ${groundName}`, def.accent[tone], ground);
319
+ check("edge", `accent.${tone} edge on ${groundName}`, def.accent[tone], ground);
320
+ }
321
+ }
322
+ check("surface pair", "surface.raised against surface.base", def.surface.raised, def.surface.base);
323
+ check("surface pair", "surface.sunken against surface.base", def.surface.sunken, def.surface.base);
324
+ check("surface pair", "surface.sunken against surface.raised", def.surface.sunken, def.surface.raised);
325
+ }
326
+ return results;
327
+ }
328
+ export {
329
+ MAXIMUM_NEUTRAL_CHROMA,
330
+ MINIMUM_RATIO,
331
+ WEB_FLOOR,
332
+ apcaContrast,
333
+ auditContrast,
334
+ auditHueAgreement,
335
+ auditSelectionDevices,
336
+ auditTextHierarchy,
337
+ composite,
338
+ contrastRatio,
339
+ parseColor,
340
+ relativeLuminance
341
+ };
342
+ //# sourceMappingURL=contrast.js.map