@frontlit/page-builder 0.1.0-alpha.1 → 0.1.0-alpha.3

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.
Files changed (116) hide show
  1. package/README.md +38 -2
  2. package/dist/blocks/index.d.ts +35 -21
  3. package/dist/blocks/index.js +4 -3
  4. package/dist/builder/index.d.ts +8 -11
  5. package/dist/builder/index.js +7 -5
  6. package/dist/{button-C3VrMJkr.d.ts → button-OLKN3wPf.d.ts} +3 -3
  7. package/dist/{chunk-7W6Q4WA7.js → chunk-36QHCIPM.js} +252 -282
  8. package/dist/chunk-36QHCIPM.js.map +1 -0
  9. package/dist/{chunk-GTY75HW2.js → chunk-ATNM5JHI.js} +2 -2
  10. package/dist/chunk-ATNM5JHI.js.map +1 -0
  11. package/dist/chunk-BBI7UHOA.js +56 -0
  12. package/dist/chunk-BBI7UHOA.js.map +1 -0
  13. package/dist/{chunk-MO76KZY2.js → chunk-GIKTOAQS.js} +661 -423
  14. package/dist/chunk-GIKTOAQS.js.map +1 -0
  15. package/dist/chunk-HWOVSHIW.js +4 -0
  16. package/dist/{chunk-RUYOL7JZ.js.map → chunk-HWOVSHIW.js.map} +1 -1
  17. package/dist/{chunk-SSQWYMQR.js → chunk-IU4KATI2.js} +74 -129
  18. package/dist/chunk-IU4KATI2.js.map +1 -0
  19. package/dist/chunk-J7Z4UW45.js +3418 -0
  20. package/dist/chunk-J7Z4UW45.js.map +1 -0
  21. package/dist/{chunk-7KIX4WAO.js → chunk-K4WCLHMS.js} +1435 -1382
  22. package/dist/chunk-K4WCLHMS.js.map +1 -0
  23. package/dist/chunk-XU7ALPGQ.js +22 -0
  24. package/dist/chunk-XU7ALPGQ.js.map +1 -0
  25. package/dist/components/index.d.ts +33 -8
  26. package/dist/components/index.js +43 -2
  27. package/dist/components/index.js.map +1 -1
  28. package/dist/data-slots.d.ts +3 -3
  29. package/dist/data-slots.js +1 -1
  30. package/dist/fonts/archivo/archivo-latin-variable.woff2 +0 -0
  31. package/dist/fonts/hanken-grotesk/hanken-grotesk-latin-variable.woff2 +0 -0
  32. package/dist/fonts/jetbrains-mono/jetbrains-mono-latin-variable.woff2 +0 -0
  33. package/dist/fonts/spline-sans-mono/spline-sans-mono-latin-variable.woff2 +0 -0
  34. package/dist/heading-DQCxQt38.d.ts +20 -0
  35. package/dist/image-Bk8kPXwP.d.ts +52 -0
  36. package/dist/index.d.ts +18 -11
  37. package/dist/index.js +9 -7
  38. package/dist/media-CnLImjVC.d.ts +21 -0
  39. package/dist/models/index.d.ts +6 -6
  40. package/dist/models/index.js +2 -1
  41. package/dist/{page-DfVl_aHX.d.ts → page-B5Gmm-Ys.d.ts} +2 -3
  42. package/dist/primitives/index.d.ts +287 -61
  43. package/dist/primitives/index.js +2 -1
  44. package/dist/renderer/index.d.ts +12 -8
  45. package/dist/renderer/index.js +6 -4
  46. package/dist/{section-background-BgOCp5l3.d.ts → section-background-6V_LCi5e.d.ts} +1 -1
  47. package/dist/server/index.d.ts +103 -0
  48. package/dist/server/index.js +2649 -0
  49. package/dist/server/index.js.map +1 -0
  50. package/dist/server/islands/code-snippet.d.ts +17 -0
  51. package/dist/server/islands/code-snippet.js +72 -0
  52. package/dist/server/islands/code-snippet.js.map +1 -0
  53. package/dist/server/islands/email-form.d.ts +50 -0
  54. package/dist/server/islands/email-form.js +406 -0
  55. package/dist/server/islands/email-form.js.map +1 -0
  56. package/dist/server/islands/header-mobile-menu.d.ts +22 -0
  57. package/dist/server/islands/header-mobile-menu.js +422 -0
  58. package/dist/server/islands/header-mobile-menu.js.map +1 -0
  59. package/dist/server/islands/inline-embed.d.ts +28 -0
  60. package/dist/server/islands/inline-embed.js +84 -0
  61. package/dist/server/islands/inline-embed.js.map +1 -0
  62. package/dist/server/islands/marquee-track.d.ts +18 -0
  63. package/dist/server/islands/marquee-track.js +67 -0
  64. package/dist/server/islands/marquee-track.js.map +1 -0
  65. package/dist/server/islands/nav-link.d.ts +37 -0
  66. package/dist/server/islands/nav-link.js +67 -0
  67. package/dist/server/islands/nav-link.js.map +1 -0
  68. package/dist/server/islands/support-form.d.ts +17 -0
  69. package/dist/server/islands/support-form.js +469 -0
  70. package/dist/server/islands/support-form.js.map +1 -0
  71. package/dist/server/islands/theme-mode-switcher.d.ts +38 -0
  72. package/dist/server/islands/theme-mode-switcher.js +169 -0
  73. package/dist/server/islands/theme-mode-switcher.js.map +1 -0
  74. package/dist/server/islands/video-with-preview.d.ts +13 -0
  75. package/dist/server/islands/video-with-preview.js +269 -0
  76. package/dist/server/islands/video-with-preview.js.map +1 -0
  77. package/dist/server/primitives.d.ts +200 -0
  78. package/dist/server/primitives.js +888 -0
  79. package/dist/server/primitives.js.map +1 -0
  80. package/dist/server/schema.d.ts +11308 -0
  81. package/dist/server/schema.js +6179 -0
  82. package/dist/server/schema.js.map +1 -0
  83. package/dist/server/theme-CF3fIKvw.d.ts +9 -0
  84. package/dist/{theme-style-39nxXQy0.d.ts → server/theme-style-5_AGy-4V.d.ts} +43 -1
  85. package/dist/server/theme-style-BPaMwk88.d.ts +183 -0
  86. package/dist/server/theme.d.ts +28 -0
  87. package/dist/{chunk-RBJBODQP.js → server/theme.js} +903 -1243
  88. package/dist/server/theme.js.map +1 -0
  89. package/dist/server/widget-YH9BrOwY.d.ts +77 -0
  90. package/dist/styles.css +4016 -8317
  91. package/dist/theme-style-BVd5tOsc.d.ts +183 -0
  92. package/dist/theme.css +186 -191
  93. package/dist/{widget--uEOpdq3.d.ts → widget-CpE2HdgU.d.ts} +25 -18
  94. package/package.json +71 -11
  95. package/schema/page.v1.json +3855 -0
  96. package/schema/site.v1.json +3811 -0
  97. package/dist/alignment-dZDzN96G.d.ts +0 -5
  98. package/dist/chunk-7KIX4WAO.js.map +0 -1
  99. package/dist/chunk-7W6Q4WA7.js.map +0 -1
  100. package/dist/chunk-GTY75HW2.js.map +0 -1
  101. package/dist/chunk-MO76KZY2.js.map +0 -1
  102. package/dist/chunk-RBJBODQP.js.map +0 -1
  103. package/dist/chunk-RUYOL7JZ.js +0 -4
  104. package/dist/chunk-SSQWYMQR.js.map +0 -1
  105. package/dist/fonts/montserrat/montserrat-latin-100-normal.woff2 +0 -0
  106. package/dist/fonts/montserrat/montserrat-latin-200-normal.woff2 +0 -0
  107. package/dist/fonts/montserrat/montserrat-latin-300-normal.woff2 +0 -0
  108. package/dist/fonts/montserrat/montserrat-latin-400-normal.woff2 +0 -0
  109. package/dist/fonts/montserrat/montserrat-latin-500-normal.woff2 +0 -0
  110. package/dist/fonts/montserrat/montserrat-latin-600-normal.woff2 +0 -0
  111. package/dist/fonts/montserrat/montserrat-latin-700-normal.woff2 +0 -0
  112. package/dist/fonts/montserrat/montserrat-latin-800-normal.woff2 +0 -0
  113. package/dist/fonts/montserrat/montserrat-latin-900-normal.woff2 +0 -0
  114. package/dist/fonts/source-sans-3/source-sans-3-latin-400-normal.woff2 +0 -0
  115. package/dist/image-D7GlnOio.d.ts +0 -30
  116. package/dist/media-Njv378j7.d.ts +0 -13
@@ -1,23 +1,33 @@
1
1
  "use client";
2
- import { blockRegistry, getAddableBlocks, getWidget, PageRenderer } from './chunk-SSQWYMQR.js';
3
- import { generateUniqueId, FieldGroup, TextField, ToggleField } from './chunk-7KIX4WAO.js';
4
- import { ThemeModeSwitcher } from './chunk-7W6Q4WA7.js';
5
- import { themes, cn, Button, Switch, Input, PageCard, PageCardContent, PageCardHeader, Link, Button2 } from './chunk-RBJBODQP.js';
6
- import React2, { useState, useMemo, useCallback, useRef, useEffect } from 'react';
2
+ import { blockRegistry, resolveThemeColor, getAddableBlocks, getWidget, PageRenderer } from './chunk-IU4KATI2.js';
3
+ import { ThemeModeSwitcher } from './chunk-BBI7UHOA.js';
4
+ import { generateUniqueId, FieldGroup, TextField, ToggleField, TitleSizeField } from './chunk-K4WCLHMS.js';
5
+ import { themes, cn, Button, Switch, Input, PageCard, PageCardContent, PageCardHeader, Link, Button2, fluidFontSize, TAILWIND_TEXT_PX } from './chunk-J7Z4UW45.js';
6
+ import { getMediaUrl } from './chunk-XU7ALPGQ.js';
7
+ import React3, { useState, useMemo, useCallback, useRef, useEffect } from 'react';
7
8
  import * as Icons from 'lucide-react';
8
9
  import { Smartphone, Tablet, Monitor, Palette, Globe, Plus, HelpCircle, X, ImageIcon, Sparkles, ExternalLink, Check, ChevronLeft, LayoutTemplate, Layers3, Pin, ChevronDown, FilePenLine, Brush, ChevronRight, Type, SquareMousePointer, Columns3, Pencil, Info } from 'lucide-react';
9
10
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
10
11
  import { createPortal } from 'react-dom';
12
+ import { ImageUploadDialog } from '@frontlit/media-uploader';
11
13
 
12
14
  // src/utils/debounce.ts
13
15
  function debounce(fn, wait) {
14
16
  let timeout;
15
- return function(...args) {
17
+ let pending = null;
18
+ const debounced = function(...args) {
19
+ pending = args;
16
20
  clearTimeout(timeout);
17
- timeout = setTimeout(() => {
18
- fn(...args);
19
- }, wait);
21
+ timeout = setTimeout(debounced.flush, wait);
20
22
  };
23
+ debounced.flush = () => {
24
+ clearTimeout(timeout);
25
+ if (!pending) return;
26
+ const args = pending;
27
+ pending = null;
28
+ fn(...args);
29
+ };
30
+ return debounced;
21
31
  }
22
32
 
23
33
  // src/utils/color-mode.ts
@@ -44,12 +54,16 @@ function createWidgetInstance(name, overrides) {
44
54
  }
45
55
  };
46
56
  }
57
+ function withDefaultSettings(layout) {
58
+ return layout.map((widget) => {
59
+ const defaults = blockRegistry[widget.name]?.getDefaultSettings?.();
60
+ return defaults ? { ...widget, settings: { ...defaults, ...widget.settings } } : widget;
61
+ });
62
+ }
47
63
  function ensureRequiredSharedWidgets(layout) {
48
64
  const header = layout.find((widget) => widget.name === "header") ?? createWidgetInstance("header");
49
65
  const footer = layout.find((widget) => widget.name === "footer") ?? createWidgetInstance("footer");
50
- const body = layout.filter(
51
- (widget) => !["header", "footer"].includes(widget.name)
52
- );
66
+ const body = layout.filter((widget) => !["header", "footer"].includes(widget.name));
53
67
  return [
54
68
  {
55
69
  ...header,
@@ -213,7 +227,11 @@ var THEME_FONT_VARS = [
213
227
  "--font-bebas-neue",
214
228
  "--font-lato",
215
229
  "--font-pt-sans",
216
- "--font-quicksand"
230
+ "--font-quicksand",
231
+ "--font-archivo",
232
+ "--font-jetbrains-mono",
233
+ "--font-hanken-grotesk",
234
+ "--font-spline-sans-mono"
217
235
  ];
218
236
  function readCssVar(sourceDoc, name) {
219
237
  const view = sourceDoc.defaultView;
@@ -250,6 +268,7 @@ function PreviewFrame({
250
268
  const iframe = iframeRef.current;
251
269
  if (!iframe) return;
252
270
  let observer;
271
+ let resizeObserver;
253
272
  const init = () => {
254
273
  const doc = iframe.contentDocument;
255
274
  if (!doc) return;
@@ -268,12 +287,21 @@ function PreviewFrame({
268
287
  characterData: true
269
288
  });
270
289
  setMountNode(doc.body);
290
+ resizeObserver?.disconnect();
291
+ const measure = () => iframe.style.setProperty(
292
+ "--pb-scrollbar",
293
+ `${Math.max(0, iframe.clientWidth - doc.documentElement.clientWidth)}px`
294
+ );
295
+ resizeObserver = new ResizeObserver(measure);
296
+ resizeObserver.observe(doc.documentElement);
297
+ measure();
271
298
  };
272
299
  if (iframe.contentDocument?.readyState === "complete") init();
273
300
  iframe.addEventListener("load", init);
274
301
  return () => {
275
302
  iframe.removeEventListener("load", init);
276
303
  observer?.disconnect();
304
+ resizeObserver?.disconnect();
277
305
  };
278
306
  }, []);
279
307
  return /* @__PURE__ */ jsx("iframe", { ref: iframeRef, title, className, children: mountNode ? createPortal(children, mountNode) : null });
@@ -335,6 +363,67 @@ function BlockPicker({
335
363
  }
336
364
  );
337
365
  }
366
+ function ConfirmDialog({
367
+ title,
368
+ description,
369
+ confirmLabel,
370
+ onConfirm,
371
+ onClose
372
+ }) {
373
+ const cancelRef = useRef(null);
374
+ useEffect(() => {
375
+ cancelRef.current?.focus();
376
+ const onKeyDown = (event) => {
377
+ if (event.key === "Escape") onClose();
378
+ };
379
+ document.addEventListener("keydown", onKeyDown);
380
+ return () => document.removeEventListener("keydown", onKeyDown);
381
+ }, [onClose]);
382
+ return /* @__PURE__ */ jsx(
383
+ "div",
384
+ {
385
+ className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",
386
+ onClick: onClose,
387
+ children: /* @__PURE__ */ jsxs(
388
+ "div",
389
+ {
390
+ role: "alertdialog",
391
+ "aria-modal": "true",
392
+ "aria-label": title,
393
+ className: "flex w-full max-w-sm flex-col gap-4 rounded-lg border border-border bg-background p-5 shadow-xl",
394
+ onClick: (e) => e.stopPropagation(),
395
+ children: [
396
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
397
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground", children: title }),
398
+ /* @__PURE__ */ jsx("p", { className: "text-xs leading-5 text-muted-foreground", children: description })
399
+ ] }),
400
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
401
+ /* @__PURE__ */ jsx(
402
+ "button",
403
+ {
404
+ ref: cancelRef,
405
+ type: "button",
406
+ className: "inline-flex h-8 items-center justify-center rounded-lg border border-input bg-background px-3 text-xs font-medium text-foreground hover:bg-muted",
407
+ onClick: onClose,
408
+ children: "Cancel"
409
+ }
410
+ ),
411
+ /* @__PURE__ */ jsx(
412
+ "button",
413
+ {
414
+ type: "button",
415
+ className: "inline-flex h-8 items-center justify-center rounded-lg bg-destructive px-3 text-xs font-medium text-destructive-foreground hover:bg-destructive/90",
416
+ onClick: onConfirm,
417
+ children: confirmLabel
418
+ }
419
+ )
420
+ ] })
421
+ ]
422
+ }
423
+ )
424
+ }
425
+ );
426
+ }
338
427
  function PanelHeader({
339
428
  title,
340
429
  onClose,
@@ -401,7 +490,6 @@ function PageBuilderSlider({
401
490
  onChange,
402
491
  tooltip,
403
492
  className,
404
- allowsReset = false,
405
493
  unit = "px"
406
494
  }) {
407
495
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-2.5", className), children: [
@@ -424,20 +512,7 @@ function PageBuilderSlider({
424
512
  className: "h-7 w-12 rounded-md border border-input bg-muted/20 px-1.5 py-1 text-center text-xs font-semibold text-foreground outline-none transition-colors focus:border-ring focus:bg-background focus:ring-2 focus:ring-ring/20"
425
513
  }
426
514
  ),
427
- unit && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-muted-foreground", children: unit }),
428
- allowsReset && /* @__PURE__ */ jsx(
429
- "button",
430
- {
431
- type: "button",
432
- title: "Reset",
433
- className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
434
- onClick: (e) => {
435
- e.preventDefault();
436
- onChange();
437
- },
438
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3 text-muted-foreground hover:text-foreground" })
439
- }
440
- )
515
+ unit && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-muted-foreground", children: unit })
441
516
  ] })
442
517
  ] }),
443
518
  /* @__PURE__ */ jsx(
@@ -464,67 +539,64 @@ function ColorSelector({
464
539
  allowReset = true,
465
540
  description
466
541
  }) {
467
- const [localValue, setLocalValue] = React2.useState(value);
468
- React2.useEffect(() => {
542
+ const [localValue, setLocalValue] = React3.useState(value);
543
+ React3.useEffect(() => {
469
544
  setLocalValue(value);
470
545
  }, [value]);
471
546
  const handleChange = (newValue) => {
472
547
  setLocalValue(newValue);
473
548
  onChange(newValue);
474
549
  };
550
+ const invalid = Boolean(localValue) && !resolveThemeColor(localValue);
475
551
  const swatchValue = /^#[0-9a-fA-F]{6}$/.test(localValue) ? localValue : "#000000";
476
- return /* @__PURE__ */ jsxs(
477
- "div",
478
- {
479
- className: cn(
480
- "flex min-w-0 items-center justify-between gap-2",
481
- className
552
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 items-center justify-between gap-2", className), children: [
553
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
554
+ /* @__PURE__ */ jsx(PageBuilderPropertyHeader, { label: title, tooltip }),
555
+ description && /* @__PURE__ */ jsx("p", { className: "break-words text-xs text-muted-foreground", children: description })
556
+ ] }),
557
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [
558
+ /* @__PURE__ */ jsx(
559
+ "input",
560
+ {
561
+ type: "color",
562
+ value: swatchValue,
563
+ onChange: (e) => handleChange(e.currentTarget.value),
564
+ className: "h-7 w-9 shrink-0 cursor-pointer rounded-md border border-input bg-background p-0.5",
565
+ "aria-label": `${title} swatch`
566
+ }
482
567
  ),
483
- children: [
484
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
485
- /* @__PURE__ */ jsx(PageBuilderPropertyHeader, { label: title, tooltip }),
486
- description && /* @__PURE__ */ jsx("p", { className: "break-words text-xs text-muted-foreground", children: description })
487
- ] }),
488
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [
489
- /* @__PURE__ */ jsx(
490
- "input",
491
- {
492
- type: "color",
493
- value: swatchValue,
494
- onChange: (e) => handleChange(e.currentTarget.value),
495
- className: "h-7 w-9 shrink-0 cursor-pointer rounded-md border border-input bg-background p-0.5",
496
- "aria-label": `${title} swatch`
497
- }
498
- ),
499
- /* @__PURE__ */ jsx(
500
- "input",
501
- {
502
- type: "text",
503
- value: localValue ?? "",
504
- placeholder: "#000000",
505
- onChange: (e) => handleChange(e.currentTarget.value),
506
- className: "h-7 w-20 min-w-0 rounded-md border border-input bg-background px-2 py-1 text-xs text-foreground outline-none focus:ring-2 focus:ring-ring/20",
507
- "aria-label": `${title} hex value`
508
- }
568
+ /* @__PURE__ */ jsx(
569
+ "input",
570
+ {
571
+ type: "text",
572
+ value: localValue ?? "",
573
+ placeholder: "#000000 or oklch(\u2026)",
574
+ title: "A hex colour, or a CSS colour such as oklch(0.6 0.2 30)",
575
+ "aria-invalid": invalid || void 0,
576
+ onChange: (e) => handleChange(e.currentTarget.value),
577
+ className: cn(
578
+ "h-7 w-28 min-w-0 rounded-md border border-input bg-background px-2 py-1 text-xs text-foreground outline-none focus:ring-2 focus:ring-ring/20",
579
+ invalid && "border-destructive focus:ring-destructive/30"
509
580
  ),
510
- allowReset && /* @__PURE__ */ jsx(
511
- "button",
512
- {
513
- type: "button",
514
- title: "Reset",
515
- className: "inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md hover:bg-muted/50",
516
- onClick: (e) => {
517
- e.preventDefault();
518
- setLocalValue("");
519
- onChange();
520
- },
521
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3 text-muted-foreground hover:text-foreground" })
522
- }
523
- )
524
- ] })
525
- ]
526
- }
527
- );
581
+ "aria-label": `${title} hex value`
582
+ }
583
+ ),
584
+ allowReset && /* @__PURE__ */ jsx(
585
+ "button",
586
+ {
587
+ type: "button",
588
+ title: "Reset",
589
+ className: "inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md hover:bg-muted/50",
590
+ onClick: (e) => {
591
+ e.preventDefault();
592
+ setLocalValue("");
593
+ onChange();
594
+ },
595
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3 text-muted-foreground hover:text-foreground" })
596
+ }
597
+ )
598
+ ] })
599
+ ] });
528
600
  }
529
601
  var MAX_WIDTH_MAP = {
530
602
  1: "max-w-2xl",
@@ -547,10 +619,6 @@ function MaxWidthSelector({
547
619
  ([_, v]) => v === (value ?? themeDefault)
548
620
  )?.[0] || "5";
549
621
  const handleChange = (newValue) => {
550
- if (newValue === void 0) {
551
- onChange();
552
- return;
553
- }
554
622
  onChange(getMaxWidthTWClass(newValue));
555
623
  };
556
624
  return /* @__PURE__ */ jsx(
@@ -563,7 +631,6 @@ function MaxWidthSelector({
563
631
  value: parseInt(numericValue),
564
632
  tooltip: "Maximum width of the content",
565
633
  className,
566
- allowsReset: true,
567
634
  unit: ""
568
635
  }
569
636
  );
@@ -594,14 +661,8 @@ function VerticalPaddingSelector({
594
661
  className = "",
595
662
  themeDefault
596
663
  }) {
597
- const numericValue = Object.entries(PADDING_MAP).find(
598
- ([_, v]) => v === (value ?? themeDefault)
599
- )?.[0] || "6";
664
+ const numericValue = Object.entries(PADDING_MAP).find(([_, v]) => v === (value ?? themeDefault))?.[0] || "6";
600
665
  const handleChange = (newValue) => {
601
- if (newValue === void 0) {
602
- onChange();
603
- return;
604
- }
605
666
  onChange(getVerticalPaddingTWClass(newValue));
606
667
  };
607
668
  return /* @__PURE__ */ jsx(
@@ -1145,10 +1206,7 @@ function PanelSelect({
1145
1206
  "select",
1146
1207
  {
1147
1208
  id,
1148
- className: cn(
1149
- controlClass,
1150
- "cursor-pointer appearance-none pr-8"
1151
- ),
1209
+ className: cn(controlClass, "cursor-pointer appearance-none pr-8"),
1152
1210
  value,
1153
1211
  onChange: (e) => onChange(e.target.value),
1154
1212
  children: options.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
@@ -1255,7 +1313,7 @@ function FixedBackgroundToggle({
1255
1313
  ] }) });
1256
1314
  }
1257
1315
  function BackgroundPreview({ value }) {
1258
- const imageUrl = value.type === "image" && typeof value.media?.file === "string" ? value.media.file : void 0;
1316
+ const imageUrl = value.type === "image" ? getMediaUrl(value.media) ?? void 0 : void 0;
1259
1317
  const previewStyle = (() => {
1260
1318
  if (value.type === "color") {
1261
1319
  return {
@@ -1283,43 +1341,36 @@ function BackgroundPreview({ value }) {
1283
1341
  })();
1284
1342
  const typeLabel = BG_TYPES.find((t) => t.type === value.type)?.label ?? "Background";
1285
1343
  return /* @__PURE__ */ jsxs("div", { className: "min-w-0 max-w-full overflow-hidden rounded-xl border border-border/70 shadow-sm", children: [
1286
- /* @__PURE__ */ jsxs(
1287
- "div",
1288
- {
1289
- className: "relative h-14 w-full max-w-full",
1290
- style: previewStyle,
1291
- children: [
1292
- value.type === "image" && imageUrl && (value.blur || 0) > 0 ? /* @__PURE__ */ jsx(
1293
- "div",
1294
- {
1295
- className: "absolute inset-0",
1296
- style: {
1297
- backdropFilter: `blur(${value.blur}px)`
1298
- }
1299
- }
1300
- ) : null,
1301
- value.type === "image" && value.overlay?.color ? /* @__PURE__ */ jsx(
1302
- "div",
1303
- {
1304
- className: "absolute inset-0",
1305
- style: {
1306
- backgroundColor: value.overlay.color,
1307
- opacity: (value.overlay.opacity || 0) / 10,
1308
- mixBlendMode: value.overlay.blendMode || "normal"
1309
- }
1310
- }
1311
- ) : null,
1312
- value.type === "image" && !imageUrl ? /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex items-center justify-center gap-1.5 text-[11px] text-muted-foreground", children: [
1313
- /* @__PURE__ */ jsx(ImageIcon, { className: "h-3.5 w-3.5", "aria-hidden": true }),
1314
- "No image yet"
1315
- ] }) : null,
1316
- value.type === "gradient" && !value.backgroundImage ? /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex items-center justify-center gap-1.5 text-[11px] text-muted-foreground", children: [
1317
- /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5", "aria-hidden": true }),
1318
- "Choose a pattern"
1319
- ] }) : null
1320
- ]
1321
- }
1322
- ),
1344
+ /* @__PURE__ */ jsxs("div", { className: "relative h-14 w-full max-w-full", style: previewStyle, children: [
1345
+ value.type === "image" && imageUrl && (value.blur || 0) > 0 ? /* @__PURE__ */ jsx(
1346
+ "div",
1347
+ {
1348
+ className: "absolute inset-0",
1349
+ style: {
1350
+ backdropFilter: `blur(${value.blur}px)`
1351
+ }
1352
+ }
1353
+ ) : null,
1354
+ value.type === "image" && value.overlay?.color ? /* @__PURE__ */ jsx(
1355
+ "div",
1356
+ {
1357
+ className: "absolute inset-0",
1358
+ style: {
1359
+ backgroundColor: value.overlay.color,
1360
+ opacity: (value.overlay.opacity || 0) / 10,
1361
+ mixBlendMode: value.overlay.blendMode || "normal"
1362
+ }
1363
+ }
1364
+ ) : null,
1365
+ value.type === "image" && !imageUrl ? /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex items-center justify-center gap-1.5 text-[11px] text-muted-foreground", children: [
1366
+ /* @__PURE__ */ jsx(ImageIcon, { className: "h-3.5 w-3.5", "aria-hidden": true }),
1367
+ "No image yet"
1368
+ ] }) : null,
1369
+ value.type === "gradient" && !value.backgroundImage ? /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex items-center justify-center gap-1.5 text-[11px] text-muted-foreground", children: [
1370
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5", "aria-hidden": true }),
1371
+ "Choose a pattern"
1372
+ ] }) : null
1373
+ ] }),
1323
1374
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-border/60 bg-muted/20 px-3 py-1.5", children: [
1324
1375
  /* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground", children: "Preview" }),
1325
1376
  /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-foreground", children: typeLabel })
@@ -1331,7 +1382,9 @@ function SectionBackgroundPanel({
1331
1382
  onChange,
1332
1383
  className
1333
1384
  }) {
1334
- const [patternFilter, setPatternFilter] = useState("all");
1385
+ const [patternFilter, setPatternFilter] = useState(
1386
+ "all"
1387
+ );
1335
1388
  const handleTypeChange = (type) => {
1336
1389
  onChange({ ...value, type });
1337
1390
  };
@@ -1453,29 +1506,115 @@ function SectionBackgroundPanel({
1453
1506
  SectionCard,
1454
1507
  {
1455
1508
  title: "Source",
1456
- description: "Paste a public image URL to use as the section backdrop.",
1457
- children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1458
- /* @__PURE__ */ jsx(FieldLabel, { htmlFor: "background-image-url", children: "Image URL" }),
1459
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1460
- /* @__PURE__ */ jsx(
1461
- ImageIcon,
1509
+ description: "Upload an image or paste a public URL for the section backdrop.",
1510
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1511
+ getMediaUrl(value.media) ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1512
+ /* @__PURE__ */ jsx("div", { className: "relative aspect-video w-full overflow-hidden rounded-lg border border-border bg-muted/40", children: /* @__PURE__ */ jsx(
1513
+ "img",
1462
1514
  {
1463
- className: "pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground",
1464
- "aria-hidden": true
1465
- }
1466
- ),
1467
- /* @__PURE__ */ jsx(
1468
- PanelInput,
1469
- {
1470
- id: "background-image-url",
1471
- value: value.media?.file || "",
1472
- onChange: (e) => handleBackgroundChange({
1473
- media: e.target.value ? { file: e.target.value } : {}
1474
- }),
1475
- placeholder: "https://example.com/background.jpg",
1476
- className: "pl-9"
1515
+ src: getMediaUrl(value.media),
1516
+ alt: "Background preview",
1517
+ className: "h-full w-full object-cover"
1477
1518
  }
1478
- )
1519
+ ) }),
1520
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1521
+ /* @__PURE__ */ jsx(
1522
+ ImageUploadDialog,
1523
+ {
1524
+ title: "Change background image",
1525
+ description: "Upload an image or select one from your library.",
1526
+ onSelect: (selected) => {
1527
+ const selMedia = selected.media;
1528
+ const mediaLitId = selMedia?.mediaLitId ?? selMedia?.mediaId ?? void 0;
1529
+ handleBackgroundChange({
1530
+ media: {
1531
+ ...mediaLitId ? { mediaId: mediaLitId, mediaLitId } : {},
1532
+ url: selected.src,
1533
+ file: selected.src,
1534
+ thumbnailUrl: selMedia?.thumbnailUrl ?? selected.src,
1535
+ thumbnail: selMedia?.thumbnailUrl ?? selected.src,
1536
+ alt: selected.alt
1537
+ }
1538
+ });
1539
+ },
1540
+ children: /* @__PURE__ */ jsxs(
1541
+ "button",
1542
+ {
1543
+ type: "button",
1544
+ className: "inline-flex h-8 items-center justify-center rounded-lg border border-input bg-background px-3 text-xs font-medium text-foreground hover:bg-muted",
1545
+ children: [
1546
+ /* @__PURE__ */ jsx(ImageIcon, { className: "mr-1.5 h-3.5 w-3.5" }),
1547
+ "Change image"
1548
+ ]
1549
+ }
1550
+ )
1551
+ }
1552
+ ),
1553
+ /* @__PURE__ */ jsx(
1554
+ "button",
1555
+ {
1556
+ type: "button",
1557
+ className: "inline-flex h-8 items-center justify-center rounded-lg border border-destructive/30 bg-background px-3 text-xs font-medium text-destructive hover:bg-destructive/10",
1558
+ onClick: () => handleBackgroundChange({ media: void 0 }),
1559
+ children: "Remove"
1560
+ }
1561
+ )
1562
+ ] })
1563
+ ] }) : /* @__PURE__ */ jsx(
1564
+ ImageUploadDialog,
1565
+ {
1566
+ title: "Choose background image",
1567
+ description: "Upload an image or select one from your library.",
1568
+ onSelect: (selected) => {
1569
+ const selMedia = selected.media;
1570
+ const mediaLitId = selMedia?.mediaLitId ?? selMedia?.mediaId ?? void 0;
1571
+ handleBackgroundChange({
1572
+ media: {
1573
+ ...mediaLitId ? { mediaId: mediaLitId, mediaLitId } : {},
1574
+ url: selected.src,
1575
+ file: selected.src,
1576
+ thumbnailUrl: selMedia?.thumbnailUrl ?? selected.src,
1577
+ thumbnail: selMedia?.thumbnailUrl ?? selected.src,
1578
+ alt: selected.alt
1579
+ }
1580
+ });
1581
+ },
1582
+ children: /* @__PURE__ */ jsxs(
1583
+ "button",
1584
+ {
1585
+ type: "button",
1586
+ className: "flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-border bg-muted/20 py-3 text-xs font-medium text-muted-foreground transition-colors hover:border-primary/40 hover:bg-muted/40 hover:text-foreground",
1587
+ children: [
1588
+ /* @__PURE__ */ jsx(ImageIcon, { className: "h-4 w-4" }),
1589
+ "Upload or choose image"
1590
+ ]
1591
+ }
1592
+ )
1593
+ }
1594
+ ),
1595
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 pt-1", children: [
1596
+ /* @__PURE__ */ jsx(FieldLabel, { htmlFor: "background-image-url", children: "Or paste image URL" }),
1597
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1598
+ /* @__PURE__ */ jsx(
1599
+ ImageIcon,
1600
+ {
1601
+ className: "pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground",
1602
+ "aria-hidden": true
1603
+ }
1604
+ ),
1605
+ /* @__PURE__ */ jsx(
1606
+ PanelInput,
1607
+ {
1608
+ id: "background-image-url",
1609
+ value: getMediaUrl(value.media) || "",
1610
+ onChange: (e) => handleBackgroundChange({
1611
+ media: e.target.value ? { file: e.target.value, url: e.target.value } : void 0
1612
+ }),
1613
+ placeholder: "https://example.com/background.jpg",
1614
+ className: "pl-9"
1615
+ }
1616
+ )
1617
+ ] })
1479
1618
  ] })
1480
1619
  ] })
1481
1620
  }
@@ -1618,13 +1757,7 @@ function SectionBackgroundPanel({
1618
1757
  className: "inline-flex items-center gap-1 text-[10px] font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline",
1619
1758
  children: [
1620
1759
  "Docs",
1621
- /* @__PURE__ */ jsx(
1622
- ExternalLink,
1623
- {
1624
- className: "h-3 w-3",
1625
- "aria-hidden": true
1626
- }
1627
- )
1760
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3", "aria-hidden": true })
1628
1761
  ]
1629
1762
  }
1630
1763
  ),
@@ -1707,13 +1840,7 @@ function SectionBackgroundPanel({
1707
1840
  maskImage: preset.apply.gradientMaskImage
1708
1841
  },
1709
1842
  "aria-hidden": true,
1710
- children: isSelected ? /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm", children: /* @__PURE__ */ jsx(
1711
- Check,
1712
- {
1713
- className: "h-2.5 w-2.5",
1714
- "aria-hidden": true
1715
- }
1716
- ) }) : null
1843
+ children: isSelected ? /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm", children: /* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5", "aria-hidden": true }) }) : null
1717
1844
  }
1718
1845
  ),
1719
1846
  /* @__PURE__ */ jsx("span", { className: "truncate border-t border-border/60 px-1 py-1 text-[10px] font-semibold leading-tight text-foreground", children: preset.label })
@@ -1853,13 +1980,7 @@ function SectionBackgroundPanel({
1853
1980
  className: "inline-flex items-center gap-1 text-[10px] font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline",
1854
1981
  children: [
1855
1982
  "Docs",
1856
- /* @__PURE__ */ jsx(
1857
- ExternalLink,
1858
- {
1859
- className: "h-3 w-3",
1860
- "aria-hidden": true
1861
- }
1862
- )
1983
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3", "aria-hidden": true })
1863
1984
  ]
1864
1985
  }
1865
1986
  ),
@@ -1966,6 +2087,7 @@ function BlockSettings({
1966
2087
  }) {
1967
2088
  const [tab, setTab] = useState("data");
1968
2089
  const Editor = definition.editor;
2090
+ const DesignEditor = definition.designEditor;
1969
2091
  const settings = instance.settings ?? {};
1970
2092
  return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 max-w-full flex-col", children: [
1971
2093
  /* @__PURE__ */ jsx(
@@ -1997,14 +2119,7 @@ function BlockSettings({
1997
2119
  })
1998
2120
  }
1999
2121
  ),
2000
- /* @__PURE__ */ jsx("div", { className: "pt-5", children: tab === "data" && Editor && /* @__PURE__ */ jsx(
2001
- Editor,
2002
- {
2003
- settings,
2004
- theme,
2005
- onChange
2006
- }
2007
- ) }),
2122
+ /* @__PURE__ */ jsx("div", { className: "pt-5", children: tab === "data" && Editor && /* @__PURE__ */ jsx(Editor, { settings, theme, onChange }) }),
2008
2123
  tab === "design" && /* @__PURE__ */ jsxs("div", { className: "min-w-0 max-w-full space-y-5 pt-5", children: [
2009
2124
  /* @__PURE__ */ jsxs("div", { children: [
2010
2125
  /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-foreground", children: "Refine the look" }),
@@ -2039,13 +2154,7 @@ function BlockSettings({
2039
2154
  }),
2040
2155
  children: [
2041
2156
  /* @__PURE__ */ jsx("span", { children: layout.name }),
2042
- active && /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(
2043
- Check,
2044
- {
2045
- className: "h-3 w-3",
2046
- "aria-hidden": "true"
2047
- }
2048
- ) })
2157
+ active && /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3", "aria-hidden": "true" }) })
2049
2158
  ]
2050
2159
  },
2051
2160
  layout.id
@@ -2054,6 +2163,21 @@ function BlockSettings({
2054
2163
  }
2055
2164
  )
2056
2165
  ] }),
2166
+ DesignEditor && /* @__PURE__ */ jsx(
2167
+ DesignEditor,
2168
+ {
2169
+ settings,
2170
+ theme,
2171
+ onChange
2172
+ }
2173
+ ),
2174
+ definition.titleSize && /* @__PURE__ */ jsx(FieldGroup, { title: "Title", children: /* @__PURE__ */ jsx(
2175
+ TitleSizeField,
2176
+ {
2177
+ settings,
2178
+ onChange: (partial) => onChange({ ...settings, ...partial })
2179
+ }
2180
+ ) }),
2057
2181
  /* @__PURE__ */ jsx(
2058
2182
  DesignFieldGroup,
2059
2183
  {
@@ -2124,15 +2248,7 @@ function ThemeCard({
2124
2248
  /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: name }),
2125
2249
  /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
2126
2250
  onEdit && /* @__PURE__ */ jsx(CardButton, { label: `Edit ${name}`, onClick: onEdit, children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" }) }),
2127
- onUse && /* @__PURE__ */ jsx(
2128
- CardButton,
2129
- {
2130
- label: `Use ${name}`,
2131
- disabled: active,
2132
- onClick: onUse,
2133
- children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })
2134
- }
2135
- )
2251
+ onUse && /* @__PURE__ */ jsx(CardButton, { label: `Use ${name}`, disabled: active, onClick: onUse, children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) })
2136
2252
  ] })
2137
2253
  ] }),
2138
2254
  /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: palette.map((color, i) => /* @__PURE__ */ jsx(
@@ -2151,14 +2267,7 @@ function PanelLabel({
2151
2267
  htmlFor,
2152
2268
  children
2153
2269
  }) {
2154
- return /* @__PURE__ */ jsx(
2155
- "label",
2156
- {
2157
- htmlFor,
2158
- className: "text-xs font-medium text-foreground",
2159
- children
2160
- }
2161
- );
2270
+ return /* @__PURE__ */ jsx("label", { htmlFor, className: "text-xs font-medium text-foreground", children });
2162
2271
  }
2163
2272
  function PanelInput2(props) {
2164
2273
  const { className, ...rest } = props;
@@ -2338,7 +2447,8 @@ var fontFamilyOptions = {
2338
2447
  { label: "Roboto", value: "font-roboto" },
2339
2448
  { label: "Mulish", value: "font-mulish" },
2340
2449
  { label: "Nunito", value: "font-nunito" },
2341
- { label: "Work Sans", value: "font-work-sans" }
2450
+ { label: "Work Sans", value: "font-work-sans" },
2451
+ { label: "Hanken Grotesk", value: "font-hanken-grotesk" }
2342
2452
  ],
2343
2453
  Serif: [
2344
2454
  { label: "Merriweather", value: "font-merriweather" },
@@ -2353,19 +2463,30 @@ var fontFamilyOptions = {
2353
2463
  { label: "Raleway", value: "font-raleway" },
2354
2464
  { label: "Rubik", value: "font-rubik" },
2355
2465
  { label: "Oswald", value: "font-oswald" },
2356
- { label: "Bebas Neue", value: "font-bebas-neue" }
2466
+ { label: "Bebas Neue", value: "font-bebas-neue" },
2467
+ { label: "Archivo", value: "font-archivo" }
2357
2468
  ],
2358
2469
  Modern: [
2359
2470
  { label: "Lato", value: "font-lato" },
2360
2471
  { label: "PT Sans", value: "font-pt-sans" },
2361
2472
  { label: "Quicksand", value: "font-quicksand" }
2473
+ ],
2474
+ Mono: [
2475
+ { label: "JetBrains Mono", value: "font-jetbrains-mono" },
2476
+ { label: "Spline Sans Mono", value: "font-spline-sans-mono" }
2362
2477
  ]
2363
2478
  };
2479
+ function defaultFluid(fontSize) {
2480
+ const max = TAILWIND_TEXT_PX[fontSize ?? ""] ?? 48;
2481
+ return { min: Math.round(max * 0.66), max };
2482
+ }
2364
2483
  function TypographySelector({
2365
2484
  value,
2366
- onChange
2485
+ onChange,
2486
+ allowFluid
2367
2487
  }) {
2368
2488
  const update = (patch) => onChange({ ...value, ...patch });
2489
+ const fluid = value?.fluid;
2369
2490
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6 p-2", children: [
2370
2491
  /* @__PURE__ */ jsx("div", { className: "h-[100px] overflow-auto rounded-lg border p-2", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-2", children: [
2371
2492
  /* @__PURE__ */ jsx(PanelLabel, { children: "Preview" }),
@@ -2380,6 +2501,7 @@ function TypographySelector({
2380
2501
  value?.letterSpacing || "",
2381
2502
  value?.textTransform || "normal-case"
2382
2503
  ),
2504
+ style: fluid ? { fontSize: fluidFontSize(fluid) } : void 0,
2383
2505
  children: "The quick brown fox jumps over the lazy dog"
2384
2506
  }
2385
2507
  )
@@ -2395,25 +2517,61 @@ function TypographySelector({
2395
2517
  onChange: (e) => update({ fontFamily: e.target.value }),
2396
2518
  children: [
2397
2519
  !value?.fontFamily && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select font family" }),
2398
- Object.entries(fontFamilyOptions).map(
2399
- ([category, fonts]) => /* @__PURE__ */ jsx("optgroup", { label: category, children: fonts.map((font) => /* @__PURE__ */ jsx(
2400
- "option",
2401
- {
2402
- value: font.value,
2403
- children: font.label
2404
- },
2405
- font.value
2406
- )) }, category)
2407
- )
2520
+ Object.entries(fontFamilyOptions).map(([category, fonts]) => /* @__PURE__ */ jsx("optgroup", { label: category, children: fonts.map((font) => /* @__PURE__ */ jsx("option", { value: font.value, children: font.label }, font.value)) }, category))
2408
2521
  ]
2409
2522
  }
2410
2523
  )
2411
2524
  ] }),
2525
+ allowFluid && /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-md border p-2", children: [
2526
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-xs font-medium text-foreground", children: [
2527
+ "Scale with screen size",
2528
+ /* @__PURE__ */ jsx(
2529
+ "input",
2530
+ {
2531
+ type: "checkbox",
2532
+ checked: Boolean(fluid),
2533
+ onChange: (e) => update({
2534
+ fluid: e.target.checked ? defaultFluid(value.fontSize) : void 0
2535
+ })
2536
+ }
2537
+ )
2538
+ ] }),
2539
+ fluid && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
2540
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2541
+ /* @__PURE__ */ jsx(PanelLabel, { htmlFor: "fluid-min", children: "Phone size (px)" }),
2542
+ /* @__PURE__ */ jsx(
2543
+ PanelInput2,
2544
+ {
2545
+ id: "fluid-min",
2546
+ type: "number",
2547
+ min: 8,
2548
+ max: 200,
2549
+ value: fluid.min,
2550
+ onChange: (e) => update({ fluid: { ...fluid, min: Number(e.target.value) || 0 } })
2551
+ }
2552
+ )
2553
+ ] }),
2554
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2555
+ /* @__PURE__ */ jsx(PanelLabel, { htmlFor: "fluid-max", children: "Desktop size (px)" }),
2556
+ /* @__PURE__ */ jsx(
2557
+ PanelInput2,
2558
+ {
2559
+ id: "fluid-max",
2560
+ type: "number",
2561
+ min: 8,
2562
+ max: 200,
2563
+ value: fluid.max,
2564
+ onChange: (e) => update({ fluid: { ...fluid, max: Number(e.target.value) || 0 } })
2565
+ }
2566
+ )
2567
+ ] })
2568
+ ] })
2569
+ ] }),
2412
2570
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
2413
2571
  /* @__PURE__ */ jsx(
2414
2572
  PanelSelect2,
2415
2573
  {
2416
- title: "Font Size",
2574
+ title: fluid ? "Font Size (unused while scaling)" : "Font Size",
2417
2575
  value: value?.fontSize || "",
2418
2576
  onChange: (v) => update({ fontSize: v }),
2419
2577
  options: fontSizeOptions,
@@ -2510,25 +2668,14 @@ function InteractiveSelector({
2510
2668
  /* @__PURE__ */ jsx("div", { children: "Card content goes here" })
2511
2669
  ] }) });
2512
2670
  case "input":
2513
- return /* @__PURE__ */ jsx(
2514
- Input,
2515
- {
2516
- theme,
2517
- type: "text",
2518
- placeholder: "Demo Input"
2519
- }
2520
- );
2671
+ return /* @__PURE__ */ jsx(Input, { theme, type: "text", placeholder: "Demo Input" });
2521
2672
  default:
2522
2673
  return null;
2523
2674
  }
2524
2675
  };
2525
2676
  const renderPaddingConfig = (allowedPaddingX, allowedPaddingY) => {
2526
- const filteredPaddingX = allowedPaddingX ? paddingOptions.x.filter(
2527
- (option) => allowedPaddingX.includes(option.value)
2528
- ) : paddingOptions.x;
2529
- const filteredPaddingY = allowedPaddingY ? paddingOptions.y.filter(
2530
- (option) => allowedPaddingY.includes(option.value)
2531
- ) : paddingOptions.y;
2677
+ const filteredPaddingX = allowedPaddingX ? paddingOptions.x.filter((option) => allowedPaddingX.includes(option.value)) : paddingOptions.x;
2678
+ const filteredPaddingY = allowedPaddingY ? paddingOptions.y.filter((option) => allowedPaddingY.includes(option.value)) : paddingOptions.y;
2532
2679
  return /* @__PURE__ */ jsx(AccordionSection, { title: "Padding", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
2533
2680
  /* @__PURE__ */ jsx(
2534
2681
  PanelSelect2,
@@ -2709,11 +2856,7 @@ var structureDisplayNames = {
2709
2856
  page: "Page",
2710
2857
  section: "Section"
2711
2858
  };
2712
- function StructureSelector({
2713
- type,
2714
- theme,
2715
- onChange
2716
- }) {
2859
+ function StructureSelector({ type, theme, onChange }) {
2717
2860
  if (type === "page") {
2718
2861
  const pageValue = theme.structure.page;
2719
2862
  return /* @__PURE__ */ jsx("div", { className: "space-y-6 p-2", children: /* @__PURE__ */ jsx(
@@ -2769,7 +2912,24 @@ function StructureSelector({
2769
2912
  updatePadding({ y });
2770
2913
  }
2771
2914
  }
2772
- )
2915
+ ),
2916
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-xs font-medium text-foreground", children: [
2917
+ "Hairline between sections",
2918
+ /* @__PURE__ */ jsx(
2919
+ "input",
2920
+ {
2921
+ type: "checkbox",
2922
+ checked: Boolean(sectionValue?.dividers),
2923
+ onChange: (e) => onChange({
2924
+ ...theme,
2925
+ structure: {
2926
+ ...theme.structure,
2927
+ section: { ...sectionValue, dividers: e.target.checked || void 0 }
2928
+ }
2929
+ })
2930
+ }
2931
+ )
2932
+ ] })
2773
2933
  ] });
2774
2934
  }
2775
2935
  var sections = [
@@ -2795,6 +2955,7 @@ var sections = [
2795
2955
  }
2796
2956
  ];
2797
2957
  var typographyDisplayNames = {
2958
+ display: "Display (hero)",
2798
2959
  header1: "Header 1",
2799
2960
  header2: "Header 2",
2800
2961
  header3: "Header 3",
@@ -2807,12 +2968,26 @@ var typographyDisplayNames = {
2807
2968
  caption: "Caption",
2808
2969
  link: "Link",
2809
2970
  button: "Button Text",
2810
- input: "Input Text"
2971
+ input: "Input Text",
2972
+ code: "Code"
2973
+ };
2974
+ var DEFAULT_CODE_TYPOGRAPHY = {
2975
+ fontFamily: "font-jetbrains-mono",
2976
+ fontSize: "text-sm",
2977
+ fontWeight: "font-normal",
2978
+ lineHeight: "leading-relaxed"
2811
2979
  };
2980
+ var fluidTypography = /* @__PURE__ */ new Set([
2981
+ "display",
2982
+ "header1",
2983
+ "header2",
2984
+ "header3",
2985
+ "header4"
2986
+ ]);
2812
2987
  var typographyCategories = [
2813
2988
  {
2814
2989
  name: "Headers",
2815
- items: ["header1", "header2", "header3", "header4", "preheader"]
2990
+ items: ["display", "header1", "header2", "header3", "header4", "preheader"]
2816
2991
  },
2817
2992
  {
2818
2993
  name: "Subheaders",
@@ -2824,7 +2999,7 @@ var typographyCategories = [
2824
2999
  },
2825
3000
  {
2826
3001
  name: "Interactive Elements",
2827
- items: ["link", "button", "input"]
3002
+ items: ["link", "button", "input", "code"]
2828
3003
  }
2829
3004
  ];
2830
3005
  var colorCategories = [
@@ -2915,12 +3090,7 @@ var colorCategories = [
2915
3090
  ]
2916
3091
  }
2917
3092
  ];
2918
- var paletteColorOrder = [
2919
- "primary",
2920
- "accent",
2921
- "secondary",
2922
- "border"
2923
- ];
3093
+ var paletteColorOrder = ["primary", "accent", "secondary", "border"];
2924
3094
  function capitalize(value) {
2925
3095
  return value.charAt(0).toUpperCase() + value.slice(1);
2926
3096
  }
@@ -2942,45 +3112,35 @@ function ThemeEditor({
2942
3112
  onApply,
2943
3113
  onCustomThemesChange
2944
3114
  }) {
2945
- const [navigationStack, setNavigationStack] = useState(
2946
- []
2947
- );
2948
- const [collapsedCategories, setCollapsedCategories] = useState(() => {
2949
- const initial = {};
2950
- typographyCategories.forEach((category) => {
2951
- initial[category.name] = true;
2952
- });
2953
- return initial;
2954
- });
3115
+ const [navigationStack, setNavigationStack] = useState([]);
2955
3116
  const navigateTo = useCallback((item) => {
2956
3117
  setNavigationStack((prev) => [...prev, item]);
2957
3118
  }, []);
2958
3119
  const navigateBack = useCallback(() => {
2959
3120
  setNavigationStack((prev) => prev.slice(0, -1));
2960
3121
  }, []);
2961
- const toggleCategory = useCallback((categoryName) => {
2962
- setCollapsedCategories((prev) => ({
2963
- ...prev,
2964
- [categoryName]: !prev[categoryName]
2965
- }));
2966
- }, []);
2967
- const applyStyle = useCallback(
2968
- (style) => {
2969
- const updated = { ...theme, theme: style };
2970
- onApply(updated);
3122
+ const commitTheme = useCallback(
3123
+ (updated) => {
3124
+ if (updated.id === appliedThemeId) {
3125
+ onApply(updated);
3126
+ } else {
3127
+ onPreview(updated);
3128
+ }
2971
3129
  onCustomThemesChange(
2972
3130
  customThemes.map((t) => t.id === updated.id ? updated : t)
2973
3131
  );
2974
3132
  },
2975
- [theme, customThemes, onApply, onCustomThemesChange]
3133
+ [appliedThemeId, customThemes, onApply, onPreview, onCustomThemesChange]
3134
+ );
3135
+ const applyStyle = useCallback(
3136
+ (style) => commitTheme({ ...theme, theme: style }),
3137
+ [theme, commitTheme]
2976
3138
  );
2977
3139
  const startEditing = useCallback(
2978
3140
  (themeItem, isSystem) => {
2979
3141
  let target = themeItem;
2980
3142
  if (isSystem) {
2981
- const takenNames = [...systemThemes, ...customThemes].map(
2982
- (t) => t.name
2983
- );
3143
+ const takenNames = [...systemThemes, ...customThemes].map((t) => t.name);
2984
3144
  target = {
2985
3145
  id: generateUniqueId(),
2986
3146
  name: copyName(themeItem.name, takenNames),
@@ -2988,10 +3148,10 @@ function ThemeEditor({
2988
3148
  };
2989
3149
  onCustomThemesChange([...customThemes, target]);
2990
3150
  }
2991
- onApply(target);
3151
+ onPreview(target);
2992
3152
  setNavigationStack([{ id: "categories", label: target.name }]);
2993
3153
  },
2994
- [systemThemes, customThemes, onApply, onCustomThemesChange]
3154
+ [systemThemes, customThemes, onPreview, onCustomThemesChange]
2995
3155
  );
2996
3156
  const renderDrillButton = (label, onClick) => /* @__PURE__ */ jsxs(
2997
3157
  "button",
@@ -3020,14 +3180,10 @@ function ThemeEditor({
3020
3180
  );
3021
3181
  const renderRootView = () => /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
3022
3182
  /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-muted-foreground", children: "System Themes" }),
3023
- /* @__PURE__ */ jsx("div", { className: "space-y-2", children: systemThemes.map(
3024
- (themeItem) => renderThemeCard(themeItem, true)
3025
- ) }),
3183
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: systemThemes.map((themeItem) => renderThemeCard(themeItem, true)) }),
3026
3184
  /* @__PURE__ */ jsx("hr", { className: "my-4 border-muted" }),
3027
3185
  /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-muted-foreground", children: "Custom Themes" }),
3028
- /* @__PURE__ */ jsx("div", { className: "space-y-2", children: customThemes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "No custom themes yet" }) : customThemes.map(
3029
- (themeItem) => renderThemeCard(themeItem, false)
3030
- ) })
3186
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: customThemes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "No custom themes yet" }) : customThemes.map((themeItem) => renderThemeCard(themeItem, false)) })
3031
3187
  ] });
3032
3188
  const renderCategoriesView = () => /* @__PURE__ */ jsx("div", { className: "space-y-1", children: sections.map((section) => /* @__PURE__ */ jsxs(
3033
3189
  "button",
@@ -3084,80 +3240,72 @@ function ThemeEditor({
3084
3240
  ))
3085
3241
  }
3086
3242
  ),
3087
- /* @__PURE__ */ jsx("div", { className: "space-y-4", children: colorCategories.map((category) => /* @__PURE__ */ jsx(
3088
- AccordionSection,
3089
- {
3090
- title: category.label,
3091
- children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: category.colors.map(
3092
- (color) => category.key === "shadows" ? /* @__PURE__ */ jsxs(
3093
- "div",
3094
- {
3095
- className: "space-y-1.5",
3096
- children: [
3097
- /* @__PURE__ */ jsx(PanelLabel, { children: color.displayName }),
3098
- /* @__PURE__ */ jsx(
3099
- PanelInput2,
3100
- {
3101
- value: colors[color.name],
3102
- onChange: (e) => updateColor(
3103
- color.name,
3104
- e.target.value
3105
- )
3106
- }
3107
- )
3108
- ]
3109
- },
3110
- color.name
3111
- ) : /* @__PURE__ */ jsx(
3112
- ColorSelector,
3113
- {
3114
- title: color.displayName,
3115
- value: colors[color.name],
3116
- onChange: (value) => updateColor(
3117
- color.name,
3118
- value ?? ""
3119
- ),
3120
- allowReset: false
3121
- },
3122
- color.name
3123
- )
3124
- ) })
3125
- },
3126
- category.key
3127
- )) })
3243
+ /* @__PURE__ */ jsx("div", { className: "space-y-4", children: colorCategories.map((category) => /* @__PURE__ */ jsx(AccordionSection, { title: category.label, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: category.colors.map(
3244
+ (color) => category.key === "shadows" ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3245
+ /* @__PURE__ */ jsx(PanelLabel, { children: color.displayName }),
3246
+ /* @__PURE__ */ jsx(
3247
+ PanelInput2,
3248
+ {
3249
+ value: colors[color.name],
3250
+ onChange: (e) => updateColor(color.name, e.target.value)
3251
+ }
3252
+ )
3253
+ ] }, color.name) : /* @__PURE__ */ jsx(
3254
+ ColorSelector,
3255
+ {
3256
+ title: color.displayName,
3257
+ value: colors[color.name],
3258
+ onChange: (value) => updateColor(color.name, value ?? ""),
3259
+ allowReset: false
3260
+ },
3261
+ color.name
3262
+ )
3263
+ ) }) }, category.key)) })
3128
3264
  ] });
3129
3265
  };
3130
- const renderTypographyList = () => /* @__PURE__ */ jsx("div", { className: "space-y-1", children: typographyCategories.map((category) => /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
3131
- /* @__PURE__ */ jsxs(
3132
- "button",
3133
- {
3134
- type: "button",
3135
- className: "flex w-full items-center rounded px-2 py-1.5 text-sm transition-colors hover:bg-muted",
3136
- onClick: () => toggleCategory(category.name),
3137
- children: [
3138
- /* @__PURE__ */ jsx(
3139
- ChevronDown,
3266
+ const renderTypographyView = () => {
3267
+ const updateTypography = (key, value) => applyStyle({
3268
+ ...theme.theme,
3269
+ typography: { ...theme.theme.typography, [key]: value }
3270
+ });
3271
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
3272
+ /* @__PURE__ */ jsx(
3273
+ PanelSelect2,
3274
+ {
3275
+ title: "Highlighted title words (*like this*)",
3276
+ value: theme.theme.highlight ?? "color",
3277
+ onChange: (highlight) => applyStyle({ ...theme.theme, highlight }),
3278
+ options: [
3279
+ { label: "Primary colour text", value: "color" },
3280
+ { label: "Marker in primary colour", value: "marker" }
3281
+ ]
3282
+ }
3283
+ ),
3284
+ typographyCategories.map((category) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
3285
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-muted-foreground", children: category.name }),
3286
+ category.items.map((typography) => {
3287
+ const key = typography;
3288
+ const value = theme.theme.typography[key] ?? (key === "display" ? theme.theme.typography.header1 : void 0) ?? (key === "code" ? DEFAULT_CODE_TYPOGRAPHY : void 0);
3289
+ if (!value) return null;
3290
+ return /* @__PURE__ */ jsx(
3291
+ AccordionSection,
3140
3292
  {
3141
- className: cn(
3142
- "mr-2 h-4 w-4 transition-transform",
3143
- collapsedCategories[category.name] ? "rotate-[-90deg]" : "rotate-0"
3293
+ title: typographyDisplayNames[typography] || capitalize(typography),
3294
+ children: /* @__PURE__ */ jsx(
3295
+ TypographySelector,
3296
+ {
3297
+ value,
3298
+ allowFluid: fluidTypography.has(typography),
3299
+ onChange: (value2) => updateTypography(key, value2)
3300
+ }
3144
3301
  )
3145
- }
3146
- ),
3147
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-muted-foreground", children: category.name })
3148
- ]
3149
- }
3150
- ),
3151
- !collapsedCategories[category.name] && /* @__PURE__ */ jsx("div", { children: category.items.map(
3152
- (typography) => renderDrillButton(
3153
- typographyDisplayNames[typography] || capitalize(typography),
3154
- () => navigateTo({
3155
- id: typography,
3156
- label: typographyDisplayNames[typography] || capitalize(typography)
3302
+ },
3303
+ typography
3304
+ );
3157
3305
  })
3158
- )
3159
- ) })
3160
- ] }, category.name)) });
3306
+ ] }, category.name))
3307
+ ] });
3308
+ };
3161
3309
  const renderInteractivesList = () => /* @__PURE__ */ jsx("div", { className: "space-y-1", children: Object.keys(interactiveDisplayNames).map(
3162
3310
  (interactive) => renderDrillButton(
3163
3311
  interactiveDisplayNames[interactive] || capitalize(interactive),
@@ -3182,22 +3330,6 @@ function ThemeEditor({
3182
3330
  if (!currentItem) {
3183
3331
  return renderRootView();
3184
3332
  }
3185
- if (parentItem?.id === "typography" && currentItem.id in theme.theme.typography) {
3186
- const key = currentItem.id;
3187
- return /* @__PURE__ */ jsx(
3188
- TypographySelector,
3189
- {
3190
- value: theme.theme.typography[key],
3191
- onChange: (value) => applyStyle({
3192
- ...theme.theme,
3193
- typography: {
3194
- ...theme.theme.typography,
3195
- [key]: value
3196
- }
3197
- })
3198
- }
3199
- );
3200
- }
3201
3333
  if (parentItem?.id === "interactives" && currentItem.id in theme.theme.interactives) {
3202
3334
  return /* @__PURE__ */ jsx(
3203
3335
  InteractiveSelector,
@@ -3224,7 +3356,7 @@ function ThemeEditor({
3224
3356
  case "colors":
3225
3357
  return renderColorsView();
3226
3358
  case "typography":
3227
- return renderTypographyList();
3359
+ return renderTypographyView();
3228
3360
  case "interactives":
3229
3361
  return renderInteractivesList();
3230
3362
  case "structure":
@@ -3246,7 +3378,35 @@ function ThemeEditor({
3246
3378
  children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
3247
3379
  }
3248
3380
  ),
3249
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-muted-foreground", children: navigationStack[navigationStack.length - 1].label })
3381
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-muted-foreground", children: navigationStack.length === 1 ? theme.name : navigationStack[navigationStack.length - 1].label })
3382
+ ] }),
3383
+ navigationStack.length === 1 && /* @__PURE__ */ jsxs("div", { className: "space-y-3 pb-2", children: [
3384
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3385
+ /* @__PURE__ */ jsx(PanelLabel, { htmlFor: "theme-name", children: "Theme name" }),
3386
+ /* @__PURE__ */ jsx(
3387
+ PanelInput2,
3388
+ {
3389
+ id: "theme-name",
3390
+ value: theme.name,
3391
+ onChange: (e) => commitTheme({ ...theme, name: e.target.value })
3392
+ }
3393
+ )
3394
+ ] }),
3395
+ theme.id === appliedThemeId ? /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
3396
+ /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
3397
+ "This is your site theme"
3398
+ ] }) : /* @__PURE__ */ jsxs(
3399
+ "button",
3400
+ {
3401
+ type: "button",
3402
+ className: "inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground hover:bg-primary/90",
3403
+ onClick: () => onApply(theme),
3404
+ children: [
3405
+ /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
3406
+ "Use this theme"
3407
+ ]
3408
+ }
3409
+ )
3250
3410
  ] }),
3251
3411
  /* @__PURE__ */ jsx("div", { className: "py-2", children: getCurrentView() })
3252
3412
  ] })
@@ -3257,7 +3417,7 @@ function SeoEditor({
3257
3417
  onChange
3258
3418
  }) {
3259
3419
  const update = (next) => onChange({ ...seo, ...next });
3260
- const imageUrl = seo.socialImage?.file;
3420
+ const imageUrl = getMediaUrl(seo.socialImage);
3261
3421
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
3262
3422
  /* @__PURE__ */ jsxs(FieldGroup, { title: "Search results", children: [
3263
3423
  /* @__PURE__ */ jsx(
@@ -3289,25 +3449,101 @@ function SeoEditor({
3289
3449
  )
3290
3450
  ] }),
3291
3451
  /* @__PURE__ */ jsxs(FieldGroup, { title: "Social card image", children: [
3452
+ imageUrl ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
3453
+ /* @__PURE__ */ jsx(
3454
+ "img",
3455
+ {
3456
+ src: imageUrl,
3457
+ alt: "Social card preview",
3458
+ className: "aspect-[1200/630] w-full rounded-md border border-border object-cover"
3459
+ }
3460
+ ),
3461
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3462
+ /* @__PURE__ */ jsx(
3463
+ ImageUploadDialog,
3464
+ {
3465
+ title: "Change social card image",
3466
+ description: "Upload an image or pick one from your library.",
3467
+ onSelect: (selected) => {
3468
+ const selMedia = selected.media;
3469
+ const mediaLitId = selMedia?.mediaLitId ?? selMedia?.mediaId ?? void 0;
3470
+ update({
3471
+ socialImage: {
3472
+ ...mediaLitId ? { mediaId: mediaLitId, mediaLitId } : {},
3473
+ url: selected.src,
3474
+ file: selected.src,
3475
+ thumbnailUrl: selMedia?.thumbnailUrl ?? selected.src,
3476
+ thumbnail: selMedia?.thumbnailUrl ?? selected.src,
3477
+ alt: selected.alt
3478
+ }
3479
+ });
3480
+ },
3481
+ children: /* @__PURE__ */ jsxs(
3482
+ "button",
3483
+ {
3484
+ type: "button",
3485
+ className: "inline-flex h-8 items-center justify-center rounded-lg border border-input bg-background px-3 text-xs font-medium text-foreground hover:bg-muted",
3486
+ children: [
3487
+ /* @__PURE__ */ jsx(ImageIcon, { className: "mr-1.5 h-3.5 w-3.5" }),
3488
+ "Change image"
3489
+ ]
3490
+ }
3491
+ )
3492
+ }
3493
+ ),
3494
+ /* @__PURE__ */ jsx(
3495
+ "button",
3496
+ {
3497
+ type: "button",
3498
+ className: "inline-flex h-8 items-center justify-center rounded-lg border border-destructive/30 bg-background px-3 text-xs font-medium text-destructive hover:bg-destructive/10",
3499
+ onClick: () => update({ socialImage: void 0 }),
3500
+ children: "Remove"
3501
+ }
3502
+ )
3503
+ ] })
3504
+ ] }) : /* @__PURE__ */ jsx(
3505
+ ImageUploadDialog,
3506
+ {
3507
+ title: "Choose social card image",
3508
+ description: "Upload an image or pick one from your library.",
3509
+ onSelect: (selected) => {
3510
+ const selMedia = selected.media;
3511
+ const mediaLitId = selMedia?.mediaLitId ?? selMedia?.mediaId ?? void 0;
3512
+ update({
3513
+ socialImage: {
3514
+ ...mediaLitId ? { mediaId: mediaLitId, mediaLitId } : {},
3515
+ url: selected.src,
3516
+ file: selected.src,
3517
+ thumbnailUrl: selMedia?.thumbnailUrl ?? selected.src,
3518
+ thumbnail: selMedia?.thumbnailUrl ?? selected.src,
3519
+ alt: selected.alt
3520
+ }
3521
+ });
3522
+ },
3523
+ children: /* @__PURE__ */ jsxs(
3524
+ "button",
3525
+ {
3526
+ type: "button",
3527
+ className: "flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-border bg-muted/20 py-3 text-xs font-medium text-muted-foreground transition-colors hover:border-primary/40 hover:bg-muted/40 hover:text-foreground",
3528
+ children: [
3529
+ /* @__PURE__ */ jsx(ImageIcon, { className: "h-4 w-4" }),
3530
+ "Upload or choose image"
3531
+ ]
3532
+ }
3533
+ )
3534
+ }
3535
+ ),
3292
3536
  /* @__PURE__ */ jsx(
3293
3537
  TextField,
3294
3538
  {
3295
- label: "Image URL",
3296
- value: imageUrl,
3539
+ label: "Or paste image URL",
3540
+ value: imageUrl ?? "",
3297
3541
  placeholder: "https://...",
3298
- onChange: (file) => update({
3299
- socialImage: file ? { mediaId: file, file } : void 0
3542
+ onChange: (url) => update({
3543
+ socialImage: url ? { url, file: url } : void 0
3300
3544
  })
3301
3545
  }
3302
3546
  ),
3303
- imageUrl && /* @__PURE__ */ jsx(
3304
- "img",
3305
- {
3306
- src: imageUrl,
3307
- alt: "Social card preview",
3308
- className: "aspect-[1200/630] w-full rounded-md border border-border object-cover"
3309
- }
3310
- ),
3311
3547
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Shown when the page is shared on social networks. Recommended size 1200\xD7630." })
3312
3548
  ] })
3313
3549
  ] });
@@ -3324,16 +3560,12 @@ function PageBuilder({
3324
3560
  className
3325
3561
  }) {
3326
3562
  const [layout, setLayout] = useState(
3327
- () => initialLayout ? ensureRequiredSharedWidgets(initialLayout) : createDefaultLayout()
3328
- );
3329
- const [theme, setTheme] = useState(
3330
- () => initialTheme ?? getDefaultTheme()
3563
+ () => initialLayout ? ensureRequiredSharedWidgets(withDefaultSettings(initialLayout)) : createDefaultLayout()
3331
3564
  );
3565
+ const [theme, setTheme] = useState(() => initialTheme ?? getDefaultTheme());
3332
3566
  const [previewTheme, setPreviewTheme] = useState(null);
3333
3567
  const paintedTheme = previewTheme ?? theme;
3334
- const [seo, setSeo] = useState(
3335
- () => initialSeo ?? { robotsAllowed: true }
3336
- );
3568
+ const [seo, setSeo] = useState(() => initialSeo ?? { robotsAllowed: true });
3337
3569
  const systemThemes = providedThemes?.system ?? themes;
3338
3570
  const [customThemes, setCustomThemes] = useState(
3339
3571
  () => providedThemes?.custom ?? []
@@ -3343,6 +3575,7 @@ function PageBuilder({
3343
3575
  const [device, setDevice] = useState("desktop");
3344
3576
  const [panel, setPanel] = useState(null);
3345
3577
  const [insertAfter, setInsertAfter] = useState(null);
3578
+ const [pendingDeleteId, setPendingDeleteId] = useState(null);
3346
3579
  const [movedWidgetId, setMovedWidgetId] = useState(null);
3347
3580
  const movedTimeoutRef = useRef(null);
3348
3581
  useEffect(() => {
@@ -3361,15 +3594,13 @@ function PageBuilder({
3361
3594
  const onChangeRef = useRef(onChange);
3362
3595
  onChangeRef.current = onChange;
3363
3596
  const emitChange = useMemo(
3364
- () => debounce(
3365
- (state) => onChangeRef.current?.(state),
3366
- 500
3367
- ),
3597
+ () => debounce((state) => onChangeRef.current?.(state), 500),
3368
3598
  []
3369
3599
  );
3370
3600
  useEffect(() => {
3371
3601
  emitChange({ layout, theme, seo, customThemes });
3372
3602
  }, [layout, theme, seo, customThemes]);
3603
+ useEffect(() => emitChange.flush, [emitChange]);
3373
3604
  const addableBlocks = useMemo(() => getAddableBlocks(), []);
3374
3605
  function updateWidgetSettings(widgetId, settings) {
3375
3606
  setLayout(
@@ -3392,12 +3623,12 @@ function PageBuilder({
3392
3623
  [next[index], next[targetIndex]] = [next[targetIndex], next[index]];
3393
3624
  return next;
3394
3625
  });
3626
+ highlightWidget(widgetId);
3627
+ }
3628
+ function highlightWidget(widgetId) {
3395
3629
  if (movedTimeoutRef.current) clearTimeout(movedTimeoutRef.current);
3396
3630
  setMovedWidgetId(widgetId);
3397
- movedTimeoutRef.current = setTimeout(
3398
- () => setMovedWidgetId(null),
3399
- 1500
3400
- );
3631
+ movedTimeoutRef.current = setTimeout(() => setMovedWidgetId(null), 1500);
3401
3632
  }
3402
3633
  function deleteWidget(widgetId) {
3403
3634
  setLayout((prev) => {
@@ -3417,16 +3648,14 @@ function PageBuilder({
3417
3648
  if (insertAfter === "end" || insertAfter === null) {
3418
3649
  const footerIndex = prev.findIndex((w) => w.name === "footer");
3419
3650
  if (footerIndex === -1) return [...prev, instance];
3420
- return [
3421
- ...prev.slice(0, footerIndex),
3422
- instance,
3423
- ...prev.slice(footerIndex)
3424
- ];
3651
+ return [...prev.slice(0, footerIndex), instance, ...prev.slice(footerIndex)];
3652
+ }
3653
+ const afterIndex = prev.findIndex((w) => w.widgetId === insertAfter);
3654
+ if (afterIndex === -1 || prev[afterIndex].name === "footer") {
3655
+ const footerIndex = prev.findIndex((w) => w.name === "footer");
3656
+ if (footerIndex === -1) return [...prev, instance];
3657
+ return [...prev.slice(0, footerIndex), instance, ...prev.slice(footerIndex)];
3425
3658
  }
3426
- const afterIndex = prev.findIndex(
3427
- (w) => w.widgetId === insertAfter
3428
- );
3429
- if (afterIndex === -1) return [...prev, instance];
3430
3659
  return [
3431
3660
  ...prev.slice(0, afterIndex + 1),
3432
3661
  instance,
@@ -3434,12 +3663,11 @@ function PageBuilder({
3434
3663
  ];
3435
3664
  });
3436
3665
  setInsertAfter(null);
3666
+ highlightWidget(instance.widgetId);
3437
3667
  setPanel({ mode: "block", widgetId: instance.widgetId });
3438
3668
  }
3439
3669
  const selectedWidgetId = panel?.mode === "block" ? panel.widgetId : null;
3440
- const selectedInstance = layout.find(
3441
- (w) => w.widgetId === selectedWidgetId
3442
- );
3670
+ const selectedInstance = layout.find((w) => w.widgetId === selectedWidgetId);
3443
3671
  const selectedDefinition = selectedInstance ? getWidget(selectedInstance.name) : void 0;
3444
3672
  const showBlockPanel = Boolean(
3445
3673
  panel?.mode === "block" && selectedInstance && selectedDefinition?.editor
@@ -3472,7 +3700,7 @@ function PageBuilder({
3472
3700
  onDeviceChange: setDevice,
3473
3701
  onOpenTheme: () => setPanel({ mode: "theme" }),
3474
3702
  onOpenSeo: () => setPanel({ mode: "seo" }),
3475
- onAddBlock: () => setInsertAfter("end"),
3703
+ onAddBlock: () => setInsertAfter(selectedWidgetId ?? "end"),
3476
3704
  allowThemeEditing
3477
3705
  }
3478
3706
  ),
@@ -3493,8 +3721,9 @@ function PageBuilder({
3493
3721
  {
3494
3722
  className: cn(
3495
3723
  "h-full w-full max-w-full transition-[max-width] duration-300",
3496
- device === "mobile" && "max-w-[390px] border-x",
3497
- device === "tablet" && "max-w-[768px] border-x"
3724
+ // Device width plus the 2px border and the measured scrollbar.
3725
+ device === "mobile" && "max-w-[calc(392px_+_var(--pb-scrollbar,0px))] border-x",
3726
+ device === "tablet" && "max-w-[calc(770px_+_var(--pb-scrollbar,0px))] border-x"
3498
3727
  ),
3499
3728
  children: /* @__PURE__ */ jsx(
3500
3729
  PageRenderer,
@@ -3503,7 +3732,6 @@ function PageBuilder({
3503
3732
  theme: paintedTheme,
3504
3733
  pageData,
3505
3734
  nextTheme: resolvedMode,
3506
- themeMode: mode,
3507
3735
  onThemeModeChange: setMode,
3508
3736
  editing: true,
3509
3737
  selectedWidgetId: selectedWidgetId ?? void 0,
@@ -3511,7 +3739,7 @@ function PageBuilder({
3511
3739
  onSelectWidget: (id) => setPanel({ mode: "block", widgetId: id }),
3512
3740
  onMoveWidgetUp: (id) => moveWidget(id, -1),
3513
3741
  onMoveWidgetDown: (id) => moveWidget(id, 1),
3514
- onDeleteWidget: deleteWidget,
3742
+ onDeleteWidget: setPendingDeleteId,
3515
3743
  onAddWidgetBelow: (id) => setInsertAfter(id),
3516
3744
  linkComponent
3517
3745
  }
@@ -3534,10 +3762,7 @@ function PageBuilder({
3534
3762
  instance: selectedInstance,
3535
3763
  definition: selectedDefinition,
3536
3764
  theme,
3537
- onChange: (settings) => updateWidgetSettings(
3538
- selectedInstance.widgetId,
3539
- settings
3540
- )
3765
+ onChange: (settings) => updateWidgetSettings(selectedInstance.widgetId, settings)
3541
3766
  },
3542
3767
  selectedInstance.widgetId
3543
3768
  )
@@ -3562,6 +3787,19 @@ function PageBuilder({
3562
3787
  ) }),
3563
3788
  showSeoPanel && /* @__PURE__ */ jsx(SettingsPanel, { title: "SEO", onClose: closePanel, children: /* @__PURE__ */ jsx(SeoEditor, { seo, onChange: setSeo }) })
3564
3789
  ] }),
3790
+ pendingDeleteId !== null && /* @__PURE__ */ jsx(
3791
+ ConfirmDialog,
3792
+ {
3793
+ title: `Delete ${getWidget(layout.find((w) => w.widgetId === pendingDeleteId)?.name ?? "")?.metadata.displayName ?? "block"}?`,
3794
+ description: "This block and its content will be removed from the page draft.",
3795
+ confirmLabel: "Delete",
3796
+ onConfirm: () => {
3797
+ deleteWidget(pendingDeleteId);
3798
+ setPendingDeleteId(null);
3799
+ },
3800
+ onClose: () => setPendingDeleteId(null)
3801
+ }
3802
+ ),
3565
3803
  insertAfter !== null && /* @__PURE__ */ jsx(
3566
3804
  BlockPicker,
3567
3805
  {
@@ -3575,6 +3813,6 @@ function PageBuilder({
3575
3813
  );
3576
3814
  }
3577
3815
 
3578
- export { BlockPicker, BuilderTopBar, ColorSelector, ContentPaddingSelector, MaxWidthSelector, PATTERN_PRESETS, PageBuilder, PageBuilderPropertyHeader, PageBuilderSlider, SectionBackgroundPanel, SeoEditor, SettingsPanel, ThemeEditor, VerticalPaddingSelector, createDefaultLayout, createWidgetInstance, ensureRequiredSharedWidgets, getDefaultTheme, getMaxWidthTWClass, getVerticalPaddingTWClass };
3579
- //# sourceMappingURL=chunk-MO76KZY2.js.map
3580
- //# sourceMappingURL=chunk-MO76KZY2.js.map
3816
+ export { BlockPicker, BuilderTopBar, ColorSelector, ContentPaddingSelector, MaxWidthSelector, PATTERN_PRESETS, PageBuilder, PageBuilderPropertyHeader, PageBuilderSlider, SectionBackgroundPanel, SeoEditor, SettingsPanel, ThemeEditor, VerticalPaddingSelector, createDefaultLayout, createWidgetInstance, ensureRequiredSharedWidgets, getDefaultTheme, getMaxWidthTWClass, getVerticalPaddingTWClass, withDefaultSettings };
3817
+ //# sourceMappingURL=chunk-GIKTOAQS.js.map
3818
+ //# sourceMappingURL=chunk-GIKTOAQS.js.map