@cloudgrid-io/ui 0.44.0 → 0.46.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.
- package/dist/badge/css-watermark.d.ts +39 -0
- package/dist/badge/css-watermark.d.ts.map +1 -0
- package/dist/badge/css-watermark.js +153 -0
- package/dist/badge/css-watermark.js.map +1 -0
- package/dist/badge/live.d.ts +9 -1
- package/dist/badge/live.d.ts.map +1 -1
- package/dist/badge/live.js +4 -3
- package/dist/badge/live.js.map +1 -1
- package/dist/badge/marks.d.ts +8 -0
- package/dist/badge/marks.d.ts.map +1 -1
- package/dist/badge/marks.js +20 -0
- package/dist/badge/marks.js.map +1 -1
- package/dist/badge/onboard-marks.d.ts +8 -0
- package/dist/badge/onboard-marks.d.ts.map +1 -0
- package/dist/badge/onboard-marks.js +15 -0
- package/dist/badge/onboard-marks.js.map +1 -0
- package/dist/badge/onboard-widget.d.ts +86 -0
- package/dist/badge/onboard-widget.d.ts.map +1 -0
- package/dist/badge/onboard-widget.js +105 -0
- package/dist/badge/onboard-widget.js.map +1 -0
- package/dist/badge/palette.d.ts +7 -0
- package/dist/badge/palette.d.ts.map +1 -1
- package/dist/badge/palette.js +14 -0
- package/dist/badge/palette.js.map +1 -1
- package/dist/badge/watermark.d.ts +152 -0
- package/dist/badge/watermark.d.ts.map +1 -0
- package/dist/badge/watermark.js +215 -0
- package/dist/badge/watermark.js.map +1 -0
- package/dist/blocks/icons.d.ts +1 -1
- package/dist/blocks/icons.d.ts.map +1 -1
- package/dist/blocks/icons.js +1 -1
- package/dist/blocks/icons.js.map +1 -1
- package/dist/blocks/onboard-widget-content.d.ts +227 -0
- package/dist/blocks/onboard-widget-content.d.ts.map +1 -0
- package/dist/blocks/onboard-widget-content.js +237 -0
- package/dist/blocks/onboard-widget-content.js.map +1 -0
- package/dist/blocks/onboard-widget.d.ts +92 -0
- package/dist/blocks/onboard-widget.d.ts.map +1 -0
- package/dist/blocks/onboard-widget.js +92 -0
- package/dist/blocks/onboard-widget.js.map +1 -0
- package/dist/blocks/phosphor-marks.d.ts +2 -0
- package/dist/blocks/phosphor-marks.d.ts.map +1 -1
- package/dist/blocks/phosphor-marks.js +3 -0
- package/dist/blocks/phosphor-marks.js.map +1 -1
- package/dist/brand/email/headline-new-address.png +0 -0
- package/dist/email/assets.d.ts +6 -0
- package/dist/email/assets.d.ts.map +1 -1
- package/dist/email/assets.js +1 -0
- package/dist/email/assets.js.map +1 -1
- package/dist/email/index.d.ts +79 -4
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +177 -35
- package/dist/email/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +9 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ONBOARD WIDGET'S CONTENT (ui#419). The words, the logo pairs, the timings,
|
|
3
|
+
* the colour Gemini's artwork and the logo cycle, as plain data and plain
|
|
4
|
+
* functions.
|
|
5
|
+
*
|
|
6
|
+
* Vlad's design: CloudGrid 2026 Figma 5ouhISIL0mwYGHurvCGoEO, "Copy Fetch
|
|
7
|
+
* Widget" 11984:30251, and his working build (the reference for motion).
|
|
8
|
+
*
|
|
9
|
+
* ## No React, deliberately
|
|
10
|
+
*
|
|
11
|
+
* Two things draw this widget: the React block `OnboardWidget` (the Console's
|
|
12
|
+
* Home, any "get started" place) and, later, the badge's framework-free
|
|
13
|
+
* watermark overlay (Figma 11918:113662), which renders without React on a
|
|
14
|
+
* customer's app. Both import this module, so a word, a pair or a timing changes
|
|
15
|
+
* in one place and the two cannot drift. That is the `install-content` shape:
|
|
16
|
+
* marks are NAMES (`TOOL_MARKS` keys), and the renderer turns them into nodes.
|
|
17
|
+
*
|
|
18
|
+
* ## The motion
|
|
19
|
+
*
|
|
20
|
+
* After a 2s hold, slot 1 crossfades (600ms ease-in-out), then slot 2, then
|
|
21
|
+
* slot 3, each starting 2600ms after the one before (the fade plus a 2s pause).
|
|
22
|
+
* The last fade ends, the widget holds 2s, and the slots cross back the same
|
|
23
|
+
* way. It loops. `createLogoCycle` schedules exactly that, one timer at a time;
|
|
24
|
+
* the renderer owns the 600ms fade as a CSS transition on opacity.
|
|
25
|
+
*
|
|
26
|
+
* `bindLogoCycle` wires it to the page: nothing moves under
|
|
27
|
+
* `prefers-reduced-motion` (set A stays and no timer is started), and the loop
|
|
28
|
+
* stops while the tab is hidden and picks up where it was when it is shown.
|
|
29
|
+
*/
|
|
30
|
+
import type { ToolMarkName } from "./tool-marks";
|
|
31
|
+
export declare const ONBOARD_WIDGET_COPY: Readonly<{
|
|
32
|
+
/** The button's text, and so its accessible name. */
|
|
33
|
+
label: "Onboard your agent to CloudGrid";
|
|
34
|
+
/**
|
|
35
|
+
* The tip above it on hover and keyboard focus. The Figma frame reads "into
|
|
36
|
+
* you AI coding tool"; the issue fixes the typo.
|
|
37
|
+
*/
|
|
38
|
+
tip: "Copy a setup prompt into your AI coding tool";
|
|
39
|
+
/** Shown for 2s after a copy, centred in the same box, and announced. */
|
|
40
|
+
copied: "Copied. Paste into your AI coding tool.";
|
|
41
|
+
/** `CopyButton`'s refusal words. */
|
|
42
|
+
blocked: "Couldn't copy";
|
|
43
|
+
/** What the live region says on a refusal; the tip then shows the prompt. */
|
|
44
|
+
announceBlocked: "Couldn't copy. The browser blocked the clipboard. The prompt is in the tip, to select by hand.";
|
|
45
|
+
}>;
|
|
46
|
+
export type OnboardWidgetSlot = {
|
|
47
|
+
/** The mark shown first, and the only one under reduced motion. */
|
|
48
|
+
a: ToolMarkName;
|
|
49
|
+
/** The mark it crossfades to. */
|
|
50
|
+
b: ToolMarkName;
|
|
51
|
+
/** Set A is drawn in colour from `GEMINI_COLOR_ART` rather than flat. */
|
|
52
|
+
aColour?: true;
|
|
53
|
+
};
|
|
54
|
+
/** The three slots, left to right. */
|
|
55
|
+
export declare const ONBOARD_WIDGET_SLOTS: readonly OnboardWidgetSlot[];
|
|
56
|
+
export declare const ONBOARD_WIDGET_TIMING: Readonly<{
|
|
57
|
+
/** The pause before each wave, and after its last fade. */
|
|
58
|
+
holdMs: 2000;
|
|
59
|
+
/** One slot's crossfade, ease-in-out. */
|
|
60
|
+
fadeMs: 600;
|
|
61
|
+
/** Start to start, slot to slot: the fade plus a 2s pause. */
|
|
62
|
+
staggerMs: 2600;
|
|
63
|
+
/** Hover waits this long before the tip shows. */
|
|
64
|
+
tipDelayMs: 300;
|
|
65
|
+
/** How long the copied line stays. */
|
|
66
|
+
copiedMs: 2000;
|
|
67
|
+
}>;
|
|
68
|
+
/**
|
|
69
|
+
* The frame's measurements, and the narrow rule (ui#423).
|
|
70
|
+
*
|
|
71
|
+
* Wide, the frame is one row, `heightPx` tall: three `slotPx` logo slots
|
|
72
|
+
* `logoGapPx` apart, the label and the copy icon, `groupGapPx` between the three
|
|
73
|
+
* groups. Its natural width is about 413px (413.34 in Chromium with Manrope).
|
|
74
|
+
*
|
|
75
|
+
* Narrower than that, it never overflows. There is no breakpoint to keep in step
|
|
76
|
+
* with the words: the content wraps, so the rule holds whatever the label's
|
|
77
|
+
* width is.
|
|
78
|
+
*
|
|
79
|
+
* - The widget takes `max-width: 100%`.
|
|
80
|
+
* - The logos are one item and the label with the icon is another, in a
|
|
81
|
+
* centred row that wraps. So when both no longer fit, the logos take their
|
|
82
|
+
* own centred row above the label and the icon, `rowGapPx` apart.
|
|
83
|
+
* - The label and the icon are one run of centred text, the icon inline
|
|
84
|
+
* `groupGapPx` after the label's last word and glued to it. A wrapped box
|
|
85
|
+
* keeps the full width, so a flex icon beside it would sit at the far edge;
|
|
86
|
+
* inline, it rides with the centred lines and never drops alone.
|
|
87
|
+
* - The height is `heightPx` with `min-height: fit-content` and `paddingBlockPx`
|
|
88
|
+
* above and below. Wide, the content plus that padding is under `heightPx`,
|
|
89
|
+
* so nothing changes; once it wraps, the height grows and keeps the padding.
|
|
90
|
+
* The radius and the side padding stay.
|
|
91
|
+
* - The label may wrap, balanced and centred, before the icon drops: the icon
|
|
92
|
+
* stays beside it down to a 288px column (a 320px phone).
|
|
93
|
+
* - The copied and refused lines wrap the same way inside their overlay, which
|
|
94
|
+
* is still the idle face's box, so nothing jumps. The check is inline too,
|
|
95
|
+
* 8px before "Copied.".
|
|
96
|
+
*
|
|
97
|
+
* `onboardWidgetNarrowCss` is the same rule as CSS, for a renderer without
|
|
98
|
+
* Tailwind (the badge's watermark twin).
|
|
99
|
+
*/
|
|
100
|
+
export declare const ONBOARD_WIDGET_LAYOUT: Readonly<{
|
|
101
|
+
heightPx: 48;
|
|
102
|
+
radiusPx: 16;
|
|
103
|
+
paddingInlinePx: 16;
|
|
104
|
+
slotPx: 20;
|
|
105
|
+
logoGapPx: 16;
|
|
106
|
+
groupGapPx: 20;
|
|
107
|
+
/** Between the copied check and "Copied.". */
|
|
108
|
+
checkGapPx: 8;
|
|
109
|
+
/** Narrow: between the logo row and the label row. */
|
|
110
|
+
rowGapPx: 12;
|
|
111
|
+
/**
|
|
112
|
+
* Above and below the content, which shows once the height grows. Wide, the
|
|
113
|
+
* 48px row centres its 22px line with 13px each side, so 12 keeps the look
|
|
114
|
+
* and stays under 48.
|
|
115
|
+
*/
|
|
116
|
+
paddingBlockPx: 12;
|
|
117
|
+
/** The narrowest column it is built and tested for: a 320px phone's 288. */
|
|
118
|
+
minColumnPx: 288;
|
|
119
|
+
}>;
|
|
120
|
+
/**
|
|
121
|
+
* The parts the narrow rule styles, as selectors in the renderer's own markup.
|
|
122
|
+
* The shape it expects:
|
|
123
|
+
*
|
|
124
|
+
* root
|
|
125
|
+
* content
|
|
126
|
+
* logos (the three slots)
|
|
127
|
+
* text "Onboard your agent to " <tail>"CloudGrid"<icon/></tail>
|
|
128
|
+
* (overlay) result <check/>"Copied. Paste into your AI coding tool."
|
|
129
|
+
*/
|
|
130
|
+
export type OnboardWidgetNarrowSelectors = {
|
|
131
|
+
/** The widget itself (the button). */
|
|
132
|
+
root: string;
|
|
133
|
+
/** The idle face's content: the logos, then the text. */
|
|
134
|
+
content: string;
|
|
135
|
+
/** The three logo slots, together. */
|
|
136
|
+
logos: string;
|
|
137
|
+
/** The label and the copy icon: one run of text. */
|
|
138
|
+
text: string;
|
|
139
|
+
/** The label's last word with the icon, kept on one line. */
|
|
140
|
+
tail: string;
|
|
141
|
+
/** The 20px copy icon, inline after the last word. */
|
|
142
|
+
icon: string;
|
|
143
|
+
/** The copied or refused line, in the overlay: one run of text. */
|
|
144
|
+
result: string;
|
|
145
|
+
/** The 20px check disc, inline before "Copied.". */
|
|
146
|
+
check: string;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* The narrow rule as CSS, for the selectors given: the declarations that make
|
|
150
|
+
* the frame wrap rather than overflow. Add it after the frame's own styles; it
|
|
151
|
+
* restates the gaps and sizes it depends on, from `ONBOARD_WIDGET_LAYOUT`.
|
|
152
|
+
*/
|
|
153
|
+
export declare function onboardWidgetNarrowCss(s: OnboardWidgetNarrowSelectors): string;
|
|
154
|
+
/**
|
|
155
|
+
* The Gemini star in colour: a blue star with green, red and yellow linear
|
|
156
|
+
* gradients laid over it. The library's flat `GeminiMark` (simple-icons, one
|
|
157
|
+
* colour) stays for every other use; the design draws this one.
|
|
158
|
+
*
|
|
159
|
+
* Source: @lobehub/icons-static-svg 1.94.0, `icons/gemini-color.svg` (MIT),
|
|
160
|
+
* read programmatically: the path, the base fill and each gradient's line and
|
|
161
|
+
* stops are that file's, unmodified. Hex without the `#`, like `TOOL_MARKS`:
|
|
162
|
+
* a vendor's own colours, not the system's. The Figma frame draws the same star
|
|
163
|
+
* as a raster image; this is its vector.
|
|
164
|
+
*/
|
|
165
|
+
export declare const GEMINI_COLOR_ART: Readonly<{
|
|
166
|
+
viewBox: "0 0 24 24";
|
|
167
|
+
path: "M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z";
|
|
168
|
+
base: "3186FF";
|
|
169
|
+
layers: readonly {
|
|
170
|
+
hex: string;
|
|
171
|
+
x1: number;
|
|
172
|
+
y1: number;
|
|
173
|
+
x2: number;
|
|
174
|
+
y2: number;
|
|
175
|
+
fadeAt: number;
|
|
176
|
+
}[];
|
|
177
|
+
}>;
|
|
178
|
+
export type LogoSet = "a" | "b";
|
|
179
|
+
type Timers = {
|
|
180
|
+
setTimeout: (fn: () => void, ms: number) => unknown;
|
|
181
|
+
clearTimeout: (handle: never) => void;
|
|
182
|
+
};
|
|
183
|
+
export type LogoCycle = {
|
|
184
|
+
/** Each slot's current set, left to right. */
|
|
185
|
+
readonly sets: readonly LogoSet[];
|
|
186
|
+
/** Runs the loop from where it is, after one hold. A second call does nothing. */
|
|
187
|
+
start(): void;
|
|
188
|
+
/** Stops it where it is. Clears its timer. */
|
|
189
|
+
stop(): void;
|
|
190
|
+
/** Puts every slot back on set A. Does not start or stop. */
|
|
191
|
+
reset(): void;
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* The loop, one timer at a time. `onChange` gets every slot's set after each
|
|
195
|
+
* flip; the renderer crossfades the slot whose set changed.
|
|
196
|
+
*
|
|
197
|
+
* A restart after a stop holds first, then finishes an unfinished wave from
|
|
198
|
+
* the first slot that has not crossed, so a tab that comes back mid-wave does
|
|
199
|
+
* not jump.
|
|
200
|
+
*/
|
|
201
|
+
export declare function createLogoCycle({ slots, timing, onChange, timers, }?: {
|
|
202
|
+
slots?: number;
|
|
203
|
+
timing?: Pick<typeof ONBOARD_WIDGET_TIMING, "holdMs" | "fadeMs" | "staggerMs">;
|
|
204
|
+
onChange?: (sets: readonly LogoSet[]) => void;
|
|
205
|
+
timers?: Timers;
|
|
206
|
+
}): LogoCycle;
|
|
207
|
+
type Listenable = {
|
|
208
|
+
addEventListener: (type: string, fn: () => void) => void;
|
|
209
|
+
removeEventListener: (type: string, fn: () => void) => void;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Runs `cycle` while it should run: never under reduced motion (switched on
|
|
213
|
+
* mid-loop, it stops and puts set A back), and not while the tab is hidden.
|
|
214
|
+
* Returns the cleanup, which stops the cycle and removes both listeners.
|
|
215
|
+
*
|
|
216
|
+
* Pass `document` and `window.matchMedia("(prefers-reduced-motion: reduce)")`.
|
|
217
|
+
*/
|
|
218
|
+
export declare function bindLogoCycle(cycle: LogoCycle, { document, reducedMotion }: {
|
|
219
|
+
document: Listenable & {
|
|
220
|
+
hidden: boolean;
|
|
221
|
+
};
|
|
222
|
+
reducedMotion: Listenable & {
|
|
223
|
+
matches: boolean;
|
|
224
|
+
};
|
|
225
|
+
}): () => void;
|
|
226
|
+
export {};
|
|
227
|
+
//# sourceMappingURL=onboard-widget-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboard-widget-content.d.ts","sourceRoot":"","sources":["../../src/blocks/onboard-widget-content.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ONBOARD WIDGET'S CONTENT (ui#419). The words, the logo pairs, the timings,
|
|
3
|
+
* the colour Gemini's artwork and the logo cycle, as plain data and plain
|
|
4
|
+
* functions.
|
|
5
|
+
*
|
|
6
|
+
* Vlad's design: CloudGrid 2026 Figma 5ouhISIL0mwYGHurvCGoEO, "Copy Fetch
|
|
7
|
+
* Widget" 11984:30251, and his working build (the reference for motion).
|
|
8
|
+
*
|
|
9
|
+
* ## No React, deliberately
|
|
10
|
+
*
|
|
11
|
+
* Two things draw this widget: the React block `OnboardWidget` (the Console's
|
|
12
|
+
* Home, any "get started" place) and, later, the badge's framework-free
|
|
13
|
+
* watermark overlay (Figma 11918:113662), which renders without React on a
|
|
14
|
+
* customer's app. Both import this module, so a word, a pair or a timing changes
|
|
15
|
+
* in one place and the two cannot drift. That is the `install-content` shape:
|
|
16
|
+
* marks are NAMES (`TOOL_MARKS` keys), and the renderer turns them into nodes.
|
|
17
|
+
*
|
|
18
|
+
* ## The motion
|
|
19
|
+
*
|
|
20
|
+
* After a 2s hold, slot 1 crossfades (600ms ease-in-out), then slot 2, then
|
|
21
|
+
* slot 3, each starting 2600ms after the one before (the fade plus a 2s pause).
|
|
22
|
+
* The last fade ends, the widget holds 2s, and the slots cross back the same
|
|
23
|
+
* way. It loops. `createLogoCycle` schedules exactly that, one timer at a time;
|
|
24
|
+
* the renderer owns the 600ms fade as a CSS transition on opacity.
|
|
25
|
+
*
|
|
26
|
+
* `bindLogoCycle` wires it to the page: nothing moves under
|
|
27
|
+
* `prefers-reduced-motion` (set A stays and no timer is started), and the loop
|
|
28
|
+
* stops while the tab is hidden and picks up where it was when it is shown.
|
|
29
|
+
*/
|
|
30
|
+
export const ONBOARD_WIDGET_COPY = Object.freeze({
|
|
31
|
+
/** The button's text, and so its accessible name. */
|
|
32
|
+
label: "Onboard your agent to CloudGrid",
|
|
33
|
+
/**
|
|
34
|
+
* The tip above it on hover and keyboard focus. The Figma frame reads "into
|
|
35
|
+
* you AI coding tool"; the issue fixes the typo.
|
|
36
|
+
*/
|
|
37
|
+
tip: "Copy a setup prompt into your AI coding tool",
|
|
38
|
+
/** Shown for 2s after a copy, centred in the same box, and announced. */
|
|
39
|
+
copied: "Copied. Paste into your AI coding tool.",
|
|
40
|
+
/** `CopyButton`'s refusal words. */
|
|
41
|
+
blocked: "Couldn't copy",
|
|
42
|
+
/** What the live region says on a refusal; the tip then shows the prompt. */
|
|
43
|
+
announceBlocked: "Couldn't copy. The browser blocked the clipboard. The prompt is in the tip, to select by hand.",
|
|
44
|
+
});
|
|
45
|
+
/** The three slots, left to right. */
|
|
46
|
+
export const ONBOARD_WIDGET_SLOTS = Object.freeze([
|
|
47
|
+
{ a: "Claude Code", b: "Codex" },
|
|
48
|
+
{ a: "Cursor", b: "Claude" },
|
|
49
|
+
{ a: "Google Gemini", b: "Antigravity", aColour: true },
|
|
50
|
+
]);
|
|
51
|
+
export const ONBOARD_WIDGET_TIMING = Object.freeze({
|
|
52
|
+
/** The pause before each wave, and after its last fade. */
|
|
53
|
+
holdMs: 2000,
|
|
54
|
+
/** One slot's crossfade, ease-in-out. */
|
|
55
|
+
fadeMs: 600,
|
|
56
|
+
/** Start to start, slot to slot: the fade plus a 2s pause. */
|
|
57
|
+
staggerMs: 2600,
|
|
58
|
+
/** Hover waits this long before the tip shows. */
|
|
59
|
+
tipDelayMs: 300,
|
|
60
|
+
/** How long the copied line stays. */
|
|
61
|
+
copiedMs: 2000,
|
|
62
|
+
});
|
|
63
|
+
/**
|
|
64
|
+
* The frame's measurements, and the narrow rule (ui#423).
|
|
65
|
+
*
|
|
66
|
+
* Wide, the frame is one row, `heightPx` tall: three `slotPx` logo slots
|
|
67
|
+
* `logoGapPx` apart, the label and the copy icon, `groupGapPx` between the three
|
|
68
|
+
* groups. Its natural width is about 413px (413.34 in Chromium with Manrope).
|
|
69
|
+
*
|
|
70
|
+
* Narrower than that, it never overflows. There is no breakpoint to keep in step
|
|
71
|
+
* with the words: the content wraps, so the rule holds whatever the label's
|
|
72
|
+
* width is.
|
|
73
|
+
*
|
|
74
|
+
* - The widget takes `max-width: 100%`.
|
|
75
|
+
* - The logos are one item and the label with the icon is another, in a
|
|
76
|
+
* centred row that wraps. So when both no longer fit, the logos take their
|
|
77
|
+
* own centred row above the label and the icon, `rowGapPx` apart.
|
|
78
|
+
* - The label and the icon are one run of centred text, the icon inline
|
|
79
|
+
* `groupGapPx` after the label's last word and glued to it. A wrapped box
|
|
80
|
+
* keeps the full width, so a flex icon beside it would sit at the far edge;
|
|
81
|
+
* inline, it rides with the centred lines and never drops alone.
|
|
82
|
+
* - The height is `heightPx` with `min-height: fit-content` and `paddingBlockPx`
|
|
83
|
+
* above and below. Wide, the content plus that padding is under `heightPx`,
|
|
84
|
+
* so nothing changes; once it wraps, the height grows and keeps the padding.
|
|
85
|
+
* The radius and the side padding stay.
|
|
86
|
+
* - The label may wrap, balanced and centred, before the icon drops: the icon
|
|
87
|
+
* stays beside it down to a 288px column (a 320px phone).
|
|
88
|
+
* - The copied and refused lines wrap the same way inside their overlay, which
|
|
89
|
+
* is still the idle face's box, so nothing jumps. The check is inline too,
|
|
90
|
+
* 8px before "Copied.".
|
|
91
|
+
*
|
|
92
|
+
* `onboardWidgetNarrowCss` is the same rule as CSS, for a renderer without
|
|
93
|
+
* Tailwind (the badge's watermark twin).
|
|
94
|
+
*/
|
|
95
|
+
export const ONBOARD_WIDGET_LAYOUT = Object.freeze({
|
|
96
|
+
heightPx: 48,
|
|
97
|
+
radiusPx: 16,
|
|
98
|
+
paddingInlinePx: 16,
|
|
99
|
+
slotPx: 20,
|
|
100
|
+
logoGapPx: 16,
|
|
101
|
+
groupGapPx: 20,
|
|
102
|
+
/** Between the copied check and "Copied.". */
|
|
103
|
+
checkGapPx: 8,
|
|
104
|
+
/** Narrow: between the logo row and the label row. */
|
|
105
|
+
rowGapPx: 12,
|
|
106
|
+
/**
|
|
107
|
+
* Above and below the content, which shows once the height grows. Wide, the
|
|
108
|
+
* 48px row centres its 22px line with 13px each side, so 12 keeps the look
|
|
109
|
+
* and stays under 48.
|
|
110
|
+
*/
|
|
111
|
+
paddingBlockPx: 12,
|
|
112
|
+
/** The narrowest column it is built and tested for: a 320px phone's 288. */
|
|
113
|
+
minColumnPx: 288,
|
|
114
|
+
});
|
|
115
|
+
/**
|
|
116
|
+
* The narrow rule as CSS, for the selectors given: the declarations that make
|
|
117
|
+
* the frame wrap rather than overflow. Add it after the frame's own styles; it
|
|
118
|
+
* restates the gaps and sizes it depends on, from `ONBOARD_WIDGET_LAYOUT`.
|
|
119
|
+
*/
|
|
120
|
+
export function onboardWidgetNarrowCss(s) {
|
|
121
|
+
const L = ONBOARD_WIDGET_LAYOUT;
|
|
122
|
+
return [
|
|
123
|
+
`${s.root}{box-sizing:border-box;max-width:100%;height:${L.heightPx}px;min-height:fit-content;padding:${L.paddingBlockPx}px ${L.paddingInlinePx}px}`,
|
|
124
|
+
`${s.content}{display:inline-flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:${L.rowGapPx}px ${L.groupGapPx}px}`,
|
|
125
|
+
`${s.logos}{display:inline-flex;flex-shrink:0;gap:${L.logoGapPx}px}`,
|
|
126
|
+
`${s.text},${s.result}{white-space:normal;text-wrap:balance;text-align:center}`,
|
|
127
|
+
`${s.tail}{white-space:nowrap}`,
|
|
128
|
+
`${s.icon},${s.check}{display:inline-flex;width:${L.slotPx}px;height:${L.slotPx}px;vertical-align:top;position:relative;top:calc((1lh - ${L.slotPx}px) / 2)}`,
|
|
129
|
+
`${s.icon}{margin-inline-start:${L.groupGapPx}px}`,
|
|
130
|
+
`${s.check}{margin-inline-end:${L.checkGapPx}px}`,
|
|
131
|
+
].join("\n");
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The Gemini star in colour: a blue star with green, red and yellow linear
|
|
135
|
+
* gradients laid over it. The library's flat `GeminiMark` (simple-icons, one
|
|
136
|
+
* colour) stays for every other use; the design draws this one.
|
|
137
|
+
*
|
|
138
|
+
* Source: @lobehub/icons-static-svg 1.94.0, `icons/gemini-color.svg` (MIT),
|
|
139
|
+
* read programmatically: the path, the base fill and each gradient's line and
|
|
140
|
+
* stops are that file's, unmodified. Hex without the `#`, like `TOOL_MARKS`:
|
|
141
|
+
* a vendor's own colours, not the system's. The Figma frame draws the same star
|
|
142
|
+
* as a raster image; this is its vector.
|
|
143
|
+
*/
|
|
144
|
+
export const GEMINI_COLOR_ART = Object.freeze({
|
|
145
|
+
viewBox: "0 0 24 24",
|
|
146
|
+
path: "M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z",
|
|
147
|
+
base: "3186FF",
|
|
148
|
+
layers: Object.freeze([
|
|
149
|
+
{ hex: "08B962", x1: 7, y1: 15.5, x2: 11, y2: 12, fadeAt: 1 },
|
|
150
|
+
{ hex: "F94543", x1: 8, y1: 5.5, x2: 11.5, y2: 11, fadeAt: 1 },
|
|
151
|
+
{ hex: "FABC12", x1: 3.5, y1: 13.5, x2: 17.5, y2: 12, fadeAt: 0.46 },
|
|
152
|
+
]),
|
|
153
|
+
});
|
|
154
|
+
/**
|
|
155
|
+
* The loop, one timer at a time. `onChange` gets every slot's set after each
|
|
156
|
+
* flip; the renderer crossfades the slot whose set changed.
|
|
157
|
+
*
|
|
158
|
+
* A restart after a stop holds first, then finishes an unfinished wave from
|
|
159
|
+
* the first slot that has not crossed, so a tab that comes back mid-wave does
|
|
160
|
+
* not jump.
|
|
161
|
+
*/
|
|
162
|
+
export function createLogoCycle({ slots = ONBOARD_WIDGET_SLOTS.length, timing = ONBOARD_WIDGET_TIMING, onChange, timers = globalThis, } = {}) {
|
|
163
|
+
let sets = Array.from({ length: slots }, () => "a");
|
|
164
|
+
let handle;
|
|
165
|
+
let running = false;
|
|
166
|
+
const schedule = (fn, ms) => {
|
|
167
|
+
handle = timers.setTimeout(fn, ms);
|
|
168
|
+
};
|
|
169
|
+
/** Crosses slot `i` to `target`, then schedules the next slot, or the next wave. */
|
|
170
|
+
const cross = (i, target) => {
|
|
171
|
+
sets = sets.map((s, j) => (j === i ? target : s));
|
|
172
|
+
onChange?.(sets);
|
|
173
|
+
if (i < slots - 1)
|
|
174
|
+
schedule(() => cross(i + 1, target), timing.staggerMs);
|
|
175
|
+
else
|
|
176
|
+
schedule(wave, timing.fadeMs + timing.holdMs);
|
|
177
|
+
};
|
|
178
|
+
/** A wave: every slot to the other set, or, mid-wave, the rest to where slot 1 went. */
|
|
179
|
+
const wave = () => {
|
|
180
|
+
const first = sets.findIndex((s) => s !== sets[0]);
|
|
181
|
+
if (first === -1)
|
|
182
|
+
cross(0, sets[0] === "a" ? "b" : "a");
|
|
183
|
+
else
|
|
184
|
+
cross(first, sets[0]);
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
get sets() {
|
|
188
|
+
return sets;
|
|
189
|
+
},
|
|
190
|
+
start() {
|
|
191
|
+
if (running)
|
|
192
|
+
return;
|
|
193
|
+
running = true;
|
|
194
|
+
schedule(wave, timing.holdMs);
|
|
195
|
+
},
|
|
196
|
+
stop() {
|
|
197
|
+
running = false;
|
|
198
|
+
timers.clearTimeout(handle);
|
|
199
|
+
handle = undefined;
|
|
200
|
+
},
|
|
201
|
+
reset() {
|
|
202
|
+
sets = sets.map(() => "a");
|
|
203
|
+
onChange?.(sets);
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Runs `cycle` while it should run: never under reduced motion (switched on
|
|
209
|
+
* mid-loop, it stops and puts set A back), and not while the tab is hidden.
|
|
210
|
+
* Returns the cleanup, which stops the cycle and removes both listeners.
|
|
211
|
+
*
|
|
212
|
+
* Pass `document` and `window.matchMedia("(prefers-reduced-motion: reduce)")`.
|
|
213
|
+
*/
|
|
214
|
+
export function bindLogoCycle(cycle, { document, reducedMotion }) {
|
|
215
|
+
const sync = () => {
|
|
216
|
+
if (reducedMotion.matches) {
|
|
217
|
+
cycle.stop();
|
|
218
|
+
if (cycle.sets.some((s) => s !== "a"))
|
|
219
|
+
cycle.reset();
|
|
220
|
+
}
|
|
221
|
+
else if (document.hidden) {
|
|
222
|
+
cycle.stop();
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
cycle.start();
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
document.addEventListener("visibilitychange", sync);
|
|
229
|
+
reducedMotion.addEventListener("change", sync);
|
|
230
|
+
sync();
|
|
231
|
+
return () => {
|
|
232
|
+
document.removeEventListener("visibilitychange", sync);
|
|
233
|
+
reducedMotion.removeEventListener("change", sync);
|
|
234
|
+
cycle.stop();
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=onboard-widget-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboard-widget-content.js","sourceRoot":"","sources":["../../src/blocks/onboard-widget-content.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* THE ONBOARD-YOUR-AGENT COPY WIDGET (ui#419), Vlad's design: CloudGrid 2026
|
|
4
|
+
* Figma 5ouhISIL0mwYGHurvCGoEO, "Copy Fetch Widget" 11984:30251 (Default with
|
|
5
|
+
* both logo sets, Hover, Clicked), and his working build for the motion.
|
|
6
|
+
*
|
|
7
|
+
* <OnboardWidget value={`fetch ${COMPANY_SITE_ORIGIN}/agents and plug this in`} />
|
|
8
|
+
* <OnboardWidget value={prompt} block /> // fills the width, as in the overlay card
|
|
9
|
+
*
|
|
10
|
+
* One button that copies `value` (no host is baked in: the caller passes it).
|
|
11
|
+
* The words, the logo pairs and the timings are `onboard-widget-content`, which
|
|
12
|
+
* the badge's framework-free watermark twin reads too.
|
|
13
|
+
*
|
|
14
|
+
* ## The layout
|
|
15
|
+
*
|
|
16
|
+
* 48 tall, padding 0 16, radius 16, a 1px hairline, the card ground, Manrope 15
|
|
17
|
+
* regular ink, no wrapping. Three 20px logo slots 16px apart, the label, and a
|
|
18
|
+
* 20px copy icon, 20px between the three groups. It hugs its content; `block`
|
|
19
|
+
* fills the width and centres the content.
|
|
20
|
+
*
|
|
21
|
+
* Narrower than its natural width (about 413px) it never overflows (ui#423):
|
|
22
|
+
* it takes `max-width: 100%`, the logos take their own centred row above the
|
|
23
|
+
* label and the icon (12px apart), and the height grows from 48 with 12px above
|
|
24
|
+
* and below. Down to a 288px column the label wraps, balanced, before the icon
|
|
25
|
+
* drops; the copied and refused lines wrap the same way. No breakpoint: the
|
|
26
|
+
* content wraps. The numbers are `ONBOARD_WIDGET_LAYOUT`, and the same rule as
|
|
27
|
+
* CSS is `onboardWidgetNarrowCss`, for the badge's twin.
|
|
28
|
+
*
|
|
29
|
+
* ## The logos
|
|
30
|
+
*
|
|
31
|
+
* Each slot holds its two marks stacked; the visible one is opacity 1 and the
|
|
32
|
+
* other 0, with a 600ms ease-in-out transition, so a flip of the slot's set is
|
|
33
|
+
* the crossfade. `createLogoCycle` flips them (slot 1, 2, 3, 2600ms apart, after
|
|
34
|
+
* a 2s hold, and back), and `bindLogoCycle` keeps it still under reduced motion
|
|
35
|
+
* (set A, no timer at all) and paused while the tab is hidden. Slot 3's set A is
|
|
36
|
+
* the colour Gemini star (`GeminiColorMark`); the library's flat `GeminiMark`
|
|
37
|
+
* stays as it is. The marks are `aria-hidden`: the label names the button.
|
|
38
|
+
*
|
|
39
|
+
* ## Hover, focus, click
|
|
40
|
+
*
|
|
41
|
+
* - Hover, only where the device hovers (Tailwind's `hover:` is inside
|
|
42
|
+
* `@media (hover: hover)`, so a tap does not leave it stuck): the ground and
|
|
43
|
+
* border take the violet wash and the copy icon turns solid Primary. The tip,
|
|
44
|
+
* "Copy a setup prompt into your AI coding tool", shows above after 300ms.
|
|
45
|
+
* - Keyboard focus: the same tip, plus the library focus ring.
|
|
46
|
+
* - Click: `CopyButton` copies `value` and holds the result for 2s. The content
|
|
47
|
+
* gives way to a centred green check and "Copied. Paste into your AI coding
|
|
48
|
+
* tool.", which is a layer over the content rather than a replacement, so the
|
|
49
|
+
* width does not move; the live region says the same line. A refusal shows
|
|
50
|
+
* "Couldn't copy" the same way and opens the tip with the prompt in it,
|
|
51
|
+
* `select-all`, so it can be copied by hand.
|
|
52
|
+
*
|
|
53
|
+
* ## The tokens
|
|
54
|
+
*
|
|
55
|
+
* Ground `bg-card`, border `border-hairline`, text `text-ink`, the copy icon
|
|
56
|
+
* `text-steel` (Figma "N3 new", slate-500) and `text-brand` on hover (Primary),
|
|
57
|
+
* the check's disc `bg-online` (Figma "Positive" #3FA24E; the library's online
|
|
58
|
+
* green #00a05c is the nearest), the tip the Tooltip primitive's dark ground.
|
|
59
|
+
*
|
|
60
|
+
* The hover wash is Figma "Primary 20", #DEDAFC, and no library token holds it:
|
|
61
|
+
* violet-150 (#e3e0fd) and violet-200 (#d9d4fb) sit exactly as far from it on
|
|
62
|
+
* either side. It IS Primary composited on white at 20% to the byte, so it is
|
|
63
|
+
* written as that, `color-mix(in srgb, var(--cg-primary) 20%, var(--cg-surface-card))`,
|
|
64
|
+
* in `--cg-onboard-widget-wash` on the button: two tokens, no literal, and it
|
|
65
|
+
* follows the brand hue if that is retuned.
|
|
66
|
+
*
|
|
67
|
+
* A light card on every surface (`data-theme="default"`, as `Notice` is).
|
|
68
|
+
*
|
|
69
|
+
* ## Specimens
|
|
70
|
+
*
|
|
71
|
+
* `specimen` pins one face for the gallery and for screenshots: a logo set
|
|
72
|
+
* (no motion), hover, focus, copied or blocked. Product code leaves it out.
|
|
73
|
+
*/
|
|
74
|
+
type Specimen = "set-a" | "set-b" | "hover" | "focus" | "copied" | "blocked";
|
|
75
|
+
/**
|
|
76
|
+
* The Gemini star in colour, from `GEMINI_COLOR_ART`. The gradient ids come from
|
|
77
|
+
* `useId`, so two widgets on one page never point at each other's gradients.
|
|
78
|
+
*/
|
|
79
|
+
declare function GeminiColorMark({ className }: {
|
|
80
|
+
className?: string;
|
|
81
|
+
}): React.JSX.Element;
|
|
82
|
+
declare function OnboardWidget({ value, block, specimen, className, }: {
|
|
83
|
+
/** What is copied, exactly. The Console passes `fetch ${COMPANY_SITE_ORIGIN}/agents and plug this in`. */
|
|
84
|
+
value: string;
|
|
85
|
+
/** Fill the width, as in the watermark overlay's card. Default: hug the content. */
|
|
86
|
+
block?: boolean;
|
|
87
|
+
/** Pins one face, for the gallery and screenshots. Leave it out in product code. */
|
|
88
|
+
specimen?: Specimen;
|
|
89
|
+
className?: string;
|
|
90
|
+
}): React.JSX.Element;
|
|
91
|
+
export { OnboardWidget, GeminiColorMark };
|
|
92
|
+
//# sourceMappingURL=onboard-widget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboard-widget.d.ts","sourceRoot":"","sources":["../../src/blocks/onboard-widget.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkB9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAEH,KAAK,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAA;AAkC5E;;;GAGG;AACH,iBAAS,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,qBAmB7D;AAiDD,iBAAS,aAAa,CAAC,EACrB,KAAK,EACL,KAAa,EACb,QAAQ,EACR,SAAS,GACV,EAAE;IACD,0GAA0G;IAC1G,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,qBAyHA;AAED,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,CAAA"}
|