@arbidocs/blocks 0.3.110 → 0.3.112

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,8 +1,8 @@
1
1
  import { create } from 'zustand';
2
2
  import { persist } from 'zustand/middleware';
3
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, ArrowLeft, Loader2, ChevronUp, BrainCircuit, Bot, LayoutGrid, FileJson, Check, Save, Download } 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, Badge, Switch, useConfigs, useAgents, Checkbox, Tabs, TabsList, TabsTrigger, TabsContent, Separator, ArbiWebSocketProvider } from '@arbidocs/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, ArrowLeft, Loader2, ChevronUp, BrainCircuit, Bot, LayoutGrid, FileJson, Save, Download, ImagePlus, Upload, Link2 } 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, Badge, Switch, useConfigs, useAgents, Checkbox, Tabs as Tabs$1, TabsList, TabsTrigger, TabsContent, Separator, ArbiWebSocketProvider, useThumbnails, useDocuments } from '@arbidocs/react';
6
6
  export { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, redlineStats, textToArtifact, toRedlineMarkdown } from '@arbidocs/react';
7
7
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
8
8
  import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
@@ -808,7 +808,7 @@ function EmptyState({
808
808
  "data-testid": testId,
809
809
  children: [
810
810
  Icon && /* @__PURE__ */ jsx("div", { className: "mb-3 flex size-11 items-center justify-center rounded-full bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { className: "size-5" }) }),
811
- /* @__PURE__ */ jsx("p", { className: "font-display text-base font-semibold text-ink", children: title }),
811
+ /* @__PURE__ */ jsx("p", { className: "font-display text-base font-semibold text-foreground", children: title }),
812
812
  description && /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: description }),
813
813
  action && /* @__PURE__ */ jsx("div", { className: "mt-4", children: action })
814
814
  ]
@@ -1176,10 +1176,10 @@ function GridView({
1176
1176
  const BORDER = 2;
1177
1177
  const PREFERRED_PAD = 16;
1178
1178
  const padTotal = Math.max(0, Math.min(PREFERRED_PAD, rowHeight - BORDER - LINE_PX));
1179
- const lines = Math.max(1, Math.floor((rowHeight - BORDER - padTotal) / LINE_PX));
1179
+ const lines2 = Math.max(1, Math.floor((rowHeight - BORDER - padTotal) / LINE_PX));
1180
1180
  const vars = style;
1181
1181
  vars["--abstract-pad-y"] = `${padTotal / 2}px`;
1182
- vars["--abstract-max-h"] = `${lines * LINE_PX + padTotal + BORDER}px`;
1182
+ vars["--abstract-max-h"] = `${lines2 * LINE_PX + padTotal + BORDER}px`;
1183
1183
  }
1184
1184
  return style;
1185
1185
  }, [height, rowHeight]);
@@ -1301,8 +1301,8 @@ function PageHeader({
1301
1301
  "data-testid": testId,
1302
1302
  children: [
1303
1303
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1304
- eyebrow && /* @__PURE__ */ jsx("p", { className: "eyebrow mb-1", children: eyebrow }),
1305
- /* @__PURE__ */ jsx("h1", { className: "font-display text-2xl font-semibold tracking-tight text-ink", children: title }),
1304
+ eyebrow && /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: eyebrow }),
1305
+ /* @__PURE__ */ jsx("h1", { className: "font-display text-2xl font-semibold tracking-tight text-foreground", children: title }),
1306
1306
  description && /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-2xl text-sm text-muted-foreground", children: description })
1307
1307
  ] }),
1308
1308
  actions && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: actions })
@@ -1316,16 +1316,26 @@ function SectionHeading({
1316
1316
  lead,
1317
1317
  align = "left",
1318
1318
  className,
1319
- invert
1319
+ invert,
1320
+ eyebrowClassName
1320
1321
  }) {
1321
1322
  return /* @__PURE__ */ jsxs("div", { className: cn("max-w-3xl", align === "center" && "mx-auto text-center", className), children: [
1322
- eyebrow && /* @__PURE__ */ jsx("p", { className: "eyebrow mb-3", children: eyebrow }),
1323
+ eyebrow && /* @__PURE__ */ jsx(
1324
+ "p",
1325
+ {
1326
+ className: cn(
1327
+ "mb-3 text-xs font-semibold uppercase tracking-wider",
1328
+ eyebrowClassName ?? (invert ? "text-background/60" : "text-muted-foreground")
1329
+ ),
1330
+ children: eyebrow
1331
+ }
1332
+ ),
1323
1333
  /* @__PURE__ */ jsx(
1324
1334
  "h2",
1325
1335
  {
1326
1336
  className: cn(
1327
1337
  "font-display text-3xl font-semibold tracking-tight text-balance sm:text-4xl",
1328
- invert ? "text-parchment" : "text-ink"
1338
+ invert ? "text-background" : "text-foreground"
1329
1339
  ),
1330
1340
  children: title
1331
1341
  }
@@ -1335,7 +1345,7 @@ function SectionHeading({
1335
1345
  {
1336
1346
  className: cn(
1337
1347
  "mt-4 text-lg leading-relaxed",
1338
- invert ? "text-parchment/70" : "text-muted-foreground"
1348
+ invert ? "text-background/70" : "text-muted-foreground"
1339
1349
  ),
1340
1350
  children: lead
1341
1351
  }
@@ -1387,15 +1397,15 @@ function MetricCard({
1387
1397
  icon: Icon,
1388
1398
  hint,
1389
1399
  delta,
1390
- accent = "ink",
1400
+ accent = "default",
1391
1401
  testId
1392
1402
  }) {
1393
- const accentColor = accent === "emerald" ? "text-emerald" : accent === "brass" ? "text-[color:var(--color-brass)]" : "text-ink";
1403
+ const accentStyle = accent && accent !== "default" ? { color: `var(--color-${accent})` } : void 0;
1394
1404
  const deltaGood = delta?.good ?? delta?.direction === "up";
1395
1405
  return /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: "card-hover rounded-xl p-5", "data-testid": testId, children: [
1396
1406
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
1397
1407
  /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: label }),
1398
- Icon && /* @__PURE__ */ jsx(Icon, { className: cn("size-4 shrink-0", accentColor) })
1408
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0", style: accentStyle })
1399
1409
  ] }),
1400
1410
  /* @__PURE__ */ jsx(
1401
1411
  "p",
@@ -1423,6 +1433,33 @@ function MetricCard({
1423
1433
  ] })
1424
1434
  ] });
1425
1435
  }
1436
+ var ASSET_REF_PREFIX = "asset:";
1437
+ function asAssetRef(docId) {
1438
+ return `${ASSET_REF_PREFIX}${docId}`;
1439
+ }
1440
+ function assetRefId(src) {
1441
+ return src.slice(ASSET_REF_PREFIX.length);
1442
+ }
1443
+ var AssetResolverContext = createContext(false);
1444
+ function AssetResolverProvider({ children }) {
1445
+ return /* @__PURE__ */ jsx(AssetResolverContext.Provider, { value: true, children });
1446
+ }
1447
+ function useAssetResolverActive() {
1448
+ return useContext(AssetResolverContext);
1449
+ }
1450
+ function isAssetRef(src) {
1451
+ return !!src && src.startsWith(ASSET_REF_PREFIX);
1452
+ }
1453
+ function AssetThumb({
1454
+ docId,
1455
+ alt,
1456
+ className,
1457
+ testId
1458
+ }) {
1459
+ const { data } = useThumbnails([docId]);
1460
+ const src = data?.[docId];
1461
+ return /* @__PURE__ */ jsx("img", { src: src ?? void 0, alt: alt ?? "", className, "data-testid": testId });
1462
+ }
1426
1463
  var ASPECT = {
1427
1464
  "16:9": "aspect-video",
1428
1465
  "4:3": "aspect-[4/3]",
@@ -1441,11 +1478,28 @@ function BlockImage({
1441
1478
  alt = "",
1442
1479
  aspect = "16:9",
1443
1480
  rounded = "lg",
1481
+ framed,
1444
1482
  className,
1445
1483
  testId
1446
1484
  }) {
1447
- const shape = cn(ASPECT[aspect] ?? ASPECT["16:9"], ROUNDED[rounded] ?? ROUNDED.lg);
1448
- if (src) {
1485
+ const shape = cn(
1486
+ ASPECT[aspect] ?? ASPECT["16:9"],
1487
+ ROUNDED[rounded] ?? ROUNDED.lg,
1488
+ framed && "ring-1 ring-border shadow-xl"
1489
+ );
1490
+ const canResolve = useAssetResolverActive();
1491
+ if (canResolve && isAssetRef(src)) {
1492
+ return /* @__PURE__ */ jsx(
1493
+ AssetThumb,
1494
+ {
1495
+ docId: assetRefId(src),
1496
+ alt,
1497
+ className: cn("w-full object-cover", shape, className),
1498
+ testId
1499
+ }
1500
+ );
1501
+ }
1502
+ if (src && !isAssetRef(src)) {
1449
1503
  return /* @__PURE__ */ jsx(
1450
1504
  "img",
1451
1505
  {
@@ -1477,38 +1531,82 @@ var TONE_SURFACE = {
1477
1531
  muted: "bg-muted text-foreground",
1478
1532
  ink: "bg-foreground text-background"
1479
1533
  };
1534
+ function renderTitle(title, emphasis) {
1535
+ if (!emphasis || !title.includes(emphasis)) return title;
1536
+ const parts = title.split(emphasis);
1537
+ return parts.map((part, i) => /* @__PURE__ */ jsxs("span", { children: [
1538
+ part,
1539
+ i < parts.length - 1 && /* @__PURE__ */ jsx("span", { className: "text-primary", children: emphasis })
1540
+ ] }, i));
1541
+ }
1480
1542
  function Hero({
1481
1543
  eyebrow,
1482
1544
  title,
1545
+ emphasis,
1483
1546
  subtitle,
1484
1547
  primaryCta,
1485
1548
  secondaryCta,
1486
1549
  align = "left",
1487
1550
  tone = "default",
1488
1551
  imageUrl,
1552
+ backgroundImage,
1553
+ overlay = true,
1554
+ eyebrowClassName,
1489
1555
  testId
1490
1556
  }) {
1491
- const subtle = tone === "ink" ? "text-background/70" : "text-muted-foreground";
1492
- const eyebrowClass = tone === "ink" ? "text-background/60" : "text-muted-foreground";
1493
- const centered = align === "center";
1494
- const copy = /* @__PURE__ */ jsxs("div", { className: cn("max-w-2xl", centered && "mx-auto text-center"), children: [
1495
- eyebrow && /* @__PURE__ */ jsx("p", { className: cn("mb-4 text-sm font-semibold uppercase tracking-wider", eyebrowClass), children: eyebrow }),
1496
- /* @__PURE__ */ jsx("h1", { className: "text-4xl font-semibold tracking-tight text-balance sm:text-5xl", children: title }),
1497
- subtitle && /* @__PURE__ */ jsx("p", { className: cn("mt-6 text-lg leading-relaxed", subtle), children: subtitle }),
1498
- (primaryCta?.label || secondaryCta?.label) && /* @__PURE__ */ jsxs(
1499
- "div",
1557
+ const onDark = Boolean(backgroundImage) || tone === "ink";
1558
+ const subtle = onDark ? "text-background/70" : "text-muted-foreground";
1559
+ const eyebrowClass = eyebrowClassName ?? (onDark ? "text-background/60" : "text-muted-foreground");
1560
+ const centered = align === "center" || Boolean(backgroundImage);
1561
+ const heading = renderTitle(title, emphasis);
1562
+ const copy = /* @__PURE__ */ jsxs(
1563
+ "div",
1564
+ {
1565
+ className: cn(
1566
+ "max-w-2xl",
1567
+ centered && "mx-auto text-center",
1568
+ backgroundImage && "text-background"
1569
+ ),
1570
+ children: [
1571
+ eyebrow && /* @__PURE__ */ jsx("p", { className: cn("mb-4 text-sm font-semibold uppercase tracking-wider", eyebrowClass), children: eyebrow }),
1572
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-semibold tracking-tight text-balance sm:text-5xl", children: heading }),
1573
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn("mt-6 text-lg leading-relaxed", subtle), children: subtitle }),
1574
+ (primaryCta?.label || secondaryCta?.label) && /* @__PURE__ */ jsxs(
1575
+ "div",
1576
+ {
1577
+ className: cn("mt-8 flex flex-col gap-3 sm:flex-row", centered && "sm:justify-center"),
1578
+ children: [
1579
+ primaryCta?.label && /* @__PURE__ */ jsx(Button, { asChild: true, size: "lg", children: /* @__PURE__ */ jsxs("a", { href: primaryCta.href || "#", children: [
1580
+ primaryCta.label,
1581
+ /* @__PURE__ */ jsx(ArrowRight, { className: "size-4" })
1582
+ ] }) }),
1583
+ secondaryCta?.label && /* @__PURE__ */ jsx(Button, { asChild: true, size: "lg", variant: "outline", children: /* @__PURE__ */ jsx("a", { href: secondaryCta.href || "#", children: secondaryCta.label }) })
1584
+ ]
1585
+ }
1586
+ )
1587
+ ]
1588
+ }
1589
+ );
1590
+ if (backgroundImage) {
1591
+ const bgStyle = {
1592
+ backgroundImage: `url("${backgroundImage}")`,
1593
+ backgroundSize: "cover",
1594
+ backgroundPosition: "center"
1595
+ };
1596
+ return /* @__PURE__ */ jsxs(
1597
+ "section",
1500
1598
  {
1501
- className: cn("mt-8 flex flex-col gap-3 sm:flex-row", centered && "sm:justify-center"),
1599
+ className: "relative isolate overflow-hidden",
1600
+ style: bgStyle,
1601
+ "data-testid": testId,
1602
+ "data-bg": testId && `${testId}-bg`,
1502
1603
  children: [
1503
- primaryCta?.label && /* @__PURE__ */ jsx(Button, { asChild: true, size: "lg", children: /* @__PURE__ */ jsxs("a", { href: primaryCta.href || "#", children: [
1504
- primaryCta.label,
1505
- /* @__PURE__ */ jsx(ArrowRight, { className: "size-4" })
1506
- ] }) }),
1507
- secondaryCta?.label && /* @__PURE__ */ jsx(Button, { asChild: true, size: "lg", variant: "outline", children: /* @__PURE__ */ jsx("a", { href: secondaryCta.href || "#", children: secondaryCta.label }) })
1604
+ overlay && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/55", "aria-hidden": true }),
1605
+ /* @__PURE__ */ jsx("div", { className: "relative mx-auto max-w-7xl px-5 py-28 lg:px-8 lg:py-36", children: copy })
1508
1606
  ]
1509
1607
  }
1510
- )
1511
- ] });
1608
+ );
1609
+ }
1512
1610
  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: [
1513
1611
  copy,
1514
1612
  imageUrl && /* @__PURE__ */ jsx(
@@ -1593,23 +1691,49 @@ var TONE_SURFACE2 = {
1593
1691
  muted: "bg-muted text-foreground",
1594
1692
  ink: "bg-foreground text-background"
1595
1693
  };
1596
- function CTABanner({ title, subtitle, cta, tone = "default", testId }) {
1597
- const subtle = tone === "default" ? "text-primary-foreground/80" : tone === "ink" ? "text-background/70" : "text-muted-foreground";
1694
+ function CTABanner({
1695
+ title,
1696
+ subtitle,
1697
+ cta,
1698
+ tone = "default",
1699
+ backgroundImage,
1700
+ overlay = true,
1701
+ testId
1702
+ }) {
1703
+ const onImage = Boolean(backgroundImage);
1704
+ const subtle = onImage ? "text-white/80" : tone === "default" ? "text-primary-foreground/80" : tone === "ink" ? "text-background/70" : "text-muted-foreground";
1705
+ const bgStyle = backgroundImage ? {
1706
+ backgroundImage: `url("${backgroundImage}")`,
1707
+ backgroundSize: "cover",
1708
+ backgroundPosition: "center"
1709
+ } : void 0;
1598
1710
  return /* @__PURE__ */ jsxs(
1599
1711
  "div",
1600
1712
  {
1601
1713
  className: cn(
1602
- "rounded-2xl px-8 py-14 text-center",
1603
- TONE_SURFACE2[tone] ?? TONE_SURFACE2.default
1714
+ "relative isolate overflow-hidden rounded-2xl px-8 py-14 text-center",
1715
+ onImage ? "text-white" : TONE_SURFACE2[tone] ?? TONE_SURFACE2.default
1604
1716
  ),
1717
+ style: bgStyle,
1605
1718
  "data-testid": testId,
1606
1719
  children: [
1607
- /* @__PURE__ */ jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance sm:text-4xl", children: title }),
1608
- subtitle && /* @__PURE__ */ jsx("p", { className: cn("mx-auto mt-4 max-w-2xl text-lg", subtle), children: subtitle }),
1609
- cta?.label && /* @__PURE__ */ jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsx(Button, { asChild: true, size: "lg", variant: tone === "default" ? "secondary" : "default", children: /* @__PURE__ */ jsxs("a", { href: cta.href || "#", children: [
1610
- cta.label,
1611
- /* @__PURE__ */ jsx(ArrowRight, { className: "size-4" })
1612
- ] }) }) })
1720
+ onImage && overlay && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/55", "aria-hidden": true }),
1721
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1722
+ /* @__PURE__ */ jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance sm:text-4xl", children: title }),
1723
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn("mx-auto mt-4 max-w-2xl text-lg", subtle), children: subtitle }),
1724
+ cta?.label && /* @__PURE__ */ jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsx(
1725
+ Button,
1726
+ {
1727
+ asChild: true,
1728
+ size: "lg",
1729
+ variant: tone === "default" && !onImage ? "secondary" : "default",
1730
+ children: /* @__PURE__ */ jsxs("a", { href: cta.href || "#", children: [
1731
+ cta.label,
1732
+ /* @__PURE__ */ jsx(ArrowRight, { className: "size-4" })
1733
+ ] })
1734
+ }
1735
+ ) })
1736
+ ] })
1613
1737
  ]
1614
1738
  }
1615
1739
  );
@@ -1774,6 +1898,449 @@ function Container({ width = "default", padding = "md", children, testId }) {
1774
1898
  }
1775
1899
  );
1776
1900
  }
1901
+ function PricingTable({ plans, testId }) {
1902
+ return /* @__PURE__ */ jsx(
1903
+ "div",
1904
+ {
1905
+ className: cn("grid gap-6", (plans?.length ?? 0) >= 3 ? "md:grid-cols-3" : "sm:grid-cols-2"),
1906
+ "data-testid": testId,
1907
+ children: (plans ?? []).map((plan, i) => /* @__PURE__ */ jsxs(
1908
+ Card,
1909
+ {
1910
+ variant: plan.featured ? "elevated" : "outline",
1911
+ className: cn("flex h-full flex-col p-6", plan.featured && "ring-2 ring-primary"),
1912
+ "data-testid": testId && `${testId}-item-${i}`,
1913
+ children: [
1914
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold tracking-tight", children: plan.name }),
1915
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-baseline gap-1", children: [
1916
+ /* @__PURE__ */ jsx("span", { className: "text-3xl font-semibold tracking-tight", children: plan.price }),
1917
+ plan.period && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
1918
+ "/",
1919
+ plan.period
1920
+ ] })
1921
+ ] }),
1922
+ plan.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: plan.description }),
1923
+ 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: [
1924
+ /* @__PURE__ */ jsx(Check, { className: "mt-0.5 size-4 shrink-0 text-primary" }),
1925
+ /* @__PURE__ */ jsx("span", { children: f })
1926
+ ] }, j)) }),
1927
+ 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 }) })
1928
+ ]
1929
+ },
1930
+ i
1931
+ ))
1932
+ }
1933
+ );
1934
+ }
1935
+ function Tabs({ items, testId }) {
1936
+ const [active, setActive] = useState(0);
1937
+ const list = items ?? [];
1938
+ const current = list[Math.min(active, Math.max(list.length - 1, 0))];
1939
+ return /* @__PURE__ */ jsxs("div", { "data-testid": testId, children: [
1940
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 border-b border-border", role: "tablist", children: list.map((t, i) => {
1941
+ const selected = i === active;
1942
+ return /* @__PURE__ */ jsx(
1943
+ "button",
1944
+ {
1945
+ type: "button",
1946
+ role: "tab",
1947
+ "aria-selected": selected,
1948
+ onClick: () => setActive(i),
1949
+ className: cn(
1950
+ "-mb-px border-b-2 px-4 py-2 text-sm font-medium transition-colors",
1951
+ selected ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
1952
+ ),
1953
+ "data-testid": testId && `${testId}-tab-${i}`,
1954
+ children: t.label
1955
+ },
1956
+ i
1957
+ );
1958
+ }) }),
1959
+ current && /* @__PURE__ */ jsx(
1960
+ "div",
1961
+ {
1962
+ role: "tabpanel",
1963
+ className: "pt-5 text-sm leading-relaxed text-muted-foreground",
1964
+ "data-testid": testId && `${testId}-panel`,
1965
+ children: current.content
1966
+ }
1967
+ )
1968
+ ] });
1969
+ }
1970
+ function Accordion({ items, allowMultiple = false, testId }) {
1971
+ const [open, setOpen] = useState([]);
1972
+ const toggle2 = (i) => setOpen(
1973
+ (prev) => prev.includes(i) ? prev.filter((n) => n !== i) : allowMultiple ? [...prev, i] : [i]
1974
+ );
1975
+ return /* @__PURE__ */ jsx("div", { className: "divide-y divide-border border-y border-border", "data-testid": testId, children: (items ?? []).map((item, i) => {
1976
+ const isOpen = open.includes(i);
1977
+ return /* @__PURE__ */ jsxs("div", { "data-testid": testId && `${testId}-item-${i}`, children: [
1978
+ /* @__PURE__ */ jsxs(
1979
+ "button",
1980
+ {
1981
+ type: "button",
1982
+ "aria-expanded": isOpen,
1983
+ onClick: () => toggle2(i),
1984
+ className: "flex w-full items-center justify-between gap-4 py-4 text-left text-base font-medium",
1985
+ "data-testid": testId && `${testId}-trigger-${i}`,
1986
+ children: [
1987
+ item.title,
1988
+ /* @__PURE__ */ jsx(
1989
+ ChevronDown,
1990
+ {
1991
+ className: cn("size-4 shrink-0 transition-transform", isOpen && "rotate-180")
1992
+ }
1993
+ )
1994
+ ]
1995
+ }
1996
+ ),
1997
+ isOpen && /* @__PURE__ */ jsx(
1998
+ "p",
1999
+ {
2000
+ className: "pb-4 text-sm leading-relaxed text-muted-foreground",
2001
+ "data-testid": testId && `${testId}-content-${i}`,
2002
+ children: item.content
2003
+ }
2004
+ )
2005
+ ] }, i);
2006
+ }) });
2007
+ }
2008
+ var COLS2 = {
2009
+ "2": "grid-cols-2",
2010
+ "3": "grid-cols-2 sm:grid-cols-3",
2011
+ "4": "grid-cols-2 sm:grid-cols-4"
2012
+ };
2013
+ function Gallery({
2014
+ images,
2015
+ columns = "3",
2016
+ aspect = "1:1",
2017
+ rounded = "lg",
2018
+ testId
2019
+ }) {
2020
+ 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: [
2021
+ /* @__PURE__ */ jsx(
2022
+ BlockImage,
2023
+ {
2024
+ src: img.src,
2025
+ alt: img.alt,
2026
+ aspect,
2027
+ rounded,
2028
+ testId: testId && `${testId}-item-${i}-image`
2029
+ }
2030
+ ),
2031
+ img.caption && /* @__PURE__ */ jsx("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: img.caption })
2032
+ ] }, i)) });
2033
+ }
2034
+ var ASPECT2 = {
2035
+ "16:9": "aspect-video",
2036
+ "4:3": "aspect-[4/3]",
2037
+ "1:1": "aspect-square"
2038
+ };
2039
+ function toEmbedUrl(url) {
2040
+ if (!url) return null;
2041
+ const yt = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([\w-]{11})/);
2042
+ if (yt) return `https://www.youtube.com/embed/${yt[1]}`;
2043
+ const vimeo = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
2044
+ if (vimeo) return `https://player.vimeo.com/video/${vimeo[1]}`;
2045
+ const loom = url.match(/loom\.com\/(?:share|embed)\/([\w-]+)/);
2046
+ if (loom) return `https://www.loom.com/embed/${loom[1]}`;
2047
+ return null;
2048
+ }
2049
+ function VideoEmbed({
2050
+ url,
2051
+ title,
2052
+ aspect = "16:9",
2053
+ rounded = true,
2054
+ testId
2055
+ }) {
2056
+ const embed = toEmbedUrl(url);
2057
+ const frame = cn(
2058
+ "w-full overflow-hidden border border-border bg-muted",
2059
+ ASPECT2[aspect] ?? ASPECT2["16:9"],
2060
+ rounded && "rounded-lg"
2061
+ );
2062
+ return /* @__PURE__ */ jsx("div", { className: frame, "data-testid": testId, children: embed ? /* @__PURE__ */ jsx(
2063
+ "iframe",
2064
+ {
2065
+ src: embed,
2066
+ title: title || "Embedded video",
2067
+ className: "size-full",
2068
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
2069
+ allowFullScreen: true,
2070
+ "data-testid": testId && `${testId}-iframe`
2071
+ }
2072
+ ) : url ? /* @__PURE__ */ jsx("video", { src: url, controls: true, className: "size-full", "data-testid": testId && `${testId}-video` }) : null });
2073
+ }
2074
+ function ContactForm({
2075
+ fields,
2076
+ submitLabel = "Send",
2077
+ action,
2078
+ successMessage = "Thanks \u2014 we\u2019ll be in touch shortly.",
2079
+ testId
2080
+ }) {
2081
+ const [sent, setSent] = useState(false);
2082
+ const list = fields ?? [];
2083
+ const onSubmit = (e) => {
2084
+ if (!action) {
2085
+ e.preventDefault();
2086
+ setSent(true);
2087
+ }
2088
+ };
2089
+ if (sent) {
2090
+ return /* @__PURE__ */ jsx(
2091
+ "p",
2092
+ {
2093
+ className: "rounded-lg border border-border bg-muted p-6 text-center text-sm text-foreground",
2094
+ role: "status",
2095
+ "data-testid": testId && `${testId}-success`,
2096
+ children: successMessage
2097
+ }
2098
+ );
2099
+ }
2100
+ return /* @__PURE__ */ jsxs(
2101
+ "form",
2102
+ {
2103
+ action,
2104
+ method: action ? "post" : void 0,
2105
+ onSubmit,
2106
+ className: "mx-auto flex max-w-xl flex-col gap-4",
2107
+ "data-testid": testId,
2108
+ children: [
2109
+ list.map((f, i) => {
2110
+ const inputClass = "w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground outline-none focus:border-primary";
2111
+ return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1.5 text-left", children: [
2112
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: f.label }),
2113
+ f.type === "textarea" ? /* @__PURE__ */ jsx(
2114
+ "textarea",
2115
+ {
2116
+ name: f.name,
2117
+ required: f.required,
2118
+ rows: 4,
2119
+ className: inputClass,
2120
+ "data-testid": testId && `${testId}-field-${f.name}`
2121
+ }
2122
+ ) : /* @__PURE__ */ jsx(
2123
+ "input",
2124
+ {
2125
+ name: f.name,
2126
+ type: f.type || "text",
2127
+ required: f.required,
2128
+ className: inputClass,
2129
+ "data-testid": testId && `${testId}-field-${f.name}`
2130
+ }
2131
+ )
2132
+ ] }, i);
2133
+ }),
2134
+ /* @__PURE__ */ jsx(
2135
+ Button,
2136
+ {
2137
+ type: "submit",
2138
+ className: cn("mt-1 self-start"),
2139
+ "data-testid": testId && `${testId}-submit`,
2140
+ children: submitLabel
2141
+ }
2142
+ )
2143
+ ]
2144
+ }
2145
+ );
2146
+ }
2147
+ var TONE = {
2148
+ primary: "bg-primary text-primary-foreground",
2149
+ muted: "bg-muted text-foreground",
2150
+ ink: "bg-foreground text-background"
2151
+ };
2152
+ function Banner({ text, cta, tone = "primary", testId }) {
2153
+ return /* @__PURE__ */ jsxs(
2154
+ "div",
2155
+ {
2156
+ className: cn("w-full px-5 py-2.5 text-center text-sm", TONE[tone] ?? TONE.primary),
2157
+ "data-testid": testId,
2158
+ children: [
2159
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: text }),
2160
+ cta?.label && /* @__PURE__ */ jsxs(
2161
+ "a",
2162
+ {
2163
+ href: cta.href || "#",
2164
+ className: "ml-2 inline-flex items-center gap-1 font-semibold underline underline-offset-4",
2165
+ "data-testid": testId && `${testId}-cta`,
2166
+ children: [
2167
+ cta.label,
2168
+ /* @__PURE__ */ jsx(ArrowRight, { className: "size-3.5" })
2169
+ ]
2170
+ }
2171
+ )
2172
+ ]
2173
+ }
2174
+ );
2175
+ }
2176
+ function MediaText({
2177
+ eyebrow,
2178
+ title,
2179
+ body,
2180
+ imageUrl,
2181
+ mediaSide = "left",
2182
+ cta,
2183
+ testId
2184
+ }) {
2185
+ const media = /* @__PURE__ */ jsx(BlockImage, { src: imageUrl, aspect: "4:3", rounded: "lg", testId: testId && `${testId}-image` });
2186
+ const copy = /* @__PURE__ */ jsxs("div", { children: [
2187
+ eyebrow && /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: eyebrow }),
2188
+ /* @__PURE__ */ jsx("h2", { className: "text-3xl font-semibold tracking-tight text-balance", children: title }),
2189
+ body && /* @__PURE__ */ jsx("p", { className: "mt-4 text-lg leading-relaxed text-muted-foreground", children: body }),
2190
+ cta?.label && /* @__PURE__ */ jsx(Button, { asChild: true, className: "mt-6", children: /* @__PURE__ */ jsxs("a", { href: cta.href || "#", children: [
2191
+ cta.label,
2192
+ /* @__PURE__ */ jsx(ArrowRight, { className: "size-4" })
2193
+ ] }) })
2194
+ ] });
2195
+ return /* @__PURE__ */ jsx("div", { className: "grid items-center gap-10 lg:grid-cols-2", "data-testid": testId, children: mediaSide === "right" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2196
+ copy,
2197
+ media
2198
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2199
+ media,
2200
+ copy
2201
+ ] }) });
2202
+ }
2203
+ var COLS3 = {
2204
+ "2": "sm:grid-cols-2",
2205
+ "3": "sm:grid-cols-2 lg:grid-cols-3",
2206
+ "4": "sm:grid-cols-2 lg:grid-cols-4"
2207
+ };
2208
+ function TeamGrid({ members, columns = "3", testId }) {
2209
+ return /* @__PURE__ */ jsx("div", { className: cn("grid gap-6", COLS3[columns] ?? COLS3["3"]), "data-testid": testId, children: (members ?? []).map((m, i) => /* @__PURE__ */ jsxs(
2210
+ Card,
2211
+ {
2212
+ variant: "outline",
2213
+ className: "flex h-full flex-col items-center p-6 text-center",
2214
+ "data-testid": testId && `${testId}-item-${i}`,
2215
+ children: [
2216
+ /* @__PURE__ */ jsxs(Avatar, { className: "size-20", children: [
2217
+ m.avatarUrl && /* @__PURE__ */ jsx(AvatarImage, { src: m.avatarUrl, alt: m.name }),
2218
+ /* @__PURE__ */ jsx(AvatarFallback, { children: initials(m.name || "?") })
2219
+ ] }),
2220
+ /* @__PURE__ */ jsx("h3", { className: "mt-4 text-base font-semibold tracking-tight", children: m.name }),
2221
+ m.role && /* @__PURE__ */ jsx("p", { className: "text-sm text-primary", children: m.role }),
2222
+ m.bio && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: m.bio })
2223
+ ]
2224
+ },
2225
+ i
2226
+ )) });
2227
+ }
2228
+ function Steps({ steps, variant = "numbered", testId }) {
2229
+ const list = steps ?? [];
2230
+ if (variant === "timeline") {
2231
+ 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: [
2232
+ /* @__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 }),
2233
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold tracking-tight", children: s.title }),
2234
+ s.description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm leading-relaxed text-muted-foreground", children: s.description })
2235
+ ] }, i)) });
2236
+ }
2237
+ return /* @__PURE__ */ jsx(
2238
+ "div",
2239
+ {
2240
+ className: cn("grid gap-6", list.length >= 4 ? "md:grid-cols-4" : "sm:grid-cols-3"),
2241
+ "data-testid": testId,
2242
+ children: list.map((s, i) => /* @__PURE__ */ jsxs("div", { "data-testid": testId && `${testId}-item-${i}`, children: [
2243
+ /* @__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 }),
2244
+ /* @__PURE__ */ jsx("h3", { className: "mt-4 text-base font-semibold tracking-tight", children: s.title }),
2245
+ s.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm leading-relaxed text-muted-foreground", children: s.description })
2246
+ ] }, i))
2247
+ }
2248
+ );
2249
+ }
2250
+ var COLS4 = {
2251
+ "2": "sm:grid-cols-2",
2252
+ "3": "sm:grid-cols-2 lg:grid-cols-3"
2253
+ };
2254
+ function TestimonialGrid({ items, columns = "3", testId }) {
2255
+ return /* @__PURE__ */ jsx("div", { className: cn("grid gap-6", COLS4[columns] ?? COLS4["3"]), "data-testid": testId, children: (items ?? []).map((t, i) => /* @__PURE__ */ jsxs(
2256
+ Card,
2257
+ {
2258
+ variant: "outline",
2259
+ className: "flex h-full flex-col p-6",
2260
+ "data-testid": testId && `${testId}-item-${i}`,
2261
+ children: [
2262
+ /* @__PURE__ */ jsx(Quote, { className: "size-6 text-primary/40", "aria-hidden": true }),
2263
+ /* @__PURE__ */ jsx("blockquote", { className: "mt-3 flex-1 text-base leading-relaxed", children: t.quote }),
2264
+ (t.author || t.avatarUrl) && /* @__PURE__ */ jsxs("div", { className: "mt-5 flex items-center gap-3", children: [
2265
+ /* @__PURE__ */ jsxs(Avatar, { className: "size-9", children: [
2266
+ t.avatarUrl && /* @__PURE__ */ jsx(AvatarImage, { src: t.avatarUrl, alt: t.author ?? "" }),
2267
+ /* @__PURE__ */ jsx(AvatarFallback, { children: initials(t.author || "?") })
2268
+ ] }),
2269
+ /* @__PURE__ */ jsxs("div", { children: [
2270
+ t.author && /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: t.author }),
2271
+ t.role && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t.role })
2272
+ ] })
2273
+ ] })
2274
+ ]
2275
+ },
2276
+ i
2277
+ )) });
2278
+ }
2279
+
2280
+ // src/studio/publish.ts
2281
+ function blobToDataUrl(blob) {
2282
+ return new Promise((resolve, reject) => {
2283
+ const reader = new FileReader();
2284
+ reader.onload = () => resolve(reader.result);
2285
+ reader.onerror = () => reject(reader.error);
2286
+ reader.readAsDataURL(blob);
2287
+ });
2288
+ }
2289
+ function createArbiMaterializer(arbi, opts = {}) {
2290
+ return async (docId) => {
2291
+ try {
2292
+ const res = await arbi.documents.download(docId);
2293
+ const blob = await res.blob();
2294
+ if (opts.publishAsset) return await opts.publishAsset(docId, blob);
2295
+ return await blobToDataUrl(blob);
2296
+ } catch {
2297
+ return null;
2298
+ }
2299
+ };
2300
+ }
2301
+ function collectAssetRefs(node, acc) {
2302
+ if (typeof node === "string") {
2303
+ if (isAssetRef(node)) acc.add(node);
2304
+ return;
2305
+ }
2306
+ if (Array.isArray(node)) {
2307
+ for (const child of node) collectAssetRefs(child, acc);
2308
+ return;
2309
+ }
2310
+ if (node && typeof node === "object") {
2311
+ for (const value of Object.values(node)) collectAssetRefs(value, acc);
2312
+ }
2313
+ }
2314
+ function replaceAssetRefs(node, replacements) {
2315
+ if (typeof node === "string") {
2316
+ return replacements.get(node) ?? node;
2317
+ }
2318
+ if (Array.isArray(node)) {
2319
+ return node.map((child) => replaceAssetRefs(child, replacements));
2320
+ }
2321
+ if (node && typeof node === "object") {
2322
+ const out = {};
2323
+ for (const [key, value] of Object.entries(node)) {
2324
+ out[key] = replaceAssetRefs(value, replacements);
2325
+ }
2326
+ return out;
2327
+ }
2328
+ return node;
2329
+ }
2330
+ async function materializePageData(data, materialize) {
2331
+ const refs = /* @__PURE__ */ new Set();
2332
+ collectAssetRefs(data, refs);
2333
+ if (refs.size === 0) return data;
2334
+ const replacements = /* @__PURE__ */ new Map();
2335
+ await Promise.all(
2336
+ [...refs].map(async (ref) => {
2337
+ const src = await materialize(assetRefId(ref));
2338
+ if (src) replacements.set(ref, src);
2339
+ })
2340
+ );
2341
+ if (replacements.size === 0) return data;
2342
+ return replaceAssetRefs(data, replacements);
2343
+ }
1777
2344
  var EMPTY_DATA = { content: [], root: {} };
1778
2345
  function StudioEditor({
1779
2346
  config,
@@ -1786,9 +2353,14 @@ function StudioEditor({
1786
2353
  backLabel = "Back",
1787
2354
  live,
1788
2355
  initialSlug,
1789
- testIdPrefix = "studio"
2356
+ testIdPrefix = "studio",
2357
+ publishAsset
1790
2358
  }) {
1791
2359
  const arbi = useArbi();
2360
+ const materialize = useMemo(
2361
+ () => createArbiMaterializer(arbi, { publishAsset }),
2362
+ [arbi, publishAsset]
2363
+ );
1792
2364
  const [slug, setSlug] = useState(initialSlug ?? pages[0]?.slug ?? "home");
1793
2365
  const [data, setData] = useState(EMPTY_DATA);
1794
2366
  const [rev, setRev] = useState(0);
@@ -1816,11 +2388,16 @@ function StudioEditor({
1816
2388
  const persist4 = useCallback(
1817
2389
  async (toSave) => {
1818
2390
  setSaving(true);
1819
- const where = await persistence.savePage(arbi, live, slug, toSave);
1820
- setVia(where);
1821
- setSaving(false);
2391
+ try {
2392
+ const where = await persistence.savePage(arbi, live, slug, toSave);
2393
+ const published = await materializePageData(toSave, materialize);
2394
+ await persistence.savePublished(arbi, live, slug, published);
2395
+ setVia(where);
2396
+ } finally {
2397
+ setSaving(false);
2398
+ }
1822
2399
  },
1823
- [arbi, live, slug, persistence]
2400
+ [arbi, live, slug, persistence, materialize]
1824
2401
  );
1825
2402
  useEffect(() => {
1826
2403
  let cancelled = false;
@@ -1850,7 +2427,7 @@ function StudioEditor({
1850
2427
  Link,
1851
2428
  {
1852
2429
  to: backHref,
1853
- className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm text-muted-foreground hover:text-emerald",
2430
+ className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm text-muted-foreground hover:text-primary",
1854
2431
  "data-testid": `${testIdPrefix}-back`,
1855
2432
  children: [
1856
2433
  /* @__PURE__ */ jsx(ArrowLeft, { className: "size-4" }),
@@ -1861,7 +2438,7 @@ function StudioEditor({
1861
2438
  ),
1862
2439
  /* @__PURE__ */ jsx("span", { className: "h-4 w-px bg-border" })
1863
2440
  ] }),
1864
- /* @__PURE__ */ jsx("span", { className: "font-display text-sm font-semibold text-ink", children: brandLabel }),
2441
+ /* @__PURE__ */ jsx("span", { className: "font-display text-sm font-semibold text-foreground", children: brandLabel }),
1865
2442
  /* @__PURE__ */ jsx(
1866
2443
  "select",
1867
2444
  {
@@ -1889,7 +2466,7 @@ function StudioEditor({
1889
2466
  {
1890
2467
  type: "button",
1891
2468
  onClick: () => setThemeOpen((o) => !o),
1892
- className: "inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground hover:border-emerald hover:text-emerald",
2469
+ className: "inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground hover:border-primary hover:text-primary",
1893
2470
  "data-testid": `${testIdPrefix}-theme-toggle`,
1894
2471
  children: [
1895
2472
  /* @__PURE__ */ jsx(Palette, { className: "size-4" }),
@@ -1903,7 +2480,7 @@ function StudioEditor({
1903
2480
  type: "button",
1904
2481
  onClick: () => void persist4(latest.current),
1905
2482
  disabled: saving || loading,
1906
- className: "inline-flex items-center gap-1.5 rounded-md bg-emerald px-3 py-1.5 text-sm font-medium text-parchment hover:bg-emerald-bright disabled:opacity-60",
2483
+ 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",
1907
2484
  "data-testid": `${testIdPrefix}-publish`,
1908
2485
  children: [
1909
2486
  saving ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(Rocket, { className: "size-4" }),
@@ -1926,7 +2503,7 @@ function StudioEditor({
1926
2503
  "\u201D\u2026"
1927
2504
  ]
1928
2505
  }
1929
- ) : /* @__PURE__ */ jsx(
2506
+ ) : /* @__PURE__ */ jsx(AssetResolverProvider, { children: /* @__PURE__ */ jsx(
1930
2507
  Puck,
1931
2508
  {
1932
2509
  config,
@@ -1937,7 +2514,7 @@ function StudioEditor({
1937
2514
  onPublish: (d) => void persist4(d)
1938
2515
  },
1939
2516
  `${slug}-${rev}`
1940
- ) }),
2517
+ ) }) }),
1941
2518
  themeOpen && /* @__PURE__ */ jsx(
1942
2519
  "aside",
1943
2520
  {
@@ -1975,11 +2552,13 @@ function PageRenderer({
1975
2552
  let cancelled = false;
1976
2553
  setLoading(true);
1977
2554
  void (async () => {
1978
- const [{ data: page }, { data: theme }] = await Promise.all([
1979
- persistence.loadPage(arbi, live, slug),
2555
+ const [{ data: published }, { data: theme }] = await Promise.all([
2556
+ persistence.loadPublished(arbi, live, slug),
1980
2557
  persistence.loadTheme(arbi, live)
1981
2558
  ]);
1982
2559
  if (cancelled) return;
2560
+ const page = published ?? (await persistence.loadPage(arbi, live, slug)).data;
2561
+ if (cancelled) return;
1983
2562
  applyThemeVars(
1984
2563
  theme ? { ...defaultTheme, ...theme, colors: { ...defaultTheme.colors, ...theme.colors } } : defaultTheme
1985
2564
  );
@@ -2009,7 +2588,7 @@ function PageRenderer({
2009
2588
  className: "mx-auto max-w-2xl px-5 py-24 text-center",
2010
2589
  "data-testid": `${testIdPrefix}-render-empty`,
2011
2590
  children: [
2012
- /* @__PURE__ */ jsx("h1", { className: "font-display text-2xl font-semibold text-ink", children: title }),
2591
+ /* @__PURE__ */ jsx("h1", { className: "font-display text-2xl font-semibold text-foreground", children: title }),
2013
2592
  /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: body })
2014
2593
  ]
2015
2594
  }
@@ -2023,9 +2602,11 @@ function createStudioPersistence(cfg) {
2023
2602
  const WORKSPACE_ID = cfg.workspaceId;
2024
2603
  const FOLDER = cfg.folder ?? "site";
2025
2604
  const pageKey = (slug) => `${cfg.storagePrefix}${slug}`;
2605
+ const publishedKey = (slug) => `${cfg.storagePrefix}published-${slug}`;
2026
2606
  const THEME_KEY = cfg.themeStorageKey ?? `${cfg.storagePrefix}theme`;
2027
2607
  const THEME_FILE = cfg.themeFileName ?? "theme.json";
2028
2608
  const pageFile = cfg.pageFileName ?? ((slug) => `${slug}.json`);
2609
+ const publishedFile = cfg.publishedFileName ?? ((slug) => `${slug}.published.json`);
2029
2610
  function canUseArbi(arbi, live) {
2030
2611
  return live && !!arbi;
2031
2612
  }
@@ -2048,39 +2629,47 @@ function createStudioPersistence(cfg) {
2048
2629
  return JSON.parse(raw);
2049
2630
  }
2050
2631
  }
2051
- async function savePage(arbi, live, slug, data) {
2632
+ async function writePage(arbi, live, kind, localKey, fileName, data) {
2052
2633
  try {
2053
- localStorage.setItem(pageKey(slug), JSON.stringify(data));
2634
+ localStorage.setItem(localKey, JSON.stringify(data));
2054
2635
  } catch {
2055
2636
  }
2056
2637
  if (canUseArbi(arbi, live)) {
2057
2638
  try {
2058
- await uploadJson(arbi, pageFile(slug), data);
2059
- console.info(`[studio] saved page "${slug}" \u2192 ARBI (${WORKSPACE_ID})`);
2639
+ await uploadJson(arbi, fileName, data);
2640
+ console.info(`[studio] saved ${kind} "${fileName}" \u2192 ARBI (${WORKSPACE_ID})`);
2060
2641
  return "arbi";
2061
2642
  } catch (e) {
2062
- console.warn(`[studio] ARBI save failed for "${slug}", kept localStorage`, e);
2643
+ console.warn(`[studio] ARBI save failed for "${fileName}", kept localStorage`, e);
2063
2644
  }
2064
2645
  }
2065
- console.info(`[studio] saved page "${slug}" \u2192 localStorage`);
2646
+ console.info(`[studio] saved ${kind} "${fileName}" \u2192 localStorage`);
2066
2647
  return "local";
2067
2648
  }
2068
- async function loadPage(arbi, live, slug) {
2649
+ async function readPage(arbi, live, localKey, fileName) {
2069
2650
  if (canUseArbi(arbi, live)) {
2070
2651
  try {
2071
- const data = await downloadJson(arbi, pageFile(slug));
2072
- if (data) {
2073
- console.info(`[studio] loaded page "${slug}" \u2190 ARBI`);
2074
- return { data, via: "arbi" };
2075
- }
2652
+ const data = await downloadJson(arbi, fileName);
2653
+ if (data) return { data, via: "arbi" };
2076
2654
  } catch (e) {
2077
- console.warn(`[studio] ARBI load failed for "${slug}", trying localStorage`, e);
2655
+ console.warn(`[studio] ARBI load failed for "${fileName}", trying localStorage`, e);
2078
2656
  }
2079
2657
  }
2080
- const raw = safeLocalGet(pageKey(slug));
2081
- console.info(`[studio] loaded page "${slug}" \u2190 localStorage`);
2658
+ const raw = safeLocalGet(localKey);
2082
2659
  return { data: raw ? JSON.parse(raw) : null, via: "local" };
2083
2660
  }
2661
+ function savePage(arbi, live, slug, data) {
2662
+ return writePage(arbi, live, "page", pageKey(slug), pageFile(slug), data);
2663
+ }
2664
+ function loadPage(arbi, live, slug) {
2665
+ return readPage(arbi, live, pageKey(slug), pageFile(slug));
2666
+ }
2667
+ function savePublished(arbi, live, slug, data) {
2668
+ return writePage(arbi, live, "published page", publishedKey(slug), publishedFile(slug), data);
2669
+ }
2670
+ function loadPublished(arbi, live, slug) {
2671
+ return readPage(arbi, live, publishedKey(slug), publishedFile(slug));
2672
+ }
2084
2673
  async function saveTheme(arbi, live, theme) {
2085
2674
  try {
2086
2675
  localStorage.setItem(THEME_KEY, JSON.stringify(theme));
@@ -2117,7 +2706,7 @@ function createStudioPersistence(cfg) {
2117
2706
  return null;
2118
2707
  }
2119
2708
  }
2120
- return { savePage, loadPage, saveTheme, loadTheme };
2709
+ return { savePage, loadPage, savePublished, loadPublished, saveTheme, loadTheme };
2121
2710
  }
2122
2711
  function AiTextField({
2123
2712
  value,
@@ -2232,21 +2821,293 @@ function createAiFields(cfg) {
2232
2821
  });
2233
2822
  return { aiText, aiTextarea };
2234
2823
  }
2235
- function Section({ children, tone = "default", className = "" }) {
2236
- const toneClass = tone === "ink" ? "bg-ink text-parchment" : tone === "muted" ? "border-y border-border bg-card/40" : "";
2237
- return /* @__PURE__ */ jsx("section", { className: `relative ${toneClass}`, children: /* @__PURE__ */ jsx("div", { className: `mx-auto max-w-7xl px-5 py-16 lg:px-8 lg:py-20 ${className}`, children }) });
2824
+ function latestImageDocId(artifacts) {
2825
+ for (let i = artifacts.length - 1; i >= 0; i--) {
2826
+ if (artifacts[i].mode === "image") return artifacts[i].doc_ext_id;
2827
+ }
2828
+ return null;
2238
2829
  }
2239
- var iconMap = {
2240
- FileText,
2241
- Folder,
2242
- Users,
2243
- Briefcase,
2244
- Calendar,
2245
- Clock,
2246
- CheckCircle,
2247
- AlertCircle,
2248
- Info: Info,
2249
- TrendingUp,
2830
+ function useImageGen() {
2831
+ const task = useFirmAiTask();
2832
+ const { run } = task;
2833
+ const generate = useCallback(
2834
+ async (prompt) => {
2835
+ const trimmed = prompt.trim();
2836
+ if (!trimmed) return null;
2837
+ const result = await run(`/image ${trimmed}`, []);
2838
+ return latestImageDocId(result?.artifacts ?? []);
2839
+ },
2840
+ [run]
2841
+ );
2842
+ const docExtId = useMemo(() => latestImageDocId(task.artifacts), [task.artifacts]);
2843
+ return {
2844
+ docExtId,
2845
+ status: task.status,
2846
+ error: task.error,
2847
+ isGenerating: task.isRunning,
2848
+ generate,
2849
+ reset: task.reset
2850
+ };
2851
+ }
2852
+ var IMAGE_EXT = /\.(png|jpe?g|webp|gif|avif|svg)$/i;
2853
+ function ImageFieldControl({
2854
+ value,
2855
+ onChange,
2856
+ name,
2857
+ workspaceId,
2858
+ folder,
2859
+ testIdPrefix
2860
+ }) {
2861
+ const [mode, setMode] = useState("asset");
2862
+ const [url, setUrl] = useState("");
2863
+ const [prompt, setPrompt] = useState("");
2864
+ const [busy, setBusy] = useState(false);
2865
+ const arbi = useArbi();
2866
+ const gen = useImageGen();
2867
+ const tp = (...q) => tid(`${testIdPrefix}-image`, name, ...q);
2868
+ const { data: docs } = useDocuments(workspaceId);
2869
+ const imageDocs = useMemo(
2870
+ () => (docs ?? []).filter(
2871
+ (d) => d.file_name && IMAGE_EXT.test(d.file_name)
2872
+ ),
2873
+ [docs]
2874
+ );
2875
+ const imageIds = useMemo(
2876
+ () => imageDocs.map((d) => d.external_id),
2877
+ [imageDocs]
2878
+ );
2879
+ const { data: thumbs } = useThumbnails(imageIds);
2880
+ const onUpload = async (e) => {
2881
+ const file = e.target.files?.[0];
2882
+ if (!file) return;
2883
+ setBusy(true);
2884
+ try {
2885
+ await arbi.selectWorkspace(workspaceId);
2886
+ const res = await arbi.documents.uploadFile(file, file.name, { folder });
2887
+ const id = res.doc_ext_ids?.[0];
2888
+ if (id) onChange(asAssetRef(id));
2889
+ } finally {
2890
+ setBusy(false);
2891
+ }
2892
+ };
2893
+ const onGenerate = async () => {
2894
+ if (!prompt.trim() || gen.isGenerating) return;
2895
+ const id = await gen.generate(prompt);
2896
+ if (id) onChange(asAssetRef(id));
2897
+ };
2898
+ const MODES = [
2899
+ { key: "asset", label: "Assets", icon: ImagePlus },
2900
+ { key: "upload", label: "Upload", icon: Upload },
2901
+ { key: "url", label: "URL", icon: Link2 },
2902
+ { key: "generate", label: "Generate", icon: Sparkles }
2903
+ ];
2904
+ const inputClass = "w-full rounded-md border border-border bg-card px-2.5 py-1.5 text-sm text-foreground outline-none focus:border-primary";
2905
+ const tabClass = (on) => `inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium ${on ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"}`;
2906
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-testid": tp(), children: [
2907
+ /* @__PURE__ */ jsx(BlockImage, { src: value, aspect: "16:9", rounded: "md", testId: tp("preview") }),
2908
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: MODES.map((m) => /* @__PURE__ */ jsxs(
2909
+ "button",
2910
+ {
2911
+ type: "button",
2912
+ className: tabClass(mode === m.key),
2913
+ onClick: () => setMode(m.key),
2914
+ "data-testid": tp("mode", m.key),
2915
+ children: [
2916
+ /* @__PURE__ */ jsx(m.icon, { className: "size-3" }),
2917
+ " ",
2918
+ m.label
2919
+ ]
2920
+ },
2921
+ m.key
2922
+ )) }),
2923
+ mode === "asset" && /* @__PURE__ */ jsxs("div", { className: "grid max-h-48 grid-cols-3 gap-1.5 overflow-auto", "data-testid": tp("assets"), children: [
2924
+ imageDocs.length === 0 && /* @__PURE__ */ jsx("p", { className: "col-span-3 py-3 text-center text-xs text-muted-foreground", children: "No image assets yet \u2014 upload or generate one." }),
2925
+ imageDocs.map((d) => {
2926
+ const thumb = thumbs?.[d.external_id];
2927
+ const selected = value === asAssetRef(d.external_id);
2928
+ return /* @__PURE__ */ jsx(
2929
+ "button",
2930
+ {
2931
+ type: "button",
2932
+ onClick: () => onChange(asAssetRef(d.external_id)),
2933
+ className: `overflow-hidden rounded border ${selected ? "border-primary ring-1 ring-primary" : "border-border"}`,
2934
+ title: d.file_name ?? "",
2935
+ "data-testid": tp("asset", d.external_id),
2936
+ children: thumb ? /* @__PURE__ */ jsx(
2937
+ "img",
2938
+ {
2939
+ src: thumb,
2940
+ alt: d.file_name ?? "",
2941
+ className: "aspect-square w-full object-cover"
2942
+ }
2943
+ ) : /* @__PURE__ */ jsx("span", { className: "flex aspect-square w-full items-center justify-center bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx(ImagePlus, { className: "size-4" }) })
2944
+ },
2945
+ d.external_id
2946
+ );
2947
+ })
2948
+ ] }),
2949
+ mode === "upload" && /* @__PURE__ */ jsxs("label", { className: "inline-flex cursor-pointer items-center gap-2 rounded-md border border-border px-2.5 py-1.5 text-sm text-foreground", children: [
2950
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(Upload, { className: "size-4" }),
2951
+ busy ? "Uploading\u2026" : "Choose a file",
2952
+ /* @__PURE__ */ jsx(
2953
+ "input",
2954
+ {
2955
+ type: "file",
2956
+ accept: "image/*",
2957
+ className: "hidden",
2958
+ disabled: busy,
2959
+ onChange: (e) => void onUpload(e),
2960
+ "data-testid": tp("upload")
2961
+ }
2962
+ )
2963
+ ] }),
2964
+ mode === "url" && /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
2965
+ /* @__PURE__ */ jsx(
2966
+ "input",
2967
+ {
2968
+ className: inputClass,
2969
+ placeholder: "https://\u2026",
2970
+ value: url,
2971
+ onChange: (e) => setUrl(e.target.value),
2972
+ "data-testid": tp("url")
2973
+ }
2974
+ ),
2975
+ /* @__PURE__ */ jsx(
2976
+ "button",
2977
+ {
2978
+ type: "button",
2979
+ className: "rounded-md bg-primary px-2.5 py-1.5 text-sm font-medium text-primary-foreground disabled:opacity-50",
2980
+ disabled: !url.trim(),
2981
+ onClick: () => onChange(url.trim()),
2982
+ "data-testid": tp("url-apply"),
2983
+ children: "Use"
2984
+ }
2985
+ )
2986
+ ] }),
2987
+ mode === "generate" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
2988
+ /* @__PURE__ */ jsx(
2989
+ "textarea",
2990
+ {
2991
+ className: inputClass,
2992
+ rows: 2,
2993
+ placeholder: "Describe the image\u2026",
2994
+ value: prompt,
2995
+ onChange: (e) => setPrompt(e.target.value),
2996
+ "data-testid": tp("prompt")
2997
+ }
2998
+ ),
2999
+ /* @__PURE__ */ jsxs(
3000
+ "button",
3001
+ {
3002
+ type: "button",
3003
+ className: "inline-flex items-center gap-1.5 self-start rounded-md bg-primary px-2.5 py-1.5 text-sm font-medium text-primary-foreground disabled:opacity-50",
3004
+ disabled: !prompt.trim() || gen.isGenerating,
3005
+ onClick: () => void onGenerate(),
3006
+ "data-testid": tp("generate"),
3007
+ children: [
3008
+ gen.isGenerating ? /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Sparkles, { className: "size-3.5" }),
3009
+ gen.isGenerating ? "Generating\u2026" : "Generate"
3010
+ ]
3011
+ }
3012
+ ),
3013
+ gen.error && /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive", role: "alert", children: gen.error })
3014
+ ] }),
3015
+ value && /* @__PURE__ */ jsx(
3016
+ "button",
3017
+ {
3018
+ type: "button",
3019
+ className: "self-start text-xs text-muted-foreground hover:text-foreground",
3020
+ onClick: () => onChange(""),
3021
+ "data-testid": tp("clear"),
3022
+ children: "Clear"
3023
+ }
3024
+ )
3025
+ ] });
3026
+ }
3027
+ function createImageField(cfg) {
3028
+ const { workspaceId } = cfg;
3029
+ const folder = cfg.folder ?? "assets";
3030
+ const testIdPrefix = cfg.testIdPrefix ?? "studio";
3031
+ const imageField = (label) => ({
3032
+ type: "custom",
3033
+ label,
3034
+ render: ({ value, onChange, name }) => /* @__PURE__ */ jsx(
3035
+ ImageFieldControl,
3036
+ {
3037
+ value: value ?? "",
3038
+ onChange,
3039
+ name,
3040
+ workspaceId,
3041
+ folder,
3042
+ testIdPrefix
3043
+ }
3044
+ )
3045
+ });
3046
+ return { imageField };
3047
+ }
3048
+ var TEXTURE_STYLE = {
3049
+ backgroundImage: "radial-gradient(currentColor 0.5px, transparent 0.5px)",
3050
+ backgroundSize: "4px 4px",
3051
+ opacity: 0.04
3052
+ };
3053
+ var PADDING2 = {
3054
+ none: "",
3055
+ sm: "py-8 lg:py-10",
3056
+ md: "py-16 lg:py-20",
3057
+ lg: "py-24 lg:py-28"
3058
+ };
3059
+ var WIDTH2 = {
3060
+ narrow: "max-w-3xl",
3061
+ default: "max-w-7xl",
3062
+ wide: "max-w-[90rem]",
3063
+ full: "max-w-none"
3064
+ };
3065
+ var INVERTED_VARS = {
3066
+ ["--color-background"]: "var(--color-foreground)",
3067
+ ["--color-foreground"]: "var(--color-background)",
3068
+ ["--color-card"]: "color-mix(in srgb, var(--color-foreground) 92%, #fff)",
3069
+ ["--color-muted"]: "color-mix(in srgb, var(--color-foreground) 85%, #fff)",
3070
+ ["--color-muted-foreground"]: "color-mix(in srgb, var(--color-background) 60%, transparent)",
3071
+ ["--color-border"]: "color-mix(in srgb, var(--color-background) 18%, transparent)",
3072
+ ["--color-accent"]: "color-mix(in srgb, var(--color-foreground) 80%, #fff)"
3073
+ };
3074
+ function Section({
3075
+ children,
3076
+ tone = "default",
3077
+ padding = "md",
3078
+ maxWidth = "default",
3079
+ texture,
3080
+ className = ""
3081
+ }) {
3082
+ const inverted = tone === "ink";
3083
+ const toneClass = inverted ? "bg-background text-foreground" : tone === "muted" ? "border-y border-border bg-card/40" : "";
3084
+ return /* @__PURE__ */ jsxs("section", { className: cn("relative", toneClass), style: inverted ? INVERTED_VARS : void 0, children: [
3085
+ texture && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0", style: TEXTURE_STYLE, "aria-hidden": true }),
3086
+ /* @__PURE__ */ jsx(
3087
+ "div",
3088
+ {
3089
+ className: cn(
3090
+ "relative mx-auto px-5 lg:px-8",
3091
+ WIDTH2[maxWidth],
3092
+ PADDING2[padding],
3093
+ className
3094
+ ),
3095
+ children
3096
+ }
3097
+ )
3098
+ ] });
3099
+ }
3100
+ var iconMap = {
3101
+ FileText,
3102
+ Folder,
3103
+ Users,
3104
+ Briefcase,
3105
+ Calendar,
3106
+ Clock,
3107
+ CheckCircle,
3108
+ AlertCircle,
3109
+ Info: Info,
3110
+ TrendingUp,
2250
3111
  DollarSign,
2251
3112
  BarChart3,
2252
3113
  Activity,
@@ -2277,10 +3138,12 @@ var ICON_OPTIONS = [
2277
3138
  { label: "None", value: "" },
2278
3139
  ...Object.keys(iconMap).map((name) => ({ label: name, value: name }))
2279
3140
  ];
2280
- function createBlockKit(ai) {
3141
+ function createBlockKit(ai, image, eyebrowClassName) {
2281
3142
  return {
3143
+ eyebrowClassName,
2282
3144
  textField: (label, placeholder) => ai ? ai.aiText(label, placeholder) : { type: "text", label, placeholder },
2283
3145
  textareaField: (label, placeholder, kind) => ai ? ai.aiTextarea(label, placeholder, kind) : { type: "textarea", label, placeholder },
3146
+ imageField: (label) => image ? image(label) : { type: "text", label },
2284
3147
  boolRadio: (label) => ({
2285
3148
  type: "radio",
2286
3149
  label,
@@ -2310,7 +3173,7 @@ var toneField = {
2310
3173
  ]
2311
3174
  };
2312
3175
  function createWebsiteBlocks(kit) {
2313
- const { textField, textareaField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
3176
+ const { textField, textareaField, imageField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
2314
3177
  const ctaField = (label) => ({
2315
3178
  type: "object",
2316
3179
  label,
@@ -2409,6 +3272,7 @@ function createWebsiteBlocks(kit) {
2409
3272
  fields: {
2410
3273
  eyebrow: textField("Eyebrow"),
2411
3274
  title: textField("Title"),
3275
+ emphasis: { type: "text", label: "Emphasised word" },
2412
3276
  subtitle: textareaField("Subtitle", void 0, "lead"),
2413
3277
  primaryCta: ctaField("Primary CTA"),
2414
3278
  secondaryCta: ctaField("Secondary CTA"),
@@ -2421,29 +3285,51 @@ function createWebsiteBlocks(kit) {
2421
3285
  ]
2422
3286
  },
2423
3287
  tone: toneField,
2424
- imageUrl: { type: "text", label: "Image URL" }
3288
+ imageUrl: imageField("Side image"),
3289
+ backgroundImage: imageField("Background image"),
3290
+ overlay: boolRadio("Background overlay")
2425
3291
  },
2426
3292
  defaultProps: {
2427
3293
  eyebrow: "Introducing",
2428
3294
  title: "Build a better website, faster.",
3295
+ emphasis: "",
2429
3296
  subtitle: "A flexible platform that helps your team ship polished, on-brand pages without waiting on engineering.",
2430
3297
  primaryCta: { label: "Get started", href: "#" },
2431
3298
  secondaryCta: { label: "Learn more", href: "#" },
2432
3299
  align: "left",
2433
3300
  tone: "default",
2434
- imageUrl: ""
3301
+ imageUrl: "",
3302
+ backgroundImage: "",
3303
+ overlay: true
2435
3304
  },
2436
- render: ({ id, eyebrow, title, subtitle, primaryCta, secondaryCta, align, tone, imageUrl }) => /* @__PURE__ */ jsx(
3305
+ render: ({
3306
+ id,
3307
+ eyebrow,
3308
+ title,
3309
+ emphasis,
3310
+ subtitle,
3311
+ primaryCta,
3312
+ secondaryCta,
3313
+ align,
3314
+ tone,
3315
+ imageUrl,
3316
+ backgroundImage,
3317
+ overlay
3318
+ }) => /* @__PURE__ */ jsx(
2437
3319
  Hero,
2438
3320
  {
2439
3321
  eyebrow,
2440
3322
  title,
3323
+ emphasis,
2441
3324
  subtitle,
2442
3325
  primaryCta,
2443
3326
  secondaryCta,
2444
3327
  align,
2445
3328
  tone,
2446
3329
  imageUrl,
3330
+ backgroundImage,
3331
+ overlay,
3332
+ eyebrowClassName: kit.eyebrowClassName,
2447
3333
  testId: blockTestId(id)
2448
3334
  }
2449
3335
  )
@@ -2560,15 +3446,30 @@ function createWebsiteBlocks(kit) {
2560
3446
  title: textField("Title"),
2561
3447
  subtitle: textareaField("Subtitle"),
2562
3448
  cta: ctaField("Button"),
2563
- tone: toneField
3449
+ tone: toneField,
3450
+ backgroundImage: imageField("Background image"),
3451
+ overlay: boolRadio("Background overlay")
2564
3452
  },
2565
3453
  defaultProps: {
2566
3454
  title: "Ready to get started?",
2567
3455
  subtitle: "Join thousands of teams already building with us.",
2568
3456
  cta: { label: "Start free trial", href: "#" },
2569
- tone: "default"
3457
+ tone: "default",
3458
+ backgroundImage: "",
3459
+ overlay: true
2570
3460
  },
2571
- render: ({ id, title, subtitle, cta, tone }) => /* @__PURE__ */ jsx(CTABanner, { title, subtitle, cta, tone, testId: blockTestId(id) })
3461
+ render: ({ id, title, subtitle, cta, tone, backgroundImage, overlay }) => /* @__PURE__ */ jsx(
3462
+ CTABanner,
3463
+ {
3464
+ title,
3465
+ subtitle,
3466
+ cta,
3467
+ tone,
3468
+ backgroundImage,
3469
+ overlay,
3470
+ testId: blockTestId(id)
3471
+ }
3472
+ )
2572
3473
  };
2573
3474
  const LogoCloudBlock = {
2574
3475
  label: "Logo cloud",
@@ -2660,7 +3561,7 @@ function createWebsiteBlocks(kit) {
2660
3561
  const ImageBlock = {
2661
3562
  label: "Image",
2662
3563
  fields: {
2663
- src: { type: "text", label: "Image URL" },
3564
+ src: imageField("Image"),
2664
3565
  alt: textField("Alt text"),
2665
3566
  aspect: {
2666
3567
  type: "radio",
@@ -2681,10 +3582,21 @@ function createWebsiteBlocks(kit) {
2681
3582
  { label: "L", value: "lg" },
2682
3583
  { label: "Full", value: "full" }
2683
3584
  ]
2684
- }
3585
+ },
3586
+ framed: boolRadio("Framed")
2685
3587
  },
2686
- defaultProps: { src: "", alt: "Image", aspect: "16:9", rounded: "lg" },
2687
- render: ({ id, src, alt, aspect, rounded }) => /* @__PURE__ */ jsx(BlockImage, { src, alt, aspect, rounded, testId: blockTestId(id) })
3588
+ defaultProps: { src: "", alt: "Image", aspect: "16:9", rounded: "lg", framed: false },
3589
+ render: ({ id, src, alt, aspect, rounded, framed }) => /* @__PURE__ */ jsx(
3590
+ BlockImage,
3591
+ {
3592
+ src,
3593
+ alt,
3594
+ aspect,
3595
+ rounded,
3596
+ framed,
3597
+ testId: blockTestId(id)
3598
+ }
3599
+ )
2688
3600
  };
2689
3601
  const AvatarBlockConfig = {
2690
3602
  label: "Avatar",
@@ -2830,8 +3742,440 @@ function createWebsiteBlocks(kit) {
2830
3742
  List: ListBlockConfig
2831
3743
  };
2832
3744
  }
3745
+ var lines = (s) => (s ?? "").split("\n").map((l) => l.trim()).filter(Boolean);
3746
+ function createSiteBlocks(kit) {
3747
+ const { textField, textareaField, imageField, boolRadio, blockTestId } = kit;
3748
+ const ctaField = (label) => ({
3749
+ type: "object",
3750
+ label,
3751
+ objectFields: {
3752
+ label: textField("Label"),
3753
+ href: { type: "text", label: "Link" }
3754
+ }
3755
+ });
3756
+ const columnsField = (values) => ({
3757
+ type: "radio",
3758
+ label: "Columns",
3759
+ options: values.map((v) => ({ label: v, value: v }))
3760
+ });
3761
+ const PricingBlock = {
3762
+ label: "Pricing",
3763
+ fields: {
3764
+ plans: {
3765
+ type: "array",
3766
+ label: "Plans",
3767
+ getItemSummary: (item) => item?.name || "Plan",
3768
+ defaultItemProps: {
3769
+ name: "Plan",
3770
+ price: "\xA30",
3771
+ period: "mo",
3772
+ description: "",
3773
+ features: "Feature one\nFeature two",
3774
+ ctaLabel: "Choose",
3775
+ ctaHref: "#",
3776
+ featured: false
3777
+ },
3778
+ arrayFields: {
3779
+ name: textField("Name"),
3780
+ price: { type: "text", label: "Price" },
3781
+ period: { type: "text", label: "Period" },
3782
+ description: textareaField("Description"),
3783
+ features: { type: "textarea", label: "Features (one per line)" },
3784
+ ctaLabel: { type: "text", label: "Button label" },
3785
+ ctaHref: { type: "text", label: "Button link" },
3786
+ featured: boolRadio("Featured")
3787
+ }
3788
+ }
3789
+ },
3790
+ defaultProps: {
3791
+ plans: [
3792
+ {
3793
+ name: "Starter",
3794
+ price: "\xA30",
3795
+ period: "mo",
3796
+ description: "For getting started.",
3797
+ features: "Up to 3 projects\nCommunity support",
3798
+ ctaLabel: "Get started",
3799
+ ctaHref: "#",
3800
+ featured: false
3801
+ },
3802
+ {
3803
+ name: "Pro",
3804
+ price: "\xA329",
3805
+ period: "mo",
3806
+ description: "For growing teams.",
3807
+ features: "Unlimited projects\nPriority support\nAdvanced analytics",
3808
+ ctaLabel: "Start free trial",
3809
+ ctaHref: "#",
3810
+ featured: true
3811
+ },
3812
+ {
3813
+ name: "Enterprise",
3814
+ price: "Custom",
3815
+ period: "",
3816
+ description: "For large organisations.",
3817
+ features: "SSO & SAML\nDedicated success manager\nSLA",
3818
+ ctaLabel: "Contact sales",
3819
+ ctaHref: "#",
3820
+ featured: false
3821
+ }
3822
+ ]
3823
+ },
3824
+ render: ({ id, plans }) => /* @__PURE__ */ jsx(
3825
+ PricingTable,
3826
+ {
3827
+ plans: (plans ?? []).map(
3828
+ (p) => ({
3829
+ name: p.name,
3830
+ price: p.price,
3831
+ period: p.period,
3832
+ description: p.description,
3833
+ features: lines(p.features),
3834
+ cta: { label: p.ctaLabel, href: p.ctaHref },
3835
+ featured: p.featured
3836
+ })
3837
+ ),
3838
+ testId: blockTestId(id)
3839
+ }
3840
+ )
3841
+ };
3842
+ const TabsBlock = {
3843
+ label: "Tabs",
3844
+ fields: {
3845
+ items: {
3846
+ type: "array",
3847
+ label: "Tabs",
3848
+ getItemSummary: (item) => item?.label || "Tab",
3849
+ defaultItemProps: { label: "Tab", content: "Tab content." },
3850
+ arrayFields: {
3851
+ label: textField("Label"),
3852
+ content: textareaField("Content")
3853
+ }
3854
+ }
3855
+ },
3856
+ defaultProps: {
3857
+ items: [
3858
+ { label: "Overview", content: "A quick overview of this section." },
3859
+ { label: "Details", content: "The finer details go here." },
3860
+ { label: "FAQ", content: "Answers to common questions." }
3861
+ ]
3862
+ },
3863
+ render: ({ id, items }) => /* @__PURE__ */ jsx(Tabs, { items, testId: blockTestId(id) })
3864
+ };
3865
+ const AccordionBlock = {
3866
+ label: "Accordion",
3867
+ fields: {
3868
+ allowMultiple: boolRadio("Allow multiple open"),
3869
+ items: {
3870
+ type: "array",
3871
+ label: "Items",
3872
+ getItemSummary: (item) => item?.title || "Item",
3873
+ defaultItemProps: { title: "Section", content: "Section content." },
3874
+ arrayFields: {
3875
+ title: textField("Title"),
3876
+ content: textareaField("Content")
3877
+ }
3878
+ }
3879
+ },
3880
+ defaultProps: {
3881
+ allowMultiple: false,
3882
+ items: [
3883
+ { title: "What is included?", content: "Everything you need to get started." },
3884
+ { title: "How does billing work?", content: "Monthly or annually, cancel anytime." }
3885
+ ]
3886
+ },
3887
+ render: ({ id, allowMultiple, items }) => /* @__PURE__ */ jsx(Accordion, { allowMultiple, items, testId: blockTestId(id) })
3888
+ };
3889
+ const GalleryBlock = {
3890
+ label: "Gallery",
3891
+ fields: {
3892
+ columns: columnsField(["2", "3", "4"]),
3893
+ aspect: {
3894
+ type: "radio",
3895
+ label: "Aspect",
3896
+ options: [
3897
+ { label: "1:1", value: "1:1" },
3898
+ { label: "4:3", value: "4:3" },
3899
+ { label: "16:9", value: "16:9" }
3900
+ ]
3901
+ },
3902
+ images: {
3903
+ type: "array",
3904
+ label: "Images",
3905
+ getItemSummary: (item) => item?.alt || "Image",
3906
+ defaultItemProps: { src: "", alt: "Image", caption: "" },
3907
+ arrayFields: {
3908
+ src: imageField("Image"),
3909
+ alt: { type: "text", label: "Alt text" },
3910
+ caption: { type: "text", label: "Caption" }
3911
+ }
3912
+ }
3913
+ },
3914
+ defaultProps: {
3915
+ columns: "3",
3916
+ aspect: "1:1",
3917
+ images: [
3918
+ { src: "", alt: "Image one", caption: "" },
3919
+ { src: "", alt: "Image two", caption: "" },
3920
+ { src: "", alt: "Image three", caption: "" }
3921
+ ]
3922
+ },
3923
+ render: ({ id, columns, aspect, images }) => /* @__PURE__ */ jsx(Gallery, { columns, aspect, images, testId: blockTestId(id) })
3924
+ };
3925
+ const VideoBlock = {
3926
+ label: "Video",
3927
+ fields: {
3928
+ url: { type: "text", label: "Video URL (YouTube/Vimeo/Loom or file)" },
3929
+ title: { type: "text", label: "Title" },
3930
+ aspect: {
3931
+ type: "radio",
3932
+ label: "Aspect",
3933
+ options: [
3934
+ { label: "16:9", value: "16:9" },
3935
+ { label: "4:3", value: "4:3" },
3936
+ { label: "1:1", value: "1:1" }
3937
+ ]
3938
+ },
3939
+ rounded: boolRadio("Rounded")
3940
+ },
3941
+ defaultProps: { url: "", title: "", aspect: "16:9", rounded: true },
3942
+ render: ({ id, url, title, aspect, rounded }) => /* @__PURE__ */ jsx(
3943
+ VideoEmbed,
3944
+ {
3945
+ url,
3946
+ title,
3947
+ aspect,
3948
+ rounded,
3949
+ testId: blockTestId(id)
3950
+ }
3951
+ )
3952
+ };
3953
+ const ContactFormBlock = {
3954
+ label: "Contact form",
3955
+ fields: {
3956
+ submitLabel: { type: "text", label: "Submit label" },
3957
+ action: { type: "text", label: "POST endpoint (optional)" },
3958
+ successMessage: textareaField("Success message"),
3959
+ fields: {
3960
+ type: "array",
3961
+ label: "Fields",
3962
+ getItemSummary: (item) => item?.label || "Field",
3963
+ defaultItemProps: { name: "field", label: "Field", type: "text", required: false },
3964
+ arrayFields: {
3965
+ name: { type: "text", label: "Name attribute" },
3966
+ label: textField("Label"),
3967
+ type: {
3968
+ type: "select",
3969
+ label: "Type",
3970
+ options: [
3971
+ { label: "Text", value: "text" },
3972
+ { label: "Email", value: "email" },
3973
+ { label: "Phone", value: "tel" },
3974
+ { label: "Multi-line", value: "textarea" }
3975
+ ]
3976
+ },
3977
+ required: boolRadio("Required")
3978
+ }
3979
+ }
3980
+ },
3981
+ defaultProps: {
3982
+ submitLabel: "Send",
3983
+ action: "",
3984
+ successMessage: "Thanks \u2014 we\u2019ll be in touch shortly.",
3985
+ fields: [
3986
+ { name: "name", label: "Name", type: "text", required: true },
3987
+ { name: "email", label: "Email", type: "email", required: true },
3988
+ { name: "message", label: "Message", type: "textarea", required: true }
3989
+ ]
3990
+ },
3991
+ render: ({ id, submitLabel, action, successMessage, fields }) => /* @__PURE__ */ jsx(
3992
+ ContactForm,
3993
+ {
3994
+ submitLabel,
3995
+ action: action || void 0,
3996
+ successMessage,
3997
+ fields,
3998
+ testId: blockTestId(id)
3999
+ }
4000
+ )
4001
+ };
4002
+ const BannerBlock = {
4003
+ label: "Banner",
4004
+ fields: {
4005
+ text: textField("Text"),
4006
+ cta: ctaField("CTA"),
4007
+ tone: {
4008
+ type: "radio",
4009
+ label: "Tone",
4010
+ options: [
4011
+ { label: "Primary", value: "primary" },
4012
+ { label: "Muted", value: "muted" },
4013
+ { label: "Dark", value: "ink" }
4014
+ ]
4015
+ }
4016
+ },
4017
+ defaultProps: {
4018
+ text: "We just shipped something new.",
4019
+ cta: { label: "Read more", href: "#" },
4020
+ tone: "primary"
4021
+ },
4022
+ render: ({ id, text, cta, tone }) => /* @__PURE__ */ jsx(Banner, { text, cta, tone, testId: blockTestId(id) })
4023
+ };
4024
+ const MediaTextBlock = {
4025
+ label: "Media & text",
4026
+ fields: {
4027
+ eyebrow: textField("Eyebrow"),
4028
+ title: textField("Title"),
4029
+ body: textareaField("Body", void 0, "lead"),
4030
+ imageUrl: imageField("Image"),
4031
+ mediaSide: {
4032
+ type: "radio",
4033
+ label: "Media side",
4034
+ options: [
4035
+ { label: "Left", value: "left" },
4036
+ { label: "Right", value: "right" }
4037
+ ]
4038
+ },
4039
+ cta: ctaField("CTA")
4040
+ },
4041
+ defaultProps: {
4042
+ eyebrow: "",
4043
+ title: "A closer look at what we do.",
4044
+ body: "Pair a compelling visual with the copy that explains it \u2014 the classic split section, on any theme.",
4045
+ imageUrl: "",
4046
+ mediaSide: "left",
4047
+ cta: { label: "Learn more", href: "#" }
4048
+ },
4049
+ render: ({ id, eyebrow, title, body, imageUrl, mediaSide, cta }) => /* @__PURE__ */ jsx(
4050
+ MediaText,
4051
+ {
4052
+ eyebrow,
4053
+ title,
4054
+ body,
4055
+ imageUrl,
4056
+ mediaSide,
4057
+ cta,
4058
+ testId: blockTestId(id)
4059
+ }
4060
+ )
4061
+ };
4062
+ const TeamGridBlock = {
4063
+ label: "Team",
4064
+ fields: {
4065
+ columns: columnsField(["2", "3", "4"]),
4066
+ members: {
4067
+ type: "array",
4068
+ label: "Members",
4069
+ getItemSummary: (item) => item?.name || "Member",
4070
+ defaultItemProps: { name: "New member", role: "Role", bio: "", avatarUrl: "" },
4071
+ arrayFields: {
4072
+ name: textField("Name"),
4073
+ role: { type: "text", label: "Role" },
4074
+ bio: textareaField("Bio"),
4075
+ avatarUrl: { type: "text", label: "Avatar URL" }
4076
+ }
4077
+ }
4078
+ },
4079
+ defaultProps: {
4080
+ columns: "3",
4081
+ members: [
4082
+ { name: "Alex Morgan", role: "Founder & CEO", bio: "", avatarUrl: "" },
4083
+ { name: "Sam Rivera", role: "Head of Product", bio: "", avatarUrl: "" },
4084
+ { name: "Jordan Lee", role: "Lead Engineer", bio: "", avatarUrl: "" }
4085
+ ]
4086
+ },
4087
+ render: ({ id, columns, members }) => /* @__PURE__ */ jsx(TeamGrid, { columns, members, testId: blockTestId(id) })
4088
+ };
4089
+ const StepsBlock = {
4090
+ label: "Steps",
4091
+ fields: {
4092
+ variant: {
4093
+ type: "radio",
4094
+ label: "Style",
4095
+ options: [
4096
+ { label: "Numbered", value: "numbered" },
4097
+ { label: "Timeline", value: "timeline" }
4098
+ ]
4099
+ },
4100
+ steps: {
4101
+ type: "array",
4102
+ label: "Steps",
4103
+ getItemSummary: (item) => item?.title || "Step",
4104
+ defaultItemProps: { title: "Step", description: "Describe this step." },
4105
+ arrayFields: {
4106
+ title: textField("Title"),
4107
+ description: textareaField("Description")
4108
+ }
4109
+ }
4110
+ },
4111
+ defaultProps: {
4112
+ variant: "numbered",
4113
+ steps: [
4114
+ { title: "Sign up", description: "Create your account in seconds." },
4115
+ { title: "Configure", description: "Set things up to match your workflow." },
4116
+ { title: "Launch", description: "Go live and start seeing results." }
4117
+ ]
4118
+ },
4119
+ render: ({ id, variant, steps }) => /* @__PURE__ */ jsx(Steps, { variant, steps, testId: blockTestId(id) })
4120
+ };
4121
+ const TestimonialGridBlock = {
4122
+ label: "Testimonial grid",
4123
+ fields: {
4124
+ columns: columnsField(["2", "3"]),
4125
+ items: {
4126
+ type: "array",
4127
+ label: "Testimonials",
4128
+ getItemSummary: (item) => item?.author || "Testimonial",
4129
+ defaultItemProps: { quote: "A great quote.", author: "Author", role: "", avatarUrl: "" },
4130
+ arrayFields: {
4131
+ quote: textareaField("Quote"),
4132
+ author: textField("Author"),
4133
+ role: { type: "text", label: "Role" },
4134
+ avatarUrl: { type: "text", label: "Avatar URL" }
4135
+ }
4136
+ }
4137
+ },
4138
+ defaultProps: {
4139
+ columns: "3",
4140
+ items: [
4141
+ {
4142
+ quote: "This changed how our whole team works.",
4143
+ author: "Alex Morgan",
4144
+ role: "CTO, Northwind",
4145
+ avatarUrl: ""
4146
+ },
4147
+ {
4148
+ quote: "The best tool we adopted this year.",
4149
+ author: "Sam Rivera",
4150
+ role: "Ops Lead, Acme",
4151
+ avatarUrl: ""
4152
+ },
4153
+ {
4154
+ quote: "Simple, fast, and it just works.",
4155
+ author: "Jordan Lee",
4156
+ role: "Founder, Globex",
4157
+ avatarUrl: ""
4158
+ }
4159
+ ]
4160
+ },
4161
+ render: ({ id, columns, items }) => /* @__PURE__ */ jsx(TestimonialGrid, { columns, items, testId: blockTestId(id) })
4162
+ };
4163
+ return {
4164
+ Pricing: PricingBlock,
4165
+ Tabs: TabsBlock,
4166
+ Accordion: AccordionBlock,
4167
+ Gallery: GalleryBlock,
4168
+ Video: VideoBlock,
4169
+ ContactForm: ContactFormBlock,
4170
+ Banner: BannerBlock,
4171
+ MediaText: MediaTextBlock,
4172
+ Team: TeamGridBlock,
4173
+ Steps: StepsBlock,
4174
+ TestimonialGrid: TestimonialGridBlock
4175
+ };
4176
+ }
2833
4177
  function createArbiBlocksConfig(opts = {}) {
2834
- const kit = createBlockKit(opts.aiFields);
4178
+ const kit = createBlockKit(opts.aiFields, opts.imageField, opts.eyebrowClassName);
2835
4179
  const { textField, textareaField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
2836
4180
  const SectionHeadingBlock = {
2837
4181
  label: "Section heading",
@@ -2850,7 +4194,17 @@ function createArbiBlocksConfig(opts = {}) {
2850
4194
  invert: boolRadio("Invert (on dark)")
2851
4195
  },
2852
4196
  defaultProps: { title: "Section title", align: "left", invert: false },
2853
- render: ({ id, title, eyebrow, lead, align, invert }) => /* @__PURE__ */ jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsx(SectionHeading, { title, eyebrow, lead, align, invert }) })
4197
+ render: ({ id, title, eyebrow, lead, align, invert }) => /* @__PURE__ */ jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsx(
4198
+ SectionHeading,
4199
+ {
4200
+ title,
4201
+ eyebrow,
4202
+ lead,
4203
+ align,
4204
+ invert,
4205
+ eyebrowClassName: kit.eyebrowClassName
4206
+ }
4207
+ ) })
2854
4208
  };
2855
4209
  const PageHeaderBlock = {
2856
4210
  label: "Page header",
@@ -2900,9 +4254,10 @@ function createArbiBlocksConfig(opts = {}) {
2900
4254
  type: "radio",
2901
4255
  label: "Accent",
2902
4256
  options: [
2903
- { label: "Ink", value: "ink" },
2904
- { label: "Emerald", value: "emerald" },
2905
- { label: "Brass", value: "brass" }
4257
+ { label: "Default", value: "default" },
4258
+ { label: "Primary", value: "primary" },
4259
+ { label: "Success", value: "success" },
4260
+ { label: "Muted", value: "muted" }
2906
4261
  ]
2907
4262
  },
2908
4263
  delta: {
@@ -2923,7 +4278,7 @@ function createArbiBlocksConfig(opts = {}) {
2923
4278
  },
2924
4279
  icon: iconField()
2925
4280
  },
2926
- defaultProps: { label: "Metric", value: "0", accent: "ink" },
4281
+ defaultProps: { label: "Metric", value: "0", accent: "default" },
2927
4282
  render: ({ id, label, value, hint, accent, delta, icon }) => /* @__PURE__ */ jsx(
2928
4283
  MetricCard,
2929
4284
  {
@@ -3026,12 +4381,39 @@ function createArbiBlocksConfig(opts = {}) {
3026
4381
  options: [
3027
4382
  { label: "Default", value: "default" },
3028
4383
  { label: "Muted", value: "muted" },
3029
- { label: "Ink", value: "ink" }
4384
+ { label: "Dark", value: "ink" }
3030
4385
  ]
3031
- }
4386
+ },
4387
+ padding: {
4388
+ type: "radio",
4389
+ label: "Padding",
4390
+ options: [
4391
+ { label: "None", value: "none" },
4392
+ { label: "S", value: "sm" },
4393
+ { label: "M", value: "md" },
4394
+ { label: "L", value: "lg" }
4395
+ ]
4396
+ },
4397
+ maxWidth: {
4398
+ type: "radio",
4399
+ label: "Width",
4400
+ options: [
4401
+ { label: "Narrow", value: "narrow" },
4402
+ { label: "Default", value: "default" },
4403
+ { label: "Wide", value: "wide" },
4404
+ { label: "Full", value: "full" }
4405
+ ]
4406
+ },
4407
+ texture: boolRadio("Texture")
4408
+ },
4409
+ defaultProps: {
4410
+ tone: "default",
4411
+ padding: "md",
4412
+ maxWidth: "default",
4413
+ texture: false,
4414
+ children: []
3032
4415
  },
3033
- defaultProps: { tone: "default", children: [] },
3034
- render: ({ id, tone, children: Children }) => /* @__PURE__ */ jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsx(Section, { tone, children: /* @__PURE__ */ jsx(Children, {}) }) })
4416
+ render: ({ id, tone, padding, maxWidth, texture, children: Children }) => /* @__PURE__ */ jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsx(Section, { tone, padding, maxWidth, texture, children: /* @__PURE__ */ jsx(Children, {}) }) })
3035
4417
  };
3036
4418
  const CardBlock = {
3037
4419
  label: "Card",
@@ -3107,6 +4489,7 @@ function createArbiBlocksConfig(opts = {}) {
3107
4489
  render: ({ id }) => /* @__PURE__ */ jsx(Separator, { "data-testid": blockTestId(id) })
3108
4490
  };
3109
4491
  const website = createWebsiteBlocks(kit);
4492
+ const site = createSiteBlocks(kit);
3110
4493
  const components = {
3111
4494
  SectionHeading: SectionHeadingBlock,
3112
4495
  PageHeader: PageHeaderBlock,
@@ -3118,7 +4501,8 @@ function createArbiBlocksConfig(opts = {}) {
3118
4501
  Button: ButtonBlock,
3119
4502
  Badge: BadgeBlock,
3120
4503
  Separator: SeparatorBlock,
3121
- ...website
4504
+ ...website,
4505
+ ...site
3122
4506
  };
3123
4507
  const config = {
3124
4508
  categories: {
@@ -3133,16 +4517,24 @@ function createArbiBlocksConfig(opts = {}) {
3133
4517
  "FeatureGrid",
3134
4518
  "StatGroup",
3135
4519
  "Testimonial",
4520
+ "TestimonialGrid",
3136
4521
  "CTABanner",
4522
+ "Banner",
3137
4523
  "LogoCloud",
3138
- "FAQ"
4524
+ "FAQ",
4525
+ "MediaText",
4526
+ "Steps",
4527
+ "Team"
3139
4528
  ]
3140
4529
  },
3141
4530
  Content: {
3142
4531
  title: "Content",
3143
4532
  components: ["SectionHeading", "PageHeader", "EmptyState", "RichText", "Callout", "List"]
3144
4533
  },
3145
- Media: { title: "Media", components: ["Image", "Avatar"] },
4534
+ Interactive: { title: "Interactive", components: ["Tabs", "Accordion"] },
4535
+ Media: { title: "Media", components: ["Image", "Avatar", "Gallery", "Video"] },
4536
+ Commerce: { title: "Commerce", components: ["Pricing"] },
4537
+ Forms: { title: "Forms", components: ["ContactForm"] },
3146
4538
  Navigation: { title: "Navigation", components: ["Navbar", "Footer"] },
3147
4539
  Data: { title: "Data", components: ["MetricCard", "DataTable"] },
3148
4540
  Primitives: { title: "Primitives", components: ["Button", "Badge", "Separator"] }
@@ -3152,8 +4544,8 @@ function createArbiBlocksConfig(opts = {}) {
3152
4544
  render: ({ children }) => /* @__PURE__ */ jsx(
3153
4545
  "div",
3154
4546
  {
3155
- className: "bg-background text-foreground font-sans",
3156
- "data-testid": "arbi-blocks-page-root",
4547
+ className: cn("bg-background text-foreground font-sans", opts.root?.className),
4548
+ "data-testid": opts.root?.testId ?? "arbi-blocks-page-root",
3157
4549
  children
3158
4550
  }
3159
4551
  )
@@ -3393,7 +4785,7 @@ function ModuleManager({ store, testIdPrefix = "builder" }) {
3393
4785
  ] }),
3394
4786
  /* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0 text-muted-foreground" }),
3395
4787
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3396
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-ink", children: m.label }),
4788
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: m.label }),
3397
4789
  /* @__PURE__ */ jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: m.to })
3398
4790
  ] }),
3399
4791
  m.arbi && /* @__PURE__ */ jsxs(Badge, { variant: "accent", className: "text-[10px]", children: [
@@ -3464,7 +4856,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
3464
4856
  "data-testid": `${testIdPrefix}-agents-configs`,
3465
4857
  children: [
3466
4858
  /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, { className: "flex items-center gap-2 text-base", children: [
3467
- /* @__PURE__ */ jsx(BrainCircuit, { className: "size-4 text-emerald" }),
4859
+ /* @__PURE__ */ jsx(BrainCircuit, { className: "size-4 text-primary" }),
3468
4860
  " Behaviour configurations",
3469
4861
  /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "ml-1", children: versions.length })
3470
4862
  ] }) }),
@@ -3486,7 +4878,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
3486
4878
  }
3487
4879
  ),
3488
4880
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3489
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-ink", children: v.title ?? "Untitled behaviour" }),
4881
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: v.title ?? "Untitled behaviour" }),
3490
4882
  /* @__PURE__ */ jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: v.external_id })
3491
4883
  ] }),
3492
4884
  /* @__PURE__ */ jsx(ConfigModelBadge, { configId: v.external_id, testIdPrefix })
@@ -3500,7 +4892,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
3500
4892
  ),
3501
4893
  /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: "rounded-xl", "data-testid": `${testIdPrefix}-agents-agents`, children: [
3502
4894
  /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, { className: "flex items-center gap-2 text-base", children: [
3503
- /* @__PURE__ */ jsx(Bot, { className: "size-4 text-emerald" }),
4895
+ /* @__PURE__ */ jsx(Bot, { className: "size-4 text-primary" }),
3504
4896
  " Agents",
3505
4897
  /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "ml-1", children: agents.length })
3506
4898
  ] }) }),
@@ -3522,7 +4914,7 @@ function AgentsPanel({ store, testIdPrefix = "builder" }) {
3522
4914
  }
3523
4915
  ),
3524
4916
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3525
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-ink", children: (a.given_name ?? "Agent").replace(/-/g, " ") }),
4917
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: (a.given_name ?? "Agent").replace(/-/g, " ") }),
3526
4918
  /* @__PURE__ */ jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: a.external_id })
3527
4919
  ] })
3528
4920
  ]
@@ -3547,7 +4939,7 @@ function ExportPanel({
3547
4939
  return /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: "rounded-xl", "data-testid": `${testIdPrefix}-export-panel`, children: [
3548
4940
  /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, { className: "flex items-center justify-between text-base", children: [
3549
4941
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
3550
- /* @__PURE__ */ jsx(FileJson, { className: "size-4 text-emerald" }),
4942
+ /* @__PURE__ */ jsx(FileJson, { className: "size-4 text-primary" }),
3551
4943
  " Vertical config"
3552
4944
  ] }),
3553
4945
  /* @__PURE__ */ jsxs(
@@ -3567,7 +4959,7 @@ function ExportPanel({
3567
4959
  /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(
3568
4960
  "pre",
3569
4961
  {
3570
- className: "scrollbar-slim max-h-[520px] overflow-auto rounded-md bg-ink p-4 font-mono text-[11px] leading-relaxed text-parchment",
4962
+ className: "scrollbar-slim max-h-[520px] overflow-auto rounded-md bg-foreground p-4 font-mono text-[11px] leading-relaxed text-background",
3571
4963
  "data-testid": `${testIdPrefix}-export-preview`,
3572
4964
  children: json
3573
4965
  }
@@ -3585,7 +4977,7 @@ function VerticalBuilder({
3585
4977
  }) {
3586
4978
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", "data-testid": `page-${testIdPrefix}`, children: [
3587
4979
  header,
3588
- /* @__PURE__ */ jsxs(Tabs, { defaultValue: "theme", children: [
4980
+ /* @__PURE__ */ jsxs(Tabs$1, { defaultValue: "theme", children: [
3589
4981
  /* @__PURE__ */ jsxs(TabsList, { "data-testid": `${testIdPrefix}-tabs`, children: [
3590
4982
  /* @__PURE__ */ jsxs(TabsTrigger, { value: "theme", "data-testid": `${testIdPrefix}-tab-theme`, children: [
3591
4983
  /* @__PURE__ */ jsx(Palette, { className: "size-4" }),
@@ -3629,6 +5021,6 @@ function VerticalBuilder({
3629
5021
  ] });
3630
5022
  }
3631
5023
 
3632
- export { AgentsPanel, AvatarBlock, BlockImage, CTABanner, Callout, Columns, ConnectionProvider, Container, DataTable, DataTableBlock, EmptyState, FAQ, FeatureGrid, Footer, GridView, Hero, LegalArbiProvider, ListBlock, LogoCloud, MetricCard, ModuleManager, Navbar, PageHeader, PageRenderer, RichTextBlock, Section, SectionHeading, Spacer, StatGroup, StudioEditor, THEME_STYLE_ID, Testimonial, ThemeEditor, Toolbar, VerticalBuilder, applyStoredTheme, applyThemeVars, clearThemeVars, createAgentSelectionStore, createAiFields, createArbiBlocksConfig, createBlockKit, createModuleRegistry, createModuleStore, createPromptLibrary, createStudioPersistence, createThemeStore, createVerticalExport, createWebsiteBlocks, daysUntil, fmtDate, fmtDateTime, fromNow, gbp, hrs, iconMap, initials, isAuthenticated, matterToContext, pct, tid, toHslTriplet, useConnection, useFirmAiTask, useSemanticSearch, useWorkspaceDocs };
5024
+ export { ASSET_REF_PREFIX, Accordion, AgentsPanel, AssetResolverProvider, AvatarBlock, Banner, BlockImage, CTABanner, Callout, Columns, ConnectionProvider, ContactForm, Container, DataTable, DataTableBlock, EmptyState, FAQ, FeatureGrid, Footer, Gallery, GridView, Hero, LegalArbiProvider, ListBlock, 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, applyStoredTheme, applyThemeVars, asAssetRef, assetRefId, clearThemeVars, createAgentSelectionStore, createAiFields, createArbiBlocksConfig, createArbiMaterializer, createBlockKit, createImageField, 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, useSemanticSearch, useWorkspaceDocs };
3633
5025
  //# sourceMappingURL=index.js.map
3634
5026
  //# sourceMappingURL=index.js.map