astryx-dracula 0.2.0 → 0.3.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 (83) hide show
  1. package/BRAND.md +30 -18
  2. package/README.md +22 -11
  3. package/USAGE.md +29 -10
  4. package/astryx-dracula.js +19 -13
  5. package/astryx-dracula.variants.d.ts +15 -0
  6. package/package.json +59 -17
  7. package/shared/auth-chrome-config.ts +18 -0
  8. package/shared/auth-chrome.tsx +25 -0
  9. package/shared/auth-copy.ts +22 -0
  10. package/shared/chaptered-doc-config.ts +92 -0
  11. package/shared/chaptered-doc.tsx +332 -0
  12. package/shared/chart-hues.ts +27 -0
  13. package/shared/chart-labels.tsx +44 -0
  14. package/shared/chart-legend.tsx +100 -0
  15. package/shared/chart-panel-style.ts +13 -0
  16. package/shared/data-bar.tsx +160 -0
  17. package/shared/gallery-image.ts +20 -0
  18. package/shared/login-demo.ts +7 -0
  19. package/shared/metric-delta.tsx +36 -0
  20. package/shared/order-desk.tsx +273 -0
  21. package/shared/revenue-chart.tsx +174 -0
  22. package/shared/scene-castle.tsx +322 -0
  23. package/shared/scene-frame.tsx +88 -0
  24. package/shared/scene-hues.ts +49 -0
  25. package/shared/scene-tile.tsx +128 -0
  26. package/shared/settings-data.ts +125 -0
  27. package/shared/settings-rows.tsx +179 -0
  28. package/shared/sparkline.tsx +94 -0
  29. package/shared/sso-icons.tsx +30 -0
  30. package/templates/ai-chat-landing.tsx +67 -32
  31. package/templates/ai-chat.tsx +41 -20
  32. package/templates/blank.tsx +5 -2
  33. package/templates/centered-hero.tsx +15 -124
  34. package/templates/classic-gallery.tsx +21 -65
  35. package/templates/contact-form.tsx +62 -40
  36. package/templates/dashboard-portfolio.tsx +146 -227
  37. package/templates/dashboard.tsx +123 -121
  38. package/templates/detail-page.tsx +101 -95
  39. package/templates/documentation-design.template.mjs +1 -1
  40. package/templates/documentation-design.tsx +85 -96
  41. package/templates/documentation-technical.template.mjs +1 -1
  42. package/templates/documentation-technical.tsx +33 -21
  43. package/templates/documentation.template.mjs +1 -1
  44. package/templates/documentation.tsx +54 -74
  45. package/templates/editor.tsx +119 -89
  46. package/templates/file-explorer.tsx +133 -84
  47. package/templates/form-two-column.tsx +62 -124
  48. package/templates/gallery-hero.tsx +17 -171
  49. package/templates/ide.tsx +27 -21
  50. package/templates/incident-console.tsx +105 -69
  51. package/templates/kanban-board.tsx +100 -41
  52. package/templates/library.template.mjs +2 -2
  53. package/templates/library.tsx +38 -41
  54. package/templates/login-card.tsx +59 -86
  55. package/templates/login-split.tsx +88 -128
  56. package/templates/login-sso.tsx +107 -68
  57. package/templates/login.tsx +108 -52
  58. package/templates/messaging-shell.tsx +172 -143
  59. package/templates/mixed-gallery.tsx +31 -130
  60. package/templates/payment-form.tsx +369 -289
  61. package/templates/product-detail.tsx +40 -57
  62. package/templates/product-gallery.tsx +31 -40
  63. package/templates/product-tour.template.mjs +6 -0
  64. package/templates/product-tour.tsx +916 -0
  65. package/templates/settings-dialog.tsx +145 -286
  66. package/templates/settings-sidebar.tsx +179 -267
  67. package/templates/settings.tsx +42 -13
  68. package/templates/shell-nav.tsx +32 -23
  69. package/templates/shell-side-nav.tsx +30 -19
  70. package/templates/shell-top-nav.tsx +81 -43
  71. package/templates/side-gallery.tsx +39 -93
  72. package/templates/table-grouped.tsx +63 -33
  73. package/templates/table-page-chart.tsx +24 -297
  74. package/templates/table-page-heatmap-status.tsx +157 -91
  75. package/templates/table-page-shoe-store-heatmap.tsx +19 -288
  76. package/templates/table-page.tsx +24 -6
  77. package/templates/table.tsx +24 -7
  78. package/templates/tech-report.template.mjs +6 -0
  79. package/templates/tech-report.tsx +1486 -0
  80. package/templates/theme-showcase.template.mjs +1 -1
  81. package/templates/theme-showcase.tsx +313 -249
  82. package/theme.css +76 -14
  83. package/tokens.css +425 -30
package/tokens.css CHANGED
@@ -3,6 +3,24 @@
3
3
  * This file is the pre-theme paint fallback. Values follow the Dracula spec UI
4
4
  * palette (Background Light #343746 surfaces, Selection #44475A quiet edges,
5
5
  * Current Line #6272A4 visible edges). Do not add new hexes here.
6
+ * Deliberate spec deviations, mirrored in astryx-theme.ts: spec Comment
7
+ * #6272A4 fails the 4.5:1 WCAG floor for body text, so secondary #9AA1BC,
8
+ * paragraph #B0B3C4 and base-muted #8288A6 are same-hue lifts, #4C5067
9
+ * (subdue) is chrome only and never text, and theme.css adds #FFD5CC as the
10
+ * pale inverted error surface. Do not revert any of them to spec hexes.
11
+ * Two categorical data hues are deliberately ABSENT from the list below, do not
12
+ * re-add them: `brown` (was byte-identical to orange, and a categorical series
13
+ * set is a palette where distinctness IS the contract) and `purple` (purple
14
+ * means tappable, never data). The purple ban is a MODULE rule —
15
+ * `shared/chart-hues.ts` is purple-free by construction — so the SEQUENTIAL
16
+ * `--color-data-purple-1..5` ramp below SURVIVES and is sanctioned; only the
17
+ * categorical half is revoked. Rationale lives in astryx-theme.ts.
18
+ * `--color-data-teal-1..5` derives off `--color-data-categorical-teal` (#A4FFFF,
19
+ * an ANSI bright, not a spec token) rather than spec Cyan #8BE9FD; provenance
20
+ * fix only, nothing renders the ramp today (HeatScale is shamrock).
21
+ * `--color-on-dark` is the spec Foreground #F8F8F2, NOT pure white — Dracula has
22
+ * no #FFFFFF, and the dark media scope repins text/icon-primary onto it. It
23
+ * must stay a 6-digit hex literal: core's Spinner concatenates an alpha onto it.
6
24
  * Fonts: copy `fonts/` to your app's served static dir (Vite: `public/fonts/`). */
7
25
 
8
26
  @font-face {
@@ -10,7 +28,7 @@
10
28
  font-style: normal;
11
29
  font-weight: 400;
12
30
  font-display: swap;
13
- src: url('/fonts/JetBrainsMono-Regular.woff2') format('woff2');
31
+ src: url('/astryx-dracula/fonts/JetBrainsMono-Regular.woff2') format('woff2');
14
32
  }
15
33
 
16
34
  @font-face {
@@ -18,11 +36,24 @@
18
36
  font-style: normal;
19
37
  font-weight: 600;
20
38
  font-display: swap;
21
- src: url('/fonts/JetBrainsMono-SemiBold.woff2') format('woff2');
39
+ src: url('/astryx-dracula/fonts/JetBrainsMono-SemiBold.woff2') format('woff2');
22
40
  }
23
41
 
42
+ /* Canvas: the document scroller's track is transparent, so the unpainted
43
+ * canvas (black in dark color-scheme) shows through as a gutter behind the
44
+ * thumb in Chrome and Safari. Paint html/body the brand bg so track and
45
+ * page read as one surface. */
46
+ html,
47
+ body {
48
+ background-color: #282A36;
49
+ }
50
+
51
+ /* Full plain-CSS mirror of astryx-theme.ts (frozen dark slots). Edit the theme,
52
+ * not this file, then re-mirror. Unlayered by design so it beats core layers;
53
+ * zero !important anywhere. */
24
54
  :root {
25
55
  color-scheme: dark;
56
+ background-color: #282A36;
26
57
 
27
58
  --dracula-bg: #282A36;
28
59
  --dracula-current-line: #6272A4;
@@ -53,34 +84,255 @@
53
84
  --tile-row: 96px;
54
85
  --border-radius: 5px;
55
86
  --radius-element: 5px;
87
+ --radius-inner: 4px;
88
+ --radius-container: 5px;
89
+ --radius-page: 5px;
90
+ --radius-chat: 5px;
56
91
  --font-family-mono: 'JetBrains Mono', ui-monospace, monospace;
92
+ --font-family-body: 'JetBrains Mono', monospace;
93
+ --font-family-heading: 'JetBrains Mono', monospace;
94
+ --font-family-code: 'JetBrains Mono', monospace;
95
+ --font-weight-normal: 400;
96
+ --font-weight-medium: 500;
97
+ --font-weight-semibold: 600;
98
+ --font-weight-bold: 700;
99
+ --font-size-4xs: 0.375rem;
100
+ --font-size-3xs: 0.4375rem;
101
+ --font-size-2xs: 0.5rem;
102
+ --font-size-xs: 0.625rem;
103
+ --font-size-sm: 0.75rem;
104
+ --font-size-base: 14px;
105
+ --font-size-lg: 1.0625rem;
106
+ --font-size-xl: 1.25rem;
107
+ --font-size-2xl: 1.5rem;
108
+ --font-size-3xl: 1.8125rem;
109
+ --font-size-4xl: 2.1875rem;
110
+ --font-size-5xl: 2.625rem;
111
+ --font-size-h1: 24px;
112
+ --font-size-h2: 20px;
113
+ --font-size-h3: 16px;
114
+ --font-size-h4: 14px;
115
+ --font-size-h5: 13px;
116
+ --font-size-h6: 12px;
117
+ --text-heading-1-size: var(--font-size-2xl);
118
+ --text-heading-1-weight: var(--font-weight-normal);
119
+ --text-heading-1-leading: 1.3333;
120
+ --text-heading-2-size: var(--font-size-xl);
121
+ --text-heading-2-weight: var(--font-weight-normal);
122
+ --text-heading-2-leading: 1.4;
123
+ --text-heading-3-size: var(--font-size-lg);
124
+ --text-heading-3-weight: var(--font-weight-normal);
125
+ --text-heading-3-leading: 1.4118;
126
+ --text-heading-4-size: var(--font-size-base);
127
+ --text-heading-4-weight: var(--font-weight-normal);
128
+ --text-heading-4-leading: 1.4286;
129
+ --text-heading-5-size: var(--font-size-sm);
130
+ --text-heading-5-weight: var(--font-weight-normal);
131
+ --text-heading-5-leading: 1.6667;
132
+ --text-heading-6-size: var(--font-size-xs);
133
+ --text-heading-6-weight: var(--font-weight-normal);
134
+ --text-heading-6-leading: 1.6;
135
+ --text-body-size: var(--font-size-base);
136
+ --text-body-weight: var(--font-weight-normal);
137
+ --text-body-leading: 1.4286;
138
+ --text-large-size: var(--font-size-lg);
139
+ --text-large-weight: var(--font-weight-semibold);
140
+ --text-large-leading: 1.4118;
141
+ --text-label-size: var(--font-size-base);
142
+ --text-label-weight: var(--font-weight-semibold);
143
+ --text-label-leading: 1.4286;
144
+ --text-code-size: var(--font-size-base);
145
+ --text-code-weight: var(--font-weight-normal);
146
+ --text-code-leading: 1.4286;
147
+ --text-supporting-size: var(--font-size-sm);
148
+ --text-supporting-weight: var(--font-weight-normal);
149
+ --text-supporting-leading: 1.6667;
150
+ --text-display-1-size: var(--font-size-5xl);
151
+ --text-display-1-weight: var(--font-weight-normal);
152
+ --text-display-1-leading: 1.2381;
153
+ --text-display-2-size: var(--font-size-4xl);
154
+ --text-display-2-weight: var(--font-weight-normal);
155
+ --text-display-2-leading: 1.2571;
156
+ --text-display-3-size: var(--font-size-3xl);
157
+ --text-display-3-weight: var(--font-weight-normal);
158
+ --text-display-3-leading: 1.3793;
159
+ --duration-fast-min: 130ms;
160
+ --duration-fast: 175ms;
161
+ --duration-fast-max: 235ms;
162
+ --duration-medium-min: 310ms;
163
+ --duration-medium: 410ms;
164
+ --duration-medium-max: 545ms;
165
+ --duration-slow-min: 730ms;
166
+ --duration-slow: 975ms;
167
+ --duration-slow-max: 1300ms;
168
+ --ease-standard: cubic-bezier(0.24, 1, 0.4, 1);
169
+ --shadow-low: 0 2px 4px #191A210D, 0 4px 8px #191A211A;
170
+ --shadow-med: 0 2px 4px #191A210D, 0 4px 12px #191A211A;
171
+ --shadow-high: 0 4px 6px #191A211A, 0 12px 24px #191A2126;
172
+ --shadow-inset-hover: inset 0px 0px 0px 2px #6272A430;
173
+ --shadow-inset-selected: inset 0px 0px 0px 2px #BD93F930;
174
+ --shadow-inset-success: inset 0px 0px 0px 2px #50FA7B30;
175
+ --shadow-inset-warning: inset 0px 0px 0px 2px #F1FA8C30;
176
+ --shadow-inset-error: inset 0px 0px 0px 2px #FF555530;
57
177
 
58
- --color-background: #282A36;
178
+ --color-background-body: #282A36;
179
+ --color-background: var(--color-background-body);
180
+ --color-background-surface: #343746;
181
+ --color-background-card: #343746;
182
+ --color-background-popover: #424450;
183
+ --color-background-muted: #44475A;
184
+ --color-background-inverted: #F8F8F2;
185
+ --color-background-error-inverted: #FFD5CC;
186
+ --color-overlay: #191A21CC;
187
+ --color-overlay-hover: #0000001F;
188
+ --color-overlay-pressed: #00000033;
189
+ --color-border: #6272A4;
190
+ --color-border-emphasized: #9AA1BC;
191
+ --color-neutral: #F8F8F21A;
192
+ --color-tint-hover: #FFFFFF;
193
+ --color-on-dark: #F8F8F2;
194
+ --color-on-light: #000000;
195
+ --color-shadow: #21222C;
196
+ --color-track: #6272A4;
197
+ --color-skeleton: #6272A4;
59
198
  --color-accent: #BD93F9;
60
199
  --color-success: #50FA7B;
61
200
  --color-error: #FF5555;
62
201
  --color-warning: #F1FA8C;
63
202
  --color-info: #8BE9FD;
203
+ --color-accent-muted: var(--color-background-purple);
204
+ --color-success-muted: var(--color-background-green);
205
+ --color-warning-muted: var(--color-background-yellow);
206
+ --color-error-muted: var(--color-background-red);
207
+ --color-info-muted: var(--color-background-cyan);
64
208
  --color-text-primary: #F8F8F2;
65
209
  --color-text-secondary: #9AA1BC;
66
210
  --color-text-disabled: #6272A4;
67
211
  --color-text-accent: #BD93F9;
68
- --color-border: #6272A4;
212
+ --color-text-highlight: #F8F8F2;
213
+ --color-text-paragraph: #B0B3C4;
214
+ --color-text-base: #9AA1BC;
215
+ --color-text-base-muted: #8288A6;
216
+ --color-text-subdue: #4C5067;
217
+ --color-icon-primary: #F8F8F2;
218
+ --color-icon-secondary: #9AA1BC;
219
+ --color-icon-disabled: #6272A4;
220
+ --color-icon-accent: #BD93F9;
221
+ --color-on-accent: #21222C;
222
+ --color-on-success: #21222C;
223
+ --color-on-warning: #21222C;
224
+ --color-on-error: #21222C;
225
+ --color-on-info: #21222C;
226
+ --color-functional-red: #DE5735;
227
+ --color-functional-orange: #A39514;
228
+ --color-functional-green: #089108;
229
+ --color-functional-cyan: #0081D6;
230
+ --color-functional-purple: #815CD6;
231
+ --color-background-blue: #6272A41A;
232
+ --color-border-blue: #6272A44D;
233
+ --color-icon-blue: #6272A4;
234
+ --color-text-blue: #6272A4;
235
+ --color-background-cyan: #8BE9FD1A;
236
+ --color-border-cyan: #8BE9FD4D;
237
+ --color-icon-cyan: #8BE9FD;
238
+ --color-text-cyan: #8BE9FD;
239
+ --color-background-gray: #9AA1BC1A;
240
+ --color-border-gray: #9AA1BC4D;
241
+ --color-icon-gray: #9AA1BC;
242
+ --color-text-gray: #9AA1BC;
243
+ --color-background-green: #50FA7B1A;
244
+ --color-border-green: #50FA7B4D;
245
+ --color-icon-green: #50FA7B;
246
+ --color-text-green: #50FA7B;
247
+ --color-background-orange: #FFB86C1A;
248
+ --color-border-orange: #FFB86C4D;
249
+ --color-icon-orange: #FFB86C;
250
+ --color-text-orange: #FFB86C;
251
+ --color-background-pink: #FF79C61A;
252
+ --color-border-pink: #FF79C64D;
253
+ --color-icon-pink: #FF79C6;
254
+ --color-text-pink: #FF79C6;
255
+ --color-background-purple: #BD93F91A;
256
+ --color-border-purple: #BD93F94D;
257
+ --color-icon-purple: #BD93F9;
258
+ --color-text-purple: #BD93F9;
259
+ --color-background-red: #FF55551A;
260
+ --color-border-red: #FF55554D;
261
+ --color-icon-red: #FF5555;
262
+ --color-text-red: #FF5555;
263
+ --color-background-teal: #A4FFFF1A;
264
+ --color-border-teal: #A4FFFF4D;
265
+ --color-icon-teal: #A4FFFF;
266
+ --color-text-teal: #A4FFFF;
267
+ --color-background-yellow: #F1FA8C1A;
268
+ --color-border-yellow: #F1FA8C4D;
269
+ --color-icon-yellow: #F1FA8C;
270
+ --color-text-yellow: #F1FA8C;
271
+ --color-data-categorical-blue: #6272A4;
272
+ --color-data-categorical-orange: #FFB86C;
273
+ --color-data-categorical-green: #50FA7B;
274
+ --color-data-categorical-pink: #FF79C6;
275
+ --color-data-categorical-cyan: #8BE9FD;
276
+ --color-data-categorical-red: #FF5555;
277
+ --color-data-categorical-teal: #A4FFFF;
278
+ --color-data-categorical-indigo: #D6ACFF;
279
+ --color-data-neutral: #8C939B;
280
+ --color-data-purple-5: hsl(264.71 89.47% 28%);
281
+ --color-data-purple-4: hsl(264.71 89.47% 44%);
282
+ --color-data-purple-3: hsl(264.71 89.47% 60%);
283
+ --color-data-purple-2: hsl(264.71 89.47% 74%);
284
+ --color-data-purple-1: hsl(264.71 89.47% 88%);
285
+ --color-data-pink-5: hsl(325.52 100% 28%);
286
+ --color-data-pink-4: hsl(325.52 100% 44%);
287
+ --color-data-pink-3: hsl(325.52 100% 60%);
288
+ --color-data-pink-2: hsl(325.52 100% 74%);
289
+ --color-data-pink-1: hsl(325.52 100% 88%);
290
+ --color-data-red-5: hsl(0 100% 28%);
291
+ --color-data-red-4: hsl(0 100% 44%);
292
+ --color-data-red-3: hsl(0 100% 60%);
293
+ --color-data-red-2: hsl(0 100% 74%);
294
+ --color-data-red-1: hsl(0 100% 88%);
295
+ --color-data-orange-5: hsl(31.02 100% 28%);
296
+ --color-data-orange-4: hsl(31.02 100% 44%);
297
+ --color-data-orange-3: hsl(31.02 100% 60%);
298
+ --color-data-orange-2: hsl(31.02 100% 74%);
299
+ --color-data-orange-1: hsl(31.02 100% 88%);
300
+ --color-data-yellow-5: hsl(64.91 91.67% 28%);
301
+ --color-data-yellow-4: hsl(64.91 91.67% 44%);
302
+ --color-data-yellow-3: hsl(64.91 91.67% 60%);
303
+ --color-data-yellow-2: hsl(64.91 91.67% 74%);
304
+ --color-data-yellow-1: hsl(64.91 91.67% 88%);
305
+ --color-data-teal-5: hsl(180 100% 28%);
306
+ --color-data-teal-4: hsl(180 100% 44%);
307
+ --color-data-teal-3: hsl(180 100% 60%);
308
+ --color-data-teal-2: hsl(180 100% 74%);
309
+ --color-data-teal-1: hsl(180 100% 88%);
310
+ --color-data-blue-5: hsl(225.45 26.61% 28%);
311
+ --color-data-blue-4: hsl(225.45 26.61% 44%);
312
+ --color-data-blue-3: hsl(225.45 26.61% 60%);
313
+ --color-data-blue-2: hsl(225.45 26.61% 74%);
314
+ --color-data-blue-1: hsl(225.45 26.61% 88%);
315
+ --color-data-shamrock-5: hsl(135.18 94.44% 28%);
316
+ --color-data-shamrock-4: hsl(135.18 94.44% 44%);
317
+ --color-data-shamrock-3: hsl(135.18 94.44% 60%);
318
+ --color-data-shamrock-2: hsl(135.18 94.44% 74%);
319
+ --color-data-shamrock-1: hsl(135.18 94.44% 88%);
320
+ --color-data-gray-5: hsl(231.43 14.89% 28%);
321
+ --color-data-gray-4: hsl(231.43 14.89% 44%);
322
+ --color-data-gray-3: hsl(231.43 14.89% 60%);
323
+ --color-data-gray-2: hsl(231.43 14.89% 74%);
324
+ --color-data-gray-1: hsl(231.43 14.89% 88%);
69
325
  --color-widget-background: #343746;
70
326
  --color-widget-content-border: #6272A4;
71
327
  --color-widget-background-highlight: #44475A;
72
328
  --color-separator: #44475A;
73
329
  --color-popover-background: #424450;
74
330
  --color-popover-border: #6272A4;
75
- --color-graph-gridlines: #44475A;
76
331
  --color-progress-border: #44475A;
77
332
  --color-progress-value: #6272A4;
333
+ --color-vertical-progress-value: #6272A4;
334
+ --color-graph-gridlines: #44475A;
78
335
  --color-widget-shadow: #21222C;
79
- --color-text-highlight: #F8F8F2;
80
- --color-text-paragraph: #B0B3C4;
81
- --color-text-base: #9AA1BC;
82
- --color-text-base-muted: #8288A6;
83
- --color-text-subdue: #4C5067;
84
336
  --color-current-line: #6272A4;
85
337
  --color-selection: #44475A;
86
338
  --color-primary: #BD93F9;
@@ -88,34 +340,177 @@
88
340
  --color-negative: #FF5555;
89
341
  --color-tag-orange: #FFB86C;
90
342
  --color-tag-pink: #FF79C6;
343
+ --color-tag-cyan: #8BE9FD;
344
+ --color-tag-yellow: #F1FA8C;
345
+ --color-tag-green: #50FA7B;
346
+ --color-tag-purple: #BD93F9;
347
+ /* The token was named `tag-blue` and held purple. Dracula has no blue; the
348
+ * closest is Comment/Comment-dim, and Purple is what the rest of the kit
349
+ * already uses for a fourth accent -- so the VALUE was right and the NAME
350
+ * was the lie. Renamed, not repinned: a `blue` tag that paints purple is
351
+ * wrong in a way no contrast number catches, and a future author adding a
352
+ * real blue tag would have found the slot occupied. Verify no consumer
353
+ * returned with `grep -rn "tag-blue"` before shipping a rename. */
91
354
  }
92
355
 
93
- /* Dracula scrollbars: Selection thumb on transparent track, Current Line on
94
- * hover. Firefox uses the two properties; Chromium uses the pseudo-elements. */
95
- :root {
96
- scrollbar-width: thin;
97
- scrollbar-color: var(--dracula-selection) transparent;
356
+ /* Dracula scrollbars. The spec defines no scrollbar token, so these follow its
357
+ * UI Design Guidelines instead: the track is transparent, so the bar reads as
358
+ * a thumb only and picks up whatever tier it sits on — the document scroller
359
+ * shows the html/body paint, surface and card scrollers show their own
360
+ * surface instead of a page-background gutter inside the card. The
361
+ * resting thumb uses Current Line, which the spec designates for
362
+ * subtle borders and separators, and the hovered thumb promotes to Purple, the
363
+ * brand accent for anything interactive. Selection (#44475A) was the resting
364
+ * thumb before, at 1.5:1 against the background, which made the scrollbar
365
+ * effectively invisible.
366
+ *
367
+ * Engine split (verified 2026-09: Safari ignores `scrollbar-color` when
368
+ * `scrollbar-width: thin` is set, and `scrollbar-width` makes WebKit ignore
369
+ * the `::-webkit-scrollbar-*` rules — the two systems are mutually exclusive
370
+ * in Safari). So: no `scrollbar-width` anywhere (Safari falls back to overlay
371
+ * scrollbars that never take space; Chrome/Firefox size via the rules below),
372
+ * Firefox-only `scrollbar-color` guarded by `@supports`, WebKit-only sizing
373
+ * and hover, also guarded so Firefox never sees a pseudo-element.
374
+ * Safari overlay scrollbars ignore thumb width/border (system-drawn while
375
+ * scrolling); resting color still applies when the bar is persistent.
376
+ *
377
+ * Environment note: the shared headless browser paints overlay scrollbars
378
+ * (scrollbarWidth 0 even with content overflowing), so themed scrollbar
379
+ * rules cannot be verified there. Check them in a real Chrome against
380
+ * /templates and /bento: resting thumb Current Line, hover Purple. */
381
+ @supports (scrollbar-color: auto) and (not (-webkit-hyphens: none)) {
382
+ :root {
383
+ scrollbar-color: var(--dracula-current-line) transparent;
384
+ }
385
+ }
386
+
387
+ @supports selector(::-webkit-scrollbar) {
388
+ *::-webkit-scrollbar {
389
+ width: 10px;
390
+ height: 10px;
391
+ background: transparent;
392
+ }
393
+
394
+ *::-webkit-scrollbar-track,
395
+ *::-webkit-scrollbar-corner {
396
+ background: transparent;
397
+ }
398
+
399
+ *::-webkit-scrollbar-thumb {
400
+ background-color: var(--dracula-current-line);
401
+ border-radius: 5px;
402
+ border: 2px solid transparent;
403
+ background-clip: content-box;
404
+ }
405
+
406
+ *::-webkit-scrollbar-thumb:hover {
407
+ background-color: var(--dracula-purple);
408
+ border: 2px solid transparent;
409
+ background-clip: content-box;
410
+ }
98
411
  }
99
412
 
100
- *::-webkit-scrollbar {
101
- width: 10px;
102
- height: 10px;
413
+ /* A resizable SideNav reports exactly 1px of horizontal overflow: the resize
414
+ * handle renders 1px wider than its own box, and the side-nav panel scrolls
415
+ * both axes, so a permanent horizontal scrollbar paints under the rail even
416
+ * though nothing overflows visually. Clip the offending pixel on the handle
417
+ * itself rather than on the panel: a collapsed rail renders its child popovers
418
+ * as descendants of that panel, and they only escape the clip because their
419
+ * root happens to be position:fixed. Clipping the panel would break them the
420
+ * day an ancestor gains a transform. The handle contains nothing but its pill,
421
+ * which sits inside its box, so clipping it costs nothing. Needed until the
422
+ * resize handle stops reporting the extra pixel. Re-verified against
423
+ * @astryxdesign/core 0.3.0: without the clip the hit area's half-pixel inline
424
+ * shift leaves content at 300.5px in a 300px rail, and the LayoutPanel
425
+ * (overflow:auto on both axes) reports scrollWidth 301 > 300 — the permanent
426
+ * bar is back. Keep. */
427
+ .astryx-app-shell-sidenav .astryx-resize-handle {
428
+ overflow: hidden;
103
429
  }
104
430
 
105
- *::-webkit-scrollbar-track,
106
- *::-webkit-scrollbar-corner {
107
- background: transparent;
431
+ /* Astryx leaves the resize handle's drag surface translated -50% along the
432
+ * block axis without ever positioning it at top:50%, so it spans only the TOP
433
+ * HALF of the rail: the pointer reads col-resize and dragging works above the
434
+ * midpoint, and nothing at all happens below it, even though the pill is drawn
435
+ * straddling that boundary. The pill sibling carries a correct top:50%, which
436
+ * is exactly what the drag surface is missing. Re-verified against
437
+ * @astryxdesign/core 0.3.0: hitAreaHorizontal is still top:0/bottom:0 while
438
+ * hitAreaOffsetX translates Y by -50%, so without this the drag surface spans
439
+ * [-50%,+50%] of the rail and the bottom half is dead (elementFromPoint hits
440
+ * the handle, which has no pointer handler). Upstream workaround; drop it if
441
+ * the component starts positioning its own hit area. */
442
+ .astryx-app-shell-sidenav
443
+ .astryx-resize-handle
444
+ > :not(.astryx-resize-handle-pill) {
445
+ top: 50%;
446
+ bottom: auto;
447
+ height: 100%;
108
448
  }
109
449
 
110
- *::-webkit-scrollbar-thumb {
111
- background: var(--dracula-selection);
112
- border-radius: 5px;
113
- border: 2px solid transparent;
114
- background-clip: content-box;
450
+ /* Everything below this line is UNLAYERED on purpose, and that is load-bearing,
451
+ * not a style choice. Unlayered author rules beat every core LAYER regardless of
452
+ * specificity — verified in Chromium against the declared order
453
+ * (`@layer reset, astryx-base, astryx-theme`, vite.config.ts) where an
454
+ * unlayered (0,1,0) rule outranked core's (0,4,0) `:not(#):not(#):not(#)` rule
455
+ * inside `@layer astryx-base`. scripts/check.ts asserts `@layer` count is 0 in
456
+ * this file, so the next agent cannot "tidy" these rules into a layer and
457
+ * silently break all of them at once.
458
+ *
459
+ * BOUNDARY, because the sentence above is true of layers and stops being true
460
+ * one step out: unlayered does NOT beat an INLINE style. But that is
461
+ * PER-PROPERTY, not per-element, and the difference decides real work:
462
+ * - Core never assigns `box-shadow` inline — 0 occurrences across core. A
463
+ * stylesheet RING therefore paints normally, over whatever background core
464
+ * set. `--shadow-inset-selected` is reachable with no !important, no core
465
+ * change and no version bump.
466
+ * - Core DOES assign `el.style.backgroundColor` on selected table rows
467
+ * (dist/Table/plugins/selection/useTableSelection.js:79). So a kit WASH on
468
+ * that state is unreachable: the exits are `!important` (banned by the gate
469
+ * below) or a core change (deferred by the 0.3.0 version ruling).
470
+ * Stated here so the next agent does not discover it the hard way, and so nobody
471
+ * reads the inline-style note as a blanket blocker and avoids box-shadow too.
472
+
473
+ /* Reduced motion. Core gates its per-component motion on
474
+ * `@media (prefers-reduced-motion: reduce)`, but each rule is inside
475
+ * `@layer astryx-base`, so an unlayered blanket here wins on layer order alone —
476
+ * no !important, no specificity arms race. Deliberately narrow: it touches only
477
+ * duration and iteration count, and NEVER `scrollbar-width`, because core's
478
+ * carousel relies on a layered `scrollbar-width: none` to hide its bar and a
479
+ * universal rule here would un-hide it (the one scrollbar-width in the stack). */
480
+ @media (prefers-reduced-motion: reduce) {
481
+ *,
482
+ *::before,
483
+ *::after {
484
+ animation-duration: 0.01ms;
485
+ animation-iteration-count: 1;
486
+ transition-duration: 0.01ms;
487
+ }
488
+
489
+ html {
490
+ scroll-behavior: auto;
491
+ }
115
492
  }
116
493
 
117
- *::-webkit-scrollbar-thumb:hover {
118
- background: var(--dracula-current-line);
119
- border: 2px solid transparent;
120
- background-clip: content-box;
494
+ /* Sticky-header focus and in-page anchor offset. AppShell in `auto` mode pins
495
+ * the header with `position: sticky` (dist/AppShell/AppShell.js:398, 630-632),
496
+ * so anything scrolled to `block: 'start'` lands underneath it — the focus ring
497
+ * renders perfectly and is simply off-screen. Two paths hit this: keyboard focus
498
+ * on a long `height="auto"` documentation page, and the four in-page outline
499
+ * navigations that call `scrollIntoView({ block: 'start' })`
500
+ * (shared/chaptered-doc.tsx, documentation-design.tsx, documentation-technical.tsx,
501
+ * product-gallery.tsx).
502
+ *
503
+ * Declared on the TARGET, not the scroller, and that is the deliberate choice.
504
+ * `scroll-padding-top` on `:root` cannot work here: a document scroller's
505
+ * scroll-padding must be declared on the root, but core measures the header and
506
+ * publishes it as `--appshell-header-height` on the SHELL element
507
+ * (AppShell.js:276-291, via ResizeObserver), and a custom property set on a
508
+ * descendant never reaches the root. A static number at `:root` would be exactly
509
+ * the unmeasured guess this repo refuses to ship. `scroll-margin-block` on the
510
+ * target resolves at a DESCENDANT, so it reads core's real measured height —
511
+ * correct at every viewport width and in both AppShell modes — with
512
+ * `--spacing-8` only as the fallback for pages that have no sticky header at all. */
513
+ :where(:focus-visible, h1, h2, h3, h4, [id]) {
514
+ scroll-margin-block: var(--appshell-header-height, var(--spacing-8))
515
+ var(--space-viewport);
121
516
  }