@arbidocs/blocks 0.3.119 → 0.3.120

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/index.js CHANGED
@@ -1,15 +1,17 @@
1
- import { create } from 'zustand';
2
- import { persist } from 'zustand/middleware';
3
- import { createContext, useState, useEffect, useCallback, useMemo, useContext, useRef } from 'react';
4
- import { MessageSquare, Cpu, Code, Cloud, Layers, Target, Award, MapPin, Phone, Mail, Lock, Globe, Heart, Star, Rocket, Shield, Zap, Sparkles, Gavel, Scale, Bell, Activity, BarChart3, DollarSign, TrendingUp, Info, AlertCircle, CheckCircle, Clock, Calendar, Briefcase, Users, Folder, FileText, RotateCcw, Palette, Search, ArrowUpRight, ArrowDownRight, ImageIcon, ArrowRight, Quote, ChevronDown, XCircle, AlertTriangle, CheckCircle2, Check, X, Menu, ArrowLeft, Loader2, ChevronUp, BrainCircuit, Bot, LayoutGrid, FileJson, Save, Download, Crop, ImagePlus, Upload, Link2, MessagesSquare } from 'lucide-react';
5
- import { Input, Card, CardHeader, CardTitle, CardContent, useArbi, ArbiProvider, useAiTask, useWorkspaceDocs as useWorkspaceDocs$1, useSemanticSearch as useSemanticSearch$1, cn, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Button, Avatar, AvatarImage, AvatarFallback, AiMarkdown, NotificationBell, UserMenu, Badge, Switch, useConfigs, useAgents, Checkbox, Tabs as Tabs$1, TabsList, TabsTrigger, TabsContent, Separator, ArbiWebSocketProvider, useDocuments, useThumbnails, ImageCropModal, DEFAULT_ASPECT_OPTIONS, useConversations } from '@arbidocs/react';
6
- export { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, redlineStats, textToArtifact, toRedlineMarkdown } from '@arbidocs/react';
7
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
1
+ import { tid, initials, fromNow, fmtDate, assetRefId, applyStoredTheme, AssetResolverProvider, ThemeEditor, applyThemeVars, SectionHeading, PricingTable, Tabs as Tabs$1, Accordion, Gallery, VideoEmbed, ContactForm, Banner, MediaText, TeamGrid, Steps, TestimonialGrid, Columns, Spacer, Container, Hero, FeatureGrid, Testimonial, CTABanner, LogoCloud, FAQ, RichTextBlock, Callout, BlockImage, AvatarBlock, Navbar, Footer, ListBlock, isAssetRef, asAssetRef } from './chunk-L6GG4KN2.js';
2
+ export { ASSET_REF_PREFIX, Accordion, AssetResolverProvider, AvatarBlock, Banner, BlockImage, BlockLink, CTABanner, Callout, Columns, ContactForm, Container, FAQ, FONT_PAIRINGS, FeatureGrid, Footer, Gallery, Hero, ListBlock, LogoCloud, MediaText, Navbar, PricingTable, RichTextBlock, SectionHeading, Spacer, Steps, THEME_STYLE_ID, Tabs, TeamGrid, Testimonial, TestimonialGrid, ThemeEditor, VideoEmbed, applyFontVars, applyStoredTheme, applyThemeVars, asAssetRef, assetRefId, clearFontVars, clearThemeVars, createThemeStore, daysUntil, fmtDate, fmtDateTime, fontPackages, fromNow, gbp, getFontPairing, hrs, initials, isAssetRef, matchFontPairing, pct, tid, toEmbedUrl, toHslTriplet, useAssetRenderer, useAssetResolverActive } from './chunk-L6GG4KN2.js';
3
+ import { createContext, useState, useCallback, useMemo, useContext, useRef, useEffect } from 'react';
4
+ import { useArbi, ArbiProvider, useAiTask, useWorkspaceDocs as useWorkspaceDocs$1, useSemanticSearch as useSemanticSearch$1, cn, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Input, Card, CardHeader, Badge, CardTitle, CardDescription, CardContent, Avatar, AvatarImage, AvatarFallback, CardFooter, Button, TooltipProvider, NotificationBell, UserMenu, useThumbnails, Switch, useConfigs, useAgents, Checkbox, Tabs, TabsList, TabsTrigger, TabsContent, Separator, ArbiWebSocketProvider, useDocuments, ImageCropModal, DEFAULT_ASPECT_OPTIONS, useConversations } from '@arbidocs/react';
5
+ export { DocumentCard, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, redlineStats, textToArtifact, toRedlineMarkdown } from '@arbidocs/react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
7
  import { QueryClient, QueryClientProvider, useQuery, useQueryClient } from '@tanstack/react-query';
9
8
  import { AgGridReact } from 'ag-grid-react';
10
9
  import { themeQuartz, AllCommunityModule } from 'ag-grid-community';
10
+ import { MessageSquare, Cpu, Code, Cloud, Layers, Target, Award, MapPin, Phone, Mail, Lock, Globe, Heart, Star, Rocket, Shield, Zap, Sparkles, Gavel, Scale, Bell, Activity, BarChart3, DollarSign, TrendingUp, Info, AlertCircle, CheckCircle, Clock, Calendar, Briefcase, Users, Folder, FileText, Loader2, UploadCloud, Search, ArrowUpRight, ArrowDownRight, FolderLock, CheckCircle2, ArrowLeftRight, X, Menu, ArrowLeft, Palette, RotateCcw, ChevronUp, ChevronDown, BrainCircuit, Bot, LayoutGrid, FileJson, Download, Crop, ImagePlus, Upload, Link2, MessagesSquare } from 'lucide-react';
11
11
  import { useLocation, Link, useParams, NavLink } from 'react-router-dom';
12
12
  import { Puck, Render } from '@measured/puck';
13
+ import { create } from 'zustand';
14
+ import { persist } from 'zustand/middleware';
13
15
 
14
16
  // src/prompts.ts
15
17
  function matterToContext(m) {
@@ -192,380 +194,23 @@ Lead with why the firm is the right choice, reference relevant experience at a t
192
194
  ];
193
195
  return { prompts, WORKFLOW_ACTIONS };
194
196
  }
195
-
196
- // src/theme/tokens.ts
197
- var THEME_STYLE_ID = "arbi-theme-vars";
198
- var TRIPLET_RE = /^-?\d+(?:\.\d+)?\s+-?\d+(?:\.\d+)?%\s+-?\d+(?:\.\d+)?%$/;
199
- function parseRgb(color) {
200
- if (color.startsWith("#")) {
201
- let hex = color.slice(1);
202
- if (hex.length === 3) {
203
- hex = hex.split("").map((ch) => ch + ch).join("");
204
- }
205
- if (hex.length === 6 && /^[0-9a-fA-F]{6}$/.test(hex)) {
206
- return {
207
- r: parseInt(hex.slice(0, 2), 16),
208
- g: parseInt(hex.slice(2, 4), 16),
209
- b: parseInt(hex.slice(4, 6), 16)
210
- };
211
- }
197
+ var ConnectionContext = createContext(null);
198
+ var WORKSPACE_STORAGE_PREFIX = "arbi:selectedWorkspace";
199
+ var workspaceStorageKey = (apiUrl) => `${WORKSPACE_STORAGE_PREFIX}:${apiUrl || "demo"}`;
200
+ function readPersistedWorkspace(apiUrl) {
201
+ try {
202
+ return window.localStorage.getItem(workspaceStorageKey(apiUrl));
203
+ } catch {
212
204
  return null;
213
205
  }
214
- const m = color.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);
215
- if (m) return { r: Number(m[1]), g: Number(m[2]), b: Number(m[3]) };
216
- return null;
217
- }
218
- function rgbToHslTriplet(r, g, b) {
219
- const rn = r / 255;
220
- const gn = g / 255;
221
- const bn = b / 255;
222
- const max = Math.max(rn, gn, bn);
223
- const min = Math.min(rn, gn, bn);
224
- const l = (max + min) / 2;
225
- const d = max - min;
226
- let h = 0;
227
- let s = 0;
228
- if (d !== 0) {
229
- s = d / (1 - Math.abs(2 * l - 1));
230
- switch (max) {
231
- case rn:
232
- h = 60 * ((gn - bn) / d % 6);
233
- break;
234
- case gn:
235
- h = 60 * ((bn - rn) / d + 2);
236
- break;
237
- default:
238
- h = 60 * ((rn - gn) / d + 4);
239
- break;
240
- }
241
- }
242
- if (h < 0) h += 360;
243
- return `${Math.round(h)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
244
- }
245
- function toHslTriplet(color) {
246
- const c = color.trim();
247
- if (TRIPLET_RE.test(c)) return c;
248
- const rgb = parseRgb(c);
249
- if (!rgb) return c;
250
- return rgbToHslTriplet(rgb.r, rgb.g, rgb.b);
251
- }
252
- function renderTripletBlock(colors) {
253
- return Object.entries(colors).map(([key, value]) => ` --${key}: ${toHslTriplet(value)};`).join("\n");
254
- }
255
- function emitCssVars(theme, dark) {
256
- if (typeof document === "undefined") return;
257
- const meta = [
258
- ` --font-display: ${theme.fonts.display};`,
259
- ` --font-sans: ${theme.fonts.sans};`,
260
- ` --radius: ${theme.radius}rem;`
261
- ].join("\n");
262
- let css = `:root {
263
- ${renderTripletBlock(theme.colors)}
264
- ${meta}
265
- }`;
266
- if (dark) {
267
- css += `
268
- :root.dark {
269
- ${renderTripletBlock(dark)}
270
- }`;
271
- }
272
- let style = document.getElementById(THEME_STYLE_ID);
273
- if (!style) {
274
- style = document.createElement("style");
275
- style.id = THEME_STYLE_ID;
276
- document.head.appendChild(style);
277
- }
278
- style.textContent = css;
279
- }
280
- function removeCssVars() {
281
- if (typeof document === "undefined") return;
282
- document.getElementById(THEME_STYLE_ID)?.remove();
283
- }
284
- function normalizeOptions(optionsOrRoot) {
285
- if (typeof HTMLElement !== "undefined" && optionsOrRoot instanceof HTMLElement) {
286
- return { root: optionsOrRoot };
287
- }
288
- return optionsOrRoot;
289
- }
290
- function applyThemeVars(theme, optionsOrRoot = {}) {
291
- const { mode = "flat", dark, root = document.documentElement } = normalizeOptions(optionsOrRoot);
292
- if (mode === "cssVars") {
293
- emitCssVars(theme, dark);
294
- return;
295
- }
296
- for (const [key, value] of Object.entries(theme.colors)) {
297
- root.style.setProperty(`--color-${key}`, value);
298
- }
299
- root.style.setProperty("--font-display", theme.fonts.display);
300
- root.style.setProperty("--font-sans", theme.fonts.sans);
301
- root.style.setProperty("--radius", `${theme.radius}rem`);
302
- }
303
- function clearThemeVars(theme, optionsOrRoot = {}) {
304
- const { mode = "flat", root = document.documentElement } = normalizeOptions(optionsOrRoot);
305
- if (mode === "cssVars") {
306
- removeCssVars();
307
- return;
308
- }
309
- for (const key of Object.keys(theme.colors)) {
310
- root.style.removeProperty(`--color-${key}`);
311
- }
312
- root.style.removeProperty("--font-display");
313
- root.style.removeProperty("--font-sans");
314
- root.style.removeProperty("--radius");
315
- }
316
- function createThemeStore(config) {
317
- const { defaultTheme, presets, storageKey } = config;
318
- const useStore = create()(
319
- persist(
320
- (set, get) => ({
321
- ...defaultTheme,
322
- activePresetId: presets[0]?.id ?? null,
323
- setColor: (key, value) => set((s) => ({ colors: { ...s.colors, [key]: value }, activePresetId: null })),
324
- setFont: (which, value) => set((s) => ({ fonts: { ...s.fonts, [which]: value }, activePresetId: null })),
325
- setRadius: (radius) => set({ radius, activePresetId: null }),
326
- applyPreset: (presetId) => {
327
- const preset = presets.find((p) => p.id === presetId);
328
- if (!preset) return;
329
- set({
330
- colors: { ...preset.tokens.colors },
331
- fonts: { ...preset.tokens.fonts },
332
- radius: preset.tokens.radius,
333
- activePresetId: presetId
334
- });
335
- },
336
- reset: () => set({
337
- colors: { ...defaultTheme.colors },
338
- fonts: { ...defaultTheme.fonts },
339
- radius: defaultTheme.radius,
340
- activePresetId: presets[0]?.id ?? null
341
- }),
342
- snapshot: () => {
343
- const { colors, fonts, radius } = get();
344
- return { colors, fonts, radius };
345
- }
346
- }),
347
- { name: storageKey }
348
- )
349
- );
350
- return { ...config, useStore };
351
- }
352
- function applyStoredTheme(bundle) {
353
- const { colors, fonts, radius } = bundle.useStore.getState();
354
- applyThemeVars({ colors, fonts, radius }, { mode: bundle.mode, dark: bundle.darkColors });
355
206
  }
356
- function ThemeEditor({
357
- bundle,
358
- variant = "panel",
359
- preview,
360
- onSave,
361
- testIdPrefix = "theme"
362
- }) {
363
- const { useStore, tokens, presets, groupLabels, mode, darkColors } = bundle;
364
- const colors = useStore((s) => s.colors);
365
- const fonts = useStore((s) => s.fonts);
366
- const radius = useStore((s) => s.radius);
367
- const activePresetId = useStore((s) => s.activePresetId);
368
- const setColor = useStore((s) => s.setColor);
369
- const setFont = useStore((s) => s.setFont);
370
- const setRadius = useStore((s) => s.setRadius);
371
- const applyPreset = useStore((s) => s.applyPreset);
372
- const reset = useStore((s) => s.reset);
373
- const [saving, setSaving] = useState(false);
374
- const [savedVia, setSavedVia] = useState(null);
375
- useEffect(() => {
376
- applyThemeVars({ colors, fonts, radius }, { mode, dark: darkColors });
377
- }, [colors, fonts, radius, mode, darkColors]);
378
- const tid2 = (suffix) => `${testIdPrefix}-${suffix}`;
379
- const groups = [...new Set(tokens.map((t) => t.group))];
380
- const save = async () => {
381
- if (!onSave) return;
382
- setSaving(true);
383
- const via = await onSave();
384
- setSavedVia(typeof via === "string" ? via : "saved");
385
- setSaving(false);
386
- };
387
- const Presets = /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
388
- presets.map((p) => /* @__PURE__ */ jsx(
389
- "button",
390
- {
391
- type: "button",
392
- onClick: () => applyPreset(p.id),
393
- 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"}`,
394
- "data-testid": tid2(`preset-${p.id}`),
395
- children: p.label
396
- },
397
- p.id
398
- )),
399
- /* @__PURE__ */ jsxs(
400
- "button",
401
- {
402
- type: "button",
403
- onClick: reset,
404
- className: "ml-auto inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground",
405
- "data-testid": tid2("reset"),
406
- children: [
407
- /* @__PURE__ */ jsx(RotateCcw, { className: "size-3" }),
408
- " Reset"
409
- ]
410
- }
411
- )
412
- ] });
413
- const colorRow = (k, label) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
414
- /* @__PURE__ */ jsx(
415
- "input",
416
- {
417
- type: "color",
418
- value: colors[k] ?? "#000000",
419
- onChange: (e) => setColor(k, e.target.value),
420
- className: "size-9 shrink-0 cursor-pointer rounded-md border border-border bg-transparent",
421
- "aria-label": label,
422
- "data-testid": tid2(`color-${k}`)
423
- }
424
- ),
425
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
426
- /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: label }),
427
- /* @__PURE__ */ jsx(
428
- Input,
429
- {
430
- value: colors[k] ?? "",
431
- onChange: (e) => setColor(k, e.target.value),
432
- 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",
433
- "data-testid": tid2(`hex-${k}`)
434
- }
435
- )
436
- ] })
437
- ] }, k);
438
- const Typography = /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
439
- /* @__PURE__ */ jsxs("div", { children: [
440
- /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: "Display font stack" }),
441
- /* @__PURE__ */ jsx(
442
- Input,
443
- {
444
- value: fonts.display,
445
- onChange: (e) => setFont("display", e.target.value),
446
- 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",
447
- "data-testid": tid2("font-display")
448
- }
449
- )
450
- ] }),
451
- /* @__PURE__ */ jsxs("div", { children: [
452
- /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: "Body font stack" }),
453
- /* @__PURE__ */ jsx(
454
- Input,
455
- {
456
- value: fonts.sans,
457
- onChange: (e) => setFont("sans", e.target.value),
458
- 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",
459
- "data-testid": tid2("font-sans")
460
- }
461
- )
462
- ] }),
463
- /* @__PURE__ */ jsxs("div", { children: [
464
- /* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
465
- "Base radius \u2014 ",
466
- radius,
467
- "rem"
468
- ] }),
469
- /* @__PURE__ */ jsx(
470
- "input",
471
- {
472
- type: "range",
473
- min: 0,
474
- max: 1.5,
475
- step: 0.125,
476
- value: radius,
477
- onChange: (e) => setRadius(Number(e.target.value)),
478
- className: "mt-2 w-full accent-[color:var(--color-primary)]",
479
- "data-testid": tid2("radius")
480
- }
481
- )
482
- ] })
483
- ] });
484
- const Preview = /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: "rounded-xl", "data-testid": tid2("preview"), children: [
485
- /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: "Live preview" }) }),
486
- /* @__PURE__ */ jsxs(CardContent, { className: "space-y-3", children: [
487
- /* @__PURE__ */ jsxs("div", { className: "rounded-[var(--radius)] bg-primary p-4 text-primary-foreground", children: [
488
- /* @__PURE__ */ jsx("p", { className: "font-display text-lg font-semibold", children: preview?.title ?? "Preview" }),
489
- preview?.subtitle && /* @__PURE__ */ jsx("p", { className: "text-xs opacity-80", children: preview.subtitle })
490
- ] }),
491
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-6 gap-1.5", children: tokens.map((t) => /* @__PURE__ */ jsx(
492
- "div",
493
- {
494
- title: t.label,
495
- className: "aspect-square rounded-md border border-border",
496
- style: { background: `var(--color-${t.key})` }
497
- },
498
- t.key
499
- )) }),
500
- /* @__PURE__ */ jsxs("div", { className: "rounded-[var(--radius)] border border-border bg-[color:var(--color-ai-accent-soft,var(--color-accent))] p-3", children: [
501
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-[color:var(--color-ai-accent,var(--color-primary))]", children: "AI action surface" }),
502
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Themed by the AI-kit accent tokens." })
503
- ] })
504
- ] })
505
- ] });
506
- const SaveBar = onSave && /* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center gap-3", children: [
507
- /* @__PURE__ */ jsxs(
508
- "button",
509
- {
510
- type: "button",
511
- onClick: () => void save(),
512
- disabled: saving,
513
- 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",
514
- "data-testid": tid2("save"),
515
- children: [
516
- savedVia ? /* @__PURE__ */ jsx(Check, { className: "size-4" }) : /* @__PURE__ */ jsx(Save, { className: "size-4" }),
517
- saving ? "Saving\u2026" : savedVia ? "Saved" : "Save theme"
518
- ]
519
- }
520
- ),
521
- savedVia && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", "data-testid": tid2("via"), children: [
522
- "stored via ",
523
- savedVia
524
- ] })
525
- ] });
526
- if (variant === "drawer") {
527
- return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col overflow-y-auto bg-card p-5", "data-testid": testIdPrefix, children: [
528
- /* @__PURE__ */ jsx("div", { className: "mb-4 flex items-center justify-between", children: /* @__PURE__ */ jsx("h2", { className: "font-display text-lg font-semibold text-foreground", children: "Theme" }) }),
529
- Presets,
530
- /* @__PURE__ */ jsx("p", { className: "mt-5 mb-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground", children: "Colours" }),
531
- /* @__PURE__ */ jsx("div", { className: "space-y-3", children: tokens.map((t) => colorRow(t.key, t.label)) }),
532
- /* @__PURE__ */ jsx("p", { className: "mt-6 mb-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground", children: "Typography & shape" }),
533
- Typography,
534
- SaveBar
535
- ] });
207
+ function persistWorkspace(apiUrl, id) {
208
+ try {
209
+ if (id) window.localStorage.setItem(workspaceStorageKey(apiUrl), id);
210
+ else window.localStorage.removeItem(workspaceStorageKey(apiUrl));
211
+ } catch {
536
212
  }
537
- return /* @__PURE__ */ jsxs("div", { className: "grid gap-6 lg:grid-cols-[1fr_320px]", "data-testid": testIdPrefix, children: [
538
- /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
539
- /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: "rounded-xl", children: [
540
- /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, { className: "flex items-center gap-2 text-base", children: [
541
- /* @__PURE__ */ jsx(Palette, { className: "size-4 text-[color:var(--color-primary)]" }),
542
- " Presets"
543
- ] }) }),
544
- /* @__PURE__ */ jsx(CardContent, { children: Presets })
545
- ] }),
546
- groups.map((group) => /* @__PURE__ */ jsxs(
547
- Card,
548
- {
549
- variant: "elevated",
550
- className: "rounded-xl",
551
- "data-testid": tid2(`group-${group}`),
552
- children: [
553
- /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: groupLabels[group] ?? group }) }),
554
- /* @__PURE__ */ jsx(CardContent, { className: "grid gap-3 sm:grid-cols-2", children: tokens.filter((t) => t.group === group).map((t) => colorRow(t.key, t.label)) })
555
- ]
556
- },
557
- group
558
- )),
559
- /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: "rounded-xl", children: [
560
- /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: "Typography & shape" }) }),
561
- /* @__PURE__ */ jsx(CardContent, { children: Typography })
562
- ] }),
563
- SaveBar
564
- ] }),
565
- /* @__PURE__ */ jsx("div", { className: "lg:sticky lg:top-4 lg:self-start", children: Preview })
566
- ] });
567
213
  }
568
- var ConnectionContext = createContext(null);
569
214
  function ConnectionProvider({
570
215
  config,
571
216
  children
@@ -598,10 +243,12 @@ function ConnectionProvider({
598
243
  await arbi.login(email, password);
599
244
  const workspaces = await arbi.workspaces.list();
600
245
  if (workspaces?.length) {
601
- const preferred = config.workspaceId;
246
+ const persisted = readPersistedWorkspace(config.apiUrl);
247
+ const preferred = persisted ?? config.workspaceId;
602
248
  const target = (preferred ? workspaces.find((w) => w.external_id === preferred) : void 0) ?? workspaces[0];
603
249
  await arbi.selectWorkspace(target.external_id);
604
250
  setWorkspaceId(target.external_id);
251
+ persistWorkspace(config.apiUrl, target.external_id);
605
252
  }
606
253
  let resolved = { name: email.split("@")[0], email };
607
254
  try {
@@ -628,7 +275,7 @@ function ConnectionProvider({
628
275
  setStatus("error");
629
276
  }
630
277
  },
631
- [arbi, enterDemo, isLiveConfigured, config.workspaceId]
278
+ [arbi, enterDemo, isLiveConfigured, config.workspaceId, config.apiUrl]
632
279
  );
633
280
  const logout = useCallback(() => {
634
281
  if (mode === "live") {
@@ -636,10 +283,25 @@ function ConnectionProvider({
636
283
  }
637
284
  setUser(null);
638
285
  setWorkspaceId(null);
286
+ persistWorkspace(config.apiUrl, null);
639
287
  setStatus("disconnected");
640
288
  setError(null);
641
- }, [arbi, mode]);
642
- const setWorkspace = useCallback((id) => setWorkspaceId(id), []);
289
+ }, [arbi, mode, config.apiUrl]);
290
+ const setWorkspace = useCallback(
291
+ async (id) => {
292
+ if (mode === "live" && arbi.isLoggedIn) {
293
+ await arbi.selectWorkspace(id);
294
+ }
295
+ setWorkspaceId(id);
296
+ persistWorkspace(config.apiUrl, id);
297
+ },
298
+ [arbi, mode, config.apiUrl]
299
+ );
300
+ const ensureWorkspaceOpen = useCallback(async () => {
301
+ if (mode !== "live" || !arbi.isLoggedIn) return;
302
+ const id = workspaceId ?? readPersistedWorkspace(config.apiUrl) ?? config.workspaceId;
303
+ if (id) await arbi.selectWorkspace(id);
304
+ }, [arbi, mode, workspaceId, config.apiUrl, config.workspaceId]);
643
305
  const value = useMemo(
644
306
  () => ({
645
307
  mode,
@@ -654,7 +316,8 @@ function ConnectionProvider({
654
316
  login,
655
317
  enterDemo,
656
318
  logout,
657
- setWorkspace
319
+ setWorkspace,
320
+ ensureWorkspaceOpen
658
321
  }),
659
322
  [
660
323
  mode,
@@ -669,7 +332,8 @@ function ConnectionProvider({
669
332
  login,
670
333
  enterDemo,
671
334
  logout,
672
- setWorkspace
335
+ setWorkspace,
336
+ ensureWorkspaceOpen
673
337
  ]
674
338
  );
675
339
  return /* @__PURE__ */ jsx(ConnectionContext.Provider, { value, children });
@@ -714,100 +378,6 @@ function useSemanticSearch() {
714
378
  const { docIds } = useWorkspaceDocs();
715
379
  return useSemanticSearch$1(docIds);
716
380
  }
717
-
718
- // src/lib/testid.ts
719
- function tid(...parts) {
720
- return parts.filter((p) => p !== void 0 && p !== false && p !== "").map(
721
- (p) => String(p).trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
722
- ).join("-");
723
- }
724
-
725
- // src/lib/format.ts
726
- function gbp(value, compact = false) {
727
- return new Intl.NumberFormat("en-GB", {
728
- style: "currency",
729
- currency: "GBP",
730
- maximumFractionDigits: compact && Math.abs(value) >= 1e3 ? 1 : 0,
731
- notation: compact ? "compact" : "standard"
732
- }).format(value);
733
- }
734
- function initials(name) {
735
- const parts = name.trim().split(/\s+/);
736
- if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
737
- return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
738
- }
739
- function parseIso(iso) {
740
- const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(iso);
741
- const d = new Date(dateOnly ? `${iso}T00:00:00` : iso);
742
- if (Number.isNaN(d.getTime())) throw new Error(`Invalid ISO date: ${iso}`);
743
- return d;
744
- }
745
- function fmtDate(iso) {
746
- try {
747
- return new Intl.DateTimeFormat("en-GB", {
748
- day: "numeric",
749
- month: "short",
750
- year: "numeric"
751
- }).format(parseIso(iso));
752
- } catch {
753
- return iso;
754
- }
755
- }
756
- function fmtDateTime(iso) {
757
- try {
758
- return new Intl.DateTimeFormat("en-GB", {
759
- day: "numeric",
760
- month: "short",
761
- year: "numeric",
762
- hour: "2-digit",
763
- minute: "2-digit",
764
- hour12: false
765
- }).format(parseIso(iso));
766
- } catch {
767
- return iso;
768
- }
769
- }
770
- var RELATIVE_UNITS = [
771
- ["year", 31536e3],
772
- ["month", 2592e3],
773
- ["week", 604800],
774
- ["day", 86400],
775
- ["hour", 3600],
776
- ["minute", 60],
777
- ["second", 1]
778
- ];
779
- function fromNow(iso, from = /* @__PURE__ */ new Date()) {
780
- try {
781
- const diffSeconds = (parseIso(iso).getTime() - from.getTime()) / 1e3;
782
- const abs = Math.abs(diffSeconds);
783
- const rtf = new Intl.RelativeTimeFormat("en-GB", { numeric: "always" });
784
- for (const [unit, secs] of RELATIVE_UNITS) {
785
- if (abs >= secs || unit === "second") {
786
- const value = Math.round(diffSeconds / secs);
787
- return rtf.format(value, unit);
788
- }
789
- }
790
- return rtf.format(0, "second");
791
- } catch {
792
- return iso;
793
- }
794
- }
795
- function daysUntil(iso, from = /* @__PURE__ */ new Date()) {
796
- try {
797
- const target = parseIso(iso);
798
- const a = Date.UTC(target.getFullYear(), target.getMonth(), target.getDate());
799
- const b = Date.UTC(from.getFullYear(), from.getMonth(), from.getDate());
800
- return Math.round((a - b) / 864e5);
801
- } catch {
802
- return 0;
803
- }
804
- }
805
- function hrs(n) {
806
- return `${n.toFixed(1)}h`;
807
- }
808
- function pct(n) {
809
- return `${Math.round(n)}%`;
810
- }
811
381
  function EmptyState({
812
382
  icon: Icon,
813
383
  title,
@@ -1300,1020 +870,289 @@ function GridView({
1300
870
  onCellValueChanged,
1301
871
  onRowClicked: handleRowClicked,
1302
872
  onStateUpdated: handleStateUpdated,
1303
- ...combinedOptions
1304
- }
1305
- ) });
1306
- }
1307
- function PageHeader({
1308
- title,
1309
- eyebrow,
1310
- description,
1311
- actions,
1312
- className,
1313
- testId
1314
- }) {
1315
- return /* @__PURE__ */ jsxs(
1316
- "div",
1317
- {
1318
- className: cn("flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between", className),
1319
- "data-testid": testId,
1320
- children: [
1321
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1322
- eyebrow && /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: eyebrow }),
1323
- /* @__PURE__ */ jsx("h1", { className: "font-display text-2xl font-semibold tracking-tight text-foreground", children: title }),
1324
- description && /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-2xl text-sm text-muted-foreground", children: description })
1325
- ] }),
1326
- actions && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: actions })
1327
- ]
1328
- }
1329
- );
1330
- }
1331
- function SectionHeading({
1332
- eyebrow,
1333
- title,
1334
- lead,
1335
- align = "left",
1336
- className,
1337
- invert,
1338
- eyebrowClassName
1339
- }) {
1340
- return /* @__PURE__ */ jsxs("div", { className: cn("max-w-3xl", align === "center" && "mx-auto text-center", className), children: [
1341
- eyebrow && /* @__PURE__ */ jsx(
1342
- "p",
1343
- {
1344
- className: cn(
1345
- "mb-3 text-xs font-semibold uppercase tracking-wider",
1346
- eyebrowClassName ?? (invert ? "text-background/60" : "text-muted-foreground")
1347
- ),
1348
- children: eyebrow
1349
- }
1350
- ),
1351
- /* @__PURE__ */ jsx(
1352
- "h2",
1353
- {
1354
- className: cn(
1355
- "font-display text-3xl font-semibold tracking-tight text-balance sm:text-4xl",
1356
- invert ? "text-background" : "text-foreground"
1357
- ),
1358
- children: title
1359
- }
1360
- ),
1361
- lead && /* @__PURE__ */ jsx(
1362
- "p",
1363
- {
1364
- className: cn(
1365
- "mt-4 text-lg leading-relaxed",
1366
- invert ? "text-background/70" : "text-muted-foreground"
1367
- ),
1368
- children: lead
1369
- }
1370
- )
1371
- ] });
1372
- }
1373
- function Toolbar({
1374
- search,
1375
- onSearch,
1376
- searchPlaceholder = "Search\u2026",
1377
- filters,
1378
- actions,
1379
- className,
1380
- testId
1381
- }) {
1382
- return /* @__PURE__ */ jsxs(
1383
- "div",
1384
- {
1385
- className: cn(
1386
- "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
1387
- className
1388
- ),
1389
- "data-testid": testId,
1390
- children: [
1391
- /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-2", children: [
1392
- onSearch && /* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-xs", children: [
1393
- /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }),
1394
- /* @__PURE__ */ jsx(
1395
- Input,
1396
- {
1397
- value: search,
1398
- onChange: (e) => onSearch(e.target.value),
1399
- placeholder: searchPlaceholder,
1400
- className: "bg-card text-sm placeholder:text-muted-foreground/70 focus-visible:border-ring/60 focus-visible:ring-2 focus-visible:ring-ring/50 pl-8",
1401
- "data-testid": testId ? `${testId}-search` : "toolbar-search"
1402
- }
1403
- )
1404
- ] }),
1405
- filters
1406
- ] }),
1407
- actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: actions })
1408
- ]
1409
- }
1410
- );
1411
- }
1412
- function MetricCard({
1413
- label,
1414
- value,
1415
- icon: Icon,
1416
- hint,
1417
- delta,
1418
- accent = "default",
1419
- testId
1420
- }) {
1421
- const accentStyle = accent && accent !== "default" ? { color: `var(--color-${accent})` } : void 0;
1422
- const deltaGood = delta?.good ?? delta?.direction === "up";
1423
- return /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: "card-hover rounded-xl p-5", "data-testid": testId, children: [
1424
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
1425
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: label }),
1426
- Icon && /* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0", style: accentStyle })
1427
- ] }),
1428
- /* @__PURE__ */ jsx(
1429
- "p",
1430
- {
1431
- className: "mt-2 font-display text-2xl font-semibold tracking-tight",
1432
- "data-testid": testId && `${testId}-value`,
1433
- children: value
1434
- }
1435
- ),
1436
- /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
1437
- delta && /* @__PURE__ */ jsxs(
1438
- "span",
1439
- {
1440
- className: cn(
1441
- "inline-flex items-center gap-0.5 text-xs font-medium",
1442
- deltaGood ? "text-success" : "text-destructive"
1443
- ),
1444
- children: [
1445
- delta.direction === "up" ? /* @__PURE__ */ jsx(ArrowUpRight, { className: "size-3" }) : /* @__PURE__ */ jsx(ArrowDownRight, { className: "size-3" }),
1446
- delta.value
1447
- ]
1448
- }
1449
- ),
1450
- hint && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: hint })
1451
- ] })
1452
- ] });
1453
- }
1454
- var ASSET_REF_PREFIX = "asset:";
1455
- function asAssetRef(docId) {
1456
- return `${ASSET_REF_PREFIX}${docId}`;
1457
- }
1458
- function assetRefId(src) {
1459
- return src.slice(ASSET_REF_PREFIX.length);
1460
- }
1461
- var AssetResolverContext = createContext(false);
1462
- function AssetResolverProvider({ children }) {
1463
- return /* @__PURE__ */ jsx(AssetResolverContext.Provider, { value: true, children });
1464
- }
1465
- function useAssetResolverActive() {
1466
- return useContext(AssetResolverContext);
1467
- }
1468
- function isAssetRef(src) {
1469
- return !!src && src.startsWith(ASSET_REF_PREFIX);
1470
- }
1471
- function useAssetImage(docId) {
1472
- const arbi = useArbi();
1473
- const { data: thumbs } = useThumbnails([docId]);
1474
- const thumb = thumbs?.[docId];
1475
- const { data: objectUrl } = useQuery({
1476
- queryKey: ["arbi", "asset-image", docId],
1477
- queryFn: async () => {
1478
- const res = await arbi.documents.download(docId);
1479
- const blob = await res.blob();
1480
- return URL.createObjectURL(blob);
1481
- },
1482
- // Only download when the thumbnail is genuinely absent (not merely loading).
1483
- enabled: !!docId && thumbs != null && !thumb && arbi.hasWorkspace,
1484
- staleTime: 10 * 6e4
1485
- });
1486
- useEffect(() => {
1487
- return () => {
1488
- if (objectUrl) URL.revokeObjectURL(objectUrl);
1489
- };
1490
- }, [objectUrl]);
1491
- return thumb ?? objectUrl;
1492
- }
1493
- function AssetThumb({
1494
- docId,
1495
- alt,
1496
- className,
1497
- testId
1498
- }) {
1499
- const src = useAssetImage(docId);
1500
- return /* @__PURE__ */ jsx("img", { src: src ?? void 0, alt: alt ?? "", className, "data-testid": testId });
1501
- }
1502
- var ASPECT = {
1503
- "16:9": "aspect-video",
1504
- "4:3": "aspect-[4/3]",
1505
- "1:1": "aspect-square",
1506
- "21:9": "aspect-[21/9]",
1507
- "4:5": "aspect-[4/5]"
1508
- };
1509
- var ROUNDED = {
1510
- none: "rounded-none",
1511
- md: "rounded-md",
1512
- lg: "rounded-xl",
1513
- full: "rounded-full"
1514
- };
1515
- function BlockImage({
1516
- src,
1517
- alt = "",
1518
- aspect = "16:9",
1519
- rounded = "lg",
1520
- framed,
1521
- className,
1522
- testId
1523
- }) {
1524
- const shape = cn(
1525
- ASPECT[aspect] ?? ASPECT["16:9"],
1526
- ROUNDED[rounded] ?? ROUNDED.lg,
1527
- framed && "ring-1 ring-border shadow-xl"
1528
- );
1529
- const canResolve = useAssetResolverActive();
1530
- if (canResolve && isAssetRef(src)) {
1531
- return /* @__PURE__ */ jsx(
1532
- AssetThumb,
1533
- {
1534
- docId: assetRefId(src),
1535
- alt,
1536
- className: cn("w-full object-cover", shape, className),
1537
- testId
1538
- }
1539
- );
1540
- }
1541
- if (src && !isAssetRef(src)) {
1542
- return /* @__PURE__ */ jsx(
1543
- "img",
1544
- {
1545
- src,
1546
- alt,
1547
- className: cn("w-full object-cover", shape, className),
1548
- "data-testid": testId
1549
- }
1550
- );
1551
- }
1552
- return /* @__PURE__ */ jsx(
1553
- "div",
1554
- {
1555
- className: cn(
1556
- "flex w-full items-center justify-center overflow-hidden border border-border",
1557
- "bg-gradient-to-br from-muted via-card to-accent text-muted-foreground",
1558
- shape,
1559
- className
1560
- ),
1561
- "data-testid": testId,
1562
- "aria-label": alt || "Image placeholder",
1563
- role: "img",
1564
- children: /* @__PURE__ */ jsx(ImageIcon, { className: "size-8 opacity-60" })
1565
- }
1566
- );
1567
- }
1568
- var TONE_SURFACE = {
1569
- default: "bg-background text-foreground",
1570
- muted: "bg-muted text-foreground",
1571
- ink: "bg-foreground text-background"
1572
- };
1573
- function renderTitle(title, emphasis) {
1574
- if (!emphasis || !title.includes(emphasis)) return title;
1575
- const parts = title.split(emphasis);
1576
- return parts.map((part, i) => /* @__PURE__ */ jsxs("span", { children: [
1577
- part,
1578
- i < parts.length - 1 && /* @__PURE__ */ jsx("span", { className: "text-primary", children: emphasis })
1579
- ] }, i));
1580
- }
1581
- function Hero({
1582
- eyebrow,
1583
- title,
1584
- emphasis,
1585
- subtitle,
1586
- primaryCta,
1587
- secondaryCta,
1588
- align = "left",
1589
- tone = "default",
1590
- imageUrl,
1591
- backgroundImage,
1592
- overlay = true,
1593
- eyebrowClassName,
1594
- testId
1595
- }) {
1596
- const onDark = Boolean(backgroundImage) || tone === "ink";
1597
- const subtle = onDark ? "text-background/70" : "text-muted-foreground";
1598
- const eyebrowClass = eyebrowClassName ?? (onDark ? "text-background/60" : "text-muted-foreground");
1599
- const centered = align === "center" || Boolean(backgroundImage);
1600
- const heading = renderTitle(title, emphasis);
1601
- const copy = /* @__PURE__ */ jsxs(
1602
- "div",
1603
- {
1604
- className: cn(
1605
- "max-w-2xl",
1606
- centered && "mx-auto text-center",
1607
- backgroundImage && "text-background"
1608
- ),
1609
- children: [
1610
- eyebrow && /* @__PURE__ */ jsx("p", { className: cn("mb-4 text-sm font-semibold uppercase tracking-wider", eyebrowClass), children: eyebrow }),
1611
- /* @__PURE__ */ jsx("h1", { className: "text-4xl font-semibold tracking-tight text-balance sm:text-5xl", children: heading }),
1612
- subtitle && /* @__PURE__ */ jsx("p", { className: cn("mt-6 text-lg leading-relaxed", subtle), children: subtitle }),
1613
- (primaryCta?.label || secondaryCta?.label) && /* @__PURE__ */ jsxs(
1614
- "div",
1615
- {
1616
- className: cn("mt-8 flex flex-col gap-3 sm:flex-row", centered && "sm:justify-center"),
1617
- children: [
1618
- primaryCta?.label && /* @__PURE__ */ jsx(Button, { asChild: true, size: "lg", children: /* @__PURE__ */ jsxs("a", { href: primaryCta.href || "#", children: [
1619
- primaryCta.label,
1620
- /* @__PURE__ */ jsx(ArrowRight, { className: "size-4" })
1621
- ] }) }),
1622
- secondaryCta?.label && /* @__PURE__ */ jsx(Button, { asChild: true, size: "lg", variant: "outline", children: /* @__PURE__ */ jsx("a", { href: secondaryCta.href || "#", children: secondaryCta.label }) })
1623
- ]
1624
- }
1625
- )
1626
- ]
1627
- }
1628
- );
1629
- if (backgroundImage) {
1630
- const bgStyle = {
1631
- backgroundImage: `url("${backgroundImage}")`,
1632
- backgroundSize: "cover",
1633
- backgroundPosition: "center"
1634
- };
1635
- return /* @__PURE__ */ jsxs(
1636
- "section",
1637
- {
1638
- className: "relative isolate overflow-hidden",
1639
- style: bgStyle,
1640
- "data-testid": testId,
1641
- "data-bg": testId && `${testId}-bg`,
1642
- children: [
1643
- overlay && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/55", "aria-hidden": true }),
1644
- /* @__PURE__ */ jsx("div", { className: "relative mx-auto max-w-7xl px-5 py-28 lg:px-8 lg:py-36", children: copy })
1645
- ]
1646
- }
1647
- );
1648
- }
1649
- return /* @__PURE__ */ jsx("section", { className: cn(TONE_SURFACE[tone] ?? TONE_SURFACE.default), "data-testid": testId, children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-7xl px-5 py-20 lg:px-8 lg:py-28", children: centered ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-12", children: [
1650
- copy,
1651
- imageUrl && /* @__PURE__ */ jsx(
1652
- BlockImage,
1653
- {
1654
- src: imageUrl,
1655
- aspect: "16:9",
1656
- rounded: "lg",
1657
- className: "w-full max-w-4xl",
1658
- testId: testId && `${testId}-image`
1659
- }
1660
- )
1661
- ] }) : /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-12 lg:grid-cols-2", children: [
1662
- copy,
1663
- /* @__PURE__ */ jsx(
1664
- BlockImage,
1665
- {
1666
- src: imageUrl,
1667
- aspect: "4:5",
1668
- rounded: "lg",
1669
- testId: testId && `${testId}-image`
1670
- }
1671
- )
1672
- ] }) }) });
1673
- }
1674
- var COLS = {
1675
- "2": "sm:grid-cols-2",
1676
- "3": "sm:grid-cols-2 lg:grid-cols-3"
1677
- };
1678
- function FeatureGrid({ columns = "3", items, testId }) {
1679
- return /* @__PURE__ */ jsx("div", { className: cn("grid gap-5", COLS[columns] ?? COLS["3"]), "data-testid": testId, children: (items ?? []).map((item, i) => {
1680
- const Icon = item.icon;
1681
- return /* @__PURE__ */ jsxs(
1682
- Card,
1683
- {
1684
- variant: "outline",
1685
- className: "h-full p-6",
1686
- "data-testid": testId && `${testId}-item-${i}`,
1687
- children: [
1688
- Icon && /* @__PURE__ */ jsx("span", { className: "mb-4 inline-flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(Icon, { className: "size-5" }) }),
1689
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold tracking-tight", children: item.title }),
1690
- item.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: item.description })
1691
- ]
1692
- },
1693
- i
1694
- );
1695
- }) });
1696
- }
1697
- var GRID = {
1698
- 1: "grid-cols-1",
1699
- 2: "grid-cols-2",
1700
- 3: "grid-cols-2 sm:grid-cols-3",
1701
- 4: "grid-cols-2 lg:grid-cols-4"
1702
- };
1703
- function StatGroup({ items, testId }) {
1704
- const list = items ?? [];
1705
- const cols = GRID[Math.min(list.length, 4)] ?? GRID[4];
1706
- return /* @__PURE__ */ jsx("dl", { className: cn("grid gap-8", cols), "data-testid": testId, children: list.map((item, i) => /* @__PURE__ */ jsxs("div", { className: "text-center", "data-testid": testId && `${testId}-item-${i}`, children: [
1707
- /* @__PURE__ */ jsx("dt", { className: "sr-only", children: item.label }),
1708
- /* @__PURE__ */ jsx("dd", { className: "text-4xl font-semibold tracking-tight sm:text-5xl", children: item.value }),
1709
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: item.label })
1710
- ] }, i)) });
1711
- }
1712
- function Testimonial({ quote, author, role, avatarUrl, testId }) {
1713
- return /* @__PURE__ */ jsxs(Card, { variant: "outline", className: "mx-auto max-w-3xl p-8 text-center", "data-testid": testId, children: [
1714
- /* @__PURE__ */ jsx(Quote, { className: "mx-auto size-8 text-primary/40", "aria-hidden": true }),
1715
- /* @__PURE__ */ jsx("blockquote", { className: "mt-4 text-xl font-medium leading-relaxed text-balance", children: quote }),
1716
- (author || avatarUrl) && /* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-center gap-3", children: [
1717
- (avatarUrl || author) && /* @__PURE__ */ jsxs(Avatar, { children: [
1718
- avatarUrl && /* @__PURE__ */ jsx(AvatarImage, { src: avatarUrl, alt: author ?? "" }),
1719
- /* @__PURE__ */ jsx(AvatarFallback, { children: initials(author || "?") })
1720
- ] }),
1721
- /* @__PURE__ */ jsxs("div", { className: "text-left", children: [
1722
- author && /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: author }),
1723
- role && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: role })
1724
- ] })
1725
- ] })
1726
- ] });
1727
- }
1728
- var TONE_SURFACE2 = {
1729
- default: "bg-primary text-primary-foreground",
1730
- muted: "bg-muted text-foreground",
1731
- ink: "bg-foreground text-background"
1732
- };
1733
- function CTABanner({
1734
- title,
1735
- subtitle,
1736
- cta,
1737
- tone = "default",
1738
- backgroundImage,
1739
- overlay = true,
1740
- testId
1741
- }) {
1742
- const onImage = Boolean(backgroundImage);
1743
- const subtle = onImage ? "text-white/80" : tone === "default" ? "text-primary-foreground/80" : tone === "ink" ? "text-background/70" : "text-muted-foreground";
1744
- const bgStyle = backgroundImage ? {
1745
- backgroundImage: `url("${backgroundImage}")`,
1746
- backgroundSize: "cover",
1747
- backgroundPosition: "center"
1748
- } : void 0;
1749
- return /* @__PURE__ */ jsxs(
1750
- "div",
1751
- {
1752
- className: cn(
1753
- "relative isolate overflow-hidden rounded-2xl px-8 py-14 text-center",
1754
- onImage ? "text-white" : TONE_SURFACE2[tone] ?? TONE_SURFACE2.default
1755
- ),
1756
- style: bgStyle,
1757
- "data-testid": testId,
1758
- children: [
1759
- onImage && overlay && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/55", "aria-hidden": true }),
1760
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1761
- /* @__PURE__ */ jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance sm:text-4xl", children: title }),
1762
- subtitle && /* @__PURE__ */ jsx("p", { className: cn("mx-auto mt-4 max-w-2xl text-lg", subtle), children: subtitle }),
1763
- cta?.label && /* @__PURE__ */ jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsx(
1764
- Button,
1765
- {
1766
- asChild: true,
1767
- size: "lg",
1768
- variant: tone === "default" && !onImage ? "secondary" : "default",
1769
- children: /* @__PURE__ */ jsxs("a", { href: cta.href || "#", children: [
1770
- cta.label,
1771
- /* @__PURE__ */ jsx(ArrowRight, { className: "size-4" })
1772
- ] })
1773
- }
1774
- ) })
1775
- ] })
1776
- ]
1777
- }
1778
- );
1779
- }
1780
- function LogoCloud({ items, testId }) {
1781
- return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center justify-center gap-4", "data-testid": testId, children: (items ?? []).map((item, i) => /* @__PURE__ */ jsx(
1782
- "span",
1783
- {
1784
- className: "rounded-md border border-border bg-muted px-5 py-2.5 text-sm font-semibold uppercase tracking-wide text-muted-foreground",
1785
- "data-testid": testId && `${testId}-item-${i}`,
1786
- children: item.label
1787
- },
1788
- i
1789
- )) });
1790
- }
1791
- function FAQ({ items, testId }) {
1792
- return /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-3xl divide-y divide-border", "data-testid": testId, children: (items ?? []).map((item, i) => /* @__PURE__ */ jsxs("details", { className: "group py-4", "data-testid": testId && `${testId}-item-${i}`, children: [
1793
- /* @__PURE__ */ jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-between gap-4 text-left text-base font-medium", children: [
1794
- item.question,
1795
- /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" })
1796
- ] }),
1797
- item.answer && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm leading-relaxed text-muted-foreground", children: item.answer })
1798
- ] }, i)) });
1799
- }
1800
- function RichTextBlock({ content, testId }) {
1801
- return /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-3xl", "data-testid": testId, children: /* @__PURE__ */ jsx(AiMarkdown, { children: content ?? "" }) });
1802
- }
1803
- var STYLES = {
1804
- info: { wrap: "border-primary/30 bg-primary/5", icon: "text-primary", Icon: Info },
1805
- success: { wrap: "border-success/30 bg-success/10", icon: "text-success", Icon: CheckCircle2 },
1806
- warning: { wrap: "border-warning/40 bg-warning/10", icon: "text-warning", Icon: AlertTriangle },
1807
- danger: {
1808
- wrap: "border-destructive/30 bg-destructive/10",
1809
- icon: "text-destructive",
1810
- Icon: XCircle
1811
- }
1812
- };
1813
- function Callout({ variant = "info", title, body, testId }) {
1814
- const style = STYLES[variant] ?? STYLES.info;
1815
- const Icon = style.Icon;
1816
- return /* @__PURE__ */ jsxs(
1817
- "div",
1818
- {
1819
- className: cn("flex gap-3 rounded-lg border p-4 text-foreground", style.wrap),
1820
- "data-testid": testId,
1821
- role: "note",
1822
- children: [
1823
- /* @__PURE__ */ jsx(Icon, { className: cn("mt-0.5 size-5 shrink-0", style.icon), "aria-hidden": true }),
1824
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1825
- title && /* @__PURE__ */ jsx("p", { className: "font-semibold", children: title }),
1826
- body && /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: body })
1827
- ] })
1828
- ]
1829
- }
1830
- );
1831
- }
1832
- var SIZE = {
1833
- sm: "size-8 text-xs",
1834
- md: "size-10 text-sm",
1835
- lg: "size-14 text-base",
1836
- xl: "size-20 text-xl"
1837
- };
1838
- function AvatarBlock({ src, name = "", size = "md", testId }) {
1839
- return /* @__PURE__ */ jsxs(Avatar, { className: cn(SIZE[size] ?? SIZE.md), "data-testid": testId, children: [
1840
- src && /* @__PURE__ */ jsx(AvatarImage, { src, alt: name }),
1841
- /* @__PURE__ */ jsx(AvatarFallback, { className: "font-semibold", children: initials(name || "?") })
1842
- ] });
1843
- }
1844
- function Navbar({ brand, links, cta, testId }) {
1845
- return /* @__PURE__ */ jsx("header", { className: "border-b border-border bg-background", "data-testid": testId, children: /* @__PURE__ */ jsxs("nav", { className: "mx-auto flex max-w-7xl items-center justify-between gap-6 px-5 py-4 lg:px-8", children: [
1846
- /* @__PURE__ */ jsx("span", { className: "text-lg font-semibold tracking-tight", children: brand }),
1847
- /* @__PURE__ */ jsx("div", { className: "hidden items-center gap-6 md:flex", children: (links ?? []).map((link, i) => /* @__PURE__ */ jsx(
1848
- "a",
1849
- {
1850
- href: link.href || "#",
1851
- className: "text-sm font-medium text-muted-foreground transition-colors hover:text-foreground",
1852
- "data-testid": testId && `${testId}-link-${i}`,
1853
- children: link.label
1854
- },
1855
- i
1856
- )) }),
1857
- cta?.label && /* @__PURE__ */ jsx(Button, { asChild: true, size: "sm", children: /* @__PURE__ */ jsx("a", { href: cta.href || "#", children: cta.label }) })
1858
- ] }) });
1859
- }
1860
- function Footer({ columns, copyright, testId }) {
1861
- return /* @__PURE__ */ jsx("footer", { className: "border-t border-border bg-muted text-foreground", "data-testid": testId, children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl px-5 py-14 lg:px-8", children: [
1862
- /* @__PURE__ */ jsx("div", { className: "grid gap-8 sm:grid-cols-2 lg:grid-cols-4", children: (columns ?? []).map((col, i) => /* @__PURE__ */ jsxs("div", { "data-testid": testId && `${testId}-col-${i}`, children: [
1863
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold tracking-tight", children: col.heading }),
1864
- /* @__PURE__ */ jsx("ul", { className: "mt-4 space-y-2", children: (col.links ?? []).map((link, j) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
1865
- "a",
1866
- {
1867
- href: link.href || "#",
1868
- className: "text-sm text-muted-foreground transition-colors hover:text-foreground",
1869
- children: link.label
1870
- }
1871
- ) }, j)) })
1872
- ] }, i)) }),
1873
- copyright && /* @__PURE__ */ jsx("p", { className: "mt-12 border-t border-border pt-6 text-sm text-muted-foreground", children: copyright })
1874
- ] }) });
1875
- }
1876
- function ListBlock({ ordered = false, items, testId }) {
1877
- const list = items ?? [];
1878
- const className = cn(
1879
- "mx-auto max-w-3xl space-y-2 pl-5 text-foreground",
1880
- ordered ? "list-decimal" : "list-disc"
1881
- );
1882
- const content = list.map((item, i) => /* @__PURE__ */ jsx("li", { className: "leading-relaxed marker:text-muted-foreground", children: item.text }, i));
1883
- return ordered ? /* @__PURE__ */ jsx("ol", { className, "data-testid": testId, children: content }) : /* @__PURE__ */ jsx("ul", { className, "data-testid": testId, children: content });
1884
- }
1885
- var GRID2 = {
1886
- 2: "sm:grid-cols-2",
1887
- 3: "sm:grid-cols-2 lg:grid-cols-3",
1888
- 4: "sm:grid-cols-2 lg:grid-cols-4"
1889
- };
1890
- var GAP = {
1891
- sm: "gap-3",
1892
- md: "gap-6",
1893
- lg: "gap-10"
1894
- };
1895
- function Columns({ count = 3, gap = "md", columns, testId }) {
1896
- return /* @__PURE__ */ jsx(
1897
- "div",
1898
- {
1899
- className: cn("grid grid-cols-1", GRID2[count] ?? GRID2[3], GAP[gap] ?? GAP.md),
1900
- "data-testid": testId,
1901
- children: columns.map((col, i) => /* @__PURE__ */ jsx("div", { "data-testid": testId && `${testId}-col-${i}`, children: col }, i))
1902
- }
1903
- );
1904
- }
1905
- var SIZE2 = {
1906
- sm: "h-4",
1907
- md: "h-8",
1908
- lg: "h-16",
1909
- xl: "h-24"
1910
- };
1911
- function Spacer({ size = "md", testId }) {
1912
- return /* @__PURE__ */ jsx("div", { className: cn("w-full", SIZE2[size] ?? SIZE2.md), "data-testid": testId, "aria-hidden": true });
1913
- }
1914
- var WIDTH = {
1915
- narrow: "max-w-3xl",
1916
- default: "max-w-5xl",
1917
- wide: "max-w-7xl",
1918
- full: "max-w-none"
1919
- };
1920
- var PADDING = {
1921
- none: "px-0 py-0",
1922
- sm: "px-4 py-6",
1923
- md: "px-5 py-12 lg:px-8",
1924
- lg: "px-5 py-20 lg:px-8"
1925
- };
1926
- function Container({ width = "default", padding = "md", children, testId }) {
1927
- return /* @__PURE__ */ jsx(
1928
- "div",
1929
- {
1930
- className: cn(
1931
- "mx-auto w-full",
1932
- WIDTH[width] ?? WIDTH.default,
1933
- PADDING[padding] ?? PADDING.md
1934
- ),
1935
- "data-testid": testId,
1936
- children
1937
- }
1938
- );
1939
- }
1940
- function PricingTable({ plans, testId }) {
1941
- return /* @__PURE__ */ jsx(
1942
- "div",
1943
- {
1944
- className: cn("grid gap-6", (plans?.length ?? 0) >= 3 ? "md:grid-cols-3" : "sm:grid-cols-2"),
1945
- "data-testid": testId,
1946
- children: (plans ?? []).map((plan, i) => /* @__PURE__ */ jsxs(
1947
- Card,
1948
- {
1949
- variant: plan.featured ? "elevated" : "outline",
1950
- className: cn("flex h-full flex-col p-6", plan.featured && "ring-2 ring-primary"),
1951
- "data-testid": testId && `${testId}-item-${i}`,
1952
- children: [
1953
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold tracking-tight", children: plan.name }),
1954
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-baseline gap-1", children: [
1955
- /* @__PURE__ */ jsx("span", { className: "text-3xl font-semibold tracking-tight", children: plan.price }),
1956
- plan.period && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
1957
- "/",
1958
- plan.period
1959
- ] })
1960
- ] }),
1961
- plan.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: plan.description }),
1962
- plan.features && plan.features.length > 0 && /* @__PURE__ */ jsx("ul", { className: "mt-5 flex-1 space-y-2", children: plan.features.map((f, j) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 text-sm", children: [
1963
- /* @__PURE__ */ jsx(Check, { className: "mt-0.5 size-4 shrink-0 text-primary" }),
1964
- /* @__PURE__ */ jsx("span", { children: f })
1965
- ] }, j)) }),
1966
- plan.cta?.label && /* @__PURE__ */ jsx(Button, { asChild: true, className: "mt-6 w-full", variant: plan.featured ? "default" : "outline", children: /* @__PURE__ */ jsx("a", { href: plan.cta.href || "#", children: plan.cta.label }) })
1967
- ]
1968
- },
1969
- i
1970
- ))
1971
- }
1972
- );
1973
- }
1974
- function Tabs({ items, testId }) {
1975
- const [active, setActive] = useState(0);
1976
- const list = items ?? [];
1977
- const current = list[Math.min(active, Math.max(list.length - 1, 0))];
1978
- return /* @__PURE__ */ jsxs("div", { "data-testid": testId, children: [
1979
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 border-b border-border", role: "tablist", children: list.map((t, i) => {
1980
- const selected = i === active;
1981
- return /* @__PURE__ */ jsx(
1982
- "button",
1983
- {
1984
- type: "button",
1985
- role: "tab",
1986
- "aria-selected": selected,
1987
- onClick: () => setActive(i),
1988
- className: cn(
1989
- "-mb-px border-b-2 px-4 py-2 text-sm font-medium transition-colors",
1990
- selected ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
1991
- ),
1992
- "data-testid": testId && `${testId}-tab-${i}`,
1993
- children: t.label
1994
- },
1995
- i
1996
- );
1997
- }) }),
1998
- current && /* @__PURE__ */ jsx(
1999
- "div",
2000
- {
2001
- role: "tabpanel",
2002
- className: "pt-5 text-sm leading-relaxed text-muted-foreground",
2003
- "data-testid": testId && `${testId}-panel`,
2004
- children: current.content
2005
- }
2006
- )
2007
- ] });
2008
- }
2009
- function Accordion({ items, allowMultiple = false, testId }) {
2010
- const [open, setOpen] = useState([]);
2011
- const toggle2 = (i) => setOpen(
2012
- (prev) => prev.includes(i) ? prev.filter((n) => n !== i) : allowMultiple ? [...prev, i] : [i]
2013
- );
2014
- return /* @__PURE__ */ jsx("div", { className: "divide-y divide-border border-y border-border", "data-testid": testId, children: (items ?? []).map((item, i) => {
2015
- const isOpen = open.includes(i);
2016
- return /* @__PURE__ */ jsxs("div", { "data-testid": testId && `${testId}-item-${i}`, children: [
2017
- /* @__PURE__ */ jsxs(
2018
- "button",
2019
- {
2020
- type: "button",
2021
- "aria-expanded": isOpen,
2022
- onClick: () => toggle2(i),
2023
- className: "flex w-full items-center justify-between gap-4 py-4 text-left text-base font-medium",
2024
- "data-testid": testId && `${testId}-trigger-${i}`,
2025
- children: [
2026
- item.title,
2027
- /* @__PURE__ */ jsx(
2028
- ChevronDown,
2029
- {
2030
- className: cn("size-4 shrink-0 transition-transform", isOpen && "rotate-180")
2031
- }
2032
- )
2033
- ]
2034
- }
2035
- ),
2036
- isOpen && /* @__PURE__ */ jsx(
2037
- "p",
2038
- {
2039
- className: "pb-4 text-sm leading-relaxed text-muted-foreground",
2040
- "data-testid": testId && `${testId}-content-${i}`,
2041
- children: item.content
2042
- }
2043
- )
2044
- ] }, i);
2045
- }) });
2046
- }
2047
- var COLS2 = {
2048
- "2": "grid-cols-2",
2049
- "3": "grid-cols-2 sm:grid-cols-3",
2050
- "4": "grid-cols-2 sm:grid-cols-4"
2051
- };
2052
- function Gallery({
2053
- images,
2054
- columns = "3",
2055
- aspect = "1:1",
2056
- rounded = "lg",
2057
- testId
2058
- }) {
2059
- return /* @__PURE__ */ jsx("div", { className: cn("grid gap-4", COLS2[columns] ?? COLS2["3"]), "data-testid": testId, children: (images ?? []).map((img, i) => /* @__PURE__ */ jsxs("figure", { "data-testid": testId && `${testId}-item-${i}`, children: [
2060
- /* @__PURE__ */ jsx(
2061
- BlockImage,
2062
- {
2063
- src: img.src,
2064
- alt: img.alt,
2065
- aspect,
2066
- rounded,
2067
- testId: testId && `${testId}-item-${i}-image`
2068
- }
2069
- ),
2070
- img.caption && /* @__PURE__ */ jsx("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: img.caption })
2071
- ] }, i)) });
2072
- }
2073
- var ASPECT2 = {
2074
- "16:9": "aspect-video",
2075
- "4:3": "aspect-[4/3]",
2076
- "1:1": "aspect-square"
2077
- };
2078
- function toEmbedUrl(url) {
2079
- if (!url) return null;
2080
- const yt = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]{11})/);
2081
- if (yt) return `https://www.youtube.com/embed/${yt[1]}`;
2082
- const vimeo = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
2083
- if (vimeo) return `https://player.vimeo.com/video/${vimeo[1]}`;
2084
- const loom = url.match(/loom\.com\/(?:share|embed)\/([\w-]+)/);
2085
- if (loom) return `https://www.loom.com/embed/${loom[1]}`;
2086
- return null;
2087
- }
2088
- function VideoEmbed({
2089
- url,
2090
- title,
2091
- aspect = "16:9",
2092
- rounded = true,
2093
- testId
2094
- }) {
2095
- const embed = toEmbedUrl(url);
2096
- const frame = cn(
2097
- "w-full overflow-hidden border border-border bg-muted",
2098
- ASPECT2[aspect] ?? ASPECT2["16:9"],
2099
- rounded && "rounded-lg"
2100
- );
2101
- return /* @__PURE__ */ jsx("div", { className: frame, "data-testid": testId, children: embed ? /* @__PURE__ */ jsx(
2102
- "iframe",
2103
- {
2104
- src: embed,
2105
- title: title || "Embedded video",
2106
- className: "size-full",
2107
- allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
2108
- allowFullScreen: true,
2109
- "data-testid": testId && `${testId}-iframe`
873
+ ...combinedOptions
2110
874
  }
2111
- ) : url ? /* @__PURE__ */ jsx("video", { src: url, controls: true, className: "size-full", "data-testid": testId && `${testId}-video` }) : null });
875
+ ) });
2112
876
  }
2113
- function ContactForm({
2114
- fields,
2115
- submitLabel = "Send",
2116
- action,
2117
- successMessage = "Thanks \u2014 we\u2019ll be in touch shortly.",
2118
- testId
877
+ function UploadDropzone({
878
+ onFiles,
879
+ accept,
880
+ multiple = true,
881
+ disabled = false,
882
+ uploading = false,
883
+ title = "Drag files here",
884
+ description = "or click to browse",
885
+ hint,
886
+ uploadingLabel = "Uploading\u2026",
887
+ icon: Icon = UploadCloud,
888
+ testId,
889
+ className
2119
890
  }) {
2120
- const [sent, setSent] = useState(false);
2121
- const list = fields ?? [];
2122
- const onSubmit = (e) => {
2123
- if (!action) {
2124
- e.preventDefault();
2125
- setSent(true);
2126
- }
891
+ const inputRef = useRef(null);
892
+ const [dragOver, setDragOver] = useState(false);
893
+ const inert = disabled || uploading;
894
+ const pick = (files) => {
895
+ if (!files?.length) return;
896
+ onFiles(Array.from(files));
897
+ };
898
+ const onDrop = (e) => {
899
+ e.preventDefault();
900
+ setDragOver(false);
901
+ if (inert) return;
902
+ pick(e.dataTransfer.files);
903
+ };
904
+ const onDragOver = (e) => {
905
+ e.preventDefault();
906
+ if (!inert) setDragOver(true);
2127
907
  };
2128
- if (sent) {
2129
- return /* @__PURE__ */ jsx(
2130
- "p",
2131
- {
2132
- className: "rounded-lg border border-border bg-muted p-6 text-center text-sm text-foreground",
2133
- role: "status",
2134
- "data-testid": testId && `${testId}-success`,
2135
- children: successMessage
2136
- }
2137
- );
2138
- }
2139
908
  return /* @__PURE__ */ jsxs(
2140
- "form",
909
+ "button",
2141
910
  {
2142
- action,
2143
- method: action ? "post" : void 0,
2144
- onSubmit,
2145
- className: "mx-auto flex max-w-xl flex-col gap-4",
911
+ type: "button",
912
+ onClick: () => !inert && inputRef.current?.click(),
913
+ onDrop,
914
+ onDragOver,
915
+ onDragLeave: () => setDragOver(false),
916
+ disabled: inert,
2146
917
  "data-testid": testId,
918
+ className: cn(
919
+ "flex w-full flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed border-border bg-muted/30 px-6 py-12 text-center transition-colors",
920
+ "hover:border-primary/50 hover:bg-muted/50",
921
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
922
+ dragOver && "border-primary bg-primary/5",
923
+ inert && "cursor-not-allowed opacity-60 hover:border-border hover:bg-muted/30",
924
+ className
925
+ ),
2147
926
  children: [
2148
- list.map((f, i) => {
2149
- const inputClass = "w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground outline-none focus:border-primary";
2150
- return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1.5 text-left", children: [
2151
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: f.label }),
2152
- f.type === "textarea" ? /* @__PURE__ */ jsx(
2153
- "textarea",
2154
- {
2155
- name: f.name,
2156
- required: f.required,
2157
- rows: 4,
2158
- className: inputClass,
2159
- "data-testid": testId && `${testId}-field-${f.name}`
2160
- }
2161
- ) : /* @__PURE__ */ jsx(
2162
- "input",
2163
- {
2164
- name: f.name,
2165
- type: f.type || "text",
2166
- required: f.required,
2167
- className: inputClass,
2168
- "data-testid": testId && `${testId}-field-${f.name}`
2169
- }
2170
- )
2171
- ] }, i);
2172
- }),
2173
927
  /* @__PURE__ */ jsx(
2174
- Button,
928
+ "input",
2175
929
  {
2176
- type: "submit",
2177
- className: cn("mt-1 self-start"),
2178
- "data-testid": testId && `${testId}-submit`,
2179
- children: submitLabel
930
+ ref: inputRef,
931
+ type: "file",
932
+ accept,
933
+ multiple,
934
+ className: "hidden",
935
+ onChange: (e) => {
936
+ pick(e.target.files);
937
+ if (inputRef.current) inputRef.current.value = "";
938
+ },
939
+ "data-testid": testId ? `${testId}-input` : void 0
2180
940
  }
2181
- )
941
+ ),
942
+ /* @__PURE__ */ jsx("div", { className: "flex size-12 items-center justify-center rounded-full bg-primary/10 text-primary", children: uploading ? /* @__PURE__ */ jsx(Loader2, { className: "size-6 animate-spin" }) : /* @__PURE__ */ jsx(Icon, { className: "size-6" }) }),
943
+ uploading ? /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: uploadingLabel }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
944
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-foreground", children: [
945
+ title,
946
+ " ",
947
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: description })
948
+ ] }),
949
+ hint && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: hint })
950
+ ] })
2182
951
  ]
2183
952
  }
2184
953
  );
2185
954
  }
2186
- var TONE = {
2187
- primary: "bg-primary text-primary-foreground",
2188
- muted: "bg-muted text-foreground",
2189
- ink: "bg-foreground text-background"
2190
- };
2191
- function Banner({ text, cta, tone = "primary", testId }) {
955
+ function PageHeader({
956
+ title,
957
+ eyebrow,
958
+ description,
959
+ actions,
960
+ className,
961
+ testId
962
+ }) {
2192
963
  return /* @__PURE__ */ jsxs(
2193
964
  "div",
2194
965
  {
2195
- className: cn("w-full px-5 py-2.5 text-center text-sm", TONE[tone] ?? TONE.primary),
966
+ className: cn("flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between", className),
2196
967
  "data-testid": testId,
2197
968
  children: [
2198
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: text }),
2199
- cta?.label && /* @__PURE__ */ jsxs(
2200
- "a",
2201
- {
2202
- href: cta.href || "#",
2203
- className: "ml-2 inline-flex items-center gap-1 font-semibold underline underline-offset-4",
2204
- "data-testid": testId && `${testId}-cta`,
2205
- children: [
2206
- cta.label,
2207
- /* @__PURE__ */ jsx(ArrowRight, { className: "size-3.5" })
2208
- ]
2209
- }
2210
- )
969
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
970
+ eyebrow && /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: eyebrow }),
971
+ /* @__PURE__ */ jsx("h1", { className: "font-display text-2xl font-semibold tracking-tight text-foreground", children: title }),
972
+ description && /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-2xl text-sm text-muted-foreground", children: description })
973
+ ] }),
974
+ actions && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: actions })
2211
975
  ]
2212
976
  }
2213
977
  );
2214
978
  }
2215
- function MediaText({
2216
- eyebrow,
2217
- title,
2218
- body,
2219
- imageUrl,
2220
- mediaSide = "left",
2221
- cta,
979
+ function Toolbar({
980
+ search,
981
+ onSearch,
982
+ searchPlaceholder = "Search\u2026",
983
+ filters,
984
+ actions,
985
+ className,
2222
986
  testId
2223
987
  }) {
2224
- const media = /* @__PURE__ */ jsx(BlockImage, { src: imageUrl, aspect: "4:3", rounded: "lg", testId: testId && `${testId}-image` });
2225
- const copy = /* @__PURE__ */ jsxs("div", { children: [
2226
- eyebrow && /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: eyebrow }),
2227
- /* @__PURE__ */ jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance", children: title }),
2228
- body && /* @__PURE__ */ jsx("p", { className: "mt-4 text-lg leading-relaxed text-muted-foreground", children: body }),
2229
- cta?.label && /* @__PURE__ */ jsx(Button, { asChild: true, className: "mt-6", children: /* @__PURE__ */ jsxs("a", { href: cta.href || "#", children: [
2230
- cta.label,
2231
- /* @__PURE__ */ jsx(ArrowRight, { className: "size-4" })
2232
- ] }) })
2233
- ] });
2234
- return /* @__PURE__ */ jsx("div", { className: "grid items-center gap-10 lg:grid-cols-2", "data-testid": testId, children: mediaSide === "right" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2235
- copy,
2236
- media
2237
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2238
- media,
2239
- copy
2240
- ] }) });
2241
- }
2242
- var COLS3 = {
2243
- "2": "sm:grid-cols-2",
2244
- "3": "sm:grid-cols-2 lg:grid-cols-3",
2245
- "4": "sm:grid-cols-2 lg:grid-cols-4"
2246
- };
2247
- function TeamGrid({ members, columns = "3", testId }) {
2248
- return /* @__PURE__ */ jsx("div", { className: cn("grid gap-6", COLS3[columns] ?? COLS3["3"]), "data-testid": testId, children: (members ?? []).map((m, i) => /* @__PURE__ */ jsxs(
2249
- Card,
988
+ return /* @__PURE__ */ jsxs(
989
+ "div",
2250
990
  {
2251
- variant: "outline",
2252
- className: "flex h-full flex-col items-center p-6 text-center",
2253
- "data-testid": testId && `${testId}-item-${i}`,
991
+ className: cn(
992
+ "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
993
+ className
994
+ ),
995
+ "data-testid": testId,
2254
996
  children: [
2255
- /* @__PURE__ */ jsxs(Avatar, { className: "size-20", children: [
2256
- m.avatarUrl && /* @__PURE__ */ jsx(AvatarImage, { src: m.avatarUrl, alt: m.name }),
2257
- /* @__PURE__ */ jsx(AvatarFallback, { children: initials(m.name || "?") })
997
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-2", children: [
998
+ onSearch && /* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-xs", children: [
999
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }),
1000
+ /* @__PURE__ */ jsx(
1001
+ Input,
1002
+ {
1003
+ value: search,
1004
+ onChange: (e) => onSearch(e.target.value),
1005
+ placeholder: searchPlaceholder,
1006
+ className: "bg-card text-sm placeholder:text-muted-foreground/70 focus-visible:border-ring/60 focus-visible:ring-2 focus-visible:ring-ring/50 pl-8",
1007
+ "data-testid": testId ? `${testId}-search` : "toolbar-search"
1008
+ }
1009
+ )
1010
+ ] }),
1011
+ filters
2258
1012
  ] }),
2259
- /* @__PURE__ */ jsx("h3", { className: "mt-4 text-base font-semibold tracking-tight", children: m.name }),
2260
- m.role && /* @__PURE__ */ jsx("p", { className: "text-sm text-primary", children: m.role }),
2261
- m.bio && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: m.bio })
1013
+ actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: actions })
2262
1014
  ]
2263
- },
2264
- i
2265
- )) });
2266
- }
2267
- function Steps({ steps, variant = "numbered", testId }) {
2268
- const list = steps ?? [];
2269
- if (variant === "timeline") {
2270
- return /* @__PURE__ */ jsx("ol", { className: "relative ml-3 border-l border-border", "data-testid": testId, children: list.map((s, i) => /* @__PURE__ */ jsxs("li", { className: "mb-8 ml-6 last:mb-0", "data-testid": testId && `${testId}-item-${i}`, children: [
2271
- /* @__PURE__ */ 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 }),
2272
- /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold tracking-tight", children: s.title }),
2273
- s.description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm leading-relaxed text-muted-foreground", children: s.description })
2274
- ] }, i)) });
2275
- }
2276
- return /* @__PURE__ */ jsx(
2277
- "div",
2278
- {
2279
- className: cn("grid gap-6", list.length >= 4 ? "md:grid-cols-4" : "sm:grid-cols-3"),
2280
- "data-testid": testId,
2281
- children: list.map((s, i) => /* @__PURE__ */ jsxs("div", { "data-testid": testId && `${testId}-item-${i}`, children: [
2282
- /* @__PURE__ */ jsx("span", { className: "flex size-9 items-center justify-center rounded-full bg-primary/10 text-sm font-semibold text-primary", children: i + 1 }),
2283
- /* @__PURE__ */ jsx("h3", { className: "mt-4 text-base font-semibold tracking-tight", children: s.title }),
2284
- s.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: s.description })
2285
- ] }, i))
2286
1015
  }
2287
1016
  );
2288
1017
  }
2289
- var COLS4 = {
2290
- "2": "sm:grid-cols-2",
2291
- "3": "sm:grid-cols-2 lg:grid-cols-3"
2292
- };
2293
- function TestimonialGrid({ items, columns = "3", testId }) {
2294
- return /* @__PURE__ */ jsx("div", { className: cn("grid gap-6", COLS4[columns] ?? COLS4["3"]), "data-testid": testId, children: (items ?? []).map((t, i) => /* @__PURE__ */ jsxs(
1018
+ function MetricCard({
1019
+ label,
1020
+ value,
1021
+ icon: Icon,
1022
+ hint,
1023
+ delta,
1024
+ accent = "default",
1025
+ testId
1026
+ }) {
1027
+ const accentStyle = accent && accent !== "default" ? { color: `var(--color-${accent})` } : void 0;
1028
+ const deltaGood = delta?.good ?? delta?.direction === "up";
1029
+ return /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: "card-hover rounded-xl p-5", "data-testid": testId, children: [
1030
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
1031
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: label }),
1032
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0", style: accentStyle })
1033
+ ] }),
1034
+ /* @__PURE__ */ jsx(
1035
+ "p",
1036
+ {
1037
+ className: "mt-2 font-display text-2xl font-semibold tracking-tight",
1038
+ "data-testid": testId && `${testId}-value`,
1039
+ children: value
1040
+ }
1041
+ ),
1042
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
1043
+ delta && /* @__PURE__ */ jsxs(
1044
+ "span",
1045
+ {
1046
+ className: cn(
1047
+ "inline-flex items-center gap-0.5 text-xs font-medium",
1048
+ deltaGood ? "text-success" : "text-destructive"
1049
+ ),
1050
+ children: [
1051
+ delta.direction === "up" ? /* @__PURE__ */ jsx(ArrowUpRight, { className: "size-3" }) : /* @__PURE__ */ jsx(ArrowDownRight, { className: "size-3" }),
1052
+ delta.value
1053
+ ]
1054
+ }
1055
+ ),
1056
+ hint && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: hint })
1057
+ ] })
1058
+ ] });
1059
+ }
1060
+ var MAX_AVATARS = 4;
1061
+ function WorkspaceCard({
1062
+ name,
1063
+ matterRef,
1064
+ description,
1065
+ docCount,
1066
+ memberCount,
1067
+ members = [],
1068
+ updatedAt,
1069
+ active = false,
1070
+ busy = false,
1071
+ onOpen,
1072
+ icon: Icon = FolderLock,
1073
+ testId,
1074
+ buttonTestId,
1075
+ className
1076
+ }) {
1077
+ const totalMembers = memberCount ?? members.length;
1078
+ const shownMembers = members.slice(0, MAX_AVATARS);
1079
+ const extraMembers = totalMembers - shownMembers.length;
1080
+ const btnTestId = buttonTestId ?? (testId ? `${testId}-open` : void 0);
1081
+ return /* @__PURE__ */ jsxs(
2295
1082
  Card,
2296
1083
  {
2297
1084
  variant: "outline",
2298
- className: "flex h-full flex-col p-6",
2299
- "data-testid": testId && `${testId}-item-${i}`,
1085
+ className: cn("card-hover flex flex-col p-5", className),
1086
+ "data-testid": testId,
2300
1087
  children: [
2301
- /* @__PURE__ */ jsx(Quote, { className: "size-6 text-primary/40", "aria-hidden": true }),
2302
- /* @__PURE__ */ jsx("blockquote", { className: "mt-3 flex-1 text-base leading-relaxed", children: t.quote }),
2303
- (t.author || t.avatarUrl) && /* @__PURE__ */ jsxs("div", { className: "mt-5 flex items-center gap-3", children: [
2304
- /* @__PURE__ */ jsxs(Avatar, { className: "size-9", children: [
2305
- t.avatarUrl && /* @__PURE__ */ jsx(AvatarImage, { src: t.avatarUrl, alt: t.author ?? "" }),
2306
- /* @__PURE__ */ jsx(AvatarFallback, { children: initials(t.author || "?") })
1088
+ /* @__PURE__ */ jsxs(CardHeader, { className: "p-0", children: [
1089
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
1090
+ /* @__PURE__ */ jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(Icon, { className: "size-5" }) }),
1091
+ active ? /* @__PURE__ */ jsxs(Badge, { children: [
1092
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "mr-1 size-3" }),
1093
+ " Active"
1094
+ ] }) : /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: "Encrypted" })
1095
+ ] }),
1096
+ /* @__PURE__ */ jsx(CardTitle, { className: "mt-3 text-base", children: name }),
1097
+ matterRef && /* @__PURE__ */ jsx(CardDescription, { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: matterRef })
1098
+ ] }),
1099
+ /* @__PURE__ */ jsxs(CardContent, { className: "mt-3 flex-1 space-y-4 p-0", children: [
1100
+ description && /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-sm text-muted-foreground", children: description }),
1101
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 text-sm", children: [
1102
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-foreground", children: [
1103
+ /* @__PURE__ */ jsx(FileText, { className: "size-4 text-muted-foreground" }),
1104
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: docCount.toLocaleString() }),
1105
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "docs" })
1106
+ ] }),
1107
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-foreground", children: [
1108
+ /* @__PURE__ */ jsx(Users, { className: "size-4 text-muted-foreground" }),
1109
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: totalMembers }),
1110
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "members" })
1111
+ ] })
2307
1112
  ] }),
2308
- /* @__PURE__ */ jsxs("div", { children: [
2309
- t.author && /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: t.author }),
2310
- t.role && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t.role })
1113
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1114
+ /* @__PURE__ */ jsxs("div", { className: "flex -space-x-2", children: [
1115
+ shownMembers.map((m, i) => /* @__PURE__ */ jsxs(Avatar, { className: "size-7 border-2 border-card", title: m.name, children: [
1116
+ m.picture && /* @__PURE__ */ jsx(AvatarImage, { src: m.picture, alt: m.name }),
1117
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-foreground text-[10px] font-medium text-background", children: initials(m.name || "?") })
1118
+ ] }, i)),
1119
+ extraMembers > 0 && /* @__PURE__ */ jsx(Avatar, { className: "size-7 border-2 border-card", children: /* @__PURE__ */ jsxs(AvatarFallback, { className: "bg-accent text-[10px] font-medium text-muted-foreground", children: [
1120
+ "+",
1121
+ extraMembers
1122
+ ] }) })
1123
+ ] }),
1124
+ updatedAt && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", title: fmtDate(updatedAt), children: [
1125
+ "Updated ",
1126
+ fromNow(updatedAt)
1127
+ ] })
2311
1128
  ] })
2312
- ] })
1129
+ ] }),
1130
+ /* @__PURE__ */ jsx(CardFooter, { className: "mt-4 p-0", children: active ? /* @__PURE__ */ jsxs(Button, { variant: "outline", className: "w-full", disabled: true, "data-testid": btnTestId, children: [
1131
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "size-4" }),
1132
+ " Current workspace"
1133
+ ] }) : /* @__PURE__ */ jsxs(Button, { className: "w-full", onClick: onOpen, disabled: busy, "data-testid": btnTestId, children: [
1134
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(ArrowLeftRight, { className: "size-4" }),
1135
+ "Open"
1136
+ ] }) })
2313
1137
  ]
2314
- },
2315
- i
2316
- )) });
1138
+ }
1139
+ );
1140
+ }
1141
+ var GRID = {
1142
+ 1: "grid-cols-1",
1143
+ 2: "grid-cols-2",
1144
+ 3: "grid-cols-2 sm:grid-cols-3",
1145
+ 4: "grid-cols-2 lg:grid-cols-4"
1146
+ };
1147
+ function StatGroup({ items, testId }) {
1148
+ const list = items ?? [];
1149
+ const cols = GRID[Math.min(list.length, 4)] ?? GRID[4];
1150
+ return /* @__PURE__ */ jsx("dl", { className: cn("grid gap-8", cols), "data-testid": testId, children: list.map((item, i) => /* @__PURE__ */ jsxs("div", { className: "text-center", "data-testid": testId && `${testId}-item-${i}`, children: [
1151
+ /* @__PURE__ */ jsx("dt", { className: "sr-only", children: item.label }),
1152
+ /* @__PURE__ */ jsx("dd", { className: "text-4xl font-semibold tracking-tight sm:text-5xl", children: item.value }),
1153
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: item.label }),
1154
+ item.sub && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground/80", children: item.sub })
1155
+ ] }, i)) });
2317
1156
  }
2318
1157
  function NavItemLink({ item, onNavigate }) {
2319
1158
  return /* @__PURE__ */ jsxs(
@@ -2386,85 +1225,90 @@ function AppShell({
2386
1225
  onNavigate
2387
1226
  }
2388
1227
  );
2389
- return /* @__PURE__ */ jsxs(
2390
- "div",
2391
- {
2392
- className: "flex h-screen overflow-hidden bg-background",
2393
- "data-testid": `${testIdPrefix}-shell`,
2394
- children: [
2395
- /* @__PURE__ */ jsx(
2396
- "aside",
2397
- {
2398
- className: "hidden h-full w-64 shrink-0 flex-col border-r border-sidebar-border bg-sidebar lg:flex",
2399
- "data-testid": `${testIdPrefix}-sidebar`,
2400
- children: body()
2401
- }
2402
- ),
2403
- open && /* @__PURE__ */ jsxs(
2404
- "div",
2405
- {
2406
- className: "fixed inset-0 z-50 lg:hidden",
2407
- "data-testid": `${testIdPrefix}-sidebar-mobile`,
2408
- children: [
2409
- /* @__PURE__ */ jsx(
2410
- "div",
2411
- {
2412
- className: "absolute inset-0 bg-black/60 backdrop-blur-sm",
2413
- onClick: () => setOpen(false),
2414
- "aria-hidden": true
2415
- }
2416
- ),
2417
- /* @__PURE__ */ jsxs("aside", { className: "absolute inset-y-0 left-0 flex w-64 max-w-[82%] flex-col border-r border-sidebar-border bg-sidebar shadow-xl", children: [
2418
- /* @__PURE__ */ jsx(
2419
- "button",
2420
- {
2421
- type: "button",
2422
- onClick: () => setOpen(false),
2423
- className: "absolute right-3 top-4 z-10 flex size-8 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground",
2424
- "aria-label": "Close menu",
2425
- "data-testid": `${testIdPrefix}-sidebar-close`,
2426
- children: /* @__PURE__ */ jsx(X, { className: "size-5" })
2427
- }
2428
- ),
2429
- body(() => setOpen(false))
2430
- ] })
2431
- ]
2432
- }
2433
- ),
2434
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
2435
- /* @__PURE__ */ jsxs(
2436
- "header",
1228
+ return (
1229
+ // One TooltipProvider for the whole back office: every kit component that
1230
+ // renders a tooltip (entity pills, document tiles, toolbars) inherits the
1231
+ // same hover delay, and no app has to remember to mount it.
1232
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(
1233
+ "div",
1234
+ {
1235
+ className: "flex h-screen overflow-hidden bg-background",
1236
+ "data-testid": `${testIdPrefix}-shell`,
1237
+ children: [
1238
+ /* @__PURE__ */ jsx(
1239
+ "aside",
1240
+ {
1241
+ className: "hidden h-full w-64 shrink-0 flex-col border-r border-sidebar-border bg-sidebar lg:flex",
1242
+ "data-testid": `${testIdPrefix}-sidebar`,
1243
+ children: body()
1244
+ }
1245
+ ),
1246
+ open && /* @__PURE__ */ jsxs(
1247
+ "div",
2437
1248
  {
2438
- className: "flex h-16 shrink-0 items-center gap-4 border-b border-border bg-card/80 px-6 backdrop-blur",
2439
- "data-testid": `${testIdPrefix}-topbar`,
1249
+ className: "fixed inset-0 z-50 lg:hidden",
1250
+ "data-testid": `${testIdPrefix}-sidebar-mobile`,
2440
1251
  children: [
2441
1252
  /* @__PURE__ */ jsx(
2442
- "button",
1253
+ "div",
2443
1254
  {
2444
- type: "button",
2445
- onClick: () => setOpen(true),
2446
- className: "flex size-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground lg:hidden",
2447
- "data-testid": `${testIdPrefix}-nav-toggle`,
2448
- "aria-label": "Open menu",
2449
- children: /* @__PURE__ */ jsx(Menu, { className: "size-5" })
1255
+ className: "absolute inset-0 bg-black/60 backdrop-blur-sm",
1256
+ onClick: () => setOpen(false),
1257
+ "aria-hidden": true
2450
1258
  }
2451
1259
  ),
2452
- topbar
1260
+ /* @__PURE__ */ jsxs("aside", { className: "absolute inset-y-0 left-0 flex w-64 max-w-[82%] flex-col border-r border-sidebar-border bg-sidebar shadow-xl", children: [
1261
+ /* @__PURE__ */ jsx(
1262
+ "button",
1263
+ {
1264
+ type: "button",
1265
+ onClick: () => setOpen(false),
1266
+ className: "absolute right-3 top-4 z-10 flex size-8 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground",
1267
+ "aria-label": "Close menu",
1268
+ "data-testid": `${testIdPrefix}-sidebar-close`,
1269
+ children: /* @__PURE__ */ jsx(X, { className: "size-5" })
1270
+ }
1271
+ ),
1272
+ body(() => setOpen(false))
1273
+ ] })
2453
1274
  ]
2454
1275
  }
2455
1276
  ),
2456
- /* @__PURE__ */ jsx(
2457
- "main",
2458
- {
2459
- className: cn("flex-1 overflow-y-auto", mainClassName),
2460
- "data-testid": `${testIdPrefix}-main`,
2461
- children
2462
- }
2463
- )
2464
- ] }),
2465
- rightRail
2466
- ]
2467
- }
1277
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
1278
+ /* @__PURE__ */ jsxs(
1279
+ "header",
1280
+ {
1281
+ className: "flex h-16 shrink-0 items-center gap-4 border-b border-border bg-card/80 px-6 backdrop-blur",
1282
+ "data-testid": `${testIdPrefix}-topbar`,
1283
+ children: [
1284
+ /* @__PURE__ */ jsx(
1285
+ "button",
1286
+ {
1287
+ type: "button",
1288
+ onClick: () => setOpen(true),
1289
+ className: "flex size-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground lg:hidden",
1290
+ "data-testid": `${testIdPrefix}-nav-toggle`,
1291
+ "aria-label": "Open menu",
1292
+ children: /* @__PURE__ */ jsx(Menu, { className: "size-5" })
1293
+ }
1294
+ ),
1295
+ topbar
1296
+ ]
1297
+ }
1298
+ ),
1299
+ /* @__PURE__ */ jsx(
1300
+ "main",
1301
+ {
1302
+ className: cn("flex-1 overflow-y-auto", mainClassName),
1303
+ "data-testid": `${testIdPrefix}-main`,
1304
+ children
1305
+ }
1306
+ )
1307
+ ] }),
1308
+ rightRail
1309
+ ]
1310
+ }
1311
+ ) })
2468
1312
  );
2469
1313
  }
2470
1314
  function AppHeader({
@@ -2699,6 +1543,32 @@ function createLiveWidgets(kit) {
2699
1543
  LiveMetric: LiveMetricWidget
2700
1544
  };
2701
1545
  }
1546
+ function useAssetImage(docId) {
1547
+ const arbi = useArbi();
1548
+ const { data: thumbs } = useThumbnails([docId]);
1549
+ const thumb = thumbs?.[docId];
1550
+ const { data: objectUrl } = useQuery({
1551
+ queryKey: ["arbi", "asset-image", docId],
1552
+ queryFn: async () => {
1553
+ const res = await arbi.documents.download(docId);
1554
+ const blob = await res.blob();
1555
+ return URL.createObjectURL(blob);
1556
+ },
1557
+ // Only download when the thumbnail is genuinely absent (not merely loading).
1558
+ enabled: !!docId && thumbs != null && !thumb && arbi.hasWorkspace,
1559
+ staleTime: 10 * 6e4
1560
+ });
1561
+ useEffect(() => {
1562
+ return () => {
1563
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
1564
+ };
1565
+ }, [objectUrl]);
1566
+ return thumb ?? objectUrl;
1567
+ }
1568
+ function AssetThumb({ docId, alt, className, testId }) {
1569
+ const src = useAssetImage(docId);
1570
+ return /* @__PURE__ */ jsx("img", { src: src ?? void 0, alt: alt ?? "", className, "data-testid": testId });
1571
+ }
2702
1572
 
2703
1573
  // src/studio/publish.ts
2704
1574
  function blobToDataUrl(blob) {
@@ -2817,7 +1687,7 @@ function StudioEditor({
2817
1687
  delete w.__studioResetSlug;
2818
1688
  };
2819
1689
  }, [resetSeam, arbi, live, persistence]);
2820
- const persist4 = useCallback(
1690
+ const persist3 = useCallback(
2821
1691
  async (toSave) => {
2822
1692
  setSaving(true);
2823
1693
  try {
@@ -2910,7 +1780,7 @@ function StudioEditor({
2910
1780
  "button",
2911
1781
  {
2912
1782
  type: "button",
2913
- onClick: () => void persist4(latest.current),
1783
+ onClick: () => void persist3(latest.current),
2914
1784
  disabled: saving || loading,
2915
1785
  className: "inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-60",
2916
1786
  "data-testid": `${testIdPrefix}-publish`,
@@ -2935,7 +1805,7 @@ function StudioEditor({
2935
1805
  "\u201D\u2026"
2936
1806
  ]
2937
1807
  }
2938
- ) : /* @__PURE__ */ jsx(AssetResolverProvider, { children: /* @__PURE__ */ jsx(
1808
+ ) : /* @__PURE__ */ jsx(AssetResolverProvider, { render: AssetThumb, children: /* @__PURE__ */ jsx(
2939
1809
  Puck,
2940
1810
  {
2941
1811
  config,
@@ -2943,7 +1813,7 @@ function StudioEditor({
2943
1813
  onChange: (d) => {
2944
1814
  latest.current = d;
2945
1815
  },
2946
- onPublish: (d) => void persist4(d)
1816
+ onPublish: (d) => void persist3(d)
2947
1817
  },
2948
1818
  `${slug}-${rev}`
2949
1819
  ) }) }),
@@ -3564,13 +2434,13 @@ var TEXTURE_STYLE = {
3564
2434
  backgroundSize: "4px 4px",
3565
2435
  opacity: 0.04
3566
2436
  };
3567
- var PADDING2 = {
2437
+ var PADDING = {
3568
2438
  none: "",
3569
2439
  sm: "py-8 lg:py-10",
3570
2440
  md: "py-16 lg:py-20",
3571
2441
  lg: "py-24 lg:py-28"
3572
2442
  };
3573
- var WIDTH2 = {
2443
+ var WIDTH = {
3574
2444
  narrow: "max-w-3xl",
3575
2445
  default: "max-w-7xl",
3576
2446
  wide: "max-w-[90rem]",
@@ -3602,8 +2472,8 @@ function Section({
3602
2472
  {
3603
2473
  className: cn(
3604
2474
  "relative mx-auto px-5 lg:px-8",
3605
- WIDTH2[maxWidth],
3606
- PADDING2[padding],
2475
+ WIDTH[maxWidth],
2476
+ PADDING[padding],
3607
2477
  className
3608
2478
  ),
3609
2479
  children
@@ -3614,13 +2484,13 @@ function Section({
3614
2484
  var iconMap = {
3615
2485
  FileText: FileText,
3616
2486
  Folder,
3617
- Users,
2487
+ Users: Users,
3618
2488
  Briefcase,
3619
2489
  Calendar,
3620
2490
  Clock,
3621
2491
  CheckCircle,
3622
2492
  AlertCircle,
3623
- Info: Info,
2493
+ Info,
3624
2494
  TrendingUp,
3625
2495
  DollarSign,
3626
2496
  BarChart3,
@@ -4374,7 +3244,7 @@ function createSiteBlocks(kit) {
4374
3244
  { label: "FAQ", content: "Answers to common questions." }
4375
3245
  ]
4376
3246
  },
4377
- render: ({ id, items }) => /* @__PURE__ */ jsx(Tabs, { items, testId: blockTestId(id) })
3247
+ render: ({ id, items }) => /* @__PURE__ */ jsx(Tabs$1, { items, testId: blockTestId(id) })
4378
3248
  };
4379
3249
  const AccordionBlock = {
4380
3250
  label: "Accordion",
@@ -5543,7 +4413,7 @@ function VerticalBuilder({
5543
4413
  }) {
5544
4414
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", "data-testid": `page-${testIdPrefix}`, children: [
5545
4415
  header,
5546
- /* @__PURE__ */ jsxs(Tabs$1, { defaultValue: "theme", children: [
4416
+ /* @__PURE__ */ jsxs(Tabs, { defaultValue: "theme", children: [
5547
4417
  /* @__PURE__ */ jsxs(TabsList, { "data-testid": `${testIdPrefix}-tabs`, children: [
5548
4418
  /* @__PURE__ */ jsxs(TabsTrigger, { value: "theme", "data-testid": `${testIdPrefix}-tab-theme`, children: [
5549
4419
  /* @__PURE__ */ jsx(Palette, { className: "size-4" }),
@@ -5587,6 +4457,6 @@ function VerticalBuilder({
5587
4457
  ] });
5588
4458
  }
5589
4459
 
5590
- export { ASSET_REF_PREFIX, Accordion, AgentsPanel, AppHeader, AppShell, AssetResolverProvider, AvatarBlock, Banner, BlockImage, CTABanner, Callout, Columns, ConnectionProvider, ContactForm, Container, DataTable, DataTableBlock, EmptyState, FAQ, FeatureGrid, Footer, Gallery, GridView, Hero, LegalArbiProvider, ListBlock, LiveDataProvider, LogoCloud, MediaText, MetricCard, ModuleManager, Navbar, PageHeader, PageRenderer, PricingTable, RichTextBlock, Section, SectionHeading, Spacer, StatGroup, Steps, StudioEditor, THEME_STYLE_ID, Tabs, TeamGrid, Testimonial, TestimonialGrid, ThemeEditor, Toolbar, VerticalBuilder, VideoEmbed, WidgetPlaceholder, applyStoredTheme, applyThemeVars, asAssetRef, assetRefId, clearThemeVars, createAgentSelectionStore, createAiFields, createArbiBlocksConfig, createArbiMaterializer, createBlockKit, createImageField, createLiveWidgets, createModuleRegistry, createModuleStore, createPromptLibrary, createSiteBlocks, createStudioPersistence, createThemeStore, createVerticalExport, createWebsiteBlocks, daysUntil, fmtDate, fmtDateTime, fromNow, gbp, hrs, iconMap, initials, isAssetRef, isAuthenticated, materializePageData, matterToContext, pct, tid, toEmbedUrl, toHslTriplet, useAssetResolverActive, useConnection, useFirmAiTask, useImageGen, useLiveDataActive, useSemanticSearch, useWorkspaceDocs };
4460
+ export { AgentsPanel, AppHeader, AppShell, AssetThumb, ConnectionProvider, DataTable, DataTableBlock, EmptyState, GridView, LegalArbiProvider, LiveDataProvider, MetricCard, ModuleManager, PageHeader, PageRenderer, Section, StatGroup, StudioEditor, Toolbar, UploadDropzone, VerticalBuilder, WidgetPlaceholder, WorkspaceCard, createAgentSelectionStore, createAiFields, createArbiBlocksConfig, createArbiMaterializer, createBlockKit, createImageField, createLiveWidgets, createModuleRegistry, createModuleStore, createPromptLibrary, createSiteBlocks, createStudioPersistence, createVerticalExport, createWebsiteBlocks, iconMap, isAuthenticated, materializePageData, matterToContext, useAssetImage, useConnection, useFirmAiTask, useImageGen, useLiveDataActive, useSemanticSearch, useWorkspaceDocs };
5591
4461
  //# sourceMappingURL=index.js.map
5592
4462
  //# sourceMappingURL=index.js.map