@siyahi/ui 0.1.9-next.3 → 0.1.9-next.30

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/DESIGN.md +170 -0
  2. package/README.md +94 -177
  3. package/THIRD_PARTY_NOTICES.md +11 -11
  4. package/agents/examples/accordion.tsx +39 -0
  5. package/agents/examples/alert.tsx +24 -0
  6. package/agents/examples/app-sidebar.tsx +40 -0
  7. package/agents/examples/avatar.tsx +24 -0
  8. package/agents/examples/badge.tsx +26 -0
  9. package/agents/examples/bar-list-labels.tsx +22 -0
  10. package/agents/examples/bar-list-sides.tsx +40 -0
  11. package/agents/examples/bar-list.tsx +24 -0
  12. package/agents/examples/breadcrumb.tsx +28 -0
  13. package/agents/examples/button-states.tsx +23 -0
  14. package/agents/examples/button.tsx +21 -0
  15. package/agents/examples/calendar.tsx +11 -0
  16. package/agents/examples/callout.tsx +33 -0
  17. package/agents/examples/card.tsx +36 -0
  18. package/agents/examples/change-bar.tsx +104 -0
  19. package/agents/examples/change-grid-money.tsx +36 -0
  20. package/agents/examples/change-grid.tsx +42 -0
  21. package/agents/examples/chart-area-log.tsx +45 -0
  22. package/agents/examples/chart-area.tsx +39 -0
  23. package/agents/examples/chart-bar-flows.tsx +32 -0
  24. package/agents/examples/chart-bar-hourly.tsx +28 -0
  25. package/agents/examples/chart-bar-losses.tsx +29 -0
  26. package/agents/examples/chart-bar-teams.tsx +72 -0
  27. package/agents/examples/chart-bar-years.tsx +40 -0
  28. package/agents/examples/chart-bar.tsx +56 -0
  29. package/agents/examples/chart-category-ticks.tsx +50 -0
  30. package/agents/examples/chart-line-events.tsx +47 -0
  31. package/agents/examples/chart-line-forecast.tsx +36 -0
  32. package/agents/examples/chart-line-log.tsx +39 -0
  33. package/agents/examples/chart-line-quiet.tsx +114 -0
  34. package/agents/examples/chart-line-timeline.tsx +41 -0
  35. package/agents/examples/chart-line.tsx +57 -0
  36. package/agents/examples/chart-panels.tsx +94 -0
  37. package/agents/examples/code-block.tsx +40 -0
  38. package/agents/examples/collapsible.tsx +18 -0
  39. package/agents/examples/command.tsx +29 -0
  40. package/agents/examples/context-menu.tsx +17 -0
  41. package/agents/examples/copy-button.tsx +8 -0
  42. package/agents/examples/data-bar.tsx +108 -0
  43. package/agents/examples/data-table-compact.tsx +96 -0
  44. package/agents/examples/data-table-filters.tsx +103 -0
  45. package/agents/examples/data-table.tsx +99 -0
  46. package/agents/examples/delta.tsx +18 -0
  47. package/agents/examples/dialog.tsx +28 -0
  48. package/agents/examples/distribution-bar-budget.tsx +30 -0
  49. package/agents/examples/distribution-bar-tug.tsx +74 -0
  50. package/agents/examples/drawdown-chart.tsx +35 -0
  51. package/agents/examples/dropdown-menu.tsx +31 -0
  52. package/agents/examples/field.tsx +108 -0
  53. package/agents/examples/formatters.tsx +42 -0
  54. package/agents/examples/heatmap-calendar-signed.tsx +67 -0
  55. package/agents/examples/heatmap-calendar.tsx +62 -0
  56. package/agents/examples/heatmap-status.tsx +61 -0
  57. package/agents/examples/heatmap-weekday.tsx +72 -0
  58. package/agents/examples/histogram-chart.tsx +28 -0
  59. package/agents/examples/input-states.tsx +23 -0
  60. package/agents/examples/input.tsx +17 -0
  61. package/agents/examples/local-time.tsx +14 -0
  62. package/agents/examples/metric-strip.tsx +46 -0
  63. package/agents/examples/overview-dashboard.tsx +180 -0
  64. package/agents/examples/page-hero.tsx +38 -0
  65. package/agents/examples/page-section.tsx +52 -0
  66. package/agents/examples/popover.tsx +23 -0
  67. package/agents/examples/price-chart.tsx +32 -0
  68. package/agents/examples/progress-tone.tsx +29 -0
  69. package/agents/examples/progress.tsx +10 -0
  70. package/agents/examples/range-position-table.tsx +101 -0
  71. package/agents/examples/range-position.tsx +55 -0
  72. package/agents/examples/scatter-chart-quadrants.tsx +63 -0
  73. package/agents/examples/scatter-chart-scales.tsx +46 -0
  74. package/agents/examples/scatter-chart.tsx +78 -0
  75. package/agents/examples/scroll-area.tsx +22 -0
  76. package/agents/examples/section-cards-select.tsx +28 -0
  77. package/agents/examples/section-cards-trend.tsx +32 -0
  78. package/agents/examples/section-cards.tsx +48 -0
  79. package/agents/examples/select.tsx +27 -0
  80. package/agents/examples/separator.tsx +13 -0
  81. package/agents/examples/sheet.tsx +15 -0
  82. package/agents/examples/skeleton.tsx +11 -0
  83. package/agents/examples/slider.tsx +5 -0
  84. package/agents/examples/slope-chart-market.tsx +33 -0
  85. package/agents/examples/slope-chart.tsx +26 -0
  86. package/agents/examples/stale-blocks.tsx +56 -0
  87. package/agents/examples/surfaces.tsx +116 -0
  88. package/agents/examples/table.tsx +60 -0
  89. package/agents/examples/tabs.tsx +16 -0
  90. package/agents/examples/target-rows.tsx +74 -0
  91. package/agents/examples/toggle-group.tsx +12 -0
  92. package/agents/examples/toggle.tsx +11 -0
  93. package/agents/examples/tooltip.tsx +13 -0
  94. package/agents/examples/treemap.tsx +93 -0
  95. package/agents/examples/waterfall-chart-stores.tsx +27 -0
  96. package/agents/examples/waterfall-chart.tsx +37 -0
  97. package/agents/manifest.json +1391 -0
  98. package/agents/report-rules.json +7 -0
  99. package/dist/components/registry/charts/area-chart-card.d.ts +7 -3
  100. package/dist/components/registry/charts/area-chart-card.js +56 -10
  101. package/dist/components/registry/charts/bar-chart-card.d.ts +1 -1
  102. package/dist/components/registry/charts/bar-chart-card.js +20 -22
  103. package/dist/components/registry/charts/line-chart-card.d.ts +3 -3
  104. package/dist/components/registry/charts/line-chart-card.js +44 -9
  105. package/dist/components/registry/dashboard-01/section-cards.d.ts +6 -0
  106. package/dist/components/registry/dashboard-01/section-cards.js +51 -35
  107. package/dist/components/registry/dashboardcn/bar-list.js +17 -10
  108. package/dist/components/registry/dashboardcn/distribution-bar.js +6 -4
  109. package/dist/components/registry/dashboardcn/kpi-row-card.d.ts +29 -0
  110. package/dist/components/registry/dashboardcn/kpi-row-card.js +81 -0
  111. package/dist/components/registry/dashboardcn/sparkline.d.ts +22 -0
  112. package/dist/components/registry/dashboardcn/sparkline.js +117 -0
  113. package/dist/components/registry/heatmap/calendar-heatmap.d.ts +11 -3
  114. package/dist/components/registry/heatmap/calendar-heatmap.js +164 -45
  115. package/dist/components/registry/heatmap/status-heatmap.d.ts +5 -3
  116. package/dist/components/registry/heatmap/status-heatmap.js +115 -54
  117. package/dist/components/registry/heatmap/weekday-heatmap.d.ts +9 -3
  118. package/dist/components/registry/heatmap/weekday-heatmap.js +83 -53
  119. package/dist/components/registry/niko-table/core/data-table-context.d.ts +18 -1
  120. package/dist/components/registry/niko-table/core/data-table-context.js +7 -1
  121. package/dist/components/registry/niko-table/core/data-table-grouped-row.js +3 -1
  122. package/dist/components/registry/niko-table/core/data-table-root.js +39 -4
  123. package/dist/components/registry/niko-table/core/data-table-structure.js +17 -12
  124. package/dist/components/registry/niko-table/core/data-table.d.ts +2 -2
  125. package/dist/components/registry/niko-table/core/data-table.js +5 -1
  126. package/dist/components/registry/niko-table/filters/table-faceted-filter.js +5 -5
  127. package/dist/components/registry/niko-table/filters/table-search-filter.js +1 -1
  128. package/dist/components/registry/niko-table/lib/flex-columns.d.ts +1 -1
  129. package/dist/components/registry/niko-table/lib/flex-columns.js +3 -3
  130. package/dist/components/registry/niko-table/lib/styles.d.ts +1 -1
  131. package/dist/components/registry/niko-table/lib/styles.js +3 -3
  132. package/dist/components/registry/niko-table/types/index.d.ts +9 -0
  133. package/dist/components/registry/niko-table/ui/table.js +2 -2
  134. package/dist/components/registry/sidebar-07/app-sidebar.d.ts +1 -1
  135. package/dist/components/registry/sidebar-07/app-sidebar.js +3 -1
  136. package/dist/components/registry/sidebar-07/nav-main.d.ts +2 -1
  137. package/dist/components/registry/sidebar-07/nav-main.js +16 -3
  138. package/dist/components/siyahi/callout.d.ts +17 -0
  139. package/dist/components/siyahi/callout.js +34 -0
  140. package/dist/components/siyahi/change-bar.d.ts +21 -0
  141. package/dist/components/siyahi/change-bar.js +35 -0
  142. package/dist/components/siyahi/change-grid.d.ts +31 -0
  143. package/dist/components/siyahi/change-grid.js +74 -0
  144. package/dist/components/siyahi/data-bar.d.ts +19 -0
  145. package/dist/components/siyahi/data-bar.js +29 -0
  146. package/dist/components/siyahi/data-table-compact-columns-toggle.d.ts +4 -0
  147. package/dist/components/siyahi/data-table-compact-columns-toggle.js +19 -0
  148. package/dist/components/siyahi/data-table-footnote.d.ts +12 -0
  149. package/dist/components/siyahi/data-table-footnote.js +35 -0
  150. package/dist/components/siyahi/default-sidebar-open.d.ts +8 -0
  151. package/dist/components/siyahi/default-sidebar-open.js +13 -0
  152. package/dist/components/siyahi/delta.d.ts +5 -1
  153. package/dist/components/siyahi/delta.js +14 -9
  154. package/dist/components/siyahi/drawdown-chart.d.ts +34 -0
  155. package/dist/components/siyahi/drawdown-chart.js +102 -0
  156. package/dist/components/siyahi/histogram-chart.d.ts +57 -0
  157. package/dist/components/siyahi/histogram-chart.js +134 -0
  158. package/dist/components/siyahi/page-hero.d.ts +23 -0
  159. package/dist/components/siyahi/page-hero.js +43 -0
  160. package/dist/components/siyahi/page-section.d.ts +4 -1
  161. package/dist/components/siyahi/page-section.js +6 -3
  162. package/dist/components/siyahi/page-toc.d.ts +20 -0
  163. package/dist/components/siyahi/page-toc.js +76 -0
  164. package/dist/components/siyahi/panel-grid.d.ts +24 -0
  165. package/dist/components/siyahi/panel-grid.js +37 -0
  166. package/dist/components/siyahi/quote.d.ts +12 -0
  167. package/dist/components/siyahi/quote.js +23 -0
  168. package/dist/components/siyahi/range-position.d.ts +24 -0
  169. package/dist/components/siyahi/range-position.js +105 -0
  170. package/dist/components/siyahi/scale-toggle.d.ts +32 -0
  171. package/dist/components/siyahi/scale-toggle.js +61 -0
  172. package/dist/components/siyahi/scatter-chart.d.ts +69 -0
  173. package/dist/components/siyahi/scatter-chart.js +395 -0
  174. package/dist/components/siyahi/siyahi-provider.d.ts +15 -2
  175. package/dist/components/siyahi/siyahi-provider.js +17 -4
  176. package/dist/components/siyahi/slope-chart.d.ts +35 -0
  177. package/dist/components/siyahi/slope-chart.js +159 -0
  178. package/dist/components/siyahi/stale-note.d.ts +9 -0
  179. package/dist/components/siyahi/stale-note.js +12 -0
  180. package/dist/components/siyahi/status-alert.d.ts +15 -0
  181. package/dist/components/siyahi/status-alert.js +29 -0
  182. package/dist/components/siyahi/status-badge.d.ts +15 -0
  183. package/dist/components/siyahi/status-badge.js +24 -0
  184. package/dist/components/siyahi/target-rows.d.ts +34 -0
  185. package/dist/components/siyahi/target-rows.js +97 -0
  186. package/dist/components/siyahi/tone-progress.d.ts +1 -1
  187. package/dist/components/siyahi/tone-progress.js +1 -1
  188. package/dist/components/siyahi/treemap.d.ts +8 -5
  189. package/dist/components/siyahi/treemap.js +80 -34
  190. package/dist/components/siyahi/waterfall-chart.d.ts +51 -0
  191. package/dist/components/siyahi/waterfall-chart.js +147 -0
  192. package/dist/components/ui/accordion.js +5 -5
  193. package/dist/components/ui/alert.js +2 -2
  194. package/dist/components/ui/badge.js +1 -1
  195. package/dist/components/ui/breadcrumb.js +2 -2
  196. package/dist/components/ui/button.js +10 -10
  197. package/dist/components/ui/calendar.js +1 -1
  198. package/dist/components/ui/card.js +4 -4
  199. package/dist/components/ui/chart.js +1 -1
  200. package/dist/components/ui/code-block.js +13 -12
  201. package/dist/components/ui/command.js +7 -6
  202. package/dist/components/ui/context-menu.js +7 -7
  203. package/dist/components/ui/dialog.js +5 -5
  204. package/dist/components/ui/dropdown-menu.js +8 -8
  205. package/dist/components/ui/field.d.ts +26 -0
  206. package/dist/components/ui/field.js +109 -0
  207. package/dist/components/ui/input-group.js +11 -11
  208. package/dist/components/ui/input.js +1 -1
  209. package/dist/components/ui/label.d.ts +5 -0
  210. package/dist/components/ui/label.js +13 -0
  211. package/dist/components/ui/popover.js +3 -3
  212. package/dist/components/ui/progress.js +1 -1
  213. package/dist/components/ui/select.js +6 -6
  214. package/dist/components/ui/sheet.js +5 -5
  215. package/dist/components/ui/sidebar.js +17 -18
  216. package/dist/components/ui/skeleton.js +1 -1
  217. package/dist/components/ui/slider.js +2 -2
  218. package/dist/components/ui/table.js +2 -2
  219. package/dist/components/ui/tabs.js +2 -2
  220. package/dist/components/ui/textarea.js +1 -1
  221. package/dist/components/ui/toggle-group.js +2 -2
  222. package/dist/components/ui/toggle.js +4 -4
  223. package/dist/components/ui/tooltip.js +2 -2
  224. package/dist/index.d.ts +35 -8
  225. package/dist/index.js +30 -4
  226. package/dist/lib/chart-frame.d.ts +51 -7
  227. package/dist/lib/chart-frame.js +390 -133
  228. package/dist/lib/chart-marks.d.ts +83 -0
  229. package/dist/lib/chart-marks.js +216 -0
  230. package/dist/lib/chart-series.d.ts +42 -10
  231. package/dist/lib/compact-columns.d.ts +19 -0
  232. package/dist/lib/compact-columns.js +55 -0
  233. package/dist/lib/fit-text.d.ts +6 -0
  234. package/dist/lib/fit-text.js +11 -0
  235. package/dist/lib/format.d.ts +8 -3
  236. package/dist/lib/format.js +55 -17
  237. package/dist/lib/heat.d.ts +7 -0
  238. package/dist/lib/heat.js +14 -0
  239. package/dist/lib/heatmap-navigation.d.ts +8 -0
  240. package/dist/lib/heatmap-navigation.js +49 -0
  241. package/dist/lib/scatter.d.ts +88 -0
  242. package/dist/lib/scatter.js +179 -0
  243. package/dist/lib/ticks.d.ts +27 -0
  244. package/dist/lib/ticks.js +67 -0
  245. package/dist/lib/tone.d.ts +36 -0
  246. package/dist/lib/tone.js +35 -0
  247. package/dist/lib/treemap.d.ts +0 -5
  248. package/dist/lib/treemap.js +1 -11
  249. package/dist/lib/use-element-width.d.ts +5 -0
  250. package/dist/lib/use-element-width.js +15 -0
  251. package/package.json +5 -1
  252. package/registry.lock.json +16 -0
  253. package/report.css +63 -47
  254. package/styles/siyahi.css +708 -55
package/styles/siyahi.css CHANGED
@@ -1,7 +1,10 @@
1
1
  /*
2
2
  * Siyahi theme: the only file in the package that holds colour values.
3
- * Stock shadcn/ui `neutral` for both schemes, plus four meaning colours and a
4
- * five-hue chart palette. `packages/tools/contrast.mjs` checks every value
3
+ * shadcn/ui `neutral` greys for both schemes, with ink indigo (hue 275)
4
+ * placed, not spread: links, the selected sidebar item's text and the first
5
+ * chart series. Buttons, badges, toggles and focus stay monochrome, where
6
+ * black and white read strongest. Plus three meaning
7
+ * colours and a five-hue chart palette. `packages/tools/contrast.mjs` checks every value
5
8
  * here in the build; when a check fails, change the value, never the bar.
6
9
  */
7
10
  @import "tailwindcss";
@@ -31,6 +34,12 @@
31
34
  * fallback draws and every prose block rewraps when Geist arrives.
32
35
  */
33
36
  --max-width-prose: 43.1em;
37
+ /*
38
+ * 11px, for labels over figures: a stat tile's label and footer lines and a
39
+ * table's column heads, set as `font-mono text-2xs tracking-wider uppercase`.
40
+ */
41
+ --text-2xs: 0.6875rem;
42
+ --text-2xs--line-height: 1rem;
34
43
  --color-background: var(--background);
35
44
  --color-foreground: var(--foreground);
36
45
  --color-card: var(--card);
@@ -69,6 +78,7 @@
69
78
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
70
79
  --color-sidebar-border: var(--sidebar-border);
71
80
  --color-sidebar-ring: var(--sidebar-ring);
81
+ --radius-xs: calc(var(--radius) * 0.3);
72
82
  --radius-sm: calc(var(--radius) * 0.6);
73
83
  --radius-md: calc(var(--radius) * 0.8);
74
84
  --radius-lg: var(--radius);
@@ -80,8 +90,9 @@
80
90
 
81
91
  :root {
82
92
  color-scheme: light dark;
83
- --radius: 0.625rem;
84
- --background: oklch(1 0 0);
93
+ /* Square: panels, buttons, fields, badges and toggles alike. Only a dot, an avatar or a slider thumb stays round. */
94
+ --radius: 0rem;
95
+ --background: oklch(0.985 0 0);
85
96
  --foreground: oklch(0.145 0 0);
86
97
  --card: oklch(1 0 0);
87
98
  --card-foreground: oklch(0.145 0 0);
@@ -92,83 +103,137 @@
92
103
  --secondary: oklch(0.97 0 0);
93
104
  --secondary-foreground: oklch(0.205 0 0);
94
105
  --muted: oklch(0.97 0 0);
95
- --muted-foreground: oklch(0.556 0 0);
106
+ /* Below shadcn's 0.556, so muted text keeps 4.5:1 on the muted fill and on every Callout's tint. */
107
+ --muted-foreground: oklch(0.51 0 0);
96
108
  --accent: oklch(0.97 0 0);
97
109
  --accent-foreground: oklch(0.205 0 0);
98
- --destructive: oklch(0.577 0.245 27.325);
99
- --border: oklch(0.922 0 0);
100
- --input: oklch(0.922 0 0);
101
- --ring: oklch(0.708 0 0);
102
- /* Tailwind green-700, red-600, amber-700, blue-600. */
103
- --positive: oklch(0.527 0.154 150.069);
110
+ /* Deeper than the loss red, so a destructive button's label reads on its own red tint, hovered too. */
111
+ --destructive: oklch(0.49 0.19 27.325);
112
+ /* A step firmer than shadcn's 0.922 grey: with square, filled panels the hairlines carry the page's structure. */
113
+ --border: oklch(0.145 0 0 / 18%);
114
+ /* The edge of a field (input, textarea, select, input group) at 3:1: --border stays faint for dividers. */
115
+ --input: oklch(0.64 0 0);
116
+ /* Focus is the text colour, as black reads strongest: a field's edge thickens in it, any other control takes a halo. */
117
+ --ring: oklch(0.145 0 0);
118
+ /* Tailwind green-700, red-600, amber-700, with their chroma lowered to the sRGB edge. */
119
+ --positive: oklch(0.527 0.145 150.069);
104
120
  --positive-foreground: oklch(0.985 0 0);
105
- --negative: oklch(0.577 0.245 27.325);
121
+ --negative: oklch(0.577 0.235 27.325);
106
122
  --negative-foreground: oklch(0.985 0 0);
107
- --warning: oklch(0.555 0.163 48.998);
123
+ --warning: oklch(0.555 0.149 48.998);
108
124
  --warning-foreground: oklch(0.985 0 0);
109
- --link: oklch(0.546 0.245 262.881);
110
125
  /*
111
- * Blue, emerald, amber, violet, rose. Each slot reaches 3:1 against the card
112
- * and against the slots next to it, which only lightness can give, so
113
- * emerald and violet are the deep members in both schemes. Slots two apart
126
+ * Heat steps: how much of --positive or --negative each of five steps mixes
127
+ * into the card, for Treemap, ChangeGrid and a signed CalendarHeatmap
128
+ * (lib/heat.ts). The contrast check reads them from here.
129
+ */
130
+ --heat-1: 12%;
131
+ --heat-2: 24%;
132
+ --heat-3: 36%;
133
+ --heat-4: 48%;
134
+ --heat-5: 60%;
135
+ /*
136
+ * An exception (a stat tile or a table row the page marks with a tone) takes this share of --negative or
137
+ * --warning over the card, and a tile an outline of the tone at --wash-edge.
138
+ */
139
+ --wash: 7%;
140
+ --wash-edge: 45%;
141
+ /* An invalid field's wash: this share of --destructive over the card. */
142
+ --field-wash: 8%;
143
+ --link: oklch(0.51 0.2 275);
144
+ /*
145
+ * Indigo, amber, plum, sky, teal: no green or red, so no series reads as a
146
+ * gain or a loss. Each slot reaches 3:1 against the canvas, the card and the
147
+ * slots next to it, which only lightness can give, so indigo, plum and teal
148
+ * are the deep members and amber and sky the pale ones, in both schemes: a
149
+ * pale indigo in dark read as lavender and lost the accent. Slots two apart
114
150
  * share a lightness and are told apart by hue; series take slots in order.
115
151
  * Every chart value is inside sRGB, so the browser paints what the contrast
116
152
  * check scored.
117
153
  */
118
- --chart-1: oklch(0.65 0.17 259.8);
119
- --chart-2: oklch(0.35 0.07 163.2);
120
- --chart-3: oklch(0.66 0.15 60);
121
- --chart-4: oklch(0.36 0.17 293);
122
- --chart-5: oklch(0.65 0.21 16.4);
154
+ --chart-1: oklch(0.4 0.17 275);
155
+ --chart-2: oklch(0.66 0.13 70);
156
+ --chart-3: oklch(0.38 0.13 335);
157
+ --chart-4: oklch(0.65 0.11 235);
158
+ --chart-5: oklch(0.36 0.06 205);
123
159
  --sidebar: oklch(0.985 0 0);
124
160
  --sidebar-foreground: oklch(0.145 0 0);
125
161
  --sidebar-primary: oklch(0.205 0 0);
126
162
  --sidebar-primary-foreground: oklch(0.985 0 0);
127
163
  --sidebar-accent: oklch(0.97 0 0);
128
- --sidebar-accent-foreground: oklch(0.205 0 0);
129
- --sidebar-border: oklch(0.922 0 0);
130
- --sidebar-ring: oklch(0.708 0 0);
164
+ --sidebar-accent-foreground: oklch(0.45 0.2 275);
165
+ --sidebar-border: oklch(0.145 0 0 / 18%);
166
+ --sidebar-ring: oklch(0.145 0 0);
131
167
 
132
168
  @variant dark {
133
- --background: oklch(0.145 0 0);
134
- --foreground: oklch(0.985 0 0);
135
- --card: oklch(0.205 0 0);
136
- --card-foreground: oklch(0.985 0 0);
137
- --popover: oklch(0.205 0 0);
138
- --popover-foreground: oklch(0.985 0 0);
169
+ /*
170
+ * The canvas lifts just off black and text sits a step below white:
171
+ * near-white on near-black (19:1) blurs for many readers, and with the
172
+ * cards' grey gone on a ruled page the canvas is what text sits on.
173
+ * Claude Code (#181818, about #e8e8e8 text) and T3 Code (#0a0a0a, about
174
+ * #bebebe) both narrow that gap; this sits between them, at 14.9:1.
175
+ * Cards sit one step above the canvas, so data reads as raised, as the
176
+ * white card does on the light canvas; the sidebar is a shade darker.
177
+ */
178
+ --background: oklch(0.14 0 0);
179
+ --foreground: oklch(0.922 0 0);
180
+ --card: oklch(0.215 0 0);
181
+ --card-foreground: oklch(0.922 0 0);
182
+ --popover: oklch(0.215 0 0);
183
+ --popover-foreground: oklch(0.922 0 0);
139
184
  --primary: oklch(0.922 0 0);
140
185
  --primary-foreground: oklch(0.205 0 0);
141
186
  --secondary: oklch(0.269 0 0);
142
- --secondary-foreground: oklch(0.985 0 0);
187
+ --secondary-foreground: oklch(0.922 0 0);
143
188
  --muted: oklch(0.269 0 0);
144
- --muted-foreground: oklch(0.708 0 0);
189
+ /* A step above shadcn's 0.708: subtitles, footnotes and table labels at data density read too dim. */
190
+ --muted-foreground: oklch(0.74 0 0);
145
191
  --accent: oklch(0.269 0 0);
146
- --accent-foreground: oklch(0.985 0 0);
147
- --destructive: oklch(0.704 0.191 22.216);
148
- --border: oklch(1 0 0 / 10%);
149
- --input: oklch(1 0 0 / 15%);
150
- --ring: oklch(0.556 0 0);
151
- /* Tailwind green-400, red-400, amber-400, blue-400. */
152
- --positive: oklch(0.792 0.209 151.711);
192
+ --accent-foreground: oklch(0.922 0 0);
193
+ /* Paler and calmer than the loss red, so a destructive label reads on its red tint over the card, hovered too. */
194
+ --destructive: oklch(0.8 0.1 22.216);
195
+ --border: oklch(1 0 0 / 20%);
196
+ --input: oklch(0.62 0 0);
197
+ --ring: oklch(0.922 0 0);
198
+ /*
199
+ * Green toned down from Tailwind green-400, below red-400's lightness so a
200
+ * treemap's deepest green tile keeps its light label at 4.5:1; amber-400.
201
+ * Red, amber and the link sit at the sRGB edge.
202
+ */
203
+ --positive: oklch(0.69 0.16 151.711);
153
204
  --positive-foreground: oklch(0.145 0 0);
154
- --negative: oklch(0.704 0.191 22.216);
205
+ --negative: oklch(0.704 0.187 22.216);
155
206
  --negative-foreground: oklch(0.145 0 0);
156
- --warning: oklch(0.828 0.189 84.429);
207
+ --warning: oklch(0.828 0.169 84.429);
157
208
  --warning-foreground: oklch(0.145 0 0);
158
- --link: oklch(0.707 0.165 254.624);
159
- --chart-1: oklch(0.84 0.08 255);
160
- --chart-2: oklch(0.52 0.11 163.2);
161
- --chart-3: oklch(0.86 0.13 80);
162
- --chart-4: oklch(0.55 0.25 293);
163
- --chart-5: oklch(0.84 0.085 12);
164
- --sidebar: oklch(0.205 0 0);
165
- --sidebar-foreground: oklch(0.985 0 0);
166
- --sidebar-primary: oklch(0.488 0.243 264.376);
167
- --sidebar-primary-foreground: oklch(0.985 0 0);
209
+ /* A 12% tint of a dark card reads as the card itself, so dark starts at 25%. */
210
+ --heat-1: 25%;
211
+ --heat-2: 33.75%;
212
+ --heat-3: 42.5%;
213
+ --heat-4: 51.25%;
214
+ --heat-5: 60%;
215
+ /*
216
+ * As with heat, a faint share of a dark card reads as the card itself; above
217
+ * 12% the amber wash takes --chart-1, a tile's Sparkline or a row's DataBar,
218
+ * under 3:1.
219
+ */
220
+ --wash: 12%;
221
+ --wash-edge: 55%;
222
+ --field-wash: 12%;
223
+ --link: oklch(0.75 0.128 275);
224
+ --chart-1: oklch(0.58 0.2 275);
225
+ --chart-2: oklch(0.88 0.08 75);
226
+ --chart-3: oklch(0.58 0.16 335);
227
+ --chart-4: oklch(0.88 0.05 235);
228
+ --chart-5: oklch(0.56 0.08 205);
229
+ --sidebar: oklch(0.145 0 0);
230
+ --sidebar-foreground: oklch(0.922 0 0);
231
+ --sidebar-primary: oklch(0.922 0 0);
232
+ --sidebar-primary-foreground: oklch(0.205 0 0);
168
233
  --sidebar-accent: oklch(0.269 0 0);
169
- --sidebar-accent-foreground: oklch(0.985 0 0);
170
- --sidebar-border: oklch(1 0 0 / 10%);
171
- --sidebar-ring: oklch(0.556 0 0);
234
+ --sidebar-accent-foreground: oklch(0.8 0.1 275);
235
+ --sidebar-border: oklch(1 0 0 / 20%);
236
+ --sidebar-ring: oklch(0.922 0 0);
172
237
  }
173
238
  }
174
239
 
@@ -195,6 +260,494 @@
195
260
  }
196
261
  }
197
262
 
263
+ /*
264
+ * Every selected toggle, a view switch or a filter chip alike, fills with the
265
+ * foreground, black on white and white on black, so the reader sees at once
266
+ * what is on; stock shadcn marks it with a faint grey. A default (pill) tab
267
+ * selects the same way: stock fills it with the canvas colour, 1.05:1 against
268
+ * its grey list. Unlayered, to outrank the stock aria-pressed:bg-muted.
269
+ */
270
+ :is([data-slot="toggle"], [data-slot="toggle-group-item"]):is([aria-pressed="true"], [data-pressed]),
271
+ [data-slot="tabs-list"][data-variant="default"] [data-slot="tabs-trigger"][data-active] {
272
+ background-color: var(--foreground);
273
+ color: var(--background);
274
+ border-color: var(--foreground);
275
+ }
276
+
277
+ /*
278
+ * A toggle group's items sit flush and share their borders, one control with
279
+ * its parts side by side; stock spaces them 8px apart. A group set to
280
+ * spacing={0} already joins its items by dropping their inner borders. The
281
+ * pressed item rises over its neighbours, so its own edge shows on both sides.
282
+ */
283
+ [data-slot="toggle-group"]:not([data-spacing="0"]) {
284
+ gap: 0;
285
+ > [data-slot="toggle-group-item"] {
286
+ &[aria-pressed="true"],
287
+ &[data-pressed] {
288
+ z-index: 1;
289
+ }
290
+ &[data-variant="outline"] + [data-slot="toggle-group-item"] {
291
+ margin-inline-start: -1px;
292
+ }
293
+ }
294
+ &[data-orientation="vertical"]
295
+ > [data-slot="toggle-group-item"][data-variant="outline"]
296
+ + [data-slot="toggle-group-item"] {
297
+ margin-inline-start: 0;
298
+ margin-block-start: -1px;
299
+ }
300
+ }
301
+
302
+ /*
303
+ * A component that lifts a part of itself with a z-index is a stacking context
304
+ * of its own: SectionCards' picker button, an avatar's badge, a calendar's
305
+ * days, a focused toggle, a code block's buttons, a price chart's layers. The
306
+ * lifted part then ranks only against the rest of its component, and can never
307
+ * sit over, or take a tap from, a page's sticky header or section strip (also
308
+ * z-10, where the later element in the page won the tie). In a group, the
309
+ * group is the context, so a badge still sits over the next, overlapping
310
+ * avatar. The data table's scroll container isolates itself.
311
+ */
312
+ :is(
313
+ [data-slot="card"],
314
+ [data-slot="avatar-group"],
315
+ [data-slot="avatar"]:not([data-slot="avatar-group"] > *),
316
+ [data-slot="calendar"],
317
+ [data-slot="toggle-group"],
318
+ [data-slot="code-block"],
319
+ [data-slot="price-chart"]
320
+ ) {
321
+ isolation: isolate;
322
+ }
323
+
324
+ /*
325
+ * A copy button sits in a row above the code, never over it. Core floats a
326
+ * CodeBlockActions written straight in the block over the code's top right
327
+ * (z-10, shown on hover), where it covers the end of a line and painted over
328
+ * the page's sticky section strip; here it takes a row of its own, as in a
329
+ * CodeBlockHeader, always shown, with the header's plain button.
330
+ */
331
+ [data-slot="code-block"] > [data-slot="code-block-actions"] {
332
+ position: static;
333
+ align-self: flex-end;
334
+ margin: 0.5rem 0.5rem 0;
335
+ opacity: 1;
336
+ }
337
+ /*
338
+ * Core's floating fill and faint resting ring go. Its fill outranks the ghost
339
+ * button's own hover fill, so that is set again here, as a header's button
340
+ * shows it; the focus ring is left alone.
341
+ */
342
+ [data-slot="code-block"] > [data-slot="code-block-actions"] > button {
343
+ background-color: transparent;
344
+ &:hover {
345
+ background-color: var(--muted);
346
+ @variant dark {
347
+ background-color: color-mix(in oklab, var(--muted) 50%, transparent);
348
+ }
349
+ }
350
+ &:not(:focus-visible) {
351
+ --tw-ring-shadow: 0 0 #0000;
352
+ }
353
+ }
354
+
355
+ /*
356
+ * --input is the edge of a field, where the reader types or picks. A button,
357
+ * a toggle and a code block are framed by their label or their content, so
358
+ * their outline stays the faint divider colour; scoping the variable keeps
359
+ * the fill each derives from it faint too. A button is matched by its own
360
+ * group/button class: rendered as a popover or menu trigger, it takes that
361
+ * trigger's data-slot instead of "button".
362
+ */
363
+ :is([class~="group/button"], [data-slot="toggle"], [data-slot="toggle-group-item"], [data-slot="code-block"]) {
364
+ --input: var(--border);
365
+ }
366
+
367
+ /*
368
+ * The command palette's search field is a field: the --input edge on a muted
369
+ * fill, and the muted placeholder every other field uses (stock leaves it at
370
+ * Tailwind's half-strength text, 3.7:1).
371
+ */
372
+ [data-slot="command-input-wrapper"] > [data-slot="input-group"] {
373
+ border-color: var(--input);
374
+ background-color: color-mix(in oklab, var(--muted) 50%, transparent);
375
+ }
376
+ [data-slot="command-input"]::placeholder {
377
+ color: var(--muted-foreground);
378
+ }
379
+
380
+ /*
381
+ * Focus is drawn inside the control, never outside it, so no neighbour and no
382
+ * box that clips can cover it: a 2px ring in the text colour (--ring) on the
383
+ * control's edge, and a 2px ring of the page colour just inside that, so the
384
+ * pair reads on any fill, black, white or hovered, and the outer ring meets
385
+ * the page around the control at the text's contrast. A field shows focus as
386
+ * its own edge, 2px in the text colour; an input group and the command search
387
+ * show it round the whole group. A chart mark is drawn in SVG, where these
388
+ * rings do not paint: it takes a dashed stroke in the text colour, apart from a
389
+ * picked mark's solid one. Menu items and options show focus as their
390
+ * highlight, and popups as themselves, so they take none.
391
+ */
392
+ :focus-visible:not(
393
+ svg,
394
+ svg *,
395
+ [role="menuitem"],
396
+ [role="menuitemcheckbox"],
397
+ [role="menuitemradio"],
398
+ [role="option"],
399
+ [role="dialog"],
400
+ [role="alertdialog"],
401
+ [role="menu"],
402
+ [role="listbox"],
403
+ [data-slot="input"],
404
+ [data-slot="textarea"],
405
+ [data-slot="select-trigger"],
406
+ [data-slot="input-group-control"],
407
+ [data-slot="command-input"]
408
+ ),
409
+ [data-slot="slider-thumb"]:has(input:focus-visible) {
410
+ outline: none;
411
+ box-shadow:
412
+ inset 0 0 0 2px var(--ring),
413
+ inset 0 0 0 4px var(--background);
414
+ }
415
+ :is([data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"]):focus-visible,
416
+ [data-slot="input-group"]:has(:is([data-slot="input-group-control"], [data-slot="command-input"]):focus-visible) {
417
+ outline: none;
418
+ border-color: var(--ring);
419
+ box-shadow: inset 0 0 0 1px var(--ring);
420
+ }
421
+ :is([data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"])[aria-invalid="true"]:focus-visible,
422
+ [data-slot="input-group"]:has([data-slot="input-group-control"][aria-invalid="true"]:focus-visible) {
423
+ border-color: var(--destructive);
424
+ box-shadow: inset 0 0 0 1px var(--destructive);
425
+ }
426
+ [class~="group/button"][class~="text-destructive"]:focus-visible {
427
+ box-shadow:
428
+ inset 0 0 0 2px var(--destructive),
429
+ inset 0 0 0 4px var(--background);
430
+ }
431
+ /* A code block shows its scrolling content's focus itself; its own frame keeps no halo. */
432
+ [data-slot="code-block"]:has(> [data-slot="code-block-content"]:focus-visible) {
433
+ --tw-ring-shadow: 0 0 #0000;
434
+ }
435
+ svg :is(rect, circle, path)[tabindex]:focus-visible,
436
+ [data-slot="treemap-tile"]:focus-visible > rect:first-child {
437
+ outline: none;
438
+ stroke: var(--ring);
439
+ stroke-width: 2px;
440
+ stroke-dasharray: 3 2;
441
+ }
442
+ /* A treemap tile clips at its own edge, so half its stroke shows: twice the width keeps 2px. */
443
+ [data-slot="treemap-tile"]:focus-visible > rect:first-child {
444
+ stroke-width: 4px;
445
+ }
446
+
447
+ /*
448
+ * A destructive control focuses, and an invalid field is edged, in the full
449
+ * destructive colour; stock fades both to 40-50%, under 3:1.
450
+ */
451
+ [class~="group/button"][class~="text-destructive"]:focus-visible,
452
+ [data-slot="code-block"][data-variant="destructive"]:has(> [data-slot="code-block-content"]:focus-visible),
453
+ :is(
454
+ [data-slot="input"],
455
+ [data-slot="textarea"],
456
+ [data-slot="select-trigger"],
457
+ [class~="group/button"]
458
+ )[aria-invalid="true"],
459
+ [data-slot="input-group"]:has([data-slot][aria-invalid="true"]) {
460
+ border-color: var(--destructive);
461
+ }
462
+ /* An invalid field also takes a faint wash of the red, so it reads as the one to fix before its message is read. */
463
+ :is([data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"])[aria-invalid="true"],
464
+ [data-slot="input-group"]:has([data-slot][aria-invalid="true"]) {
465
+ background-color: color-mix(in oklab, var(--destructive) var(--field-wash), var(--card));
466
+ }
467
+
468
+ /*
469
+ * A field's label sits above it in small monospaced capitals, muted, as the
470
+ * labels over figures do: FieldLabel in a Field (directly or in its
471
+ * FieldContent), the home a page gets it from. A label for a checkbox, a
472
+ * switch or a radio reads as a sentence and keeps the sans face, whatever the
473
+ * Field's orientation.
474
+ */
475
+ [data-slot="field"]:not(:has([role="checkbox"], [role="switch"], [role="radio"])) [data-slot="field-label"] {
476
+ @apply font-mono text-2xs tracking-wider text-muted-foreground uppercase;
477
+ }
478
+
479
+ /* Progress is a bar, square as every bar is; stock draws it as a pill. */
480
+ [data-slot="progress-track"] {
481
+ border-radius: 0;
482
+ }
483
+
484
+ /*
485
+ * A slider's unfilled track is outlined in the field edge, so its extent reads
486
+ * at 3:1; the muted track alone is 1.05:1 on the canvas.
487
+ */
488
+ [data-slot="slider-track"] {
489
+ box-shadow: inset 0 0 0 1px var(--input);
490
+ }
491
+
492
+ /*
493
+ * A number column (meta.variant "number") right-aligns its head and cells, so
494
+ * figures line up by their units down the column, as a ledger reads.
495
+ */
496
+ :is([data-slot="table-head"], [data-slot="table-cell"])[data-numeric] {
497
+ text-align: right;
498
+ }
499
+ [data-slot="table-head"][data-numeric] > *,
500
+ [data-slot="table-cell"][data-numeric] > :is(div, span)[class*="flex"] {
501
+ justify-content: flex-end;
502
+ margin-inline-start: auto;
503
+ }
504
+
505
+ /*
506
+ * Column heads are labels, in small monospaced capitals; a sort header's
507
+ * button and a data table's column title take the head's type rather than
508
+ * their own. A total row reads as a
509
+ * ledger's: one rule above it and a double rule under it in the text colour,
510
+ * with no band.
511
+ */
512
+ [data-slot="table-head"] {
513
+ @apply font-mono text-2xs tracking-wider uppercase;
514
+ :is([class~="group/button"], [data-slot="column-title"]) {
515
+ font: inherit;
516
+ letter-spacing: inherit;
517
+ text-transform: inherit;
518
+ }
519
+ }
520
+ /*
521
+ * Tables are dense, as a ledger is: compact rows, a rule in the text colour
522
+ * under the column heads, the first column (what each row is) in medium
523
+ * weight, and figures in the mono face, a size down so a column of them takes
524
+ * no more room than the sans did. A figure is a cell marked data-numeric (the
525
+ * data table's number columns, or a plain TableCell the page marks), which
526
+ * also right-aligns it, and any Delta in a cell. Alignment alone says nothing:
527
+ * a right-aligned word stays in the sans. Row hairlines stay; no stripes, no
528
+ * column rules.
529
+ */
530
+ [data-slot="table-head"] {
531
+ height: 1.75rem;
532
+ }
533
+ [data-slot="table-cell"] {
534
+ padding-block: 0.2rem;
535
+ }
536
+ [data-slot="table-header"] [data-slot="table-row"] {
537
+ border-bottom-color: var(--foreground);
538
+ }
539
+ /* A data table's sticky header draws its rule as a line of its own, which stays when the rows scroll under it. */
540
+ [data-slot="table-header"]::after {
541
+ background-color: var(--foreground);
542
+ }
543
+ [data-slot="table-body"] [data-slot="table-cell"]:first-child {
544
+ font-weight: 500;
545
+ }
546
+ :is([data-slot="table-cell"][data-numeric], [data-slot="table-cell"] [data-slot="delta"]) {
547
+ @apply font-mono;
548
+ font-size: 0.8125rem;
549
+ }
550
+ [data-slot="table-footer"] {
551
+ background: none;
552
+ border-top: 1px solid var(--foreground);
553
+ border-bottom: 3px double var(--foreground);
554
+ font-weight: 600;
555
+ }
556
+
557
+ /*
558
+ * An exception the page marks, a stat tile (SectionCard's tone) or a table
559
+ * row (<TableRow data-tone="negative">), takes a faint wash of its colour over
560
+ * the card, and a tile an outline of it, so the one figure needing attention
561
+ * stands out of a run of neutral ones. Unlayered, to outrank a row's hover band.
562
+ */
563
+ :is([data-slot="card"], [data-slot="table-row"]) {
564
+ &[data-tone="negative"] {
565
+ --exception: var(--negative);
566
+ }
567
+ &[data-tone="warning"] {
568
+ --exception: var(--warning);
569
+ }
570
+ }
571
+ [data-slot="card"]:is([data-tone="negative"], [data-tone="warning"]) {
572
+ background-color: color-mix(in oklab, var(--exception) var(--wash), var(--card));
573
+ --tw-ring-color: color-mix(in oklab, var(--exception) var(--wash-edge), transparent);
574
+ }
575
+ [data-slot="table-row"]:is([data-tone="negative"], [data-tone="warning"]) {
576
+ background-color: color-mix(in oklab, var(--exception) var(--wash), transparent);
577
+ }
578
+
579
+ /*
580
+ * Panels in a PanelGrid are numbered 01, 02 before their title, in the mono
581
+ * face and the link colour, so the text can point to one. A panel without a
582
+ * card title, such as a MetricStrip with none, takes no number; a block
583
+ * wrapped in its own SiyahiProvider (which draws no box) still counts. The panel
584
+ * counts, not the title: a card header is a size container, whose style
585
+ * containment would keep each title's count to itself.
586
+ */
587
+ [data-slot="panel-grid"] {
588
+ counter-reset: panel;
589
+ }
590
+ [data-slot="panel"]:has(
591
+ > [data-slot="card"] > [data-slot="card-header"] > [data-slot="card-title"],
592
+ > [data-slot="siyahi-provider"] > [data-slot="card"] > [data-slot="card-header"] > [data-slot="card-title"]
593
+ ) {
594
+ counter-increment: panel;
595
+ }
596
+ :is([data-slot="panel"], [data-slot="panel"] > [data-slot="siyahi-provider"])
597
+ > [data-slot="card"]
598
+ > [data-slot="card-header"]
599
+ > [data-slot="card-title"]::before {
600
+ content: counter(panel, decimal-leading-zero);
601
+ @apply font-mono text-2xs font-medium text-link;
602
+ letter-spacing: 0.04em;
603
+ margin-inline-end: 0.7em;
604
+ position: relative;
605
+ top: -0.12em;
606
+ }
607
+
608
+ /*
609
+ * A data table inside a card drops its own border: the card's ring or the
610
+ * ruled page's hairlines already frame it. On an open page, with no frame,
611
+ * it keeps its border.
612
+ */
613
+ @scope (:root, [data-surface="ruled"], [data-surface="card"]) to ([data-surface="open"]) {
614
+ [data-slot="card"] [data-slot="table-container"] {
615
+ border-color: transparent;
616
+ }
617
+ }
618
+
619
+ /*
620
+ * A series in a forecast is drawn twice (LineChartCard, AreaChartCard): solid
621
+ * up to forecastFrom and dashed after it, each copy clipped to its side by the
622
+ * clip paths lib/chart-marks draws. The chart sets the two clip paths' urls.
623
+ */
624
+ [data-slot="chart"] .chart-actual {
625
+ clip-path: var(--chart-actual);
626
+ }
627
+ [data-slot="chart"] .chart-forecast {
628
+ clip-path: var(--chart-forecast);
629
+ }
630
+
631
+ /*
632
+ * Pen-stroke motion: a chart's lines draw in left to right over 1.3s, and a
633
+ * stack's fills are revealed with them (Recharts' own animation, which reduced
634
+ * motion turns off). Then the names at the line ends fade in; a line's end
635
+ * dots appear once it has drawn and fade in then; bars, with no line to wait
636
+ * for, fade in at once rather than grow. The names are Siyahi's own element,
637
+ * kept across data updates, so they fade in once. Reduced motion shows them
638
+ * all at once.
639
+ */
640
+ @keyframes chart-fade-in {
641
+ from {
642
+ opacity: 0;
643
+ }
644
+ }
645
+ [data-slot="chart"] :is(.recharts-bar-rectangles, .recharts-line-dots, .recharts-area-dots) {
646
+ animation: chart-fade-in 0.6s ease-out backwards;
647
+ }
648
+ [data-slot="chart"] [data-slot="end-labels"] {
649
+ animation: chart-fade-in 0.6s ease-out 1.3s backwards;
650
+ }
651
+ @media (prefers-reduced-motion: reduce) {
652
+ [data-slot="chart"]
653
+ :is(.recharts-bar-rectangles, .recharts-line-dots, .recharts-area-dots, [data-slot="end-labels"]) {
654
+ animation: none;
655
+ }
656
+ }
657
+
658
+ /*
659
+ * A block the page marks stale (stale on a chart card or a SectionCards tile)
660
+ * loses its colour: its chart, sparkline and badge turn grey, and a tile's
661
+ * figure and badge turn muted. The chart and sparkline also draw at 65%, so a
662
+ * dark slot's line does not read heavier than a fresh one: a stale block is
663
+ * meant to be harder to read, so its marks are exempt from the 3:1 mark bar.
664
+ * The small "Stale · 3 Nov" note under the title and the figure keep their
665
+ * text contrast.
666
+ */
667
+ [data-slot="card"][data-stale] :is([data-slot="chart"], [data-slot="sparkline"], [data-slot="badge"]) {
668
+ filter: grayscale(1);
669
+ }
670
+ [data-slot="card"][data-stale] :is([data-slot="chart"], [data-slot="sparkline"]) {
671
+ opacity: 0.65;
672
+ }
673
+ [data-slot="section-card"] [data-slot="card"][data-stale] :is([data-slot="card-title"], [data-slot="badge"]) {
674
+ color: var(--muted-foreground);
675
+ }
676
+
677
+ /*
678
+ * Chart gridlines take the full border colour, solid: stock shadcn draws them
679
+ * dashed at half of it, which all but vanishes on a dark card (5% white).
680
+ */
681
+ [data-slot="chart"] .recharts-cartesian-grid line {
682
+ stroke: var(--border);
683
+ }
684
+
685
+ /*
686
+ * The sidebar is parted from the page by a hairline on its page-side edge,
687
+ * its group labels are small monospaced capitals, as every label over content
688
+ * is, and its rows are compact. Fill, icons and the current-page mark stay as
689
+ * shadcn draws them.
690
+ */
691
+ /* shadcn edges a collapsible sidebar on its page side already; a non-collapsible one, set in the page's flow, has none. */
692
+ div[data-slot="sidebar"]:not([data-side], [data-mobile]) {
693
+ border-inline-end: 1px solid var(--sidebar-border);
694
+ }
695
+ [data-slot="sidebar-group-label"] {
696
+ @apply font-mono text-2xs tracking-wider uppercase;
697
+ }
698
+ [data-slot="sidebar-menu"] {
699
+ gap: 0;
700
+ }
701
+ /* A row that opens a group renders as its collapsible trigger, which takes that slot, so rows are found by data-sidebar. */
702
+ [data-sidebar="menu-button"]:not([data-size="lg"]) {
703
+ height: 1.75rem;
704
+ }
705
+ [data-slot="sidebar-menu-sub"] {
706
+ gap: 0;
707
+ padding-block: 0.125rem;
708
+ }
709
+ [data-sidebar="menu-sub-button"] {
710
+ height: 1.5rem;
711
+ }
712
+
713
+ /*
714
+ * The selected sub-page marks its stretch of the sidebar's rail in the accent,
715
+ * so the current place reads at a glance in a long list, as a docs index does.
716
+ */
717
+ [data-slot="sidebar-menu-sub-item"]:has(> [data-active])::before {
718
+ content: "";
719
+ position: absolute;
720
+ inset-block: 2px;
721
+ left: -11px;
722
+ width: 2px;
723
+ border-radius: 1px;
724
+ background: var(--sidebar-accent-foreground);
725
+ }
726
+
727
+ /* Touch targets follow input capability, including touch laptops. */
728
+ @media (pointer: coarse) {
729
+ :is(
730
+ [class~="group/button"],
731
+ [data-slot="toggle"],
732
+ [data-slot="toggle-group-item"],
733
+ [data-slot="select-trigger"],
734
+ [data-slot="tabs-trigger"],
735
+ [data-slot="input-group-button"]
736
+ ) {
737
+ min-height: --spacing(11);
738
+ min-width: --spacing(11);
739
+ }
740
+ :is([data-slot="input"], [data-slot="textarea"], [data-slot="input-group"], [data-slot="input-group-control"]) {
741
+ min-height: --spacing(11);
742
+ }
743
+ :is([data-slot="input"], [data-slot="textarea"], [data-slot="input-group-control"], [data-slot="command-input"]) {
744
+ font-size: 1rem;
745
+ }
746
+ [role="search"] > [data-slot="input"] {
747
+ padding-right: --spacing(14);
748
+ }
749
+ }
750
+
198
751
  /*
199
752
  * On a narrow card a header action (a toggle, a button) drops under the title
200
753
  * and description, left-aligned. Stock CardHeader keeps the action in its own
@@ -215,3 +768,103 @@
215
768
  }
216
769
  }
217
770
  }
771
+
772
+ /*
773
+ * Surfaces, from SiyahiProvider's surface prop. Ruled, the default, keeps
774
+ * every card's fill and drops its ring: PanelGrid and SectionCards fill their
775
+ * grid with the card colour and draw hairlines between the blocks, with a rule
776
+ * above and below the group and none on its left and right edges, and a card
777
+ * that stands alone, such as a MetricStrip or a table's card, takes those
778
+ * rules above and below itself. Card is a filled square card with a hairline
779
+ * ring and no shadow, 8px from the next, for a page of separate blocks. Open
780
+ * takes the box off every core Card: no fill, no ring, no tinted footer band,
781
+ * and no padding, so a block's content sits level with the page's text. Each
782
+ * scope stops at a provider that says otherwise. Unlayered, like the rules
783
+ * above, to outrank the core components' utility classes; a picked
784
+ * SectionCards tile keeps its ring, which is important, and an exception
785
+ * keeps its outline, which is more specific.
786
+ */
787
+ [data-slot="card"] {
788
+ --tw-ring-color: var(--border);
789
+ }
790
+
791
+ @scope (:root, [data-surface="ruled"]) to ([data-surface="card"], [data-surface="open"]) {
792
+ [data-slot="card"] {
793
+ --tw-ring-color: transparent;
794
+ }
795
+ /*
796
+ * A card inside a panel or a tile's cell, or nested in another card, is ruled by what holds it; only a holder
797
+ * inside this scope counts, so a ruled provider inside a card starts its own lone cards afresh.
798
+ */
799
+ [data-slot="card"]:not(
800
+ :scope :is([data-slot="panel"], [data-slot="section-card"], [data-slot="card"]) [data-slot="card"]
801
+ ) {
802
+ border-block: 1px solid var(--border);
803
+ }
804
+ }
805
+
806
+ @scope ([data-surface="open"]) to ([data-surface="ruled"], [data-surface="card"]) {
807
+ [data-slot="card"] {
808
+ background: none;
809
+ --tw-ring-color: transparent;
810
+ --card-spacing: 0px;
811
+ gap: calc(var(--spacing) * 4);
812
+ }
813
+ [data-slot="card-footer"] {
814
+ background: none;
815
+ border-top-width: 0;
816
+ /* The band's own top padding stacked on the card's gap, and with no band it read as a hole under the figure. */
817
+ padding-top: 0;
818
+ }
819
+ }
820
+
821
+ /*
822
+ * A table, or a page's section strip, that scrolls sideways fades at the edge
823
+ * with more behind it,
824
+ * so a phone reader sees there is more without a visible scrollbar. The fade
825
+ * follows the scroll position; a table that fits has no timeline and no fade.
826
+ */
827
+ @property --table-fade-start {
828
+ syntax: "<length>";
829
+ inherits: false;
830
+ initial-value: 0px;
831
+ }
832
+ @property --table-fade-end {
833
+ syntax: "<length>";
834
+ inherits: false;
835
+ initial-value: 0px;
836
+ }
837
+ @keyframes table-edge-fade {
838
+ 0% {
839
+ --table-fade-start: 0px;
840
+ --table-fade-end: 2rem;
841
+ }
842
+ 8%,
843
+ 92% {
844
+ --table-fade-start: 2rem;
845
+ --table-fade-end: 2rem;
846
+ }
847
+ 100% {
848
+ --table-fade-start: 2rem;
849
+ --table-fade-end: 0px;
850
+ }
851
+ }
852
+ @layer base {
853
+ :is([data-slot="table-container"], [data-slot="page-toc"] > div) {
854
+ mask-image: linear-gradient(
855
+ to right,
856
+ rgb(0 0 0 / 0.2),
857
+ #000 var(--table-fade-start),
858
+ #000 calc(100% - var(--table-fade-end)),
859
+ rgb(0 0 0 / 0.2)
860
+ );
861
+ animation: table-edge-fade linear both;
862
+ animation-timeline: scroll(self inline);
863
+ }
864
+ /* Scroll position, not motion: the reduced-motion reset above must not collapse it. */
865
+ @media (prefers-reduced-motion: reduce) {
866
+ :is([data-slot="table-container"], [data-slot="page-toc"] > div) {
867
+ animation-duration: auto !important;
868
+ }
869
+ }
870
+ }