@hraness/design-kit 0.31.1 → 0.32.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 (42) hide show
  1. package/AGENT_SETUP.md +89 -0
  2. package/ARTICLE_COPY.md +1 -1
  3. package/MARKETING_COPY.md +13 -1
  4. package/README.md +13 -4
  5. package/dist/{chunk-7b1586eb.js → chunk-4d79eskk.js} +62 -33
  6. package/dist/{chunk-44djxk16.js → chunk-95p0j72v.js} +643 -612
  7. package/dist/{chunk-eh71jz57.js → chunk-vst6p4wd.js} +96 -9
  8. package/dist/index.js +6 -2
  9. package/dist/mockups/client.js +19 -18
  10. package/dist/provider-marks.js +1 -1
  11. package/dist/react/index.js +1615 -649
  12. package/dist/react/server.js +4 -2
  13. package/dist/stylex-manifest.json +1 -1
  14. package/dist/stylex.css +180 -4
  15. package/package.json +7 -3
  16. package/portfolio-inventory.json +1 -1
  17. package/src/agent-setup.ts +47 -0
  18. package/src/index.ts +1 -0
  19. package/src/mockups/client.tsx +26 -22
  20. package/src/mockups.css +44 -13
  21. package/src/provider-marks.generated.ts +16 -0
  22. package/src/provider-marks.ts +14 -15
  23. package/src/react/agent-setup-prompt.stylex.ts +196 -0
  24. package/src/react/agent-setup-prompt.tsx +413 -0
  25. package/src/react/article.tsx +12 -20
  26. package/src/react/design-gallery.tsx +28 -1
  27. package/src/react/index.ts +1 -0
  28. package/src/react/launch-beats.tsx +7 -4
  29. package/src/react/product-marketing.stylex.ts +8 -0
  30. package/src/react/product-marketing.tsx +37 -10
  31. package/src/react/provider-mark.stylex.ts +17 -10
  32. package/src/react/provider-mark.tsx +6 -16
  33. package/vendor/provider-marks/MEM0-LICENSE +201 -0
  34. package/vendor/provider-marks/SUPERMEMORY-LICENSE +21 -0
  35. package/vendor/provider-marks/UPSTREAM.md +12 -1
  36. package/vendor/provider-marks/lobehub/githubcopilot.svg +1 -0
  37. package/vendor/provider-marks/lobehub/obsidian-color.svg +1 -0
  38. package/vendor/provider-marks/lobehub/obsidian.svg +1 -0
  39. package/vendor/provider-marks/mem0-art.svg +1 -0
  40. package/vendor/provider-marks/mem0-glyph.svg +1 -0
  41. package/vendor/provider-marks/mem0.svg +1 -0
  42. package/vendor/provider-marks/supermemory.svg +1 -0
package/dist/stylex.css CHANGED
@@ -57,10 +57,30 @@
57
57
  padding: .3rem .85rem;
58
58
  }
59
59
 
60
+ .xk2ntsi {
61
+ padding: .4rem .4rem 0 1rem;
62
+ }
63
+
60
64
  .x3699eh {
61
65
  padding: .55rem .9rem;
62
66
  }
63
67
 
68
+ .x1xplop1 {
69
+ padding: .5rem 1rem 1rem;
70
+ }
71
+
72
+ .xde91s7 {
73
+ padding: .5rem 1rem 4rem;
74
+ }
75
+
76
+ .x1i4hug3 {
77
+ padding: .65rem .8rem;
78
+ }
79
+
80
+ .xz0q7xm {
81
+ padding: .6rem .8rem;
82
+ }
83
+
64
84
  .x19qf9ol {
65
85
  padding: .6rem 1.1rem;
66
86
  }
@@ -77,10 +97,18 @@
77
97
  padding: 0;
78
98
  }
79
99
 
100
+ .x194jd1b {
101
+ padding: 1.1rem 7rem 1rem 1rem;
102
+ }
103
+
80
104
  .x1dk9mx5 {
81
105
  padding: 1.25rem clamp(.8rem, 2vw, 1.5rem);
82
106
  }
83
107
 
108
+ .x67o20r {
109
+ padding: 1rem 1rem 0;
110
+ }
111
+
84
112
  .x1nn0urv {
85
113
  padding: clamp(1.25rem, 2.5vw, 1.75rem);
86
114
  }
@@ -208,6 +236,10 @@
208
236
  border-bottom: var(--hraness-marketing-surface-rule);
209
237
  }
210
238
 
239
+ .x3ugid2 {
240
+ border-color: color-mix(in srgb,var(--foreground, CanvasText) 15%,transparent);
241
+ }
242
+
211
243
  .x1agitey {
212
244
  border-color: color-mix(in srgb,var(--hraness-design-procedural-ripple-color) 46%,transparent);
213
245
  }
@@ -444,6 +476,10 @@
444
476
  gap: .3rem 1rem;
445
477
  }
446
478
 
479
+ .xbcm6b9 {
480
+ gap: .3rem;
481
+ }
482
+
447
483
  .x1rcpt3j {
448
484
  gap: .45rem;
449
485
  }
@@ -452,6 +488,10 @@
452
488
  gap: .4rem;
453
489
  }
454
490
 
491
+ .xpraio3 {
492
+ gap: .55rem;
493
+ }
494
+
455
495
  .x339ura {
456
496
  gap: .5rem 1.25rem;
457
497
  }
@@ -788,6 +828,10 @@
788
828
  border-radius: .25rem;
789
829
  }
790
830
 
831
+ .xr5lfjo:focus-visible {
832
+ outline: 2px solid var(--focus, Highlight);
833
+ }
834
+
791
835
  .x11dsm9b:focus-visible {
792
836
  outline: 2px solid var(--hraness-marketing-accent, var(--focus, Highlight));
793
837
  }
@@ -1195,6 +1239,10 @@
1195
1239
  background-color: var(--_mark-accent);
1196
1240
  }
1197
1241
 
1242
+ .x17tv4j5 {
1243
+ background-color: var(--background, Canvas);
1244
+ }
1245
+
1198
1246
  .x11gw9ax {
1199
1247
  background-color: var(--background);
1200
1248
  }
@@ -1203,6 +1251,10 @@
1203
1251
  background-color: var(--card, var(--surface, #111217));
1204
1252
  }
1205
1253
 
1254
+ .x11tn3as {
1255
+ background-color: var(--foreground, CanvasText);
1256
+ }
1257
+
1206
1258
  .x1uslotl {
1207
1259
  background-color: var(--grid);
1208
1260
  }
@@ -1279,6 +1331,10 @@
1279
1331
  background-color: var(--secondary);
1280
1332
  }
1281
1333
 
1334
+ .xw51l6n {
1335
+ background-color: var(--surface, Canvas);
1336
+ }
1337
+
1282
1338
  .xuvt9l {
1283
1339
  background-color: var(--surface);
1284
1340
  }
@@ -1307,10 +1363,6 @@
1307
1363
  background-image: linear-gradient(142deg, #d8dce7 0, #666d7d 22%, #f4f5f8 43%, #4f5563 67%, #c9ced9 100%);
1308
1364
  }
1309
1365
 
1310
- .xs51ml9 {
1311
- background-image: linear-gradient(180deg,#ffffff38,transparent 52%),linear-gradient(160deg,transparent,color-mix(in srgb,var(--_mark-accent) 78%,black));
1312
- }
1313
-
1314
1366
  .x1hkejs3 {
1315
1367
  background-image: linear-gradient(180deg,#ffffff3d,transparent 48%),linear-gradient(160deg,color-mix(in srgb,var(--_mark-accent) 26%,transparent),color-mix(in srgb,var(--_mark-accent) 6%,transparent) 74%);
1316
1368
  }
@@ -1855,6 +1907,10 @@
1855
1907
  color: var(--_mark-on-accent, #f7f6f2);
1856
1908
  }
1857
1909
 
1910
+ .x19ydol1 {
1911
+ color: var(--background, Canvas);
1912
+ }
1913
+
1858
1914
  .x1gu5z8g {
1859
1915
  color: var(--card-foreground, var(--foreground, currentColor));
1860
1916
  }
@@ -1951,6 +2007,10 @@
1951
2007
  contain: paint;
1952
2008
  }
1953
2009
 
2010
+ .x1w8g7s4 {
2011
+ container-name: hraness-agent-setup;
2012
+ }
2013
+
1954
2014
  .x1bd2tma {
1955
2015
  container-name: hraness-platform-install;
1956
2016
  }
@@ -2059,6 +2119,10 @@
2059
2119
  font-family: inherit;
2060
2120
  }
2061
2121
 
2122
+ .x1nbx2l5 {
2123
+ font-family: var(--font-mono, ui-monospace,monospace);
2124
+ }
2125
+
2062
2126
  .xpw0y0u {
2063
2127
  font-family: var(--font-mono);
2064
2128
  }
@@ -2067,6 +2131,10 @@
2067
2131
  font-family: var(--font-text, system-ui,sans-serif);
2068
2132
  }
2069
2133
 
2134
+ .xtls1wf {
2135
+ font-family: var(--font-text, ui-sans-serif,system-ui,sans-serif);
2136
+ }
2137
+
2070
2138
  .x1d3so1v {
2071
2139
  font-family: var(--font-text);
2072
2140
  }
@@ -2351,10 +2419,18 @@
2351
2419
  grid-template-columns: minmax(0, 1fr) auto;
2352
2420
  }
2353
2421
 
2422
+ .x1mkdm3x {
2423
+ grid-template-columns: minmax(0, 1fr);
2424
+ }
2425
+
2354
2426
  .x1xdnkkc {
2355
2427
  grid-template-columns: minmax(12rem, .6fr) minmax(0, 1.4fr);
2356
2428
  }
2357
2429
 
2430
+ .x5m93ek {
2431
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
2432
+ }
2433
+
2358
2434
  .x1p797x9 {
2359
2435
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
2360
2436
  }
@@ -2375,6 +2451,10 @@
2375
2451
  grid-template-columns: repeat(var(--hraness-marketing-pillar-columns, 3),minmax(0,1fr));
2376
2452
  }
2377
2453
 
2454
+ .xkewwhh {
2455
+ grid-template-columns: var(--_hraness-agent-setup-columns, minmax(0,1fr));
2456
+ }
2457
+
2378
2458
  .x16p6thf {
2379
2459
  grid-template-rows: auto minmax(0, 1fr);
2380
2460
  }
@@ -2471,6 +2551,10 @@
2471
2551
  inset-block-start: calc(100% + .5rem);
2472
2552
  }
2473
2553
 
2554
+ .xe388cy {
2555
+ inset-inline-end: .5rem;
2556
+ }
2557
+
2474
2558
  .xtijo5x {
2475
2559
  inset-inline-end: 0;
2476
2560
  }
@@ -2595,6 +2679,10 @@
2595
2679
  line-height: 1.6;
2596
2680
  }
2597
2681
 
2682
+ .x11m8ghp {
2683
+ line-height: 1.65;
2684
+ }
2685
+
2598
2686
  .xo5v014 {
2599
2687
  line-height: 1;
2600
2688
  }
@@ -2655,10 +2743,19 @@
2655
2743
  margin-block-start: .8rem;
2656
2744
  }
2657
2745
 
2746
+ .xsyyole {
2747
+ margin-block-start: 1.5rem;
2748
+ }
2749
+
2658
2750
  .xvc5jky {
2659
2751
  margin-inline-start: auto;
2660
2752
  }
2661
2753
 
2754
+ .xn48363 {
2755
+ -webkit-mask-image: linear-gradient(#000 55%, #0000);
2756
+ mask-image: linear-gradient(#000 55%, #0000);
2757
+ }
2758
+
2662
2759
  .x885qll {
2663
2760
  -webkit-mask-image: radial-gradient(#000 8%, #0000 78%);
2664
2761
  mask-image: radial-gradient(#000 8%, #0000 78%);
@@ -2957,10 +3054,18 @@
2957
3054
  outline-offset: -2px;
2958
3055
  }
2959
3056
 
3057
+ .x1v57pqu {
3058
+ outline-offset: -3px;
3059
+ }
3060
+
2960
3061
  .x1y3gkto {
2961
3062
  outline-offset: 1px;
2962
3063
  }
2963
3064
 
3065
+ .x1hl8ikr {
3066
+ outline-offset: 2px;
3067
+ }
3068
+
2964
3069
  .x1ewu8gn {
2965
3070
  outline-offset: 3px;
2966
3071
  }
@@ -3230,6 +3335,10 @@
3230
3335
  white-space: nowrap;
3231
3336
  }
3232
3337
 
3338
+ .x126k92a {
3339
+ white-space: pre-wrap;
3340
+ }
3341
+
3233
3342
  .x1sdyfia {
3234
3343
  white-space: pre;
3235
3344
  }
@@ -3410,6 +3519,14 @@
3410
3519
  background-color: #0000;
3411
3520
  }
3412
3521
 
3522
+ .x1jf9jbc:hover {
3523
+ background-color: var(--background, Canvas);
3524
+ }
3525
+
3526
+ .x1w11x55:hover {
3527
+ background-color: var(--foreground, CanvasText);
3528
+ }
3529
+
3413
3530
  .xr4j8zq:hover {
3414
3531
  background-color: var(--hraness-foil-surface, var(--surface, var(--background, Canvas)));
3415
3532
  }
@@ -3418,6 +3535,10 @@
3418
3535
  background-color: var(--hraness-marketing-surface, var(--surface, Canvas));
3419
3536
  }
3420
3537
 
3538
+ .x1lf5oka:hover {
3539
+ background-color: var(--surface, Canvas);
3540
+ }
3541
+
3421
3542
  .xn3cpwa:hover {
3422
3543
  background-image: none;
3423
3544
  }
@@ -3442,6 +3563,14 @@
3442
3563
  box-shadow: 0 1px 4px color-mix(in srgb,var(--foreground, CanvasText) 12%,transparent);
3443
3564
  }
3444
3565
 
3566
+ .x54addn:hover {
3567
+ color: var(--background, Canvas);
3568
+ }
3569
+
3570
+ .x10zor9s:hover {
3571
+ color: var(--foreground, CanvasText);
3572
+ }
3573
+
3445
3574
  .xucth70:hover {
3446
3575
  color: var(--hraness-marketing-accent-ink);
3447
3576
  }
@@ -3744,7 +3873,16 @@
3744
3873
  .x1e5rwe6.x1e5rwe6 {
3745
3874
  margin-inline-start: calc(-1 * var(--hraness-marketing-gutter));
3746
3875
  }
3876
+ }
3877
+
3878
+ @media (forced-colors: active) {
3879
+ .x1fww0mx.x1fww0mx {
3880
+ -webkit-mask-image: none;
3881
+ mask-image: none;
3882
+ }
3883
+ }
3747
3884
 
3885
+ @media (width <= 48rem) {
3748
3886
  .x6ri7ij.x6ri7ij {
3749
3887
  max-inline-size: 16ch;
3750
3888
  }
@@ -3982,6 +4120,10 @@
3982
4120
  border-top-width: 2px;
3983
4121
  }
3984
4122
 
4123
+ .x1of4z1n {
4124
+ bottom: .5rem;
4125
+ }
4126
+
3985
4127
  .x1ey2m1c {
3986
4128
  bottom: 0;
3987
4129
  }
@@ -4006,6 +4148,10 @@
4006
4148
  height: 1px;
4007
4149
  }
4008
4150
 
4151
+ .x1l36t39 {
4152
+ height: 1rem;
4153
+ }
4154
+
4009
4155
  .x36qwtl {
4010
4156
  height: 2px;
4011
4157
  }
@@ -4082,6 +4228,14 @@
4082
4228
  margin-bottom: var(--space-2);
4083
4229
  }
4084
4230
 
4231
+ .xz6j1ir {
4232
+ max-height: 10rem;
4233
+ }
4234
+
4235
+ .x1q2tmi7 {
4236
+ max-height: 11rem;
4237
+ }
4238
+
4085
4239
  .x193iq5w {
4086
4240
  max-width: 100%;
4087
4241
  }
@@ -4094,6 +4248,10 @@
4094
4248
  min-height: 2.5rem;
4095
4249
  }
4096
4250
 
4251
+ .xgbkey0 {
4252
+ min-height: 2.75rem;
4253
+ }
4254
+
4097
4255
  .xjjci79 {
4098
4256
  min-height: 250px;
4099
4257
  }
@@ -4102,6 +4260,10 @@
4102
4260
  min-height: 2rem;
4103
4261
  }
4104
4262
 
4263
+ .x13gjtz1 {
4264
+ min-height: 3.5rem;
4265
+ }
4266
+
4105
4267
  .xe8gcm {
4106
4268
  min-height: 3rem;
4107
4269
  }
@@ -4202,6 +4364,10 @@
4202
4364
  width: 1px;
4203
4365
  }
4204
4366
 
4367
+ .xcdlrvm {
4368
+ width: 1rem;
4369
+ }
4370
+
4205
4371
  .x1endk3i {
4206
4372
  width: 64%;
4207
4373
  }
@@ -4391,6 +4557,10 @@
4391
4557
  color: var(--hraness-marketing-muted);
4392
4558
  }
4393
4559
 
4560
+ .x13c0qn1::marker {
4561
+ content: "";
4562
+ }
4563
+
4394
4564
  .x1cpjm7i:before, .x1s928wv:after {
4395
4565
  content: "";
4396
4566
  }
@@ -5337,6 +5507,12 @@
5337
5507
  }
5338
5508
  }
5339
5509
 
5510
+ @container hraness-agent-setup (width >= 42rem) {
5511
+ .x18n472x.x18n472x {
5512
+ --_hraness-agent-setup-columns: minmax(0,1fr) minmax(11rem,14rem);
5513
+ }
5514
+ }
5515
+
5340
5516
  @container hraness-platform-install (width <= 30rem) {
5341
5517
  .x1rp0mr4.x1rp0mr4 {
5342
5518
  --_hraness-platform-install-narrow-tab-font-size: .8125rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/design-kit",
3
- "version": "0.31.1",
3
+ "version": "0.32.0",
4
4
  "description": "React components and CSS for Hraness apps and product sites: application shells, marketing sections, charts, themes, effects, and syntax highlighting. Built on @hraness/ui.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -292,7 +292,11 @@
292
292
  "src/mockups/index.ts",
293
293
  "src/mockups/surfaces.tsx",
294
294
  "src/mockups.css",
295
- "src/testing.ts"
295
+ "src/testing.ts",
296
+ "src/agent-setup.ts",
297
+ "src/react/agent-setup-prompt.tsx",
298
+ "src/react/agent-setup-prompt.stylex.ts",
299
+ "AGENT_SETUP.md"
296
300
  ],
297
301
  "scripts": {
298
302
  "build": "bun run ./scripts/build.ts",
@@ -306,7 +310,7 @@
306
310
  "browser:install": "bun ./node_modules/playwright-core/cli.js install chromium",
307
311
  "test:browser": "bun run test:browser:marketing && bun run test:browser:rest",
308
312
  "test:browser:marketing": "bun run ./scripts/product-marketing-browser.ts",
309
- "test:browser:rest": "bun run ./scripts/site-shell-browser.ts && bun run ./scripts/palette-browser.ts && bun run ./scripts/gallery-browser.ts && bun run ./scripts/global-error-browser.ts && bun run ./scripts/security-delivery-browser.ts && bun run ./scripts/paper-theme-browser.ts && bun run ./scripts/product-marketing-preset-browser.ts && bun run ./scripts/lantern-material-browser.ts && bun run ./scripts/foil-browser.ts && bun run ./scripts/hero-light-browser.ts && bun run ./scripts/status-page-browser.ts && bun run ./scripts/platform-install-browser.ts && bun run ./scripts/mockups-browser.ts",
313
+ "test:browser:rest": "bun run ./scripts/site-shell-browser.ts && bun run ./scripts/palette-browser.ts && bun run ./scripts/gallery-browser.ts && bun run ./scripts/global-error-browser.ts && bun run ./scripts/security-delivery-browser.ts && bun run ./scripts/paper-theme-browser.ts && bun run ./scripts/product-marketing-preset-browser.ts && bun run ./scripts/lantern-material-browser.ts && bun run ./scripts/foil-browser.ts && bun run ./scripts/hero-light-browser.ts && bun run ./scripts/status-page-browser.ts && bun run ./scripts/platform-install-browser.ts && bun run ./scripts/mockups-browser.ts && bun run ./scripts/agent-setup-browser.ts",
310
314
  "test:package": "bun run ./scripts/package-smoke.ts && bun run ./scripts/mockups-import.ts",
311
315
  "kb:refresh": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell refresh --root kb",
312
316
  "kb:check:lane": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell check --root kb --no-catalog",
@@ -6,7 +6,7 @@
6
6
  "name": "@hraness/design-kit",
7
7
  "path": ".",
8
8
  "visibility": "public",
9
- "version": "0.31.1"
9
+ "version": "0.32.0"
10
10
  }
11
11
  ],
12
12
  "contract": "hraness.portfolio-inventory/v1",
@@ -0,0 +1,47 @@
1
+ import type { ProviderMarkId } from "./provider-marks.js";
2
+
3
+ export type ComputerAgentId = "dot" | "grok-bot" | "muse" | "cursor" | "codex-app" | "devin";
4
+
5
+ /** A documented product entry or a link that fills a composer without sending. */
6
+ export interface AgentSetupDestination {
7
+ readonly id: ComputerAgentId;
8
+ readonly label: string;
9
+ readonly mark: ProviderMarkId;
10
+ readonly href: string;
11
+ readonly mode: "prefill" | "copy-and-open";
12
+ /** Filter cloud computers out when a product needs the user's local machine. */
13
+ readonly host: "local" | "cloud";
14
+ }
15
+
16
+ export interface AgentSetupTargetOptions {
17
+ /** Use prompt-free entry links when a product keeps its prompt out of URLs. */
18
+ readonly prefill?: boolean;
19
+ }
20
+
21
+ /** Cursor's documented maximum; longer prompts use copy and an ordinary entry link. */
22
+ export const MAX_AGENT_SETUP_URL = 10_000;
23
+
24
+ /**
25
+ * Product prompts stay with their callers. These destinations are shared by
26
+ * install pages; capability and URL sources are recorded in AGENT_SETUP.md.
27
+ * None of these actions sends a prompt or starts work automatically.
28
+ */
29
+ export function agentSetupTargets(
30
+ prompt: string,
31
+ options: AgentSetupTargetOptions = {},
32
+ ): readonly AgentSetupDestination[] {
33
+ if (typeof prompt !== "string" || prompt.trim() === "") throw new RangeError("Agent setup prompt must contain text.");
34
+ const encoded = options.prefill === false ? "" : encodeURIComponent(prompt);
35
+ const prefill = (href: string, entry: string) => options.prefill !== false && href.length <= MAX_AGENT_SETUP_URL
36
+ ? { href, mode: "prefill" as const }
37
+ : { href: entry, mode: "copy-and-open" as const };
38
+ const codex = prefill(`codex://new?prompt=${encoded}`, "https://chatgpt.com/codex");
39
+ return [
40
+ { id: "dot", label: "OpenAI Dot", mark: "openai", href: "https://chatgpt.com/", mode: "copy-and-open", host: "cloud" },
41
+ { id: "grok-bot", label: "Grok Bot", mark: "xai", href: "https://cursor.com/dashboard/bot", mode: "copy-and-open", host: "cloud" },
42
+ { id: "muse", label: "Muse", mark: "meta", href: "https://applink.muse.ai/", mode: "copy-and-open", host: "cloud" },
43
+ { id: "cursor", label: "Cursor", mark: "cursor", host: "local", ...prefill(`https://cursor.com/link/prompt?text=${encoded}`, "https://cursor.com/") },
44
+ { id: "codex-app", label: "Codex", mark: "codex", host: codex.mode === "prefill" ? "local" : "cloud", ...codex },
45
+ { id: "devin", label: "Devin", mark: "devin", href: "https://app.devin.ai/", mode: "copy-and-open", host: "cloud" },
46
+ ];
47
+ }
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from "./status-page-html.js";
10
10
  export * from "./provider-marks.js";
11
11
  export * from "./platforms.js";
12
12
  export * from "./relative-time.js";
13
+ export * from "./agent-setup.js";
13
14
 
14
15
  export const colors = {
15
16
  light: {
@@ -40,10 +40,10 @@ function itemAt<T>(items: readonly T[], index: number, what: string): T {
40
40
  return item;
41
41
  }
42
42
 
43
- function assertCaption(caption: string, component: string): void {
44
- if (typeof caption !== "string" || caption.trim() === "") {
45
- throw new TypeError(`${component} needs a caption that says the picture is an illustration.`);
46
- }
43
+ function optionalText(value: string | undefined, component: string): string | undefined {
44
+ if (value === undefined) return undefined;
45
+ if (typeof value !== "string") throw new TypeError(`${component} text must be a string.`);
46
+ return value.trim() || undefined;
47
47
  }
48
48
 
49
49
  /** Arrow, Home, and End move between tabs, as in the WAI-ARIA tabs pattern. */
@@ -109,8 +109,8 @@ export function FitToWidth({ children, className, minWidth = 400 }: Readonly<{ c
109
109
  /* ModeShowcase */
110
110
  /* ------------------------------------------------------------------ */
111
111
 
112
- /** One choice on a showcase axis. `hint` says in one sentence what the choice does. */
113
- export type ShowcaseChoice<I extends string> = Readonly<{ id: I; label: string; hint: string }>;
112
+ /** One choice on a showcase axis. Add a hint only when its label needs context. */
113
+ export type ShowcaseChoice<I extends string> = Readonly<{ id: I; label: string; hint?: string }>;
114
114
 
115
115
  /** What a surface's render function receives. */
116
116
  export type ShowcaseState<M extends string, O extends string = string> = Readonly<{
@@ -143,8 +143,8 @@ export type ModeShowcaseProps<S extends string, M extends string, O extends stri
143
143
  initial?: Readonly<{ surface?: S; mode?: M; option?: O }>;
144
144
  /** A live line under the stage that says what the current state shows. */
145
145
  status?: (state: Readonly<{ surface: S; mode: M; option: O | undefined }>) => ReactNode;
146
- /** Required. Say it is an illustration, such as "Illustration. Names and text are made up." */
147
- caption: string;
146
+ /** Optional context that adds to the visual. Omit repeated descriptions or generic disclaimers. */
147
+ caption?: string;
148
148
  /** Page-window height in CSS pixels at full scale. */
149
149
  height?: number;
150
150
  /** Narrowest layout width before the stage scales down. */
@@ -180,7 +180,7 @@ export function ModeShowcase<S extends string, M extends string, O extends strin
180
180
  assertUniqueIds(surfaces, "ModeShowcase surface");
181
181
  assertUniqueIds(modes, "ModeShowcase mode");
182
182
  if (options !== undefined) assertUniqueIds(options, "ModeShowcase option");
183
- assertCaption(caption, "ModeShowcase");
183
+ const captionText = optionalText(caption, "ModeShowcase");
184
184
  if (!(height > 0)) throw new RangeError("ModeShowcase height must be positive.");
185
185
 
186
186
  const id = useId();
@@ -221,6 +221,11 @@ export function ModeShowcase<S extends string, M extends string, O extends strin
221
221
  };
222
222
 
223
223
  const statusNode = status?.({ mode, option, surface: surface.id });
224
+ const hint = [modeChoice.hint, optionInactive ? undefined : optionChoice?.hint]
225
+ .map((text) => optionalText(text, "ModeShowcase hint"))
226
+ .filter((text) => text !== undefined)
227
+ .join(" ");
228
+ const hasStatus = statusNode !== undefined && statusNode !== null && statusNode !== false && statusNode !== "";
224
229
  return (
225
230
  <figure
226
231
  aria-label={label?.(surface) ?? `Illustration of ${surface.label.toLowerCase()}`}
@@ -277,10 +282,7 @@ export function ModeShowcase<S extends string, M extends string, O extends strin
277
282
  </div>
278
283
  )}
279
284
  </div>
280
- <p className="hkm-showcase-hint">
281
- {modeChoice.hint}
282
- {optionChoice === undefined || optionInactive ? null : ` ${optionChoice.hint}`}
283
- </p>
285
+ {hint === "" ? null : <p className="hkm-showcase-hint">{hint}</p>}
284
286
  </div>
285
287
  <div
286
288
  aria-labelledby={surfaces.length > 1 ? `${id}-tab-${surface.id}` : undefined}
@@ -295,10 +297,12 @@ export function ModeShowcase<S extends string, M extends string, O extends strin
295
297
  {surface.render({ animated, mode, option, previousMode, theme })}
296
298
  </FitToWidth>
297
299
  </div>
298
- <figcaption className="hkm-showcase-caption">
299
- {statusNode === undefined ? null : <span aria-live="polite" className="hkm-showcase-status">{statusNode}</span>}
300
- <span>{caption}</span>
301
- </figcaption>
300
+ {!hasStatus && captionText === undefined ? null : (
301
+ <figcaption className="hkm-showcase-caption">
302
+ {hasStatus ? <span aria-live="polite" className="hkm-showcase-status">{statusNode}</span> : null}
303
+ {captionText === undefined ? null : <span>{captionText}</span>}
304
+ </figcaption>
305
+ )}
302
306
  </figure>
303
307
  );
304
308
  }
@@ -330,7 +334,7 @@ export function StepThrough({
330
334
  theme,
331
335
  }: Readonly<{
332
336
  steps: readonly ThroughStep[];
333
- caption: string;
337
+ caption?: string;
334
338
  initial?: string;
335
339
  label?: string;
336
340
  minWidth?: number;
@@ -338,7 +342,7 @@ export function StepThrough({
338
342
  className?: string;
339
343
  }>) {
340
344
  assertUniqueIds(steps, "StepThrough step");
341
- assertCaption(caption, "StepThrough");
345
+ const captionText = optionalText(caption, "StepThrough");
342
346
  const id = useId();
343
347
  const [index, setIndex] = useState(() => Math.max(0, steps.findIndex((step) => step.id === initial)));
344
348
  const [animated, setAnimated] = useState(false);
@@ -407,9 +411,9 @@ export function StepThrough({
407
411
  Next
408
412
  </button>
409
413
  </div>
410
- <figcaption className="hkm-showcase-caption">
411
- <span>{caption}</span>
412
- </figcaption>
414
+ {captionText === undefined ? null : (
415
+ <figcaption className="hkm-showcase-caption"><span>{captionText}</span></figcaption>
416
+ )}
413
417
  </figure>
414
418
  );
415
419
  }