@cueplusplus/ui 0.18.2 → 0.19.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 (136) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/brand/_motif-seed.js +20 -0
  3. package/dist/brand/gloss-object.d.ts +23 -0
  4. package/dist/brand/gloss-object.js +108 -0
  5. package/dist/brand/halftone.d.ts +23 -0
  6. package/dist/brand/halftone.js +54 -0
  7. package/dist/brand/index.d.ts +6 -1
  8. package/dist/brand/motif.d.ts +32 -0
  9. package/dist/brand/motif.js +71 -0
  10. package/dist/brand/particle-mark.d.ts +29 -0
  11. package/dist/brand/particle-mark.js +137 -0
  12. package/dist/brand/ribbon.d.ts +20 -0
  13. package/dist/brand/ribbon.js +54 -0
  14. package/dist/chrome/announcement-bar.d.ts +30 -0
  15. package/dist/chrome/announcement-bar.js +38 -0
  16. package/dist/chrome/app-bar.d.ts +4 -1
  17. package/dist/chrome/app-bar.js +7 -2
  18. package/dist/chrome/app-shell.js +8 -1
  19. package/dist/chrome/band.d.ts +3 -1
  20. package/dist/chrome/band.js +4 -3
  21. package/dist/chrome/hero.d.ts +61 -0
  22. package/dist/chrome/hero.js +108 -0
  23. package/dist/chrome/index.d.ts +6 -1
  24. package/dist/chrome/index.js +6 -1
  25. package/dist/chrome/keyword.d.ts +22 -0
  26. package/dist/chrome/keyword.js +28 -0
  27. package/dist/chrome/logo-strip.d.ts +23 -0
  28. package/dist/chrome/logo-strip.js +43 -0
  29. package/dist/chrome/page-shell.js +13 -1
  30. package/dist/chrome/stat-band.d.ts +32 -0
  31. package/dist/chrome/stat-band.js +39 -0
  32. package/dist/dmx/channel-matrix.js +5 -3
  33. package/dist/dmx/dmx-strip.js +5 -3
  34. package/dist/elements/animated-number.js +4 -1
  35. package/dist/elements/replay.d.ts +1 -1
  36. package/dist/elements/replay.js +5 -2
  37. package/dist/elements/scroll-anchor.js +4 -1
  38. package/dist/index.d.ts +13 -1
  39. package/dist/index.js +14 -2
  40. package/dist/instruments/index.js +1 -1
  41. package/dist/instruments/stat.js +1 -1
  42. package/dist/instruments/terminal-frame.js +5 -2
  43. package/dist/layout/grid.js +4 -2
  44. package/dist/layout/preview.js +4 -188
  45. package/dist/layout/sidebar.js +88 -52
  46. package/dist/overlays/_surface.d.ts +2 -2
  47. package/dist/primitives/button.js +22 -3
  48. package/dist/primitives/chip.js +62 -6
  49. package/dist/styles.css +26 -0
  50. package/dist/system/_scoped-pairing.js +18 -0
  51. package/dist/system/_stage-scope.js +199 -0
  52. package/dist/system/_style-check.js +1 -1
  53. package/dist/system/_style-chip.js +1 -1
  54. package/dist/system/_style-group-label.js +7 -3
  55. package/dist/system/_style-hero-title.js +41 -0
  56. package/dist/system/_style-kbd.js +1 -1
  57. package/dist/system/_style-label-ink.js +12 -5
  58. package/dist/system/_style-menu.js +1 -1
  59. package/dist/system/_style-page-title.js +1 -1
  60. package/dist/system/_style-segmented-control.js +1 -1
  61. package/dist/system/_style-skeleton.js +1 -1
  62. package/dist/system/_style-tab.js +1 -1
  63. package/dist/system/font-scope.d.ts +38 -0
  64. package/dist/system/font-scope.js +69 -0
  65. package/dist/system/index.d.ts +3 -1
  66. package/dist/system/index.js +3 -1
  67. package/dist/system/portal.js +7 -1
  68. package/dist/system/still.d.ts +32 -0
  69. package/dist/system/still.js +47 -0
  70. package/manifest/components/alert-dialog.json +1 -1
  71. package/manifest/components/announcement-bar.json +82 -0
  72. package/manifest/components/app-bar.json +3 -2
  73. package/manifest/components/app-shell.json +3 -0
  74. package/manifest/components/autocomplete.json +1 -0
  75. package/manifest/components/band.json +2 -1
  76. package/manifest/components/bento.json +12 -12
  77. package/manifest/components/button.json +1 -0
  78. package/manifest/components/channel-matrix.json +1 -0
  79. package/manifest/components/chart-tooltip-content.json +1 -0
  80. package/manifest/components/checkbox-group.json +1 -1
  81. package/manifest/components/checkbox.json +1 -1
  82. package/manifest/components/chip.json +1 -0
  83. package/manifest/components/combobox.json +1 -0
  84. package/manifest/components/command-palette.json +1 -0
  85. package/manifest/components/context-menu.json +1 -1
  86. package/manifest/components/density.json +6 -2
  87. package/manifest/components/dialog.json +1 -1
  88. package/manifest/components/dmx-bar.json +1 -1
  89. package/manifest/components/dmx-strip.json +1 -1
  90. package/manifest/components/drawer.json +1 -1
  91. package/manifest/components/dropdown-menu.json +1 -0
  92. package/manifest/components/font-scope.json +81 -0
  93. package/manifest/components/footer.json +0 -1
  94. package/manifest/components/gloss-object.json +81 -0
  95. package/manifest/components/grid.json +6 -6
  96. package/manifest/components/halftone.json +79 -0
  97. package/manifest/components/hero.json +204 -0
  98. package/manifest/components/hover-card.json +1 -1
  99. package/manifest/components/kbd.json +1 -0
  100. package/manifest/components/keyword.json +67 -0
  101. package/manifest/components/ledger.json +4 -4
  102. package/manifest/components/logo-strip.json +93 -0
  103. package/manifest/components/markdown-text.json +2 -2
  104. package/manifest/components/menubar.json +1 -1
  105. package/manifest/components/motif.json +90 -0
  106. package/manifest/components/multi-select.json +1 -0
  107. package/manifest/components/navigation-menu.json +1 -0
  108. package/manifest/components/page-shell.json +5 -0
  109. package/manifest/components/particle-mark.json +75 -0
  110. package/manifest/components/popover.json +1 -1
  111. package/manifest/components/preview.json +9 -4
  112. package/manifest/components/radio-group.json +0 -1
  113. package/manifest/components/radio.json +0 -1
  114. package/manifest/components/ribbon.json +65 -0
  115. package/manifest/components/scrollable-tabs-list.json +1 -0
  116. package/manifest/components/segmented-control.json +1 -0
  117. package/manifest/components/sheet.json +1 -1
  118. package/manifest/components/sidebar.json +1 -0
  119. package/manifest/components/skeleton.json +1 -0
  120. package/manifest/components/stack.json +4 -4
  121. package/manifest/components/stat-band.json +68 -0
  122. package/manifest/components/stat.json +1 -1
  123. package/manifest/components/still-provider.json +60 -0
  124. package/manifest/components/switch.json +0 -1
  125. package/manifest/components/table.json +1 -1
  126. package/manifest/components/tabs.json +1 -0
  127. package/manifest/components/tags-input.json +1 -0
  128. package/manifest/components/theme-configurator.json +1 -0
  129. package/manifest/components/theme-provider.json +9 -4
  130. package/manifest/components/toast.json +1 -0
  131. package/manifest/components/tooltip.json +1 -1
  132. package/manifest/fixtures.json +18 -0
  133. package/manifest/manifest.json +267 -55
  134. package/manifest/tokens.json +91 -3
  135. package/package.json +4 -4
  136. package/dist/system/_paint-ground-pattern.js +0 -30
@@ -0,0 +1,30 @@
1
+ import * as React from "react";
2
+ //#region src/chrome/announcement-bar.d.ts
3
+ /** The bar's one link. */
4
+ interface AnnouncementBarAction {
5
+ /** The link's text, e.g. "Read the notes". */
6
+ label: string;
7
+ /** Where it goes. */
8
+ href: string;
9
+ }
10
+ interface AnnouncementBarProps extends React.ComponentPropsWithoutRef<"div"> {
11
+ /** The one line. */
12
+ children: React.ReactNode;
13
+ /** One link, read as the bar's action. */
14
+ action?: AnnouncementBarAction;
15
+ }
16
+ /**
17
+ * One line across the top of a page, and one link: a launch, a change, a date.
18
+ *
19
+ * Filled in the accent, centred, and as short as its line. It is not a
20
+ * notification: it says the same thing to every reader and cannot be dismissed,
21
+ * so use `Toast` or `Alert` for anything that is about the reader.
22
+ *
23
+ * Static markup — no `"use client"`.
24
+ *
25
+ * @example
26
+ * <AnnouncementBar action={{ label: "Learn more", href: "/changelog" }}>Plans for teams are here.</AnnouncementBar>
27
+ */
28
+ declare const AnnouncementBar: React.ForwardRefExoticComponent<AnnouncementBarProps & React.RefAttributes<HTMLDivElement>>;
29
+ //#endregion
30
+ export { AnnouncementBar, AnnouncementBarAction, AnnouncementBarProps };
@@ -0,0 +1,38 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import * as React from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/chrome/announcement-bar.tsx
5
+ /**
6
+ * One line across the top of a page, and one link: a launch, a change, a date.
7
+ *
8
+ * Filled in the accent, centred, and as short as its line. It is not a
9
+ * notification: it says the same thing to every reader and cannot be dismissed,
10
+ * so use `Toast` or `Alert` for anything that is about the reader.
11
+ *
12
+ * Static markup — no `"use client"`.
13
+ *
14
+ * @example
15
+ * <AnnouncementBar action={{ label: "Learn more", href: "/changelog" }}>Plans for teams are here.</AnnouncementBar>
16
+ */
17
+ const AnnouncementBar = React.forwardRef(function AnnouncementBar({ className, children, action, ...elementProps }, ref) {
18
+ return /* @__PURE__ */ jsxs("div", {
19
+ ref,
20
+ "data-slot": "announcement-bar",
21
+ className: cn("flex w-full flex-wrap items-center justify-center gap-x-(--cue-space-3) bg-accent-fill px-(--cue-space-5) py-(--cue-space-2) text-center text-(length:--cue-text-ui) text-accent-fill-fg", className),
22
+ ...elementProps,
23
+ children: [/* @__PURE__ */ jsx("span", { children }), action === void 0 ? null : /* @__PURE__ */ jsxs("a", {
24
+ href: action.href,
25
+ className: "font-semibold underline underline-offset-4",
26
+ children: [
27
+ action.label,
28
+ " ",
29
+ /* @__PURE__ */ jsx("span", {
30
+ "aria-hidden": "true",
31
+ children: "→"
32
+ })
33
+ ]
34
+ })]
35
+ });
36
+ });
37
+ //#endregion
38
+ export { AnnouncementBar };
@@ -15,7 +15,10 @@ interface AppBarRootProps extends React.ComponentPropsWithoutRef<"header"> {
15
15
  }
16
16
  type AppBarBrandProps = Omit<LinkProps, "variant" | "external">;
17
17
  interface AppBarNavProps extends Omit<React.ComponentPropsWithoutRef<"nav">, "children"> {
18
- /** Where the links sit when they are narrower than the space. Defaults to `"end"`. */
18
+ /**
19
+ * Where the links sit when they are narrower than the space. Omitted, the style
20
+ * set's `style-nav-align` decides, and without one it is `"end"`.
21
+ */
19
22
  align?: "start" | "center" | "end";
20
23
  /**
21
24
  * What the navigation does when the links do not fit. Defaults to `"scroll"`.
@@ -89,6 +89,11 @@ const NAV_ALIGN = {
89
89
  end: "justify-end"
90
90
  };
91
91
  /**
92
+ * The style set's nav alignment (structural Looks, spec 2026-10-02 §3.5), for a nav
93
+ * whose caller named none: `flex-end` unset, which is what `justify-end` draws.
94
+ */
95
+ const NAV_ALIGN_FROM_SET = "[justify-content:var(--cue-style-nav-align,flex-end)]";
96
+ /**
92
97
  * The `<nav>` landmark between the brand and the actions, and the one part of
93
98
  * the bar that gives up width.
94
99
  *
@@ -102,14 +107,14 @@ const NAV_ALIGN = {
102
107
  * scroll to reach it. Aligning the frame and letting the nav grow from its
103
108
  * start keeps every link reachable at every width.
104
109
  */
105
- const AppBarNav = React.forwardRef(function AppBarNav({ className, frameClassName, align = "end", overflow = "scroll", previousLabel = "Show earlier links", nextLabel = "Show later links", ...elementProps }, ref) {
110
+ const AppBarNav = React.forwardRef(function AppBarNav({ className, frameClassName, align, overflow = "scroll", previousLabel = "Show earlier links", nextLabel = "Show later links", ...elementProps }, ref) {
106
111
  return /* @__PURE__ */ jsx(EdgeScrollNav, {
107
112
  ref,
108
113
  "data-slot": "app-bar-nav",
109
114
  scrolls: overflow === "scroll",
110
115
  previousLabel,
111
116
  nextLabel,
112
- frameClassName: cn("min-w-0 flex-1", NAV_ALIGN[align], frameClassName),
117
+ frameClassName: cn("min-w-0 flex-1", align === void 0 ? NAV_ALIGN_FROM_SET : NAV_ALIGN[align], frameClassName),
113
118
  className: cn("flex min-w-0 items-center gap-(--cue-space-4)", className),
114
119
  ...elementProps
115
120
  });
@@ -10,6 +10,13 @@ import { DRAG_CLASSES, NO_DRAG_CLASSES } from "./_drag.js";
10
10
  import * as React from "react";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
12
  //#region src/chrome/app-shell.tsx
13
+ /**
14
+ * A style set's page ground (structural Looks, spec 2026-10-02 §3.1): a gradient of
15
+ * the theme's own inks, tiled or not. The frame is the one element that covers the
16
+ * whole window, so the ground lands here. Unset, every reader falls back to the
17
+ * value it had — no image, `auto`, `no-repeat` — and the frame paints as it did.
18
+ */
19
+ const PAGE_GROUND_CLASSES = "[background-image:var(--cue-style-page-ground-image,none)] [background-size:var(--cue-style-page-ground-tile,auto)] [background-repeat:var(--cue-style-page-ground-repeat,no-repeat)]";
13
20
  const AppShellContext = React.createContext({
14
21
  navOpen: false,
15
22
  setNavOpen: () => {},
@@ -114,7 +121,7 @@ const AppShell = {
114
121
  ref,
115
122
  "data-slot": "app-shell",
116
123
  "data-rail": rail ? "" : void 0,
117
- className: cn("group/app-shell relative flex h-dvh flex-col overflow-clip bg-bg text-fg", TEXTURE_CLASSES, className),
124
+ className: cn("group/app-shell relative flex h-dvh flex-col overflow-clip bg-bg text-fg", TEXTURE_CLASSES, PAGE_GROUND_CLASSES, className),
118
125
  style: {
119
126
  [NAV_WIDTH_PROPERTY]: rail ? railWidth : navWidth,
120
127
  ...style
@@ -76,7 +76,9 @@ interface BandProps extends React.ComponentPropsWithoutRef<"section"> {
76
76
  * nothing and they do not vanish symmetrically: `--cue-band-ground` is
77
77
  * `transparent` on `:root` and on every set but `marketing` and `kiln`, so a
78
78
  * `"ground"` paints nothing on most pages, while `--cue-band-rule` is `0px`
79
- * under those two alone. Both are deliberate and nothing warns.
79
+ * under those two and under `ledger` and `prism`, which separate bands by
80
+ * rhythm alone (and `prism`'s by a ground image, `style-band-ground-image`).
81
+ * Both are deliberate and nothing warns.
80
82
  *
81
83
  * **Two substitution boundaries, because `--cue-band-y` is a `calc()` resolved
82
84
  * at the element that declares it.** A `<Density>` island nested inside a
@@ -3,13 +3,12 @@ import { TEXTURE_CLASSES } from "../system/_paint-texture.js";
3
3
  import { Container } from "../layout/container.js";
4
4
  import { bandClasses } from "./_band.js";
5
5
  import { BAND_BROAD_WIDTH, BAND_Y_HALVES } from "../system/_style-band.js";
6
- import { GROUND_PATTERN_CLASSES } from "../system/_paint-ground-pattern.js";
7
6
  import * as React from "react";
8
7
  import { jsx } from "react/jsx-runtime";
9
8
  //#region src/chrome/band.tsx
10
9
  const SEPARATION_PAINT = {
11
10
  rule: "border-(--cue-band-rule-color)",
12
- ground: `bg-(--cue-band-ground) ${GROUND_PATTERN_CLASSES} ${TEXTURE_CLASSES} before:[mask-image:linear-gradient(var(--cue-band-ground),var(--cue-band-ground)),var(--cue-paint-fade)] before:[-webkit-mask-image:linear-gradient(var(--cue-band-ground),var(--cue-band-ground)),var(--cue-paint-fade)]`,
11
+ ground: `bg-(--cue-band-ground) [background-image:var(--cue-paint-ground-pattern,none),var(--cue-style-band-ground-image,none)] [background-size:var(--cue-paint-ground-pattern-size,auto),auto] ${TEXTURE_CLASSES} before:[mask-image:linear-gradient(var(--cue-band-ground),var(--cue-band-ground)),var(--cue-paint-fade)] before:[-webkit-mask-image:linear-gradient(var(--cue-band-ground),var(--cue-band-ground)),var(--cue-paint-fade)]`,
13
12
  bleed: null,
14
13
  none: null
15
14
  };
@@ -32,7 +31,9 @@ const SEPARATION_PAINT = {
32
31
  * nothing and they do not vanish symmetrically: `--cue-band-ground` is
33
32
  * `transparent` on `:root` and on every set but `marketing` and `kiln`, so a
34
33
  * `"ground"` paints nothing on most pages, while `--cue-band-rule` is `0px`
35
- * under those two alone. Both are deliberate and nothing warns.
34
+ * under those two and under `ledger` and `prism`, which separate bands by
35
+ * rhythm alone (and `prism`'s by a ground image, `style-band-ground-image`).
36
+ * Both are deliberate and nothing warns.
36
37
  *
37
38
  * **Two substitution boundaries, because `--cue-band-y` is a `calc()` resolved
38
39
  * at the element that declares it.** A `<Density>` island nested inside a
@@ -0,0 +1,61 @@
1
+ import * as React from "react";
2
+ //#region src/chrome/hero.d.ts
3
+ interface HeroProps extends Omit<React.ComponentPropsWithoutRef<"section">, "title"> {
4
+ /** A short line above the title, usually an `Eyebrow`. */
5
+ eyebrow?: React.ReactNode;
6
+ /** The title's first line. A hero without a title is a band; use `Band`. */
7
+ title: React.ReactNode;
8
+ /**
9
+ * The title's second line, drawn in the style set's second ink: the accent, a
10
+ * muted ink or the keyword gradient, by the Look. Omit it for a one-line title.
11
+ */
12
+ titleSecond?: React.ReactNode;
13
+ /**
14
+ * The title's heading level. `1`, the default, is a page's opening; `2` or `3`
15
+ * is a hero that opens a section under the page's own `<h1>`, or a preview of
16
+ * one on a page about something else.
17
+ */
18
+ level?: 1 | 2 | 3;
19
+ /** The sentence under the title. */
20
+ lede?: React.ReactNode;
21
+ /** The calls to action, usually a primary and a secondary `Button`. */
22
+ actions?: React.ReactNode;
23
+ /** Art beside or behind the words: a `Motif`, an image, a product shot. */
24
+ media?: React.ReactNode;
25
+ /** A row of figures under the lede, usually a `StatBand`. */
26
+ stats?: React.ReactNode;
27
+ /**
28
+ * `page` draws the hero on the page's own ground. `inverse` draws it as a dark
29
+ * card on any page — its whole colour scope flipped, controls and focus included —
30
+ * which is how a report opens on a light page. Defaults to `"page"`.
31
+ */
32
+ surface?: "page" | "inverse";
33
+ }
34
+ /**
35
+ * The opening of a page: a title, the line under it, what to do next, and art.
36
+ *
37
+ * **The Look decides the arrangement, not this component.** The grid reads the
38
+ * style set's hero recipe — `style-hero-columns`, `style-hero-align` and
39
+ * `style-hero-media-order` — so the same hero opens centred under one Look, with
40
+ * its art beside the words under another, and with the art first under a third,
41
+ * with no prop changed. Unset, it is one column, start-aligned, art after the
42
+ * words.
43
+ *
44
+ * **No media, one column**, whatever the recipe says: two columns with nothing in
45
+ * the second is half a page of nothing.
46
+ *
47
+ * `surface="inverse"` is a theme island in dark mode, so every ink inside it —
48
+ * the title, the lede, a button's fill and its focus ring — is the dark palette's,
49
+ * and an overlay opened from it inherits that. The island carries
50
+ * `data-cue-rescope`, where the token build re-declares the active set's and
51
+ * pack's inks, so the card is right in the first frame, before hydration.
52
+ *
53
+ * Static markup — no `"use client"`.
54
+ *
55
+ * @example
56
+ * <Hero eyebrow={<Eyebrow>Category report</Eyebrow>} title="Snacks & nuts" titleSecond="Range review"
57
+ * lede="Every recommendation has a boundary." actions={<Button>Open the report</Button>} />
58
+ */
59
+ declare const Hero: React.ForwardRefExoticComponent<HeroProps & React.RefAttributes<HTMLElement>>;
60
+ //#endregion
61
+ export { Hero, HeroProps };
@@ -0,0 +1,108 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { ThemeProvider } from "../system/theme-provider.js";
3
+ import { BODY_VOICE } from "../system/_style-body.js";
4
+ import { LEDE_INK, LEDE_LEADING, LEDE_SIZE, LEDE_TRACKING } from "../system/_style-lede.js";
5
+ import { HERO_TITLE_VOICE } from "../system/_style-hero-title.js";
6
+ import * as React from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/chrome/hero.tsx
9
+ /**
10
+ * The second title line's ink (spec 2026-10-02 §3.1, §15.2). A colour by default;
11
+ * under a set whose line takes the keyword gradient, the build restates the
12
+ * colour as the accent and derives a fill, a clip and a text fill, so the
13
+ * gradient paints the glyphs through `background-clip: text`. Each derived
14
+ * name's fallback is the property's own initial value, so a line no set
15
+ * touches computes exactly what it did. All of it sits behind
16
+ * `@supports (background-clip: text)`, so a browser that cannot clip keeps the
17
+ * solid accent; forced colours drop the image and the transparent fill, as
18
+ * `Keyword` does.
19
+ */
20
+ const TITLE_SECOND = "block text-[color:var(--cue-style-hero-title-ink-2,var(--cue-fg))] supports-[background-clip:text]:[background-image:var(--cue-style-hero-title-fill-2,none)] supports-[background-clip:text]:[background-clip:var(--cue-style-hero-title-clip-2,border-box)] supports-[background-clip:text]:[-webkit-background-clip:var(--cue-style-hero-title-clip-2,border-box)] supports-[background-clip:text]:[-webkit-text-fill-color:var(--cue-style-hero-title-text-2,currentcolor)] forced-colors:[background-image:none] forced-colors:[-webkit-text-fill-color:CanvasText]";
21
+ /**
22
+ * The opening of a page: a title, the line under it, what to do next, and art.
23
+ *
24
+ * **The Look decides the arrangement, not this component.** The grid reads the
25
+ * style set's hero recipe — `style-hero-columns`, `style-hero-align` and
26
+ * `style-hero-media-order` — so the same hero opens centred under one Look, with
27
+ * its art beside the words under another, and with the art first under a third,
28
+ * with no prop changed. Unset, it is one column, start-aligned, art after the
29
+ * words.
30
+ *
31
+ * **No media, one column**, whatever the recipe says: two columns with nothing in
32
+ * the second is half a page of nothing.
33
+ *
34
+ * `surface="inverse"` is a theme island in dark mode, so every ink inside it —
35
+ * the title, the lede, a button's fill and its focus ring — is the dark palette's,
36
+ * and an overlay opened from it inherits that. The island carries
37
+ * `data-cue-rescope`, where the token build re-declares the active set's and
38
+ * pack's inks, so the card is right in the first frame, before hydration.
39
+ *
40
+ * Static markup — no `"use client"`.
41
+ *
42
+ * @example
43
+ * <Hero eyebrow={<Eyebrow>Category report</Eyebrow>} title="Snacks & nuts" titleSecond="Range review"
44
+ * lede="Every recommendation has a boundary." actions={<Button>Open the report</Button>} />
45
+ */
46
+ const Hero = React.forwardRef(function Hero({ className, eyebrow, title, titleSecond, level = 1, lede, actions, media, stats, surface = "page", ...elementProps }, ref) {
47
+ const words = /* @__PURE__ */ jsxs("div", {
48
+ "data-slot": "hero-words",
49
+ className: "flex min-w-0 flex-col gap-(--cue-space-5) [align-items:var(--cue-style-hero-align,start)] [text-align:var(--cue-style-hero-align,start)]",
50
+ children: [
51
+ eyebrow,
52
+ /* @__PURE__ */ jsxs(level === 3 ? "h3" : level === 2 ? "h2" : "h1", {
53
+ "data-slot": "hero-title",
54
+ className: cn(HERO_TITLE_VOICE, "m-0"),
55
+ children: [/* @__PURE__ */ jsx("span", {
56
+ className: "block",
57
+ children: title
58
+ }), titleSecond === void 0 ? null : /* @__PURE__ */ jsx("span", {
59
+ "data-slot": "hero-title-second",
60
+ className: TITLE_SECOND,
61
+ children: titleSecond
62
+ })]
63
+ }),
64
+ lede === void 0 ? null : /* @__PURE__ */ jsx("p", {
65
+ "data-slot": "hero-lede",
66
+ className: cn(BODY_VOICE, LEDE_SIZE, LEDE_LEADING, LEDE_TRACKING, LEDE_INK, "m-0 max-w-[48ch]"),
67
+ children: lede
68
+ }),
69
+ actions === void 0 ? null : /* @__PURE__ */ jsx("div", {
70
+ "data-slot": "hero-actions",
71
+ className: "flex flex-wrap items-center gap-(--cue-space-3) [justify-content:var(--cue-style-hero-align,start)]",
72
+ children: actions
73
+ }),
74
+ stats === void 0 ? null : /* @__PURE__ */ jsx("div", {
75
+ "data-slot": "hero-stats",
76
+ className: "w-full",
77
+ children: stats
78
+ })
79
+ ]
80
+ });
81
+ const body = /* @__PURE__ */ jsxs("div", {
82
+ "data-slot": "hero-grid",
83
+ "data-media": media === void 0 ? void 0 : "",
84
+ className: cn("grid w-full items-center gap-(--cue-space-7)", media === void 0 ? "grid-cols-1" : "grid-cols-1 md:[grid-template-columns:var(--cue-style-hero-columns,1fr)]"),
85
+ children: [words, media === void 0 ? null : /* @__PURE__ */ jsx("div", {
86
+ "data-slot": "hero-media",
87
+ className: "min-w-0 [order:var(--cue-style-hero-media-order,1)]",
88
+ children: media
89
+ })]
90
+ });
91
+ return /* @__PURE__ */ jsx("section", {
92
+ ref,
93
+ "data-slot": "hero",
94
+ "data-surface": surface,
95
+ className: cn("w-full", className),
96
+ ...elementProps,
97
+ children: surface === "inverse" ? /* @__PURE__ */ jsx(ThemeProvider, {
98
+ mode: "dark",
99
+ children: /* @__PURE__ */ jsx("div", {
100
+ "data-cue-rescope": "",
101
+ className: "rounded-surface bg-bg p-(--cue-space-7) text-fg",
102
+ children: body
103
+ })
104
+ }) : body
105
+ });
106
+ });
107
+ //#endregion
108
+ export { Hero };
@@ -21,4 +21,9 @@ import { ScrollableTabsList, ScrollableTabsListProps, Tabs, TabsListProps, TabsP
21
21
  import { TitleBar, TitleBarProps } from "./title-bar.js";
22
22
  import { Tree, TreeProps, TreeVisibility, TreeVisibilityToggle, TreeVisibilityToggleProps } from "./tree.js";
23
23
  import { Toolbar, ToolbarButtonProps, ToolbarGroupProps, ToolbarRootProps, ToolbarSeparatorProps } from "./toolbar.js";
24
- export { AppBar, type AppBarActionsProps, type AppBarBrandProps, type AppBarLinkProps, type AppBarNavProps, type AppBarRegister, type AppBarRootProps, type AppBarSeparatorProps, AppShell, type AppShellAsideProps, type AppShellBarProps, type AppShellBodyProps, type AppShellContentProps, type AppShellNavTriggerProps, type AppShellRootProps, type AppShellScrollerProps, type AppShellSidebarProps, Band, type BandProps, type BandSeparation, type BandWidth, type BarOverflow, EmptyState, type EmptyStateProps, Eyebrow, type EyebrowProps, Footer, type FooterProps, Inspector, type InspectorFooterProps, type InspectorRootProps, type InspectorRowProps, type InspectorSectionProps, NavigationMenu, type NavigationMenuContentProps, type NavigationMenuItemProps, type NavigationMenuLinkProps, type NavigationMenuListProps, type NavigationMenuRootProps, type NavigationMenuTriggerProps, type NavigationMenuViewportProps, PageShell, type PageShellProps, type PageWidth, Panel, PanelHeader, type PanelHeaderProps, type PanelProps, Row, type RowProps, ScrollArea, type ScrollAreaOrientation, type ScrollAreaProps, type ScrollEdges, ScrollableTabsList, type ScrollableTabsListProps, SeamList, type SeamListProps, SectionHeader, type SectionHeaderProps, SegmentedControl, type SegmentedControlItem, type SegmentedControlProps, type SegmentedControlSize, StatusBar, type StatusBarGroupProps, type StatusBarItemProps, type StatusBarProminence, type StatusBarProps, type StatusBarTone, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, type TabsListProps, type TabsPanelProps, type TabsRootProps, type TabsTabProps, TitleBar, type TitleBarProps, Toolbar, type ToolbarButtonProps, type ToolbarGroupProps, type ToolbarRootProps, type ToolbarSeparatorProps, Tree, type TreeGuide, type TreeItem, type TreeProps, type TreeRow, type TreeVisibility, TreeVisibilityToggle, type TreeVisibilityToggleProps, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
24
+ import { AnnouncementBar, AnnouncementBarAction, AnnouncementBarProps } from "./announcement-bar.js";
25
+ import { Hero, HeroProps } from "./hero.js";
26
+ import { Keyword, KeywordProps } from "./keyword.js";
27
+ import { LogoStrip, LogoStripProps } from "./logo-strip.js";
28
+ import { StatBand, StatBandProps, StatBandStat } from "./stat-band.js";
29
+ export { AnnouncementBar, type AnnouncementBarAction, type AnnouncementBarProps, AppBar, type AppBarActionsProps, type AppBarBrandProps, type AppBarLinkProps, type AppBarNavProps, type AppBarRegister, type AppBarRootProps, type AppBarSeparatorProps, AppShell, type AppShellAsideProps, type AppShellBarProps, type AppShellBodyProps, type AppShellContentProps, type AppShellNavTriggerProps, type AppShellRootProps, type AppShellScrollerProps, type AppShellSidebarProps, Band, type BandProps, type BandSeparation, type BandWidth, type BarOverflow, EmptyState, type EmptyStateProps, Eyebrow, type EyebrowProps, Footer, type FooterProps, Hero, type HeroProps, Inspector, type InspectorFooterProps, type InspectorRootProps, type InspectorRowProps, type InspectorSectionProps, Keyword, type KeywordProps, LogoStrip, type LogoStripProps, NavigationMenu, type NavigationMenuContentProps, type NavigationMenuItemProps, type NavigationMenuLinkProps, type NavigationMenuListProps, type NavigationMenuRootProps, type NavigationMenuTriggerProps, type NavigationMenuViewportProps, PageShell, type PageShellProps, type PageWidth, Panel, PanelHeader, type PanelHeaderProps, type PanelProps, Row, type RowProps, ScrollArea, type ScrollAreaOrientation, type ScrollAreaProps, type ScrollEdges, ScrollableTabsList, type ScrollableTabsListProps, SeamList, type SeamListProps, SectionHeader, type SectionHeaderProps, SegmentedControl, type SegmentedControlItem, type SegmentedControlProps, type SegmentedControlSize, StatBand, type StatBandProps, type StatBandStat, StatusBar, type StatusBarGroupProps, type StatusBarItemProps, type StatusBarProminence, type StatusBarProps, type StatusBarTone, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, type TabsListProps, type TabsPanelProps, type TabsRootProps, type TabsTabProps, TitleBar, type TitleBarProps, Toolbar, type ToolbarButtonProps, type ToolbarGroupProps, type ToolbarRootProps, type ToolbarSeparatorProps, Tree, type TreeGuide, type TreeItem, type TreeProps, type TreeRow, type TreeVisibility, TreeVisibilityToggle, type TreeVisibilityToggleProps, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
@@ -19,4 +19,9 @@ import { ScrollableTabsList, Tabs } from "./tabs.js";
19
19
  import { TitleBar } from "./title-bar.js";
20
20
  import { Tree, TreeVisibilityToggle } from "./tree.js";
21
21
  import { Toolbar } from "./toolbar.js";
22
- export { AppBar, AppShell, Band, EmptyState, Eyebrow, Footer, Inspector, NavigationMenu, PageShell, Panel, PanelHeader, Row, ScrollArea, ScrollableTabsList, SeamList, SectionHeader, SegmentedControl, StatusBar, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, TitleBar, Toolbar, Tree, TreeVisibilityToggle, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
22
+ import { AnnouncementBar } from "./announcement-bar.js";
23
+ import { Hero } from "./hero.js";
24
+ import { Keyword } from "./keyword.js";
25
+ import { LogoStrip } from "./logo-strip.js";
26
+ import { StatBand } from "./stat-band.js";
27
+ export { AnnouncementBar, AppBar, AppShell, Band, EmptyState, Eyebrow, Footer, Hero, Inspector, Keyword, LogoStrip, NavigationMenu, PageShell, Panel, PanelHeader, Row, ScrollArea, ScrollableTabsList, SeamList, SectionHeader, SegmentedControl, StatBand, StatusBar, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, TitleBar, Toolbar, Tree, TreeVisibilityToggle, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+ //#region src/chrome/keyword.d.ts
3
+ interface KeywordProps extends React.ComponentPropsWithoutRef<"span"> {
4
+ /** The words to mark: one word or a short phrase in a title. */
5
+ children: React.ReactNode;
6
+ }
7
+ /**
8
+ * A word in a title filled with the Look's keyword gradient.
9
+ *
10
+ * Reads `style-keyword-fill`, a gradient of the theme's own inks, through
11
+ * `background-clip: text`. Where the browser cannot clip a background to text, or
12
+ * in forced colours, it is the accent instead, so the word is never transparent.
13
+ * Unset, the fill is the accent itself.
14
+ *
15
+ * Static markup — no `"use client"`.
16
+ *
17
+ * @example
18
+ * <h1>The world's first <Keyword>predictive</Keyword> retail system.</h1>
19
+ */
20
+ declare const Keyword: React.ForwardRefExoticComponent<KeywordProps & React.RefAttributes<HTMLSpanElement>>;
21
+ //#endregion
22
+ export { Keyword, KeywordProps };
@@ -0,0 +1,28 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import * as React from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/chrome/keyword.tsx
5
+ /**
6
+ * A word in a title filled with the Look's keyword gradient.
7
+ *
8
+ * Reads `style-keyword-fill`, a gradient of the theme's own inks, through
9
+ * `background-clip: text`. Where the browser cannot clip a background to text, or
10
+ * in forced colours, it is the accent instead, so the word is never transparent.
11
+ * Unset, the fill is the accent itself.
12
+ *
13
+ * Static markup — no `"use client"`.
14
+ *
15
+ * @example
16
+ * <h1>The world's first <Keyword>predictive</Keyword> retail system.</h1>
17
+ */
18
+ const Keyword = React.forwardRef(function Keyword({ className, children, ...elementProps }, ref) {
19
+ return /* @__PURE__ */ jsx("span", {
20
+ ref,
21
+ "data-slot": "keyword",
22
+ className: cn("text-accent", "supports-[background-clip:text]:bg-[image:var(--cue-style-keyword-fill,linear-gradient(var(--cue-accent),var(--cue-accent)))] supports-[background-clip:text]:bg-clip-text supports-[background-clip:text]:text-transparent", "forced-colors:bg-none forced-colors:text-[CanvasText]", className),
23
+ ...elementProps,
24
+ children
25
+ });
26
+ });
27
+ //#endregion
28
+ export { Keyword };
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ //#region src/chrome/logo-strip.d.ts
3
+ interface LogoStripProps extends React.ComponentPropsWithoutRef<"div"> {
4
+ /** The names, as text. A real company's logo is never drawn here. */
5
+ names: readonly string[];
6
+ /** The line above the row, e.g. "Trusted by teams at". */
7
+ caption?: React.ReactNode;
8
+ }
9
+ /**
10
+ * A row of names under a caption: the "trusted by" strip of a product page.
11
+ *
12
+ * Text wordmarks only, set in the display face at a muted ink, never an image: a
13
+ * real company's mark is a trademark that is not this library's to draw, and a
14
+ * gallery page's names are fictional. The row wraps, and centres when it does.
15
+ *
16
+ * Static markup — no `"use client"`.
17
+ *
18
+ * @example
19
+ * <LogoStrip caption="Trusted by teams at" names={["Northwind", "Halcyon", "Kestrel"]} />
20
+ */
21
+ declare const LogoStrip: React.ForwardRefExoticComponent<LogoStripProps & React.RefAttributes<HTMLDivElement>>;
22
+ //#endregion
23
+ export { LogoStrip, LogoStripProps };
@@ -0,0 +1,43 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { LABEL_INK, LABEL_INK_SUBTLE } from "../system/_style-label-ink.js";
3
+ import { LABEL_FACE } from "../system/_style-label-face.js";
4
+ import { LABEL_CASE } from "../system/_style-label-case.js";
5
+ import { LABEL_TRACKING } from "../system/_style-label-tracking.js";
6
+ import { LABEL_SIZE } from "../system/_style-label-size.js";
7
+ import * as React from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ //#region src/chrome/logo-strip.tsx
10
+ /**
11
+ * A row of names under a caption: the "trusted by" strip of a product page.
12
+ *
13
+ * Text wordmarks only, set in the display face at a muted ink, never an image: a
14
+ * real company's mark is a trademark that is not this library's to draw, and a
15
+ * gallery page's names are fictional. The row wraps, and centres when it does.
16
+ *
17
+ * Static markup — no `"use client"`.
18
+ *
19
+ * @example
20
+ * <LogoStrip caption="Trusted by teams at" names={["Northwind", "Halcyon", "Kestrel"]} />
21
+ */
22
+ const LogoStrip = React.forwardRef(function LogoStrip({ className, names, caption, ...elementProps }, ref) {
23
+ return /* @__PURE__ */ jsxs("div", {
24
+ ref,
25
+ "data-slot": "logo-strip",
26
+ className: cn("flex flex-col items-center gap-(--cue-space-4)", className),
27
+ ...elementProps,
28
+ children: [caption === void 0 ? null : /* @__PURE__ */ jsx("p", {
29
+ "data-slot": "logo-strip-caption",
30
+ className: cn(LABEL_FACE, LABEL_SIZE, LABEL_CASE, LABEL_TRACKING, LABEL_INK_SUBTLE, "m-0"),
31
+ children: caption
32
+ }), /* @__PURE__ */ jsx("ul", {
33
+ role: "list",
34
+ className: "m-0 flex list-none flex-wrap items-center justify-center gap-x-(--cue-space-7) gap-y-(--cue-space-4) p-0",
35
+ children: names.map((name) => /* @__PURE__ */ jsx("li", {
36
+ className: cn("font-sans text-(length:--cue-text-title) font-semibold", LABEL_INK),
37
+ children: name
38
+ }, name))
39
+ })]
40
+ });
41
+ });
42
+ //#endregion
43
+ export { LogoStrip };
@@ -26,6 +26,18 @@ const WIDTHS = {
26
26
  full: ""
27
27
  };
28
28
  /**
29
+ * A style set's frame and grid lines (structural Looks, spec 2026-10-02 §3.1): a
30
+ * dashed hairline round the content column, and two hairlines at its edges. Each is
31
+ * a pseudo-element whose `content` is a token the build derives — `none` unless the
32
+ * set asks, which generates no box at all — and the shell is their containing block
33
+ * only while one is drawn, so a set without lines lays out and paints as before.
34
+ */
35
+ const FRAME_LINES_CLASSES = [
36
+ "[position:var(--cue-style-page-frame-position,static)]",
37
+ "before:content-[var(--cue-style-frame-lines-content,none)] before:pointer-events-none before:absolute before:inset-0 before:border before:border-dashed before:border-(--cue-border)",
38
+ "after:content-[var(--cue-style-grid-lines-content,none)] after:pointer-events-none after:absolute after:inset-y-0 after:inset-x-(--cue-space-6) after:border-x after:border-(--cue-border)"
39
+ ].join(" ");
40
+ /**
29
41
  * A centred content column with a standard page header.
30
42
  *
31
43
  * The header is emitted only when something fills it, so a shell used purely
@@ -47,7 +59,7 @@ const PageShell = React.forwardRef(function PageShell({ className, width = "narr
47
59
  return /* @__PURE__ */ jsxs("div", {
48
60
  ref,
49
61
  "data-slot": "page-shell",
50
- className: cn("mx-auto flex w-full flex-col gap-(--cue-space-6) px-(--cue-space-6) py-(--cue-space-7)", WIDTHS[width], className),
62
+ className: cn("mx-auto flex w-full flex-col gap-(--cue-space-6) px-(--cue-space-6) py-(--cue-space-7)", FRAME_LINES_CLASSES, WIDTHS[width], className),
51
63
  ...elementProps,
52
64
  children: [hasHeader ? /* @__PURE__ */ jsxs("header", {
53
65
  "data-slot": "page-shell-header",
@@ -0,0 +1,32 @@
1
+ import * as React from "react";
2
+ //#region src/chrome/stat-band.d.ts
3
+ interface StatBandStat {
4
+ /** What is measured. */
5
+ label: React.ReactNode;
6
+ /** The reading. */
7
+ value: React.ReactNode;
8
+ /** An optional second line: a delta, a window, a unit. */
9
+ note?: React.ReactNode;
10
+ }
11
+ interface StatBandProps extends React.ComponentPropsWithoutRef<"div"> {
12
+ /** The figures, in reading order. One to six sit in one row; more wrap. */
13
+ stats: readonly StatBandStat[];
14
+ }
15
+ /**
16
+ * A row of named figures: the KPI band under a report's title.
17
+ *
18
+ * Built from `Stat`, so each figure keeps its caption-over-reading voice and its
19
+ * tabular numerals. The style set decides the band's look: `style-stat-divider`
20
+ * draws a rule between figures, and `Stat` reads `style-stat-numeral-ink` for the
21
+ * reading's colour. Unset, there is no rule and the reading keeps its ink. A
22
+ * wrapped row's first figure draws no rule, so a narrow screen never shows a
23
+ * divider hanging off the left edge.
24
+ *
25
+ * Static markup — no `"use client"`.
26
+ *
27
+ * @example
28
+ * <StatBand stats={[{ label: "SKUs", value: "1,091" }, { label: "Brands", value: "148" }]} />
29
+ */
30
+ declare const StatBand: React.ForwardRefExoticComponent<StatBandProps & React.RefAttributes<HTMLDivElement>>;
31
+ //#endregion
32
+ export { StatBand, StatBandProps, StatBandStat };
@@ -0,0 +1,39 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { Stat } from "../instruments/stat.js";
3
+ import * as React from "react";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/chrome/stat-band.tsx
6
+ /**
7
+ * A row of named figures: the KPI band under a report's title.
8
+ *
9
+ * Built from `Stat`, so each figure keeps its caption-over-reading voice and its
10
+ * tabular numerals. The style set decides the band's look: `style-stat-divider`
11
+ * draws a rule between figures, and `Stat` reads `style-stat-numeral-ink` for the
12
+ * reading's colour. Unset, there is no rule and the reading keeps its ink. A
13
+ * wrapped row's first figure draws no rule, so a narrow screen never shows a
14
+ * divider hanging off the left edge.
15
+ *
16
+ * Static markup — no `"use client"`.
17
+ *
18
+ * @example
19
+ * <StatBand stats={[{ label: "SKUs", value: "1,091" }, { label: "Brands", value: "148" }]} />
20
+ */
21
+ const StatBand = React.forwardRef(function StatBand({ className, stats, ...elementProps }, ref) {
22
+ return /* @__PURE__ */ jsx("div", {
23
+ ref,
24
+ "data-slot": "stat-band",
25
+ className: cn("overflow-hidden", className),
26
+ ...elementProps,
27
+ children: /* @__PURE__ */ jsx("div", {
28
+ className: "-ms-(--cue-space-5) flex flex-wrap gap-y-(--cue-space-5)",
29
+ children: stats.map((stat, index) => /* @__PURE__ */ jsx(Stat, {
30
+ label: stat.label,
31
+ value: stat.value,
32
+ detail: stat.note,
33
+ className: "relative min-w-[10rem] flex-1 ps-(--cue-space-5) before:pointer-events-none before:absolute before:inset-y-0 before:start-0 before:[border-inline-start:var(--cue-style-stat-divider,none)] before:content-['']"
34
+ }, index))
35
+ })
36
+ });
37
+ });
38
+ //#endregion
39
+ export { StatBand };
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
+ import { usePrefersReducedMotion } from "../instruments/_motion.js";
4
+ import { useStill } from "../system/still.js";
3
5
  import { FOCUS_RING } from "../system/_focus-ring.js";
4
6
  import { useIsomorphicLayoutEffect } from "../system/use-isomorphic-layout-effect.js";
5
7
  import { useComposedRefs } from "../lib/compose.js";
6
8
  import { useCuePortalProps } from "../system/portal.js";
7
- import { usePrefersReducedMotion } from "../instruments/_motion.js";
8
9
  import { DMX_RAMP_NAMES, activeChannels, dmxPalette, dmxPaletteColor, dmxPaletteInk } from "./_ramp.js";
9
10
  import { DmxReadout } from "./_readout.js";
10
11
  import * as React from "react";
@@ -77,8 +78,9 @@ const ChannelMatrix = React.forwardRef(function ChannelMatrix({ className, data,
77
78
  universesRef.current = universes;
78
79
  const [hover, setHover] = React.useState(null);
79
80
  const portal = useCuePortalProps();
80
- const reduced = usePrefersReducedMotion();
81
- const streaming = live && !reduced;
81
+ const prefersReduced = usePrefersReducedMotion();
82
+ const held = useStill();
83
+ const streaming = live && !(prefersReduced || held);
82
84
  const paint = React.useCallback(() => {
83
85
  const bytes = dataRef.current;
84
86
  const palette = paletteRef.current;