astryx-dracula 0.2.1 → 0.3.1

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 +17 -12
  3. package/USAGE.md +30 -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 +181 -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 +102 -0
  29. package/shared/sso-icons.tsx +30 -0
  30. package/templates/ai-chat-landing.tsx +66 -33
  31. package/templates/ai-chat.tsx +36 -19
  32. package/templates/blank.tsx +4 -3
  33. package/templates/centered-hero.tsx +14 -125
  34. package/templates/classic-gallery.tsx +20 -66
  35. package/templates/contact-form.tsx +59 -39
  36. package/templates/dashboard-portfolio.tsx +143 -226
  37. package/templates/dashboard.tsx +122 -122
  38. package/templates/detail-page.tsx +95 -91
  39. package/templates/documentation-design.template.mjs +1 -1
  40. package/templates/documentation-design.tsx +83 -96
  41. package/templates/documentation-technical.template.mjs +1 -1
  42. package/templates/documentation-technical.tsx +29 -19
  43. package/templates/documentation.template.mjs +1 -1
  44. package/templates/documentation.tsx +52 -74
  45. package/templates/editor.tsx +112 -84
  46. package/templates/file-explorer.tsx +131 -84
  47. package/templates/form-two-column.tsx +61 -125
  48. package/templates/gallery-hero.tsx +15 -171
  49. package/templates/ide.tsx +22 -20
  50. package/templates/incident-console.tsx +104 -70
  51. package/templates/kanban-board.tsx +99 -42
  52. package/templates/library.template.mjs +2 -2
  53. package/templates/library.tsx +37 -42
  54. package/templates/login-card.tsx +58 -87
  55. package/templates/login-split.tsx +88 -126
  56. package/templates/login-sso.tsx +105 -64
  57. package/templates/login.tsx +107 -53
  58. package/templates/messaging-shell.tsx +171 -144
  59. package/templates/mixed-gallery.tsx +29 -130
  60. package/templates/payment-form.tsx +368 -290
  61. package/templates/product-detail.tsx +39 -58
  62. package/templates/product-gallery.tsx +30 -41
  63. package/templates/product-tour.template.mjs +6 -0
  64. package/templates/product-tour.tsx +916 -0
  65. package/templates/settings-dialog.tsx +135 -282
  66. package/templates/settings-sidebar.tsx +178 -268
  67. package/templates/settings.tsx +41 -14
  68. package/templates/shell-nav.tsx +31 -24
  69. package/templates/shell-side-nav.tsx +29 -20
  70. package/templates/shell-top-nav.tsx +80 -44
  71. package/templates/side-gallery.tsx +38 -94
  72. package/templates/table-grouped.tsx +62 -34
  73. package/templates/table-page-chart.tsx +23 -302
  74. package/templates/table-page-heatmap-status.tsx +156 -92
  75. package/templates/table-page-shoe-store-heatmap.tsx +18 -293
  76. package/templates/table-page.tsx +23 -7
  77. package/templates/table.tsx +23 -8
  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 +299 -237
  82. package/theme.css +76 -14
  83. package/tokens.css +433 -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,185 @@
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
+ /* Renamed in 0.3.0 from `tag-blue`, which held this same #BD93F9. Dracula
348
+ * has no blue; Purple is what the rest of the kit already uses for a fourth
349
+ * accent, so the VALUE was right and the NAME was the lie. Renamed rather
350
+ * than repinned, because a `blue` tag that paints purple is wrong in a way
351
+ * no contrast number catches, and a future author adding a real blue tag
352
+ * would have found the slot occupied.
353
+ *
354
+ * Migrating: s/--color-tag-blue/--color-tag-purple/ in your own CSS. The
355
+ * rename is silent -- a stale reference resolves to nothing rather than
356
+ * erroring -- and it ships with no deprecated alias, so nothing falls back
357
+ * for you. Note that `blue` is also the name of six OTHER tokens that really
358
+ * are Comment #6272A4 (--color-background-blue, -border-, -icon-, -text-,
359
+ * --color-data-categorical-blue, and the --color-data-blue-1..5 ramp), so a
360
+ * repo-wide replace on "blue" repoints all six at Purple. Target the tag
361
+ * token by name. */
91
362
  }
92
363
 
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;
364
+ /* Dracula scrollbars. The spec defines no scrollbar token, so these follow its
365
+ * UI Design Guidelines instead: the track is transparent, so the bar reads as
366
+ * a thumb only and picks up whatever tier it sits on — the document scroller
367
+ * shows the html/body paint, surface and card scrollers show their own
368
+ * surface instead of a page-background gutter inside the card. The
369
+ * resting thumb uses Current Line, which the spec designates for
370
+ * subtle borders and separators, and the hovered thumb promotes to Purple, the
371
+ * brand accent for anything interactive. Selection (#44475A) was the resting
372
+ * thumb before, at 1.5:1 against the background, which made the scrollbar
373
+ * effectively invisible.
374
+ *
375
+ * Engine split (verified 2026-09: Safari ignores `scrollbar-color` when
376
+ * `scrollbar-width: thin` is set, and `scrollbar-width` makes WebKit ignore
377
+ * the `::-webkit-scrollbar-*` rules — the two systems are mutually exclusive
378
+ * in Safari). So: no `scrollbar-width` anywhere (Safari falls back to overlay
379
+ * scrollbars that never take space; Chrome/Firefox size via the rules below),
380
+ * Firefox-only `scrollbar-color` guarded by `@supports`, WebKit-only sizing
381
+ * and hover, also guarded so Firefox never sees a pseudo-element.
382
+ * Safari overlay scrollbars ignore thumb width/border (system-drawn while
383
+ * scrolling); resting color still applies when the bar is persistent.
384
+ *
385
+ * Environment note: the shared headless browser paints overlay scrollbars
386
+ * (scrollbarWidth 0 even with content overflowing), so themed scrollbar
387
+ * rules cannot be verified there. Check them in a real Chrome against
388
+ * /templates and /bento: resting thumb Current Line, hover Purple. */
389
+ @supports (scrollbar-color: auto) and (not (-webkit-hyphens: none)) {
390
+ :root {
391
+ scrollbar-color: var(--dracula-current-line) transparent;
392
+ }
393
+ }
394
+
395
+ @supports selector(::-webkit-scrollbar) {
396
+ *::-webkit-scrollbar {
397
+ width: 10px;
398
+ height: 10px;
399
+ background: transparent;
400
+ }
401
+
402
+ *::-webkit-scrollbar-track,
403
+ *::-webkit-scrollbar-corner {
404
+ background: transparent;
405
+ }
406
+
407
+ *::-webkit-scrollbar-thumb {
408
+ background-color: var(--dracula-current-line);
409
+ border-radius: 5px;
410
+ border: 2px solid transparent;
411
+ background-clip: content-box;
412
+ }
413
+
414
+ *::-webkit-scrollbar-thumb:hover {
415
+ background-color: var(--dracula-purple);
416
+ border: 2px solid transparent;
417
+ background-clip: content-box;
418
+ }
98
419
  }
99
420
 
100
- *::-webkit-scrollbar {
101
- width: 10px;
102
- height: 10px;
421
+ /* A resizable SideNav reports exactly 1px of horizontal overflow: the resize
422
+ * handle renders 1px wider than its own box, and the side-nav panel scrolls
423
+ * both axes, so a permanent horizontal scrollbar paints under the rail even
424
+ * though nothing overflows visually. Clip the offending pixel on the handle
425
+ * itself rather than on the panel: a collapsed rail renders its child popovers
426
+ * as descendants of that panel, and they only escape the clip because their
427
+ * root happens to be position:fixed. Clipping the panel would break them the
428
+ * day an ancestor gains a transform. The handle contains nothing but its pill,
429
+ * which sits inside its box, so clipping it costs nothing. Needed until the
430
+ * resize handle stops reporting the extra pixel. Re-verified against
431
+ * @astryxdesign/core 0.3.0: without the clip the hit area's half-pixel inline
432
+ * shift leaves content at 300.5px in a 300px rail, and the LayoutPanel
433
+ * (overflow:auto on both axes) reports scrollWidth 301 > 300 — the permanent
434
+ * bar is back. Keep. */
435
+ .astryx-app-shell-sidenav .astryx-resize-handle {
436
+ overflow: hidden;
103
437
  }
104
438
 
105
- *::-webkit-scrollbar-track,
106
- *::-webkit-scrollbar-corner {
107
- background: transparent;
439
+ /* Astryx leaves the resize handle's drag surface translated -50% along the
440
+ * block axis without ever positioning it at top:50%, so it spans only the TOP
441
+ * HALF of the rail: the pointer reads col-resize and dragging works above the
442
+ * midpoint, and nothing at all happens below it, even though the pill is drawn
443
+ * straddling that boundary. The pill sibling carries a correct top:50%, which
444
+ * is exactly what the drag surface is missing. Re-verified against
445
+ * @astryxdesign/core 0.3.0: hitAreaHorizontal is still top:0/bottom:0 while
446
+ * hitAreaOffsetX translates Y by -50%, so without this the drag surface spans
447
+ * [-50%,+50%] of the rail and the bottom half is dead (elementFromPoint hits
448
+ * the handle, which has no pointer handler). Upstream workaround; drop it if
449
+ * the component starts positioning its own hit area. */
450
+ .astryx-app-shell-sidenav
451
+ .astryx-resize-handle
452
+ > :not(.astryx-resize-handle-pill) {
453
+ top: 50%;
454
+ bottom: auto;
455
+ height: 100%;
108
456
  }
109
457
 
110
- *::-webkit-scrollbar-thumb {
111
- background: var(--dracula-selection);
112
- border-radius: 5px;
113
- border: 2px solid transparent;
114
- background-clip: content-box;
458
+ /* Everything below this line is UNLAYERED on purpose, and that is load-bearing,
459
+ * not a style choice. Unlayered author rules beat every core LAYER regardless of
460
+ * specificity — verified in Chromium against the declared order
461
+ * (`@layer reset, astryx-base, astryx-theme`, vite.config.ts) where an
462
+ * unlayered (0,1,0) rule outranked core's (0,4,0) `:not(#):not(#):not(#)` rule
463
+ * inside `@layer astryx-base`. scripts/check.ts asserts `@layer` count is 0 in
464
+ * this file, so the next agent cannot "tidy" these rules into a layer and
465
+ * silently break all of them at once.
466
+ *
467
+ * BOUNDARY, because the sentence above is true of layers and stops being true
468
+ * one step out: unlayered does NOT beat an INLINE style. But that is
469
+ * PER-PROPERTY, not per-element, and the difference decides real work:
470
+ * - Core never assigns `box-shadow` inline — 0 occurrences across core. A
471
+ * stylesheet RING therefore paints normally, over whatever background core
472
+ * set. `--shadow-inset-selected` is reachable with no !important, no core
473
+ * change and no version bump.
474
+ * - Core DOES assign `el.style.backgroundColor` on selected table rows
475
+ * (dist/Table/plugins/selection/useTableSelection.js:79). So a kit WASH on
476
+ * that state is unreachable: the exits are `!important` (banned by the gate
477
+ * below) or a core change (deferred by the 0.3.0 version ruling).
478
+ * Stated here so the next agent does not discover it the hard way, and so nobody
479
+ * reads the inline-style note as a blanket blocker and avoids box-shadow too.
480
+
481
+ /* Reduced motion. Core gates its per-component motion on
482
+ * `@media (prefers-reduced-motion: reduce)`, but each rule is inside
483
+ * `@layer astryx-base`, so an unlayered blanket here wins on layer order alone —
484
+ * no !important, no specificity arms race. Deliberately narrow: it touches only
485
+ * duration and iteration count, and NEVER `scrollbar-width`, because core's
486
+ * carousel relies on a layered `scrollbar-width: none` to hide its bar and a
487
+ * universal rule here would un-hide it (the one scrollbar-width in the stack). */
488
+ @media (prefers-reduced-motion: reduce) {
489
+ *,
490
+ *::before,
491
+ *::after {
492
+ animation-duration: 0.01ms;
493
+ animation-iteration-count: 1;
494
+ transition-duration: 0.01ms;
495
+ }
496
+
497
+ html {
498
+ scroll-behavior: auto;
499
+ }
115
500
  }
116
501
 
117
- *::-webkit-scrollbar-thumb:hover {
118
- background: var(--dracula-current-line);
119
- border: 2px solid transparent;
120
- background-clip: content-box;
502
+ /* Sticky-header focus and in-page anchor offset. AppShell in `auto` mode pins
503
+ * the header with `position: sticky` (dist/AppShell/AppShell.js:398, 630-632),
504
+ * so anything scrolled to `block: 'start'` lands underneath it — the focus ring
505
+ * renders perfectly and is simply off-screen. Two paths hit this: keyboard focus
506
+ * on a long `height="auto"` documentation page, and the four in-page outline
507
+ * navigations that call `scrollIntoView({ block: 'start' })`
508
+ * (shared/chaptered-doc.tsx, documentation-design.tsx, documentation-technical.tsx,
509
+ * product-gallery.tsx).
510
+ *
511
+ * Declared on the TARGET, not the scroller, and that is the deliberate choice.
512
+ * `scroll-padding-top` on `:root` cannot work here: a document scroller's
513
+ * scroll-padding must be declared on the root, but core measures the header and
514
+ * publishes it as `--appshell-header-height` on the SHELL element
515
+ * (AppShell.js:276-291, via ResizeObserver), and a custom property set on a
516
+ * descendant never reaches the root. A static number at `:root` would be exactly
517
+ * the unmeasured guess this repo refuses to ship. `scroll-margin-block` on the
518
+ * target resolves at a DESCENDANT, so it reads core's real measured height —
519
+ * correct at every viewport width and in both AppShell modes — with
520
+ * `--spacing-8` only as the fallback for pages that have no sticky header at all. */
521
+ :where(:focus-visible, h1, h2, h3, h4, [id]) {
522
+ scroll-margin-block: var(--appshell-header-height, var(--spacing-8))
523
+ var(--space-viewport);
121
524
  }