@sableui/react 0.5.0 → 0.6.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 (82) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +135 -0
  3. package/CONTRIBUTING.md +1 -1
  4. package/THIRD_PARTY_NOTICES.md +11 -0
  5. package/bin/sable-lint.mjs +2 -1
  6. package/bin/sable.mjs +3 -2
  7. package/dist/{_contract-Cn3lUpih.d.ts → _contract-C2M1SjBf.d.ts} +20 -2
  8. package/dist/blocks/index.js +19 -51
  9. package/dist/{chunk-RCLKXF2N.js → chunk-7QSDYBXO.js} +610 -30
  10. package/dist/{chunk-GU3T7SBA.js → chunk-EDXLM7YL.js} +1 -1
  11. package/dist/chunk-FKZXAFIY.js +12 -0
  12. package/dist/{chunk-OQACYT2D.js → chunk-NETUFI34.js} +23 -1
  13. package/dist/{chunk-ZBZMKOO7.js → chunk-VVW4UCDI.js} +3 -3
  14. package/dist/foundations/index.js +6 -0
  15. package/dist/index.d.ts +127 -11
  16. package/dist/index.js +23 -13
  17. package/dist/ios/index.d.ts +6 -1
  18. package/dist/ios/index.js +4 -3
  19. package/dist/ios/styles.css +9 -0
  20. package/dist/patterns/index.js +22 -28
  21. package/dist/presets/index.d.ts +108 -5
  22. package/dist/presets/index.js +239 -6
  23. package/dist/styles.css +50 -0
  24. package/dist/{theme-provider-CGeiusgu.d.ts → theme-provider-kQszHYji.d.ts} +1 -1
  25. package/dist/themes/atelier/atelier.css +31 -3
  26. package/dist/themes/atelier/index.d.ts +1 -1
  27. package/dist/themes/ember/ember.css +38 -7
  28. package/dist/themes/ember/index.d.ts +1 -1
  29. package/dist/themes/index.d.ts +2 -2
  30. package/dist/themes/meridian/index.d.ts +1 -1
  31. package/dist/themes/meridian/meridian.css +33 -4
  32. package/dist/themes/poppy/index.d.ts +1 -1
  33. package/dist/themes/poppy/poppy.css +28 -0
  34. package/dist/themes/prism/index.d.ts +1 -1
  35. package/dist/themes/prism/prism.css +26 -0
  36. package/dist/themes/sable/index.d.ts +1 -1
  37. package/dist/themes/sable/sable.css +24 -11
  38. package/dist/themes/verdant/index.d.ts +1 -1
  39. package/dist/themes/verdant/verdant.css +34 -4
  40. package/llms/blocks/danger-zone-block.md +1 -1
  41. package/llms/blocks/event-card-block.md +1 -1
  42. package/llms/blocks/feedback-form-block.md +1 -1
  43. package/llms/blocks/invite-team-block.md +1 -1
  44. package/llms/blocks/media-player-block.md +3 -16
  45. package/llms/blocks/notification-list-block.md +3 -3
  46. package/llms/blocks/product-card-block.md +3 -17
  47. package/llms/blocks/profile-card-block.md +4 -17
  48. package/llms/blocks/profile-form-block.md +2 -2
  49. package/llms/blocks/team-members-block.md +1 -1
  50. package/llms/blocks/testimonial-block.md +1 -1
  51. package/llms/components/avatar.md +13 -1
  52. package/llms/components/card.md +1 -1
  53. package/llms/components/gradient-art.md +80 -0
  54. package/llms/components/sheet.md +1 -1
  55. package/llms/foundations/accessibility.md +1 -0
  56. package/llms/patterns/auth-page.md +5 -19
  57. package/llms/patterns/brand-landing-page.md +1 -1
  58. package/llms/patterns/chat-panel.md +1 -1
  59. package/llms/patterns/dashboard-page.md +4 -4
  60. package/llms/patterns/feed-page.md +4 -4
  61. package/llms/patterns/landing-page.md +1 -1
  62. package/llms/patterns/list-detail-page.md +3 -2
  63. package/llms/patterns/settings-page.md +3 -3
  64. package/llms/reference/token-intent.md +9 -1
  65. package/llms/tasks/auditing-an-app.md +6 -0
  66. package/llms/tasks/building-an-ios-prototype.md +3 -1
  67. package/llms/tasks/converting-an-app.md +1 -1
  68. package/llms/tasks/creating-a-preset.md +24 -0
  69. package/llms/tasks/reviewing-a-design.md +9 -0
  70. package/llms/tasks/using-presets.md +6 -1
  71. package/llms-full.txt +201 -104
  72. package/llms.txt +3 -2
  73. package/package.json +9 -7
  74. package/registry.json +279 -30
  75. package/scripts/design-presets.mjs +27 -4
  76. package/scripts/grade.mjs +2 -1
  77. package/scripts/label-contrast.mjs +138 -0
  78. package/scripts/lint-rules.mjs +23 -2
  79. package/skills/sable/SKILL.md +3 -1
  80. package/targets/ios.json +18 -2
  81. package/third-party/paper-shaders-LICENSE.txt +201 -0
  82. package/third-party/paper-shaders-NOTICE.txt +5 -0
@@ -110,14 +110,18 @@
110
110
  --accent: oklch(93% 0 0);
111
111
  --accent-foreground: #000000;
112
112
  --destructive: oklch(50% 0.18 22);
113
+ --destructive-text: oklch(50% 0.18 22);
113
114
  --destructive-foreground: #ffffff;
114
115
  --success: oklch(50% 0.10 173);
116
+ --success-text: oklch(50% 0.10 173);
115
117
  --success-foreground: #ffffff;
116
118
  --tone-standard: oklch(45% 0 0);
117
119
  --tone-collectible: oklch(46% 0.11 300);
118
120
  --tone-edition: oklch(46% 0.09 173);
119
121
  --tone-info: oklch(47% 0.10 250);
122
+ --tone-info-text: oklch(47% 0.10 250);
120
123
  --tone-warning: oklch(52% 0.12 52);
124
+ --tone-warning-text: oklch(52% 0.12 52);
121
125
  --highlight: oklch(93% 0 0);
122
126
  --tone-standard-foreground: #ffffff;
123
127
  --tone-collectible-foreground: #ffffff;
@@ -187,15 +191,20 @@
187
191
  --secondary-foreground: #ffffff;
188
192
  --accent: oklch(28% 0 0); /* above --popover: a hover row or chip inside a menu/dialog must still read */
189
193
  --accent-foreground: #ffffff;
190
- --destructive: oklch(66% 0.19 22);
191
- --destructive-foreground: oklch(13% 0 0);
192
- --success: oklch(72% 0.12 173);
194
+ /* Status colours come in two: the fill carries a label (destructive sits low under white ink), and `-text` is the same colour drawn on a surface — text, borders, rings — kept light enough to read on a dark card. */
195
+ --destructive: oklch(58.5% 0.19 22);
196
+ --destructive-text: oklch(77.5% 0.129 22);
197
+ --destructive-foreground: #ffffff;
198
+ --success: oklch(74% 0.12 173);
199
+ --success-text: oklch(74% 0.12 173);
193
200
  --success-foreground: oklch(13% 0 0);
194
201
  --tone-standard: oklch(80% 0 0);
195
202
  --tone-collectible: oklch(76% 0.12 300);
196
203
  --tone-edition: oklch(76% 0.10 173);
197
204
  --tone-info: oklch(76% 0.10 250);
205
+ --tone-info-text: oklch(76% 0.10 250);
198
206
  --tone-warning: oklch(80% 0.13 52);
207
+ --tone-warning-text: oklch(80% 0.13 52);
199
208
  --highlight: oklch(26% 0 0);
200
209
  --tone-standard-foreground: oklch(13% 0 0);
201
210
  --tone-collectible-foreground: oklch(13% 0 0);
@@ -214,3 +223,22 @@
214
223
  --sidebar-border: oklch(26% 0 0);
215
224
  --sidebar-ring: #ffffff;
216
225
  }
226
+
227
+ /* Art direction for generated imagery (GradientArt; ART_TOKENS in
228
+ themes/_contract.ts). Cinematic monochrome (art-direction.md): a dark studio ground with
229
+ one warm amber glow, a little film grain. Saturation lives in the light, never
230
+ in the chrome.
231
+ Declared on every arm so a .dark or .light island derives from its own
232
+ tokens rather than inheriting this scope's resolved colours. */
233
+ [data-sable-theme="atelier"],
234
+ [data-sable-theme="atelier"] .dark,
235
+ [data-sable-theme="atelier"] .light {
236
+ --theme-art-1: oklch(0.16 0 0);
237
+ --theme-art-2: oklch(0.34 0.01 60);
238
+ --theme-art-3: oklch(from var(--tone-warning) 0.74 0.14 h);
239
+ --theme-art-4: oklch(0.1 0 0);
240
+ --theme-art-ground: oklch(0.12 0 0);
241
+ --theme-art-warp: 0.6;
242
+ --theme-art-softness: 0.8;
243
+ --theme-art-grain: 0.3;
244
+ }
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
1
+ import { T as ThemeManifest } from '../../_contract-C2M1SjBf.js';
2
2
 
3
3
  /**
4
4
  * Mono Editorial — a Sable theme. Concrete token values live in
@@ -166,14 +166,18 @@
166
166
  --accent: oklch(92% 0.011 74);
167
167
  --accent-foreground: oklch(23.1% 0.014 70);
168
168
  --destructive: oklch(50% 0.17 25);
169
+ --destructive-text: oklch(50% 0.17 25);
169
170
  --destructive-foreground: oklch(100% 0 0);
170
171
  --success: oklch(43% 0.10 165);
172
+ --success-text: oklch(43% 0.10 165);
171
173
  --success-foreground: oklch(100% 0 0);
172
174
  --tone-standard: oklch(40% 0.07 130);
173
175
  --tone-collectible: oklch(46% 0.14 285);
174
176
  --tone-edition: oklch(45% 0.15 309);
175
177
  --tone-info: oklch(48% 0.13 250);
178
+ --tone-info-text: oklch(48% 0.13 250);
176
179
  --tone-warning: oklch(50% 0.12 52);
180
+ --tone-warning-text: oklch(50% 0.12 52);
177
181
  --highlight: oklch(58% 0.245 32.9);
178
182
  --tone-standard-foreground: oklch(100% 0 0);
179
183
  --tone-collectible-foreground: oklch(100% 0 0);
@@ -240,29 +244,35 @@
240
244
  --ring: oklch(65.2% 0.235 32.9);
241
245
  /* The light-mode fill, not a brighter one: on the brighter fill WCAG preferred
242
246
  dark ink (5.6:1), but it reads at APCA Lc 42 — muddy — against Lc 67 for
243
- white. At 58% white clears both (4.8:1, Lc 76). */
247
+ white. At 58% white clears both (4.8:1, Lc 76). The sidebar's active item
248
+ and the highlight are the same brand fill and follow. */
244
249
  --primary: oklch(58% 0.245 32.9);
245
250
  --primary-foreground: oklch(100% 0 0);
246
251
  --secondary: oklch(26% 0.012 67.2); /* a step ABOVE the card, so a secondary Button reads on a card and on the page */
247
252
  --secondary-foreground: oklch(100% 0 0);
248
253
  --accent: oklch(29% 0.014 60);
249
254
  --accent-foreground: oklch(100% 0 0);
250
- --destructive: oklch(62% 0.16 25);
251
- --destructive-foreground: oklch(14.1% 0.018 70.5);
252
- --success: oklch(72% 0.12 165);
255
+ /* Status colours come in two: the fill carries a label (destructive sits low under white ink), and `-text` is the same colour drawn on a surface — text, borders, rings — kept light enough to read on a dark card. */
256
+ --destructive: oklch(58% 0.16 25);
257
+ --destructive-text: oklch(77.5% 0.129 25);
258
+ --destructive-foreground: oklch(100% 0 0);
259
+ --success: oklch(74% 0.12 165);
260
+ --success-text: oklch(74% 0.12 165);
253
261
  --success-foreground: oklch(14.1% 0.018 70.5);
254
262
  --tone-standard: oklch(70% 0.09 130);
255
263
  --tone-collectible: oklch(70% 0.12 285);
256
264
  --tone-edition: oklch(68% 0.14 309);
257
- --tone-info: oklch(72% 0.11 250);
265
+ --tone-info: oklch(75.5% 0.11 250);
266
+ --tone-info-text: oklch(75.5% 0.11 250);
258
267
  --tone-warning: oklch(78% 0.13 60);
259
- --highlight: oklch(65.2% 0.235 32.9);
268
+ --tone-warning-text: oklch(78% 0.13 60);
269
+ --highlight: oklch(58% 0.245 32.9);
260
270
  --tone-standard-foreground: oklch(14.1% 0.018 70.5);
261
271
  --tone-collectible-foreground: oklch(14.1% 0.018 70.5);
262
272
  --tone-edition-foreground: oklch(14.1% 0.018 70.5);
263
273
  --tone-info-foreground: oklch(14.1% 0.018 70.5);
264
274
  --tone-warning-foreground: oklch(14.1% 0.018 70.5);
265
- --highlight-foreground: oklch(14.1% 0.018 70.5);
275
+ --highlight-foreground: oklch(100% 0 0);
266
276
  --overlay: oklch(14.1% 0.018 70.5);
267
277
  --overlay-foreground: oklch(100% 0 0);
268
278
  --sidebar: oklch(15.9% 0.016 65);
@@ -274,3 +284,24 @@
274
284
  --sidebar-border: oklch(25.2% 0.012 67.2);
275
285
  --sidebar-ring: oklch(65.2% 0.235 32.9);
276
286
  }
287
+
288
+ /* Art direction for generated imagery (GradientArt; ART_TOKENS in
289
+ themes/_contract.ts). Light as the accent (art-direction.md): a hot core blooming out of a
290
+ warm near-black ground and falling off steeply — fire, not a wash. Perfectly
291
+ flat, no grain. Every colour is the brand moved along the flame, so a new
292
+ brand still burns in its own hue.
293
+ Declared on every arm so a .dark or .light island derives from its own
294
+ tokens rather than inheriting this scope's resolved colours. */
295
+ [data-sable-theme="ember"],
296
+ [data-sable-theme="ember"] .dark,
297
+ [data-sable-theme="ember"] .light {
298
+ --theme-art-1: oklch(from var(--primary) 0.17 0.03 calc(h + 20));
299
+ --theme-art-2: var(--primary);
300
+ --theme-art-3: oklch(from var(--primary) 0.68 0.21 calc(h + 15));
301
+ --theme-art-4: oklch(from var(--primary) 0.88 0.15 calc(h + 50));
302
+ --theme-art-5: oklch(from var(--primary) 0.38 0.15 calc(h - 5));
303
+ --theme-art-ground: oklch(from var(--primary) 0.14 0.02 calc(h + 20));
304
+ --theme-art-warp: 0.95;
305
+ --theme-art-softness: 0.72;
306
+ --theme-art-grain: 0;
307
+ }
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
1
+ import { T as ThemeManifest } from '../../_contract-C2M1SjBf.js';
2
2
 
3
3
  /**
4
4
  * Ember — a Sable theme. Concrete token values live in
@@ -1,4 +1,4 @@
1
- export { A as AnyThemeId, D as DEFAULT_THEME_ID, S as SABLE_COLOR_STORAGE_KEY, a as SABLE_THEME_ATTRIBUTE, b as SABLE_THEME_STORAGE_KEY, c as SableColorMode, d as SableThemeContextValue, e as SableThemePreference, f as SableThemeProvider, g as SableThemeProviderProps, T as ThemeId, h as applySableColorMode, i as applySableTheme, j as buildThemeManifestById, k as defaultThemeId, l as featuredThemeManifests, m as getThemeManifest, n as isKnownThemeId, o as isThemeId, t as themeIds, p as themeManifestById, q as themeManifests, u as useSableTheme } from '../theme-provider-CGeiusgu.js';
1
+ export { A as AnyThemeId, D as DEFAULT_THEME_ID, S as SABLE_COLOR_STORAGE_KEY, a as SABLE_THEME_ATTRIBUTE, b as SABLE_THEME_STORAGE_KEY, c as SableColorMode, d as SableThemeContextValue, e as SableThemePreference, f as SableThemeProvider, g as SableThemeProviderProps, T as ThemeId, h as applySableColorMode, i as applySableTheme, j as buildThemeManifestById, k as defaultThemeId, l as featuredThemeManifests, m as getThemeManifest, n as isKnownThemeId, o as isThemeId, t as themeIds, p as themeManifestById, q as themeManifests, u as useSableTheme } from '../theme-provider-kQszHYji.js';
2
2
  export { atelierTheme } from './atelier/index.js';
3
3
  export { default as emberTheme } from './ember/index.js';
4
4
  export { default as meridianTheme } from './meridian/index.js';
@@ -7,4 +7,4 @@ export { default as prismTheme } from './prism/index.js';
7
7
  export { default as sableTheme } from './sable/index.js';
8
8
  export { default as verdantTheme } from './verdant/index.js';
9
9
  import 'react';
10
- import '../_contract-Cn3lUpih.js';
10
+ import '../_contract-C2M1SjBf.js';
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
1
+ import { T as ThemeManifest } from '../../_contract-C2M1SjBf.js';
2
2
 
3
3
  /**
4
4
  * Meridian — a Sable theme. Concrete token values live in
@@ -143,14 +143,18 @@
143
143
  --accent: oklch(57.5% 0.2 31.5);
144
144
  --accent-foreground: oklch(99% 0.004 91.5);
145
145
  --destructive: oklch(48% 0.2 25);
146
+ --destructive-text: oklch(48% 0.2 25);
146
147
  --destructive-foreground: oklch(94.6% 0.0083 91.48);
147
148
  --success: oklch(43% 0.1 165);
149
+ --success-text: oklch(43% 0.1 165);
148
150
  --success-foreground: oklch(94.6% 0.0083 91.48);
149
151
  --tone-standard: oklch(40% 0.07 130);
150
152
  --tone-collectible: oklch(46% 0.14 285);
151
153
  --tone-edition: oklch(45% 0.15 309);
152
154
  --tone-info: oklch(48% 0.13 250);
155
+ --tone-info-text: oklch(48% 0.13 250);
153
156
  --tone-warning: oklch(50% 0.12 52);
157
+ --tone-warning-text: oklch(50% 0.12 52);
154
158
  --highlight: oklch(57.5% 0.2 31.5);
155
159
  /* Ember as TEXT (eyebrow numbers, arrows). The fill ember carries white ink
156
160
  but is too light to be read as ink itself (4.1:1 on the sand); this is the
@@ -230,15 +234,20 @@
230
234
  --secondary-foreground: oklch(94.3% 0.0042 91.45);
231
235
  --accent: oklch(57.5% 0.2 31.5);
232
236
  --accent-foreground: oklch(99% 0.004 91.5);
233
- --destructive: oklch(66% 0.21 25);
234
- --destructive-foreground: oklch(22.46% 0.0094 107.13);
235
- --success: oklch(72% 0.12 165);
237
+ /* Status colours come in two: the fill carries a label (destructive sits low under white ink), and `-text` is the same colour drawn on a surface — text, borders, rings — kept light enough to read on a dark card. */
238
+ --destructive: oklch(55% 0.21 25);
239
+ --destructive-text: oklch(78.5% 0.122 25);
240
+ --destructive-foreground: oklch(94.6% 0.0083 91.48);
241
+ --success: oklch(75.5% 0.12 165);
242
+ --success-text: oklch(75.5% 0.12 165);
236
243
  --success-foreground: oklch(22.46% 0.0094 107.13);
237
244
  --tone-standard: oklch(70% 0.09 130);
238
245
  --tone-collectible: oklch(70% 0.12 285);
239
246
  --tone-edition: oklch(68% 0.14 309);
240
- --tone-info: oklch(72% 0.11 250);
247
+ --tone-info: oklch(76.5% 0.11 250);
248
+ --tone-info-text: oklch(76.5% 0.11 250);
241
249
  --tone-warning: oklch(78% 0.13 60);
250
+ --tone-warning-text: oklch(78% 0.13 60);
242
251
  --highlight: oklch(57.5% 0.2 31.5);
243
252
  --meridian-ember-ink: oklch(72% 0.17 33); /* 6.4:1 on the dark ground */
244
253
  --tone-standard-foreground: oklch(22.46% 0.0094 107.13);
@@ -258,3 +267,23 @@
258
267
  --sidebar-border: oklch(34.69% 0.0186 98.75);
259
268
  --sidebar-ring: oklch(65.11% 0.2063 32.56);
260
269
  }
270
+
271
+ /* Art direction for generated imagery (GradientArt; ART_TOKENS in
272
+ themes/_contract.ts). Grainy monochrome photographic grounds (art-direction.md): soft
273
+ mid-grey tones, slightly lifted blacks, film grain — and none of the orange
274
+ accent, which belongs to typography and controls. Greys carry the ink's
275
+ warm cast.
276
+ Declared on every arm so a .dark or .light island derives from its own
277
+ tokens rather than inheriting this scope's resolved colours. */
278
+ [data-sable-theme="meridian"],
279
+ [data-sable-theme="meridian"] .dark,
280
+ [data-sable-theme="meridian"] .light {
281
+ --theme-art-1: oklch(from var(--foreground) 0.88 0.008 h);
282
+ --theme-art-2: oklch(from var(--foreground) 0.62 0.006 h);
283
+ --theme-art-3: oklch(from var(--foreground) 0.38 0.008 h);
284
+ --theme-art-4: oklch(from var(--foreground) 0.74 0.006 h);
285
+ --theme-art-ground: oklch(from var(--foreground) 0.28 0.008 h);
286
+ --theme-art-warp: 0.4;
287
+ --theme-art-softness: 0.92;
288
+ --theme-art-grain: 0.55;
289
+ }
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
1
+ import { T as ThemeManifest } from '../../_contract-C2M1SjBf.js';
2
2
 
3
3
  /** Original Sable preset; no third-party artwork or reference-site code. */
4
4
  declare const poppyTheme: ThemeManifest;
@@ -79,14 +79,18 @@
79
79
  --accent: oklch(87.83223% 0.046910 333.7803);
80
80
  --accent-foreground: oklch(27.91553% 0.070219 323.8141);
81
81
  --destructive: oklch(51.71923% 0.183122 13.5471);
82
+ --destructive-text: oklch(51.71923% 0.183122 13.5471);
82
83
  --destructive-foreground: oklch(100.00000% 0.000000 89.8756);
83
84
  --success: oklch(44.82304% 0.085811 158.9570);
85
+ --success-text: oklch(44.82304% 0.085811 158.9570);
84
86
  --success-foreground: oklch(100.00000% 0.000000 89.8756);
85
87
  --tone-standard: oklch(45.04846% 0.050543 265.7140);
86
88
  --tone-collectible: oklch(44.74226% 0.160756 301.6122);
87
89
  --tone-edition: oklch(48.37678% 0.151106 344.8805);
88
90
  --tone-info: oklch(47.10428% 0.117665 253.7666);
91
+ --tone-info-text: oklch(47.10428% 0.117665 253.7666);
89
92
  --tone-warning: oklch(48.46763% 0.102195 75.1153);
93
+ --tone-warning-text: oklch(48.46763% 0.102195 75.1153);
90
94
  --highlight: oklch(86.64637% 0.118528 86.6791);
91
95
  --tone-standard-foreground: oklch(100.00000% 0.000000 89.8756);
92
96
  --tone-collectible-foreground: oklch(100.00000% 0.000000 89.8756);
@@ -162,14 +166,18 @@
162
166
  --accent: oklch(45.76059% 0.076204 319.6398);
163
167
  --accent-foreground: oklch(96.99788% 0.031195 84.5922);
164
168
  --destructive: oklch(80.09751% 0.118613 6.8786);
169
+ --destructive-text: oklch(80.09751% 0.118613 6.8786);
165
170
  --destructive-foreground: oklch(27.91553% 0.070219 323.8141);
166
171
  --success: oklch(83.08052% 0.080357 158.1865);
172
+ --success-text: oklch(83.08052% 0.080357 158.1865);
167
173
  --success-foreground: oklch(27.91553% 0.070219 323.8141);
168
174
  --tone-standard: oklch(82.64273% 0.059760 270.2687);
169
175
  --tone-collectible: oklch(81.83406% 0.094036 306.4836);
170
176
  --tone-edition: oklch(82.56509% 0.096677 343.4480);
171
177
  --tone-info: oklch(85.19509% 0.074997 252.9332);
178
+ --tone-info-text: oklch(85.19509% 0.074997 252.9332);
172
179
  --tone-warning: oklch(86.64637% 0.118528 86.6791);
180
+ --tone-warning-text: oklch(86.64637% 0.118528 86.6791);
173
181
  --highlight: oklch(86.64637% 0.118528 86.6791);
174
182
  --highlight-foreground: oklch(27.91553% 0.070219 323.8141);
175
183
  --overlay: oklch(13.55719% 0.032037 309.1234);
@@ -188,3 +196,23 @@
188
196
  --tone-info-foreground: oklch(27.91553% 0.070219 323.8141);
189
197
  --tone-warning-foreground: oklch(27.91553% 0.070219 323.8141);
190
198
  }
199
+
200
+ /* Art direction for generated imagery (GradientArt; ART_TOKENS in
201
+ themes/_contract.ts). Playful: candy colours in crisp, wobbly blobs — sunny yellow,
202
+ bubblegum, grape, sky and a mint the palette lacks. Low softness keeps each
203
+ blob its own colour instead of melting into one.
204
+ Declared on every arm so a .dark or .light island derives from its own
205
+ tokens rather than inheriting this scope's resolved colours. */
206
+ [data-sable-theme="poppy"],
207
+ [data-sable-theme="poppy"] .dark,
208
+ [data-sable-theme="poppy"] .light {
209
+ --theme-art-1: oklch(from var(--highlight) 0.88 0.15 h);
210
+ --theme-art-2: oklch(from var(--tone-edition) 0.72 0.19 h);
211
+ --theme-art-3: oklch(from var(--tone-collectible) 0.64 0.2 h);
212
+ --theme-art-4: oklch(from var(--tone-info) 0.76 0.13 h);
213
+ --theme-art-5: oklch(0.85 0.12 168);
214
+ --theme-art-ground: oklch(from var(--tone-collectible) 0.45 0.16 h);
215
+ --theme-art-warp: 1;
216
+ --theme-art-softness: 0.5;
217
+ --theme-art-grain: 0;
218
+ }
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
1
+ import { T as ThemeManifest } from '../../_contract-C2M1SjBf.js';
2
2
 
3
3
  /** Original Sable preset; no third-party artwork or reference-site code. */
4
4
  declare const prismTheme: ThemeManifest;
@@ -79,14 +79,18 @@
79
79
  --accent: oklch(90.43693% 0.027753 253.2434);
80
80
  --accent-foreground: oklch(26.55994% 0.047330 255.4106);
81
81
  --destructive: oklch(51.62734% 0.183834 19.4551);
82
+ --destructive-text: oklch(51.62734% 0.183834 19.4551);
82
83
  --destructive-foreground: oklch(100.00000% 0.000000 89.8756);
83
84
  --success: oklch(45.54384% 0.081395 166.9049);
85
+ --success-text: oklch(45.54384% 0.081395 166.9049);
84
86
  --success-foreground: oklch(100.00000% 0.000000 89.8756);
85
87
  --tone-standard: oklch(44.17719% 0.043085 257.4210);
86
88
  --tone-collectible: oklch(45.52411% 0.167785 299.9667);
87
89
  --tone-edition: oklch(46.78672% 0.141265 343.5094);
88
90
  --tone-info: oklch(46.18475% 0.139719 259.8084);
91
+ --tone-info-text: oklch(46.18475% 0.139719 259.8084);
89
92
  --tone-warning: oklch(48.72795% 0.103396 73.7587);
93
+ --tone-warning-text: oklch(48.72795% 0.103396 73.7587);
90
94
  --highlight: oklch(87.60451% 0.060193 266.4847);
91
95
  --tone-standard-foreground: oklch(100.00000% 0.000000 89.8756);
92
96
  --tone-collectible-foreground: oklch(100.00000% 0.000000 89.8756);
@@ -158,14 +162,18 @@
158
162
  --accent: oklch(44.66150% 0.052212 253.9323);
159
163
  --accent-foreground: oklch(96.50786% 0.016545 259.4182);
160
164
  --destructive: oklch(79.47983% 0.122016 8.7083);
165
+ --destructive-text: oklch(79.47983% 0.122016 8.7083);
161
166
  --destructive-foreground: oklch(25.20350% 0.047984 259.9119);
162
167
  --success: oklch(82.23776% 0.085532 165.8233);
168
+ --success-text: oklch(82.23776% 0.085532 165.8233);
163
169
  --success-foreground: oklch(25.20350% 0.047984 259.9119);
164
170
  --tone-standard: oklch(82.02131% 0.046953 256.4806);
165
171
  --tone-collectible: oklch(77.28561% 0.118760 301.0442);
166
172
  --tone-edition: oklch(80.44093% 0.102099 341.6738);
167
173
  --tone-info: oklch(80.57243% 0.097939 259.4939);
174
+ --tone-info-text: oklch(80.57243% 0.097939 259.4939);
168
175
  --tone-warning: oklch(84.66478% 0.108333 84.1636);
176
+ --tone-warning-text: oklch(84.66478% 0.108333 84.1636);
169
177
  --highlight: oklch(82.28684% 0.088371 261.1846);
170
178
  --highlight-foreground: oklch(25.20350% 0.047984 259.9119);
171
179
  --overlay: oklch(14.80476% 0.014876 256.6891);
@@ -184,3 +192,21 @@
184
192
  --tone-info-foreground: oklch(25.20350% 0.047984 259.9119);
185
193
  --tone-warning-foreground: oklch(25.20350% 0.047984 259.9119);
186
194
  }
195
+
196
+ /* Art direction for generated imagery (GradientArt; ART_TOKENS in
197
+ themes/_contract.ts). Long soft airbrushed fields, pink to violet to periwinkle, with one
198
+ vivid indigo (art-direction.md). Diffuse, sourceless glow; no grain.
199
+ Declared on every arm so a .dark or .light island derives from its own
200
+ tokens rather than inheriting this scope's resolved colours. */
201
+ [data-sable-theme="prism"],
202
+ [data-sable-theme="prism"] .dark,
203
+ [data-sable-theme="prism"] .light {
204
+ --theme-art-1: oklch(from var(--tone-edition) 0.83 0.1 h);
205
+ --theme-art-2: oklch(from var(--tone-collectible) 0.68 0.16 h);
206
+ --theme-art-3: oklch(from var(--tone-info) 0.82 0.09 h);
207
+ --theme-art-4: oklch(from var(--primary) 0.55 0.2 h);
208
+ --theme-art-ground: oklch(from var(--primary) 0.35 0.14 h);
209
+ --theme-art-warp: 0.45;
210
+ --theme-art-softness: 1;
211
+ --theme-art-grain: 0;
212
+ }
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
1
+ import { T as ThemeManifest } from '../../_contract-C2M1SjBf.js';
2
2
 
3
3
  /**
4
4
  * Sable — the default theme. Concrete token values live in
@@ -220,8 +220,12 @@
220
220
  --tone-standard-dark: oklch(78% 0.205 168);
221
221
  --tone-info-dark: oklch(76% 0.165 235);
222
222
  --tone-warning-dark: oklch(82% 0.165 65);
223
- --highlight-dark: oklch(70% 0.285 318);
224
- --destructive-dark: oklch(68% 0.225 25);
223
+ /* A labelled fill: dropped to where white reads (4.8:1, Lc 72) rather than
224
+ lifted to where dark ink reads muddy (Lc 46). */
225
+ --highlight-dark: oklch(57% 0.28 318);
226
+ /* Status colours come in two: the fill carries a label (destructive sits low under white ink), and `-text` is the same colour drawn on a surface — text, borders, rings — kept light enough to read on a dark card. */
227
+ --destructive-dark: oklch(77.5% 0.129 25);
228
+ --destructive-fill-dark: oklch(59% 0.225 25);
225
229
  /* Success twins the standard tone's dark value today (same caribbean-green),
226
230
  but is a SEPARATE token a theme can retune independently of the post-type. */
227
231
  --success-dark: oklch(78% 0.205 168);
@@ -245,8 +249,10 @@
245
249
  --accent: var(--zinc-100);
246
250
  --accent-foreground: var(--zinc-950);
247
251
  --destructive: var(--torch-red-600);
252
+ --destructive-text: var(--torch-red-600);
248
253
  --destructive-foreground: var(--white);
249
254
  --success: var(--caribbean-green-600);
255
+ --success-text: var(--caribbean-green-600);
250
256
  --success-foreground: var(--zinc-50);
251
257
 
252
258
  --border: var(--zinc-200);
@@ -263,7 +269,9 @@
263
269
  --tone-collectible: var(--blue-gem-600);
264
270
  --tone-edition: var(--purple-heart-700);
265
271
  --tone-warning: var(--flush-orange-700);
272
+ --tone-warning-text: var(--flush-orange-700);
266
273
  --tone-info: var(--azure-radiance-600);
274
+ --tone-info-text: var(--azure-radiance-700); /* a step under the fill: 600 is 4.46:1 as text on muted */
267
275
  --highlight: var(--purple-heart-700);
268
276
 
269
277
  /* Tone ink — light mode the tone fills resolve to dark 600/700 steps, so the
@@ -422,15 +430,18 @@
422
430
  --muted-foreground: var(--zinc-400);
423
431
  --accent: var(--zinc-800);
424
432
  --accent-foreground: var(--zinc-50);
425
- --destructive: var(--destructive-dark);
426
- /* Dark `--destructive` is a light, vibrant red (so `text-destructive` reads on
427
- the near-black canvas). White ink on that fill is only ~3.3:1 (fails AA), so
428
- the legible ink flips to the dark neutral — exactly like the tone fills below.
429
- Dark ink on the light-red fill is ~5.9:1. */
430
- --destructive-foreground: var(--zinc-950);
431
- /* Like the tone fills, dark `--success` is a light, vibrant green, so the
432
- legible ink flips to the dark neutral (same model as --destructive above). */
433
+ --destructive: var(--destructive-fill-dark);
434
+ --destructive-text: var(--destructive-dark);
435
+ /* Dark destructive is two colours. The fill is a saturated red low enough for
436
+ white (4.6:1, Lc 75); dark ink on the old light-red fill passed the ratio but
437
+ read muddy (Lc 44). `text-destructive` and its borders and rings read
438
+ `--destructive-text`, the light, vibrant red that reads on the near-black
439
+ canvas. */
440
+ --destructive-foreground: var(--white);
441
+ /* Dark `--success` is a light, vibrant green that works as both fill and text,
442
+ so the legible ink flips to the dark neutral. */
433
443
  --success: var(--success-dark);
444
+ --success-text: var(--success-dark);
434
445
  --success-foreground: var(--zinc-950);
435
446
 
436
447
  --border: oklch(from var(--zinc-700) l c h / 0.4);
@@ -444,7 +455,9 @@
444
455
  --tone-collectible: var(--tone-collectible-dark);
445
456
  --tone-edition: var(--tone-edition-dark);
446
457
  --tone-warning: var(--tone-warning-dark);
458
+ --tone-warning-text: var(--tone-warning-dark);
447
459
  --tone-info: var(--tone-info-dark);
460
+ --tone-info-text: var(--tone-info-dark);
448
461
  --highlight: var(--highlight-dark);
449
462
 
450
463
  /* Tone ink — dark mode the tone fills resolve to lighter dark-tuned values,
@@ -455,7 +468,7 @@
455
468
  --tone-edition-foreground: var(--zinc-950);
456
469
  --tone-warning-foreground: var(--zinc-950);
457
470
  --tone-info-foreground: var(--zinc-950);
458
- --highlight-foreground: var(--zinc-950);
471
+ --highlight-foreground: var(--zinc-50);
459
472
  --overlay: var(--zinc-950);
460
473
  --overlay-foreground: var(--zinc-50);
461
474
 
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
1
+ import { T as ThemeManifest } from '../../_contract-C2M1SjBf.js';
2
2
 
3
3
  /**
4
4
  * Verdant — a Sable theme. Concrete token values live in
@@ -111,8 +111,10 @@
111
111
  --accent: oklch(91.5% 0.014 170);
112
112
  --accent-foreground: oklch(24% 0.035 175);
113
113
  --destructive: oklch(51% 0.19 22);
114
+ --destructive-text: oklch(51% 0.19 22);
114
115
  --destructive-foreground: oklch(98.6% 0.004 106);
115
116
  --success: oklch(50% 0.11 173);
117
+ --success-text: oklch(50% 0.11 173);
116
118
  --success-foreground: oklch(98.6% 0.004 106);
117
119
  --tone-standard: oklch(49% 0.018 175);
118
120
  --tone-standard-foreground: oklch(98.6% 0.004 106);
@@ -121,8 +123,10 @@
121
123
  --tone-edition: oklch(50% 0.11 173);
122
124
  --tone-edition-foreground: oklch(98.6% 0.004 106);
123
125
  --tone-info: oklch(51% 0.12 250);
126
+ --tone-info-text: oklch(51% 0.12 250);
124
127
  --tone-info-foreground: oklch(98.6% 0.004 106);
125
128
  --tone-warning: oklch(53% 0.12 65);
129
+ --tone-warning-text: oklch(51% 0.12 65); /* a step under the fill: 53% is 4.44:1 as text on muted */
126
130
  --tone-warning-foreground: oklch(98.6% 0.004 106);
127
131
  --highlight: oklch(90% 0.05 100);
128
132
  --highlight-foreground: oklch(24% 0.035 175);
@@ -186,9 +190,12 @@
186
190
  --secondary-foreground: oklch(98.6% 0.004 106);
187
191
  --accent: oklch(33% 0.045 175); /* above --popover: a hover row or chip inside a menu/dialog must still read */
188
192
  --accent-foreground: oklch(98.6% 0.004 106);
189
- --destructive: oklch(64% 0.19 22);
190
- --destructive-foreground: oklch(16% 0.03 175);
191
- --success: oklch(72% 0.13 173);
193
+ /* Status colours come in two: the fill carries a label (destructive sits low under white ink), and `-text` is the same colour drawn on a surface — text, borders, rings — kept light enough to read on a dark card. */
194
+ --destructive: oklch(57.5% 0.19 22);
195
+ --destructive-text: oklch(78% 0.125 22);
196
+ --destructive-foreground: oklch(98.6% 0.004 106);
197
+ --success: oklch(74% 0.13 173);
198
+ --success-text: oklch(74% 0.13 173);
192
199
  --success-foreground: oklch(16% 0.03 175);
193
200
  --tone-standard: oklch(72% 0.015 175);
194
201
  --tone-standard-foreground: oklch(16% 0.03 175);
@@ -196,9 +203,11 @@
196
203
  --tone-collectible-foreground: oklch(16% 0.03 175);
197
204
  --tone-edition: oklch(76% 0.12 173);
198
205
  --tone-edition-foreground: oklch(16% 0.03 175);
199
- --tone-info: oklch(74% 0.11 250);
206
+ --tone-info: oklch(76% 0.11 250);
207
+ --tone-info-text: oklch(76% 0.11 250);
200
208
  --tone-info-foreground: oklch(16% 0.03 175);
201
209
  --tone-warning: oklch(80% 0.13 75);
210
+ --tone-warning-text: oklch(80% 0.13 75);
202
211
  --tone-warning-foreground: oklch(16% 0.03 175);
203
212
  --highlight: oklch(40% 0.06 90);
204
213
  --highlight-foreground: oklch(98.6% 0.004 106);
@@ -213,3 +222,24 @@
213
222
  --sidebar-border: oklch(30% 0.03 175);
214
223
  --sidebar-ring: oklch(85% 0.015 170);
215
224
  }
225
+
226
+ /* Art direction for generated imagery (GradientArt; ART_TOKENS in
227
+ themes/_contract.ts). Out-of-focus stained glass (art-direction.md): saturated teal,
228
+ orange, magenta, blue and green zones melting into each other, glowing from
229
+ within, each zone keeping its own colour. Colour lives in imagery only, so the art is far brighter than the
230
+ two-tone chrome. No grain.
231
+ Declared on every arm so a .dark or .light island derives from its own
232
+ tokens rather than inheriting this scope's resolved colours. */
233
+ [data-sable-theme="verdant"],
234
+ [data-sable-theme="verdant"] .dark,
235
+ [data-sable-theme="verdant"] .light {
236
+ --theme-art-1: oklch(from var(--tone-edition) 0.76 0.15 h);
237
+ --theme-art-2: oklch(from var(--tone-warning) 0.76 0.16 h);
238
+ --theme-art-3: oklch(0.66 0.24 355);
239
+ --theme-art-4: oklch(from var(--tone-info) 0.62 0.17 h);
240
+ --theme-art-5: oklch(0.8 0.19 140);
241
+ --theme-art-ground: oklch(from var(--tone-collectible) 0.24 0.05 h);
242
+ --theme-art-warp: 0.9;
243
+ --theme-art-softness: 0.62;
244
+ --theme-art-grain: 0;
245
+ }
@@ -44,7 +44,7 @@ export function DangerZoneBlock({ className, framed = true, onDelete }: DangerZo
44
44
  Permanently remove your profile, editions, and sales history. Collectors keep the editions they own.
45
45
  </CardDescription>
46
46
  </CardHeader>
47
- <CardFooter className="justify-end">
47
+ <CardFooter actions className="justify-end">
48
48
  <Button variant="destructive" onClick={() => setOpen(true)}>
49
49
  Delete account
50
50
  </Button>
@@ -66,7 +66,7 @@ export function EventCardBlock({ className, framed = true, onRespond }: EventCar
66
66
  <CardFooter className="flex-wrap justify-between gap-3">
67
67
  <AvatarGroup aria-label={`${event.total} attendees`} total={event.total} size="sm" surface={framed ? "card" : undefined} fit>
68
68
  {event.attendees.map((initials) => (
69
- <Avatar key={initials} fallback={initials} alt={initials} />
69
+ <Avatar variant="art" key={initials} fallback={initials} alt={initials} />
70
70
  ))}
71
71
  </AvatarGroup>
72
72
  <div className="flex gap-2">
@@ -52,7 +52,7 @@ export function FeedbackFormBlock({ className, framed = true, onDone }: Feedback
52
52
  </ToggleGroup>
53
53
  <Textarea placeholder="What worked, what didn’t…" aria-label="Notes" rows={3} value={notes} onChange={(e) => setNotes(e.target.value)} />
54
54
  </CardContent>
55
- <CardFooter className="justify-end">
55
+ <CardFooter actions className="justify-end">
56
56
  <Button variant="ghost" size="sm" onClick={() => onDone?.(false)}>
57
57
  Skip
58
58
  </Button>
@@ -41,7 +41,7 @@ export function InviteTeamBlock({ className, framed = true, onSend }: InviteTeam
41
41
  <CardContent className="flex flex-col gap-4">
42
42
  <AvatarGroup aria-label={`${collaborators.length} collaborators`} size="sm" surface={framed ? "card" : undefined}>
43
43
  {collaborators.map((initials) => (
44
- <Avatar key={initials} fallback={initials} alt={initials} />
44
+ <Avatar variant="art" key={initials} fallback={initials} alt={initials} />
45
45
  ))}
46
46
  </AvatarGroup>
47
47
  <form
@@ -21,24 +21,10 @@ A now-playing card: cover, title and artist as an Entity with a Like action, a p
21
21
 
22
22
  ```tsx
23
23
  import { useState } from "react"
24
- import { Button, Card, CardContent, cn, Entity, Icon, Slider } from "@sableui/react"
24
+ import { Button, Card, CardContent, cn, Entity, GradientArt, Icon, Slider } from "@sableui/react"
25
25
 
26
26
  const track = { title: "Low tide, high noon", artist: "Juno Avery", position: 38, elapsed: "1:24", remaining: "-2:16" }
27
27
 
28
- /**
29
- * Placeholder cover painted from the palette — the theme's decorative tones
30
- * over the brand, so it follows every look. Replace with the cover image.
31
- */
32
- const ART_TONES = ["info", "collectible", "edition"]
33
- const ART_SPOTS = [[20, 20, 70], [85, 30, 60], [60, 90, 65]]
34
- const cover = [
35
- ...ART_SPOTS.map(
36
- ([x, y, reach], i) =>
37
- // design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
38
- `radial-gradient(in oklab at ${x}% ${y}%, oklch(from var(--tone-${ART_TONES[i % ART_TONES.length]}) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h), transparent ${reach}%)`,
39
- ),
40
- "var(--primary)",
41
- ].join(", ")
42
28
 
43
29
  export interface MediaPlayerBlockProps {
44
30
  className?: string
@@ -59,7 +45,8 @@ export function MediaPlayerBlock({ className, framed = true, onPlayingChange, on
59
45
  card already pads it. */}
60
46
  <Entity
61
47
  className="p-0"
62
- leading={<div aria-hidden className="size-14 rounded-md" style={{ background: cover }} />}
48
+ // Placeholder cover painted from the theme. Replace with the cover image.
49
+ leading={<GradientArt aria-hidden seed={track.title} className="size-14 rounded-md" />}
63
50
  title={track.title}
64
51
  subtitle={track.artist}
65
52
  action={