@arbidocs/blocks 0.3.119 → 0.3.121

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,1540 @@
1
+ 'use strict';
2
+
3
+ var zustand = require('zustand');
4
+ var middleware = require('zustand/middleware');
5
+ var react = require('react');
6
+ var lucideReact = require('lucide-react');
7
+ var ui = require('@arbidocs/react/ui');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ // src/theme/tokens.ts
11
+ var THEME_STYLE_ID = "arbi-theme-vars";
12
+ var TRIPLET_RE = /^-?\d+(?:\.\d+)?\s+-?\d+(?:\.\d+)?%\s+-?\d+(?:\.\d+)?%$/;
13
+ function parseRgb(color) {
14
+ if (color.startsWith("#")) {
15
+ let hex = color.slice(1);
16
+ if (hex.length === 3) {
17
+ hex = hex.split("").map((ch) => ch + ch).join("");
18
+ }
19
+ if (hex.length === 6 && /^[0-9a-fA-F]{6}$/.test(hex)) {
20
+ return {
21
+ r: parseInt(hex.slice(0, 2), 16),
22
+ g: parseInt(hex.slice(2, 4), 16),
23
+ b: parseInt(hex.slice(4, 6), 16)
24
+ };
25
+ }
26
+ return null;
27
+ }
28
+ const m = color.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);
29
+ if (m) return { r: Number(m[1]), g: Number(m[2]), b: Number(m[3]) };
30
+ return null;
31
+ }
32
+ function rgbToHslTriplet(r, g, b) {
33
+ const rn = r / 255;
34
+ const gn = g / 255;
35
+ const bn = b / 255;
36
+ const max = Math.max(rn, gn, bn);
37
+ const min = Math.min(rn, gn, bn);
38
+ const l = (max + min) / 2;
39
+ const d = max - min;
40
+ let h = 0;
41
+ let s = 0;
42
+ if (d !== 0) {
43
+ s = d / (1 - Math.abs(2 * l - 1));
44
+ switch (max) {
45
+ case rn:
46
+ h = 60 * ((gn - bn) / d % 6);
47
+ break;
48
+ case gn:
49
+ h = 60 * ((bn - rn) / d + 2);
50
+ break;
51
+ default:
52
+ h = 60 * ((rn - gn) / d + 4);
53
+ break;
54
+ }
55
+ }
56
+ if (h < 0) h += 360;
57
+ return `${Math.round(h)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
58
+ }
59
+ function toHslTriplet(color) {
60
+ const c = color.trim();
61
+ if (TRIPLET_RE.test(c)) return c;
62
+ const rgb = parseRgb(c);
63
+ if (!rgb) return c;
64
+ return rgbToHslTriplet(rgb.r, rgb.g, rgb.b);
65
+ }
66
+ function renderTripletBlock(colors) {
67
+ return Object.entries(colors).map(([key, value]) => ` --${key}: ${toHslTriplet(value)};`).join("\n");
68
+ }
69
+ function emitCssVars(theme, dark) {
70
+ if (typeof document === "undefined") return;
71
+ const meta = [
72
+ ` --font-display: ${theme.fonts.display};`,
73
+ ` --font-sans: ${theme.fonts.sans};`,
74
+ ` --radius: ${theme.radius}rem;`
75
+ ].join("\n");
76
+ let css = `:root {
77
+ ${renderTripletBlock(theme.colors)}
78
+ ${meta}
79
+ }`;
80
+ if (dark) {
81
+ css += `
82
+ :root.dark {
83
+ ${renderTripletBlock(dark)}
84
+ }`;
85
+ }
86
+ let style = document.getElementById(THEME_STYLE_ID);
87
+ if (!style) {
88
+ style = document.createElement("style");
89
+ style.id = THEME_STYLE_ID;
90
+ document.head.appendChild(style);
91
+ }
92
+ style.textContent = css;
93
+ }
94
+ function removeCssVars() {
95
+ if (typeof document === "undefined") return;
96
+ document.getElementById(THEME_STYLE_ID)?.remove();
97
+ }
98
+ function normalizeOptions(optionsOrRoot) {
99
+ if (typeof HTMLElement !== "undefined" && optionsOrRoot instanceof HTMLElement) {
100
+ return { root: optionsOrRoot };
101
+ }
102
+ return optionsOrRoot;
103
+ }
104
+ function applyThemeVars(theme, optionsOrRoot = {}) {
105
+ const { mode = "flat", dark, root = document.documentElement } = normalizeOptions(optionsOrRoot);
106
+ if (mode === "cssVars") {
107
+ emitCssVars(theme, dark);
108
+ return;
109
+ }
110
+ for (const [key, value] of Object.entries(theme.colors)) {
111
+ root.style.setProperty(`--color-${key}`, value);
112
+ }
113
+ root.style.setProperty("--font-display", theme.fonts.display);
114
+ root.style.setProperty("--font-sans", theme.fonts.sans);
115
+ root.style.setProperty("--radius", `${theme.radius}rem`);
116
+ }
117
+ function applyFontVars(fonts, root = document.documentElement) {
118
+ root.style.setProperty("--font-display", fonts.display);
119
+ root.style.setProperty("--font-sans", fonts.sans);
120
+ }
121
+ function clearFontVars(root = document.documentElement) {
122
+ root.style.removeProperty("--font-display");
123
+ root.style.removeProperty("--font-sans");
124
+ }
125
+ function clearThemeVars(theme, optionsOrRoot = {}) {
126
+ const { mode = "flat", root = document.documentElement } = normalizeOptions(optionsOrRoot);
127
+ if (mode === "cssVars") {
128
+ removeCssVars();
129
+ return;
130
+ }
131
+ for (const key of Object.keys(theme.colors)) {
132
+ root.style.removeProperty(`--color-${key}`);
133
+ }
134
+ root.style.removeProperty("--font-display");
135
+ root.style.removeProperty("--font-sans");
136
+ root.style.removeProperty("--radius");
137
+ }
138
+
139
+ // src/theme/fontPairings.ts
140
+ var SERIF_TAIL = "ui-serif, Georgia, serif";
141
+ var SANS_TAIL = "ui-sans-serif, system-ui, sans-serif";
142
+ var FONT_PAIRINGS = [
143
+ {
144
+ id: "system",
145
+ label: "System",
146
+ description: "The reader\u2019s own interface font. Loads nothing.",
147
+ display: SANS_TAIL,
148
+ sans: SANS_TAIL,
149
+ packages: []
150
+ },
151
+ {
152
+ id: "newsreader-inter",
153
+ label: "Newsreader / Inter",
154
+ description: "Editorial serif headlines over a neutral, highly legible body.",
155
+ display: `"Newsreader Variable", ${SERIF_TAIL}`,
156
+ sans: `"Inter Variable", ${SANS_TAIL}`,
157
+ packages: ["@fontsource-variable/newsreader", "@fontsource-variable/inter"]
158
+ },
159
+ {
160
+ id: "fraunces-inter",
161
+ label: "Fraunces / Inter",
162
+ description: "Characterful, slightly literary display with a quiet body.",
163
+ display: `"Fraunces Variable", ${SERIF_TAIL}`,
164
+ sans: `"Inter Variable", ${SANS_TAIL}`,
165
+ packages: ["@fontsource-variable/fraunces", "@fontsource-variable/inter"]
166
+ },
167
+ {
168
+ id: "source-serif-inter",
169
+ label: "Source Serif / Inter",
170
+ description: "A workhorse serif for headings; sober and institutional.",
171
+ display: `"Source Serif 4 Variable", ${SERIF_TAIL}`,
172
+ sans: `"Inter Variable", ${SANS_TAIL}`,
173
+ packages: ["@fontsource-variable/source-serif-4", "@fontsource-variable/inter"]
174
+ },
175
+ {
176
+ id: "inter",
177
+ label: "Inter",
178
+ description: "One neutral grotesque throughout. Product-like, no contrast.",
179
+ display: `"Inter Variable", ${SANS_TAIL}`,
180
+ sans: `"Inter Variable", ${SANS_TAIL}`,
181
+ packages: ["@fontsource-variable/inter"]
182
+ },
183
+ {
184
+ id: "montserrat-nunito",
185
+ label: "Montserrat / Nunito",
186
+ description: "Geometric headings over a rounded body. ARBI\u2019s historic look.",
187
+ display: `"Montserrat Variable", ${SANS_TAIL}`,
188
+ sans: `"Nunito Variable", ${SANS_TAIL}`,
189
+ packages: ["@fontsource-variable/montserrat", "@fontsource-variable/nunito"]
190
+ }
191
+ ];
192
+ function getFontPairing(id) {
193
+ return FONT_PAIRINGS.find((p) => p.id === id);
194
+ }
195
+ function matchFontPairing(fonts) {
196
+ return FONT_PAIRINGS.find((p) => p.display === fonts.display && p.sans === fonts.sans);
197
+ }
198
+ function fontPackages(pairings = FONT_PAIRINGS) {
199
+ return [...new Set(pairings.flatMap((p) => p.packages))].sort();
200
+ }
201
+ function createThemeStore(config) {
202
+ const { defaultTheme, presets, storageKey } = config;
203
+ const useStore = zustand.create()(
204
+ middleware.persist(
205
+ (set, get) => ({
206
+ ...defaultTheme,
207
+ activePresetId: presets[0]?.id ?? null,
208
+ setColor: (key, value) => set((s) => ({ colors: { ...s.colors, [key]: value }, activePresetId: null })),
209
+ setFont: (which, value) => set((s) => ({ fonts: { ...s.fonts, [which]: value }, activePresetId: null })),
210
+ // A pairing sets both faces together — that is the whole point of it.
211
+ // Unknown ids are ignored rather than clearing the typography.
212
+ setFontPairing: (pairingId) => {
213
+ const pairing = getFontPairing(pairingId);
214
+ if (!pairing) return;
215
+ set({ fonts: { display: pairing.display, sans: pairing.sans } });
216
+ },
217
+ setRadius: (radius) => set({ radius, activePresetId: null }),
218
+ applyPreset: (presetId) => {
219
+ const preset = presets.find((p) => p.id === presetId);
220
+ if (!preset) return;
221
+ set({
222
+ colors: { ...preset.tokens.colors },
223
+ fonts: { ...preset.tokens.fonts },
224
+ radius: preset.tokens.radius,
225
+ activePresetId: presetId
226
+ });
227
+ },
228
+ reset: () => set({
229
+ colors: { ...defaultTheme.colors },
230
+ fonts: { ...defaultTheme.fonts },
231
+ radius: defaultTheme.radius,
232
+ activePresetId: presets[0]?.id ?? null
233
+ }),
234
+ snapshot: () => {
235
+ const { colors, fonts, radius } = get();
236
+ return { colors, fonts, radius };
237
+ }
238
+ }),
239
+ { name: storageKey }
240
+ )
241
+ );
242
+ return { ...config, useStore };
243
+ }
244
+ function applyStoredTheme(bundle) {
245
+ const { colors, fonts, radius } = bundle.useStore.getState();
246
+ applyThemeVars({ colors, fonts, radius }, { mode: bundle.mode, dark: bundle.darkColors });
247
+ }
248
+ function ThemeEditor({
249
+ bundle,
250
+ variant = "panel",
251
+ preview,
252
+ onSave,
253
+ testIdPrefix = "theme"
254
+ }) {
255
+ const { useStore, tokens, presets, groupLabels, mode, darkColors } = bundle;
256
+ const colors = useStore((s) => s.colors);
257
+ const fonts = useStore((s) => s.fonts);
258
+ const radius = useStore((s) => s.radius);
259
+ const activePresetId = useStore((s) => s.activePresetId);
260
+ const setColor = useStore((s) => s.setColor);
261
+ const setFont = useStore((s) => s.setFont);
262
+ const setFontPairing = useStore((s) => s.setFontPairing);
263
+ const setRadius = useStore((s) => s.setRadius);
264
+ const applyPreset = useStore((s) => s.applyPreset);
265
+ const reset = useStore((s) => s.reset);
266
+ const [saving, setSaving] = react.useState(false);
267
+ const [savedVia, setSavedVia] = react.useState(null);
268
+ react.useEffect(() => {
269
+ applyThemeVars({ colors, fonts, radius }, { mode, dark: darkColors });
270
+ }, [colors, fonts, radius, mode, darkColors]);
271
+ const tid2 = (suffix) => `${testIdPrefix}-${suffix}`;
272
+ const groups = [...new Set(tokens.map((t) => t.group))];
273
+ const save = async () => {
274
+ if (!onSave) return;
275
+ setSaving(true);
276
+ const via = await onSave();
277
+ setSavedVia(typeof via === "string" ? via : "saved");
278
+ setSaving(false);
279
+ };
280
+ const Presets = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
281
+ presets.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
282
+ "button",
283
+ {
284
+ type: "button",
285
+ onClick: () => applyPreset(p.id),
286
+ className: `rounded-full border px-3 py-1 text-xs font-medium transition-colors ${activePresetId === p.id ? "border-primary bg-primary text-primary-foreground" : "border-border text-foreground hover:border-primary"}`,
287
+ "data-testid": tid2(`preset-${p.id}`),
288
+ children: p.label
289
+ },
290
+ p.id
291
+ )),
292
+ /* @__PURE__ */ jsxRuntime.jsxs(
293
+ "button",
294
+ {
295
+ type: "button",
296
+ onClick: reset,
297
+ className: "ml-auto inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground",
298
+ "data-testid": tid2("reset"),
299
+ children: [
300
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { className: "size-3" }),
301
+ " Reset"
302
+ ]
303
+ }
304
+ )
305
+ ] });
306
+ const colorRow = (k, label) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
307
+ /* @__PURE__ */ jsxRuntime.jsx(
308
+ "input",
309
+ {
310
+ type: "color",
311
+ value: colors[k] ?? "#000000",
312
+ onChange: (e) => setColor(k, e.target.value),
313
+ className: "size-9 shrink-0 cursor-pointer rounded-md border border-border bg-transparent",
314
+ "aria-label": label,
315
+ "data-testid": tid2(`color-${k}`)
316
+ }
317
+ ),
318
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
319
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-xs text-muted-foreground", children: label }),
320
+ /* @__PURE__ */ jsxRuntime.jsx(
321
+ ui.Input,
322
+ {
323
+ value: colors[k] ?? "",
324
+ onChange: (e) => setColor(k, e.target.value),
325
+ className: "bg-card placeholder:text-muted-foreground/70 focus-visible:border-ring/60 focus-visible:ring-2 focus-visible:ring-ring/50 h-7 font-mono text-xs",
326
+ "data-testid": tid2(`hex-${k}`)
327
+ }
328
+ )
329
+ ] })
330
+ ] }, k);
331
+ const activePairing = matchFontPairing(fonts);
332
+ const Typography = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
333
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
334
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-xs text-muted-foreground", children: "Typeface" }),
335
+ /* @__PURE__ */ jsxRuntime.jsxs(
336
+ "select",
337
+ {
338
+ value: activePairing?.id ?? "",
339
+ onChange: (e) => setFontPairing(e.target.value),
340
+ className: "mt-1 h-8 w-full rounded-md border border-border bg-card px-2 text-xs text-foreground focus-visible:border-ring/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50",
341
+ "data-testid": tid2("font-pairing"),
342
+ children: [
343
+ !activePairing && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Custom" }),
344
+ FONT_PAIRINGS.map((pairing) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: pairing.id, children: pairing.label }, pairing.id))
345
+ ]
346
+ }
347
+ ),
348
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-[11px] text-muted-foreground", children: activePairing?.description ?? "Hand-edited font stacks." })
349
+ ] }),
350
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
351
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-xs text-muted-foreground", children: "Display font stack" }),
352
+ /* @__PURE__ */ jsxRuntime.jsx(
353
+ ui.Input,
354
+ {
355
+ value: fonts.display,
356
+ onChange: (e) => setFont("display", e.target.value),
357
+ className: "bg-card placeholder:text-muted-foreground/70 focus-visible:border-ring/60 focus-visible:ring-2 focus-visible:ring-ring/50 mt-1 font-mono text-xs",
358
+ "data-testid": tid2("font-display")
359
+ }
360
+ )
361
+ ] }),
362
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
363
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-xs text-muted-foreground", children: "Body font stack" }),
364
+ /* @__PURE__ */ jsxRuntime.jsx(
365
+ ui.Input,
366
+ {
367
+ value: fonts.sans,
368
+ onChange: (e) => setFont("sans", e.target.value),
369
+ className: "bg-card placeholder:text-muted-foreground/70 focus-visible:border-ring/60 focus-visible:ring-2 focus-visible:ring-ring/50 mt-1 font-mono text-xs",
370
+ "data-testid": tid2("font-sans")
371
+ }
372
+ )
373
+ ] }),
374
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
375
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "block text-xs text-muted-foreground", children: [
376
+ "Base radius \u2014 ",
377
+ radius,
378
+ "rem"
379
+ ] }),
380
+ /* @__PURE__ */ jsxRuntime.jsx(
381
+ "input",
382
+ {
383
+ type: "range",
384
+ min: 0,
385
+ max: 1.5,
386
+ step: 0.125,
387
+ value: radius,
388
+ onChange: (e) => setRadius(Number(e.target.value)),
389
+ className: "mt-2 w-full accent-[color:var(--color-primary)]",
390
+ "data-testid": tid2("radius")
391
+ }
392
+ )
393
+ ] })
394
+ ] });
395
+ const Preview = /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { variant: "elevated", className: "rounded-xl", "data-testid": tid2("preview"), children: [
396
+ /* @__PURE__ */ jsxRuntime.jsx(ui.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.CardTitle, { className: "text-base", children: "Live preview" }) }),
397
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.CardContent, { className: "space-y-3", children: [
398
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-[var(--radius)] bg-primary p-4 text-primary-foreground", children: [
399
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-display text-lg font-semibold", children: preview?.title ?? "Preview" }),
400
+ preview?.subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs opacity-80", children: preview.subtitle })
401
+ ] }),
402
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-6 gap-1.5", children: tokens.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
403
+ "div",
404
+ {
405
+ title: t.label,
406
+ className: "aspect-square rounded-md border border-border",
407
+ style: { background: `var(--color-${t.key})` }
408
+ },
409
+ t.key
410
+ )) }),
411
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-[var(--radius)] border border-border bg-[color:var(--color-ai-accent-soft,var(--color-accent))] p-3", children: [
412
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-[color:var(--color-ai-accent,var(--color-primary))]", children: "AI action surface" }),
413
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Themed by the AI-kit accent tokens." })
414
+ ] })
415
+ ] })
416
+ ] });
417
+ const SaveBar = onSave && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 flex items-center gap-3", children: [
418
+ /* @__PURE__ */ jsxRuntime.jsxs(
419
+ "button",
420
+ {
421
+ type: "button",
422
+ onClick: () => void save(),
423
+ disabled: saving,
424
+ className: "inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 disabled:opacity-60",
425
+ "data-testid": tid2("save"),
426
+ children: [
427
+ savedVia ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { className: "size-4" }),
428
+ saving ? "Saving\u2026" : savedVia ? "Saved" : "Save theme"
429
+ ]
430
+ }
431
+ ),
432
+ savedVia && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", "data-testid": tid2("via"), children: [
433
+ "stored via ",
434
+ savedVia
435
+ ] })
436
+ ] });
437
+ if (variant === "drawer") {
438
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col overflow-y-auto bg-card p-5", "data-testid": testIdPrefix, children: [
439
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 flex items-center justify-between", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "font-display text-lg font-semibold text-foreground", children: "Theme" }) }),
440
+ Presets,
441
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-5 mb-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground", children: "Colours" }),
442
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: tokens.map((t) => colorRow(t.key, t.label)) }),
443
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-6 mb-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground", children: "Typography & shape" }),
444
+ Typography,
445
+ SaveBar
446
+ ] });
447
+ }
448
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6 lg:grid-cols-[1fr_320px]", "data-testid": testIdPrefix, children: [
449
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
450
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { variant: "elevated", className: "rounded-xl", children: [
451
+ /* @__PURE__ */ jsxRuntime.jsx(ui.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.CardTitle, { className: "flex items-center gap-2 text-base", children: [
452
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Palette, { className: "size-4 text-[color:var(--color-primary)]" }),
453
+ " Presets"
454
+ ] }) }),
455
+ /* @__PURE__ */ jsxRuntime.jsx(ui.CardContent, { children: Presets })
456
+ ] }),
457
+ groups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(
458
+ ui.Card,
459
+ {
460
+ variant: "elevated",
461
+ className: "rounded-xl",
462
+ "data-testid": tid2(`group-${group}`),
463
+ children: [
464
+ /* @__PURE__ */ jsxRuntime.jsx(ui.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.CardTitle, { className: "text-base", children: groupLabels[group] ?? group }) }),
465
+ /* @__PURE__ */ jsxRuntime.jsx(ui.CardContent, { className: "grid gap-3 sm:grid-cols-2", children: tokens.filter((t) => t.group === group).map((t) => colorRow(t.key, t.label)) })
466
+ ]
467
+ },
468
+ group
469
+ )),
470
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { variant: "elevated", className: "rounded-xl", children: [
471
+ /* @__PURE__ */ jsxRuntime.jsx(ui.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.CardTitle, { className: "text-base", children: "Typography & shape" }) }),
472
+ /* @__PURE__ */ jsxRuntime.jsx(ui.CardContent, { children: Typography })
473
+ ] }),
474
+ SaveBar
475
+ ] }),
476
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lg:sticky lg:top-4 lg:self-start", children: Preview })
477
+ ] });
478
+ }
479
+
480
+ // src/lib/testid.ts
481
+ function tid(...parts) {
482
+ return parts.filter((p) => p !== void 0 && p !== false && p !== "").map(
483
+ (p) => String(p).trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
484
+ ).join("-");
485
+ }
486
+
487
+ // src/lib/format.ts
488
+ function gbp(value, compact = false) {
489
+ return new Intl.NumberFormat("en-GB", {
490
+ style: "currency",
491
+ currency: "GBP",
492
+ maximumFractionDigits: compact && Math.abs(value) >= 1e3 ? 1 : 0,
493
+ notation: compact ? "compact" : "standard"
494
+ }).format(value);
495
+ }
496
+ function initials(name) {
497
+ const parts = name.trim().split(/\s+/);
498
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
499
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
500
+ }
501
+ function parseIso(iso) {
502
+ const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(iso);
503
+ const d = new Date(dateOnly ? `${iso}T00:00:00` : iso);
504
+ if (Number.isNaN(d.getTime())) throw new Error(`Invalid ISO date: ${iso}`);
505
+ return d;
506
+ }
507
+ function fmtDate(iso) {
508
+ try {
509
+ return new Intl.DateTimeFormat("en-GB", {
510
+ day: "numeric",
511
+ month: "short",
512
+ year: "numeric"
513
+ }).format(parseIso(iso));
514
+ } catch {
515
+ return iso;
516
+ }
517
+ }
518
+ function fmtDateTime(iso) {
519
+ try {
520
+ return new Intl.DateTimeFormat("en-GB", {
521
+ day: "numeric",
522
+ month: "short",
523
+ year: "numeric",
524
+ hour: "2-digit",
525
+ minute: "2-digit",
526
+ hour12: false
527
+ }).format(parseIso(iso));
528
+ } catch {
529
+ return iso;
530
+ }
531
+ }
532
+ var RELATIVE_UNITS = [
533
+ ["year", 31536e3],
534
+ ["month", 2592e3],
535
+ ["week", 604800],
536
+ ["day", 86400],
537
+ ["hour", 3600],
538
+ ["minute", 60],
539
+ ["second", 1]
540
+ ];
541
+ function fromNow(iso, from = /* @__PURE__ */ new Date()) {
542
+ try {
543
+ const diffSeconds = (parseIso(iso).getTime() - from.getTime()) / 1e3;
544
+ const abs = Math.abs(diffSeconds);
545
+ const rtf = new Intl.RelativeTimeFormat("en-GB", { numeric: "always" });
546
+ for (const [unit, secs] of RELATIVE_UNITS) {
547
+ if (abs >= secs || unit === "second") {
548
+ const value = Math.round(diffSeconds / secs);
549
+ return rtf.format(value, unit);
550
+ }
551
+ }
552
+ return rtf.format(0, "second");
553
+ } catch {
554
+ return iso;
555
+ }
556
+ }
557
+ function daysUntil(iso, from = /* @__PURE__ */ new Date()) {
558
+ try {
559
+ const target = parseIso(iso);
560
+ const a = Date.UTC(target.getFullYear(), target.getMonth(), target.getDate());
561
+ const b = Date.UTC(from.getFullYear(), from.getMonth(), from.getDate());
562
+ return Math.round((a - b) / 864e5);
563
+ } catch {
564
+ return 0;
565
+ }
566
+ }
567
+ function hrs(n) {
568
+ return `${n.toFixed(1)}h`;
569
+ }
570
+ function pct(n) {
571
+ return `${Math.round(n)}%`;
572
+ }
573
+ function SectionHeading({
574
+ eyebrow,
575
+ title,
576
+ lead,
577
+ align = "left",
578
+ className,
579
+ invert,
580
+ eyebrowClassName
581
+ }) {
582
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("max-w-3xl", align === "center" && "mx-auto text-center", className), children: [
583
+ eyebrow && /* @__PURE__ */ jsxRuntime.jsx(
584
+ "p",
585
+ {
586
+ className: ui.cn(
587
+ "mb-3 text-xs font-semibold uppercase tracking-wider",
588
+ eyebrowClassName ?? (invert ? "text-background/60" : "text-muted-foreground")
589
+ ),
590
+ children: eyebrow
591
+ }
592
+ ),
593
+ /* @__PURE__ */ jsxRuntime.jsx(
594
+ "h2",
595
+ {
596
+ className: ui.cn(
597
+ "font-display text-3xl font-semibold tracking-tight text-balance sm:text-4xl",
598
+ invert ? "text-background" : "text-foreground"
599
+ ),
600
+ children: title
601
+ }
602
+ ),
603
+ lead && /* @__PURE__ */ jsxRuntime.jsx(
604
+ "p",
605
+ {
606
+ className: ui.cn(
607
+ "mt-4 text-lg leading-relaxed",
608
+ invert ? "text-background/70" : "text-muted-foreground"
609
+ ),
610
+ children: lead
611
+ }
612
+ )
613
+ ] });
614
+ }
615
+ var ASSET_REF_PREFIX = "asset:";
616
+ function asAssetRef(docId) {
617
+ return `${ASSET_REF_PREFIX}${docId}`;
618
+ }
619
+ function assetRefId(src) {
620
+ return src.slice(ASSET_REF_PREFIX.length);
621
+ }
622
+ var AssetResolverContext = react.createContext(null);
623
+ function AssetResolverProvider({
624
+ render,
625
+ children
626
+ }) {
627
+ return /* @__PURE__ */ jsxRuntime.jsx(AssetResolverContext.Provider, { value: render, children });
628
+ }
629
+ function useAssetRenderer() {
630
+ return react.useContext(AssetResolverContext);
631
+ }
632
+ function useAssetResolverActive() {
633
+ return useAssetRenderer() !== null;
634
+ }
635
+ function isAssetRef(src) {
636
+ return !!src && src.startsWith(ASSET_REF_PREFIX);
637
+ }
638
+ var ASPECT = {
639
+ "16:9": "aspect-video",
640
+ "4:3": "aspect-[4/3]",
641
+ "1:1": "aspect-square",
642
+ "21:9": "aspect-[21/9]",
643
+ "4:5": "aspect-[4/5]"
644
+ };
645
+ var ROUNDED = {
646
+ none: "rounded-none",
647
+ md: "rounded-md",
648
+ lg: "rounded-xl",
649
+ full: "rounded-full"
650
+ };
651
+ function BlockImage({
652
+ src,
653
+ alt = "",
654
+ aspect = "16:9",
655
+ rounded = "lg",
656
+ framed,
657
+ className,
658
+ testId
659
+ }) {
660
+ const shape = ui.cn(
661
+ ASPECT[aspect] ?? ASPECT["16:9"],
662
+ ROUNDED[rounded] ?? ROUNDED.lg,
663
+ framed && "ring-1 ring-border shadow-xl"
664
+ );
665
+ const AssetRenderer = useAssetRenderer();
666
+ if (AssetRenderer && isAssetRef(src)) {
667
+ return /* @__PURE__ */ jsxRuntime.jsx(
668
+ AssetRenderer,
669
+ {
670
+ docId: assetRefId(src),
671
+ alt,
672
+ className: ui.cn("w-full object-cover", shape, className),
673
+ testId
674
+ }
675
+ );
676
+ }
677
+ if (src && !isAssetRef(src)) {
678
+ return /* @__PURE__ */ jsxRuntime.jsx(
679
+ "img",
680
+ {
681
+ src,
682
+ alt,
683
+ className: ui.cn("w-full object-cover", shape, className),
684
+ "data-testid": testId
685
+ }
686
+ );
687
+ }
688
+ return /* @__PURE__ */ jsxRuntime.jsx(
689
+ "div",
690
+ {
691
+ className: ui.cn(
692
+ "flex w-full items-center justify-center overflow-hidden border border-border",
693
+ "bg-gradient-to-br from-muted via-card to-accent text-muted-foreground",
694
+ shape,
695
+ className
696
+ ),
697
+ "data-testid": testId,
698
+ "aria-label": alt || "Image placeholder",
699
+ role: "img",
700
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImageIcon, { className: "size-8 opacity-60" })
701
+ }
702
+ );
703
+ }
704
+ var TONE_SURFACE = {
705
+ default: "bg-background text-foreground",
706
+ muted: "bg-muted text-foreground",
707
+ ink: "bg-foreground text-background"
708
+ };
709
+ function renderTitle(title, emphasis) {
710
+ if (!emphasis || !title.includes(emphasis)) return title;
711
+ const parts = title.split(emphasis);
712
+ return parts.map((part, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
713
+ part,
714
+ i < parts.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary", children: emphasis })
715
+ ] }, i));
716
+ }
717
+ function Hero({
718
+ eyebrow,
719
+ title,
720
+ emphasis,
721
+ subtitle,
722
+ primaryCta,
723
+ secondaryCta,
724
+ align = "left",
725
+ tone = "default",
726
+ imageUrl,
727
+ backgroundImage,
728
+ overlay = true,
729
+ eyebrowClassName,
730
+ testId
731
+ }) {
732
+ const onDark = Boolean(backgroundImage) || tone === "ink";
733
+ const subtle = onDark ? "text-background/70" : "text-muted-foreground";
734
+ const eyebrowClass = eyebrowClassName ?? (onDark ? "text-background/60" : "text-muted-foreground");
735
+ const centered = align === "center" || Boolean(backgroundImage);
736
+ const heading = renderTitle(title, emphasis);
737
+ const copy = /* @__PURE__ */ jsxRuntime.jsxs(
738
+ "div",
739
+ {
740
+ className: ui.cn(
741
+ "max-w-2xl",
742
+ centered && "mx-auto text-center",
743
+ backgroundImage && "text-background"
744
+ ),
745
+ children: [
746
+ eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: ui.cn("mb-4 text-sm font-semibold uppercase tracking-wider", eyebrowClass), children: eyebrow }),
747
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-4xl font-semibold tracking-tight text-balance sm:text-5xl", children: heading }),
748
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: ui.cn("mt-6 text-lg leading-relaxed", subtle), children: subtitle }),
749
+ (primaryCta?.label || secondaryCta?.label) && /* @__PURE__ */ jsxRuntime.jsxs(
750
+ "div",
751
+ {
752
+ className: ui.cn("mt-8 flex flex-col gap-3 sm:flex-row", centered && "sm:justify-center"),
753
+ children: [
754
+ primaryCta?.label && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { asChild: true, size: "lg", children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: primaryCta.href || "#", children: [
755
+ primaryCta.label,
756
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-4" })
757
+ ] }) }),
758
+ secondaryCta?.label && /* @__PURE__ */ jsxRuntime.jsx(
759
+ ui.Button,
760
+ {
761
+ asChild: true,
762
+ size: "lg",
763
+ variant: "outline",
764
+ className: ui.cn(
765
+ onDark && "border-background/35 bg-transparent text-background hover:bg-background/10 hover:text-background"
766
+ ),
767
+ children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: secondaryCta.href || "#", children: secondaryCta.label })
768
+ }
769
+ )
770
+ ]
771
+ }
772
+ )
773
+ ]
774
+ }
775
+ );
776
+ if (backgroundImage) {
777
+ const bgStyle = {
778
+ backgroundImage: `url("${backgroundImage}")`,
779
+ backgroundSize: "cover",
780
+ backgroundPosition: "center"
781
+ };
782
+ return /* @__PURE__ */ jsxRuntime.jsxs(
783
+ "section",
784
+ {
785
+ className: "relative isolate overflow-hidden",
786
+ style: bgStyle,
787
+ "data-testid": testId,
788
+ "data-bg": testId && `${testId}-bg`,
789
+ children: [
790
+ overlay && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/55", "aria-hidden": true }),
791
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative mx-auto max-w-7xl px-5 py-28 lg:px-8 lg:py-36", children: copy })
792
+ ]
793
+ }
794
+ );
795
+ }
796
+ return /* @__PURE__ */ jsxRuntime.jsx("section", { className: ui.cn(TONE_SURFACE[tone] ?? TONE_SURFACE.default), "data-testid": testId, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-7xl px-5 py-20 lg:px-8 lg:py-28", children: centered ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-12", children: [
797
+ copy,
798
+ imageUrl && /* @__PURE__ */ jsxRuntime.jsx(
799
+ BlockImage,
800
+ {
801
+ src: imageUrl,
802
+ aspect: "16:9",
803
+ rounded: "lg",
804
+ className: "w-full max-w-4xl",
805
+ testId: testId && `${testId}-image`
806
+ }
807
+ )
808
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid items-center gap-12 lg:grid-cols-2", children: [
809
+ copy,
810
+ /* @__PURE__ */ jsxRuntime.jsx(
811
+ BlockImage,
812
+ {
813
+ src: imageUrl,
814
+ aspect: "4:5",
815
+ rounded: "lg",
816
+ testId: testId && `${testId}-image`
817
+ }
818
+ )
819
+ ] }) }) });
820
+ }
821
+ function BlockLink({ href, linkComponent: Link, className, children }) {
822
+ if (!href) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
823
+ if (Link) {
824
+ return /* @__PURE__ */ jsxRuntime.jsx(Link, { href, className, children });
825
+ }
826
+ return /* @__PURE__ */ jsxRuntime.jsx("a", { href, className, children });
827
+ }
828
+ var COLS = {
829
+ "2": "sm:grid-cols-2",
830
+ "3": "sm:grid-cols-2 lg:grid-cols-3"
831
+ };
832
+ function FeatureGrid({ columns = "3", items, linkComponent, testId }) {
833
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("grid gap-5", COLS[columns] ?? COLS["3"]), "data-testid": testId, children: (items ?? []).map((item, i) => {
834
+ const Icon = item.icon;
835
+ return /* @__PURE__ */ jsxRuntime.jsx(
836
+ BlockLink,
837
+ {
838
+ href: item.href,
839
+ linkComponent,
840
+ className: ui.cn("block h-full", item.href && "group"),
841
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
842
+ ui.Card,
843
+ {
844
+ variant: "outline",
845
+ className: ui.cn(
846
+ "h-full p-6",
847
+ item.href && "transition-colors group-hover:border-primary/40"
848
+ ),
849
+ "data-testid": testId && `${testId}-item-${i}`,
850
+ children: [
851
+ Icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-4 inline-flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-5" }) }),
852
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold tracking-tight", children: item.title }),
853
+ item.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: item.description })
854
+ ]
855
+ }
856
+ )
857
+ },
858
+ i
859
+ );
860
+ }) });
861
+ }
862
+ function Testimonial({ quote, author, role, avatarUrl, testId }) {
863
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { variant: "outline", className: "mx-auto max-w-3xl p-8 text-center", "data-testid": testId, children: [
864
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Quote, { className: "mx-auto size-8 text-primary/40", "aria-hidden": true }),
865
+ /* @__PURE__ */ jsxRuntime.jsx("blockquote", { className: "mt-4 text-xl font-medium leading-relaxed text-balance", children: quote }),
866
+ (author || avatarUrl) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 flex items-center justify-center gap-3", children: [
867
+ (avatarUrl || author) && /* @__PURE__ */ jsxRuntime.jsxs(ui.Avatar, { children: [
868
+ avatarUrl && /* @__PURE__ */ jsxRuntime.jsx(ui.AvatarImage, { src: avatarUrl, alt: author ?? "" }),
869
+ /* @__PURE__ */ jsxRuntime.jsx(ui.AvatarFallback, { children: initials(author || "?") })
870
+ ] }),
871
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-left", children: [
872
+ author && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold", children: author }),
873
+ role && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: role })
874
+ ] })
875
+ ] })
876
+ ] });
877
+ }
878
+ var TONE_SURFACE2 = {
879
+ default: "bg-primary text-primary-foreground",
880
+ muted: "bg-muted text-foreground",
881
+ ink: "bg-foreground text-background"
882
+ };
883
+ function CTABanner({
884
+ title,
885
+ subtitle,
886
+ cta,
887
+ tone = "default",
888
+ backgroundImage,
889
+ overlay = true,
890
+ testId
891
+ }) {
892
+ const onImage = Boolean(backgroundImage);
893
+ const subtle = onImage ? "text-white/80" : tone === "default" ? "text-primary-foreground/80" : tone === "ink" ? "text-background/70" : "text-muted-foreground";
894
+ const bgStyle = backgroundImage ? {
895
+ backgroundImage: `url("${backgroundImage}")`,
896
+ backgroundSize: "cover",
897
+ backgroundPosition: "center"
898
+ } : void 0;
899
+ return /* @__PURE__ */ jsxRuntime.jsxs(
900
+ "div",
901
+ {
902
+ className: ui.cn(
903
+ "relative isolate overflow-hidden rounded-2xl px-8 py-14 text-center",
904
+ onImage ? "text-white" : TONE_SURFACE2[tone] ?? TONE_SURFACE2.default
905
+ ),
906
+ style: bgStyle,
907
+ "data-testid": testId,
908
+ children: [
909
+ onImage && overlay && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/55", "aria-hidden": true }),
910
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
911
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance sm:text-4xl", children: title }),
912
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: ui.cn("mx-auto mt-4 max-w-2xl text-lg", subtle), children: subtitle }),
913
+ cta?.label && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsxRuntime.jsx(
914
+ ui.Button,
915
+ {
916
+ asChild: true,
917
+ size: "lg",
918
+ variant: tone === "default" && !onImage ? "secondary" : "default",
919
+ children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: cta.href || "#", children: [
920
+ cta.label,
921
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-4" })
922
+ ] })
923
+ }
924
+ ) })
925
+ ] })
926
+ ]
927
+ }
928
+ );
929
+ }
930
+ function LogoCloud({ items, testId }) {
931
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center justify-center gap-4", "data-testid": testId, children: (items ?? []).map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(
932
+ "span",
933
+ {
934
+ className: "rounded-md border border-border bg-muted px-5 py-2.5 text-sm font-semibold uppercase tracking-wide text-muted-foreground",
935
+ "data-testid": testId && `${testId}-item-${i}`,
936
+ children: item.label
937
+ },
938
+ i
939
+ )) });
940
+ }
941
+ function FAQ({ items, testId }) {
942
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-3xl divide-y divide-border", "data-testid": testId, children: (items ?? []).map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "group py-4", "data-testid": testId && `${testId}-item-${i}`, children: [
943
+ /* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-between gap-4 text-left text-base font-medium", children: [
944
+ item.question,
945
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "size-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" })
946
+ ] }),
947
+ item.answer && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 text-sm leading-relaxed text-muted-foreground", children: item.answer })
948
+ ] }, i)) });
949
+ }
950
+ function RichTextBlock({ content, testId }) {
951
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-3xl", "data-testid": testId, children: /* @__PURE__ */ jsxRuntime.jsx(ui.AiMarkdown, { children: content ?? "" }) });
952
+ }
953
+ var STYLES = {
954
+ info: { wrap: "border-primary/30 bg-primary/5", icon: "text-primary", Icon: lucideReact.Info },
955
+ success: { wrap: "border-success/30 bg-success/10", icon: "text-success", Icon: lucideReact.CheckCircle2 },
956
+ warning: { wrap: "border-warning/40 bg-warning/10", icon: "text-warning", Icon: lucideReact.AlertTriangle },
957
+ danger: {
958
+ wrap: "border-destructive/30 bg-destructive/10",
959
+ icon: "text-destructive",
960
+ Icon: lucideReact.XCircle
961
+ }
962
+ };
963
+ function Callout({ variant = "info", title, body, testId }) {
964
+ const style = STYLES[variant] ?? STYLES.info;
965
+ const Icon = style.Icon;
966
+ return /* @__PURE__ */ jsxRuntime.jsxs(
967
+ "div",
968
+ {
969
+ className: ui.cn("flex gap-3 rounded-lg border p-4 text-foreground", style.wrap),
970
+ "data-testid": testId,
971
+ role: "note",
972
+ children: [
973
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: ui.cn("mt-0.5 size-5 shrink-0", style.icon), "aria-hidden": true }),
974
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
975
+ title && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold", children: title }),
976
+ body && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: body })
977
+ ] })
978
+ ]
979
+ }
980
+ );
981
+ }
982
+ var SIZE = {
983
+ sm: "size-8 text-xs",
984
+ md: "size-10 text-sm",
985
+ lg: "size-14 text-base",
986
+ xl: "size-20 text-xl"
987
+ };
988
+ function AvatarBlock({ src, name = "", size = "md", testId }) {
989
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Avatar, { className: ui.cn(SIZE[size] ?? SIZE.md), "data-testid": testId, children: [
990
+ src && /* @__PURE__ */ jsxRuntime.jsx(ui.AvatarImage, { src, alt: name }),
991
+ /* @__PURE__ */ jsxRuntime.jsx(ui.AvatarFallback, { className: "font-semibold", children: initials(name || "?") })
992
+ ] });
993
+ }
994
+ function Navbar({ brand, links, cta, testId }) {
995
+ return /* @__PURE__ */ jsxRuntime.jsx("header", { className: "border-b border-border bg-background", "data-testid": testId, children: /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "mx-auto flex max-w-7xl items-center justify-between gap-6 px-5 py-4 lg:px-8", children: [
996
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-lg font-semibold tracking-tight", children: brand }),
997
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden items-center gap-6 md:flex", children: (links ?? []).map((link, i) => /* @__PURE__ */ jsxRuntime.jsx(
998
+ "a",
999
+ {
1000
+ href: link.href || "#",
1001
+ className: "text-sm font-medium text-muted-foreground transition-colors hover:text-foreground",
1002
+ "data-testid": testId && `${testId}-link-${i}`,
1003
+ children: link.label
1004
+ },
1005
+ i
1006
+ )) }),
1007
+ cta?.label && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { asChild: true, size: "sm", children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: cta.href || "#", children: cta.label }) })
1008
+ ] }) });
1009
+ }
1010
+ function Footer({ columns, copyright, testId }) {
1011
+ return /* @__PURE__ */ jsxRuntime.jsx("footer", { className: "border-t border-border bg-muted text-foreground", "data-testid": testId, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-7xl px-5 py-14 lg:px-8", children: [
1012
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-8 sm:grid-cols-2 lg:grid-cols-4", children: (columns ?? []).map((col, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId && `${testId}-col-${i}`, children: [
1013
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold tracking-tight", children: col.heading }),
1014
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-4 space-y-2", children: (col.links ?? []).map((link, j) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
1015
+ "a",
1016
+ {
1017
+ href: link.href || "#",
1018
+ className: "text-sm text-muted-foreground transition-colors hover:text-foreground",
1019
+ children: link.label
1020
+ }
1021
+ ) }, j)) })
1022
+ ] }, i)) }),
1023
+ copyright && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-12 border-t border-border pt-6 text-sm text-muted-foreground", children: copyright })
1024
+ ] }) });
1025
+ }
1026
+ function ListBlock({ ordered = false, items, testId }) {
1027
+ const list = items ?? [];
1028
+ const className = ui.cn(
1029
+ "mx-auto max-w-3xl space-y-2 pl-5 text-foreground",
1030
+ ordered ? "list-decimal" : "list-disc"
1031
+ );
1032
+ const content = list.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { className: "leading-relaxed marker:text-muted-foreground", children: item.text }, i));
1033
+ return ordered ? /* @__PURE__ */ jsxRuntime.jsx("ol", { className, "data-testid": testId, children: content }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className, "data-testid": testId, children: content });
1034
+ }
1035
+ var GRID = {
1036
+ 2: "sm:grid-cols-2",
1037
+ 3: "sm:grid-cols-2 lg:grid-cols-3",
1038
+ 4: "sm:grid-cols-2 lg:grid-cols-4"
1039
+ };
1040
+ var GAP = {
1041
+ sm: "gap-3",
1042
+ md: "gap-6",
1043
+ lg: "gap-10"
1044
+ };
1045
+ function Columns({ count = 3, gap = "md", columns, testId }) {
1046
+ return /* @__PURE__ */ jsxRuntime.jsx(
1047
+ "div",
1048
+ {
1049
+ className: ui.cn("grid grid-cols-1", GRID[count] ?? GRID[3], GAP[gap] ?? GAP.md),
1050
+ "data-testid": testId,
1051
+ children: columns.map((col, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": testId && `${testId}-col-${i}`, children: col }, i))
1052
+ }
1053
+ );
1054
+ }
1055
+ var SIZE2 = {
1056
+ sm: "h-4",
1057
+ md: "h-8",
1058
+ lg: "h-16",
1059
+ xl: "h-24"
1060
+ };
1061
+ function Spacer({ size = "md", testId }) {
1062
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("w-full", SIZE2[size] ?? SIZE2.md), "data-testid": testId, "aria-hidden": true });
1063
+ }
1064
+ var WIDTH = {
1065
+ narrow: "max-w-3xl",
1066
+ default: "max-w-5xl",
1067
+ wide: "max-w-7xl",
1068
+ full: "max-w-none"
1069
+ };
1070
+ var PADDING = {
1071
+ none: "px-0 py-0",
1072
+ sm: "px-4 py-6",
1073
+ md: "px-5 py-12 lg:px-8",
1074
+ lg: "px-5 py-20 lg:px-8"
1075
+ };
1076
+ function Container({ width = "default", padding = "md", children, testId }) {
1077
+ return /* @__PURE__ */ jsxRuntime.jsx(
1078
+ "div",
1079
+ {
1080
+ className: ui.cn(
1081
+ "mx-auto w-full",
1082
+ WIDTH[width] ?? WIDTH.default,
1083
+ PADDING[padding] ?? PADDING.md
1084
+ ),
1085
+ "data-testid": testId,
1086
+ children
1087
+ }
1088
+ );
1089
+ }
1090
+ function PricingTable({ plans, testId }) {
1091
+ return /* @__PURE__ */ jsxRuntime.jsx(
1092
+ "div",
1093
+ {
1094
+ className: ui.cn("grid gap-6", (plans?.length ?? 0) >= 3 ? "md:grid-cols-3" : "sm:grid-cols-2"),
1095
+ "data-testid": testId,
1096
+ children: (plans ?? []).map((plan, i) => /* @__PURE__ */ jsxRuntime.jsxs(
1097
+ ui.Card,
1098
+ {
1099
+ variant: plan.featured ? "elevated" : "outline",
1100
+ className: ui.cn("flex h-full flex-col p-6", plan.featured && "ring-2 ring-primary"),
1101
+ "data-testid": testId && `${testId}-item-${i}`,
1102
+ children: [
1103
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold tracking-tight", children: plan.name }),
1104
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-baseline gap-1", children: [
1105
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-3xl font-semibold tracking-tight", children: plan.price }),
1106
+ plan.period && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-muted-foreground", children: [
1107
+ "/",
1108
+ plan.period
1109
+ ] })
1110
+ ] }),
1111
+ plan.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: plan.description }),
1112
+ plan.features && plan.features.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-5 flex-1 space-y-2", children: plan.features.map((f, j) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-start gap-2 text-sm", children: [
1113
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "mt-0.5 size-4 shrink-0 text-primary" }),
1114
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: f })
1115
+ ] }, j)) }),
1116
+ plan.cta?.label && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { asChild: true, className: "mt-6 w-full", variant: plan.featured ? "default" : "outline", children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: plan.cta.href || "#", children: plan.cta.label }) })
1117
+ ]
1118
+ },
1119
+ i
1120
+ ))
1121
+ }
1122
+ );
1123
+ }
1124
+ function Tabs({ items, testId }) {
1125
+ const [active, setActive] = react.useState(0);
1126
+ const list = items ?? [];
1127
+ const current = list[Math.min(active, Math.max(list.length - 1, 0))];
1128
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId, children: [
1129
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 border-b border-border", role: "tablist", children: list.map((t, i) => {
1130
+ const selected = i === active;
1131
+ return /* @__PURE__ */ jsxRuntime.jsx(
1132
+ "button",
1133
+ {
1134
+ type: "button",
1135
+ role: "tab",
1136
+ "aria-selected": selected,
1137
+ onClick: () => setActive(i),
1138
+ className: ui.cn(
1139
+ "-mb-px border-b-2 px-4 py-2 text-sm font-medium transition-colors",
1140
+ selected ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
1141
+ ),
1142
+ "data-testid": testId && `${testId}-tab-${i}`,
1143
+ children: t.label
1144
+ },
1145
+ i
1146
+ );
1147
+ }) }),
1148
+ current && /* @__PURE__ */ jsxRuntime.jsx(
1149
+ "div",
1150
+ {
1151
+ role: "tabpanel",
1152
+ className: "pt-5 text-sm leading-relaxed text-muted-foreground",
1153
+ "data-testid": testId && `${testId}-panel`,
1154
+ children: current.content
1155
+ }
1156
+ )
1157
+ ] });
1158
+ }
1159
+ function Accordion({ items, allowMultiple = false, testId }) {
1160
+ const [open, setOpen] = react.useState([]);
1161
+ const toggle = (i) => setOpen(
1162
+ (prev) => prev.includes(i) ? prev.filter((n) => n !== i) : allowMultiple ? [...prev, i] : [i]
1163
+ );
1164
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border border-y border-border", "data-testid": testId, children: (items ?? []).map((item, i) => {
1165
+ const isOpen = open.includes(i);
1166
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId && `${testId}-item-${i}`, children: [
1167
+ /* @__PURE__ */ jsxRuntime.jsxs(
1168
+ "button",
1169
+ {
1170
+ type: "button",
1171
+ "aria-expanded": isOpen,
1172
+ onClick: () => toggle(i),
1173
+ className: "flex w-full items-center justify-between gap-4 py-4 text-left text-base font-medium",
1174
+ "data-testid": testId && `${testId}-trigger-${i}`,
1175
+ children: [
1176
+ item.title,
1177
+ /* @__PURE__ */ jsxRuntime.jsx(
1178
+ lucideReact.ChevronDown,
1179
+ {
1180
+ className: ui.cn("size-4 shrink-0 transition-transform", isOpen && "rotate-180")
1181
+ }
1182
+ )
1183
+ ]
1184
+ }
1185
+ ),
1186
+ isOpen && /* @__PURE__ */ jsxRuntime.jsx(
1187
+ "p",
1188
+ {
1189
+ className: "pb-4 text-sm leading-relaxed text-muted-foreground",
1190
+ "data-testid": testId && `${testId}-content-${i}`,
1191
+ children: item.content
1192
+ }
1193
+ )
1194
+ ] }, i);
1195
+ }) });
1196
+ }
1197
+ var COLS2 = {
1198
+ "2": "grid-cols-2",
1199
+ "3": "grid-cols-2 sm:grid-cols-3",
1200
+ "4": "grid-cols-2 sm:grid-cols-4"
1201
+ };
1202
+ function Gallery({
1203
+ images,
1204
+ columns = "3",
1205
+ aspect = "1:1",
1206
+ rounded = "lg",
1207
+ testId
1208
+ }) {
1209
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("grid gap-4", COLS2[columns] ?? COLS2["3"]), "data-testid": testId, children: (images ?? []).map((img, i) => /* @__PURE__ */ jsxRuntime.jsxs("figure", { "data-testid": testId && `${testId}-item-${i}`, children: [
1210
+ /* @__PURE__ */ jsxRuntime.jsx(
1211
+ BlockImage,
1212
+ {
1213
+ src: img.src,
1214
+ alt: img.alt,
1215
+ aspect,
1216
+ rounded,
1217
+ testId: testId && `${testId}-item-${i}-image`
1218
+ }
1219
+ ),
1220
+ img.caption && /* @__PURE__ */ jsxRuntime.jsx("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: img.caption })
1221
+ ] }, i)) });
1222
+ }
1223
+ var ASPECT2 = {
1224
+ "16:9": "aspect-video",
1225
+ "4:3": "aspect-[4/3]",
1226
+ "1:1": "aspect-square"
1227
+ };
1228
+ function toEmbedUrl(url) {
1229
+ if (!url) return null;
1230
+ const yt = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]{11})/);
1231
+ if (yt) return `https://www.youtube.com/embed/${yt[1]}`;
1232
+ const vimeo = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
1233
+ if (vimeo) return `https://player.vimeo.com/video/${vimeo[1]}`;
1234
+ const loom = url.match(/loom\.com\/(?:share|embed)\/([\w-]+)/);
1235
+ if (loom) return `https://www.loom.com/embed/${loom[1]}`;
1236
+ return null;
1237
+ }
1238
+ function VideoEmbed({
1239
+ url,
1240
+ title,
1241
+ aspect = "16:9",
1242
+ rounded = true,
1243
+ testId
1244
+ }) {
1245
+ const embed = toEmbedUrl(url);
1246
+ const frame = ui.cn(
1247
+ "w-full overflow-hidden border border-border bg-muted",
1248
+ ASPECT2[aspect] ?? ASPECT2["16:9"],
1249
+ rounded && "rounded-lg"
1250
+ );
1251
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frame, "data-testid": testId, children: embed ? /* @__PURE__ */ jsxRuntime.jsx(
1252
+ "iframe",
1253
+ {
1254
+ src: embed,
1255
+ title: title || "Embedded video",
1256
+ className: "size-full",
1257
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
1258
+ allowFullScreen: true,
1259
+ "data-testid": testId && `${testId}-iframe`
1260
+ }
1261
+ ) : url ? /* @__PURE__ */ jsxRuntime.jsx("video", { src: url, controls: true, className: "size-full", "data-testid": testId && `${testId}-video` }) : null });
1262
+ }
1263
+ function ContactForm({
1264
+ fields,
1265
+ submitLabel = "Send",
1266
+ action,
1267
+ successMessage = "Thanks \u2014 we\u2019ll be in touch shortly.",
1268
+ testId
1269
+ }) {
1270
+ const [sent, setSent] = react.useState(false);
1271
+ const list = fields ?? [];
1272
+ const onSubmit = (e) => {
1273
+ if (!action) {
1274
+ e.preventDefault();
1275
+ setSent(true);
1276
+ }
1277
+ };
1278
+ if (sent) {
1279
+ return /* @__PURE__ */ jsxRuntime.jsx(
1280
+ "p",
1281
+ {
1282
+ className: "rounded-lg border border-border bg-muted p-6 text-center text-sm text-foreground",
1283
+ role: "status",
1284
+ "data-testid": testId && `${testId}-success`,
1285
+ children: successMessage
1286
+ }
1287
+ );
1288
+ }
1289
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1290
+ "form",
1291
+ {
1292
+ action,
1293
+ method: action ? "post" : void 0,
1294
+ onSubmit,
1295
+ className: "mx-auto flex max-w-xl flex-col gap-4",
1296
+ "data-testid": testId,
1297
+ children: [
1298
+ list.map((f, i) => {
1299
+ const inputClass = "w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground outline-none focus:border-primary";
1300
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex flex-col gap-1.5 text-left", children: [
1301
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: f.label }),
1302
+ f.type === "textarea" ? /* @__PURE__ */ jsxRuntime.jsx(
1303
+ "textarea",
1304
+ {
1305
+ name: f.name,
1306
+ required: f.required,
1307
+ rows: 4,
1308
+ className: inputClass,
1309
+ "data-testid": testId && `${testId}-field-${f.name}`
1310
+ }
1311
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1312
+ "input",
1313
+ {
1314
+ name: f.name,
1315
+ type: f.type || "text",
1316
+ required: f.required,
1317
+ className: inputClass,
1318
+ "data-testid": testId && `${testId}-field-${f.name}`
1319
+ }
1320
+ )
1321
+ ] }, i);
1322
+ }),
1323
+ /* @__PURE__ */ jsxRuntime.jsx(
1324
+ ui.Button,
1325
+ {
1326
+ type: "submit",
1327
+ className: ui.cn("mt-1 self-start"),
1328
+ "data-testid": testId && `${testId}-submit`,
1329
+ children: submitLabel
1330
+ }
1331
+ )
1332
+ ]
1333
+ }
1334
+ );
1335
+ }
1336
+ var TONE = {
1337
+ primary: "bg-primary text-primary-foreground",
1338
+ muted: "bg-muted text-foreground",
1339
+ ink: "bg-foreground text-background"
1340
+ };
1341
+ function Banner({ text, cta, tone = "primary", testId }) {
1342
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1343
+ "div",
1344
+ {
1345
+ className: ui.cn("w-full px-5 py-2.5 text-center text-sm", TONE[tone] ?? TONE.primary),
1346
+ "data-testid": testId,
1347
+ children: [
1348
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: text }),
1349
+ cta?.label && /* @__PURE__ */ jsxRuntime.jsxs(
1350
+ "a",
1351
+ {
1352
+ href: cta.href || "#",
1353
+ className: "ml-2 inline-flex items-center gap-1 font-semibold underline underline-offset-4",
1354
+ "data-testid": testId && `${testId}-cta`,
1355
+ children: [
1356
+ cta.label,
1357
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-3.5" })
1358
+ ]
1359
+ }
1360
+ )
1361
+ ]
1362
+ }
1363
+ );
1364
+ }
1365
+ function MediaText({
1366
+ eyebrow,
1367
+ title,
1368
+ body,
1369
+ imageUrl,
1370
+ mediaSide = "left",
1371
+ cta,
1372
+ testId
1373
+ }) {
1374
+ const media = /* @__PURE__ */ jsxRuntime.jsx(BlockImage, { src: imageUrl, aspect: "4:3", rounded: "lg", testId: testId && `${testId}-image` });
1375
+ const copy = /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1376
+ eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: eyebrow }),
1377
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance", children: title }),
1378
+ body && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-4 text-lg leading-relaxed text-muted-foreground", children: body }),
1379
+ cta?.label && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { asChild: true, className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: cta.href || "#", children: [
1380
+ cta.label,
1381
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "size-4" })
1382
+ ] }) })
1383
+ ] });
1384
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid items-center gap-10 lg:grid-cols-2", "data-testid": testId, children: mediaSide === "right" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1385
+ copy,
1386
+ media
1387
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1388
+ media,
1389
+ copy
1390
+ ] }) });
1391
+ }
1392
+ var COLS3 = {
1393
+ "2": "sm:grid-cols-2",
1394
+ "3": "sm:grid-cols-2 lg:grid-cols-3",
1395
+ "4": "sm:grid-cols-2 lg:grid-cols-4"
1396
+ };
1397
+ function TeamGrid({ members, columns = "3", linkComponent, testId }) {
1398
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("grid gap-6", COLS3[columns] ?? COLS3["3"]), "data-testid": testId, children: (members ?? []).map((m, i) => /* @__PURE__ */ jsxRuntime.jsx(
1399
+ BlockLink,
1400
+ {
1401
+ href: m.href,
1402
+ linkComponent,
1403
+ className: ui.cn("block h-full", m.href && "group"),
1404
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1405
+ ui.Card,
1406
+ {
1407
+ variant: "outline",
1408
+ className: ui.cn(
1409
+ "flex h-full flex-col items-center p-6 text-center",
1410
+ m.href && "transition-colors group-hover:border-primary/40"
1411
+ ),
1412
+ "data-testid": testId && `${testId}-item-${i}`,
1413
+ children: [
1414
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Avatar, { className: "size-20", children: [
1415
+ m.avatarUrl && /* @__PURE__ */ jsxRuntime.jsx(ui.AvatarImage, { src: m.avatarUrl, alt: m.name }),
1416
+ /* @__PURE__ */ jsxRuntime.jsx(ui.AvatarFallback, { children: initials(m.name || "?") })
1417
+ ] }),
1418
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mt-4 text-base font-semibold tracking-tight", children: m.name }),
1419
+ m.role && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-primary", children: m.role }),
1420
+ m.bio && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: m.bio })
1421
+ ]
1422
+ }
1423
+ )
1424
+ },
1425
+ i
1426
+ )) });
1427
+ }
1428
+ function Steps({ steps, variant = "numbered", testId }) {
1429
+ const list = steps ?? [];
1430
+ if (variant === "timeline") {
1431
+ return /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "relative ml-3 border-l border-border", "data-testid": testId, children: list.map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "mb-8 ml-6 last:mb-0", "data-testid": testId && `${testId}-item-${i}`, children: [
1432
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -left-3 flex size-6 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground", children: i + 1 }),
1433
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-semibold tracking-tight", children: s.title }),
1434
+ s.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm leading-relaxed text-muted-foreground", children: s.description })
1435
+ ] }, i)) });
1436
+ }
1437
+ return /* @__PURE__ */ jsxRuntime.jsx(
1438
+ "div",
1439
+ {
1440
+ className: ui.cn("grid gap-6", list.length >= 4 ? "md:grid-cols-4" : "sm:grid-cols-3"),
1441
+ "data-testid": testId,
1442
+ children: list.map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId && `${testId}-item-${i}`, children: [
1443
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex size-9 items-center justify-center rounded-full bg-primary/10 text-sm font-semibold text-primary", children: i + 1 }),
1444
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mt-4 text-base font-semibold tracking-tight", children: s.title }),
1445
+ s.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: s.description })
1446
+ ] }, i))
1447
+ }
1448
+ );
1449
+ }
1450
+ var COLS4 = {
1451
+ "2": "sm:grid-cols-2",
1452
+ "3": "sm:grid-cols-2 lg:grid-cols-3"
1453
+ };
1454
+ function TestimonialGrid({ items, columns = "3", testId }) {
1455
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("grid gap-6", COLS4[columns] ?? COLS4["3"]), "data-testid": testId, children: (items ?? []).map((t, i) => /* @__PURE__ */ jsxRuntime.jsxs(
1456
+ ui.Card,
1457
+ {
1458
+ variant: "outline",
1459
+ className: "flex h-full flex-col p-6",
1460
+ "data-testid": testId && `${testId}-item-${i}`,
1461
+ children: [
1462
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Quote, { className: "size-6 text-primary/40", "aria-hidden": true }),
1463
+ /* @__PURE__ */ jsxRuntime.jsx("blockquote", { className: "mt-3 flex-1 text-base leading-relaxed", children: t.quote }),
1464
+ (t.author || t.avatarUrl) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 flex items-center gap-3", children: [
1465
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Avatar, { className: "size-9", children: [
1466
+ t.avatarUrl && /* @__PURE__ */ jsxRuntime.jsx(ui.AvatarImage, { src: t.avatarUrl, alt: t.author ?? "" }),
1467
+ /* @__PURE__ */ jsxRuntime.jsx(ui.AvatarFallback, { children: initials(t.author || "?") })
1468
+ ] }),
1469
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1470
+ t.author && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold", children: t.author }),
1471
+ t.role && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t.role })
1472
+ ] })
1473
+ ] })
1474
+ ]
1475
+ },
1476
+ i
1477
+ )) });
1478
+ }
1479
+
1480
+ exports.ASSET_REF_PREFIX = ASSET_REF_PREFIX;
1481
+ exports.Accordion = Accordion;
1482
+ exports.AssetResolverProvider = AssetResolverProvider;
1483
+ exports.AvatarBlock = AvatarBlock;
1484
+ exports.Banner = Banner;
1485
+ exports.BlockImage = BlockImage;
1486
+ exports.BlockLink = BlockLink;
1487
+ exports.CTABanner = CTABanner;
1488
+ exports.Callout = Callout;
1489
+ exports.Columns = Columns;
1490
+ exports.ContactForm = ContactForm;
1491
+ exports.Container = Container;
1492
+ exports.FAQ = FAQ;
1493
+ exports.FONT_PAIRINGS = FONT_PAIRINGS;
1494
+ exports.FeatureGrid = FeatureGrid;
1495
+ exports.Footer = Footer;
1496
+ exports.Gallery = Gallery;
1497
+ exports.Hero = Hero;
1498
+ exports.ListBlock = ListBlock;
1499
+ exports.LogoCloud = LogoCloud;
1500
+ exports.MediaText = MediaText;
1501
+ exports.Navbar = Navbar;
1502
+ exports.PricingTable = PricingTable;
1503
+ exports.RichTextBlock = RichTextBlock;
1504
+ exports.SectionHeading = SectionHeading;
1505
+ exports.Spacer = Spacer;
1506
+ exports.Steps = Steps;
1507
+ exports.THEME_STYLE_ID = THEME_STYLE_ID;
1508
+ exports.Tabs = Tabs;
1509
+ exports.TeamGrid = TeamGrid;
1510
+ exports.Testimonial = Testimonial;
1511
+ exports.TestimonialGrid = TestimonialGrid;
1512
+ exports.ThemeEditor = ThemeEditor;
1513
+ exports.VideoEmbed = VideoEmbed;
1514
+ exports.applyFontVars = applyFontVars;
1515
+ exports.applyStoredTheme = applyStoredTheme;
1516
+ exports.applyThemeVars = applyThemeVars;
1517
+ exports.asAssetRef = asAssetRef;
1518
+ exports.assetRefId = assetRefId;
1519
+ exports.clearFontVars = clearFontVars;
1520
+ exports.clearThemeVars = clearThemeVars;
1521
+ exports.createThemeStore = createThemeStore;
1522
+ exports.daysUntil = daysUntil;
1523
+ exports.fmtDate = fmtDate;
1524
+ exports.fmtDateTime = fmtDateTime;
1525
+ exports.fontPackages = fontPackages;
1526
+ exports.fromNow = fromNow;
1527
+ exports.gbp = gbp;
1528
+ exports.getFontPairing = getFontPairing;
1529
+ exports.hrs = hrs;
1530
+ exports.initials = initials;
1531
+ exports.isAssetRef = isAssetRef;
1532
+ exports.matchFontPairing = matchFontPairing;
1533
+ exports.pct = pct;
1534
+ exports.tid = tid;
1535
+ exports.toEmbedUrl = toEmbedUrl;
1536
+ exports.toHslTriplet = toHslTriplet;
1537
+ exports.useAssetRenderer = useAssetRenderer;
1538
+ exports.useAssetResolverActive = useAssetResolverActive;
1539
+ //# sourceMappingURL=chunk-AHWIZJ4M.cjs.map
1540
+ //# sourceMappingURL=chunk-AHWIZJ4M.cjs.map