@siyahi/ui 0.1.9-next.36 → 0.1.9-next.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/styles/siyahi.css CHANGED
@@ -1,9 +1,9 @@
1
1
  /*
2
2
  * Siyahi theme: the only file in the package that holds colour values.
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
3
+ * A terminal: the mono face, shadcn/ui `neutral` greys for both schemes, and
4
+ * terminal amber placed, not spread: links, the eyebrow and panel numbers and
5
+ * the selected sidebar item's text. Buttons, badges, toggles and focus stay
6
+ * monochrome, where black and white read strongest. Plus three meaning
7
7
  * colours and a five-hue chart palette. `packages/tools/contrast.mjs` checks every value
8
8
  * here in the build; when a check fails, change the value, never the bar.
9
9
  */
@@ -11,7 +11,7 @@
11
11
  @import "tw-animate-css";
12
12
  @import "./shadcn.css";
13
13
  @import "@fontsource-variable/geist";
14
- @import "@fontsource-variable/geist-mono";
14
+ @import "./m-plus-1-code.css";
15
15
  @import "./fallback-fonts.css";
16
16
 
17
17
  /* Tailwind does not scan node_modules, so name the package's own classes. */
@@ -41,8 +41,13 @@
41
41
 
42
42
  @theme inline {
43
43
  --font-sans: "Geist Variable", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;
44
- --font-mono: "Geist Mono Variable", "Geist Mono Fallback", ui-monospace, monospace;
45
- --font-heading: var(--font-sans);
44
+ /*
45
+ * M PLUS 1 Code: a mono half a font size wide (6px a character at 12px, Geist Mono's 7), so a terminal page fits
46
+ * a table's every column and a row's words on one line. 20KB for every weight.
47
+ */
48
+ --font-mono: "M PLUS 1 Code Variable", "M PLUS 1 Code Fallback", ui-monospace, monospace;
49
+ /* Titles are data labels on a terminal: the mono face, as the page itself is. */
50
+ --font-heading: var(--font-mono);
46
51
  /*
47
52
  * Tailwind's 65ch, measured in Geist, whose "0" is 0.663em wide. A ch
48
53
  * follows whichever font is drawing, so a plain 65ch is narrower while the
@@ -109,7 +114,8 @@
109
114
  --radius: 0rem;
110
115
  --background: oklch(0.985 0 0);
111
116
  --foreground: oklch(0.145 0 0);
112
- --card: oklch(1 0 0);
117
+ /* The page colour: a card is a block on the page, not a box, so what lies under its marks is the page. */
118
+ --card: oklch(0.985 0 0);
113
119
  --card-foreground: oklch(0.145 0 0);
114
120
  --popover: oklch(1 0 0);
115
121
  --popover-foreground: oklch(0.145 0 0);
@@ -130,12 +136,13 @@
130
136
  --input: oklch(0.64 0 0);
131
137
  /* Focus is the text colour, as black reads strongest: a field's edge thickens in it, any other control takes a halo. */
132
138
  --ring: oklch(0.145 0 0);
133
- /* Tailwind green-700, red-600, amber-700, with their chroma lowered to the sRGB edge. */
139
+ /* Tailwind green-700 and red-600, with their chroma lowered to the sRGB edge, and a mustard yellow. */
134
140
  --positive: oklch(0.527 0.145 150.069);
135
141
  --positive-foreground: oklch(0.985 0 0);
136
142
  --negative: oklch(0.577 0.235 27.325);
137
143
  --negative-foreground: oklch(0.985 0 0);
138
- --warning: oklch(0.555 0.149 48.998);
144
+ /* Yellow, not amber: amber is the accent, so caution takes its own hue. */
145
+ --warning: oklch(0.53 0.1 95);
139
146
  --warning-foreground: oklch(0.985 0 0);
140
147
  /*
141
148
  * Heat steps: how much of --positive or --negative each of five steps mixes
@@ -161,7 +168,16 @@
161
168
  * contrast check holds each bar colour's tint against the card and each slot's against the next slot's.
162
169
  */
163
170
  --bar-tint: 50%;
164
- --link: oklch(0.51 0.2 275);
171
+ /* Terminal amber, deepened to burnt orange so it reads as text on the light page. */
172
+ --link: oklch(0.55 0.13 58);
173
+ /*
174
+ * A headline MetricStrip's blocks (variant "blocks"): the orange, then the text colour, then a grey, each with the
175
+ * ink its figures take on it.
176
+ */
177
+ --headline: oklch(0.68 0.19 40);
178
+ --headline-foreground: oklch(0.145 0 0);
179
+ --headline-muted: oklch(0.88 0 0);
180
+ --headline-muted-foreground: oklch(0.145 0 0);
165
181
  /*
166
182
  * Indigo, amber, plum, sky, teal: no green or red, so no series reads as a
167
183
  * gain or a loss. Each slot reaches 3:1 against the canvas, the card and the
@@ -182,7 +198,7 @@
182
198
  --sidebar-primary: oklch(0.205 0 0);
183
199
  --sidebar-primary-foreground: oklch(0.985 0 0);
184
200
  --sidebar-accent: oklch(0.97 0 0);
185
- --sidebar-accent-foreground: oklch(0.45 0.2 275);
201
+ --sidebar-accent-foreground: oklch(0.55 0.13 58);
186
202
  --sidebar-border: oklch(0.145 0 0 / 18%);
187
203
  --sidebar-ring: oklch(0.145 0 0);
188
204
 
@@ -193,12 +209,12 @@
193
209
  * page, where cards have no fill, the canvas is what text sits on.
194
210
  * Claude Code (#181818, about #e8e8e8 text) and T3 Code (#0a0a0a, about
195
211
  * #bebebe) both narrow that gap; this sits between them, at 14.9:1.
196
- * Cards sit one step above the canvas, so data reads as raised, as the
197
- * white card does on the light canvas; the sidebar is a shade darker.
212
+ * A popover sits one step above the canvas, so it reads as lifted over
213
+ * the page; the sidebar is a shade darker.
198
214
  */
199
215
  --background: oklch(0.14 0 0);
200
216
  --foreground: oklch(0.922 0 0);
201
- --card: oklch(0.215 0 0);
217
+ --card: oklch(0.14 0 0);
202
218
  --card-foreground: oklch(0.922 0 0);
203
219
  --popover: oklch(0.215 0 0);
204
220
  --popover-foreground: oklch(0.922 0 0);
@@ -218,14 +234,14 @@
218
234
  --ring: oklch(0.922 0 0);
219
235
  /*
220
236
  * Green toned down from Tailwind green-400, below red-400's lightness so a
221
- * treemap's deepest green tile keeps its light label at 4.5:1; amber-400.
222
- * Red, amber and the link sit at the sRGB edge.
237
+ * treemap's deepest green tile keeps its light label at 4.5:1; a yellow
238
+ * for caution, apart from the amber link. Red sits at the sRGB edge.
223
239
  */
224
240
  --positive: oklch(0.69 0.16 151.711);
225
241
  --positive-foreground: oklch(0.145 0 0);
226
242
  --negative: oklch(0.704 0.187 22.216);
227
243
  --negative-foreground: oklch(0.145 0 0);
228
- --warning: oklch(0.828 0.169 84.429);
244
+ --warning: oklch(0.8 0.15 98);
229
245
  --warning-foreground: oklch(0.145 0 0);
230
246
  /* A 12% tint of a dark card reads as the card itself, so dark starts at 25%. */
231
247
  --heat-1: 25%;
@@ -242,7 +258,11 @@
242
258
  --wash-edge: 55%;
243
259
  --field-wash: 12%;
244
260
  --bar-tint: 50%;
245
- --link: oklch(0.75 0.128 275);
261
+ --link: oklch(0.8 0.13 65);
262
+ --headline: oklch(0.7 0.19 40);
263
+ --headline-foreground: oklch(0.145 0 0);
264
+ --headline-muted: oklch(0.27 0 0);
265
+ --headline-muted-foreground: oklch(0.922 0 0);
246
266
  --chart-1: oklch(0.58 0.2 275);
247
267
  --chart-2: oklch(0.88 0.08 75);
248
268
  --chart-3: oklch(0.58 0.16 335);
@@ -253,7 +273,7 @@
253
273
  --sidebar-primary: oklch(0.922 0 0);
254
274
  --sidebar-primary-foreground: oklch(0.205 0 0);
255
275
  --sidebar-accent: oklch(0.269 0 0);
256
- --sidebar-accent-foreground: oklch(0.8 0.1 275);
276
+ --sidebar-accent-foreground: oklch(0.8 0.13 65);
257
277
  --sidebar-border: oklch(1 0 0 / 20%);
258
278
  --sidebar-ring: oklch(0.922 0 0);
259
279
  }
@@ -267,7 +287,16 @@
267
287
  @apply bg-background text-foreground;
268
288
  font-variant-numeric: tabular-nums;
269
289
  }
290
+ /*
291
+ * A terminal: the page is set in the mono face, so figures, labels, controls and titles share one grid. Text that
292
+ * reads as sentences (a paragraph, a quote, a card's or a dialog's description) takes the sans, where
293
+ * mono tires the eye over a line. Not a list item: a sidebar row, a menu option or a tile is one, and its contents
294
+ * are labels. Zero specificity, so a component's own font class wins.
295
+ */
270
296
  html {
297
+ @apply font-mono;
298
+ }
299
+ :where(p, blockquote, dd, figcaption, [data-slot$="description"]) {
271
300
  @apply font-sans;
272
301
  }
273
302
  @media (prefers-reduced-motion: reduce) {
@@ -283,12 +312,9 @@
283
312
  }
284
313
 
285
314
  /*
286
- * Every selected toggle, a view switch or a filter chip alike, fills with the
287
- * foreground, black on white and white on black, so the reader sees at once
288
- * what is on; stock shadcn marks it with a faint grey. A default (pill) tab
289
- * selects the same way: stock fills it with the canvas colour, 1.05:1 against
290
- * its grey list, lifted on a drop shadow, which the fill makes needless. Unlayered, to outrank the stock
291
- * aria-pressed:bg-muted.
315
+ * A selected toggle, a view switch or a filter chip alike, and a selected pill tab fill with the foreground, black on
316
+ * white and white on black. Stock shadcn marks them with a faint grey, 1.05:1 against the canvas, so the reader cannot
317
+ * see which one is on. Unlayered, to outrank the stock aria-pressed:bg-muted.
292
318
  */
293
319
  :is([data-slot="toggle"], [data-slot="toggle-group-item"]):is([aria-pressed="true"], [data-pressed]),
294
320
  [data-slot="tabs-list"][data-variant="default"] [data-slot="tabs-trigger"][data-active] {
@@ -298,31 +324,6 @@
298
324
  --tw-shadow: 0 0 #0000;
299
325
  }
300
326
 
301
- /*
302
- * A toggle group's items sit flush and share their borders, one control with
303
- * its parts side by side; stock spaces them 8px apart. A group set to
304
- * spacing={0} already joins its items by dropping their inner borders. The
305
- * pressed item rises over its neighbours, so its own edge shows on both sides.
306
- */
307
- [data-slot="toggle-group"]:not([data-spacing="0"]) {
308
- gap: 0;
309
- > [data-slot="toggle-group-item"] {
310
- &[aria-pressed="true"],
311
- &[data-pressed] {
312
- z-index: 1;
313
- }
314
- &[data-variant="outline"] + [data-slot="toggle-group-item"] {
315
- margin-inline-start: -1px;
316
- }
317
- }
318
- &[data-orientation="vertical"]
319
- > [data-slot="toggle-group-item"][data-variant="outline"]
320
- + [data-slot="toggle-group-item"] {
321
- margin-inline-start: 0;
322
- margin-block-start: -1px;
323
- }
324
- }
325
-
326
327
  /*
327
328
  * A component that lifts a part of itself with a z-index is a stacking context
328
329
  * of its own: MetricStrip's picker buttons, an avatar's badge, a calendar's
@@ -352,43 +353,10 @@
352
353
  }
353
354
 
354
355
  /*
355
- * A copy button sits in a row above the code, never over it. Core floats a
356
- * CodeBlockActions written straight in the block over the code's top right
357
- * (z-10, shown on hover), where it covers the end of a line and painted over
358
- * the page's sticky section strip; here it takes a row of its own, as in a
359
- * CodeBlockHeader, always shown, with the header's plain button.
360
- */
361
- [data-slot="code-block"] > [data-slot="code-block-actions"] {
362
- position: static;
363
- align-self: flex-end;
364
- margin: 0.5rem 0.5rem 0;
365
- opacity: 1;
366
- }
367
- /*
368
- * Core's floating fill and faint resting ring go. Its fill outranks the ghost
369
- * button's own hover fill, so that is set again here, as a header's button
370
- * shows it; the focus ring is left alone.
371
- */
372
- [data-slot="code-block"] > [data-slot="code-block-actions"] > button {
373
- background-color: transparent;
374
- &:hover {
375
- background-color: var(--muted);
376
- @variant dark {
377
- background-color: color-mix(in oklab, var(--muted) 50%, transparent);
378
- }
379
- }
380
- &:not(:focus-visible) {
381
- --tw-ring-shadow: 0 0 #0000;
382
- }
383
- }
384
-
385
- /*
386
- * --input is the edge of a field, where the reader types or picks. A button,
387
- * a toggle and a code block are framed by their label or their content, so
388
- * their outline stays the faint divider colour; scoping the variable keeps
389
- * the fill each derives from it faint too. A button is matched by its own
390
- * group/button class: rendered as a popover or menu trigger, it takes that
391
- * trigger's data-slot instead of "button".
356
+ * --input is the edge of a field, raised to 3:1 so the reader sees where to type. Stock buttons, toggles and code
357
+ * blocks also edge and fill themselves from it (dark:bg-input/30), which the raised value would make heavy, so for
358
+ * them it stays the faint divider colour, as stock draws them. A button is matched by its own group/button class:
359
+ * rendered as a popover or menu trigger, it takes that trigger's data-slot instead of "button".
392
360
  */
393
361
  :is([class~="group/button"], [data-slot="toggle"], [data-slot="toggle-group-item"], [data-slot="code-block"]) {
394
362
  --input: var(--border);
@@ -590,85 +558,6 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
590
558
  background-color: color-mix(in oklab, var(--destructive) var(--field-wash), var(--card));
591
559
  }
592
560
 
593
- /*
594
- * A field's label sits above it in small monospaced capitals, muted, as the
595
- * labels over figures do: FieldLabel in a Field (directly or in its
596
- * FieldContent), the home a page gets it from. A label for a checkbox, a
597
- * switch or a radio reads as a sentence and keeps the sans face, whatever the
598
- * Field's orientation.
599
- */
600
- [data-slot="field"]:not(:has([role="checkbox"], [role="switch"], [role="radio"])) [data-slot="field-label"] {
601
- @apply font-mono text-2xs tracking-wider text-muted-foreground uppercase;
602
- }
603
-
604
- /*
605
- * A group's label in a menu (dropdown, context, menubar), a select's or a combobox's list or the
606
- * command palette is a label over its items, set as one.
607
- */
608
- :is(
609
- [data-slot$="menu-label"],
610
- [data-slot="menubar-label"],
611
- [data-slot="select-label"],
612
- [data-slot="combobox-label"],
613
- [data-slot="command-group"] [cmdk-group-heading]
614
- ) {
615
- @apply font-mono text-2xs tracking-wider text-muted-foreground uppercase;
616
- }
617
-
618
- /*
619
- * A floating surface reads as a sheet of the page, not a bubble over it: no shadow (the theme has none) and the
620
- * card's hairline for an edge. Stock shadcn edges a menu, a list, a popover, a dialog or a card with a fainter ring
621
- * (ring-foreground/10, also behind a variant such as data-open:); it takes the hairline wherever it appears, on
622
- * a component added later too. The
623
- * chart's tooltip, which Recharts draws, is named.
624
- */
625
- [class*="ring-foreground/10"] {
626
- --tw-ring-color: var(--border);
627
- }
628
- [data-slot="chart"] .recharts-tooltip-wrapper > div {
629
- border-color: var(--border);
630
- }
631
-
632
- /*
633
- * Square corners everywhere (DESIGN.md): every element and its ::before and ::after are square, whatever rounding
634
- * a component asks for (rounded-full, rounded-[4px], hover:rounded-full, after:rounded-full), on a component added
635
- * later too. Only what is round by nature stays round, by what it is rather than its classes: an avatar, a radio
636
- * and a radio's dot (a questionnaire's radio choice too), a status dot and a spinner. A chart draws its round marks
637
- * in SVG, which this does not touch.
638
- */
639
- :not(
640
- [data-slot^="avatar"],
641
- [data-slot="message-avatar"],
642
- [role="radio"],
643
- [role="radio"] *,
644
- [data-type="radio"] [data-slot$="indicator"],
645
- [data-type="radio"] [data-slot$="indicator-dot"],
646
- [data-slot="status-dot"],
647
- .animate-spin
648
- ),
649
- :not(
650
- [data-slot^="avatar"],
651
- [data-slot="message-avatar"],
652
- [role="radio"],
653
- [role="radio"] *,
654
- [data-type="radio"] [data-slot$="indicator"],
655
- [data-type="radio"] [data-slot$="indicator-dot"],
656
- [data-slot="status-dot"],
657
- .animate-spin
658
- )::before,
659
- :not(
660
- [data-slot^="avatar"],
661
- [data-slot="message-avatar"],
662
- [role="radio"],
663
- [role="radio"] *,
664
- [data-type="radio"] [data-slot$="indicator"],
665
- [data-type="radio"] [data-slot$="indicator-dot"],
666
- [data-slot="status-dot"],
667
- .animate-spin
668
- )::after {
669
- border-radius: 0;
670
- }
671
-
672
561
  /*
673
562
  * Muted text is already the faintest that reads at 4.5:1 (contrast.mjs). Stock shadcn fades it further on some
674
563
  * parts (text-muted-foreground/70 on a trace's description), under that bar, so a faded muted class reads as the
@@ -705,55 +594,40 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
705
594
  }
706
595
 
707
596
  /*
708
- * Column heads are labels, in small monospaced capitals; a sort header's
709
- * button and a data table's column title take the head's type rather than
710
- * their own. A total row reads as a
711
- * ledger's: one rule above it and a double rule under it in the text colour,
712
- * with no band.
597
+ * A label over data reads as a terminal's column head: small capitals, muted. A table's column heads (a sort
598
+ * header's button and a data table's column title take the head's type rather than their own), a sidebar group's
599
+ * label, and a group's label in a menu, a select's or a combobox's list or the command palette.
713
600
  */
714
- [data-slot="table-head"] {
715
- @apply font-mono text-2xs tracking-wider uppercase;
716
- :is([class~="group/button"], [data-slot="column-title"]) {
717
- font: inherit;
718
- letter-spacing: inherit;
719
- text-transform: inherit;
720
- }
601
+ :is(
602
+ [data-slot="table-head"],
603
+ [data-slot="sidebar-group-label"],
604
+ [data-slot$="menu-label"],
605
+ [data-slot="menubar-label"],
606
+ [data-slot="select-label"],
607
+ [data-slot="combobox-label"],
608
+ [data-slot="command-group"] [cmdk-group-heading]
609
+ ) {
610
+ @apply text-2xs tracking-wider text-muted-foreground uppercase;
611
+ }
612
+ [data-slot="table-head"] :is([class~="group/button"], [data-slot="column-title"]) {
613
+ font: inherit;
614
+ letter-spacing: inherit;
615
+ text-transform: inherit;
721
616
  }
617
+
722
618
  /*
723
- * Tables are dense, as a ledger is: compact rows, a rule in the text colour
724
- * under the column heads, the first column (what each row is) in medium
725
- * weight, and figures in the mono face, a size down so a column of them takes
726
- * no more room than the sans did. A figure is a cell marked data-numeric (the
727
- * data table's number columns, or a plain TableCell the page marks), which
728
- * also right-aligns it, and any Delta in a cell. Alignment alone says nothing:
729
- * a right-aligned word stays in the sans. Row hairlines stay; no stripes, no
730
- * column rules.
619
+ * Tables are dense, so a screen shows more rows and columns: 13px text, 24px column heads, cells 2px by 6px. Row
620
+ * hairlines stay; no stripes, no column rules.
731
621
  */
622
+ [data-slot="table"] {
623
+ font-size: 0.8125rem;
624
+ }
732
625
  [data-slot="table-head"] {
733
- height: 1.75rem;
626
+ height: 1.5rem;
627
+ padding-inline: 0.375rem;
734
628
  }
735
629
  [data-slot="table-cell"] {
736
- padding-block: 0.2rem;
737
- }
738
- [data-slot="table-header"] [data-slot="table-row"] {
739
- border-bottom-color: var(--foreground);
740
- }
741
- /* A data table's sticky header draws its rule as a line of its own, which stays when the rows scroll under it. */
742
- [data-slot="table-header"]::after {
743
- background-color: var(--foreground);
744
- }
745
- [data-slot="table-body"] [data-slot="table-cell"]:first-child {
746
- font-weight: 500;
747
- }
748
- :is([data-slot="table-cell"][data-numeric], [data-slot="table-cell"] [data-slot="delta"]) {
749
- @apply font-mono;
750
- font-size: 0.8125rem;
751
- }
752
- [data-slot="table-footer"] {
753
- background: none;
754
- border-top: 1px solid var(--foreground);
755
- border-bottom: 3px double var(--foreground);
756
- font-weight: 600;
630
+ padding: 0.125rem 0.375rem;
757
631
  }
758
632
 
759
633
  /*
@@ -789,6 +663,33 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
789
663
  outline-offset: var(--figure-reach);
790
664
  }
791
665
  }
666
+ /*
667
+ * A headline MetricStrip sets each figure on a solid block, in turn the headline orange, the text colour and a grey,
668
+ * touching. Everything in a block takes the block's ink: its label, note and a change's trend icon would not read on
669
+ * the fill in their own colours, and the sign still says gain or loss. A picked block's outline sits inside it.
670
+ */
671
+ [data-slot="metric-strip"][data-variant="blocks"] [data-slot="metric-strip-item"] {
672
+ background-color: var(--headline);
673
+ color: var(--headline-foreground);
674
+ &:nth-child(3n + 2) {
675
+ background-color: var(--foreground);
676
+ color: var(--background);
677
+ }
678
+ &:nth-child(3n) {
679
+ background-color: var(--headline-muted);
680
+ color: var(--headline-muted-foreground);
681
+ }
682
+ & > * {
683
+ --foreground: currentColor;
684
+ --muted-foreground: currentColor;
685
+ --positive: currentColor;
686
+ --negative: currentColor;
687
+ }
688
+ &[data-picked] {
689
+ outline-offset: -4px;
690
+ outline-color: currentColor;
691
+ }
692
+ }
792
693
  /* A pinned cell paints the card under itself (niko-table's lib/styles), so it lays the row's wash over that. */
793
694
  [data-slot="table-row"]:is([data-tone="negative"], [data-tone="warning"]) {
794
695
  --row-wash: color-mix(in oklab, var(--exception) var(--wash), transparent);
@@ -801,24 +702,17 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
801
702
  /*
802
703
  * Panels in a PanelGrid are numbered 01, 02 before their title, in the mono
803
704
  * face and the link colour, so the text can point to one. A panel without a
804
- * card title, such as a MetricStrip with none, takes no number; a block
805
- * wrapped in its own SiyahiProvider (which draws no box) still counts. The panel
705
+ * card title, such as a MetricStrip with none, takes no number. The panel
806
706
  * counts, not the title: a card header is a size container, whose style
807
707
  * containment would keep each title's count to itself.
808
708
  */
809
709
  [data-slot="panel-grid"] {
810
710
  counter-reset: panel;
811
711
  }
812
- [data-slot="panel"]:has(
813
- > [data-slot="card"] > [data-slot="card-header"] > [data-slot="card-title"],
814
- > [data-slot="siyahi-provider"] > [data-slot="card"] > [data-slot="card-header"] > [data-slot="card-title"]
815
- ) {
712
+ [data-slot="panel"]:has(> [data-slot="card"] > [data-slot="card-header"] > [data-slot="card-title"]) {
816
713
  counter-increment: panel;
817
714
  }
818
- :is([data-slot="panel"], [data-slot="panel"] > [data-slot="siyahi-provider"])
819
- > [data-slot="card"]
820
- > [data-slot="card-header"]
821
- > [data-slot="card-title"]::before {
715
+ [data-slot="panel"] > [data-slot="card"] > [data-slot="card-header"] > [data-slot="card-title"]::before {
822
716
  content: counter(panel, decimal-leading-zero);
823
717
  @apply font-mono text-2xs font-medium text-link;
824
718
  letter-spacing: 0.04em;
@@ -828,21 +722,11 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
828
722
  }
829
723
 
830
724
  /*
831
- * A data table inside a boxed card drops its own border: the card's ring
832
- * already frames it. On an open page, with no frame, it keeps its border.
833
- */
834
- @scope ([data-surface="card"]) to ([data-surface="open"]) {
835
- [data-slot="card"] [data-slot="table-container"] {
836
- border-color: transparent;
837
- }
838
- }
839
- /*
840
- * The opaque fill a data table's sticky header and pinned cells paint, so rows
841
- * scrolling under them stay hidden: the colour under the table, the card it
842
- * sits in or, on an open surface, whose cards have no fill, the page.
725
+ * The opaque fill a data table's sticky header and pinned cells paint, so rows scrolling under them stay hidden: the
726
+ * page, which a card does not cover.
843
727
  */
844
728
  [data-slot="table-container"] {
845
- --table-fill: var(--card);
729
+ --table-fill: var(--background);
846
730
  }
847
731
 
848
732
  /*
@@ -905,26 +789,20 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
905
789
  }
906
790
 
907
791
  /*
908
- * Chart gridlines take the full border colour, solid: stock shadcn draws them
909
- * dashed at half of it, which all but vanishes on a dark card (5% white).
792
+ * Chart gridlines take the full border colour: stock shadcn draws them at half of it, which all but vanishes on the
793
+ * dark page (10% white).
910
794
  */
911
795
  [data-slot="chart"] .recharts-cartesian-grid line {
912
796
  stroke: var(--border);
913
797
  }
914
798
 
915
799
  /*
916
- * The sidebar is parted from the page by a hairline on its page-side edge,
917
- * its group labels are small monospaced capitals, as every label over content
918
- * is, and its rows are compact. Fill, icons and the current-page mark stay as
919
- * shadcn draws them.
800
+ * The sidebar's rows are compact, as a table's are. shadcn edges a collapsible sidebar on its page side already; a
801
+ * non-collapsible one, set in the page's flow, has none, so it takes the hairline here.
920
802
  */
921
- /* shadcn edges a collapsible sidebar on its page side already; a non-collapsible one, set in the page's flow, has none. */
922
803
  div[data-slot="sidebar"]:not([data-side], [data-mobile]) {
923
804
  border-inline-end: 1px solid var(--sidebar-border);
924
805
  }
925
- [data-slot="sidebar-group-label"] {
926
- @apply font-mono text-2xs tracking-wider uppercase;
927
- }
928
806
  [data-slot="sidebar-menu"] {
929
807
  gap: 0;
930
808
  }
@@ -940,19 +818,6 @@ div[data-slot="sidebar"]:not([data-side], [data-mobile]) {
940
818
  height: 1.5rem;
941
819
  }
942
820
 
943
- /*
944
- * The selected sub-page marks its stretch of the sidebar's rail in the accent,
945
- * so the current place reads at a glance in a long list, as a docs index does.
946
- */
947
- [data-slot="sidebar-menu-sub-item"]:has(> [data-active])::before {
948
- content: "";
949
- position: absolute;
950
- inset-block: 2px;
951
- left: -11px;
952
- width: 2px;
953
- background: var(--sidebar-accent-foreground);
954
- }
955
-
956
821
  /* Touch targets follow input capability, including touch laptops. */
957
822
  @media (pointer: coarse) {
958
823
  :is(
@@ -1000,50 +865,24 @@ div[data-slot="sidebar"]:not([data-side], [data-mobile]) {
1000
865
  }
1001
866
 
1002
867
  /*
1003
- * Surfaces, from SiyahiProvider's surface prop. Open, the default, takes the
1004
- * box off every core Card: no fill, no ring, no tinted footer band, and no
1005
- * padding, so a block's content sits level with the page's text and space,
1006
- * not a box or a rule, parts it from the next. Card is a filled square card
1007
- * with a hairline ring and no shadow, 8px from the next, for a page of
1008
- * separate blocks. Each scope stops at a provider that says otherwise.
1009
- * Unlayered, like the rules above, to outrank the core components' utility
1010
- * classes.
1011
- */
1012
- /*
1013
- * An open card renames --card to the page colour for everything inside it, and a custom property inherits past a
1014
- * scope's end, so a card provider nested in an open card would fill its boxes with the page colour. --card-fill keeps
1015
- * the theme's card colour, set once at the root, and each card provider takes it back.
868
+ * A Card is a block on the page, not a box: no fill, no ring, no tinted footer band and no padding, so its content
869
+ * sits level with the page's text, and space, not a box or a rule, parts it from the next. --card is the page colour
870
+ * (above), so what a block mixes into, a forecast's halo, a bar's tint or an exception's wash, is what lies under it.
871
+ * Unlayered, like the rules above, to outrank the core component's utility classes.
1016
872
  */
1017
- :root {
1018
- --card-fill: var(--card);
1019
- }
1020
- [data-surface="card"] {
1021
- --card: var(--card-fill);
873
+ [data-slot="card"] {
874
+ /* No box, so no edge to clip at: a figure's wash or picker reaching past its text into the gap stays whole. */
875
+ overflow: visible;
876
+ background: none;
877
+ --tw-ring-color: transparent;
878
+ --card-spacing: 0px;
879
+ gap: calc(var(--spacing) * 3);
1022
880
  }
1023
-
1024
- @scope (:root, [data-surface="open"]) to ([data-surface="card"]) {
1025
- [data-slot="table-container"] {
1026
- --table-fill: var(--background);
1027
- }
1028
- /*
1029
- * With no fill, what lies under a card's marks is the page, so --card names the page colour inside it: a forecast's
1030
- * halos, a dot's ring, a bar's tint and an exception's wash mix into the canvas, not into a grey no box shows.
1031
- */
1032
- [data-slot="card"] {
1033
- --card: var(--background);
1034
- /* No box, so no edge to clip at: a figure's wash or picker reaching past its text into the gap stays whole. */
1035
- overflow: visible;
1036
- background: none;
1037
- --tw-ring-color: transparent;
1038
- --card-spacing: 0px;
1039
- gap: calc(var(--spacing) * 4);
1040
- }
1041
- [data-slot="card-footer"] {
1042
- background: none;
1043
- border-top-width: 0;
1044
- /* The band's own top padding stacked on the card's gap, and with no band it read as a hole under the figure. */
1045
- padding-top: 0;
1046
- }
881
+ [data-slot="card-footer"] {
882
+ background: none;
883
+ border-top-width: 0;
884
+ /* The band's own top padding stacked on the card's gap, and with no band it read as a hole under the figure. */
885
+ padding-top: 0;
1047
886
  }
1048
887
 
1049
888
  /*