@awarebydefault/display-case 1.0.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.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +309 -0
  3. package/display-case.prompt.md +64 -0
  4. package/docs/ai-agents.md +126 -0
  5. package/docs/cli.md +99 -0
  6. package/docs/configuration.md +410 -0
  7. package/docs/documentation-panel.md +50 -0
  8. package/docs/examples/README.md +14 -0
  9. package/docs/examples/multi-variant.case.tsx +30 -0
  10. package/docs/examples/plain.case.tsx +22 -0
  11. package/docs/examples/tweak-control.placard.md +80 -0
  12. package/docs/examples/tweaks.case.tsx +39 -0
  13. package/docs/hierarchy.md +59 -0
  14. package/docs/quick-start.md +78 -0
  15. package/docs/style-engines.md +180 -0
  16. package/docs/testing.md +245 -0
  17. package/docs/theming.md +97 -0
  18. package/docs/tweaks.md +75 -0
  19. package/docs/writing-cases.md +144 -0
  20. package/docs/writing-placard-docs.md +194 -0
  21. package/package.json +113 -0
  22. package/skills/display-case-author-case/README.md +20 -0
  23. package/skills/display-case-author-case/SKILL.md +40 -0
  24. package/skills/display-case-author-placard-doc/README.md +24 -0
  25. package/skills/display-case-author-placard-doc/SKILL.md +65 -0
  26. package/skills/display-case-review/README.md +19 -0
  27. package/skills/display-case-review/SKILL.md +30 -0
  28. package/skills/display-case-snapshot/README.md +20 -0
  29. package/skills/display-case-snapshot/SKILL.md +29 -0
  30. package/src/checks/a11y-scanner.test.ts +240 -0
  31. package/src/checks/a11y-scanner.ts +410 -0
  32. package/src/checks/check-text.test.ts +53 -0
  33. package/src/checks/check-text.ts +78 -0
  34. package/src/checks/check.test.ts +194 -0
  35. package/src/checks/check.ts +473 -0
  36. package/src/checks/providers/pixelmatch-diff.test.ts +79 -0
  37. package/src/checks/providers/pixelmatch-diff.ts +30 -0
  38. package/src/checks/providers/playwright-driver.ts +104 -0
  39. package/src/checks/ssr-check.test.ts +73 -0
  40. package/src/checks/ssr-check.ts +96 -0
  41. package/src/checks/structure-check.cross-package.test.ts +165 -0
  42. package/src/checks/structure-check.test.ts +651 -0
  43. package/src/checks/structure-check.ts +988 -0
  44. package/src/checks/tokens-check.test.ts +159 -0
  45. package/src/checks/tokens-check.ts +162 -0
  46. package/src/cli.ts +218 -0
  47. package/src/commands/agents.test.ts +24 -0
  48. package/src/commands/agents.ts +28 -0
  49. package/src/commands/init-run.test.ts +123 -0
  50. package/src/commands/init.test.ts +63 -0
  51. package/src/commands/init.ts +412 -0
  52. package/src/commands/publish.test.ts +210 -0
  53. package/src/commands/publish.ts +292 -0
  54. package/src/core/affected.test.ts +99 -0
  55. package/src/core/affected.ts +144 -0
  56. package/src/core/catalog.test.ts +152 -0
  57. package/src/core/catalog.ts +92 -0
  58. package/src/core/discovery.test.ts +184 -0
  59. package/src/core/discovery.ts +250 -0
  60. package/src/core/manifest.ts +41 -0
  61. package/src/core/mdx-lite/__fixtures__/box-stub.tsx +7 -0
  62. package/src/core/mdx-lite/index.ts +393 -0
  63. package/src/core/mdx-lite/mdx-lite.test.ts +345 -0
  64. package/src/core/mdx-plugin.test.ts +60 -0
  65. package/src/core/mdx-plugin.ts +30 -0
  66. package/src/flow-step.test-d.ts +39 -0
  67. package/src/index.test.ts +100 -0
  68. package/src/index.ts +564 -0
  69. package/src/render/collect-styles.emotion.test.tsx +114 -0
  70. package/src/render/collect-styles.test.tsx +72 -0
  71. package/src/render/collect-styles.ts +33 -0
  72. package/src/render/documents.test.ts +184 -0
  73. package/src/render/documents.ts +88 -0
  74. package/src/render/render-node.test.tsx +160 -0
  75. package/src/render/render-node.tsx +133 -0
  76. package/src/render/ssr-primer.test.tsx +25 -0
  77. package/src/render/ssr-primer.tsx +54 -0
  78. package/src/render/ssr-render.test.tsx +142 -0
  79. package/src/render/ssr-render.tsx +63 -0
  80. package/src/render/ssr-shell.test.tsx +57 -0
  81. package/src/render/ssr-shell.tsx +54 -0
  82. package/src/server/prod-server.ts +237 -0
  83. package/src/server/server.test.ts +117 -0
  84. package/src/server/server.ts +1039 -0
  85. package/src/style-engine.test-d.ts +37 -0
  86. package/src/testing/test-helpers.ts +27 -0
  87. package/src/types/pixelmatch.d.ts +12 -0
  88. package/src/ui/browser-entry.tsx +51 -0
  89. package/src/ui/chrome.css +485 -0
  90. package/src/ui/design-system/README.md +88 -0
  91. package/src/ui/design-system/components/controls/Button.case.tsx +52 -0
  92. package/src/ui/design-system/components/controls/Button.css +89 -0
  93. package/src/ui/design-system/components/controls/Button.placard.md +14 -0
  94. package/src/ui/design-system/components/controls/Button.test.tsx +45 -0
  95. package/src/ui/design-system/components/controls/Button.tsx +41 -0
  96. package/src/ui/design-system/components/controls/IconButton.case.tsx +52 -0
  97. package/src/ui/design-system/components/controls/IconButton.css +67 -0
  98. package/src/ui/design-system/components/controls/IconButton.placard.md +13 -0
  99. package/src/ui/design-system/components/controls/IconButton.test.tsx +39 -0
  100. package/src/ui/design-system/components/controls/IconButton.tsx +47 -0
  101. package/src/ui/design-system/components/controls/Input.case.tsx +50 -0
  102. package/src/ui/design-system/components/controls/Input.css +52 -0
  103. package/src/ui/design-system/components/controls/Input.placard.md +12 -0
  104. package/src/ui/design-system/components/controls/Input.test.tsx +43 -0
  105. package/src/ui/design-system/components/controls/Input.tsx +45 -0
  106. package/src/ui/design-system/components/controls/Select.case.tsx +48 -0
  107. package/src/ui/design-system/components/controls/Select.css +44 -0
  108. package/src/ui/design-system/components/controls/Select.placard.md +15 -0
  109. package/src/ui/design-system/components/controls/Select.test.tsx +57 -0
  110. package/src/ui/design-system/components/controls/Select.tsx +58 -0
  111. package/src/ui/design-system/components/controls/SelectMenu.case.tsx +100 -0
  112. package/src/ui/design-system/components/controls/SelectMenu.css +72 -0
  113. package/src/ui/design-system/components/controls/SelectMenu.placard.md +18 -0
  114. package/src/ui/design-system/components/controls/SelectMenu.test.tsx +66 -0
  115. package/src/ui/design-system/components/controls/SelectMenu.tsx +377 -0
  116. package/src/ui/design-system/components/index.ts +66 -0
  117. package/src/ui/design-system/components/primer-specimen/ColorRamp.case.tsx +44 -0
  118. package/src/ui/design-system/components/primer-specimen/ColorRamp.placard.md +15 -0
  119. package/src/ui/design-system/components/primer-specimen/ColorRamp.tsx +51 -0
  120. package/src/ui/design-system/components/primer-specimen/DefinitionList.case.tsx +38 -0
  121. package/src/ui/design-system/components/primer-specimen/DefinitionList.placard.md +15 -0
  122. package/src/ui/design-system/components/primer-specimen/DefinitionList.tsx +41 -0
  123. package/src/ui/design-system/components/primer-specimen/FontFamilies.case.tsx +24 -0
  124. package/src/ui/design-system/components/primer-specimen/FontFamilies.placard.md +12 -0
  125. package/src/ui/design-system/components/primer-specimen/FontFamilies.tsx +41 -0
  126. package/src/ui/design-system/components/primer-specimen/GlyphGrid.case.tsx +27 -0
  127. package/src/ui/design-system/components/primer-specimen/GlyphGrid.placard.md +13 -0
  128. package/src/ui/design-system/components/primer-specimen/GlyphGrid.tsx +34 -0
  129. package/src/ui/design-system/components/primer-specimen/LayoutMock.case.tsx +36 -0
  130. package/src/ui/design-system/components/primer-specimen/LayoutMock.placard.md +7 -0
  131. package/src/ui/design-system/components/primer-specimen/LayoutMock.tsx +36 -0
  132. package/src/ui/design-system/components/primer-specimen/SpacingScale.case.tsx +20 -0
  133. package/src/ui/design-system/components/primer-specimen/SpacingScale.placard.md +12 -0
  134. package/src/ui/design-system/components/primer-specimen/SpacingScale.tsx +33 -0
  135. package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.case.tsx +56 -0
  136. package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.placard.md +17 -0
  137. package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.tsx +45 -0
  138. package/src/ui/design-system/components/primer-specimen/StatusList.case.tsx +17 -0
  139. package/src/ui/design-system/components/primer-specimen/StatusList.placard.md +16 -0
  140. package/src/ui/design-system/components/primer-specimen/StatusList.tsx +39 -0
  141. package/src/ui/design-system/components/primer-specimen/SwatchGrid.case.tsx +26 -0
  142. package/src/ui/design-system/components/primer-specimen/SwatchGrid.placard.md +15 -0
  143. package/src/ui/design-system/components/primer-specimen/SwatchGrid.tsx +42 -0
  144. package/src/ui/design-system/components/primer-specimen/TypeScale.case.tsx +23 -0
  145. package/src/ui/design-system/components/primer-specimen/TypeScale.placard.md +14 -0
  146. package/src/ui/design-system/components/primer-specimen/TypeScale.tsx +34 -0
  147. package/src/ui/design-system/components/primer-specimen/WeightSpecimen.case.tsx +28 -0
  148. package/src/ui/design-system/components/primer-specimen/WeightSpecimen.placard.md +15 -0
  149. package/src/ui/design-system/components/primer-specimen/WeightSpecimen.tsx +46 -0
  150. package/src/ui/design-system/components/primer-specimen/index.ts +31 -0
  151. package/src/ui/design-system/components/primer-specimen/styles.css +476 -0
  152. package/src/ui/design-system/components/shell/A11yPage.case.tsx +237 -0
  153. package/src/ui/design-system/components/shell/A11yPage.placard.md +15 -0
  154. package/src/ui/design-system/components/shell/CaseTemplate.case.tsx +32 -0
  155. package/src/ui/design-system/components/shell/CaseTemplate.placard.md +5 -0
  156. package/src/ui/design-system/components/shell/CasesPage.case.tsx +141 -0
  157. package/src/ui/design-system/components/shell/CasesPage.placard.md +12 -0
  158. package/src/ui/design-system/components/shell/PrimerPage.case.tsx +22 -0
  159. package/src/ui/design-system/components/shell/PrimerPage.placard.md +3 -0
  160. package/src/ui/design-system/components/shell/PrimerTemplate.case.tsx +22 -0
  161. package/src/ui/design-system/components/shell/PrimerTemplate.placard.md +5 -0
  162. package/src/ui/design-system/components/shell/ShellView.case.tsx +57 -0
  163. package/src/ui/design-system/components/shell/ShellView.placard.md +5 -0
  164. package/src/ui/design-system/components/shell/ShellView.tsx +678 -0
  165. package/src/ui/design-system/components/shell/shell-fixtures.tsx +727 -0
  166. package/src/ui/design-system/components/showcase/A11yBadge.case.tsx +46 -0
  167. package/src/ui/design-system/components/showcase/A11yBadge.css +27 -0
  168. package/src/ui/design-system/components/showcase/A11yBadge.placard.md +11 -0
  169. package/src/ui/design-system/components/showcase/A11yBadge.test.tsx +31 -0
  170. package/src/ui/design-system/components/showcase/A11yBadge.tsx +41 -0
  171. package/src/ui/design-system/components/showcase/A11yPanel.case.tsx +121 -0
  172. package/src/ui/design-system/components/showcase/A11yPanel.css +198 -0
  173. package/src/ui/design-system/components/showcase/A11yPanel.placard.md +19 -0
  174. package/src/ui/design-system/components/showcase/A11yPanel.test.tsx +81 -0
  175. package/src/ui/design-system/components/showcase/A11yPanel.tsx +144 -0
  176. package/src/ui/design-system/components/showcase/Chip.case.tsx +48 -0
  177. package/src/ui/design-system/components/showcase/Chip.css +51 -0
  178. package/src/ui/design-system/components/showcase/Chip.placard.md +13 -0
  179. package/src/ui/design-system/components/showcase/Chip.test.tsx +46 -0
  180. package/src/ui/design-system/components/showcase/Chip.tsx +54 -0
  181. package/src/ui/design-system/components/showcase/Eyebrow.case.tsx +30 -0
  182. package/src/ui/design-system/components/showcase/Eyebrow.css +16 -0
  183. package/src/ui/design-system/components/showcase/Eyebrow.placard.md +10 -0
  184. package/src/ui/design-system/components/showcase/Eyebrow.test.tsx +38 -0
  185. package/src/ui/design-system/components/showcase/Eyebrow.tsx +29 -0
  186. package/src/ui/design-system/components/showcase/FlowNav.case.tsx +35 -0
  187. package/src/ui/design-system/components/showcase/FlowNav.css +29 -0
  188. package/src/ui/design-system/components/showcase/FlowNav.placard.md +13 -0
  189. package/src/ui/design-system/components/showcase/FlowNav.test.tsx +48 -0
  190. package/src/ui/design-system/components/showcase/FlowNav.tsx +58 -0
  191. package/src/ui/design-system/components/showcase/ImpactTag.case.tsx +19 -0
  192. package/src/ui/design-system/components/showcase/ImpactTag.css +36 -0
  193. package/src/ui/design-system/components/showcase/ImpactTag.placard.md +14 -0
  194. package/src/ui/design-system/components/showcase/ImpactTag.test.tsx +40 -0
  195. package/src/ui/design-system/components/showcase/ImpactTag.tsx +35 -0
  196. package/src/ui/design-system/components/showcase/NavItem.case.tsx +86 -0
  197. package/src/ui/design-system/components/showcase/NavItem.css +111 -0
  198. package/src/ui/design-system/components/showcase/NavItem.placard.md +13 -0
  199. package/src/ui/design-system/components/showcase/NavItem.test.tsx +65 -0
  200. package/src/ui/design-system/components/showcase/NavItem.tsx +95 -0
  201. package/src/ui/design-system/components/showcase/RenderAddress.case.tsx +21 -0
  202. package/src/ui/design-system/components/showcase/RenderAddress.css +35 -0
  203. package/src/ui/design-system/components/showcase/RenderAddress.placard.md +7 -0
  204. package/src/ui/design-system/components/showcase/RenderAddress.test.tsx +26 -0
  205. package/src/ui/design-system/components/showcase/RenderAddress.tsx +43 -0
  206. package/src/ui/design-system/components/showcase/SegmentedToggle.case.tsx +84 -0
  207. package/src/ui/design-system/components/showcase/SegmentedToggle.css +61 -0
  208. package/src/ui/design-system/components/showcase/SegmentedToggle.placard.md +21 -0
  209. package/src/ui/design-system/components/showcase/SegmentedToggle.test.tsx +81 -0
  210. package/src/ui/design-system/components/showcase/SegmentedToggle.tsx +75 -0
  211. package/src/ui/design-system/components/showcase/Sidebar.case.tsx +67 -0
  212. package/src/ui/design-system/components/showcase/Sidebar.css +6 -0
  213. package/src/ui/design-system/components/showcase/Sidebar.placard.md +14 -0
  214. package/src/ui/design-system/components/showcase/Sidebar.test.tsx +32 -0
  215. package/src/ui/design-system/components/showcase/Sidebar.tsx +30 -0
  216. package/src/ui/design-system/components/showcase/Stage.case.tsx +51 -0
  217. package/src/ui/design-system/components/showcase/Stage.css +91 -0
  218. package/src/ui/design-system/components/showcase/Stage.placard.md +15 -0
  219. package/src/ui/design-system/components/showcase/Stage.test.tsx +84 -0
  220. package/src/ui/design-system/components/showcase/Stage.tsx +97 -0
  221. package/src/ui/design-system/components/showcase/TweaksPanel.case.tsx +81 -0
  222. package/src/ui/design-system/components/showcase/TweaksPanel.css +169 -0
  223. package/src/ui/design-system/components/showcase/TweaksPanel.placard.md +20 -0
  224. package/src/ui/design-system/components/showcase/TweaksPanel.tsx +230 -0
  225. package/src/ui/design-system/components/showcase/Wordmark.case.tsx +42 -0
  226. package/src/ui/design-system/components/showcase/Wordmark.css +31 -0
  227. package/src/ui/design-system/components/showcase/Wordmark.placard.md +10 -0
  228. package/src/ui/design-system/components/showcase/Wordmark.test.tsx +22 -0
  229. package/src/ui/design-system/components/showcase/Wordmark.tsx +22 -0
  230. package/src/ui/design-system/primer-specimens/brand.tsx +26 -0
  231. package/src/ui/design-system/primer-specimens/colors.tsx +83 -0
  232. package/src/ui/design-system/primer-specimens/components.tsx +308 -0
  233. package/src/ui/design-system/primer-specimens/foundations.tsx +71 -0
  234. package/src/ui/design-system/primer-specimens/index.ts +25 -0
  235. package/src/ui/design-system/primer-specimens/showcase.tsx +68 -0
  236. package/src/ui/design-system/primer-specimens/spacing.tsx +101 -0
  237. package/src/ui/design-system/primer-specimens/type.tsx +75 -0
  238. package/src/ui/design-system/primer.mdx +236 -0
  239. package/src/ui/design-system/styles.css +14 -0
  240. package/src/ui/design-system/tokens/colors.css +172 -0
  241. package/src/ui/design-system/tokens/fonts.css +18 -0
  242. package/src/ui/design-system/tokens/spacing.css +48 -0
  243. package/src/ui/design-system/tokens/typography.css +49 -0
  244. package/src/ui/markdown.test.tsx +54 -0
  245. package/src/ui/markdown.tsx +19 -0
  246. package/src/ui/primer-mount.tsx +76 -0
  247. package/src/ui/primer.css +175 -0
  248. package/src/ui/primer.tsx +277 -0
  249. package/src/ui/render-mount.tsx +284 -0
  250. package/src/ui/shell-core.test.ts +340 -0
  251. package/src/ui/shell-core.ts +295 -0
  252. package/src/ui/shell.tsx +60 -0
  253. package/src/ui/test-ids.ts +53 -0
  254. package/src/ui/use-shell.ts +1230 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Interactive showcase specimens for Display Case's own Primer. These wrap
3
+ * stateful behaviour the MDX can't express inline: the FlowNav stepper (tracks
4
+ * the active step) and the TweaksPanel demo.
5
+ *
6
+ * The render-address bar is the reusable {@link RenderAddress} showcase
7
+ * component (under `components/showcase/`); this module just supplies Display
8
+ * Case's default URL.
9
+ */
10
+ import { useState } from 'react'
11
+ import {
12
+ FlowNav,
13
+ Input,
14
+ Select,
15
+ RenderAddress as SpecimenRenderAddress,
16
+ TweaksPanel,
17
+ } from '../components'
18
+
19
+ const FLOW_STEPS = [
20
+ { id: 'Request link', label: 'Request link' },
21
+ { id: 'Check email', label: 'Check email' },
22
+ { id: 'Signed in', label: 'Signed in' },
23
+ ]
24
+
25
+ export function FlowNavDemo() {
26
+ const [active, setActive] = useState('Check email')
27
+ return (
28
+ <div className="dcpl-block">
29
+ <FlowNav steps={FLOW_STEPS} activeId={active} onSelect={setActive} />
30
+ </div>
31
+ )
32
+ }
33
+
34
+ export function TweaksPanelDemo() {
35
+ const items = [
36
+ {
37
+ label: 'label',
38
+ control: (
39
+ <Input size="sm" defaultValue="Snapshot" style={{ width: '8rem' }} />
40
+ ),
41
+ },
42
+ {
43
+ label: 'variant',
44
+ control: <Select size="sm" options={['ghost', 'accent']} />,
45
+ },
46
+ {
47
+ label: 'disabled',
48
+ control: <input type="checkbox" aria-label="disabled" />,
49
+ },
50
+ ]
51
+ return (
52
+ <div style={{ width: '100%', maxWidth: '24rem', margin: '0 auto' }}>
53
+ <TweaksPanel
54
+ url="?t.variant=accent"
55
+ items={items}
56
+ onToggleMode={() => {}}
57
+ />
58
+ </div>
59
+ )
60
+ }
61
+
62
+ export function RenderAddress({
63
+ url = '/render/button/playground?theme=light&t.variant=accent',
64
+ }: {
65
+ url?: string
66
+ }) {
67
+ return <SpecimenRenderAddress url={url} />
68
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Spacing specimens for Display Case's own Primer — the tight rem-based spacing
3
+ * scale, the modest corner radii, and the flat, border-led elevation model
4
+ * (shadow is reserved for genuinely floating layers).
5
+ *
6
+ * Thin wrappers over the reusable {@link SpacingScale}/{@link SpecimenBoxRow}
7
+ * primitives (under `components/primer-specimen/`); the Display-Case-specific
8
+ * data lives here.
9
+ */
10
+ import {
11
+ type BoxSpec,
12
+ type SpaceStep,
13
+ SpecimenBoxRow,
14
+ SpacingScale as SpecimenSpacingScale,
15
+ } from '../components/primer-specimen'
16
+
17
+ const SPACE: SpaceStep[] = [
18
+ { token: 'space-1', value: '2px', width: 2 },
19
+ { token: 'space-2', value: '4px', width: 4 },
20
+ { token: 'space-3', value: '6px', width: 6 },
21
+ { token: 'space-4', value: '8px', width: 8 },
22
+ { token: 'space-6', value: '12px', width: 12 },
23
+ { token: 'space-8', value: '16px', width: 16 },
24
+ { token: 'space-10', value: '20px', width: 20 },
25
+ { token: 'space-16', value: '32px', width: 32 },
26
+ ]
27
+
28
+ const RADII: BoxSpec[] = [
29
+ { label: 'none', note: '0', boxStyle: { borderRadius: '0' } },
30
+ {
31
+ label: 'sm',
32
+ note: '5px · controls',
33
+ boxStyle: { borderRadius: 'var(--dc-radius-sm)' },
34
+ },
35
+ {
36
+ label: 'md',
37
+ note: '8px · panels',
38
+ boxStyle: { borderRadius: 'var(--dc-radius-md)' },
39
+ },
40
+ {
41
+ label: 'lg',
42
+ note: '12px · overlays',
43
+ boxStyle: { borderRadius: 'var(--dc-radius-lg)' },
44
+ },
45
+ {
46
+ label: 'full',
47
+ note: 'pills · dots',
48
+ boxStyle: { borderRadius: 'var(--dc-radius-full)', width: '48px' },
49
+ },
50
+ ]
51
+
52
+ // The elevation boxes share a wider surface-on-shadow shape; only the border /
53
+ // shadow differs per item.
54
+ const elevBox = (extra: BoxSpec['boxStyle']): BoxSpec['boxStyle'] => ({
55
+ width: '134px',
56
+ height: '56px',
57
+ background: 'var(--dc-surface)',
58
+ borderRadius: 'var(--dc-radius-md)',
59
+ ...extra,
60
+ })
61
+
62
+ const ELEVATION: BoxSpec[] = [
63
+ {
64
+ note: 'border-line',
65
+ content: '1px border',
66
+ boxStyle: elevBox({ border: 'var(--dc-border-line)' }),
67
+ },
68
+ {
69
+ note: 'border-strong',
70
+ content: 'strong',
71
+ boxStyle: elevBox({ border: '1px solid var(--dc-border-strong)' }),
72
+ },
73
+ {
74
+ note: 'rare — hairline lift',
75
+ content: 'shadow-sm',
76
+ boxStyle: elevBox({
77
+ border: 'var(--dc-border-line)',
78
+ boxShadow: 'var(--dc-shadow-sm)',
79
+ }),
80
+ },
81
+ {
82
+ note: 'menus / popovers only',
83
+ content: 'overlay',
84
+ boxStyle: elevBox({
85
+ border: 'var(--dc-border-line)',
86
+ boxShadow: 'var(--dc-shadow-overlay)',
87
+ }),
88
+ },
89
+ ]
90
+
91
+ export function SpacingScale() {
92
+ return <SpecimenSpacingScale steps={SPACE} />
93
+ }
94
+
95
+ export function RadiusRow() {
96
+ return <SpecimenBoxRow items={RADII} />
97
+ }
98
+
99
+ export function ElevationRow() {
100
+ return <SpecimenBoxRow items={ELEVATION} />
101
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Type specimens for Display Case's own Primer — the two-family pairing (Hanken
3
+ * Grotesk for chrome, JetBrains Mono for developer texture), the dense px-based
4
+ * size scale, and the three weights plus the signature uppercase eyebrow label.
5
+ *
6
+ * Thin wrappers over the reusable {@link FontFamilies}/{@link TypeScale}/
7
+ * {@link WeightSpecimen} primitives (under `components/primer-specimen/`); the
8
+ * Display-Case-specific data lives here.
9
+ */
10
+ import { Eyebrow } from '../components'
11
+ import {
12
+ type FontFamily,
13
+ FontFamilies as SpecimenFontFamilies,
14
+ TypeScale as SpecimenTypeScale,
15
+ type TypeStep,
16
+ type WeightSpec,
17
+ WeightSpecimen,
18
+ } from '../components/primer-specimen'
19
+
20
+ const FAMILIES: FontFamily[] = [
21
+ {
22
+ tag: 'Sans · UI',
23
+ sample: 'Display Case shows the work, not itself',
24
+ note: 'Hanken Grotesk, ui-sans-serif, system-ui, "Segoe UI", Roboto…',
25
+ },
26
+ {
27
+ tag: 'Mono · Code',
28
+ sample: '/render/<component>/<case>?theme=dark',
29
+ note: 'JetBrains Mono, ui-monospace, "SF Mono", Menlo…',
30
+ mono: true,
31
+ },
32
+ ]
33
+
34
+ const SCALE: TypeStep[] = [
35
+ { tag: 'xl · 28', size: '28px', sample: 'Doc heading' },
36
+ { tag: 'lg · 20', size: '20px', sample: 'Section title' },
37
+ { tag: 'md · 16', size: '16px', sample: 'Icon glyph / emphasis' },
38
+ {
39
+ tag: 'base · 14',
40
+ size: '14px',
41
+ sample: 'Chrome baseline — nav, buttons, body',
42
+ },
43
+ { tag: 'sm · 12', size: '12px', sample: 'Secondary — zoom %, captions' },
44
+ { tag: 'xs · 11', size: '11px', sample: 'Eyebrow labels, dimension hints' },
45
+ ]
46
+
47
+ const WEIGHTS: WeightSpec[] = [
48
+ { weight: 400, name: 'Normal', role: 'body' },
49
+ { weight: 500, name: 'Medium', role: 'active' },
50
+ { weight: 600, name: 'Semibold', role: 'titles' },
51
+ ]
52
+
53
+ export function FontFamilies() {
54
+ return <SpecimenFontFamilies families={FAMILIES} />
55
+ }
56
+
57
+ export function TypeScale() {
58
+ return <SpecimenTypeScale steps={SCALE} />
59
+ }
60
+
61
+ export function Weights() {
62
+ return (
63
+ <WeightSpecimen
64
+ weights={WEIGHTS}
65
+ footer={
66
+ <>
67
+ <div className="dcpl-divider" />
68
+ <Eyebrow>
69
+ Components · group label · 11px / 500 / 0.08em uppercase
70
+ </Eyebrow>
71
+ </>
72
+ }
73
+ />
74
+ )
75
+ }
@@ -0,0 +1,236 @@
1
+ import { Button, Eyebrow, Stage, Wordmark } from './components'
2
+ import { MarigoldRamp, PaperRamp, SemanticSwatches, StatusHues, DefList, StatesRow, LayoutMock, FontFamilies, TypeScale, Weights, SpacingScale, RadiusRow, ElevationRow, Glyphs, RenderAddress, ControlsOverview, ShowcaseOverview, PrimerOverview } from './primer-specimens'
3
+
4
+ <Eyebrow tone="accent">Primer</Eyebrow>
5
+
6
+ # Design System
7
+
8
+ Display Case is **chrome that defers to the content**. Its whole job is to frame
9
+ someone else's component — the exhibit — so the showcase itself stays quiet,
10
+ warm, and legible. It's Bun-native, AI-forward, and a lightweight alternative to
11
+ Storybook. This page is the **Primer**: long-form wall text with embedded live
12
+ specimens, authored in MDX and dogfooding the design system's own components.
13
+
14
+ <Display
15
+ title="Wordmark"
16
+ subtitle="Type-only lockup — brackets frame the name like a case">
17
+ <Wordmark style={{ fontSize: '0.875rem' }}>Display Case</Wordmark>
18
+ <Wordmark style={{ fontSize: '1.5rem' }}>Display Case</Wordmark>
19
+ <Wordmark style={{ fontSize: '2.25rem' }}>Display Case</Wordmark>
20
+ </Display>
21
+
22
+ Three ideas shape every decision here. **Defer to the content** — the chrome is
23
+ small, warm, and recessive; the component on the stage owns the visual weight.
24
+ **Have a little charm** — paper neutrals, a single marigold accent, a bracketed
25
+ wordmark, soft corner ticks; crafted, not clinical. **Be legible to machines** —
26
+ deterministic render URLs, a manifest-first mindset, grouped addressable controls.
27
+
28
+ ## Content fundamentals
29
+
30
+ How Display Case writes. The vibe is a calm museum primer, not a marketing
31
+ page — quietly opinionated, never shouting, never decorating the copy.
32
+
33
+ <Display
34
+ title="Voice & mechanics"
35
+ subtitle="The rules every label, button, and line of copy follows"
36
+ flush>
37
+ <DefList />
38
+ </Display>
39
+
40
+ ## Visual foundations
41
+
42
+ The "Vitrine" identity — warm, editorial, flat, and quiet. Detailed specimens
43
+ for colour, type, and spacing follow in their own sections; these are the
44
+ principles that bind them.
45
+
46
+ **Elevation** is flat by default — the only thing that lifts is a genuinely
47
+ floating layer (a menu or popover). Everything else is border-led; there are no
48
+ card shadows. **Backgrounds** carry no gradients, photos, or patterns; the one
49
+ permitted texture is the faint dotted backdrop behind an exhibit, and the corner
50
+ ticks are the only decorative flourish. **Motion** is quiet and quick — around
51
+ 120ms on a gentle ease, no bounces or loops. **Interaction states**: hover is a
52
+ warm fill wash, pressed deepens it with a half-pixel nudge, **selected is
53
+ marigold**, focus draws a 2px marigold ring, and disabled drops to ~45% opacity.
54
+
55
+ <Display
56
+ title="States"
57
+ subtitle="Hover · selected (marigold) · focus · disabled">
58
+ <StatesRow />
59
+ </Display>
60
+
61
+ <Display
62
+ title="Layout"
63
+ subtitle="A fixed three-region grid — header, sidebar, main">
64
+ <LayoutMock />
65
+ </Display>
66
+
67
+ ## Colours
68
+
69
+ Warm paper neutrals, not cold grey. The canvas is ivory, the sidebar a touch
70
+ warmer, text a warm near-black ink — never `#000`. One colour does all the
71
+ emphatic work: **marigold** marks the active case, focus rings, and links.
72
+ Restraint is the point — a second accent would compete with the exhibit.
73
+
74
+ Warmth never costs legibility — every text and accent pairing clears **WCAG AA**
75
+ in both themes. The accent takes the most tuning: small marigold text on
76
+ near-white paper is hard-capped by the 4.5∶1 rule, so the brand token sits at
77
+ **`#a8570a`** — the brightest marigold that still clears AA *both* as a label on
78
+ the sidebar fill and under the white text of an accent button. (The ramp's
79
+ brighter `#c2690a` reads at only ~3.5 as text, so it stays a fill/decoration
80
+ hue.) The quiet "subtle" text tier and the status hues are likewise nudged per
81
+ theme to stay readable on ivory and on charcoal.
82
+
83
+ <Display
84
+ title="Accent — marigold"
85
+ subtitle="The brand token is #a8570a (AA-tuned); the ramp's #c2690a anchors the family">
86
+ <MarigoldRamp />
87
+ </Display>
88
+
89
+ <Display
90
+ title="Paper ramp"
91
+ subtitle="Warm taupe-undertoned neutrals — surfaces, text, borders">
92
+ <PaperRamp />
93
+ </Display>
94
+
95
+ <Display
96
+ title="Semantic roles — light"
97
+ subtitle="The role tokens components consume"
98
+ theme="light">
99
+ <SemanticSwatches />
100
+ </Display>
101
+
102
+ <Display
103
+ title="Semantic roles — dark"
104
+ subtitle="The same roles under data-theme=dark — warm charcoal, never pure black"
105
+ theme="dark">
106
+ <SemanticSwatches />
107
+ </Display>
108
+
109
+ <Display
110
+ title="Status hues"
111
+ subtitle="Reserved for check output — pass, warn, fail. Lift on charcoal to hold AA.">
112
+ <StatusHues />
113
+ </Display>
114
+
115
+ ## Type
116
+
117
+ Two families. **Hanken Grotesk** carries all chrome text; **JetBrains Mono**
118
+ carries developer texture — labels, values, code, and the wordmark. A dense 14px
119
+ base keeps the chrome compact so the exhibit gets the room.
120
+
121
+ <Display
122
+ title="Font families"
123
+ subtitle="System sans for chrome, mono for code & values">
124
+ <FontFamilies />
125
+ </Display>
126
+
127
+ <Display
128
+ title="Type scale"
129
+ subtitle="px-based; the chrome lives small so content gets room">
130
+ <TypeScale />
131
+ </Display>
132
+
133
+ <Display
134
+ title="Weights & labels"
135
+ subtitle="Three weights; the uppercase eyebrow pattern">
136
+ <Weights />
137
+ </Display>
138
+
139
+ ## Spacing
140
+
141
+ Tight, rem-based spacing keeps the chrome compact. Corners are modest — 5px on
142
+ controls, 8px on panels, 12px on overlays. **Borders carry the structure**: 1px
143
+ warm hairlines define every region, and elevation is reserved for floating layers.
144
+
145
+ <Display
146
+ title="Spacing scale"
147
+ subtitle="Tight, rem-based — dense chrome yields room to content">
148
+ <SpacingScale />
149
+ </Display>
150
+
151
+ <Display
152
+ title="Radius"
153
+ subtitle="Modest, precise — softened, never pill-round or brutalist-square">
154
+ <RadiusRow />
155
+ </Display>
156
+
157
+ <Display
158
+ title="Borders & elevation"
159
+ subtitle="Flat by default — borders carry structure, not shadow">
160
+ <ElevationRow />
161
+ </Display>
162
+
163
+ ## Brand
164
+
165
+ The wordmark is type only — `[ Display Case ]` with mono brackets in marigold.
166
+ There is no logo image, and that's intentional. The icon vocabulary is just as
167
+ restrained: Unicode glyphs only — no icon font, no SVG, no emoji.
168
+
169
+ <Display
170
+ title="Iconography"
171
+ subtitle="Unicode glyphs only — rendered in the UI font">
172
+ <Glyphs />
173
+ </Display>
174
+
175
+ ## Components
176
+
177
+ Three families compose the whole interface, mirroring the `components/` folders.
178
+ **Controls** are the input atoms, **showcase parts** are the shell's molecules
179
+ and organisms, and the **Primer primitives** are the prop-driven specimens this
180
+ wall text is assembled from. Each family below is one grouped exhibit — a mono
181
+ tag on the left, the live component on the right. **Display Case dogfoods every
182
+ one of them.**
183
+
184
+ <Display
185
+ title="Controls"
186
+ subtitle="Button · IconButton · Input · Select">
187
+ <ControlsOverview />
188
+ </Display>
189
+
190
+ <Display
191
+ title="Showcase parts"
192
+ subtitle="Eyebrow · Chip · NavItem · Stage · FlowNav · TweaksPanel · RenderAddress">
193
+ <ShowcaseOverview />
194
+ </Display>
195
+
196
+ <Display
197
+ title="Primer primitives"
198
+ subtitle="SwatchGrid · SpacingScale · GlyphGrid · SpecimenBoxRow">
199
+ <PrimerOverview />
200
+ </Display>
201
+
202
+ ## Deterministic render URLs
203
+
204
+ Every case renders at a stable URL. **Same URL, same render** — so an agent can
205
+ enumerate the manifest, snapshot exactly one variant, and verify it without a
206
+ human in the loop. Tweak state encodes into the query string, so a specific
207
+ configuration is a link you can paste.
208
+
209
+ <Display
210
+ title="Render address"
211
+ subtitle="GET /render/<component>/<case>?theme=…&t.<tweak>=…">
212
+ <RenderAddress />
213
+ </Display>
214
+
215
+ ## Get started
216
+
217
+ 1. **Link the tokens once.** Pull in `styles.css` — every value is a `--dc-*`
218
+ custom property.
219
+ 2. **Render under a scope.** Wrap anything in `data-theme="light"` or `"dark"`.
220
+ 3. **Wrap specimens.** Put each live demo in `<Display title subtitle theme>`;
221
+ its title becomes the sidebar table-of-contents entry.
222
+
223
+ ```mdx
224
+ import { Button, Stage } from './components'
225
+
226
+ <Display title="Button" subtitle="The one true action" theme="dark">
227
+ <Stage caption="Playground">
228
+ <Button variant="accent">Snapshot</Button>
229
+ </Stage>
230
+ </Display>
231
+ ```
232
+
233
+ Switch to **Cases** in the sidebar to browse every component and its variants on
234
+ the stage. The source of truth for this system is the **Display Case Design
235
+ System** project on [claude.ai/design](https://claude.ai/design), kept in sync
236
+ via `/design-sync`.
@@ -0,0 +1,14 @@
1
+ /* ============================================================
2
+ Display Case Design System — "The Vitrine".
3
+ Global stylesheet entry point. Consumers link THIS one file;
4
+ it is a manifest of @imports only. Every token and the webfont
5
+ declarations live in the files below.
6
+
7
+ Display Case dogfoods this system: its own browse chrome
8
+ (ui/chrome.css) is styled entirely from these `--dc-*` tokens.
9
+ ============================================================ */
10
+
11
+ @import "./tokens/fonts.css";
12
+ @import "./tokens/colors.css";
13
+ @import "./tokens/typography.css";
14
+ @import "./tokens/spacing.css";
@@ -0,0 +1,172 @@
1
+ /* ============================================================
2
+ Display Case — Color tokens
3
+ "The Vitrine": warm paper neutrals, a warm-ink primary, and a
4
+ single marigold accent. Deliberately NOT cold gray — the chrome
5
+ is calm and warm so the showcased component is the exhibit.
6
+ No external UI library is referenced.
7
+ ============================================================ */
8
+
9
+ :root {
10
+ /* --- Paper ramp (warm, taupe-undertoned neutrals) ----------- */
11
+ --dc-paper-50: #fbfaf6;
12
+ --dc-paper-100: #f4f1e9;
13
+ --dc-paper-200: #e6e0d4;
14
+ --dc-paper-300: #d6cebe;
15
+ --dc-paper-400: #b3a896;
16
+ --dc-paper-500: #8a8073;
17
+ --dc-paper-600: #6b6259;
18
+ --dc-paper-700: #4a423a;
19
+ --dc-paper-800: #2e2822;
20
+ --dc-paper-900: #211d18;
21
+ --dc-paper-950: #15110d;
22
+
23
+ /* --- Marigold ramp (the one accent) ------------------------- */
24
+ --dc-marigold-200: #fbe3bb;
25
+ --dc-marigold-300: #f6c878;
26
+ --dc-marigold-400: #f0a23b;
27
+ --dc-marigold-500: #e0820b; /* mid */
28
+ --dc-marigold-600: #c2690a; /* the marigold family's nominal accent step */
29
+ --dc-marigold-700: #9a4f0a; /* deeper — the accent's hover/pressed */
30
+
31
+ /* --- Status hues (used sparingly, for check output) --------- */
32
+ --dc-green-600: #3f7d4e;
33
+ --dc-amber-500: #d98a1f;
34
+ --dc-red-600: #c4493a;
35
+ --dc-red-700: #9e3528;
36
+
37
+ /* ============================================================
38
+ Semantic aliases — LIGHT (default)
39
+ These are the names components consume.
40
+ ============================================================ */
41
+ --dc-bg: var(--dc-paper-50); /* app canvas */
42
+ --dc-bg-subtle: var(--dc-paper-100); /* sidebar / raised fill */
43
+ --dc-surface: #ffffff; /* inputs / stage inside */
44
+ --dc-fg: var(--dc-paper-900); /* primary text (warm ink)*/
45
+ --dc-fg-muted: var(--dc-paper-600); /* labels / secondary */
46
+ --dc-fg-subtle: #736a5d; /* quiet text — the lightest warm taupe that still
47
+ clears AA (4.5) on paper-50/100 + white. paper-500
48
+ (#8a8073) read at only ~3.4–3.9. */
49
+ --dc-border: var(--dc-paper-200); /* warm hairlines */
50
+ --dc-border-strong: var(--dc-paper-300);
51
+
52
+ /* Primary = warm ink (rare, for the one true action). */
53
+ --dc-ink: var(--dc-paper-900);
54
+ --dc-ink-hover: var(--dc-paper-800);
55
+ --dc-ink-fg: var(--dc-paper-50);
56
+
57
+ /* Accent = marigold (active case, focus, links, highlights). Tuned to #a8570a:
58
+ the brightest marigold that still clears AA *both* as small text on paper
59
+ (4.6 on the sidebar fill) and under white fill text on the accent button
60
+ (5.2) — the two ways the accent is used. The ramp's nominal #c2690a reads at
61
+ only ~3.5 as text; this keeps as much of that brightness as AA allows. -700
62
+ carries the hover. */
63
+ --dc-brand: #a8570a;
64
+ --dc-brand-hover: var(--dc-marigold-700);
65
+ --dc-brand-fg: #ffffff;
66
+ --dc-brand-subtle: #fbf0dd; /* faint marigold wash */
67
+
68
+ --dc-hover: var(--dc-paper-100); /* control hover fill */
69
+ --dc-active: var(--dc-paper-200); /* control pressed fill */
70
+ --dc-focus-ring: var(--dc-marigold-400);
71
+
72
+ --dc-success: var(--dc-green-600);
73
+ --dc-warning: var(--dc-amber-500);
74
+ --dc-danger: var(--dc-red-600);
75
+
76
+ /* Consumer-app canvas names the render harness + app-surface specimens paint
77
+ (server.ts/primer `var(--color-bg, …)`). The Vitrine IS its own consumer,
78
+ so it fulfils that contract by aliasing them to its themed surface — without
79
+ this, the /render frame body stays transparent and every dark-theme specimen
80
+ renders on a white canvas (failing contrast). Re-declared per theme below so
81
+ they re-resolve against the active scope's --dc-bg/--dc-fg. */
82
+ --color-bg: var(--dc-bg);
83
+ --color-fg: var(--dc-fg);
84
+ }
85
+
86
+ /* ============================================================
87
+ Semantic aliases — LIGHT (explicit scope)
88
+ The light values above live only on :root, so a scope nested
89
+ inside a dark ancestor (e.g. a <Display theme="light"> specimen
90
+ on a dark primer) needs them re-declared to override the
91
+ inherited dark values. The ramps stay global on :root.
92
+ ============================================================ */
93
+ [data-theme="light"] {
94
+ --dc-bg: var(--dc-paper-50);
95
+ --dc-bg-subtle: var(--dc-paper-100);
96
+ --dc-surface: #ffffff;
97
+ --dc-fg: var(--dc-paper-900);
98
+ --dc-fg-muted: var(--dc-paper-600);
99
+ --dc-fg-subtle: #736a5d;
100
+ --dc-border: var(--dc-paper-200);
101
+ --dc-border-strong: var(--dc-paper-300);
102
+ /* Re-resolve the composite so its inner var(--dc-border) binds to *this*
103
+ scope's border colour — see the note in the dark block below. */
104
+ --dc-border-line: var(--dc-border-width) solid var(--dc-border);
105
+
106
+ --dc-ink: var(--dc-paper-900);
107
+ --dc-ink-hover: var(--dc-paper-800);
108
+ --dc-ink-fg: var(--dc-paper-50);
109
+
110
+ --dc-brand: #a8570a;
111
+ --dc-brand-hover: var(--dc-marigold-700);
112
+ --dc-brand-fg: #ffffff;
113
+ --dc-brand-subtle: #fbf0dd;
114
+
115
+ --dc-hover: var(--dc-paper-100);
116
+ --dc-active: var(--dc-paper-200);
117
+ --dc-focus-ring: var(--dc-marigold-400);
118
+
119
+ --dc-success: var(--dc-green-600);
120
+ --dc-warning: var(--dc-amber-500);
121
+ --dc-danger: var(--dc-red-600);
122
+
123
+ --color-bg: var(--dc-bg);
124
+ --color-fg: var(--dc-fg);
125
+ }
126
+
127
+ /* ============================================================
128
+ Semantic aliases — DARK
129
+ Activated by [data-theme="dark"] on <html> or any scope.
130
+ Warm charcoal, never pure black.
131
+ ============================================================ */
132
+ :root[data-theme="dark"],
133
+ [data-theme="dark"] {
134
+ --dc-bg: #1a1714;
135
+ --dc-bg-subtle: #211d19;
136
+ --dc-surface: #262019;
137
+ --dc-fg: #f2ede3;
138
+ --dc-fg-muted: #a89e91;
139
+ --dc-fg-subtle: #9a8f80; /* quiet text on charcoal — lifted from #7c7164
140
+ (~3.4–3.7) to clear AA on bg/subtle/surface. */
141
+ --dc-border: #332c24;
142
+ --dc-border-strong: #453c30;
143
+ /* Composite border tokens substitute their inner var() at the scope where
144
+ they are *declared*, not where they are used. Declared only on :root, the
145
+ light --dc-border would stay baked into --dc-border-line even inside a
146
+ forced-dark scope, so themed swatches/tiles kept light hairlines. Re-declare
147
+ it here so the composite re-resolves against this scope's --dc-border. */
148
+ --dc-border-line: var(--dc-border-width) solid var(--dc-border);
149
+
150
+ --dc-ink: #f2ede3;
151
+ --dc-ink-hover: #ffffff;
152
+ --dc-ink-fg: #1a1714;
153
+
154
+ --dc-brand: var(--dc-marigold-400);
155
+ --dc-brand-hover: var(--dc-marigold-300);
156
+ --dc-brand-fg: #1a1714;
157
+ --dc-brand-subtle: #2a2118;
158
+
159
+ --dc-hover: #211d19;
160
+ --dc-active: #2e2820;
161
+ --dc-focus-ring: var(--dc-marigold-500);
162
+
163
+ /* Status as TEXT on charcoal: the light red/green read at only ~3.3 here, so
164
+ lift them for dark. (warning/amber already clears AA on charcoal.) Tags and
165
+ badges that carry white text use the fixed ramp reds directly, not these. */
166
+ --dc-success: #579d68;
167
+ --dc-warning: var(--dc-amber-500);
168
+ --dc-danger: #d97366;
169
+
170
+ --color-bg: var(--dc-bg);
171
+ --color-fg: var(--dc-fg);
172
+ }
@@ -0,0 +1,18 @@
1
+ /* ============================================================
2
+ Display Case — Font face declarations
3
+ Two webfonts give the chrome its developer-native personality:
4
+ · Hanken Grotesk — warm, legible UI grotesk for all chrome text
5
+ · JetBrains Mono — characterful mono for labels, values, code,
6
+ and the bracketed wordmark
7
+ Loaded from the Google Fonts CDN. Swap these @import lines for
8
+ self-hosted woff2 if you need a fully offline bundle.
9
+
10
+ Note: when Display Case serves its own browse chrome it injects
11
+ the equivalent <link rel="stylesheet"> into the document head
12
+ (see server.ts FONT_LINKS) rather than this @import, so the font
13
+ declaration leads the document and never fights a consumer's own
14
+ stylesheet @imports. This file remains the canonical source for
15
+ anyone linking styles.css directly.
16
+ ============================================================ */
17
+
18
+ @import url("https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap");