@orion-studios/payload-studio 0.5.0-beta.8 → 0.5.0-beta.80

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 (67) hide show
  1. package/README.md +20 -0
  2. package/dist/admin/client.d.mts +2 -0
  3. package/dist/admin/client.d.ts +2 -0
  4. package/dist/admin/client.js +779 -137
  5. package/dist/admin/client.mjs +769 -129
  6. package/dist/admin/index.d.mts +1 -1
  7. package/dist/admin/index.d.ts +1 -1
  8. package/dist/admin/index.js +100 -8
  9. package/dist/admin/index.mjs +3 -1
  10. package/dist/admin-app/client.d.mts +7 -0
  11. package/dist/admin-app/client.d.ts +7 -0
  12. package/dist/admin-app/client.js +1262 -3
  13. package/dist/admin-app/client.mjs +1164 -2
  14. package/dist/admin-app/index.d.mts +1 -1
  15. package/dist/admin-app/index.d.ts +1 -1
  16. package/dist/admin-app/index.js +167 -0
  17. package/dist/admin-app/index.mjs +13 -1
  18. package/dist/admin-app/styles.css +229 -0
  19. package/dist/blocks/index.js +633 -8
  20. package/dist/blocks/index.mjs +2 -2
  21. package/dist/chunk-ADIIWIYL.mjs +322 -0
  22. package/dist/{chunk-ZLLNO5FM.mjs → chunk-AFLEATYB.mjs} +44 -13
  23. package/dist/chunk-FWVVRZ32.mjs +1143 -0
  24. package/dist/{chunk-J7W5EE3B.mjs → chunk-HCEPGEAI.mjs} +100 -8
  25. package/dist/chunk-ROTPP5CU.mjs +99 -0
  26. package/dist/{chunk-ETRRXURT.mjs → chunk-SIL2J5MF.mjs} +14 -0
  27. package/dist/{chunk-PC5622T7.mjs → chunk-XK3K5GRP.mjs} +620 -9
  28. package/dist/chunk-XVH5SCBD.mjs +234 -0
  29. package/dist/index-BBp-3l6M.d.ts +134 -0
  30. package/dist/{index-CmR6NInu.d.ts → index-BIwu3qIH.d.mts} +29 -3
  31. package/dist/{index-CmR6NInu.d.mts → index-BIwu3qIH.d.ts} +29 -3
  32. package/dist/{index-DbH0Ljwp.d.ts → index-CpG3UHcS.d.mts} +1 -0
  33. package/dist/{index-DbH0Ljwp.d.mts → index-CpG3UHcS.d.ts} +1 -0
  34. package/dist/index-DT_Vorvh.d.mts +134 -0
  35. package/dist/index-ZbOx4OCF.d.mts +128 -0
  36. package/dist/index-ZbOx4OCF.d.ts +128 -0
  37. package/dist/{index-DJFhANvJ.d.mts → index-cDYkEj29.d.mts} +20 -2
  38. package/dist/{index-DJFhANvJ.d.ts → index-cDYkEj29.d.ts} +20 -2
  39. package/dist/index.d.mts +5 -5
  40. package/dist/index.d.ts +5 -5
  41. package/dist/index.js +2232 -324
  42. package/dist/index.mjs +7 -7
  43. package/dist/nextjs/index.d.mts +1 -1
  44. package/dist/nextjs/index.d.ts +1 -1
  45. package/dist/nextjs/index.js +1002 -13
  46. package/dist/nextjs/index.mjs +4 -1
  47. package/dist/studio/index.d.mts +2 -1
  48. package/dist/studio/index.d.ts +2 -1
  49. package/dist/studio/index.js +171 -5
  50. package/dist/studio/index.mjs +7 -3
  51. package/dist/studio-pages/builder.css +472 -8
  52. package/dist/studio-pages/client.d.mts +17 -0
  53. package/dist/studio-pages/client.d.ts +17 -0
  54. package/dist/studio-pages/client.js +5057 -2546
  55. package/dist/studio-pages/client.mjs +4972 -2552
  56. package/dist/studio-pages/index.d.mts +3 -2
  57. package/dist/studio-pages/index.d.ts +3 -2
  58. package/dist/studio-pages/index.js +873 -32
  59. package/dist/studio-pages/index.mjs +6 -2
  60. package/package.json +26 -12
  61. package/dist/chunk-AAOHJDNS.mjs +0 -67
  62. package/dist/chunk-N67KVM2S.mjs +0 -156
  63. package/dist/chunk-TVDMZNKU.mjs +0 -301
  64. package/dist/index-B9N5MyjF.d.mts +0 -39
  65. package/dist/index-BallJs-K.d.mts +0 -43
  66. package/dist/index-BallJs-K.d.ts +0 -43
  67. package/dist/index-g8tBHLKD.d.ts +0 -39
package/dist/index.js CHANGED
@@ -43,6 +43,7 @@ module.exports = __toCommonJS(index_exports);
43
43
  var admin_exports = {};
44
44
  __export(admin_exports, {
45
45
  configureAdmin: () => configureAdmin,
46
+ createHeaderNavItemsField: () => createHeaderNavItemsField,
46
47
  createThemePreferenceField: () => createThemePreferenceField,
47
48
  themePreferenceField: () => themePreferenceField,
48
49
  withTooltips: () => withTooltips
@@ -98,14 +99,25 @@ function configureAdmin(config) {
98
99
  defaultTheme = "brand-light",
99
100
  logoUrl
100
101
  } = config;
101
- const studioEnabled = Boolean(config.studio?.enabled);
102
+ const studioEnabled = config.studio?.enabled ?? true;
102
103
  const pagesCollectionSlug = config.studio?.pages?.collectionSlug || "pages";
103
104
  const mediaCollectionSlug = config.studio?.media?.collectionSlug || "media";
104
- const globals = config.studio?.globals || [
105
+ const contactFormStudioPath = "/studio-contact-form";
106
+ const configuredGlobals = config.studio?.globals || [
105
107
  { slug: "site-settings", label: "Website Settings" },
106
108
  { slug: "header", label: "Header & Navigation" },
107
- { slug: "footer", label: "Footer" }
109
+ { slug: "footer", label: "Footer" },
110
+ { slug: "contact-form", label: "Contact Form" }
108
111
  ];
112
+ const globals = configuredGlobals.map((global) => {
113
+ if (global.slug !== "contact-form" || global.href) {
114
+ return global;
115
+ }
116
+ return {
117
+ ...global,
118
+ href: contactFormStudioPath
119
+ };
120
+ });
109
121
  let cssPath;
110
122
  const pkgDist = getPkgDistDir();
111
123
  const sourceCssPath = import_path.default.resolve(pkgDist, "admin.css");
@@ -135,7 +147,8 @@ function configureAdmin(config) {
135
147
  clientProps: {
136
148
  brandName,
137
149
  logoUrl,
138
- globalsBasePath: "/admin/studio-globals",
150
+ globalsBasePath: "/studio-globals",
151
+ globalsExtraMatchPrefixes: [contactFormStudioPath],
139
152
  mediaCollectionSlug,
140
153
  pagesCollectionSlug
141
154
  }
@@ -167,7 +180,8 @@ function configureAdmin(config) {
167
180
  clientProps: {
168
181
  brandName,
169
182
  logoUrl,
170
- globalsBasePath: "/admin/studio-globals",
183
+ globalsBasePath: "/studio-globals",
184
+ globalsExtraMatchPrefixes: [contactFormStudioPath],
171
185
  mediaCollectionSlug,
172
186
  pagesCollectionSlug
173
187
  }
@@ -181,7 +195,18 @@ function configureAdmin(config) {
181
195
  path: clientPath,
182
196
  clientProps: {
183
197
  globals,
184
- globalsBasePath: "/admin/studio-globals"
198
+ globalsBasePath: "/studio-globals"
199
+ }
200
+ }
201
+ },
202
+ studioContactForm: {
203
+ path: "/studio-contact-form",
204
+ Component: {
205
+ exportName: "AdminStudioContactFormView",
206
+ path: clientPath,
207
+ clientProps: {
208
+ globalSlug: "contact-form",
209
+ globalsBasePath: "/studio-globals"
185
210
  }
186
211
  }
187
212
  }
@@ -228,16 +253,47 @@ function configureAdmin(config) {
228
253
  const labelMap = {
229
254
  header: { group: "Site Design", label: "Header & Navigation" },
230
255
  footer: { group: "Site Design", label: "Footer" },
231
- "site-settings": { group: "Site Design", label: "Website Settings" }
256
+ "site-settings": { group: "Site Design", label: "Website Settings" },
257
+ "contact-form": { group: "Lead Forms", label: "Contact Form" }
232
258
  };
233
259
  return globals2.map((global) => {
234
260
  const mapping = labelMap[global.slug];
235
261
  if (!mapping) return global;
262
+ const shouldAttachContactFormRedirect = studioEnabled && global.slug === "contact-form";
263
+ const existingViews = global.admin?.components?.views;
264
+ const existingEditViews = existingViews?.edit;
265
+ const hasCustomContactFormEditView = Boolean(
266
+ existingEditViews?.root || existingEditViews?.default && typeof existingEditViews.default === "object" && existingEditViews.default.Component
267
+ );
268
+ const contactFormEditViews = shouldAttachContactFormRedirect && !hasCustomContactFormEditView ? {
269
+ ...existingEditViews,
270
+ default: {
271
+ ...typeof existingEditViews?.default === "object" ? existingEditViews.default : {},
272
+ Component: {
273
+ exportName: "StudioContactFormRedirect",
274
+ path: clientPath,
275
+ clientProps: {
276
+ studioContactFormPath: contactFormStudioPath
277
+ }
278
+ }
279
+ }
280
+ } : existingEditViews;
236
281
  return {
237
282
  ...global,
238
283
  admin: {
239
284
  ...global.admin,
240
- group: mapping.group
285
+ group: mapping.group,
286
+ components: {
287
+ ...global.admin?.components,
288
+ ...shouldAttachContactFormRedirect ? {
289
+ views: {
290
+ ...existingViews,
291
+ ...contactFormEditViews ? {
292
+ edit: contactFormEditViews
293
+ } : {}
294
+ }
295
+ } : {}
296
+ }
241
297
  },
242
298
  label: mapping.label
243
299
  };
@@ -306,12 +362,53 @@ function addTooltipToField(field, tooltips) {
306
362
  return field;
307
363
  }
308
364
 
365
+ // src/admin/fields/headerNav.ts
366
+ var createHeaderNavItemsField = () => ({
367
+ name: "navItems",
368
+ type: "array",
369
+ labels: { singular: "Navigation Link", plural: "Navigation Links" },
370
+ admin: {
371
+ description: "The links displayed in your website's main navigation menu."
372
+ },
373
+ fields: [
374
+ {
375
+ name: "label",
376
+ type: "text",
377
+ required: true,
378
+ admin: {
379
+ description: "The text shown for this navigation link."
380
+ }
381
+ },
382
+ {
383
+ name: "href",
384
+ type: "text",
385
+ required: true,
386
+ admin: {
387
+ description: 'The URL this link points to (e.g., "/about" or "https://example.com").'
388
+ }
389
+ },
390
+ {
391
+ name: "parentHref",
392
+ type: "text",
393
+ admin: {
394
+ description: "Optional parent link URL. If set to another nav item href, this item appears in that dropdown."
395
+ }
396
+ }
397
+ ]
398
+ });
399
+
309
400
  // src/admin-app/index.ts
310
401
  var admin_app_exports = {};
311
402
  __export(admin_app_exports, {
312
403
  AdminBreadcrumbs: () => AdminBreadcrumbs,
313
404
  AdminPage: () => AdminPage,
405
+ buildAdminPageLinkOptions: () => buildAdminPageLinkOptions,
406
+ buildNestedNavTree: () => buildNestedNavTree,
407
+ getAdminNavRows: () => getAdminNavRows,
314
408
  navItemIsActive: () => navItemIsActive,
409
+ normalizeAdminNavInputs: () => normalizeAdminNavInputs,
410
+ normalizeNestedNavItems: () => normalizeNestedNavItems,
411
+ parseAdminHeaderNavFromForm: () => parseAdminHeaderNavFromForm,
315
412
  roleCanAccessNav: () => roleCanAccessNav
316
413
  });
317
414
 
@@ -345,6 +442,161 @@ function AdminPage({ title, description, breadcrumbs, actions, children }) {
345
442
  ] });
346
443
  }
347
444
 
445
+ // src/admin-app/nestedNavigation.ts
446
+ var normalizeNestedNavItems = (items) => {
447
+ const deduped = [];
448
+ const seen = /* @__PURE__ */ new Set();
449
+ for (const item of items) {
450
+ const href = typeof item.href === "string" ? item.href.trim() : "";
451
+ const label = typeof item.label === "string" ? item.label.trim() : "";
452
+ const parentHref = typeof item.parentHref === "string" ? item.parentHref.trim() : "";
453
+ if (!href || !label || seen.has(href)) {
454
+ continue;
455
+ }
456
+ seen.add(href);
457
+ deduped.push({
458
+ href,
459
+ label,
460
+ ...parentHref ? { parentHref } : {}
461
+ });
462
+ }
463
+ const hrefs = new Set(deduped.map((item) => item.href));
464
+ return deduped.map((item) => ({
465
+ href: item.href,
466
+ label: item.label,
467
+ ...item.parentHref && item.parentHref !== item.href && hrefs.has(item.parentHref) ? { parentHref: item.parentHref } : {}
468
+ }));
469
+ };
470
+ var buildNestedNavTree = (items) => {
471
+ const childrenByParent = /* @__PURE__ */ new Map();
472
+ const topLevel = [];
473
+ for (const item of items) {
474
+ if (!item.parentHref) {
475
+ topLevel.push(item);
476
+ continue;
477
+ }
478
+ const children = childrenByParent.get(item.parentHref) || [];
479
+ children.push(item);
480
+ childrenByParent.set(item.parentHref, children);
481
+ }
482
+ if (topLevel.length === 0 && items.length > 0) {
483
+ return {
484
+ topLevel: items.map((item) => ({ href: item.href, label: item.label })),
485
+ childrenByParent: /* @__PURE__ */ new Map()
486
+ };
487
+ }
488
+ return { childrenByParent, topLevel };
489
+ };
490
+
491
+ // src/admin-app/navigationLinks.ts
492
+ var fallbackHomeOption = {
493
+ href: "/",
494
+ label: "Home",
495
+ title: "Home"
496
+ };
497
+ var buildAdminPageLinkOptions = (pages) => {
498
+ const options = pages.map((page) => {
499
+ const href = typeof page.path === "string" ? page.path.trim() : "";
500
+ const title = typeof page.title === "string" && page.title.trim().length > 0 ? page.title.trim() : "Untitled Page";
501
+ if (!href) {
502
+ return null;
503
+ }
504
+ const depth = href === "/" ? 0 : href.split("/").filter(Boolean).length;
505
+ const indentLevel = Math.max(depth - 1, 0);
506
+ const indent = indentLevel > 0 ? `${"\u21B3 ".repeat(indentLevel)}` : "";
507
+ return {
508
+ href,
509
+ label: `${indent}${title}`,
510
+ title
511
+ };
512
+ }).filter((option) => option !== null).sort((a, b) => {
513
+ if (a.href === "/" && b.href !== "/") return -1;
514
+ if (b.href === "/" && a.href !== "/") return 1;
515
+ return a.href.localeCompare(b.href);
516
+ });
517
+ if (options.length === 0) {
518
+ return [fallbackHomeOption];
519
+ }
520
+ return options;
521
+ };
522
+ var getAdminNavRows = (navItems, pageOptions, minRows = 6, maxRows = 20) => {
523
+ const preferredRows = Math.max(navItems.length + 2, Math.min(8, pageOptions.length));
524
+ return Math.min(Math.max(minRows, preferredRows), maxRows);
525
+ };
526
+ var normalizeAdminNavInputs = (rows, pageOptions) => {
527
+ const allowedLinks = new Map(pageOptions.map((option) => [option.href, option.title]));
528
+ const deduped = [];
529
+ const seen = /* @__PURE__ */ new Set();
530
+ for (const row of rows) {
531
+ const href = typeof row.href === "string" ? row.href.trim() : "";
532
+ const defaultLabel = allowedLinks.get(href);
533
+ const explicitLabel = typeof row.label === "string" ? row.label.trim() : "";
534
+ const parentHref = typeof row.parentHref === "string" ? row.parentHref.trim() : "";
535
+ if (!href || !defaultLabel || seen.has(href)) {
536
+ continue;
537
+ }
538
+ seen.add(href);
539
+ deduped.push({
540
+ href,
541
+ label: explicitLabel.length > 0 ? explicitLabel : defaultLabel,
542
+ ...parentHref.length > 0 ? { parentHref } : {}
543
+ });
544
+ }
545
+ const hrefs = new Set(deduped.map((item) => item.href));
546
+ const parentByHref = new Map(deduped.map((item) => [item.href, item.parentHref || ""]));
547
+ const createsCycle = (href, parentHref) => {
548
+ const visited = /* @__PURE__ */ new Set([href]);
549
+ let current = parentHref;
550
+ while (current) {
551
+ if (visited.has(current)) {
552
+ return true;
553
+ }
554
+ visited.add(current);
555
+ current = parentByHref.get(current) || "";
556
+ }
557
+ return false;
558
+ };
559
+ return deduped.map((item) => {
560
+ const parentHref = item.parentHref;
561
+ if (!parentHref || parentHref === item.href || !hrefs.has(parentHref) || createsCycle(item.href, parentHref)) {
562
+ return {
563
+ href: item.href,
564
+ label: item.label
565
+ };
566
+ }
567
+ return item;
568
+ });
569
+ };
570
+ var parseAdminHeaderNavFromForm = (formData, pageOptions, maxRows = 24) => {
571
+ const serialized = String(formData.get("navItemsState") || "").trim();
572
+ if (serialized.length > 0) {
573
+ try {
574
+ const parsed = JSON.parse(serialized);
575
+ if (Array.isArray(parsed)) {
576
+ return normalizeAdminNavInputs(parsed.slice(0, maxRows), pageOptions);
577
+ }
578
+ } catch {
579
+ }
580
+ }
581
+ const rawCount = Number(String(formData.get("navCount") || "0"));
582
+ const navCount = Number.isFinite(rawCount) ? Math.max(0, Math.min(rawCount, maxRows)) : 0;
583
+ const navRows = [];
584
+ for (let index = 0; index < navCount; index += 1) {
585
+ const href = String(formData.get(`navPage_${index}`) || "").trim();
586
+ const label = String(formData.get(`navLabel_${index}`) || "").trim();
587
+ const parentHref = String(formData.get(`navParentHref_${index}`) || "").trim();
588
+ navRows.push({
589
+ href,
590
+ label,
591
+ parentHref
592
+ });
593
+ }
594
+ if (navRows.length > 0) {
595
+ return normalizeAdminNavInputs(navRows, pageOptions);
596
+ }
597
+ return [];
598
+ };
599
+
348
600
  // src/admin-app/routeRegistry.ts
349
601
  var roleCanAccessNav = (role, item) => {
350
602
  if (!item.roles || item.roles.length === 0) {
@@ -400,6 +652,7 @@ var sectionStyleDefaults = {
400
652
  contentGradientPreset: "none",
401
653
  contentGradientTo: "#f4f6f2",
402
654
  contentWidth: "inherit",
655
+ sectionPaddingX: "inherit",
403
656
  sectionBackgroundColor: "#ffffff",
404
657
  sectionBackgroundMode: "none",
405
658
  sectionGradientAngle: "135",
@@ -437,6 +690,19 @@ var sectionStyleFields = () => [
437
690
  type: "select",
438
691
  defaultValue: sectionStyleDefaults.sectionPaddingY,
439
692
  options: [
693
+ { label: "None", value: "none" },
694
+ { label: "Small", value: "sm" },
695
+ { label: "Medium", value: "md" },
696
+ { label: "Large", value: "lg" }
697
+ ]
698
+ },
699
+ {
700
+ name: "sectionPaddingX",
701
+ type: "select",
702
+ defaultValue: sectionStyleDefaults.sectionPaddingX,
703
+ options: [
704
+ { label: "Inherit", value: "inherit" },
705
+ { label: "None", value: "none" },
440
706
  { label: "Small", value: "sm" },
441
707
  { label: "Medium", value: "md" },
442
708
  { label: "Large", value: "lg" }
@@ -552,11 +818,22 @@ var BeforeAfterBlock = {
552
818
  name: "subtitle",
553
819
  type: "textarea"
554
820
  },
821
+ {
822
+ name: "itemsPerRow",
823
+ type: "number",
824
+ defaultValue: 2,
825
+ min: 1,
826
+ max: 4,
827
+ admin: {
828
+ description: "How many project cards to show per row on desktop.",
829
+ step: 1
830
+ }
831
+ },
555
832
  {
556
833
  name: "items",
557
834
  type: "array",
558
835
  minRows: 1,
559
- maxRows: 6,
836
+ maxRows: 30,
560
837
  fields: [
561
838
  {
562
839
  name: "label",
@@ -575,12 +852,88 @@ var BeforeAfterBlock = {
575
852
  relationTo: "media",
576
853
  required: false
577
854
  },
855
+ {
856
+ name: "imageHeight",
857
+ type: "number",
858
+ defaultValue: 160,
859
+ min: 60,
860
+ max: 600,
861
+ admin: {
862
+ description: "Overrides the before/after image height (in pixels).",
863
+ step: 10
864
+ }
865
+ },
866
+ {
867
+ name: "imageFit",
868
+ type: "select",
869
+ defaultValue: "cover",
870
+ options: [
871
+ { label: "Cover", value: "cover" },
872
+ { label: "Contain", value: "contain" }
873
+ ]
874
+ },
875
+ {
876
+ name: "imageCornerStyle",
877
+ type: "select",
878
+ defaultValue: "rounded",
879
+ options: [
880
+ { label: "Rounded", value: "rounded" },
881
+ { label: "Square", value: "square" }
882
+ ]
883
+ },
884
+ {
885
+ name: "imagePosition",
886
+ type: "select",
887
+ defaultValue: "center",
888
+ options: [
889
+ { label: "Center", value: "center" },
890
+ { label: "Top", value: "top" },
891
+ { label: "Bottom", value: "bottom" },
892
+ { label: "Left", value: "left" },
893
+ { label: "Right", value: "right" }
894
+ ]
895
+ },
896
+ {
897
+ name: "imagePositionX",
898
+ type: "number",
899
+ min: 0,
900
+ max: 100,
901
+ admin: {
902
+ description: "Optional custom horizontal focus (0-100). Overrides Image Position when set.",
903
+ step: 1
904
+ }
905
+ },
906
+ {
907
+ name: "imagePositionY",
908
+ type: "number",
909
+ min: 0,
910
+ max: 100,
911
+ admin: {
912
+ description: "Optional custom vertical focus (0-100). Overrides Image Position when set.",
913
+ step: 1
914
+ }
915
+ },
578
916
  {
579
917
  name: "description",
580
918
  type: "textarea"
919
+ },
920
+ {
921
+ name: "settings",
922
+ type: "json",
923
+ admin: {
924
+ hidden: true
925
+ }
581
926
  }
582
927
  ]
583
928
  },
929
+ {
930
+ name: "settings",
931
+ type: "json",
932
+ admin: {
933
+ description: "Internal builder settings schema v2.",
934
+ hidden: true
935
+ }
936
+ },
584
937
  ...sectionStyleFields()
585
938
  ]
586
939
  };
@@ -618,6 +971,14 @@ var BookingEmbedBlock = {
618
971
  type: "text",
619
972
  defaultValue: "/contact"
620
973
  },
974
+ {
975
+ name: "settings",
976
+ type: "json",
977
+ admin: {
978
+ description: "Internal builder settings schema v2.",
979
+ hidden: true
980
+ }
981
+ },
621
982
  ...sectionStyleFields()
622
983
  ]
623
984
  };
@@ -679,6 +1040,14 @@ var CtaBlock = {
679
1040
  description: "Optional background color override for the CTA strip (example: #124a37)."
680
1041
  }
681
1042
  },
1043
+ {
1044
+ name: "settings",
1045
+ type: "json",
1046
+ admin: {
1047
+ description: "Internal builder settings schema v2.",
1048
+ hidden: true
1049
+ }
1050
+ },
682
1051
  ...sectionStyleFields()
683
1052
  ]
684
1053
  };
@@ -718,9 +1087,24 @@ var FaqBlock = {
718
1087
  name: "answer",
719
1088
  type: "textarea",
720
1089
  required: true
1090
+ },
1091
+ {
1092
+ name: "settings",
1093
+ type: "json",
1094
+ admin: {
1095
+ hidden: true
1096
+ }
721
1097
  }
722
1098
  ]
723
1099
  },
1100
+ {
1101
+ name: "settings",
1102
+ type: "json",
1103
+ admin: {
1104
+ description: "Internal builder settings schema v2.",
1105
+ hidden: true
1106
+ }
1107
+ },
724
1108
  ...sectionStyleFields()
725
1109
  ]
726
1110
  };
@@ -745,6 +1129,17 @@ var FeatureGridBlock = {
745
1129
  type: "text",
746
1130
  required: true
747
1131
  },
1132
+ {
1133
+ name: "itemsPerRow",
1134
+ type: "number",
1135
+ defaultValue: 3,
1136
+ min: 1,
1137
+ max: 6,
1138
+ admin: {
1139
+ description: "How many items to show per row on desktop.",
1140
+ step: 1
1141
+ }
1142
+ },
748
1143
  {
749
1144
  name: "items",
750
1145
  type: "array",
@@ -760,11 +1155,43 @@ var FeatureGridBlock = {
760
1155
  name: "description",
761
1156
  type: "textarea"
762
1157
  },
1158
+ {
1159
+ name: "iconType",
1160
+ type: "select",
1161
+ defaultValue: "badge",
1162
+ options: [
1163
+ { label: "Badge Text", value: "badge" },
1164
+ { label: "Icon", value: "lucide" }
1165
+ ],
1166
+ admin: {
1167
+ description: "Choose whether this item uses a short badge label or an icon."
1168
+ }
1169
+ },
763
1170
  {
764
1171
  name: "icon",
765
1172
  type: "text",
766
1173
  admin: {
767
- description: 'Optional short icon label (e.g. "01", "Leaf", "Star").'
1174
+ description: 'Optional short icon label (e.g. "01", "Leaf", "Star").',
1175
+ condition: (_, siblingData) => siblingData?.iconType !== "lucide"
1176
+ }
1177
+ },
1178
+ {
1179
+ name: "iconLucide",
1180
+ type: "select",
1181
+ options: [
1182
+ { label: "Shield Check", value: "ShieldCheck" },
1183
+ { label: "Clock", value: "Clock" },
1184
+ { label: "Leaf", value: "Leaf" },
1185
+ { label: "Dollar Sign", value: "DollarSign" },
1186
+ { label: "Hard Hat", value: "HardHat" },
1187
+ { label: "Sparkles", value: "Sparkles" },
1188
+ { label: "Tree Pine", value: "TreePine" },
1189
+ { label: "Badge Check", value: "BadgeCheck" },
1190
+ { label: "Calendar Clock", value: "CalendarClock" }
1191
+ ],
1192
+ admin: {
1193
+ description: "Select an icon for this item.",
1194
+ condition: (_, siblingData) => siblingData?.iconType === "lucide"
768
1195
  }
769
1196
  },
770
1197
  {
@@ -772,6 +1199,74 @@ var FeatureGridBlock = {
772
1199
  type: "upload",
773
1200
  relationTo: "media",
774
1201
  required: false
1202
+ },
1203
+ {
1204
+ name: "imageHeight",
1205
+ type: "number",
1206
+ defaultValue: 160,
1207
+ min: 40,
1208
+ max: 600,
1209
+ admin: {
1210
+ description: "Overrides the image height in the builder/section (in pixels).",
1211
+ step: 10
1212
+ }
1213
+ },
1214
+ {
1215
+ name: "imageFit",
1216
+ type: "select",
1217
+ defaultValue: "cover",
1218
+ options: [
1219
+ { label: "Cover", value: "cover" },
1220
+ { label: "Contain", value: "contain" }
1221
+ ]
1222
+ },
1223
+ {
1224
+ name: "imageCornerStyle",
1225
+ type: "select",
1226
+ defaultValue: "rounded",
1227
+ options: [
1228
+ { label: "Rounded", value: "rounded" },
1229
+ { label: "Square", value: "square" }
1230
+ ]
1231
+ },
1232
+ {
1233
+ name: "imagePosition",
1234
+ type: "select",
1235
+ defaultValue: "center",
1236
+ options: [
1237
+ { label: "Center", value: "center" },
1238
+ { label: "Top", value: "top" },
1239
+ { label: "Bottom", value: "bottom" },
1240
+ { label: "Left", value: "left" },
1241
+ { label: "Right", value: "right" }
1242
+ ]
1243
+ },
1244
+ {
1245
+ name: "imagePositionX",
1246
+ type: "number",
1247
+ min: 0,
1248
+ max: 100,
1249
+ admin: {
1250
+ description: "Optional custom horizontal focus (0-100). Overrides Image Position when set.",
1251
+ step: 1
1252
+ }
1253
+ },
1254
+ {
1255
+ name: "imagePositionY",
1256
+ type: "number",
1257
+ min: 0,
1258
+ max: 100,
1259
+ admin: {
1260
+ description: "Optional custom vertical focus (0-100). Overrides Image Position when set.",
1261
+ step: 1
1262
+ }
1263
+ },
1264
+ {
1265
+ name: "settings",
1266
+ type: "json",
1267
+ admin: {
1268
+ hidden: true
1269
+ }
775
1270
  }
776
1271
  ]
777
1272
  },
@@ -797,6 +1292,14 @@ var FeatureGridBlock = {
797
1292
  description: "Optional background color override when using the Highlight variant (example: #1f684f)."
798
1293
  }
799
1294
  },
1295
+ {
1296
+ name: "settings",
1297
+ type: "json",
1298
+ admin: {
1299
+ description: "Internal builder settings schema v2.",
1300
+ hidden: true
1301
+ }
1302
+ },
800
1303
  ...sectionStyleFields()
801
1304
  ]
802
1305
  };
@@ -835,6 +1338,14 @@ var FormEmbedBlock = {
835
1338
  }
836
1339
  ]
837
1340
  },
1341
+ {
1342
+ name: "settings",
1343
+ type: "json",
1344
+ admin: {
1345
+ description: "Internal builder settings schema v2.",
1346
+ hidden: true
1347
+ }
1348
+ },
838
1349
  ...sectionStyleFields()
839
1350
  ]
840
1351
  };
@@ -898,10 +1409,44 @@ var HeroBlock = {
898
1409
  relationTo: "media"
899
1410
  },
900
1411
  {
901
- name: "backgroundImageURL",
902
- type: "text",
1412
+ name: "backgroundImageFit",
1413
+ type: "select",
1414
+ defaultValue: "cover",
1415
+ options: [
1416
+ { label: "Cover", value: "cover" },
1417
+ { label: "Cover (Square)", value: "cover-square" },
1418
+ { label: "Contain", value: "contain" },
1419
+ { label: "Contain (Square)", value: "contain-square" }
1420
+ ],
1421
+ admin: {
1422
+ description: "How the hero image should be sized within the section."
1423
+ }
1424
+ },
1425
+ {
1426
+ name: "backgroundImageCornerStyle",
1427
+ type: "select",
1428
+ defaultValue: "rounded",
1429
+ options: [
1430
+ { label: "Rounded", value: "rounded" },
1431
+ { label: "Square", value: "square" }
1432
+ ],
903
1433
  admin: {
904
- description: "Optional external/background image URL override for this hero section."
1434
+ description: "How the hero image corners should appear."
1435
+ }
1436
+ },
1437
+ {
1438
+ name: "backgroundImagePosition",
1439
+ type: "select",
1440
+ defaultValue: "center",
1441
+ options: [
1442
+ { label: "Center", value: "center" },
1443
+ { label: "Top", value: "top" },
1444
+ { label: "Bottom", value: "bottom" },
1445
+ { label: "Left", value: "left" },
1446
+ { label: "Right", value: "right" }
1447
+ ],
1448
+ admin: {
1449
+ description: "Where the hero image should anchor inside the section."
905
1450
  }
906
1451
  },
907
1452
  {
@@ -911,6 +1456,113 @@ var HeroBlock = {
911
1456
  description: "Optional background color override (example: #124a37)."
912
1457
  }
913
1458
  },
1459
+ {
1460
+ name: "backgroundOverlayMode",
1461
+ type: "select",
1462
+ defaultValue: "none",
1463
+ options: [
1464
+ { label: "None", value: "none" },
1465
+ { label: "Solid", value: "solid" },
1466
+ { label: "Gradient", value: "gradient" }
1467
+ ],
1468
+ admin: {
1469
+ description: "Optional overlay on top of the hero image (applies when an image is present)."
1470
+ }
1471
+ },
1472
+ {
1473
+ name: "backgroundOverlayOpacity",
1474
+ type: "number",
1475
+ defaultValue: 45,
1476
+ min: 0,
1477
+ max: 100,
1478
+ admin: {
1479
+ description: "Overlay opacity (0-100).",
1480
+ step: 1
1481
+ }
1482
+ },
1483
+ {
1484
+ name: "backgroundOverlayColor",
1485
+ type: "text",
1486
+ admin: {
1487
+ description: "Overlay solid color (example: #000000). Used when Overlay Mode is Solid."
1488
+ }
1489
+ },
1490
+ {
1491
+ name: "backgroundOverlayGradientFrom",
1492
+ type: "text",
1493
+ admin: {
1494
+ description: "Gradient overlay start color (example: #0d4a37). Used when Overlay Mode is Gradient."
1495
+ }
1496
+ },
1497
+ {
1498
+ name: "backgroundOverlayGradientTo",
1499
+ type: "text",
1500
+ admin: {
1501
+ description: "Gradient overlay end color (example: #1f684f). Used when Overlay Mode is Gradient."
1502
+ }
1503
+ },
1504
+ {
1505
+ name: "backgroundOverlayGradientAngle",
1506
+ type: "text",
1507
+ admin: {
1508
+ description: "Gradient overlay angle in degrees (0-360). Used when Overlay Mode is Gradient."
1509
+ }
1510
+ },
1511
+ {
1512
+ name: "backgroundOverlayGradientFromStrength",
1513
+ type: "number",
1514
+ defaultValue: 100,
1515
+ min: 0,
1516
+ max: 100,
1517
+ admin: {
1518
+ description: "Gradient start strength (0-100). Set to 0 for transparent.",
1519
+ step: 1
1520
+ }
1521
+ },
1522
+ {
1523
+ name: "backgroundOverlayGradientToStrength",
1524
+ type: "number",
1525
+ defaultValue: 100,
1526
+ min: 0,
1527
+ max: 100,
1528
+ admin: {
1529
+ description: "Gradient end strength (0-100). Set to 0 for transparent.",
1530
+ step: 1
1531
+ }
1532
+ },
1533
+ {
1534
+ name: "backgroundOverlayGradientStart",
1535
+ type: "number",
1536
+ defaultValue: 0,
1537
+ min: 0,
1538
+ max: 100,
1539
+ admin: {
1540
+ description: "Where the gradient starts (0-100).",
1541
+ step: 1
1542
+ }
1543
+ },
1544
+ {
1545
+ name: "backgroundOverlayGradientEnd",
1546
+ type: "number",
1547
+ defaultValue: 100,
1548
+ min: 0,
1549
+ max: 100,
1550
+ admin: {
1551
+ description: "Where the gradient ends (0-100).",
1552
+ step: 1
1553
+ }
1554
+ },
1555
+ {
1556
+ name: "backgroundOverlayGradientFeather",
1557
+ type: "number",
1558
+ defaultValue: 100,
1559
+ min: 0,
1560
+ max: 100,
1561
+ admin: {
1562
+ description: "How soft the transition is (0 = hard edge, 100 = smooth).",
1563
+ step: 1
1564
+ }
1565
+ },
914
1566
  {
915
1567
  name: "variant",
916
1568
  type: "select",
@@ -926,14 +1578,44 @@ var HeroBlock = {
926
1578
  }
927
1579
  ]
928
1580
  },
929
- ...sectionStyleFields()
930
- ]
931
- };
932
-
933
- // src/blocks/blocks/LogoWall.ts
934
- var LogoWallBlock = {
935
- slug: "logoWall",
936
- imageURL: "/images/logo-mark.svg",
1581
+ {
1582
+ name: "heroHeight",
1583
+ type: "select",
1584
+ defaultValue: "sm",
1585
+ options: [
1586
+ {
1587
+ label: "Small",
1588
+ value: "sm"
1589
+ },
1590
+ {
1591
+ label: "Medium (Half Screen)",
1592
+ value: "md"
1593
+ },
1594
+ {
1595
+ label: "Full Screen",
1596
+ value: "full"
1597
+ }
1598
+ ],
1599
+ admin: {
1600
+ description: "Controls the vertical height of the hero section."
1601
+ }
1602
+ },
1603
+ {
1604
+ name: "settings",
1605
+ type: "json",
1606
+ admin: {
1607
+ description: "Internal builder settings schema v2.",
1608
+ hidden: true
1609
+ }
1610
+ },
1611
+ ...sectionStyleFields()
1612
+ ]
1613
+ };
1614
+
1615
+ // src/blocks/blocks/LogoWall.ts
1616
+ var LogoWallBlock = {
1617
+ slug: "logoWall",
1618
+ imageURL: "/images/logo-mark.svg",
937
1619
  imageAltText: "Logo wall section preview",
938
1620
  admin: {
939
1621
  components: {
@@ -971,12 +1653,88 @@ var LogoWallBlock = {
971
1653
  relationTo: "media",
972
1654
  required: false
973
1655
  },
1656
+ {
1657
+ name: "imageHeight",
1658
+ type: "number",
1659
+ defaultValue: 64,
1660
+ min: 24,
1661
+ max: 200,
1662
+ admin: {
1663
+ description: "Overrides the logo image height (in pixels).",
1664
+ step: 4
1665
+ }
1666
+ },
1667
+ {
1668
+ name: "imageFit",
1669
+ type: "select",
1670
+ defaultValue: "contain",
1671
+ options: [
1672
+ { label: "Cover", value: "cover" },
1673
+ { label: "Contain", value: "contain" }
1674
+ ]
1675
+ },
1676
+ {
1677
+ name: "imageCornerStyle",
1678
+ type: "select",
1679
+ defaultValue: "rounded",
1680
+ options: [
1681
+ { label: "Rounded", value: "rounded" },
1682
+ { label: "Square", value: "square" }
1683
+ ]
1684
+ },
1685
+ {
1686
+ name: "imagePosition",
1687
+ type: "select",
1688
+ defaultValue: "center",
1689
+ options: [
1690
+ { label: "Center", value: "center" },
1691
+ { label: "Top", value: "top" },
1692
+ { label: "Bottom", value: "bottom" },
1693
+ { label: "Left", value: "left" },
1694
+ { label: "Right", value: "right" }
1695
+ ]
1696
+ },
1697
+ {
1698
+ name: "imagePositionX",
1699
+ type: "number",
1700
+ min: 0,
1701
+ max: 100,
1702
+ admin: {
1703
+ description: "Optional custom horizontal focus (0-100). Overrides Image Position when set.",
1704
+ step: 1
1705
+ }
1706
+ },
1707
+ {
1708
+ name: "imagePositionY",
1709
+ type: "number",
1710
+ min: 0,
1711
+ max: 100,
1712
+ admin: {
1713
+ description: "Optional custom vertical focus (0-100). Overrides Image Position when set.",
1714
+ step: 1
1715
+ }
1716
+ },
974
1717
  {
975
1718
  name: "href",
976
1719
  type: "text"
1720
+ },
1721
+ {
1722
+ name: "settings",
1723
+ type: "json",
1724
+ admin: {
1725
+ hidden: true
1726
+ }
977
1727
  }
978
1728
  ]
979
1729
  },
1730
+ {
1731
+ name: "settings",
1732
+ type: "json",
1733
+ admin: {
1734
+ description: "Internal builder settings schema v2.",
1735
+ hidden: true
1736
+ }
1737
+ },
980
1738
  ...sectionStyleFields()
981
1739
  ]
982
1740
  };
@@ -1021,6 +1779,44 @@ var MediaBlock = {
1021
1779
  }
1022
1780
  ]
1023
1781
  },
1782
+ {
1783
+ name: "imageFit",
1784
+ type: "select",
1785
+ defaultValue: "cover",
1786
+ options: [
1787
+ { label: "Cover", value: "cover" },
1788
+ { label: "Contain", value: "contain" }
1789
+ ]
1790
+ },
1791
+ {
1792
+ name: "imageCornerStyle",
1793
+ type: "select",
1794
+ defaultValue: "rounded",
1795
+ options: [
1796
+ { label: "Rounded", value: "rounded" },
1797
+ { label: "Square", value: "square" }
1798
+ ]
1799
+ },
1800
+ {
1801
+ name: "imagePosition",
1802
+ type: "select",
1803
+ defaultValue: "center",
1804
+ options: [
1805
+ { label: "Center", value: "center" },
1806
+ { label: "Top", value: "top" },
1807
+ { label: "Bottom", value: "bottom" },
1808
+ { label: "Left", value: "left" },
1809
+ { label: "Right", value: "right" }
1810
+ ]
1811
+ },
1812
+ {
1813
+ name: "settings",
1814
+ type: "json",
1815
+ admin: {
1816
+ description: "Internal builder settings schema v2.",
1817
+ hidden: true
1818
+ }
1819
+ },
1024
1820
  ...sectionStyleFields()
1025
1821
  ]
1026
1822
  };
@@ -1064,6 +1860,14 @@ var RichTextBlock = {
1064
1860
  }
1065
1861
  ]
1066
1862
  },
1863
+ {
1864
+ name: "settings",
1865
+ type: "json",
1866
+ admin: {
1867
+ description: "Internal builder settings schema v2.",
1868
+ hidden: true
1869
+ }
1870
+ },
1067
1871
  ...sectionStyleFields()
1068
1872
  ]
1069
1873
  };
@@ -1111,9 +1915,24 @@ var StatsBlock = {
1111
1915
  {
1112
1916
  name: "description",
1113
1917
  type: "text"
1918
+ },
1919
+ {
1920
+ name: "settings",
1921
+ type: "json",
1922
+ admin: {
1923
+ hidden: true
1924
+ }
1114
1925
  }
1115
1926
  ]
1116
1927
  },
1928
+ {
1929
+ name: "settings",
1930
+ type: "json",
1931
+ admin: {
1932
+ description: "Internal builder settings schema v2.",
1933
+ hidden: true
1934
+ }
1935
+ },
1117
1936
  ...sectionStyleFields()
1118
1937
  ]
1119
1938
  };
@@ -1138,11 +1957,41 @@ var TestimonialsBlock = {
1138
1957
  type: "text",
1139
1958
  required: true
1140
1959
  },
1960
+ {
1961
+ name: "visibleCount",
1962
+ type: "number",
1963
+ defaultValue: 3,
1964
+ min: 1,
1965
+ max: 6,
1966
+ admin: {
1967
+ description: "How many testimonials to show at once.",
1968
+ step: 1
1969
+ }
1970
+ },
1971
+ {
1972
+ name: "autoRotate",
1973
+ type: "checkbox",
1974
+ defaultValue: true,
1975
+ admin: {
1976
+ description: "Automatically rotates through all testimonials."
1977
+ }
1978
+ },
1979
+ {
1980
+ name: "rotateIntervalSeconds",
1981
+ type: "number",
1982
+ defaultValue: 7,
1983
+ min: 2,
1984
+ max: 30,
1985
+ admin: {
1986
+ description: "How often to rotate (in seconds).",
1987
+ step: 1
1988
+ }
1989
+ },
1141
1990
  {
1142
1991
  name: "items",
1143
1992
  type: "array",
1144
1993
  minRows: 1,
1145
- maxRows: 6,
1994
+ maxRows: 30,
1146
1995
  fields: [
1147
1996
  {
1148
1997
  name: "quote",
@@ -1157,9 +2006,35 @@ var TestimonialsBlock = {
1157
2006
  {
1158
2007
  name: "location",
1159
2008
  type: "text"
2009
+ },
2010
+ {
2011
+ name: "rating",
2012
+ type: "number",
2013
+ defaultValue: 5,
2014
+ min: 1,
2015
+ max: 5,
2016
+ admin: {
2017
+ description: "Star rating (1-5).",
2018
+ step: 1
2019
+ }
2020
+ },
2021
+ {
2022
+ name: "settings",
2023
+ type: "json",
2024
+ admin: {
2025
+ hidden: true
2026
+ }
1160
2027
  }
1161
2028
  ]
1162
2029
  },
2030
+ {
2031
+ name: "settings",
2032
+ type: "json",
2033
+ admin: {
2034
+ description: "Internal builder settings schema v2.",
2035
+ hidden: true
2036
+ }
2037
+ },
1163
2038
  ...sectionStyleFields()
1164
2039
  ]
1165
2040
  };
@@ -1283,12 +2158,14 @@ var sectionPresets = [
1283
2158
  {
1284
2159
  quote: "Great communication, fair pricing, and the cleanup was perfect. We will use them again.",
1285
2160
  name: "Katie M.",
1286
- location: "Austin, TX"
2161
+ location: "Austin, TX",
2162
+ rating: 5
1287
2163
  },
1288
2164
  {
1289
2165
  quote: "They removed a dangerous limb over our driveway quickly and safely.",
1290
2166
  name: "James R.",
1291
- location: "Round Rock, TX"
2167
+ location: "Round Rock, TX",
2168
+ rating: 5
1292
2169
  }
1293
2170
  ]
1294
2171
  },
@@ -1510,257 +2387,233 @@ function createPayloadClient(config) {
1510
2387
 
1511
2388
  // src/nextjs/queries/pages.ts
1512
2389
  var import_cache = require("next/cache");
1513
- function normalizePath(segments) {
1514
- if (!segments || segments.length === 0) {
1515
- return "/";
2390
+
2391
+ // src/studio/index.ts
2392
+ var studio_exports = {};
2393
+ __export(studio_exports, {
2394
+ assertStudioDocumentV1: () => assertStudioDocumentV1,
2395
+ compileStudioDocument: () => compileStudioDocument,
2396
+ createEmptyStudioDocument: () => createEmptyStudioDocument,
2397
+ createImageUploadOptimizationHook: () => createImageUploadOptimizationHook,
2398
+ createStudioRegistry: () => createStudioRegistry,
2399
+ migrateStudioDocument: () => migrateStudioDocument,
2400
+ validateStudioDocument: () => validateStudioDocument,
2401
+ withImageUploadOptimization: () => withImageUploadOptimization
2402
+ });
2403
+
2404
+ // src/studio/imageUploadOptimization.ts
2405
+ var import_promises = require("fs/promises");
2406
+ var DEFAULT_SUPPORTED_MIME_TYPES = [
2407
+ "image/jpeg",
2408
+ "image/jpg",
2409
+ "image/png",
2410
+ "image/webp",
2411
+ "image/avif",
2412
+ "image/tiff"
2413
+ ];
2414
+ var DEFAULT_OPTIONS = {
2415
+ avifQuality: 50,
2416
+ enforceSmallerForLossy: true,
2417
+ jpegQuality: 78,
2418
+ minBytes: 48 * 1024,
2419
+ minQualityFloor: 42,
2420
+ onlyIfSmaller: true,
2421
+ pngCompressionLevel: 9,
2422
+ skipAnimated: true,
2423
+ tiffQuality: 75,
2424
+ webpQuality: 78
2425
+ };
2426
+ var clamp = (value, min, max) => Math.max(min, Math.min(max, Math.round(value)));
2427
+ var isUploadMutationOperation = (operation) => operation === "create" || operation === "update" || operation === "updateByID";
2428
+ var isLossyMimeType = (mimetype) => mimetype === "image/jpeg" || mimetype === "image/jpg" || mimetype === "image/webp" || mimetype === "image/avif" || mimetype === "image/tiff";
2429
+ var readIncomingBuffer = async (file) => {
2430
+ if (file.tempFilePath) {
2431
+ return (0, import_promises.readFile)(file.tempFilePath);
1516
2432
  }
1517
- const cleaned = segments.map((segment) => segment.trim()).filter(Boolean).join("/");
1518
- return cleaned.length > 0 ? `/${cleaned}` : "/";
1519
- }
1520
- async function queryPageByPath(payload, path2, draft) {
1521
- const result = await payload.find({
1522
- collection: "pages",
1523
- depth: 2,
1524
- draft,
1525
- limit: 1,
1526
- overrideAccess: false,
1527
- where: {
1528
- path: {
1529
- equals: path2
2433
+ return file.data;
2434
+ };
2435
+ var writeOptimizedBuffer = async (file, buffer) => {
2436
+ if (file.tempFilePath) {
2437
+ await (0, import_promises.unlink)(file.tempFilePath).catch(() => void 0);
2438
+ }
2439
+ return {
2440
+ ...file,
2441
+ data: buffer,
2442
+ size: buffer.length,
2443
+ tempFilePath: void 0
2444
+ };
2445
+ };
2446
+ var createImageUploadOptimizationHook = (options = {}) => {
2447
+ const supportedMimeTypes = new Set(options.supportedMimeTypes || DEFAULT_SUPPORTED_MIME_TYPES);
2448
+ const settings = { ...DEFAULT_OPTIONS, ...options };
2449
+ return async ({ operation, req }) => {
2450
+ if (!isUploadMutationOperation(operation)) {
2451
+ return;
2452
+ }
2453
+ const file = req.file;
2454
+ if (!file) {
2455
+ return;
2456
+ }
2457
+ if (!supportedMimeTypes.has(file.mimetype)) {
2458
+ return;
2459
+ }
2460
+ if (typeof file.size === "number" && file.size < settings.minBytes) {
2461
+ return;
2462
+ }
2463
+ const sharpFactory = req.payload?.config?.sharp;
2464
+ if (typeof sharpFactory !== "function") {
2465
+ return;
2466
+ }
2467
+ const originalBuffer = await readIncomingBuffer(file);
2468
+ if (!Buffer.isBuffer(originalBuffer) || originalBuffer.length === 0) {
2469
+ return;
2470
+ }
2471
+ const animatedInput = file.mimetype === "image/avif" || file.mimetype === "image/webp";
2472
+ const metadataProbe = await sharpFactory(
2473
+ originalBuffer,
2474
+ animatedInput ? {
2475
+ animated: true
2476
+ } : void 0
2477
+ ).metadata();
2478
+ if (settings.skipAnimated && typeof metadataProbe.pages === "number" && metadataProbe.pages > 1) {
2479
+ return;
2480
+ }
2481
+ const buildPipeline = (qualityOverride) => {
2482
+ let pipeline = sharpFactory(
2483
+ originalBuffer,
2484
+ animatedInput ? {
2485
+ animated: true
2486
+ } : void 0
2487
+ );
2488
+ switch (file.mimetype) {
2489
+ case "image/jpeg":
2490
+ case "image/jpg":
2491
+ pipeline = pipeline.jpeg({
2492
+ mozjpeg: true,
2493
+ progressive: true,
2494
+ quality: clamp(qualityOverride ?? settings.jpegQuality, 20, 100)
2495
+ });
2496
+ break;
2497
+ case "image/png":
2498
+ pipeline = pipeline.png({
2499
+ compressionLevel: clamp(settings.pngCompressionLevel, 0, 9),
2500
+ palette: false
2501
+ });
2502
+ break;
2503
+ case "image/webp":
2504
+ pipeline = pipeline.webp({
2505
+ quality: clamp(qualityOverride ?? settings.webpQuality, 20, 100)
2506
+ });
2507
+ break;
2508
+ case "image/avif":
2509
+ pipeline = pipeline.avif({
2510
+ quality: clamp(qualityOverride ?? settings.avifQuality, 20, 100)
2511
+ });
2512
+ break;
2513
+ case "image/tiff":
2514
+ pipeline = pipeline.tiff({
2515
+ quality: clamp(qualityOverride ?? settings.tiffQuality, 20, 100)
2516
+ });
2517
+ break;
2518
+ default:
2519
+ return null;
1530
2520
  }
2521
+ return pipeline;
2522
+ };
2523
+ const initialPipeline = buildPipeline();
2524
+ if (!initialPipeline) {
2525
+ return;
1531
2526
  }
1532
- });
1533
- if (result.docs.length > 0) {
1534
- return result.docs[0] || null;
1535
- }
1536
- if (path2 === "/") {
1537
- const homeResult = await payload.find({
1538
- collection: "pages",
1539
- depth: 2,
1540
- draft,
1541
- limit: 1,
1542
- overrideAccess: false,
1543
- where: {
1544
- slug: {
1545
- equals: "home"
2527
+ let optimizedBuffer = await initialPipeline.toBuffer();
2528
+ const shouldSweepQuality = settings.enforceSmallerForLossy && isLossyMimeType(file.mimetype);
2529
+ if (shouldSweepQuality) {
2530
+ const initialQuality = file.mimetype === "image/jpeg" || file.mimetype === "image/jpg" ? settings.jpegQuality : file.mimetype === "image/webp" ? settings.webpQuality : file.mimetype === "image/avif" ? settings.avifQuality : settings.tiffQuality;
2531
+ let bestBuffer = optimizedBuffer;
2532
+ let quality = clamp(initialQuality - 5, settings.minQualityFloor, 100);
2533
+ while (quality >= settings.minQualityFloor) {
2534
+ const retryPipeline = buildPipeline(quality);
2535
+ if (!retryPipeline) {
2536
+ break;
2537
+ }
2538
+ const retryBuffer = await retryPipeline.toBuffer();
2539
+ if (retryBuffer.length < bestBuffer.length) {
2540
+ bestBuffer = retryBuffer;
1546
2541
  }
2542
+ quality -= 5;
1547
2543
  }
1548
- });
1549
- return homeResult.docs[0] || null;
2544
+ optimizedBuffer = bestBuffer;
2545
+ }
2546
+ if (settings.onlyIfSmaller && optimizedBuffer.length >= originalBuffer.length) {
2547
+ return;
2548
+ }
2549
+ req.file = await writeOptimizedBuffer(file, optimizedBuffer);
2550
+ };
2551
+ };
2552
+ var withImageUploadOptimization = (collection, options = {}) => {
2553
+ if (!collection.upload) {
2554
+ return collection;
1550
2555
  }
1551
- return null;
1552
- }
1553
- function createPageQueries(getPayloadClient, contentTag = "website-content") {
1554
- const getPublishedPageByPathCached = (0, import_cache.unstable_cache)(
1555
- async (path2) => {
1556
- const payload = await getPayloadClient();
1557
- return queryPageByPath(payload, path2, false);
1558
- },
1559
- ["page-by-path"],
1560
- { tags: [contentTag] }
1561
- );
1562
- async function getPageBySegments(segments, draft = false) {
1563
- const path2 = normalizePath(segments);
1564
- const payload = await getPayloadClient();
1565
- if (draft) {
1566
- return queryPageByPath(payload, path2, true);
2556
+ const existingHooks = collection.hooks || {};
2557
+ const beforeOperation = existingHooks.beforeOperation || [];
2558
+ return {
2559
+ ...collection,
2560
+ hooks: {
2561
+ ...existingHooks,
2562
+ beforeOperation: [createImageUploadOptimizationHook(options), ...beforeOperation]
1567
2563
  }
1568
- return getPublishedPageByPathCached(path2);
2564
+ };
2565
+ };
2566
+
2567
+ // src/studio/index.ts
2568
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
2569
+ var makeIssue = (message, path2, code = "studio.invalid") => ({
2570
+ code,
2571
+ message,
2572
+ path: path2,
2573
+ severity: "error"
2574
+ });
2575
+ var createEmptyStudioDocument = (title) => ({
2576
+ metadata: {},
2577
+ schemaVersion: 1,
2578
+ title,
2579
+ nodes: [],
2580
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2581
+ });
2582
+ function assertStudioDocumentV1(input) {
2583
+ if (!isRecord(input)) {
2584
+ throw new Error("Studio document must be an object");
1569
2585
  }
1570
- async function listPublishedPagePaths() {
1571
- const payload = await getPayloadClient();
1572
- const pages = await payload.find({
1573
- collection: "pages",
1574
- depth: 0,
1575
- draft: false,
1576
- limit: 1e3,
1577
- pagination: false,
1578
- overrideAccess: false,
1579
- where: {
1580
- _status: {
1581
- equals: "published"
1582
- }
1583
- }
1584
- });
1585
- return pages.docs.map((doc) => doc.path).filter((path2) => typeof path2 === "string" && path2.length > 0);
2586
+ if (input.schemaVersion !== 1) {
2587
+ throw new Error("Unsupported studio schemaVersion");
1586
2588
  }
1587
- function pathToSegments(path2) {
1588
- if (!path2 || path2 === "/") {
1589
- return [];
1590
- }
1591
- return path2.split("/").filter(Boolean);
2589
+ if (!Array.isArray(input.nodes)) {
2590
+ throw new Error("Studio document nodes must be an array");
1592
2591
  }
2592
+ const nodes = input.nodes.map((node, index) => {
2593
+ if (!isRecord(node)) {
2594
+ throw new Error(`Node at index ${index} must be an object`);
2595
+ }
2596
+ if (typeof node.id !== "string" || node.id.length === 0) {
2597
+ throw new Error(`Node at index ${index} has invalid id`);
2598
+ }
2599
+ if (typeof node.type !== "string" || node.type.length === 0) {
2600
+ throw new Error(`Node at index ${index} has invalid type`);
2601
+ }
2602
+ if (!isRecord(node.data)) {
2603
+ throw new Error(`Node at index ${index} has invalid data`);
2604
+ }
2605
+ return {
2606
+ id: node.id,
2607
+ type: node.type,
2608
+ data: node.data
2609
+ };
2610
+ });
1593
2611
  return {
1594
- getPageBySegments,
1595
- listPublishedPagePaths,
1596
- pathToSegments
1597
- };
1598
- }
1599
-
1600
- // src/nextjs/queries/site.ts
1601
- var import_cache2 = require("next/cache");
1602
- function createSiteQueries(getPayloadClient, contentTag = "website-content") {
1603
- const getSiteSettingsCached = (0, import_cache2.unstable_cache)(
1604
- async () => {
1605
- const payload = await getPayloadClient();
1606
- const settings = await payload.findGlobal({
1607
- slug: "site-settings",
1608
- depth: 1
1609
- });
1610
- return settings;
1611
- },
1612
- ["site-settings-global"],
1613
- { tags: [contentTag] }
1614
- );
1615
- const getHeaderCached = (0, import_cache2.unstable_cache)(
1616
- async () => {
1617
- const payload = await getPayloadClient();
1618
- const header = await payload.findGlobal({
1619
- slug: "header",
1620
- depth: 1
1621
- });
1622
- return header;
1623
- },
1624
- ["header-global"],
1625
- { tags: [contentTag] }
1626
- );
1627
- const getFooterCached = (0, import_cache2.unstable_cache)(
1628
- async () => {
1629
- const payload = await getPayloadClient();
1630
- const footer = await payload.findGlobal({
1631
- slug: "footer",
1632
- depth: 1
1633
- });
1634
- return footer;
1635
- },
1636
- ["footer-global"],
1637
- { tags: [contentTag] }
1638
- );
1639
- async function getSiteSettings(draft = false) {
1640
- if (draft) {
1641
- const payload = await getPayloadClient();
1642
- const settings = await payload.findGlobal({
1643
- slug: "site-settings",
1644
- depth: 1,
1645
- draft: true
1646
- });
1647
- return settings;
1648
- }
1649
- return getSiteSettingsCached();
1650
- }
1651
- async function getHeader(draft = false) {
1652
- if (draft) {
1653
- const payload = await getPayloadClient();
1654
- const header = await payload.findGlobal({
1655
- slug: "header",
1656
- depth: 1,
1657
- draft: true
1658
- });
1659
- return header;
1660
- }
1661
- return getHeaderCached();
1662
- }
1663
- async function getFooter(draft = false) {
1664
- if (draft) {
1665
- const payload = await getPayloadClient();
1666
- const footer = await payload.findGlobal({
1667
- slug: "footer",
1668
- depth: 1,
1669
- draft: true
1670
- });
1671
- return footer;
1672
- }
1673
- return getFooterCached();
1674
- }
1675
- return {
1676
- getSiteSettings,
1677
- getHeader,
1678
- getFooter
1679
- };
1680
- }
1681
-
1682
- // src/nextjs/utilities/media.ts
1683
- function resolveMedia(media) {
1684
- if (!media) {
1685
- return null;
1686
- }
1687
- if (typeof media === "number" || typeof media === "string") {
1688
- return null;
1689
- }
1690
- if (media.url) {
1691
- return {
1692
- url: media.url,
1693
- alt: media.alt || ""
1694
- };
1695
- }
1696
- if (media.filename) {
1697
- return {
1698
- url: `/media/${media.filename}`,
1699
- alt: media.alt || ""
1700
- };
1701
- }
1702
- return null;
1703
- }
1704
-
1705
- // src/studio/index.ts
1706
- var studio_exports = {};
1707
- __export(studio_exports, {
1708
- assertStudioDocumentV1: () => assertStudioDocumentV1,
1709
- compileStudioDocument: () => compileStudioDocument,
1710
- createEmptyStudioDocument: () => createEmptyStudioDocument,
1711
- createStudioRegistry: () => createStudioRegistry,
1712
- migrateStudioDocument: () => migrateStudioDocument,
1713
- validateStudioDocument: () => validateStudioDocument
1714
- });
1715
- var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
1716
- var makeIssue = (message, path2, code = "studio.invalid") => ({
1717
- code,
1718
- message,
1719
- path: path2,
1720
- severity: "error"
1721
- });
1722
- var createEmptyStudioDocument = (title) => ({
1723
- metadata: {},
1724
- schemaVersion: 1,
1725
- title,
1726
- nodes: [],
1727
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1728
- });
1729
- function assertStudioDocumentV1(input) {
1730
- if (!isRecord(input)) {
1731
- throw new Error("Studio document must be an object");
1732
- }
1733
- if (input.schemaVersion !== 1) {
1734
- throw new Error("Unsupported studio schemaVersion");
1735
- }
1736
- if (!Array.isArray(input.nodes)) {
1737
- throw new Error("Studio document nodes must be an array");
1738
- }
1739
- const nodes = input.nodes.map((node, index) => {
1740
- if (!isRecord(node)) {
1741
- throw new Error(`Node at index ${index} must be an object`);
1742
- }
1743
- if (typeof node.id !== "string" || node.id.length === 0) {
1744
- throw new Error(`Node at index ${index} has invalid id`);
1745
- }
1746
- if (typeof node.type !== "string" || node.type.length === 0) {
1747
- throw new Error(`Node at index ${index} has invalid type`);
1748
- }
1749
- if (!isRecord(node.data)) {
1750
- throw new Error(`Node at index ${index} has invalid data`);
1751
- }
1752
- return {
1753
- id: node.id,
1754
- type: node.type,
1755
- data: node.data
1756
- };
1757
- });
1758
- return {
1759
- metadata: isRecord(input.metadata) ? input.metadata : void 0,
1760
- schemaVersion: 1,
1761
- title: typeof input.title === "string" ? input.title : void 0,
1762
- nodes,
1763
- updatedAt: typeof input.updatedAt === "string" ? input.updatedAt : void 0
2612
+ metadata: isRecord(input.metadata) ? input.metadata : void 0,
2613
+ schemaVersion: 1,
2614
+ title: typeof input.title === "string" ? input.title : void 0,
2615
+ nodes,
2616
+ updatedAt: typeof input.updatedAt === "string" ? input.updatedAt : void 0
1764
2617
  };
1765
2618
  }
1766
2619
  function createStudioRegistry(modules) {
@@ -1821,41 +2674,833 @@ function compileStudioDocument(document, modules) {
1821
2674
  }
1822
2675
  return {
1823
2676
  id: node.id,
1824
- blockType: node.type,
1825
- ...node.data
2677
+ blockType: node.type,
2678
+ ...node.data
2679
+ };
2680
+ });
2681
+ return {
2682
+ issues,
2683
+ layout
2684
+ };
2685
+ }
2686
+ function migrateStudioDocument(value, migrations) {
2687
+ const sorted = [...migrations].sort((a, b) => a.fromVersion - b.fromVersion);
2688
+ let current = value;
2689
+ for (const migration of sorted) {
2690
+ if (!isRecord(current) || current.schemaVersion !== migration.fromVersion) {
2691
+ continue;
2692
+ }
2693
+ current = migration.migrate(current);
2694
+ }
2695
+ return assertStudioDocumentV1(current);
2696
+ }
2697
+
2698
+ // src/studio-pages/index.ts
2699
+ var studio_pages_exports = {};
2700
+ __export(studio_pages_exports, {
2701
+ createDefaultStudioDocument: () => createDefaultStudioDocument,
2702
+ defaultBuilderThemeTokens: () => defaultBuilderThemeTokens,
2703
+ layoutToStudioDocument: () => layoutToStudioDocument,
2704
+ pageInspectorPanels: () => pageInspectorPanels,
2705
+ pageNodeTypes: () => pageNodeTypes,
2706
+ pagePaletteGroups: () => pagePaletteGroups,
2707
+ pageStudioModuleManifest: () => pageStudioModuleManifest,
2708
+ resolveBuilderThemeTokens: () => resolveBuilderThemeTokens,
2709
+ studioDocumentToLayout: () => studioDocumentToLayout
2710
+ });
2711
+
2712
+ // src/studio-pages/builder/settings-v2/types.ts
2713
+ var defaultBuilderBlockSettingsV2 = {
2714
+ advanced: {
2715
+ customClassName: "",
2716
+ editCopyInPanel: false,
2717
+ hideOnMobile: false
2718
+ },
2719
+ appearance: {
2720
+ contentBackgroundColor: "#ffffff",
2721
+ contentBackgroundMode: "none",
2722
+ contentGradientAngle: "135",
2723
+ contentGradientFrom: "#ffffff",
2724
+ contentGradientPreset: "none",
2725
+ contentGradientTo: "#f4f6f2",
2726
+ sectionBackgroundColor: "#ffffff",
2727
+ sectionBackgroundMode: "none",
2728
+ sectionGradientAngle: "135",
2729
+ sectionGradientFrom: "#124a37",
2730
+ sectionGradientPreset: "forest",
2731
+ sectionGradientTo: "#1f684f"
2732
+ },
2733
+ layout: {
2734
+ contentWidth: "inherit",
2735
+ sectionPaddingX: "inherit",
2736
+ sectionPaddingY: "md"
2737
+ },
2738
+ media: {
2739
+ cornerStyle: "rounded",
2740
+ fit: "cover",
2741
+ height: null,
2742
+ position: "center",
2743
+ positionX: null,
2744
+ positionY: null
2745
+ },
2746
+ typography: {
2747
+ bodyAlign: "left",
2748
+ headingAlign: "left",
2749
+ letterSpacingPreset: "normal",
2750
+ lineHeightPreset: "normal",
2751
+ maxTextWidth: "auto"
2752
+ },
2753
+ version: 2
2754
+ };
2755
+ var defaultBuilderItemSettingsV2 = {
2756
+ layout: {
2757
+ contentWidth: "inherit",
2758
+ sectionPaddingX: "inherit",
2759
+ sectionPaddingY: "md"
2760
+ },
2761
+ media: {
2762
+ cornerStyle: "rounded",
2763
+ fit: "cover",
2764
+ height: null,
2765
+ position: "center",
2766
+ positionX: null,
2767
+ positionY: null
2768
+ },
2769
+ typography: {
2770
+ bodyAlign: "left",
2771
+ headingAlign: "left",
2772
+ letterSpacingPreset: "normal",
2773
+ lineHeightPreset: "normal",
2774
+ maxTextWidth: "auto"
2775
+ },
2776
+ version: 2
2777
+ };
2778
+ var defaultBuilderThemeTokens = {
2779
+ colors: {
2780
+ accent: "#0d4a37",
2781
+ bodyText: "#13211c",
2782
+ headingText: "#13211c",
2783
+ surface: "#ffffff"
2784
+ },
2785
+ radii: {
2786
+ card: 16,
2787
+ panel: 14
2788
+ },
2789
+ spacing: {
2790
+ sectionGap: "md",
2791
+ sectionPadding: "md"
2792
+ },
2793
+ typography: {
2794
+ bodySize: "md",
2795
+ headingSize: "md"
2796
+ }
2797
+ };
2798
+
2799
+ // src/studio-pages/builder/adapters/settingsV2.ts
2800
+ var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
2801
+ var parsePercent = (value) => {
2802
+ if (typeof value === "number" && Number.isFinite(value)) {
2803
+ return Math.max(0, Math.min(100, value));
2804
+ }
2805
+ if (typeof value === "string" && value.trim().length > 0) {
2806
+ const parsed = Number(value);
2807
+ if (Number.isFinite(parsed)) {
2808
+ return Math.max(0, Math.min(100, parsed));
2809
+ }
2810
+ }
2811
+ return null;
2812
+ };
2813
+ var parsePixel = (value) => {
2814
+ if (typeof value === "number" && Number.isFinite(value)) {
2815
+ return value;
2816
+ }
2817
+ if (typeof value === "string" && value.trim().length > 0) {
2818
+ const parsed = Number(value);
2819
+ if (Number.isFinite(parsed)) {
2820
+ return parsed;
2821
+ }
2822
+ }
2823
+ return null;
2824
+ };
2825
+ var mergeSettings = (defaults, input) => {
2826
+ if (!isRecord2(input)) {
2827
+ return structuredClone(defaults);
2828
+ }
2829
+ const next = structuredClone(defaults);
2830
+ for (const [key, value] of Object.entries(input)) {
2831
+ if (isRecord2(value) && isRecord2(next[key])) {
2832
+ next[key] = mergeSettings(next[key], value);
2833
+ continue;
2834
+ }
2835
+ if (typeof value !== "undefined") {
2836
+ next[key] = value;
2837
+ }
2838
+ }
2839
+ return next;
2840
+ };
2841
+ var legacyBlockToV2Settings = (block) => {
2842
+ const current = structuredClone(defaultBuilderBlockSettingsV2);
2843
+ current.layout.contentWidth = block.contentWidth === "narrow" || block.contentWidth === "content" || block.contentWidth === "wide" || block.contentWidth === "full" || block.contentWidth === "inherit" ? block.contentWidth : current.layout.contentWidth;
2844
+ current.layout.sectionPaddingX = block.sectionPaddingX === "none" || block.sectionPaddingX === "sm" || block.sectionPaddingX === "md" || block.sectionPaddingX === "lg" || block.sectionPaddingX === "inherit" ? block.sectionPaddingX : current.layout.sectionPaddingX;
2845
+ current.layout.sectionPaddingY = block.sectionPaddingY === "none" || block.sectionPaddingY === "sm" || block.sectionPaddingY === "lg" ? block.sectionPaddingY : current.layout.sectionPaddingY;
2846
+ current.appearance.sectionBackgroundMode = block.sectionBackgroundMode === "none" || block.sectionBackgroundMode === "color" || block.sectionBackgroundMode === "gradient" ? block.sectionBackgroundMode : current.appearance.sectionBackgroundMode;
2847
+ current.appearance.sectionBackgroundColor = typeof block.sectionBackgroundColor === "string" ? block.sectionBackgroundColor : current.appearance.sectionBackgroundColor;
2848
+ current.appearance.sectionGradientPreset = typeof block.sectionGradientPreset === "string" ? block.sectionGradientPreset : current.appearance.sectionGradientPreset;
2849
+ current.appearance.sectionGradientFrom = typeof block.sectionGradientFrom === "string" ? block.sectionGradientFrom : current.appearance.sectionGradientFrom;
2850
+ current.appearance.sectionGradientTo = typeof block.sectionGradientTo === "string" ? block.sectionGradientTo : current.appearance.sectionGradientTo;
2851
+ current.appearance.sectionGradientAngle = typeof block.sectionGradientAngle === "string" ? block.sectionGradientAngle : current.appearance.sectionGradientAngle;
2852
+ current.appearance.contentBackgroundMode = block.contentBackgroundMode === "none" || block.contentBackgroundMode === "color" || block.contentBackgroundMode === "gradient" ? block.contentBackgroundMode : current.appearance.contentBackgroundMode;
2853
+ current.appearance.contentBackgroundColor = typeof block.contentBackgroundColor === "string" ? block.contentBackgroundColor : current.appearance.contentBackgroundColor;
2854
+ current.appearance.contentGradientPreset = typeof block.contentGradientPreset === "string" ? block.contentGradientPreset : current.appearance.contentGradientPreset;
2855
+ current.appearance.contentGradientFrom = typeof block.contentGradientFrom === "string" ? block.contentGradientFrom : current.appearance.contentGradientFrom;
2856
+ current.appearance.contentGradientTo = typeof block.contentGradientTo === "string" ? block.contentGradientTo : current.appearance.contentGradientTo;
2857
+ current.appearance.contentGradientAngle = typeof block.contentGradientAngle === "string" ? block.contentGradientAngle : current.appearance.contentGradientAngle;
2858
+ if (block.backgroundImageFit === "cover" || block.backgroundImageFit === "contain") {
2859
+ current.media.fit = block.backgroundImageFit;
2860
+ }
2861
+ if (block.imageFit === "cover" || block.imageFit === "contain") {
2862
+ current.media.fit = block.imageFit;
2863
+ }
2864
+ if (block.backgroundImageCornerStyle === "rounded" || block.backgroundImageCornerStyle === "square") {
2865
+ current.media.cornerStyle = block.backgroundImageCornerStyle;
2866
+ }
2867
+ if (block.imageCornerStyle === "rounded" || block.imageCornerStyle === "square") {
2868
+ current.media.cornerStyle = block.imageCornerStyle;
2869
+ }
2870
+ if (block.backgroundImagePosition === "top" || block.backgroundImagePosition === "bottom" || block.backgroundImagePosition === "left" || block.backgroundImagePosition === "right" || block.backgroundImagePosition === "center") {
2871
+ current.media.position = block.backgroundImagePosition;
2872
+ }
2873
+ if (block.imagePosition === "top" || block.imagePosition === "bottom" || block.imagePosition === "left" || block.imagePosition === "right" || block.imagePosition === "center") {
2874
+ current.media.position = block.imagePosition;
2875
+ }
2876
+ current.media.positionX = parsePercent(block.imagePositionX ?? block.backgroundImagePositionX);
2877
+ current.media.positionY = parsePercent(block.imagePositionY ?? block.backgroundImagePositionY);
2878
+ current.media.height = parsePixel(block.imageHeight);
2879
+ current.typography.headingAlign = block.textHeadingAlign === "left" || block.textHeadingAlign === "center" || block.textHeadingAlign === "right" || block.textHeadingAlign === "justify" ? block.textHeadingAlign : current.typography.headingAlign;
2880
+ current.typography.bodyAlign = block.textBodyAlign === "left" || block.textBodyAlign === "center" || block.textBodyAlign === "right" || block.textBodyAlign === "justify" ? block.textBodyAlign : current.typography.bodyAlign;
2881
+ current.typography.maxTextWidth = block.textMaxWidth === "auto" || block.textMaxWidth === "sm" || block.textMaxWidth === "md" || block.textMaxWidth === "lg" || block.textMaxWidth === "full" ? block.textMaxWidth : current.typography.maxTextWidth;
2882
+ current.typography.lineHeightPreset = block.textLineHeightPreset === "tight" || block.textLineHeightPreset === "normal" || block.textLineHeightPreset === "relaxed" ? block.textLineHeightPreset : current.typography.lineHeightPreset;
2883
+ current.typography.letterSpacingPreset = block.textLetterSpacingPreset === "tight" || block.textLetterSpacingPreset === "normal" || block.textLetterSpacingPreset === "relaxed" ? block.textLetterSpacingPreset : current.typography.letterSpacingPreset;
2884
+ current.advanced.editCopyInPanel = Boolean(block.editCopyInPanel ?? current.advanced.editCopyInPanel);
2885
+ current.advanced.customClassName = typeof block.customClassName === "string" ? block.customClassName : current.advanced.customClassName;
2886
+ current.advanced.hideOnMobile = Boolean(block.hideOnMobile ?? current.advanced.hideOnMobile);
2887
+ return mergeSettings(current, block.settings);
2888
+ };
2889
+ var v2SettingsToLegacyBlock = (blockWithSettings) => {
2890
+ const settings = legacyBlockToV2Settings(blockWithSettings);
2891
+ const next = {
2892
+ ...blockWithSettings,
2893
+ settings
2894
+ };
2895
+ next.contentWidth = settings.layout.contentWidth;
2896
+ next.sectionPaddingX = settings.layout.sectionPaddingX;
2897
+ next.sectionPaddingY = settings.layout.sectionPaddingY;
2898
+ next.sectionBackgroundMode = settings.appearance.sectionBackgroundMode;
2899
+ next.sectionBackgroundColor = settings.appearance.sectionBackgroundColor;
2900
+ next.sectionGradientPreset = settings.appearance.sectionGradientPreset;
2901
+ next.sectionGradientFrom = settings.appearance.sectionGradientFrom;
2902
+ next.sectionGradientTo = settings.appearance.sectionGradientTo;
2903
+ next.sectionGradientAngle = settings.appearance.sectionGradientAngle;
2904
+ next.contentBackgroundMode = settings.appearance.contentBackgroundMode;
2905
+ next.contentBackgroundColor = settings.appearance.contentBackgroundColor;
2906
+ next.contentGradientPreset = settings.appearance.contentGradientPreset;
2907
+ next.contentGradientFrom = settings.appearance.contentGradientFrom;
2908
+ next.contentGradientTo = settings.appearance.contentGradientTo;
2909
+ next.contentGradientAngle = settings.appearance.contentGradientAngle;
2910
+ next.textHeadingAlign = settings.typography.headingAlign;
2911
+ next.textBodyAlign = settings.typography.bodyAlign;
2912
+ next.textMaxWidth = settings.typography.maxTextWidth;
2913
+ next.textLineHeightPreset = settings.typography.lineHeightPreset;
2914
+ next.textLetterSpacingPreset = settings.typography.letterSpacingPreset;
2915
+ next.editCopyInPanel = settings.advanced.editCopyInPanel;
2916
+ next.customClassName = settings.advanced.customClassName;
2917
+ next.hideOnMobile = settings.advanced.hideOnMobile;
2918
+ if (next.blockType === "hero") {
2919
+ next.backgroundImageFit = settings.media.fit;
2920
+ next.backgroundImageCornerStyle = settings.media.cornerStyle;
2921
+ next.backgroundImagePosition = settings.media.position;
2922
+ next.backgroundImagePositionX = settings.media.positionX;
2923
+ next.backgroundImagePositionY = settings.media.positionY;
2924
+ } else {
2925
+ next.imageFit = settings.media.fit;
2926
+ next.imageCornerStyle = settings.media.cornerStyle;
2927
+ next.imagePosition = settings.media.position;
2928
+ next.imagePositionX = settings.media.positionX;
2929
+ next.imagePositionY = settings.media.positionY;
2930
+ next.imageHeight = settings.media.height;
2931
+ }
2932
+ if (Array.isArray(next.items)) {
2933
+ next.items = next.items.map((rawItem) => isRecord2(rawItem) ? v2SettingsToLegacyItem(rawItem) : rawItem);
2934
+ }
2935
+ return next;
2936
+ };
2937
+ var legacyItemToV2Settings = (item) => {
2938
+ const current = structuredClone(defaultBuilderItemSettingsV2);
2939
+ if (item.imageFit === "cover" || item.imageFit === "contain") {
2940
+ current.media.fit = item.imageFit;
2941
+ }
2942
+ if (item.imageCornerStyle === "rounded" || item.imageCornerStyle === "square") {
2943
+ current.media.cornerStyle = item.imageCornerStyle;
2944
+ }
2945
+ if (item.imagePosition === "top" || item.imagePosition === "bottom" || item.imagePosition === "left" || item.imagePosition === "right" || item.imagePosition === "center") {
2946
+ current.media.position = item.imagePosition;
2947
+ }
2948
+ current.media.positionX = parsePercent(item.imagePositionX);
2949
+ current.media.positionY = parsePercent(item.imagePositionY);
2950
+ current.media.height = parsePixel(item.imageHeight);
2951
+ return mergeSettings(current, item.settings);
2952
+ };
2953
+ var v2SettingsToLegacyItem = (itemWithSettings) => {
2954
+ const settings = legacyItemToV2Settings(itemWithSettings);
2955
+ return {
2956
+ ...itemWithSettings,
2957
+ imageCornerStyle: settings.media.cornerStyle,
2958
+ imageFit: settings.media.fit,
2959
+ imageHeight: settings.media.height,
2960
+ imagePosition: settings.media.position,
2961
+ imagePositionX: settings.media.positionX,
2962
+ imagePositionY: settings.media.positionY,
2963
+ settings
2964
+ };
2965
+ };
2966
+ var migrateBlockToSettingsV2 = (block) => {
2967
+ const withLegacyMirrors = v2SettingsToLegacyBlock(block);
2968
+ if (!Array.isArray(withLegacyMirrors.items)) {
2969
+ return withLegacyMirrors;
2970
+ }
2971
+ return {
2972
+ ...withLegacyMirrors,
2973
+ items: withLegacyMirrors.items.map((rawItem) => isRecord2(rawItem) ? v2SettingsToLegacyItem(rawItem) : rawItem)
2974
+ };
2975
+ };
2976
+
2977
+ // src/studio-pages/builder/settings-v2/themeTokens.ts
2978
+ var isRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
2979
+ var merge = (base, next) => {
2980
+ if (!next || !isRecord3(next)) {
2981
+ return base;
2982
+ }
2983
+ const merged = { ...base };
2984
+ for (const [key, value] of Object.entries(next)) {
2985
+ if (isRecord3(value) && isRecord3(merged[key])) {
2986
+ merged[key] = merge(merged[key], value);
2987
+ continue;
2988
+ }
2989
+ if (typeof value !== "undefined") {
2990
+ merged[key] = value;
2991
+ }
2992
+ }
2993
+ return merged;
2994
+ };
2995
+ var resolveBuilderThemeTokens = (layers) => {
2996
+ const withSite = merge(defaultBuilderThemeTokens, layers.site);
2997
+ const withPage = merge(withSite, layers.page);
2998
+ return merge(withPage, layers.block);
2999
+ };
3000
+
3001
+ // src/studio-pages/builder/settings-v2/BlockInspectorRenderer.tsx
3002
+ var import_react = require("react");
3003
+
3004
+ // src/studio-pages/builder/ui/Accordion.tsx
3005
+ var import_jsx_runtime4 = require("react/jsx-runtime");
3006
+
3007
+ // src/studio-pages/builder/ui/ImageControls.tsx
3008
+ var import_jsx_runtime5 = require("react/jsx-runtime");
3009
+
3010
+ // src/studio-pages/builder/settings-v2/inspectorSchema.ts
3011
+ var alignOptions = [
3012
+ { label: "Left", value: "left" },
3013
+ { label: "Center", value: "center" },
3014
+ { label: "Right", value: "right" },
3015
+ { label: "Justify", value: "justify" }
3016
+ ];
3017
+ var layoutFieldSet = [
3018
+ {
3019
+ group: "layout",
3020
+ key: "settings.layout.contentWidth",
3021
+ label: "Content Width",
3022
+ options: [
3023
+ { label: "Inherit Page Default", value: "inherit" },
3024
+ { label: "Narrow", value: "narrow" },
3025
+ { label: "Content", value: "content" },
3026
+ { label: "Wide", value: "wide" },
3027
+ { label: "Full", value: "full" }
3028
+ ],
3029
+ tags: ["width", "container"],
3030
+ type: "select"
3031
+ },
3032
+ {
3033
+ group: "layout",
3034
+ key: "settings.layout.sectionPaddingY",
3035
+ label: "Section Vertical Spacing",
3036
+ options: [
3037
+ { label: "None", value: "none" },
3038
+ { label: "Small", value: "sm" },
3039
+ { label: "Medium", value: "md" },
3040
+ { label: "Large", value: "lg" }
3041
+ ],
3042
+ tags: ["spacing", "padding"],
3043
+ type: "select"
3044
+ },
3045
+ {
3046
+ group: "layout",
3047
+ key: "settings.layout.sectionPaddingX",
3048
+ label: "Section Horizontal Spacing",
3049
+ options: [
3050
+ { label: "Inherit", value: "inherit" },
3051
+ { label: "None", value: "none" },
3052
+ { label: "Small", value: "sm" },
3053
+ { label: "Medium", value: "md" },
3054
+ { label: "Large", value: "lg" }
3055
+ ],
3056
+ tags: ["spacing", "padding"],
3057
+ type: "select"
3058
+ }
3059
+ ];
3060
+ var typographyFieldSet = [
3061
+ {
3062
+ group: "typography",
3063
+ key: "settings.typography.headingAlign",
3064
+ label: "Heading Alignment",
3065
+ options: alignOptions,
3066
+ tags: ["text", "align", "heading"],
3067
+ type: "select"
3068
+ },
3069
+ {
3070
+ group: "typography",
3071
+ key: "settings.typography.bodyAlign",
3072
+ label: "Body Alignment",
3073
+ options: alignOptions,
3074
+ tags: ["text", "align", "paragraph"],
3075
+ type: "select"
3076
+ },
3077
+ {
3078
+ group: "typography",
3079
+ key: "settings.typography.maxTextWidth",
3080
+ label: "Text Width",
3081
+ options: [
3082
+ { label: "Auto", value: "auto" },
3083
+ { label: "Small", value: "sm" },
3084
+ { label: "Medium", value: "md" },
3085
+ { label: "Large", value: "lg" },
3086
+ { label: "Full", value: "full" }
3087
+ ],
3088
+ tags: ["readability", "measure", "line length"],
3089
+ type: "select"
3090
+ },
3091
+ {
3092
+ advanced: true,
3093
+ group: "typography",
3094
+ key: "settings.typography.lineHeightPreset",
3095
+ label: "Line Height",
3096
+ options: [
3097
+ { label: "Tight", value: "tight" },
3098
+ { label: "Normal", value: "normal" },
3099
+ { label: "Relaxed", value: "relaxed" }
3100
+ ],
3101
+ type: "select"
3102
+ },
3103
+ {
3104
+ advanced: true,
3105
+ group: "typography",
3106
+ key: "settings.typography.letterSpacingPreset",
3107
+ label: "Letter Spacing",
3108
+ options: [
3109
+ { label: "Tight", value: "tight" },
3110
+ { label: "Normal", value: "normal" },
3111
+ { label: "Relaxed", value: "relaxed" }
3112
+ ],
3113
+ type: "select"
3114
+ }
3115
+ ];
3116
+ var styleFieldSet = [
3117
+ {
3118
+ group: "style",
3119
+ key: "settings.appearance.sectionBackgroundMode",
3120
+ label: "Section Background",
3121
+ options: [
3122
+ { label: "None", value: "none" },
3123
+ { label: "Color", value: "color" },
3124
+ { label: "Gradient", value: "gradient" }
3125
+ ],
3126
+ tags: ["background", "section"],
3127
+ type: "select"
3128
+ },
3129
+ {
3130
+ group: "style",
3131
+ key: "settings.appearance.sectionBackgroundColor",
3132
+ label: "Section Background Color",
3133
+ type: "color"
3134
+ },
3135
+ {
3136
+ group: "style",
3137
+ key: "settings.appearance.contentBackgroundMode",
3138
+ label: "Content Background",
3139
+ options: [
3140
+ { label: "None", value: "none" },
3141
+ { label: "Color", value: "color" },
3142
+ { label: "Gradient", value: "gradient" }
3143
+ ],
3144
+ tags: ["background", "content"],
3145
+ type: "select"
3146
+ },
3147
+ {
3148
+ group: "style",
3149
+ key: "settings.appearance.contentBackgroundColor",
3150
+ label: "Content Background Color",
3151
+ type: "color"
3152
+ }
3153
+ ];
3154
+ var commonAdvanced = [
3155
+ {
3156
+ group: "advanced",
3157
+ inlineEditable: false,
3158
+ key: "settings.advanced.editCopyInPanel",
3159
+ label: "Edit Copy In Panel",
3160
+ tags: ["inline", "copy", "text"],
3161
+ type: "checkbox"
3162
+ },
3163
+ {
3164
+ advanced: true,
3165
+ group: "advanced",
3166
+ key: "settings.advanced.hideOnMobile",
3167
+ label: "Hide On Mobile",
3168
+ type: "checkbox"
3169
+ },
3170
+ {
3171
+ advanced: true,
3172
+ group: "advanced",
3173
+ key: "settings.advanced.customClassName",
3174
+ label: "Custom Class Name",
3175
+ type: "text"
3176
+ }
3177
+ ];
3178
+ var mediaFieldSet = [
3179
+ {
3180
+ group: "media",
3181
+ key: "settings.media.fit",
3182
+ label: "Image Fit",
3183
+ options: [
3184
+ { label: "Cover", value: "cover" },
3185
+ { label: "Contain", value: "contain" }
3186
+ ],
3187
+ tags: ["image", "media"],
3188
+ type: "select"
3189
+ },
3190
+ {
3191
+ group: "media",
3192
+ key: "settings.media.cornerStyle",
3193
+ label: "Image Corners",
3194
+ options: [
3195
+ { label: "Rounded", value: "rounded" },
3196
+ { label: "Square", value: "square" }
3197
+ ],
3198
+ tags: ["image", "radius", "corners"],
3199
+ type: "select"
3200
+ },
3201
+ {
3202
+ group: "media",
3203
+ key: "settings.media.position",
3204
+ label: "Image Position",
3205
+ options: [
3206
+ { label: "Center", value: "center" },
3207
+ { label: "Top", value: "top" },
3208
+ { label: "Bottom", value: "bottom" },
3209
+ { label: "Left", value: "left" },
3210
+ { label: "Right", value: "right" }
3211
+ ],
3212
+ type: "select"
3213
+ }
3214
+ ];
3215
+ var inspectorDefinitionByBlockType = {
3216
+ beforeAfter: {
3217
+ blockType: "beforeAfter",
3218
+ fields: [
3219
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3220
+ { group: "basics", inlineEditable: true, key: "subtitle", label: "Subtitle", type: "textarea" },
3221
+ { group: "basics", key: "itemsPerRow", label: "Items Per Row", max: 4, min: 1, type: "number" },
3222
+ ...layoutFieldSet,
3223
+ ...typographyFieldSet,
3224
+ ...styleFieldSet,
3225
+ ...commonAdvanced
3226
+ ]
3227
+ },
3228
+ bookingEmbed: {
3229
+ blockType: "bookingEmbed",
3230
+ fields: [
3231
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3232
+ { group: "basics", inlineEditable: true, key: "description", label: "Description", type: "textarea" },
3233
+ { group: "basics", inlineEditable: false, key: "buttonLabel", label: "Button Label", type: "text" },
3234
+ { group: "basics", inlineEditable: false, key: "buttonHref", label: "Button URL", type: "text" },
3235
+ ...layoutFieldSet,
3236
+ ...typographyFieldSet,
3237
+ ...styleFieldSet,
3238
+ ...commonAdvanced
3239
+ ]
3240
+ },
3241
+ cta: {
3242
+ blockType: "cta",
3243
+ fields: [
3244
+ { group: "basics", inlineEditable: true, key: "headline", label: "Headline", type: "text" },
3245
+ { group: "basics", inlineEditable: true, key: "description", label: "Description", type: "textarea" },
3246
+ { group: "basics", inlineEditable: false, key: "buttonLabel", label: "Button Label", type: "text" },
3247
+ { group: "basics", inlineEditable: false, key: "buttonHref", label: "Button URL", type: "text" },
3248
+ {
3249
+ group: "basics",
3250
+ key: "style",
3251
+ label: "Variant",
3252
+ options: [
3253
+ { label: "Light", value: "light" },
3254
+ { label: "Dark", value: "dark" }
3255
+ ],
3256
+ type: "select"
3257
+ },
3258
+ ...layoutFieldSet,
3259
+ ...typographyFieldSet,
3260
+ ...styleFieldSet,
3261
+ ...commonAdvanced
3262
+ ]
3263
+ },
3264
+ faq: {
3265
+ blockType: "faq",
3266
+ fields: [
3267
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3268
+ ...layoutFieldSet,
3269
+ ...typographyFieldSet,
3270
+ ...styleFieldSet,
3271
+ ...commonAdvanced
3272
+ ]
3273
+ },
3274
+ featureGrid: {
3275
+ blockType: "featureGrid",
3276
+ fields: [
3277
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3278
+ {
3279
+ group: "basics",
3280
+ key: "variant",
3281
+ label: "Variant",
3282
+ options: [
3283
+ { label: "Cards", value: "cards" },
3284
+ { label: "Highlight", value: "highlight" }
3285
+ ],
3286
+ type: "select"
3287
+ },
3288
+ { group: "basics", key: "itemsPerRow", label: "Items Per Row", max: 6, min: 1, type: "number" },
3289
+ ...layoutFieldSet,
3290
+ ...typographyFieldSet,
3291
+ ...mediaFieldSet,
3292
+ ...styleFieldSet,
3293
+ ...commonAdvanced
3294
+ ]
3295
+ },
3296
+ formEmbed: {
3297
+ blockType: "formEmbed",
3298
+ fields: [
3299
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3300
+ { group: "basics", inlineEditable: true, key: "description", label: "Description", type: "textarea" },
3301
+ {
3302
+ group: "basics",
3303
+ key: "formType",
3304
+ label: "Form Type",
3305
+ options: [{ label: "Quote", value: "quote" }],
3306
+ type: "select"
3307
+ },
3308
+ ...layoutFieldSet,
3309
+ ...typographyFieldSet,
3310
+ ...styleFieldSet,
3311
+ ...commonAdvanced
3312
+ ]
3313
+ },
3314
+ hero: {
3315
+ blockType: "hero",
3316
+ fields: [
3317
+ { group: "basics", inlineEditable: true, key: "kicker", label: "Kicker", type: "text" },
3318
+ { group: "basics", inlineEditable: true, key: "headline", label: "Headline", type: "text" },
3319
+ { group: "basics", inlineEditable: true, key: "subheadline", label: "Subheadline", type: "textarea" },
3320
+ {
3321
+ group: "basics",
3322
+ key: "variant",
3323
+ label: "Variant",
3324
+ options: [
3325
+ { label: "Default", value: "default" },
3326
+ { label: "Centered", value: "centered" }
3327
+ ],
3328
+ type: "select"
3329
+ },
3330
+ {
3331
+ group: "layout",
3332
+ key: "heroHeight",
3333
+ label: "Hero Height",
3334
+ options: [
3335
+ { label: "Small", value: "sm" },
3336
+ { label: "Medium (Half Screen)", value: "md" },
3337
+ { label: "Full Screen", value: "full" }
3338
+ ],
3339
+ type: "select"
3340
+ },
3341
+ ...layoutFieldSet,
3342
+ ...typographyFieldSet,
3343
+ ...mediaFieldSet,
3344
+ ...styleFieldSet,
3345
+ ...commonAdvanced
3346
+ ]
3347
+ },
3348
+ logoWall: {
3349
+ blockType: "logoWall",
3350
+ fields: [
3351
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3352
+ { group: "basics", inlineEditable: true, key: "subtitle", label: "Subtitle", type: "textarea" },
3353
+ ...layoutFieldSet,
3354
+ ...typographyFieldSet,
3355
+ ...mediaFieldSet,
3356
+ ...styleFieldSet,
3357
+ ...commonAdvanced
3358
+ ]
3359
+ },
3360
+ media: {
3361
+ blockType: "media",
3362
+ fields: [
3363
+ { group: "basics", inlineEditable: true, key: "caption", label: "Caption", type: "text" },
3364
+ {
3365
+ group: "basics",
3366
+ key: "size",
3367
+ label: "Size",
3368
+ options: [
3369
+ { label: "Default", value: "default" },
3370
+ { label: "Wide", value: "wide" }
3371
+ ],
3372
+ type: "select"
3373
+ },
3374
+ ...layoutFieldSet,
3375
+ ...typographyFieldSet,
3376
+ ...mediaFieldSet,
3377
+ ...styleFieldSet,
3378
+ ...commonAdvanced
3379
+ ]
3380
+ },
3381
+ richText: {
3382
+ blockType: "richText",
3383
+ fields: [
3384
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3385
+ {
3386
+ group: "basics",
3387
+ key: "width",
3388
+ label: "Rich Text Width",
3389
+ options: [
3390
+ { label: "Normal", value: "normal" },
3391
+ { label: "Narrow", value: "narrow" }
3392
+ ],
3393
+ type: "select"
3394
+ },
3395
+ ...layoutFieldSet,
3396
+ ...typographyFieldSet,
3397
+ ...styleFieldSet,
3398
+ ...commonAdvanced
3399
+ ]
3400
+ },
3401
+ stats: {
3402
+ blockType: "stats",
3403
+ fields: [
3404
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3405
+ { group: "basics", inlineEditable: true, key: "subtitle", label: "Subtitle", type: "textarea" },
3406
+ ...layoutFieldSet,
3407
+ ...typographyFieldSet,
3408
+ ...styleFieldSet,
3409
+ ...commonAdvanced
3410
+ ]
3411
+ },
3412
+ testimonials: {
3413
+ blockType: "testimonials",
3414
+ fields: [
3415
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3416
+ { group: "basics", key: "visibleCount", label: "Visible At Once", max: 6, min: 1, type: "number" },
3417
+ { group: "basics", key: "autoRotate", label: "Auto Rotate", type: "checkbox" },
3418
+ {
3419
+ advanced: true,
3420
+ group: "advanced",
3421
+ key: "rotateIntervalSeconds",
3422
+ label: "Rotate Interval Seconds",
3423
+ max: 30,
3424
+ min: 2,
3425
+ type: "number"
3426
+ },
3427
+ ...layoutFieldSet,
3428
+ ...typographyFieldSet,
3429
+ ...styleFieldSet,
3430
+ ...commonAdvanced
3431
+ ]
3432
+ }
3433
+ };
3434
+
3435
+ // src/studio-pages/builder/settings-v2/BlockInspectorRenderer.tsx
3436
+ var import_jsx_runtime6 = require("react/jsx-runtime");
3437
+
3438
+ // src/studio-pages/migrations.ts
3439
+ var isRecord4 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
3440
+ var assertPageStudioDocumentV1 = (value) => {
3441
+ if (!isRecord4(value)) {
3442
+ throw new Error("Studio document must be an object");
3443
+ }
3444
+ if (value.schemaVersion !== 1) {
3445
+ throw new Error("Unsupported studio schemaVersion");
3446
+ }
3447
+ if (!Array.isArray(value.nodes)) {
3448
+ throw new Error("Studio document nodes must be an array");
3449
+ }
3450
+ const nodes = value.nodes.map((node, index) => {
3451
+ if (!isRecord4(node)) {
3452
+ throw new Error(`Node at index ${index} must be an object`);
3453
+ }
3454
+ if (typeof node.id !== "string" || node.id.length === 0) {
3455
+ throw new Error(`Node at index ${index} has invalid id`);
3456
+ }
3457
+ if (typeof node.type !== "string" || node.type.length === 0) {
3458
+ throw new Error(`Node at index ${index} has invalid type`);
3459
+ }
3460
+ if (!isRecord4(node.data)) {
3461
+ throw new Error(`Node at index ${index} has invalid data`);
3462
+ }
3463
+ return {
3464
+ data: node.data,
3465
+ id: node.id,
3466
+ type: node.type
1826
3467
  };
1827
3468
  });
1828
3469
  return {
1829
- issues,
1830
- layout
3470
+ metadata: isRecord4(value.metadata) ? value.metadata : void 0,
3471
+ nodes,
3472
+ schemaVersion: 1,
3473
+ title: typeof value.title === "string" ? value.title : void 0,
3474
+ updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : void 0
1831
3475
  };
1832
- }
1833
- function migrateStudioDocument(value, migrations) {
1834
- const sorted = [...migrations].sort((a, b) => a.fromVersion - b.fromVersion);
1835
- if (isRecord(value) && value.schemaVersion === 1) {
1836
- return assertStudioDocumentV1(value);
1837
- }
1838
- let current = value;
1839
- for (const migration of sorted) {
1840
- if (!isRecord(current) || current.schemaVersion !== migration.fromVersion) {
1841
- continue;
1842
- }
1843
- current = migration.migrate(current);
3476
+ };
3477
+ var migratePageNodeToSettingsV2 = (node) => {
3478
+ const normalized = migrateBlockToSettingsV2({
3479
+ blockType: node.type,
3480
+ ...node.data
3481
+ });
3482
+ const { blockType: _ignoredBlockType, ...data } = normalized;
3483
+ return {
3484
+ ...node,
3485
+ data
3486
+ };
3487
+ };
3488
+ var migratePageDocumentSettingsToV2 = (value) => {
3489
+ const current = assertPageStudioDocumentV1(value);
3490
+ return {
3491
+ ...current,
3492
+ nodes: current.nodes.map(migratePageNodeToSettingsV2)
3493
+ };
3494
+ };
3495
+ var pageStudioMigrations = [
3496
+ {
3497
+ fromVersion: 1,
3498
+ migrate: migratePageDocumentSettingsToV2,
3499
+ toVersion: 1
1844
3500
  }
1845
- return assertStudioDocumentV1(current);
1846
- }
3501
+ ];
1847
3502
 
1848
3503
  // src/studio-pages/index.ts
1849
- var studio_pages_exports = {};
1850
- __export(studio_pages_exports, {
1851
- createDefaultStudioDocument: () => createDefaultStudioDocument,
1852
- layoutToStudioDocument: () => layoutToStudioDocument,
1853
- pageInspectorPanels: () => pageInspectorPanels,
1854
- pageNodeTypes: () => pageNodeTypes,
1855
- pagePaletteGroups: () => pagePaletteGroups,
1856
- pageStudioModuleManifest: () => pageStudioModuleManifest,
1857
- studioDocumentToLayout: () => studioDocumentToLayout
1858
- });
1859
3504
  var withSectionStyleDefaults = (value) => ({
1860
3505
  ...sectionStyleDefaults,
1861
3506
  ...value
@@ -1869,9 +3514,13 @@ var defaultNodeData = {
1869
3514
  title: "Book a Time"
1870
3515
  },
1871
3516
  beforeAfter: withSectionStyleDefaults({
3517
+ itemsPerRow: 2,
1872
3518
  items: [
1873
3519
  {
1874
3520
  description: "Before and after result summary.",
3521
+ imageCornerStyle: "rounded",
3522
+ imageFit: "cover",
3523
+ imagePosition: "center",
1875
3524
  label: "Project One"
1876
3525
  }
1877
3526
  ],
@@ -1894,10 +3543,11 @@ var defaultNodeData = {
1894
3543
  },
1895
3544
  featureGrid: {
1896
3545
  ...withSectionStyleDefaults({}),
3546
+ itemsPerRow: 3,
1897
3547
  items: [
1898
- { description: "Explain this point.", icon: "01", title: "Feature One" },
1899
- { description: "Explain this point.", icon: "02", title: "Feature Two" },
1900
- { description: "Explain this point.", icon: "03", title: "Feature Three" }
3548
+ { description: "Explain this point.", iconType: "badge", icon: "01", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature One" },
3549
+ { description: "Explain this point.", iconType: "badge", icon: "02", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature Two" },
3550
+ { description: "Explain this point.", iconType: "badge", icon: "03", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature Three" }
1901
3551
  ],
1902
3552
  title: "Section Title",
1903
3553
  variant: "cards"
@@ -1911,6 +3561,21 @@ var defaultNodeData = {
1911
3561
  hero: {
1912
3562
  ...withSectionStyleDefaults({}),
1913
3563
  backgroundColor: "",
3564
+ backgroundOverlayMode: "none",
3565
+ backgroundOverlayOpacity: 45,
3566
+ backgroundOverlayColor: "#000000",
3567
+ backgroundOverlayGradientFrom: "#0d4a37",
3568
+ backgroundOverlayGradientTo: "#1f684f",
3569
+ backgroundOverlayGradientAngle: "135",
3570
+ backgroundOverlayGradientFromStrength: 100,
3571
+ backgroundOverlayGradientToStrength: 100,
3572
+ backgroundOverlayGradientStart: 0,
3573
+ backgroundOverlayGradientEnd: 100,
3574
+ backgroundOverlayGradientFeather: 100,
3575
+ backgroundImageCornerStyle: "rounded",
3576
+ backgroundImageFit: "cover",
3577
+ backgroundImagePosition: "center",
3578
+ heroHeight: "sm",
1914
3579
  headline: "New Hero Section",
1915
3580
  kicker: "Optional kicker",
1916
3581
  primaryHref: "/contact",
@@ -1923,13 +3588,16 @@ var defaultNodeData = {
1923
3588
  media: {
1924
3589
  ...withSectionStyleDefaults({}),
1925
3590
  caption: "Add a caption",
3591
+ imageCornerStyle: "rounded",
3592
+ imageFit: "cover",
3593
+ imagePosition: "center",
1926
3594
  size: "default"
1927
3595
  },
1928
3596
  logoWall: withSectionStyleDefaults({
1929
3597
  items: [
1930
- { name: "Trusted Partner 1" },
1931
- { name: "Trusted Partner 2" },
1932
- { name: "Trusted Partner 3" }
3598
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 1" },
3599
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 2" },
3600
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 3" }
1933
3601
  ],
1934
3602
  subtitle: "Trusted by teams and homeowners across Central Texas.",
1935
3603
  title: "Trusted by Local Organizations"
@@ -1970,8 +3638,11 @@ var defaultNodeData = {
1970
3638
  },
1971
3639
  testimonials: {
1972
3640
  ...withSectionStyleDefaults({}),
1973
- items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here." }],
1974
- title: "What Customers Say"
3641
+ autoRotate: true,
3642
+ items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here.", rating: 5 }],
3643
+ rotateIntervalSeconds: 7,
3644
+ title: "What Customers Say",
3645
+ visibleCount: 3
1975
3646
  },
1976
3647
  stats: withSectionStyleDefaults({
1977
3648
  items: [
@@ -2001,7 +3672,11 @@ var pageNodeTypes = Object.keys(defaultNodeData).map((type) => ({
2001
3672
  type,
2002
3673
  displayName: nodeTypeLabels[type] || type,
2003
3674
  description: `Page node for ${nodeTypeLabels[type] || type}`,
2004
- getDefaultData: () => structuredClone(defaultNodeData[type])
3675
+ getDefaultData: () => {
3676
+ const migrated = migrateBlockToSettingsV2(structuredClone(defaultNodeData[type]));
3677
+ const { blockType: _ignoredBlockType, ...data } = migrated;
3678
+ return data;
3679
+ }
2005
3680
  }));
2006
3681
  var validatePageDocument = (document) => {
2007
3682
  const issues = [];
@@ -2053,17 +3728,24 @@ var pagePaletteGroups = [
2053
3728
  ]
2054
3729
  }
2055
3730
  ];
2056
- var pageInspectorPanels = Object.keys(defaultNodeData).map((nodeType) => ({
2057
- id: `${nodeType}-panel`,
2058
- label: `${nodeTypeLabels[nodeType] || nodeType} Settings`,
3731
+ var pageInspectorPanelRegistry = Object.keys(defaultNodeData).map((nodeType) => ({
2059
3732
  nodeType,
2060
- fields: [
2061
- {
2062
- key: "title",
2063
- label: "Title",
2064
- type: "text"
2065
- }
2066
- ]
3733
+ panelID: `${nodeType}-panel`,
3734
+ panelLabel: `${nodeTypeLabels[nodeType] || nodeType} Settings`
3735
+ }));
3736
+ var resolvePanelFields = (nodeType) => inspectorDefinitionByBlockType[nodeType]?.fields.map((field) => ({
3737
+ advanced: field.advanced,
3738
+ group: field.group,
3739
+ inlineEditable: field.inlineEditable,
3740
+ key: field.key,
3741
+ label: field.label,
3742
+ type: field.type
3743
+ })) || [];
3744
+ var pageInspectorPanels = pageInspectorPanelRegistry.map((entry) => ({
3745
+ fields: resolvePanelFields(entry.nodeType),
3746
+ id: entry.panelID,
3747
+ label: entry.panelLabel,
3748
+ nodeType: entry.nodeType
2067
3749
  }));
2068
3750
  var pageStudioModuleManifest = {
2069
3751
  id: "studio-pages",
@@ -2073,13 +3755,18 @@ var pageStudioModuleManifest = {
2073
3755
  paletteGroups: pagePaletteGroups,
2074
3756
  inspectorPanels: pageInspectorPanels,
2075
3757
  validators: [validatePageDocument],
2076
- migrations: [],
3758
+ migrations: pageStudioMigrations,
2077
3759
  compiler: {
2078
- compileNode: (node) => ({
2079
- id: node.id,
2080
- blockType: node.type,
2081
- ...node.data
2082
- })
3760
+ compileNode: (node) => {
3761
+ const normalized = migrateBlockToSettingsV2({
3762
+ blockType: node.type,
3763
+ ...node.data
3764
+ });
3765
+ return {
3766
+ id: node.id,
3767
+ ...normalized
3768
+ };
3769
+ }
2083
3770
  },
2084
3771
  permissions: [
2085
3772
  { action: "read", role: "admin" },
@@ -2099,9 +3786,10 @@ var ensureNodeID = (inputID, index) => {
2099
3786
  return `node-${index + 1}`;
2100
3787
  };
2101
3788
  var layoutToStudioDocument = (layout, title, metadata) => {
2102
- const nodes = layout.filter((block) => typeof block.blockType === "string").map((block, index) => {
3789
+ const nodes = layout.filter((block) => typeof block.blockType === "string").map((rawBlock, index) => {
3790
+ const block = migrateBlockToSettingsV2(rawBlock);
2103
3791
  const blockType = String(block.blockType);
2104
- const { id, blockType: _, ...data } = block;
3792
+ const { id, blockType: _ignoredBlockType, ...data } = block;
2105
3793
  return {
2106
3794
  id: ensureNodeID(id, index),
2107
3795
  type: blockType,
@@ -2116,11 +3804,13 @@ var layoutToStudioDocument = (layout, title, metadata) => {
2116
3804
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2117
3805
  };
2118
3806
  };
2119
- var studioDocumentToLayout = (document) => document.nodes.map((node) => ({
2120
- id: node.id,
2121
- blockType: node.type,
2122
- ...node.data
2123
- }));
3807
+ var studioDocumentToLayout = (document) => document.nodes.map(
3808
+ (node) => migrateBlockToSettingsV2({
3809
+ id: node.id,
3810
+ blockType: node.type,
3811
+ ...node.data
3812
+ })
3813
+ );
2124
3814
  var createDefaultStudioDocument = (title) => ({
2125
3815
  metadata: {},
2126
3816
  schemaVersion: 1,
@@ -2128,6 +3818,224 @@ var createDefaultStudioDocument = (title) => ({
2128
3818
  nodes: [],
2129
3819
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2130
3820
  });
3821
+
3822
+ // src/nextjs/queries/pages.ts
3823
+ var PAGE_QUERY_CACHE_VERSION = "v4-published-only-public";
3824
+ function withStudioDocumentLayout(page) {
3825
+ if (!page) {
3826
+ return null;
3827
+ }
3828
+ try {
3829
+ const studioDocument = assertStudioDocumentV1(page.studioDocument);
3830
+ const compiledLayout = studioDocumentToLayout(studioDocument);
3831
+ if (Array.isArray(compiledLayout) && compiledLayout.length > 0) {
3832
+ return {
3833
+ ...page,
3834
+ layout: compiledLayout
3835
+ };
3836
+ }
3837
+ } catch {
3838
+ }
3839
+ return page;
3840
+ }
3841
+ function normalizePath(segments) {
3842
+ if (!segments || segments.length === 0) {
3843
+ return "/";
3844
+ }
3845
+ const cleaned = segments.map((segment) => segment.trim()).filter(Boolean).join("/");
3846
+ return cleaned.length > 0 ? `/${cleaned}` : "/";
3847
+ }
3848
+ async function queryPageByPath(payload, path2, draft) {
3849
+ const pathWhere = {
3850
+ path: {
3851
+ equals: path2
3852
+ }
3853
+ };
3854
+ const publishedWhere = {
3855
+ _status: {
3856
+ equals: "published"
3857
+ }
3858
+ };
3859
+ const result = await payload.find({
3860
+ collection: "pages",
3861
+ depth: 2,
3862
+ draft,
3863
+ limit: 1,
3864
+ overrideAccess: false,
3865
+ where: draft ? pathWhere : { and: [pathWhere, publishedWhere] }
3866
+ });
3867
+ if (result.docs.length > 0) {
3868
+ return withStudioDocumentLayout(result.docs[0] || null);
3869
+ }
3870
+ if (path2 === "/") {
3871
+ const homeWhere = {
3872
+ slug: {
3873
+ equals: "home"
3874
+ }
3875
+ };
3876
+ const homeResult = await payload.find({
3877
+ collection: "pages",
3878
+ depth: 2,
3879
+ draft,
3880
+ limit: 1,
3881
+ overrideAccess: false,
3882
+ where: draft ? homeWhere : { and: [homeWhere, publishedWhere] }
3883
+ });
3884
+ return withStudioDocumentLayout(homeResult.docs[0] || null);
3885
+ }
3886
+ return null;
3887
+ }
3888
+ function createPageQueries(getPayloadClient, contentTag = "website-content") {
3889
+ const getPublishedPageByPathCached = (0, import_cache.unstable_cache)(
3890
+ async (path2) => {
3891
+ const payload = await getPayloadClient();
3892
+ return queryPageByPath(payload, path2, false);
3893
+ },
3894
+ ["page-by-path", PAGE_QUERY_CACHE_VERSION],
3895
+ { tags: [contentTag] }
3896
+ );
3897
+ async function getPageBySegments(segments, draft = false) {
3898
+ const path2 = normalizePath(segments);
3899
+ const payload = await getPayloadClient();
3900
+ if (draft) {
3901
+ return queryPageByPath(payload, path2, true);
3902
+ }
3903
+ return getPublishedPageByPathCached(path2);
3904
+ }
3905
+ async function listPublishedPagePaths() {
3906
+ const payload = await getPayloadClient();
3907
+ const pages = await payload.find({
3908
+ collection: "pages",
3909
+ depth: 0,
3910
+ draft: false,
3911
+ limit: 1e3,
3912
+ pagination: false,
3913
+ overrideAccess: false,
3914
+ where: {
3915
+ _status: {
3916
+ equals: "published"
3917
+ }
3918
+ }
3919
+ });
3920
+ return pages.docs.map((doc) => doc.path).filter((path2) => typeof path2 === "string" && path2.length > 0);
3921
+ }
3922
+ function pathToSegments(path2) {
3923
+ if (!path2 || path2 === "/") {
3924
+ return [];
3925
+ }
3926
+ return path2.split("/").filter(Boolean);
3927
+ }
3928
+ return {
3929
+ getPageBySegments,
3930
+ listPublishedPagePaths,
3931
+ pathToSegments
3932
+ };
3933
+ }
3934
+
3935
+ // src/nextjs/queries/site.ts
3936
+ var import_cache2 = require("next/cache");
3937
+ function createSiteQueries(getPayloadClient, contentTag = "website-content") {
3938
+ const getSiteSettingsCached = (0, import_cache2.unstable_cache)(
3939
+ async () => {
3940
+ const payload = await getPayloadClient();
3941
+ const settings = await payload.findGlobal({
3942
+ slug: "site-settings",
3943
+ depth: 1
3944
+ });
3945
+ return settings;
3946
+ },
3947
+ ["site-settings-global"],
3948
+ { tags: [contentTag] }
3949
+ );
3950
+ const getHeaderCached = (0, import_cache2.unstable_cache)(
3951
+ async () => {
3952
+ const payload = await getPayloadClient();
3953
+ const header = await payload.findGlobal({
3954
+ slug: "header",
3955
+ depth: 1
3956
+ });
3957
+ return header;
3958
+ },
3959
+ ["header-global"],
3960
+ { tags: [contentTag] }
3961
+ );
3962
+ const getFooterCached = (0, import_cache2.unstable_cache)(
3963
+ async () => {
3964
+ const payload = await getPayloadClient();
3965
+ const footer = await payload.findGlobal({
3966
+ slug: "footer",
3967
+ depth: 1
3968
+ });
3969
+ return footer;
3970
+ },
3971
+ ["footer-global"],
3972
+ { tags: [contentTag] }
3973
+ );
3974
+ async function getSiteSettings(draft = false) {
3975
+ if (draft) {
3976
+ const payload = await getPayloadClient();
3977
+ const settings = await payload.findGlobal({
3978
+ slug: "site-settings",
3979
+ depth: 1,
3980
+ draft: true
3981
+ });
3982
+ return settings;
3983
+ }
3984
+ return getSiteSettingsCached();
3985
+ }
3986
+ async function getHeader(draft = false) {
3987
+ if (draft) {
3988
+ const payload = await getPayloadClient();
3989
+ const header = await payload.findGlobal({
3990
+ slug: "header",
3991
+ depth: 1,
3992
+ draft: true
3993
+ });
3994
+ return header;
3995
+ }
3996
+ return getHeaderCached();
3997
+ }
3998
+ async function getFooter(draft = false) {
3999
+ if (draft) {
4000
+ const payload = await getPayloadClient();
4001
+ const footer = await payload.findGlobal({
4002
+ slug: "footer",
4003
+ depth: 1,
4004
+ draft: true
4005
+ });
4006
+ return footer;
4007
+ }
4008
+ return getFooterCached();
4009
+ }
4010
+ return {
4011
+ getSiteSettings,
4012
+ getHeader,
4013
+ getFooter
4014
+ };
4015
+ }
4016
+
4017
+ // src/nextjs/utilities/media.ts
4018
+ function resolveMedia(media) {
4019
+ if (!media) {
4020
+ return null;
4021
+ }
4022
+ if (typeof media === "number" || typeof media === "string") {
4023
+ return null;
4024
+ }
4025
+ if (media.url) {
4026
+ return {
4027
+ url: media.url,
4028
+ alt: media.alt || ""
4029
+ };
4030
+ }
4031
+ if (media.filename) {
4032
+ return {
4033
+ url: `/media/${media.filename}`,
4034
+ alt: media.alt || ""
4035
+ };
4036
+ }
4037
+ return null;
4038
+ }
2131
4039
  // Annotate the CommonJS export names for ESM import in node:
2132
4040
  0 && (module.exports = {
2133
4041
  admin,