@cloudgrid-io/ui 0.14.0 → 0.16.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.
@@ -0,0 +1,16 @@
1
+ export interface CelebrationProps {
2
+ /**
3
+ * Called once, when the burst has finished and the overlay is gone. Under
4
+ * reduced motion it is called immediately instead.
5
+ */
6
+ onDone?: () => void;
7
+ /** Milliseconds. Read once at mount, clamped. */
8
+ duration?: number;
9
+ /** Read once at mount, clamped. */
10
+ particleCount?: number;
11
+ /** Applied to the overlay canvas. */
12
+ className?: string;
13
+ }
14
+ declare function Celebration({ onDone, duration, particleCount, className }: CelebrationProps): null;
15
+ export { Celebration };
16
+ //# sourceMappingURL=celebration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"celebration.d.ts","sourceRoot":"","sources":["../../src/blocks/celebration.tsx"],"names":[],"mappings":"AAgIA,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAwCD,iBAAS,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,gBAAgB,QAqJpF;AAED,OAAO,EAAE,WAAW,EAAE,CAAA"}
@@ -0,0 +1,259 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ /**
4
+ * One burst of confetti, and then it is gone.
5
+ *
6
+ * ## Why this exists (#160)
7
+ *
8
+ * The onboarding welcome screen wanted a celebration. Written inline in the
9
+ * console it would be a canvas animation carrying its own hardcoded palette,
10
+ * living beside the screen that happens to use it first, and a second screen
11
+ * that wanted one would get a second copy. So it is a component, and its colours
12
+ * come out of the token layer like everything else here.
13
+ *
14
+ * ## What it does
15
+ *
16
+ * Mounts, paints a single burst over the viewport, removes itself, and calls
17
+ * `onDone`. It never loops, never fires twice, and never restarts because its
18
+ * parent re-rendered or handed it a new prop. Mount it to celebrate; unmount is
19
+ * not required, since it takes its own overlay away when the burst is over.
20
+ *
21
+ * ```tsx
22
+ * {justPlugged ? <Celebration onDone={() => setJustPlugged(false)} /> : null}
23
+ * ```
24
+ *
25
+ * ## Reduced motion
26
+ *
27
+ * `prefers-reduced-motion: reduce` renders nothing at all — no canvas is
28
+ * created — and `onDone` fires immediately, on the same tick as the mount. That
29
+ * second half is the important one: a caller may gate a screen transition on
30
+ * `onDone`, and it must not sit for 1.8s waiting for an animation that is never
31
+ * going to run. The celebration is decoration; the screen is the message.
32
+ *
33
+ * A **static accent** — the same burst painted once and held, rather than
34
+ * animated — is the obvious future alternative, and it would slot in here with
35
+ * no API change: same mount, same `onDone`, one `drawFrame` call instead of a
36
+ * loop. It is deliberately not shipped yet, because an accent that only
37
+ * reduced-motion users ever see is a second design and nobody has drawn it.
38
+ *
39
+ * ## Colour
40
+ *
41
+ * `--cg-primary`, `--cg-secondary` and `--cg-tertiary`, resolved off the
42
+ * document root once at mount. Canvas takes colour strings rather than CSS
43
+ * variables, so the read has to happen somewhere; doing it at mount keeps it off
44
+ * the per-frame path and means a re-theme moves the confetti with everything
45
+ * else. The ROOT is read deliberately, not the nearest surface: this overlay
46
+ * belongs to the viewport, not to whichever panel mounted it.
47
+ *
48
+ * If none of the three resolve — a consumer who never imported `tokens.css` —
49
+ * the fallback is the overlay's own inherited text colour. Never a hardcoded
50
+ * one: a brand effect that invents a brand is exactly what the token layer
51
+ * exists to prevent.
52
+ *
53
+ * ## What it costs
54
+ *
55
+ * Nothing at import time, no dependency beyond React, and no node in the
56
+ * caller's tree — the component renders `null` and the overlay hangs off
57
+ * `document.body`. That last part is not a shortcut. A `position: fixed`
58
+ * element is positioned against the nearest ancestor carrying a `transform`,
59
+ * `filter` or `will-change`, and an onboarding screen is exactly the kind of
60
+ * surface that animates its own container, so an overlay rendered in place
61
+ * would cover the viewport everywhere it was tested and cover a card where it
62
+ * shipped.
63
+ *
64
+ * ## Props
65
+ *
66
+ * - `onDone` — called once, when the burst is over. Not called if the component
67
+ * is unmounted mid-burst: the caller who unmounted it already knows.
68
+ * - `duration` — milliseconds, default {@link DEFAULT_DURATION_MS}, clamped to
69
+ * {@link MIN_DURATION_MS}–{@link MAX_DURATION_MS}. A celebration is a moment,
70
+ * not a state.
71
+ * - `particleCount` — default {@link DEFAULT_PARTICLE_COUNT}, capped at
72
+ * {@link MAX_PARTICLE_COUNT}.
73
+ * - `className` — lands on the overlay canvas. Its position, inset,
74
+ * pointer-events and stacking are inline and win over a utility, because those
75
+ * four are the contract rather than a default.
76
+ *
77
+ * `duration` and `particleCount` are read ONCE, at mount. Changing them later
78
+ * does nothing, deliberately: a burst that restarted when its parent re-rendered
79
+ * with a new number is the "repeats on re-render" defect wearing a plausible
80
+ * face. `onDone` is the exception and is always the current one.
81
+ */
82
+ /** A celebration is a moment. Long enough to register, short enough to leave. */
83
+ const DEFAULT_DURATION_MS = 1800;
84
+ const MIN_DURATION_MS = 300;
85
+ const MAX_DURATION_MS = 8000;
86
+ /** Conservative on purpose: this runs on the first screen a new person sees. */
87
+ const DEFAULT_PARTICLE_COUNT = 90;
88
+ const MAX_PARTICLE_COUNT = 400;
89
+ /** The brand ramp, in the order the burst cycles it. */
90
+ const RAMP = ["--cg-primary", "--cg-secondary", "--cg-tertiary"];
91
+ /**
92
+ * Above application chrome, below a browser's own UI. Inline rather than a
93
+ * token because it is a stacking decision, not a brand one.
94
+ */
95
+ const OVERLAY_Z_INDEX = 9999;
96
+ /** Per frame at 60fps, which is what `dt` normalises to. */
97
+ const GRAVITY = 0.4;
98
+ const DRAG = 0.99;
99
+ /** The share of the burst that plays at full opacity before it fades out. */
100
+ const FADE_FROM = 0.7;
101
+ /** Retina is worth paying for; a 3x phone is not, for 90 rectangles. */
102
+ const MAX_PIXEL_RATIO = 2;
103
+ const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
104
+ /**
105
+ * A cone of scraps thrown up from a little above the middle of the screen.
106
+ *
107
+ * Minted once per burst and kept, so a React strict-mode remount resumes the
108
+ * same confetti rather than throwing a second handful. See the effect.
109
+ */
110
+ function mint(count, width, height, palette) {
111
+ const originX = width / 2;
112
+ const originY = height * 0.45;
113
+ const particles = [];
114
+ for (let i = 0; i < count; i++) {
115
+ // Straight up, give or take a radian either way. Wide enough to read as a
116
+ // burst, narrow enough that most of it stays on the screen it decorates.
117
+ const angle = -Math.PI / 2 + (Math.random() - 0.5) * 2;
118
+ const speed = 6 + Math.random() * 8;
119
+ particles.push({
120
+ x: originX + (Math.random() - 0.5) * width * 0.1,
121
+ y: originY + (Math.random() - 0.5) * 20,
122
+ vx: Math.cos(angle) * speed,
123
+ vy: Math.sin(angle) * speed,
124
+ rot: Math.random() * Math.PI * 2,
125
+ spin: (Math.random() - 0.5) * 0.3,
126
+ w: 6 + Math.random() * 6,
127
+ h: 9 + Math.random() * 7,
128
+ wobble: Math.random() * Math.PI * 2,
129
+ wobbleRate: 0.08 + Math.random() * 0.1,
130
+ colour: palette[i % palette.length],
131
+ });
132
+ }
133
+ return particles;
134
+ }
135
+ function Celebration({ onDone, duration, particleCount, className }) {
136
+ // The callback is kept fresh; the numbers are not. See the props note above.
137
+ const onDoneRef = React.useRef(onDone);
138
+ React.useEffect(() => {
139
+ onDoneRef.current = onDone;
140
+ });
141
+ // useRef keeps its FIRST argument for the life of the component, which is
142
+ // exactly "read once at mount" without a second hook to say so.
143
+ const settings = React.useRef({ duration, particleCount, className });
144
+ /**
145
+ * The burst's own state, on a ref so it survives what React does to effects.
146
+ *
147
+ * Strict mode runs an effect, cleans it up, and runs it again. The naive guard
148
+ * — a boolean that makes the second run a no-op — is worse than none: the
149
+ * first run's cleanup has already cancelled the frame and removed the canvas,
150
+ * so it produces a component that animates nothing in development and
151
+ * correctly in production.
152
+ *
153
+ * One burst means one CLOCK, not one effect run. `startedAt` and `particles`
154
+ * are minted on the first run only, so a second run re-creates the canvas and
155
+ * resumes the same confetti at the elapsed time the clock says. `finished`
156
+ * short-circuits everything after, so `onDone` cannot fire twice.
157
+ */
158
+ const run = React.useRef({ startedAt: null, particles: null, finished: false });
159
+ React.useEffect(() => {
160
+ const state = run.current;
161
+ if (state.finished)
162
+ return;
163
+ const finish = () => {
164
+ if (state.finished)
165
+ return;
166
+ state.finished = true;
167
+ onDoneRef.current?.();
168
+ };
169
+ const reduced = typeof window.matchMedia === "function" &&
170
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches;
171
+ if (reduced) {
172
+ finish();
173
+ return;
174
+ }
175
+ const durationMs = clamp(settings.current.duration ?? DEFAULT_DURATION_MS, MIN_DURATION_MS, MAX_DURATION_MS);
176
+ const count = Math.round(clamp(settings.current.particleCount ?? DEFAULT_PARTICLE_COUNT, 0, MAX_PARTICLE_COUNT));
177
+ const canvas = document.createElement("canvas");
178
+ canvas.dataset.slot = "celebration";
179
+ canvas.setAttribute("aria-hidden", "true");
180
+ if (settings.current.className)
181
+ canvas.className = settings.current.className;
182
+ // width/height are not redundant beside inset: a <canvas> is a REPLACED
183
+ // element, and an absolutely positioned replaced box with auto width keeps
184
+ // its INTRINSIC size rather than stretching to its offsets.
185
+ canvas.style.cssText =
186
+ "position:fixed;inset:0;width:100%;height:100%;" +
187
+ `pointer-events:none;z-index:${OVERLAY_Z_INDEX}`;
188
+ document.body.appendChild(canvas);
189
+ const ctx = canvas.getContext("2d");
190
+ if (!ctx) {
191
+ canvas.remove();
192
+ finish();
193
+ return;
194
+ }
195
+ const width = window.innerWidth;
196
+ const height = window.innerHeight;
197
+ const ratio = Math.min(window.devicePixelRatio || 1, MAX_PIXEL_RATIO);
198
+ canvas.width = Math.round(width * ratio);
199
+ canvas.height = Math.round(height * ratio);
200
+ ctx.scale(ratio, ratio);
201
+ const rootStyle = getComputedStyle(document.documentElement);
202
+ const palette = RAMP.map((name) => rootStyle.getPropertyValue(name).trim()).filter(Boolean);
203
+ if (palette.length === 0)
204
+ palette.push(getComputedStyle(canvas).color);
205
+ if (state.particles === null)
206
+ state.particles = mint(count, width, height, palette);
207
+ if (state.startedAt === null)
208
+ state.startedAt = performance.now();
209
+ const particles = state.particles;
210
+ const startedAt = state.startedAt;
211
+ let last = performance.now();
212
+ let frame = 0;
213
+ const step = (now) => {
214
+ const progress = (now - startedAt) / durationMs;
215
+ if (progress >= 1) {
216
+ canvas.remove();
217
+ finish();
218
+ return;
219
+ }
220
+ // Normalised to a 60fps frame and capped, so a backgrounded tab resumes
221
+ // rather than teleporting every scrap off the bottom of the screen.
222
+ const dt = Math.min(now - last, 64) / (1000 / 60);
223
+ last = now;
224
+ ctx.clearRect(0, 0, width, height);
225
+ ctx.globalAlpha =
226
+ progress < FADE_FROM ? 1 : 1 - (progress - FADE_FROM) / (1 - FADE_FROM);
227
+ for (const p of particles) {
228
+ p.vy += GRAVITY * dt;
229
+ p.vx *= DRAG ** dt;
230
+ p.vy *= DRAG ** dt;
231
+ p.x += p.vx * dt;
232
+ p.y += p.vy * dt;
233
+ p.rot += p.spin * dt;
234
+ p.wobble += p.wobbleRate * dt;
235
+ ctx.save();
236
+ ctx.translate(p.x, p.y);
237
+ ctx.rotate(p.rot);
238
+ ctx.scale(1, Math.cos(p.wobble));
239
+ ctx.fillStyle = p.colour;
240
+ ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h);
241
+ ctx.restore();
242
+ }
243
+ frame = requestAnimationFrame(step);
244
+ };
245
+ frame = requestAnimationFrame(step);
246
+ return () => {
247
+ cancelAnimationFrame(frame);
248
+ canvas.remove();
249
+ };
250
+ // Mount only. Every prop this effect reads is read through a ref, precisely
251
+ // so that a changed prop cannot re-run it and start a second burst.
252
+ // eslint-disable-next-line react-hooks/exhaustive-deps
253
+ }, []);
254
+ // Nothing in the caller's tree, on the server or on the client. The overlay is
255
+ // the effect's, and it lives on document.body.
256
+ return null;
257
+ }
258
+ export { Celebration };
259
+ //# sourceMappingURL=celebration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"celebration.js","sourceRoot":"","sources":["../../src/blocks/celebration.tsx"],"names":[],"mappings":"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"}
@@ -32,6 +32,79 @@
32
32
  * identity card is `aria-hidden` whenever a title sits beside it, because
33
33
  * announcing a decorative preview after the title reads the card twice.
34
34
  *
35
+ * ## The kind badge says WHAT the thing is
36
+ *
37
+ * `kind` is agent, inspiration or app, and it paints a circular badge on the
38
+ * preview's top-right corner — and beside the title when there is no preview to
39
+ * put it on. Founder request 2026-09-08 (#165): the console once carried it, a
40
+ * build dropped it, and every thumbnail had been showing the same badge
41
+ * regardless of what the entity was.
42
+ *
43
+ * Three deliberate choices in it.
44
+ *
45
+ * The glyphs come from `@/registry/ui/icons` under CloudGrid names, so the marks
46
+ * can be swapped for the founder's Figma set without touching this file or any
47
+ * consumer. `ENTITY_KIND_ICON` and `ENTITY_KIND_LABEL` are the whole mapping and
48
+ * nothing else here branches on a kind, the same arrangement `EntityStatus` uses
49
+ * for its ten statuses.
50
+ *
51
+ * The badge does NOT flip per surface, which is the identity tile's rule applied
52
+ * to a mark. Its ground is `--cg-surface-raised` and its glyph is
53
+ * `--cg-text-on-accent`, both declared at `:root` only, so a light disc with an
54
+ * ink glyph is what renders on white, on the purple panel and on ink alike. The
55
+ * alternative — a badge painted with `--cg-surface-card` and `--cg-text-primary`
56
+ * — inverts to white-on-white the moment the card sits inside
57
+ * `data-cg-surface="ink"`, because half of what it reads flips and the picture
58
+ * under it does not. Its hairline is mixed from `--cg-ink` for the same reason:
59
+ * `--cg-border-hairline` is a white wash on both dark surfaces and would leave
60
+ * the badge with no edge at all.
61
+ *
62
+ * The kind is announced. The glyph is `aria-hidden` and the word rides along in
63
+ * an `sr-only` span, because "agent" is information and a decorative mark is not
64
+ * a way to convey it.
65
+ *
66
+ * ## The hover reveal, and the rule it supersedes
67
+ *
68
+ * `reveal` puts a panel over the preview carrying the title, one line of
69
+ * description, the creator, the status and two real links: Open and Settings.
70
+ * It shows on hover and on `:focus-within`, and it respects
71
+ * `prefers-reduced-motion` by arriving without the fade rather than not at all.
72
+ *
73
+ * **This supersedes the "cards are visually static" rule of the console's
74
+ * 2026-08-16 card spec, for the media variant only.** Founder direction
75
+ * 2026-09-08 (#165). The rest of that rule stands and is unchanged: no lift, no
76
+ * shadow, no scale, no colour change on the card itself, on either variant. What
77
+ * moves is a panel that was always there, not the card.
78
+ *
79
+ * Four things about it that are not obvious:
80
+ *
81
+ * **A revealed card is not a whole-card link.** `href` still names where the
82
+ * entity lives, but with `reveal` set it is spent on the panel's Open action
83
+ * instead of on a wrapper, because an `<a>` inside an `<a>` is invalid HTML and
84
+ * browsers repair it by closing the outer one — which silently unlinks the card
85
+ * and leaves the actions where the parser decides to put them. The panel is the
86
+ * link surface instead: Open stretches across the whole panel with an
87
+ * `::after`, so a click anywhere on the thumbnail opens the entity, and Settings
88
+ * sits above it. Every existing caller is unaffected — `reveal` defaults off and
89
+ * the wrapper behaves exactly as it did.
90
+ *
91
+ * **The panel stays clickable while it is invisible.** `opacity-0` is a paint
92
+ * state, not a pointer state, so a touch user with no hover at all still gets
93
+ * Open by tapping the thumbnail. Gating pointer events on hover would make the
94
+ * card do nothing on a phone.
95
+ *
96
+ * **It is an ink surface, declared.** The panel carries
97
+ * `data-cg-surface="ink"`, so the status chip, the hairlines and the two links
98
+ * inside it resolve to their on-dark values without this file knowing what any
99
+ * of them are. It also declares `text-ink` itself, because text is the one thing
100
+ * that does not flip by inheritance.
101
+ *
102
+ * **The radii cannot disagree.** The preview carries `rounded-t-xl` — the same
103
+ * `--cg-radius-xl` the card rounds to — and the panel is `inset-0` inside it
104
+ * with no radius of its own, so there is no inner corner to mismatch when the
105
+ * panel appears. An e2e assertion compares the two computed radii rather than
106
+ * trusting that.
107
+ *
35
108
  * ## Status is a slot
36
109
  *
37
110
  * `status` takes a node, not a status name. `EntityStatus` is a separate
@@ -44,8 +117,8 @@
44
117
  *
45
118
  * The chrome flips: ground, hairlines, title, body text and the stat rule all
46
119
  * come from tokens, so `data-cg-surface="panel"` on any ancestor moves them
47
- * together. The identity tile deliberately does not flip — see its own note; it
48
- * is a picture, and a picture does not flip.
120
+ * together. The identity tile and the kind badge deliberately do not flip — see
121
+ * their notes; they are pictures, and a picture does not flip.
49
122
  */
50
123
  export interface EntityCardStat {
51
124
  /**
@@ -57,7 +130,45 @@ export interface EntityCardStat {
57
130
  /** Names the stat, e.g. "Views". Rendered for assistive tech. */
58
131
  label: string;
59
132
  }
60
- declare function EntityCard({ title, slug, description, titleLines, descriptionLines, status, stats, seed, previewUrl, href, ariaLabel, meta, footer, className, children, ...props }: Omit<React.ComponentProps<"div">, "ref" | "title"> & {
133
+ /**
134
+ * What an entity IS. Three kinds, and they are the product's, not this file's:
135
+ * an agent, an inspiration someone can pick up, and a runnable app.
136
+ *
137
+ * The array is the source and the type is derived from it, the arrangement
138
+ * `ENTITY_STATUS_ORDER` uses: a kind added here is immediately missing from both
139
+ * tables below and typecheck says so, so a half-added kind cannot ship.
140
+ */
141
+ export declare const ENTITY_KIND_ORDER: readonly ["agent", "inspiration", "app"];
142
+ export type EntityKind = (typeof ENTITY_KIND_ORDER)[number];
143
+ /**
144
+ * THE MARKS. Edit this to change what a kind looks like.
145
+ *
146
+ * Every entry points at a name from `@/registry/ui/icons` rather than at a
147
+ * lucide import, which is what makes the founder's Figma marks a one-file swap
148
+ * over there instead of an edit here and in every consumer.
149
+ */
150
+ export declare const ENTITY_KIND_ICON: Record<EntityKind, React.ComponentType<{
151
+ className?: string;
152
+ }>>;
153
+ /**
154
+ * THE WORDS. What a screen reader hears where a sighted reader sees the mark.
155
+ *
156
+ * Plain nouns, no adjective: this names a thing, it does not sell it.
157
+ */
158
+ export declare const ENTITY_KIND_LABEL: Record<EntityKind, string>;
159
+ /**
160
+ * The kind badge on its own, exported because a listing header, a detail page or
161
+ * a row may want the same mark without a card around it.
162
+ *
163
+ * `size` is the only variant: `md` is the 28px disc that sits on a preview,
164
+ * `sm` the 20px one that sits beside a title. Both render a 16px-class glyph,
165
+ * which is what the marks were picked to survive.
166
+ */
167
+ declare function EntityKindBadge({ kind, size, className, ...props }: Omit<React.ComponentProps<"span">, "children"> & {
168
+ kind: EntityKind;
169
+ size?: "sm" | "md";
170
+ }): import("react").JSX.Element;
171
+ declare function EntityCard({ title, slug, description, titleLines, descriptionLines, status, stats, seed, previewUrl, kind, reveal, settingsHref, creator, href, ariaLabel, meta, footer, className, children, ...props }: Omit<React.ComponentProps<"div">, "ref" | "title"> & {
61
172
  title: string;
62
173
  /** The entity's address, in the mono utility face. */
63
174
  slug?: string;
@@ -74,6 +185,30 @@ declare function EntityCard({ title, slug, description, titleLines, descriptionL
74
185
  seed?: string;
75
186
  /** A real capture, painted over the identity tile. A miss falls back to it. */
76
187
  previewUrl?: string;
188
+ /**
189
+ * What the entity IS. Paints the badge on the preview's top-right corner, or
190
+ * beside the title when there is no preview.
191
+ */
192
+ kind?: EntityKind;
193
+ /**
194
+ * Turns the hover reveal on. Needs `seed`, since the panel covers the preview.
195
+ *
196
+ * Off by default, and it changes what `href` does: a revealed card is NOT
197
+ * wrapped in a whole-card link, because the panel's own actions are anchors
198
+ * and an anchor inside an anchor is invalid HTML. See the note above.
199
+ */
200
+ reveal?: boolean;
201
+ /** The Settings action's target. Only read when `reveal` is on. */
202
+ settingsHref?: string;
203
+ /**
204
+ * Who made it, shown on the reveal panel. `avatar` is a node so a consumer can
205
+ * pass its own image, initials tile or `EntityIdentity`; this card does not
206
+ * fetch one and does not know where an avatar lives.
207
+ */
208
+ creator?: {
209
+ name: string;
210
+ avatar?: React.ReactNode;
211
+ };
77
212
  /** When set, the whole card is the target. A plain anchor: routing is yours. */
78
213
  href?: string;
79
214
  /** Accessible name for the whole-card link. Defaults to `title`. */
@@ -85,5 +220,5 @@ declare function EntityCard({ title, slug, description, titleLines, descriptionL
85
220
  /** Replaces the whole body. The preview and the card chrome still render. */
86
221
  children?: React.ReactNode;
87
222
  }): import("react").JSX.Element;
88
- export { EntityCard };
223
+ export { EntityCard, EntityKindBadge };
89
224
  //# sourceMappingURL=entity-card.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entity-card.d.ts","sourceRoot":"","sources":["../../src/blocks/entity-card.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;CACd;AAED,iBAAS,UAAU,CAAC,EAClB,KAAK,EACL,IAAI,EACJ,WAAW,EACX,UAAc,EACd,gBAAoB,EACpB,MAAM,EACN,KAAK,EACL,IAAI,EACJ,UAAU,EACV,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,MAAM,EACN,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG;IACtD,KAAK,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IAClB,gBAAgB,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IACxB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,KAAK,CAAC,EAAE,cAAc,EAAE,CAAA;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uFAAuF;IACvF,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,kDAAkD;IAClD,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B,+BAwIA;AAED,OAAO,EAAE,UAAU,EAAE,CAAA"}
1
+ {"version":3,"file":"entity-card.d.ts","sourceRoot":"","sources":["../../src/blocks/entity-card.tsx"],"names":[],"mappings":"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"}
@@ -1,10 +1,68 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Card } from "../ui/card.js";
3
3
  import { EntityIdentity } from "./entity-identity.js";
4
+ import { KindAgentIcon, KindAppIcon, KindInspirationIcon, OpenLiveIcon, SettingsIcon, } from "./icons.js";
4
5
  import { cn } from "../lib/cloudgrid-cn.js";
5
- function EntityCard({ title, slug, description, titleLines = 2, descriptionLines = 1, status, stats, seed, previewUrl, href, ariaLabel, meta, footer, className, children, ...props }) {
6
+ /**
7
+ * What an entity IS. Three kinds, and they are the product's, not this file's:
8
+ * an agent, an inspiration someone can pick up, and a runnable app.
9
+ *
10
+ * The array is the source and the type is derived from it, the arrangement
11
+ * `ENTITY_STATUS_ORDER` uses: a kind added here is immediately missing from both
12
+ * tables below and typecheck says so, so a half-added kind cannot ship.
13
+ */
14
+ export const ENTITY_KIND_ORDER = ["agent", "inspiration", "app"];
15
+ /**
16
+ * THE MARKS. Edit this to change what a kind looks like.
17
+ *
18
+ * Every entry points at a name from `@/registry/ui/icons` rather than at a
19
+ * lucide import, which is what makes the founder's Figma marks a one-file swap
20
+ * over there instead of an edit here and in every consumer.
21
+ */
22
+ export const ENTITY_KIND_ICON = {
23
+ agent: KindAgentIcon,
24
+ inspiration: KindInspirationIcon,
25
+ app: KindAppIcon,
26
+ };
27
+ /**
28
+ * THE WORDS. What a screen reader hears where a sighted reader sees the mark.
29
+ *
30
+ * Plain nouns, no adjective: this names a thing, it does not sell it.
31
+ */
32
+ export const ENTITY_KIND_LABEL = {
33
+ agent: "Agent",
34
+ inspiration: "Inspiration",
35
+ app: "App",
36
+ };
37
+ /**
38
+ * The kind badge on its own, exported because a listing header, a detail page or
39
+ * a row may want the same mark without a card around it.
40
+ *
41
+ * `size` is the only variant: `md` is the 28px disc that sits on a preview,
42
+ * `sm` the 20px one that sits beside a title. Both render a 16px-class glyph,
43
+ * which is what the marks were picked to survive.
44
+ */
45
+ function EntityKindBadge({ kind, size = "md", className, ...props }) {
46
+ const Icon = ENTITY_KIND_ICON[kind];
47
+ return (_jsxs("span", { "data-slot": "entity-card-kind", "data-kind": kind, className: cn("inline-flex shrink-0 items-center justify-center rounded-pill bg-raised text-on-accent", size === "md" ? "size-7" : "size-5", className), style: {
48
+ // Mixed from --cg-ink rather than taken from --cg-border-hairline, and
49
+ // for the same reason the ground and the glyph are pinned: the hairline
50
+ // is a white wash on both dark surfaces, so a badge inside
51
+ // data-cg-surface="ink" would lose its edge while the disc under it
52
+ // stayed light. The mesh in EntityIdentity mixes its own line the same
53
+ // way, at 8%.
54
+ border: "1px solid color-mix(in srgb, var(--cg-ink) 12%, transparent)",
55
+ }, ...props, children: [_jsx(Icon, { className: size === "md" ? "size-4" : "size-3.5", "aria-hidden": "true" }), _jsx("span", { className: "sr-only", children: ENTITY_KIND_LABEL[kind] })] }));
56
+ }
57
+ function EntityCard({ title, slug, description, titleLines = 2, descriptionLines = 1, status, stats, seed, previewUrl, kind, reveal = false, settingsHref, creator, href, ariaLabel, meta, footer, className, children, ...props }) {
58
+ // The reveal owns the preview and spends the `href`, so it only exists when
59
+ // both are there. Asked for without a `seed` it would be a panel over nothing;
60
+ // asked for without an `href` its Open action would be an anchor with no
61
+ // target, which renders as a link, is not focusable, and does nothing when
62
+ // clicked. Silently off in both cases rather than half-built.
63
+ const revealed = reveal && Boolean(seed) && Boolean(href);
6
64
  const body = children ??
7
- (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("span", { dir: "auto", "data-slot": "entity-card-title", title: title, className: cn("block text-body-sm font-medium break-words text-ink", titleLines === 1 ? "truncate" : "line-clamp-2"), children: title }), slug ? (_jsx("span", { dir: "auto", "data-slot": "entity-card-slug", title: slug, className: "mt-0.5 block truncate font-mono text-caption text-faint", children: slug })) : null] }), status ? _jsx("div", { className: "shrink-0", children: status }) : null] }), description ? (_jsx("p", { dir: "auto", "data-slot": "entity-card-description", title: description, className: cn("m-0 text-body-sm text-body", descriptionLines === 1 ? "truncate" : "line-clamp-2"), children: description })) : null, meta, _jsx("div", { "aria-hidden": "true", className: "grow" }), stats && stats.length > 0 ? (_jsx("div", { "data-slot": "entity-card-stats", className: "flex flex-wrap items-center gap-4 border-t border-hairline pt-3 text-caption text-body", children: stats.map((stat) => (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [stat.icon ? (_jsx("span", { "aria-hidden": "true", className: "inline-flex [&>svg]:size-4", children: stat.icon })) : null, stat.value, _jsx("span", { className: "sr-only", children: stat.label })] }, stat.label))) })) : null, footer] }));
65
+ (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("span", { dir: "auto", "data-slot": "entity-card-title", title: title, className: cn("block text-body-sm font-medium break-words text-ink", titleLines === 1 ? "truncate" : "line-clamp-2"), children: [kind && !seed ? (_jsx(EntityKindBadge, { kind: kind, size: "sm", className: "mr-1.5 align-[-0.2em]" })) : null, title] }), slug ? (_jsx("span", { dir: "auto", "data-slot": "entity-card-slug", title: slug, className: "mt-0.5 block truncate font-mono text-caption text-faint", children: slug })) : null] }), status ? _jsx("div", { className: "shrink-0", children: status }) : null] }), description ? (_jsx("p", { dir: "auto", "data-slot": "entity-card-description", title: description, className: cn("m-0 text-body-sm text-body", descriptionLines === 1 ? "truncate" : "line-clamp-2"), children: description })) : null, meta, _jsx("div", { "aria-hidden": "true", className: "grow" }), stats && stats.length > 0 ? (_jsx("div", { "data-slot": "entity-card-stats", className: "flex flex-wrap items-center gap-4 border-t border-hairline pt-3 text-caption text-body", children: stats.map((stat) => (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [stat.icon ? (_jsx("span", { "aria-hidden": "true", className: "inline-flex [&>svg]:size-4", children: stat.icon })) : null, stat.value, _jsx("span", { className: "sr-only", children: stat.label })] }, stat.label))) })) : null, footer] }));
8
66
  const card = (_jsxs(Card, { "data-slot": "entity-card",
9
67
  // gap-0 py-0: `Card` pads itself for a header/content/footer stack, and
10
68
  // this card's preview is full bleed to the top edge. The padding moves
@@ -12,18 +70,53 @@ function EntityCard({ title, slug, description, titleLines = 2, descriptionLines
12
70
  // No hover lift, shadow, scale or colour change, even when the whole card
13
71
  // is a link. That is the console's founder card spec (2026-08-16) carried
14
72
  // across rather than re-decided here, and it matches this system's own
15
- // canon: containment is a hairline, not elevation.
73
+ // canon: containment is a hairline, not elevation. The reveal panel is
74
+ // this rule's one exception, and only on the media variant: see the note
75
+ // at the top of this file.
16
76
  // `h-full` only inside the link wrapper, where the card has to fill the
17
77
  // anchor. As a direct grid item it must NOT be h-full: a grid area has a
18
78
  // definite height, so height:100% pins the card to the tallest card in the
19
79
  // row even when the grid was told `items-start`.
20
- className: cn("gap-0 py-0", href && "h-full", className), ...props, children: [seed ? (_jsxs("div", { "data-slot": "entity-card-preview", className: "relative shrink-0 border-b border-hairline", children: [_jsx(EntityIdentity, { seed: seed, name: title }), previewUrl ? (_jsx("div", { "data-slot": "entity-card-capture", "aria-hidden": "true", className: "absolute inset-0 bg-cover bg-center",
80
+ className: cn("gap-0 py-0", href && !revealed && "h-full", className), ...props, children: [seed ? (_jsxs("div", { "data-slot": "entity-card-preview",
81
+ // rounded-t-xl is the same --cg-radius-xl the card rounds to, and
82
+ // overflow-hidden is what keeps the reveal panel inside it. The card
83
+ // already clips; stating it here means the panel can never paint a
84
+ // corner the card does not have.
85
+ className: "relative shrink-0 overflow-hidden rounded-t-xl border-b border-hairline", children: [_jsx(EntityIdentity, { seed: seed, name: title }), previewUrl ? (_jsx("div", { "data-slot": "entity-card-capture", "aria-hidden": "true", className: "absolute inset-0 bg-cover bg-center",
21
86
  // A background image, not an <img>: a URL that 404s paints
22
87
  // nothing and the identity tile below shows through, with no
23
88
  // state and no client boundary. encodeURI escapes the quote that
24
89
  // would otherwise break out of url("...").
25
- style: { backgroundImage: `url("${encodeURI(previewUrl)}")` } })) : null] })) : null, _jsx("div", { className: "flex min-h-0 flex-1 flex-col gap-3 p-(--card-spacing)", children: body })] }));
26
- if (!href)
90
+ style: { backgroundImage: `url("${encodeURI(previewUrl)}")` } })) : null, revealed ? (_jsxs("div", { "data-slot": "entity-card-reveal", "data-cg-surface": "ink", className: cn("absolute inset-0 flex flex-col justify-end gap-1.5 bg-scrim-strong p-3 text-ink",
91
+ // opacity-0, not hidden: the panel is in the tab order at all
92
+ // times, so tabbing into Open is what reveals it, and a touch
93
+ // user with no hover still has a live tap target.
94
+ "opacity-0 transition-opacity duration-[var(--cg-duration)] ease-out", "group-hover/card:opacity-100 group-focus-within/card:opacity-100",
95
+ // Reduced motion removes the FADE, not the panel. Someone who
96
+ // has asked for less movement still needs the actions.
97
+ "motion-reduce:transition-none"), children: [_jsx("span", { "data-slot": "entity-card-reveal-title", dir: "auto", className: "truncate text-body-sm font-medium", children: title }), description ? (_jsx("p", { "data-slot": "entity-card-reveal-description", dir: "auto",
98
+ // text-body, never text-faint. The secondary measures 6.18:1
99
+ // on this scrim and the guardrail holds it there; the muted
100
+ // rung measures 4.06:1, under AA, and is deliberately outside
101
+ // that assertion because it is documented as non-essential
102
+ // text. So there is no muted tier available on this panel.
103
+ className: "m-0 truncate text-caption text-body", children: description })) : null, creator || status ? (_jsxs("div", { className: "flex min-w-0 items-center justify-between gap-2", children: [creator ? (_jsxs("span", { "data-slot": "entity-card-reveal-creator", className: "flex min-w-0 items-center gap-1.5 text-caption text-body", children: [creator.avatar ? (_jsx("span", { "aria-hidden": "true", className: "inline-flex size-4 shrink-0 overflow-hidden rounded-pill [&>*]:size-full", children: creator.avatar })) : null, _jsx("span", { className: "truncate", children: creator.name })] })) : (_jsx("span", {})), status ? _jsx("span", { className: "shrink-0", children: status }) : null] })) : null, _jsxs("div", { className: "flex items-center gap-2 pt-0.5", children: [_jsxs("a", { href: href, "data-slot": "entity-card-open",
104
+ // after:inset-0 makes this link the whole panel, so a click
105
+ // anywhere on the thumbnail opens the entity. The anchor
106
+ // itself must NOT be positioned or the stretched layer sizes
107
+ // to the anchor instead of to the panel — the panel is already
108
+ // absolute, so it is the containing block. Settings comes
109
+ // later in the DOM and IS positioned, so it stacks above the
110
+ // stretched layer rather than under it.
111
+ className: "inline-flex items-center gap-1 rounded-pill border border-hairline px-2 py-1 text-caption no-underline after:absolute after:inset-0 after:content-[''] focus-visible:ring-3 focus-visible:ring-ring focus-visible:outline-none", children: [_jsx(OpenLiveIcon, { className: "size-3.5", "aria-hidden": "true" }), "Open"] }), settingsHref ? (_jsxs("a", { href: settingsHref, "data-slot": "entity-card-settings",
112
+ // relative, and that is load-bearing: it lifts this link
113
+ // above Open's stretched ::after, which otherwise covers the
114
+ // whole panel and would swallow every click meant for here.
115
+ className: "relative inline-flex items-center gap-1 rounded-pill border border-hairline px-2 py-1 text-caption no-underline focus-visible:ring-3 focus-visible:ring-ring focus-visible:outline-none", children: [_jsx(SettingsIcon, { className: "size-3.5", "aria-hidden": "true" }), "Settings"] })) : null] })] })) : null, kind ? (_jsx(EntityKindBadge, { kind: kind, className: "pointer-events-none absolute top-2 right-2" })) : null] })) : null, _jsx("div", { className: "flex min-h-0 flex-1 flex-col gap-3 p-(--card-spacing)", children: body })] }));
116
+ // A revealed card is never wrapped: the panel's Open action is already an
117
+ // anchor, and an anchor inside an anchor is invalid HTML that browsers repair
118
+ // by closing the outer one.
119
+ if (!href || revealed)
27
120
  return card;
28
121
  return (_jsx("a", { href: href, "aria-label": ariaLabel ?? title,
29
122
  // The focus ring is the same token every control uses, so it flips with
@@ -31,5 +124,5 @@ function EntityCard({ title, slug, description, titleLines = 2, descriptionLines
31
124
  // by keyboard, and that failure never shows up in a screenshot.
32
125
  className: "group block rounded-xl no-underline focus-visible:ring-3 focus-visible:ring-ring focus-visible:outline-none", children: card }));
33
126
  }
34
- export { EntityCard };
127
+ export { EntityCard, EntityKindBadge };
35
128
  //# sourceMappingURL=entity-card.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"entity-card.js","sourceRoot":"","sources":["../../src/blocks/entity-card.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"entity-card.js","sourceRoot":"","sources":["../../src/blocks/entity-card.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The curated icon surface: every glyph a CloudGrid surface is allowed to reach
3
+ * for, under a CloudGrid name.
4
+ *
5
+ * ## Why the library owns the names
6
+ *
7
+ * Before this, a consumer that wanted the "open it live" arrow imported
8
+ * `ArrowUpRight` from lucide directly. That is a second design system entering
9
+ * the app through the side door: nothing stops two surfaces picking two
10
+ * different arrows for the same idea, nothing records which glyph means what,
11
+ * and swapping a family later means editing every call site in every repo. This
12
+ * repo's own gallery was doing it, for eight glyphs.
13
+ *
14
+ * So the rule is the one the colour layer already has: a consumer names the
15
+ * ROLE, the library decides what it looks like.
16
+ *
17
+ * ## THE SWAP POINT
18
+ *
19
+ * These are lucide picks, and the three `Kind*` marks are placeholders in the
20
+ * strongest sense: the founder has Figma marks for agent, inspiration and app
21
+ * and will supply them (#165). When they arrive, replace the three re-exports
22
+ * below with real components — hand-drawn marks in the shape
23
+ * `phosphor-marks.tsx` uses, or a generated set beside it — and change NOTHING
24
+ * else. The names are the contract; every consumer keeps compiling and every
25
+ * surface repaints at once.
26
+ *
27
+ * That is the whole reason this file re-exports under new names rather than
28
+ * telling consumers to import lucide with an alias of their own. An alias at the
29
+ * call site is a rename that then has to happen in every repo simultaneously.
30
+ *
31
+ * ## Why re-exports rather than wrappers
32
+ *
33
+ * A wrapper component would let this file pin a size and a stroke width, and it
34
+ * would also break the property that makes an icon usable: `<KindAgentIcon />`
35
+ * has to take `className`, `aria-hidden`, `strokeWidth` and the rest exactly as
36
+ * lucide's own does, or every consumer wraps it a second time. Sizing stays the
37
+ * caller's, through `size-4` and friends, which is how every existing component
38
+ * in this library already sizes a glyph.
39
+ *
40
+ * lucide-react is already a dependency of this package and of eight registry
41
+ * items, so this costs nothing: each icon is its own module and a consumer's
42
+ * bundler drops the ones nobody imports.
43
+ *
44
+ * ## The picks, and why each survives 16px inside a circle
45
+ *
46
+ * The kind marks render at 16px inside a 28px badge on an entity card, so a
47
+ * glyph with fine interior detail turns to mush there. Each was chosen against
48
+ * that constraint rather than in a 24px rail.
49
+ *
50
+ * KindAgentIcon lucide `Bot`. A head, two eyes, an antenna: three heavy
51
+ * shapes and no interior hairlines. `BrainCircuit` loses
52
+ * its traces entirely at this size.
53
+ * KindInspirationIcon lucide `Sparkles`. Three four-pointed stars, and the
54
+ * glyph the whole web now reads as "generated, not
55
+ * built" — which is what an inspiration is: something
56
+ * picked up rather than plugged.
57
+ * KindAppIcon lucide `AppWindow`. A window with a title bar: a thing
58
+ * that runs and has a screen, which separates it from the
59
+ * agent (no screen) and the inspiration (not running).
60
+ * `LayoutGrid` reads as a dashboard, not as one app.
61
+ * OpenLiveIcon lucide `ArrowUpRight`. Named by the issue. One stroke,
62
+ * and it means "leaves this surface" everywhere else.
63
+ * SettingsIcon lucide `Settings`, the gear. `Settings2` is a slider
64
+ * stack, which reads as "filters" in a listing that also
65
+ * has filters.
66
+ * ViewsIcon lucide `Eye`, for a count in a card's stat row.
67
+ * CommentsIcon lucide `MessageSquare`, for the count beside it.
68
+ *
69
+ * No comments inside the export block below, deliberately. The barrel's
70
+ * collision test reads exported names with a regex that splits the braces on
71
+ * commas, so a sentence in there is parsed as an export name.
72
+ */
73
+ export { Bot as KindAgentIcon, Sparkles as KindInspirationIcon, AppWindow as KindAppIcon, ArrowUpRight as OpenLiveIcon, Settings as SettingsIcon, Eye as ViewsIcon, MessageSquare as CommentsIcon, } from "lucide-react";
74
+ /**
75
+ * The props every icon above takes. Re-exported so a consumer can type an icon
76
+ * slot without importing lucide either.
77
+ */
78
+ export type { LucideProps as CloudGridIconProps } from "lucide-react";
79
+ //# sourceMappingURL=icons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/blocks/icons.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAEH,OAAO,EACL,GAAG,IAAI,aAAa,EACpB,QAAQ,IAAI,mBAAmB,EAC/B,SAAS,IAAI,WAAW,EACxB,YAAY,IAAI,YAAY,EAC5B,QAAQ,IAAI,YAAY,EACxB,GAAG,IAAI,SAAS,EAChB,aAAa,IAAI,YAAY,GAC9B,MAAM,cAAc,CAAA;AAErB;;;GAGG;AACH,YAAY,EAAE,WAAW,IAAI,kBAAkB,EAAE,MAAM,cAAc,CAAA"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The curated icon surface: every glyph a CloudGrid surface is allowed to reach
3
+ * for, under a CloudGrid name.
4
+ *
5
+ * ## Why the library owns the names
6
+ *
7
+ * Before this, a consumer that wanted the "open it live" arrow imported
8
+ * `ArrowUpRight` from lucide directly. That is a second design system entering
9
+ * the app through the side door: nothing stops two surfaces picking two
10
+ * different arrows for the same idea, nothing records which glyph means what,
11
+ * and swapping a family later means editing every call site in every repo. This
12
+ * repo's own gallery was doing it, for eight glyphs.
13
+ *
14
+ * So the rule is the one the colour layer already has: a consumer names the
15
+ * ROLE, the library decides what it looks like.
16
+ *
17
+ * ## THE SWAP POINT
18
+ *
19
+ * These are lucide picks, and the three `Kind*` marks are placeholders in the
20
+ * strongest sense: the founder has Figma marks for agent, inspiration and app
21
+ * and will supply them (#165). When they arrive, replace the three re-exports
22
+ * below with real components — hand-drawn marks in the shape
23
+ * `phosphor-marks.tsx` uses, or a generated set beside it — and change NOTHING
24
+ * else. The names are the contract; every consumer keeps compiling and every
25
+ * surface repaints at once.
26
+ *
27
+ * That is the whole reason this file re-exports under new names rather than
28
+ * telling consumers to import lucide with an alias of their own. An alias at the
29
+ * call site is a rename that then has to happen in every repo simultaneously.
30
+ *
31
+ * ## Why re-exports rather than wrappers
32
+ *
33
+ * A wrapper component would let this file pin a size and a stroke width, and it
34
+ * would also break the property that makes an icon usable: `<KindAgentIcon />`
35
+ * has to take `className`, `aria-hidden`, `strokeWidth` and the rest exactly as
36
+ * lucide's own does, or every consumer wraps it a second time. Sizing stays the
37
+ * caller's, through `size-4` and friends, which is how every existing component
38
+ * in this library already sizes a glyph.
39
+ *
40
+ * lucide-react is already a dependency of this package and of eight registry
41
+ * items, so this costs nothing: each icon is its own module and a consumer's
42
+ * bundler drops the ones nobody imports.
43
+ *
44
+ * ## The picks, and why each survives 16px inside a circle
45
+ *
46
+ * The kind marks render at 16px inside a 28px badge on an entity card, so a
47
+ * glyph with fine interior detail turns to mush there. Each was chosen against
48
+ * that constraint rather than in a 24px rail.
49
+ *
50
+ * KindAgentIcon lucide `Bot`. A head, two eyes, an antenna: three heavy
51
+ * shapes and no interior hairlines. `BrainCircuit` loses
52
+ * its traces entirely at this size.
53
+ * KindInspirationIcon lucide `Sparkles`. Three four-pointed stars, and the
54
+ * glyph the whole web now reads as "generated, not
55
+ * built" — which is what an inspiration is: something
56
+ * picked up rather than plugged.
57
+ * KindAppIcon lucide `AppWindow`. A window with a title bar: a thing
58
+ * that runs and has a screen, which separates it from the
59
+ * agent (no screen) and the inspiration (not running).
60
+ * `LayoutGrid` reads as a dashboard, not as one app.
61
+ * OpenLiveIcon lucide `ArrowUpRight`. Named by the issue. One stroke,
62
+ * and it means "leaves this surface" everywhere else.
63
+ * SettingsIcon lucide `Settings`, the gear. `Settings2` is a slider
64
+ * stack, which reads as "filters" in a listing that also
65
+ * has filters.
66
+ * ViewsIcon lucide `Eye`, for a count in a card's stat row.
67
+ * CommentsIcon lucide `MessageSquare`, for the count beside it.
68
+ *
69
+ * No comments inside the export block below, deliberately. The barrel's
70
+ * collision test reads exported names with a regex that splits the braces on
71
+ * commas, so a sentence in there is parsed as an export name.
72
+ */
73
+ export { Bot as KindAgentIcon, Sparkles as KindInspirationIcon, AppWindow as KindAppIcon, ArrowUpRight as OpenLiveIcon, Settings as SettingsIcon, Eye as ViewsIcon, MessageSquare as CommentsIcon, } from "lucide-react";
74
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.js","sourceRoot":"","sources":["../../src/blocks/icons.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAEH,OAAO,EACL,GAAG,IAAI,aAAa,EACpB,QAAQ,IAAI,mBAAmB,EAC/B,SAAS,IAAI,WAAW,EACxB,YAAY,IAAI,YAAY,EAC5B,QAAQ,IAAI,YAAY,EACxB,GAAG,IAAI,SAAS,EAChB,aAAa,IAAI,YAAY,GAC9B,MAAM,cAAc,CAAA"}
package/dist/index.d.ts CHANGED
@@ -23,6 +23,7 @@ export * from "./ui/textarea.js";
23
23
  export * from "./ui/tooltip.js";
24
24
  export * from "./blocks/agent-prompt.js";
25
25
  export * from "./blocks/card-grid.js";
26
+ export * from "./blocks/celebration.js";
26
27
  export * from "./blocks/compare-table.js";
27
28
  export * from "./blocks/creation-tile.js";
28
29
  export * from "./blocks/cta-banner.js";
@@ -32,6 +33,7 @@ export * from "./blocks/entity-identity.js";
32
33
  export * from "./blocks/entity-status.js";
33
34
  export * from "./blocks/feature-cells.js";
34
35
  export * from "./blocks/hub-index.js";
36
+ export * from "./blocks/icons.js";
35
37
  export * from "./blocks/logo.js";
36
38
  export * from "./blocks/onboarding-question.js";
37
39
  export * from "./blocks/option-card.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAG/B,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAGtC,cAAc,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAG/B,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAGtC,cAAc,uBAAuB,CAAA"}
package/dist/index.js CHANGED
@@ -40,6 +40,7 @@ export * from "./ui/tooltip.js";
40
40
  // Composed blocks.
41
41
  export * from "./blocks/agent-prompt.js";
42
42
  export * from "./blocks/card-grid.js";
43
+ export * from "./blocks/celebration.js";
43
44
  export * from "./blocks/compare-table.js";
44
45
  export * from "./blocks/creation-tile.js";
45
46
  export * from "./blocks/cta-banner.js";
@@ -49,6 +50,7 @@ export * from "./blocks/entity-identity.js";
49
50
  export * from "./blocks/entity-status.js";
50
51
  export * from "./blocks/feature-cells.js";
51
52
  export * from "./blocks/hub-index.js";
53
+ export * from "./blocks/icons.js";
52
54
  export * from "./blocks/logo.js";
53
55
  export * from "./blocks/onboarding-question.js";
54
56
  export * from "./blocks/option-card.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,yEAAyE;AACzE,gFAAgF;AAChF,4EAA4E;AAC5E,EAAE;AACF,kEAAkE;AAClE,8EAA8E;AAC9E,uEAAuE;AAEvE,cAAc;AACd,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAE/B,mBAAmB;AACnB,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,0EAA0E;AAC1E,cAAc,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,yEAAyE;AACzE,gFAAgF;AAChF,4EAA4E;AAC5E,EAAE;AACF,kEAAkE;AAClE,8EAA8E;AAC9E,uEAAuE;AAEvE,cAAc;AACd,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAE/B,mBAAmB;AACnB,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,0EAA0E;AAC1E,cAAc,uBAAuB,CAAA"}
package/dist/theme.css CHANGED
@@ -167,6 +167,19 @@
167
167
  --color-line-strong: var(--cg-border-strong);
168
168
  --color-line-accent: var(--cg-border-accent);
169
169
 
170
+ /* ---- washes ----
171
+ The two scrims, so a component that lays one over a picture writes
172
+ bg-scrim-strong rather than an arbitrary value or an inline style. Both are
173
+ declared at :root only and neither flips: a scrim composites over whatever
174
+ is beneath it, which is why a surface-scoped override would darken a wash
175
+ that is already dark.
176
+
177
+ --cg-hover deliberately gets no utility. `bg-hover` would sit one character
178
+ away from Tailwind's own `hover:` variant, and a name that reads as a
179
+ variant is a name someone writes by accident. */
180
+ --color-scrim: var(--cg-scrim);
181
+ --color-scrim-strong: var(--cg-scrim-strong);
182
+
170
183
  /* ---- type families ----
171
184
  --font-sans points at the body face so the stock font-sans utility is
172
185
  correct rather than something to remember not to use. */
@@ -1328,6 +1328,16 @@
1328
1328
  }
1329
1329
  }
1330
1330
  },
1331
+ "scrim-strong": {
1332
+ "$value": "rgba(34, 34, 34, 0.82)",
1333
+ "$type": "color",
1334
+ "$description": "The wash under TEXT laid over a picture, e.g. the entity card's hover reveal.\n\nA second scrim rather than a heavier --cg-scrim, because the two carry different\nobligations. --cg-scrim sits behind a modal and only has to separate two layers,\nso 0.4 is a mood. This one has words on it, and the picture underneath is an\narbitrary screenshot: the worst case is a white capture, where the composite is\nthe lightest this token ever gets and every foreground on it is at its least\nreadable.\n\nMEASURED against that worst case, over --cg-neutral-0: 8.89:1 for the ink\nsurface's own #ffffff and 6.18:1 for its rgba(255,255,255,0.78) secondary, both\nclear of the 4.5:1 AA floor for normal text. At 0.6 the white would measure\n4.26:1 and fail, which is why this is not a rounder number. Asserted in\ntest/contrast-guardrail.test.mjs, so lightening it goes red rather than shipping\na caption nobody can read.\n\nDoes NOT flip per surface, for the same reason --cg-scrim does not: it\ncomposites over whatever is beneath it, and what is beneath it is a picture that\ndoes not flip either.",
1335
+ "$extensions": {
1336
+ "io.cloudgrid": {
1337
+ "css": "--cg-scrim-strong"
1338
+ }
1339
+ }
1340
+ },
1331
1341
  "hover": {
1332
1342
  "$value": "rgba(34, 34, 34, 0.045)",
1333
1343
  "$type": "color",
package/dist/tokens.css CHANGED
@@ -637,6 +637,27 @@
637
637
  Does NOT flip per surface, deliberately: it composites over whatever is beneath
638
638
  it, so a panel-scoped override would darken a scrim that is already dark. */
639
639
  --cg-scrim: rgba(34, 34, 34, 0.4);
640
+ /*
641
+ The wash under TEXT laid over a picture, e.g. the entity card's hover reveal.
642
+
643
+ A second scrim rather than a heavier --cg-scrim, because the two carry different
644
+ obligations. --cg-scrim sits behind a modal and only has to separate two layers,
645
+ so 0.4 is a mood. This one has words on it, and the picture underneath is an
646
+ arbitrary screenshot: the worst case is a white capture, where the composite is
647
+ the lightest this token ever gets and every foreground on it is at its least
648
+ readable.
649
+
650
+ MEASURED against that worst case, over --cg-neutral-0: 8.89:1 for the ink
651
+ surface's own #ffffff and 6.18:1 for its rgba(255,255,255,0.78) secondary, both
652
+ clear of the 4.5:1 AA floor for normal text. At 0.6 the white would measure
653
+ 4.26:1 and fail, which is why this is not a rounder number. Asserted in
654
+ test/contrast-guardrail.test.mjs, so lightening it goes red rather than shipping
655
+ a caption nobody can read.
656
+
657
+ Does NOT flip per surface, for the same reason --cg-scrim does not: it
658
+ composites over whatever is beneath it, and what is beneath it is a picture that
659
+ does not flip either. */
660
+ --cg-scrim-strong: rgba(34, 34, 34, 0.82);
640
661
  /*
641
662
  The wash under a hovered row or control. The system had no hover token; components were each inventing one. */
642
663
  --cg-hover: rgba(34, 34, 34, 0.045);
package/dist/tokens.json CHANGED
@@ -143,6 +143,7 @@
143
143
  "--cg-volt-ink": "#243d00",
144
144
  "--cg-live-halo": "color-mix(in srgb, var(--cg-volt) 32%, transparent)",
145
145
  "--cg-scrim": "rgba(34, 34, 34, 0.4)",
146
+ "--cg-scrim-strong": "rgba(34, 34, 34, 0.82)",
146
147
  "--cg-hover": "rgba(34, 34, 34, 0.045)"
147
148
  },
148
149
  "panel": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/ui",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "CloudGrid design system: React components, brand design tokens, Tailwind theme, self-hosted fonts.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",