@orion-studios/payload-studio 0.5.0-beta.11 → 0.5.0-beta.111

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 (77) hide show
  1. package/README.md +54 -0
  2. package/dist/admin/client.d.mts +3 -0
  3. package/dist/admin/client.d.ts +3 -0
  4. package/dist/admin/client.js +1745 -200
  5. package/dist/admin/client.mjs +1756 -214
  6. package/dist/admin/index.d.mts +2 -1
  7. package/dist/admin/index.d.ts +2 -1
  8. package/dist/admin/index.js +424 -11
  9. package/dist/admin/index.mjs +19 -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 +1270 -3
  13. package/dist/admin-app/client.mjs +1172 -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 +247 -0
  19. package/dist/admin.css +8 -0
  20. package/dist/blocks/index.js +1011 -191
  21. package/dist/blocks/index.mjs +2 -2
  22. package/dist/chunk-ADIIWIYL.mjs +322 -0
  23. package/dist/{chunk-ZLLNO5FM.mjs → chunk-BET2YLAS.mjs} +104 -15
  24. package/dist/chunk-DAIZDGHL.mjs +614 -0
  25. package/dist/chunk-GKMBYYXF.mjs +776 -0
  26. package/dist/chunk-JQAHXYAM.mjs +1829 -0
  27. package/dist/chunk-OQSEJXC4.mjs +166 -0
  28. package/dist/chunk-PF3EBZXF.mjs +326 -0
  29. package/dist/chunk-ROTPP5CU.mjs +99 -0
  30. package/dist/chunk-XVH5SCBD.mjs +234 -0
  31. package/dist/chunk-ZTXJG4K5.mjs +85 -0
  32. package/dist/index-7lxTrxSG.d.mts +128 -0
  33. package/dist/index-7lxTrxSG.d.ts +128 -0
  34. package/dist/index-B7QvY3yF.d.mts +245 -0
  35. package/dist/index-BK03FiEM.d.ts +245 -0
  36. package/dist/{index-CmR6NInu.d.ts → index-BzKOThsI.d.mts} +30 -3
  37. package/dist/{index-CmR6NInu.d.mts → index-BzKOThsI.d.ts} +30 -3
  38. package/dist/{index-DbH0Ljwp.d.mts → index-D8BNfUJb.d.mts} +17 -2
  39. package/dist/{index-DbH0Ljwp.d.ts → index-DD_E2UfJ.d.ts} +17 -2
  40. package/dist/index-DUi_XND6.d.ts +193 -0
  41. package/dist/index-gLl_358v.d.mts +193 -0
  42. package/dist/index.d.mts +6 -5
  43. package/dist/index.d.ts +6 -5
  44. package/dist/index.js +2876 -460
  45. package/dist/index.mjs +12 -10
  46. package/dist/nextjs/index.d.mts +2 -1
  47. package/dist/nextjs/index.d.ts +2 -1
  48. package/dist/nextjs/index.js +497 -16
  49. package/dist/nextjs/index.mjs +8 -3
  50. package/dist/socialMedia-C05Iy-SV.d.mts +21 -0
  51. package/dist/socialMedia-C05Iy-SV.d.ts +21 -0
  52. package/dist/studio/index.d.mts +2 -1
  53. package/dist/studio/index.d.ts +2 -1
  54. package/dist/studio/index.js +171 -5
  55. package/dist/studio/index.mjs +7 -3
  56. package/dist/studio-pages/builder.css +517 -32
  57. package/dist/studio-pages/client.d.mts +75 -1
  58. package/dist/studio-pages/client.d.ts +75 -1
  59. package/dist/studio-pages/client.js +5662 -2759
  60. package/dist/studio-pages/client.mjs +5578 -2767
  61. package/dist/studio-pages/index.d.mts +4 -2
  62. package/dist/studio-pages/index.d.ts +4 -2
  63. package/dist/studio-pages/index.js +859 -71
  64. package/dist/studio-pages/index.mjs +10 -4
  65. package/package.json +35 -13
  66. package/dist/chunk-AAOHJDNS.mjs +0 -67
  67. package/dist/chunk-ETRRXURT.mjs +0 -141
  68. package/dist/chunk-J7W5EE3B.mjs +0 -278
  69. package/dist/chunk-N67KVM2S.mjs +0 -156
  70. package/dist/chunk-NESLJZFE.mjs +0 -303
  71. package/dist/chunk-U5BSPWAD.mjs +0 -1034
  72. package/dist/index-B9N5MyjF.d.mts +0 -39
  73. package/dist/index-BallJs-K.d.mts +0 -43
  74. package/dist/index-BallJs-K.d.ts +0 -43
  75. package/dist/index-DJFhANvJ.d.mts +0 -128
  76. package/dist/index-DJFhANvJ.d.ts +0 -128
  77. package/dist/index-g8tBHLKD.d.ts +0 -39
package/dist/index.js CHANGED
@@ -42,8 +42,16 @@ module.exports = __toCommonJS(index_exports);
42
42
  // src/admin/index.ts
43
43
  var admin_exports = {};
44
44
  __export(admin_exports, {
45
+ SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM: () => SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM,
46
+ SOCIAL_MEDIA_ICON_OPTIONS: () => SOCIAL_MEDIA_ICON_OPTIONS,
47
+ SOCIAL_MEDIA_PLATFORMS: () => SOCIAL_MEDIA_PLATFORMS,
48
+ SOCIAL_MEDIA_PLATFORM_LABELS: () => SOCIAL_MEDIA_PLATFORM_LABELS,
45
49
  configureAdmin: () => configureAdmin,
50
+ createHeaderNavItemsField: () => createHeaderNavItemsField,
51
+ createSocialMediaConnectionsField: () => createSocialMediaConnectionsField,
52
+ createSocialMediaGlobal: () => createSocialMediaGlobal,
46
53
  createThemePreferenceField: () => createThemePreferenceField,
54
+ socialMediaConnectionsField: () => socialMediaConnectionsField,
47
55
  themePreferenceField: () => themePreferenceField,
48
56
  withTooltips: () => withTooltips
49
57
  });
@@ -71,6 +79,113 @@ var createThemePreferenceField = (defaultTheme = "brand-light") => ({
71
79
  });
72
80
  var themePreferenceField = createThemePreferenceField("brand-light");
73
81
 
82
+ // src/shared/studioSections.ts
83
+ var studioRoles = /* @__PURE__ */ new Set(["admin", "editor", "client"]);
84
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
85
+ var isAbsoluteExternalURL = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
86
+ var normalizePathLikeValue = (value) => {
87
+ const trimmed = value.trim();
88
+ if (!trimmed) {
89
+ return "";
90
+ }
91
+ if (isAbsoluteExternalURL(trimmed)) {
92
+ return trimmed;
93
+ }
94
+ const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
95
+ const normalized = withLeadingSlash.replace(/\/+$/, "");
96
+ return normalized || "/";
97
+ };
98
+ var normalizeStringArray = (value) => {
99
+ if (!Array.isArray(value)) {
100
+ return [];
101
+ }
102
+ return value.filter((entry) => typeof entry === "string").map((entry) => normalizePathLikeValue(entry)).filter((entry) => entry.length > 0);
103
+ };
104
+ var normalizeRoles = (value) => {
105
+ if (!Array.isArray(value)) {
106
+ return void 0;
107
+ }
108
+ const roles = value.filter((entry) => typeof entry === "string" && studioRoles.has(entry));
109
+ return roles.length > 0 ? roles : void 0;
110
+ };
111
+ var normalizeCard = (value) => {
112
+ if (!isRecord(value) || typeof value.title !== "string") {
113
+ return void 0;
114
+ }
115
+ const title = value.title.trim();
116
+ if (!title) {
117
+ return void 0;
118
+ }
119
+ return {
120
+ title,
121
+ ...typeof value.description === "string" && value.description.trim().length > 0 ? { description: value.description.trim() } : {}
122
+ };
123
+ };
124
+ var resolveStudioSections = (value) => {
125
+ if (!Array.isArray(value)) {
126
+ return [];
127
+ }
128
+ const sections = [];
129
+ const seen = /* @__PURE__ */ new Set();
130
+ for (const entry of value) {
131
+ if (!isRecord(entry) || typeof entry.id !== "string" || typeof entry.label !== "string") {
132
+ continue;
133
+ }
134
+ const id = entry.id.trim();
135
+ const label = entry.label.trim();
136
+ if (!id || !label || seen.has(id)) {
137
+ continue;
138
+ }
139
+ const href = typeof entry.href === "string" && entry.href.trim().length > 0 ? normalizePathLikeValue(entry.href) : isRecord(entry.view) && typeof entry.view.path === "string" ? normalizePathLikeValue(entry.view.path) : "";
140
+ if (!href) {
141
+ continue;
142
+ }
143
+ const matchPrefixes = Array.from(/* @__PURE__ */ new Set([href, ...normalizeStringArray(entry.matchPrefixes)]));
144
+ sections.push({
145
+ id,
146
+ label,
147
+ href,
148
+ matchPrefixes,
149
+ ...normalizeRoles(entry.roles) ? { roles: normalizeRoles(entry.roles) } : {},
150
+ ...normalizeCard(entry.card) ? { card: normalizeCard(entry.card) } : {}
151
+ });
152
+ seen.add(id);
153
+ }
154
+ return sections;
155
+ };
156
+ var resolveStudioSectionViews = (value) => {
157
+ if (!Array.isArray(value)) {
158
+ return {};
159
+ }
160
+ const views = {};
161
+ for (const entry of value) {
162
+ if (!isRecord(entry) || typeof entry.id !== "string" || !isRecord(entry.view)) {
163
+ continue;
164
+ }
165
+ const id = entry.id.trim();
166
+ const view = entry.view;
167
+ const component = isRecord(view.Component) ? view.Component : null;
168
+ if (!id || typeof view.path !== "string" || !component || typeof component.exportName !== "string" || typeof component.path !== "string") {
169
+ continue;
170
+ }
171
+ const path2 = normalizePathLikeValue(view.path);
172
+ const componentPath = component.path.trim();
173
+ const exportName = component.exportName.trim();
174
+ if (!path2 || !componentPath || !exportName) {
175
+ continue;
176
+ }
177
+ views[id] = {
178
+ path: path2,
179
+ Component: {
180
+ exportName,
181
+ path: componentPath,
182
+ ...isRecord(component.clientProps) ? { clientProps: component.clientProps } : {}
183
+ }
184
+ };
185
+ }
186
+ return views;
187
+ };
188
+
74
189
  // src/admin/helpers/configureAdmin.ts
75
190
  var import_meta = {};
76
191
  function getPkgDistDir() {
@@ -98,14 +213,31 @@ function configureAdmin(config) {
98
213
  defaultTheme = "brand-light",
99
214
  logoUrl
100
215
  } = config;
101
- const studioEnabled = Boolean(config.studio?.enabled);
216
+ const studioEnabled = config.studio?.enabled ?? true;
217
+ const formsEnabled = config.studio?.forms?.enabled ?? false;
218
+ const formsCollectionSlug = config.studio?.forms?.collectionSlug || "forms";
219
+ const formSubmissionsCollectionSlug = config.studio?.forms?.submissionsCollectionSlug || "form-submissions";
220
+ const formUploadsCollectionSlug = config.studio?.forms?.uploadsCollectionSlug || "form-uploads";
102
221
  const pagesCollectionSlug = config.studio?.pages?.collectionSlug || "pages";
103
222
  const mediaCollectionSlug = config.studio?.media?.collectionSlug || "media";
104
- const globals = config.studio?.globals || [
223
+ const contactFormStudioPath = "/studio-contact-form";
224
+ const configuredGlobals = config.studio?.globals || [
105
225
  { slug: "site-settings", label: "Website Settings" },
106
226
  { slug: "header", label: "Header & Navigation" },
107
- { slug: "footer", label: "Footer" }
227
+ { slug: "footer", label: "Footer" },
228
+ { slug: "contact-form", label: "Contact Form" }
108
229
  ];
230
+ const globals = configuredGlobals.map((global) => {
231
+ if (global.slug !== "contact-form" || global.href) {
232
+ return global;
233
+ }
234
+ return {
235
+ ...global,
236
+ href: contactFormStudioPath
237
+ };
238
+ });
239
+ const studioSections = resolveStudioSections(config.studio?.sections || []);
240
+ const studioSectionViews = resolveStudioSectionViews(config.studio?.sections || []);
109
241
  let cssPath;
110
242
  const pkgDist = getPkgDistDir();
111
243
  const sourceCssPath = import_path.default.resolve(pkgDist, "admin.css");
@@ -135,9 +267,15 @@ function configureAdmin(config) {
135
267
  clientProps: {
136
268
  brandName,
137
269
  logoUrl,
138
- globalsBasePath: "/admin/studio-globals",
270
+ globalsBasePath: "/studio-globals",
271
+ globalsExtraMatchPrefixes: [contactFormStudioPath],
272
+ formSubmissionsCollectionSlug,
273
+ formsCollectionSlug,
274
+ formsEnabled,
275
+ formUploadsCollectionSlug,
139
276
  mediaCollectionSlug,
140
- pagesCollectionSlug
277
+ pagesCollectionSlug,
278
+ sections: studioSections
141
279
  }
142
280
  }
143
281
  } : {},
@@ -167,9 +305,15 @@ function configureAdmin(config) {
167
305
  clientProps: {
168
306
  brandName,
169
307
  logoUrl,
170
- globalsBasePath: "/admin/studio-globals",
308
+ globalsBasePath: "/studio-globals",
309
+ globalsExtraMatchPrefixes: [contactFormStudioPath],
310
+ formSubmissionsCollectionSlug,
311
+ formsCollectionSlug,
312
+ formsEnabled,
313
+ formUploadsCollectionSlug,
171
314
  mediaCollectionSlug,
172
- pagesCollectionSlug
315
+ pagesCollectionSlug,
316
+ sections: studioSections
173
317
  }
174
318
  }
175
319
  },
@@ -181,10 +325,44 @@ function configureAdmin(config) {
181
325
  path: clientPath,
182
326
  clientProps: {
183
327
  globals,
184
- globalsBasePath: "/admin/studio-globals"
328
+ globalsBasePath: "/studio-globals"
185
329
  }
186
330
  }
187
- }
331
+ },
332
+ studioContactForm: {
333
+ path: "/studio-contact-form",
334
+ Component: {
335
+ exportName: "AdminStudioContactFormView",
336
+ path: clientPath,
337
+ clientProps: {
338
+ globalSlug: "contact-form",
339
+ globalsBasePath: "/studio-globals"
340
+ }
341
+ }
342
+ },
343
+ ...formsEnabled ? {
344
+ studioForms: {
345
+ path: "/studio-forms",
346
+ Component: {
347
+ exportName: "AdminStudioFormsView",
348
+ path: clientPath,
349
+ clientProps: {
350
+ formsCollectionSlug,
351
+ formSubmissionsCollectionSlug,
352
+ formUploadsCollectionSlug
353
+ }
354
+ }
355
+ }
356
+ } : {},
357
+ ...Object.fromEntries(
358
+ Object.entries(studioSectionViews).map(([id, view]) => [
359
+ id,
360
+ {
361
+ path: view.path,
362
+ Component: view.Component
363
+ }
364
+ ])
365
+ )
188
366
  } : {}
189
367
  },
190
368
  providers: [
@@ -228,16 +406,48 @@ function configureAdmin(config) {
228
406
  const labelMap = {
229
407
  header: { group: "Site Design", label: "Header & Navigation" },
230
408
  footer: { group: "Site Design", label: "Footer" },
231
- "site-settings": { group: "Site Design", label: "Website Settings" }
409
+ "site-settings": { group: "Site Design", label: "Website Settings" },
410
+ "social-media": { group: "Site Design", label: "Social Media" },
411
+ "contact-form": { group: "Lead Forms", label: "Contact Form" }
232
412
  };
233
413
  return globals2.map((global) => {
234
414
  const mapping = labelMap[global.slug];
235
415
  if (!mapping) return global;
416
+ const shouldAttachContactFormRedirect = studioEnabled && global.slug === "contact-form";
417
+ const existingViews = global.admin?.components?.views;
418
+ const existingEditViews = existingViews?.edit;
419
+ const hasCustomContactFormEditView = Boolean(
420
+ existingEditViews?.root || existingEditViews?.default && typeof existingEditViews.default === "object" && existingEditViews.default.Component
421
+ );
422
+ const contactFormEditViews = shouldAttachContactFormRedirect && !hasCustomContactFormEditView ? {
423
+ ...existingEditViews,
424
+ default: {
425
+ ...typeof existingEditViews?.default === "object" ? existingEditViews.default : {},
426
+ Component: {
427
+ exportName: "StudioContactFormRedirect",
428
+ path: clientPath,
429
+ clientProps: {
430
+ studioContactFormPath: contactFormStudioPath
431
+ }
432
+ }
433
+ }
434
+ } : existingEditViews;
236
435
  return {
237
436
  ...global,
238
437
  admin: {
239
438
  ...global.admin,
240
- group: mapping.group
439
+ group: mapping.group,
440
+ components: {
441
+ ...global.admin?.components,
442
+ ...shouldAttachContactFormRedirect ? {
443
+ views: {
444
+ ...existingViews,
445
+ ...contactFormEditViews ? {
446
+ edit: contactFormEditViews
447
+ } : {}
448
+ }
449
+ } : {}
450
+ }
241
451
  },
242
452
  label: mapping.label
243
453
  };
@@ -306,12 +516,213 @@ function addTooltipToField(field, tooltips) {
306
516
  return field;
307
517
  }
308
518
 
519
+ // src/admin/fields/headerNav.ts
520
+ var createHeaderNavItemsField = () => ({
521
+ name: "navItems",
522
+ type: "array",
523
+ labels: { singular: "Navigation Link", plural: "Navigation Links" },
524
+ admin: {
525
+ description: "The links displayed in your website's main navigation menu."
526
+ },
527
+ fields: [
528
+ {
529
+ name: "label",
530
+ type: "text",
531
+ required: true,
532
+ admin: {
533
+ description: "The text shown for this navigation link."
534
+ }
535
+ },
536
+ {
537
+ name: "href",
538
+ type: "text",
539
+ required: true,
540
+ admin: {
541
+ description: 'The URL this link points to (e.g., "/about" or "https://example.com").'
542
+ }
543
+ },
544
+ {
545
+ name: "parentHref",
546
+ type: "text",
547
+ admin: {
548
+ description: "Optional parent link URL. If set to another nav item href, this item appears in that dropdown."
549
+ }
550
+ }
551
+ ]
552
+ });
553
+
554
+ // src/shared/socialMedia.ts
555
+ var SOCIAL_MEDIA_PLATFORM_LABELS = {
556
+ facebook: "Facebook",
557
+ instagram: "Instagram",
558
+ x: "X (Twitter)",
559
+ linkedin: "LinkedIn",
560
+ youtube: "YouTube",
561
+ tiktok: "TikTok",
562
+ pinterest: "Pinterest",
563
+ snapchat: "Snapchat"
564
+ };
565
+ var SOCIAL_MEDIA_PLATFORMS = [
566
+ "facebook",
567
+ "instagram",
568
+ "x",
569
+ "linkedin",
570
+ "youtube",
571
+ "tiktok",
572
+ "pinterest",
573
+ "snapchat"
574
+ ];
575
+ var SOCIAL_MEDIA_ICON_OPTIONS = {
576
+ facebook: [
577
+ { label: "Simple Icons", library: "simple-icons", value: "simple-icons:facebook" },
578
+ { label: "Font Awesome Brands", library: "font-awesome-brands", value: "fa6-brands:facebook-f" },
579
+ { label: "Tabler Brands", library: "tabler-brands", value: "tabler:brand-facebook" },
580
+ { label: "Remix Icons", library: "remix-icons", value: "ri:facebook-fill" }
581
+ ],
582
+ instagram: [
583
+ { label: "Simple Icons", library: "simple-icons", value: "simple-icons:instagram" },
584
+ { label: "Font Awesome Brands", library: "font-awesome-brands", value: "fa6-brands:instagram" },
585
+ { label: "Tabler Brands", library: "tabler-brands", value: "tabler:brand-instagram" },
586
+ { label: "Remix Icons", library: "remix-icons", value: "ri:instagram-fill" }
587
+ ],
588
+ x: [
589
+ { label: "Simple Icons", library: "simple-icons", value: "simple-icons:x" },
590
+ { label: "Font Awesome Brands", library: "font-awesome-brands", value: "fa6-brands:x-twitter" },
591
+ { label: "Tabler Brands", library: "tabler-brands", value: "tabler:brand-x" },
592
+ { label: "Remix Icons", library: "remix-icons", value: "ri:twitter-x-fill" }
593
+ ],
594
+ linkedin: [
595
+ { label: "Simple Icons", library: "simple-icons", value: "simple-icons:linkedin" },
596
+ { label: "Font Awesome Brands", library: "font-awesome-brands", value: "fa6-brands:linkedin-in" },
597
+ { label: "Tabler Brands", library: "tabler-brands", value: "tabler:brand-linkedin" },
598
+ { label: "Remix Icons", library: "remix-icons", value: "ri:linkedin-fill" }
599
+ ],
600
+ youtube: [
601
+ { label: "Simple Icons", library: "simple-icons", value: "simple-icons:youtube" },
602
+ { label: "Font Awesome Brands", library: "font-awesome-brands", value: "fa6-brands:youtube" },
603
+ { label: "Tabler Brands", library: "tabler-brands", value: "tabler:brand-youtube" },
604
+ { label: "Remix Icons", library: "remix-icons", value: "ri:youtube-fill" }
605
+ ],
606
+ tiktok: [
607
+ { label: "Simple Icons", library: "simple-icons", value: "simple-icons:tiktok" },
608
+ { label: "Font Awesome Brands", library: "font-awesome-brands", value: "fa6-brands:tiktok" },
609
+ { label: "Tabler Brands", library: "tabler-brands", value: "tabler:brand-tiktok" },
610
+ { label: "Remix Icons", library: "remix-icons", value: "ri:tiktok-fill" }
611
+ ],
612
+ pinterest: [
613
+ { label: "Simple Icons", library: "simple-icons", value: "simple-icons:pinterest" },
614
+ { label: "Font Awesome Brands", library: "font-awesome-brands", value: "fa6-brands:pinterest-p" },
615
+ { label: "Tabler Brands", library: "tabler-brands", value: "tabler:brand-pinterest" },
616
+ { label: "Remix Icons", library: "remix-icons", value: "ri:pinterest-fill" }
617
+ ],
618
+ snapchat: [
619
+ { label: "Simple Icons", library: "simple-icons", value: "simple-icons:snapchat" },
620
+ { label: "Font Awesome Brands", library: "font-awesome-brands", value: "fa6-brands:snapchat" },
621
+ { label: "Tabler Brands", library: "tabler-brands", value: "tabler:brand-snapchat" },
622
+ { label: "Remix Icons", library: "remix-icons", value: "ri:snapchat-fill" }
623
+ ]
624
+ };
625
+ var SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM = SOCIAL_MEDIA_PLATFORMS.reduce(
626
+ (acc, platform) => {
627
+ acc[platform] = SOCIAL_MEDIA_ICON_OPTIONS[platform][0].value;
628
+ return acc;
629
+ },
630
+ {}
631
+ );
632
+
633
+ // src/admin/fields/socialMedia.ts
634
+ var DEFAULT_DESCRIPTION = "Add profile URLs and choose one of two icon styles for each platform. Leave a URL blank to hide that profile on your site.";
635
+ function validateOptionalHttpsUrl(value) {
636
+ if (value === null || value === void 0 || value === "") {
637
+ return true;
638
+ }
639
+ if (typeof value !== "string") {
640
+ return "Enter a valid URL.";
641
+ }
642
+ try {
643
+ const url = new URL(value);
644
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
645
+ return "Use an http:// or https:// URL.";
646
+ }
647
+ return true;
648
+ } catch {
649
+ return "Enter a valid URL (for example: https://instagram.com/yourbrand).";
650
+ }
651
+ }
652
+ function createPlatformField(platform) {
653
+ const platformLabel = SOCIAL_MEDIA_PLATFORM_LABELS[platform];
654
+ const iconOptions = SOCIAL_MEDIA_ICON_OPTIONS[platform].map((option) => ({
655
+ label: option.label,
656
+ value: option.value
657
+ }));
658
+ return {
659
+ name: platform,
660
+ type: "group",
661
+ label: platformLabel,
662
+ fields: [
663
+ {
664
+ name: "url",
665
+ type: "text",
666
+ admin: {
667
+ description: `Full ${platformLabel} profile URL. Leave blank to hide ${platformLabel}.`
668
+ },
669
+ validate: validateOptionalHttpsUrl
670
+ },
671
+ {
672
+ name: "icon",
673
+ type: "select",
674
+ defaultValue: SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM[platform],
675
+ options: iconOptions,
676
+ admin: {
677
+ description: "Pick which icon style to display for this platform."
678
+ }
679
+ }
680
+ ]
681
+ };
682
+ }
683
+ var createSocialMediaConnectionsField = (options = {}) => {
684
+ const selectedPlatforms = Array.isArray(options.platforms) && options.platforms.length > 0 ? options.platforms : SOCIAL_MEDIA_PLATFORMS;
685
+ return {
686
+ name: options.name || "profiles",
687
+ type: "group",
688
+ label: options.label || "Social Media Profiles",
689
+ admin: {
690
+ description: options.description || DEFAULT_DESCRIPTION
691
+ },
692
+ fields: selectedPlatforms.map((platform) => createPlatformField(platform))
693
+ };
694
+ };
695
+ var socialMediaConnectionsField = createSocialMediaConnectionsField();
696
+
697
+ // src/admin/globals/socialMedia.ts
698
+ var createSocialMediaGlobal = (options = {}) => ({
699
+ slug: options.slug || "social-media",
700
+ label: "Social Media",
701
+ admin: {
702
+ description: options.description || "Manage social profile links and icon variants used across your website.",
703
+ group: "Site Design"
704
+ },
705
+ fields: [
706
+ createSocialMediaConnectionsField({
707
+ ...options.fieldOptions,
708
+ label: "Profiles",
709
+ name: "profiles"
710
+ })
711
+ ]
712
+ });
713
+
309
714
  // src/admin-app/index.ts
310
715
  var admin_app_exports = {};
311
716
  __export(admin_app_exports, {
312
717
  AdminBreadcrumbs: () => AdminBreadcrumbs,
313
718
  AdminPage: () => AdminPage,
719
+ buildAdminPageLinkOptions: () => buildAdminPageLinkOptions,
720
+ buildNestedNavTree: () => buildNestedNavTree,
721
+ getAdminNavRows: () => getAdminNavRows,
314
722
  navItemIsActive: () => navItemIsActive,
723
+ normalizeAdminNavInputs: () => normalizeAdminNavInputs,
724
+ normalizeNestedNavItems: () => normalizeNestedNavItems,
725
+ parseAdminHeaderNavFromForm: () => parseAdminHeaderNavFromForm,
315
726
  roleCanAccessNav: () => roleCanAccessNav
316
727
  });
317
728
 
@@ -345,6 +756,161 @@ function AdminPage({ title, description, breadcrumbs, actions, children }) {
345
756
  ] });
346
757
  }
347
758
 
759
+ // src/admin-app/nestedNavigation.ts
760
+ var normalizeNestedNavItems = (items) => {
761
+ const deduped = [];
762
+ const seen = /* @__PURE__ */ new Set();
763
+ for (const item of items) {
764
+ const href = typeof item.href === "string" ? item.href.trim() : "";
765
+ const label = typeof item.label === "string" ? item.label.trim() : "";
766
+ const parentHref = typeof item.parentHref === "string" ? item.parentHref.trim() : "";
767
+ if (!href || !label || seen.has(href)) {
768
+ continue;
769
+ }
770
+ seen.add(href);
771
+ deduped.push({
772
+ href,
773
+ label,
774
+ ...parentHref ? { parentHref } : {}
775
+ });
776
+ }
777
+ const hrefs = new Set(deduped.map((item) => item.href));
778
+ return deduped.map((item) => ({
779
+ href: item.href,
780
+ label: item.label,
781
+ ...item.parentHref && item.parentHref !== item.href && hrefs.has(item.parentHref) ? { parentHref: item.parentHref } : {}
782
+ }));
783
+ };
784
+ var buildNestedNavTree = (items) => {
785
+ const childrenByParent = /* @__PURE__ */ new Map();
786
+ const topLevel = [];
787
+ for (const item of items) {
788
+ if (!item.parentHref) {
789
+ topLevel.push(item);
790
+ continue;
791
+ }
792
+ const children = childrenByParent.get(item.parentHref) || [];
793
+ children.push(item);
794
+ childrenByParent.set(item.parentHref, children);
795
+ }
796
+ if (topLevel.length === 0 && items.length > 0) {
797
+ return {
798
+ topLevel: items.map((item) => ({ href: item.href, label: item.label })),
799
+ childrenByParent: /* @__PURE__ */ new Map()
800
+ };
801
+ }
802
+ return { childrenByParent, topLevel };
803
+ };
804
+
805
+ // src/admin-app/navigationLinks.ts
806
+ var fallbackHomeOption = {
807
+ href: "/",
808
+ label: "Home",
809
+ title: "Home"
810
+ };
811
+ var buildAdminPageLinkOptions = (pages) => {
812
+ const options = pages.map((page) => {
813
+ const href = typeof page.path === "string" ? page.path.trim() : "";
814
+ const title = typeof page.title === "string" && page.title.trim().length > 0 ? page.title.trim() : "Untitled Page";
815
+ if (!href) {
816
+ return null;
817
+ }
818
+ const depth = href === "/" ? 0 : href.split("/").filter(Boolean).length;
819
+ const indentLevel = Math.max(depth - 1, 0);
820
+ const indent = indentLevel > 0 ? `${"\u21B3 ".repeat(indentLevel)}` : "";
821
+ return {
822
+ href,
823
+ label: `${indent}${title}`,
824
+ title
825
+ };
826
+ }).filter((option) => option !== null).sort((a, b) => {
827
+ if (a.href === "/" && b.href !== "/") return -1;
828
+ if (b.href === "/" && a.href !== "/") return 1;
829
+ return a.href.localeCompare(b.href);
830
+ });
831
+ if (options.length === 0) {
832
+ return [fallbackHomeOption];
833
+ }
834
+ return options;
835
+ };
836
+ var getAdminNavRows = (navItems, pageOptions, minRows = 6, maxRows = 20) => {
837
+ const preferredRows = Math.max(navItems.length + 2, Math.min(8, pageOptions.length));
838
+ return Math.min(Math.max(minRows, preferredRows), maxRows);
839
+ };
840
+ var normalizeAdminNavInputs = (rows, pageOptions) => {
841
+ const allowedLinks = new Map(pageOptions.map((option) => [option.href, option.title]));
842
+ const deduped = [];
843
+ const seen = /* @__PURE__ */ new Set();
844
+ for (const row of rows) {
845
+ const href = typeof row.href === "string" ? row.href.trim() : "";
846
+ const defaultLabel = allowedLinks.get(href);
847
+ const explicitLabel = typeof row.label === "string" ? row.label.trim() : "";
848
+ const parentHref = typeof row.parentHref === "string" ? row.parentHref.trim() : "";
849
+ if (!href || !defaultLabel || seen.has(href)) {
850
+ continue;
851
+ }
852
+ seen.add(href);
853
+ deduped.push({
854
+ href,
855
+ label: explicitLabel.length > 0 ? explicitLabel : defaultLabel,
856
+ ...parentHref.length > 0 ? { parentHref } : {}
857
+ });
858
+ }
859
+ const hrefs = new Set(deduped.map((item) => item.href));
860
+ const parentByHref = new Map(deduped.map((item) => [item.href, item.parentHref || ""]));
861
+ const createsCycle = (href, parentHref) => {
862
+ const visited = /* @__PURE__ */ new Set([href]);
863
+ let current = parentHref;
864
+ while (current) {
865
+ if (visited.has(current)) {
866
+ return true;
867
+ }
868
+ visited.add(current);
869
+ current = parentByHref.get(current) || "";
870
+ }
871
+ return false;
872
+ };
873
+ return deduped.map((item) => {
874
+ const parentHref = item.parentHref;
875
+ if (!parentHref || parentHref === item.href || !hrefs.has(parentHref) || createsCycle(item.href, parentHref)) {
876
+ return {
877
+ href: item.href,
878
+ label: item.label
879
+ };
880
+ }
881
+ return item;
882
+ });
883
+ };
884
+ var parseAdminHeaderNavFromForm = (formData, pageOptions, maxRows = 24) => {
885
+ const serialized = String(formData.get("navItemsState") || "").trim();
886
+ if (serialized.length > 0) {
887
+ try {
888
+ const parsed = JSON.parse(serialized);
889
+ if (Array.isArray(parsed)) {
890
+ return normalizeAdminNavInputs(parsed.slice(0, maxRows), pageOptions);
891
+ }
892
+ } catch {
893
+ }
894
+ }
895
+ const rawCount = Number(String(formData.get("navCount") || "0"));
896
+ const navCount = Number.isFinite(rawCount) ? Math.max(0, Math.min(rawCount, maxRows)) : 0;
897
+ const navRows = [];
898
+ for (let index = 0; index < navCount; index += 1) {
899
+ const href = String(formData.get(`navPage_${index}`) || "").trim();
900
+ const label = String(formData.get(`navLabel_${index}`) || "").trim();
901
+ const parentHref = String(formData.get(`navParentHref_${index}`) || "").trim();
902
+ navRows.push({
903
+ href,
904
+ label,
905
+ parentHref
906
+ });
907
+ }
908
+ if (navRows.length > 0) {
909
+ return normalizeAdminNavInputs(navRows, pageOptions);
910
+ }
911
+ return [];
912
+ };
913
+
348
914
  // src/admin-app/routeRegistry.ts
349
915
  var roleCanAccessNav = (role, item) => {
350
916
  if (!item.roles || item.roles.length === 0) {
@@ -398,189 +964,315 @@ var sectionStyleDefaults = {
398
964
  contentGradientAngle: "135",
399
965
  contentGradientFrom: "#ffffff",
400
966
  contentGradientPreset: "none",
401
- contentGradientTo: "#f4f6f2",
967
+ contentGradientTo: "#f4f6f8",
402
968
  contentWidth: "inherit",
969
+ sectionPaddingX: "inherit",
403
970
  sectionBackgroundColor: "#ffffff",
404
971
  sectionBackgroundMode: "none",
405
972
  sectionGradientAngle: "135",
406
- sectionGradientFrom: "#124a37",
407
- sectionGradientPreset: "forest",
408
- sectionGradientTo: "#1f684f",
973
+ sectionGradientFrom: "#334b63",
974
+ sectionGradientPreset: "slate",
975
+ sectionGradientTo: "#496582",
409
976
  sectionPaddingY: "md",
410
977
  sectionWidth: "content"
411
978
  };
412
- var sectionStyleFields = () => [
413
- {
414
- name: "sectionWidth",
415
- type: "select",
416
- defaultValue: sectionStyleDefaults.sectionWidth,
417
- options: [
418
- { label: "Content", value: "content" },
419
- { label: "Wide", value: "wide" },
420
- { label: "Full", value: "full" }
421
- ]
422
- },
423
- {
424
- name: "contentWidth",
425
- type: "select",
426
- defaultValue: sectionStyleDefaults.contentWidth,
427
- options: [
428
- { label: "Inherit", value: "inherit" },
429
- { label: "Narrow", value: "narrow" },
430
- { label: "Content", value: "content" },
431
- { label: "Wide", value: "wide" },
432
- { label: "Full", value: "full" }
433
- ]
434
- },
435
- {
436
- name: "sectionPaddingY",
437
- type: "select",
438
- defaultValue: sectionStyleDefaults.sectionPaddingY,
439
- options: [
440
- { label: "Small", value: "sm" },
441
- { label: "Medium", value: "md" },
442
- { label: "Large", value: "lg" }
443
- ]
444
- },
445
- {
446
- name: "sectionBackgroundMode",
447
- type: "select",
448
- defaultValue: sectionStyleDefaults.sectionBackgroundMode,
449
- options: [
450
- { label: "None", value: "none" },
451
- { label: "Color", value: "color" },
452
- { label: "Gradient", value: "gradient" }
453
- ]
454
- },
455
- {
456
- name: "sectionBackgroundColor",
457
- type: "text",
458
- defaultValue: sectionStyleDefaults.sectionBackgroundColor
459
- },
460
- {
461
- name: "sectionGradientPreset",
462
- type: "select",
463
- defaultValue: sectionStyleDefaults.sectionGradientPreset,
464
- options: [
465
- { label: "None", value: "none" },
466
- { label: "Forest", value: "forest" },
467
- { label: "Moss", value: "moss" },
468
- { label: "Cream", value: "cream" },
469
- { label: "Slate", value: "slate" }
470
- ]
471
- },
472
- {
473
- name: "sectionGradientFrom",
474
- type: "text",
475
- defaultValue: sectionStyleDefaults.sectionGradientFrom
476
- },
477
- {
478
- name: "sectionGradientTo",
479
- type: "text",
480
- defaultValue: sectionStyleDefaults.sectionGradientTo
481
- },
482
- {
483
- name: "sectionGradientAngle",
484
- type: "text",
485
- defaultValue: sectionStyleDefaults.sectionGradientAngle
486
- },
487
- {
488
- name: "contentBackgroundMode",
489
- type: "select",
490
- defaultValue: sectionStyleDefaults.contentBackgroundMode,
491
- options: [
492
- { label: "None", value: "none" },
493
- { label: "Color", value: "color" },
494
- { label: "Gradient", value: "gradient" }
495
- ]
496
- },
497
- {
498
- name: "contentBackgroundColor",
499
- type: "text",
500
- defaultValue: sectionStyleDefaults.contentBackgroundColor
501
- },
502
- {
503
- name: "contentGradientPreset",
504
- type: "select",
505
- defaultValue: sectionStyleDefaults.contentGradientPreset,
506
- options: [
507
- { label: "None", value: "none" },
508
- { label: "Cloud", value: "cloud" },
509
- { label: "Sand", value: "sand" },
510
- { label: "Mint", value: "mint" },
511
- { label: "Night", value: "night" }
512
- ]
513
- },
514
- {
515
- name: "contentGradientFrom",
516
- type: "text",
517
- defaultValue: sectionStyleDefaults.contentGradientFrom
518
- },
519
- {
520
- name: "contentGradientTo",
521
- type: "text",
522
- defaultValue: sectionStyleDefaults.contentGradientTo
523
- },
524
- {
525
- name: "contentGradientAngle",
526
- type: "text",
527
- defaultValue: sectionStyleDefaults.contentGradientAngle
528
- }
529
- ];
530
-
531
- // src/blocks/blocks/BeforeAfter.ts
532
- var BeforeAfterBlock = {
533
- slug: "beforeAfter",
534
- imageURL: "/images/project-before-2.svg",
535
- imageAltText: "Before and after section preview",
979
+ var hideFromCMS = (field) => ({
980
+ ...field,
536
981
  admin: {
537
- components: {
538
- Label: builderBlockLabelComponent
539
- }
540
- },
541
- labels: {
542
- singular: "Before / After",
543
- plural: "Before / After"
544
- },
545
- fields: [
982
+ ...field.admin || {},
983
+ hidden: true
984
+ }
985
+ });
986
+ var sectionStyleFields = () => {
987
+ const fields = [
546
988
  {
547
- name: "title",
548
- type: "text",
549
- required: true
989
+ name: "sectionWidth",
990
+ type: "select",
991
+ defaultValue: sectionStyleDefaults.sectionWidth,
992
+ options: [
993
+ { label: "Content", value: "content" },
994
+ { label: "Wide", value: "wide" },
995
+ { label: "Full", value: "full" }
996
+ ]
550
997
  },
551
998
  {
552
- name: "subtitle",
553
- type: "textarea"
999
+ name: "contentWidth",
1000
+ type: "select",
1001
+ defaultValue: sectionStyleDefaults.contentWidth,
1002
+ options: [
1003
+ { label: "Inherit", value: "inherit" },
1004
+ { label: "Narrow", value: "narrow" },
1005
+ { label: "Content", value: "content" },
1006
+ { label: "Wide", value: "wide" },
1007
+ { label: "Full", value: "full" }
1008
+ ]
554
1009
  },
555
1010
  {
556
- name: "items",
557
- type: "array",
558
- minRows: 1,
559
- maxRows: 6,
560
- fields: [
561
- {
562
- name: "label",
563
- type: "text",
564
- required: true
565
- },
566
- {
1011
+ name: "sectionPaddingY",
1012
+ type: "select",
1013
+ defaultValue: sectionStyleDefaults.sectionPaddingY,
1014
+ options: [
1015
+ { label: "None", value: "none" },
1016
+ { label: "Small", value: "sm" },
1017
+ { label: "Medium", value: "md" },
1018
+ { label: "Large", value: "lg" }
1019
+ ]
1020
+ },
1021
+ {
1022
+ name: "sectionPaddingX",
1023
+ type: "select",
1024
+ defaultValue: sectionStyleDefaults.sectionPaddingX,
1025
+ options: [
1026
+ { label: "Inherit", value: "inherit" },
1027
+ { label: "None", value: "none" },
1028
+ { label: "Small", value: "sm" },
1029
+ { label: "Medium", value: "md" },
1030
+ { label: "Large", value: "lg" }
1031
+ ]
1032
+ },
1033
+ {
1034
+ name: "sectionBackgroundMode",
1035
+ type: "select",
1036
+ defaultValue: sectionStyleDefaults.sectionBackgroundMode,
1037
+ options: [
1038
+ { label: "None", value: "none" },
1039
+ { label: "Color", value: "color" },
1040
+ { label: "Gradient", value: "gradient" }
1041
+ ]
1042
+ },
1043
+ {
1044
+ name: "sectionBackgroundColor",
1045
+ type: "text",
1046
+ defaultValue: sectionStyleDefaults.sectionBackgroundColor
1047
+ },
1048
+ {
1049
+ name: "sectionGradientPreset",
1050
+ type: "select",
1051
+ defaultValue: sectionStyleDefaults.sectionGradientPreset,
1052
+ options: [
1053
+ { label: "None", value: "none" },
1054
+ { label: "Brand", value: "brand" },
1055
+ { label: "Forest", value: "forest" },
1056
+ { label: "Moss", value: "moss" },
1057
+ { label: "Cream", value: "cream" },
1058
+ { label: "Slate", value: "slate" }
1059
+ ]
1060
+ },
1061
+ {
1062
+ name: "sectionGradientFrom",
1063
+ type: "text",
1064
+ defaultValue: sectionStyleDefaults.sectionGradientFrom
1065
+ },
1066
+ {
1067
+ name: "sectionGradientTo",
1068
+ type: "text",
1069
+ defaultValue: sectionStyleDefaults.sectionGradientTo
1070
+ },
1071
+ {
1072
+ name: "sectionGradientAngle",
1073
+ type: "text",
1074
+ defaultValue: sectionStyleDefaults.sectionGradientAngle
1075
+ },
1076
+ {
1077
+ name: "contentBackgroundMode",
1078
+ type: "select",
1079
+ defaultValue: sectionStyleDefaults.contentBackgroundMode,
1080
+ options: [
1081
+ { label: "None", value: "none" },
1082
+ { label: "Color", value: "color" },
1083
+ { label: "Gradient", value: "gradient" }
1084
+ ]
1085
+ },
1086
+ {
1087
+ name: "contentBackgroundColor",
1088
+ type: "text",
1089
+ defaultValue: sectionStyleDefaults.contentBackgroundColor
1090
+ },
1091
+ {
1092
+ name: "contentGradientPreset",
1093
+ type: "select",
1094
+ defaultValue: sectionStyleDefaults.contentGradientPreset,
1095
+ options: [
1096
+ { label: "None", value: "none" },
1097
+ { label: "Cloud", value: "cloud" },
1098
+ { label: "Sand", value: "sand" },
1099
+ { label: "Mint", value: "mint" },
1100
+ { label: "Night", value: "night" }
1101
+ ]
1102
+ },
1103
+ {
1104
+ name: "contentGradientFrom",
1105
+ type: "text",
1106
+ defaultValue: sectionStyleDefaults.contentGradientFrom
1107
+ },
1108
+ {
1109
+ name: "contentGradientTo",
1110
+ type: "text",
1111
+ defaultValue: sectionStyleDefaults.contentGradientTo
1112
+ },
1113
+ {
1114
+ name: "contentGradientAngle",
1115
+ type: "text",
1116
+ defaultValue: sectionStyleDefaults.contentGradientAngle
1117
+ }
1118
+ ];
1119
+ return fields.map(hideFromCMS);
1120
+ };
1121
+
1122
+ // src/blocks/blocks/BeforeAfter.ts
1123
+ var BeforeAfterBlock = {
1124
+ slug: "beforeAfter",
1125
+ imageURL: "/images/project-before-2.svg",
1126
+ imageAltText: "Before and after section preview",
1127
+ admin: {
1128
+ components: {
1129
+ Label: builderBlockLabelComponent
1130
+ }
1131
+ },
1132
+ labels: {
1133
+ singular: "Before / After",
1134
+ plural: "Before / After"
1135
+ },
1136
+ fields: [
1137
+ {
1138
+ name: "title",
1139
+ type: "text",
1140
+ required: true
1141
+ },
1142
+ {
1143
+ name: "subtitle",
1144
+ type: "textarea"
1145
+ },
1146
+ {
1147
+ name: "itemsPerRow",
1148
+ type: "number",
1149
+ defaultValue: 2,
1150
+ min: 1,
1151
+ max: 4,
1152
+ admin: {
1153
+ description: "How many project cards to show per row on desktop.",
1154
+ step: 1
1155
+ }
1156
+ },
1157
+ {
1158
+ name: "items",
1159
+ type: "array",
1160
+ minRows: 1,
1161
+ maxRows: 30,
1162
+ fields: [
1163
+ {
1164
+ name: "label",
1165
+ type: "text",
1166
+ required: true
1167
+ },
1168
+ {
567
1169
  name: "beforeMedia",
568
1170
  type: "upload",
569
1171
  relationTo: "media",
570
1172
  required: false
571
1173
  },
1174
+ {
1175
+ name: "beforeImageURL",
1176
+ type: "text",
1177
+ admin: {
1178
+ description: "Optional direct URL for the before image when using an external asset."
1179
+ }
1180
+ },
572
1181
  {
573
1182
  name: "afterMedia",
574
1183
  type: "upload",
575
1184
  relationTo: "media",
576
1185
  required: false
577
1186
  },
1187
+ {
1188
+ name: "afterImageURL",
1189
+ type: "text",
1190
+ admin: {
1191
+ description: "Optional direct URL for the after image when using an external asset."
1192
+ }
1193
+ },
1194
+ {
1195
+ name: "imageHeight",
1196
+ type: "number",
1197
+ defaultValue: 160,
1198
+ min: 60,
1199
+ max: 600,
1200
+ admin: {
1201
+ description: "Overrides the before/after image height (in pixels).",
1202
+ step: 10
1203
+ }
1204
+ },
1205
+ {
1206
+ name: "imageFit",
1207
+ type: "select",
1208
+ defaultValue: "cover",
1209
+ options: [
1210
+ { label: "Cover", value: "cover" },
1211
+ { label: "Contain", value: "contain" }
1212
+ ]
1213
+ },
1214
+ {
1215
+ name: "imageCornerStyle",
1216
+ type: "select",
1217
+ defaultValue: "rounded",
1218
+ options: [
1219
+ { label: "Rounded", value: "rounded" },
1220
+ { label: "Square", value: "square" }
1221
+ ]
1222
+ },
1223
+ {
1224
+ name: "imagePosition",
1225
+ type: "select",
1226
+ defaultValue: "center",
1227
+ options: [
1228
+ { label: "Center", value: "center" },
1229
+ { label: "Top", value: "top" },
1230
+ { label: "Bottom", value: "bottom" },
1231
+ { label: "Left", value: "left" },
1232
+ { label: "Right", value: "right" }
1233
+ ]
1234
+ },
1235
+ {
1236
+ name: "imagePositionX",
1237
+ type: "number",
1238
+ min: 0,
1239
+ max: 100,
1240
+ admin: {
1241
+ description: "Optional custom horizontal focus (0-100). Overrides Image Position when set.",
1242
+ step: 1
1243
+ }
1244
+ },
1245
+ {
1246
+ name: "imagePositionY",
1247
+ type: "number",
1248
+ min: 0,
1249
+ max: 100,
1250
+ admin: {
1251
+ description: "Optional custom vertical focus (0-100). Overrides Image Position when set.",
1252
+ step: 1
1253
+ }
1254
+ },
578
1255
  {
579
1256
  name: "description",
580
1257
  type: "textarea"
1258
+ },
1259
+ {
1260
+ name: "settings",
1261
+ type: "json",
1262
+ admin: {
1263
+ hidden: true
1264
+ }
581
1265
  }
582
1266
  ]
583
1267
  },
1268
+ {
1269
+ name: "settings",
1270
+ type: "json",
1271
+ admin: {
1272
+ description: "Internal builder settings schema v2.",
1273
+ hidden: true
1274
+ }
1275
+ },
584
1276
  ...sectionStyleFields()
585
1277
  ]
586
1278
  };
@@ -618,6 +1310,14 @@ var BookingEmbedBlock = {
618
1310
  type: "text",
619
1311
  defaultValue: "/contact"
620
1312
  },
1313
+ {
1314
+ name: "settings",
1315
+ type: "json",
1316
+ admin: {
1317
+ description: "Internal builder settings schema v2.",
1318
+ hidden: true
1319
+ }
1320
+ },
621
1321
  ...sectionStyleFields()
622
1322
  ]
623
1323
  };
@@ -646,6 +1346,10 @@ var CtaBlock = {
646
1346
  name: "description",
647
1347
  type: "textarea"
648
1348
  },
1349
+ {
1350
+ name: "eyebrow",
1351
+ type: "text"
1352
+ },
649
1353
  {
650
1354
  name: "buttonLabel",
651
1355
  type: "text"
@@ -676,7 +1380,39 @@ var CtaBlock = {
676
1380
  name: "backgroundColor",
677
1381
  type: "text",
678
1382
  admin: {
679
- description: "Optional background color override for the CTA strip (example: #124a37)."
1383
+ description: "Optional background color override for the CTA strip (example: #334b63)."
1384
+ }
1385
+ },
1386
+ {
1387
+ name: "media",
1388
+ type: "upload",
1389
+ relationTo: "media",
1390
+ required: false
1391
+ },
1392
+ {
1393
+ name: "imageURL",
1394
+ type: "text",
1395
+ admin: {
1396
+ description: "Optional direct image URL when this CTA should use an external image."
1397
+ }
1398
+ },
1399
+ {
1400
+ name: "bullets",
1401
+ type: "array",
1402
+ fields: [
1403
+ {
1404
+ name: "label",
1405
+ type: "text",
1406
+ required: true
1407
+ }
1408
+ ]
1409
+ },
1410
+ {
1411
+ name: "settings",
1412
+ type: "json",
1413
+ admin: {
1414
+ description: "Internal builder settings schema v2.",
1415
+ hidden: true
680
1416
  }
681
1417
  },
682
1418
  ...sectionStyleFields()
@@ -698,6 +1434,10 @@ var FaqBlock = {
698
1434
  plural: "FAQs"
699
1435
  },
700
1436
  fields: [
1437
+ {
1438
+ name: "eyebrow",
1439
+ type: "text"
1440
+ },
701
1441
  {
702
1442
  name: "title",
703
1443
  type: "text",
@@ -718,9 +1458,24 @@ var FaqBlock = {
718
1458
  name: "answer",
719
1459
  type: "textarea",
720
1460
  required: true
1461
+ },
1462
+ {
1463
+ name: "settings",
1464
+ type: "json",
1465
+ admin: {
1466
+ hidden: true
1467
+ }
721
1468
  }
722
1469
  ]
723
1470
  },
1471
+ {
1472
+ name: "settings",
1473
+ type: "json",
1474
+ admin: {
1475
+ description: "Internal builder settings schema v2.",
1476
+ hidden: true
1477
+ }
1478
+ },
724
1479
  ...sectionStyleFields()
725
1480
  ]
726
1481
  };
@@ -740,10 +1495,28 @@ var FeatureGridBlock = {
740
1495
  plural: "Feature Grids"
741
1496
  },
742
1497
  fields: [
1498
+ {
1499
+ name: "eyebrow",
1500
+ type: "text"
1501
+ },
743
1502
  {
744
1503
  name: "title",
745
- type: "text",
746
- required: true
1504
+ type: "text"
1505
+ },
1506
+ {
1507
+ name: "subtitle",
1508
+ type: "textarea"
1509
+ },
1510
+ {
1511
+ name: "itemsPerRow",
1512
+ type: "number",
1513
+ defaultValue: 3,
1514
+ min: 1,
1515
+ max: 6,
1516
+ admin: {
1517
+ description: "How many items to show per row on desktop.",
1518
+ step: 1
1519
+ }
747
1520
  },
748
1521
  {
749
1522
  name: "items",
@@ -760,11 +1533,53 @@ var FeatureGridBlock = {
760
1533
  name: "description",
761
1534
  type: "textarea"
762
1535
  },
1536
+ {
1537
+ name: "tone",
1538
+ type: "select",
1539
+ defaultValue: "warm",
1540
+ options: [
1541
+ { label: "Warm", value: "warm" },
1542
+ { label: "Cool", value: "cool" },
1543
+ { label: "Neutral", value: "neutral" }
1544
+ ]
1545
+ },
1546
+ {
1547
+ name: "iconType",
1548
+ type: "select",
1549
+ defaultValue: "badge",
1550
+ options: [
1551
+ { label: "Badge Text", value: "badge" },
1552
+ { label: "Icon", value: "lucide" }
1553
+ ],
1554
+ admin: {
1555
+ description: "Choose whether this item uses a short badge label or an icon."
1556
+ }
1557
+ },
763
1558
  {
764
1559
  name: "icon",
765
1560
  type: "text",
766
1561
  admin: {
767
- description: 'Optional short icon label (e.g. "01", "Leaf", "Star").'
1562
+ description: 'Optional short icon label (e.g. "01", "Leaf", "Star").',
1563
+ condition: (_, siblingData) => siblingData?.iconType !== "lucide"
1564
+ }
1565
+ },
1566
+ {
1567
+ name: "iconLucide",
1568
+ type: "select",
1569
+ options: [
1570
+ { label: "Shield Check", value: "ShieldCheck" },
1571
+ { label: "Clock", value: "Clock" },
1572
+ { label: "Leaf", value: "Leaf" },
1573
+ { label: "Dollar Sign", value: "DollarSign" },
1574
+ { label: "Hard Hat", value: "HardHat" },
1575
+ { label: "Sparkles", value: "Sparkles" },
1576
+ { label: "Tree Pine", value: "TreePine" },
1577
+ { label: "Badge Check", value: "BadgeCheck" },
1578
+ { label: "Calendar Clock", value: "CalendarClock" }
1579
+ ],
1580
+ admin: {
1581
+ description: "Select an icon for this item.",
1582
+ condition: (_, siblingData) => siblingData?.iconType === "lucide"
768
1583
  }
769
1584
  },
770
1585
  {
@@ -772,6 +1587,107 @@ var FeatureGridBlock = {
772
1587
  type: "upload",
773
1588
  relationTo: "media",
774
1589
  required: false
1590
+ },
1591
+ {
1592
+ name: "imageURL",
1593
+ type: "text",
1594
+ admin: {
1595
+ description: "Optional direct image URL when this item should use an external image."
1596
+ }
1597
+ },
1598
+ {
1599
+ name: "embedURL",
1600
+ type: "text",
1601
+ admin: {
1602
+ description: "Optional iframe/embed URL for items like maps or other embedded content."
1603
+ }
1604
+ },
1605
+ {
1606
+ name: "buttonLabel",
1607
+ type: "text"
1608
+ },
1609
+ {
1610
+ name: "buttonHref",
1611
+ type: "text"
1612
+ },
1613
+ {
1614
+ name: "bullets",
1615
+ type: "array",
1616
+ fields: [
1617
+ {
1618
+ name: "label",
1619
+ type: "text",
1620
+ required: true
1621
+ }
1622
+ ]
1623
+ },
1624
+ {
1625
+ name: "imageHeight",
1626
+ type: "number",
1627
+ defaultValue: 160,
1628
+ min: 40,
1629
+ max: 600,
1630
+ admin: {
1631
+ description: "Overrides the image height in the builder/section (in pixels).",
1632
+ step: 10
1633
+ }
1634
+ },
1635
+ {
1636
+ name: "imageFit",
1637
+ type: "select",
1638
+ defaultValue: "cover",
1639
+ options: [
1640
+ { label: "Cover", value: "cover" },
1641
+ { label: "Contain", value: "contain" }
1642
+ ]
1643
+ },
1644
+ {
1645
+ name: "imageCornerStyle",
1646
+ type: "select",
1647
+ defaultValue: "rounded",
1648
+ options: [
1649
+ { label: "Rounded", value: "rounded" },
1650
+ { label: "Square", value: "square" }
1651
+ ]
1652
+ },
1653
+ {
1654
+ name: "imagePosition",
1655
+ type: "select",
1656
+ defaultValue: "center",
1657
+ options: [
1658
+ { label: "Center", value: "center" },
1659
+ { label: "Top", value: "top" },
1660
+ { label: "Bottom", value: "bottom" },
1661
+ { label: "Left", value: "left" },
1662
+ { label: "Right", value: "right" }
1663
+ ]
1664
+ },
1665
+ {
1666
+ name: "imagePositionX",
1667
+ type: "number",
1668
+ min: 0,
1669
+ max: 100,
1670
+ admin: {
1671
+ description: "Optional custom horizontal focus (0-100). Overrides Image Position when set.",
1672
+ step: 1
1673
+ }
1674
+ },
1675
+ {
1676
+ name: "imagePositionY",
1677
+ type: "number",
1678
+ min: 0,
1679
+ max: 100,
1680
+ admin: {
1681
+ description: "Optional custom vertical focus (0-100). Overrides Image Position when set.",
1682
+ step: 1
1683
+ }
1684
+ },
1685
+ {
1686
+ name: "settings",
1687
+ type: "json",
1688
+ admin: {
1689
+ hidden: true
1690
+ }
775
1691
  }
776
1692
  ]
777
1693
  },
@@ -787,6 +1703,22 @@ var FeatureGridBlock = {
787
1703
  {
788
1704
  label: "Highlight",
789
1705
  value: "highlight"
1706
+ },
1707
+ {
1708
+ label: "Split List",
1709
+ value: "splitList"
1710
+ },
1711
+ {
1712
+ label: "Panels",
1713
+ value: "panels"
1714
+ },
1715
+ {
1716
+ label: "Catalog",
1717
+ value: "catalog"
1718
+ },
1719
+ {
1720
+ label: "Contact Split",
1721
+ value: "contact"
790
1722
  }
791
1723
  ]
792
1724
  },
@@ -794,7 +1726,15 @@ var FeatureGridBlock = {
794
1726
  name: "backgroundColor",
795
1727
  type: "text",
796
1728
  admin: {
797
- description: "Optional background color override when using the Highlight variant (example: #1f684f)."
1729
+ description: "Optional background color override when using the Highlight variant (example: #334b63)."
1730
+ }
1731
+ },
1732
+ {
1733
+ name: "settings",
1734
+ type: "json",
1735
+ admin: {
1736
+ description: "Internal builder settings schema v2.",
1737
+ hidden: true
798
1738
  }
799
1739
  },
800
1740
  ...sectionStyleFields()
@@ -816,6 +1756,10 @@ var FormEmbedBlock = {
816
1756
  plural: "Form Embeds"
817
1757
  },
818
1758
  fields: [
1759
+ {
1760
+ name: "eyebrow",
1761
+ type: "text"
1762
+ },
819
1763
  {
820
1764
  name: "title",
821
1765
  type: "text"
@@ -824,6 +1768,10 @@ var FormEmbedBlock = {
824
1768
  name: "description",
825
1769
  type: "textarea"
826
1770
  },
1771
+ {
1772
+ name: "submitLabel",
1773
+ type: "text"
1774
+ },
827
1775
  {
828
1776
  name: "formType",
829
1777
  type: "select",
@@ -835,6 +1783,14 @@ var FormEmbedBlock = {
835
1783
  }
836
1784
  ]
837
1785
  },
1786
+ {
1787
+ name: "settings",
1788
+ type: "json",
1789
+ admin: {
1790
+ description: "Internal builder settings schema v2.",
1791
+ hidden: true
1792
+ }
1793
+ },
838
1794
  ...sectionStyleFields()
839
1795
  ]
840
1796
  };
@@ -897,18 +1853,39 @@ var HeroBlock = {
897
1853
  type: "upload",
898
1854
  relationTo: "media"
899
1855
  },
1856
+ {
1857
+ name: "backgroundImageURL",
1858
+ type: "text",
1859
+ admin: {
1860
+ description: "Optional direct image URL when the hero should use an external image source."
1861
+ }
1862
+ },
900
1863
  {
901
1864
  name: "backgroundImageFit",
902
1865
  type: "select",
903
1866
  defaultValue: "cover",
904
1867
  options: [
905
1868
  { label: "Cover", value: "cover" },
906
- { label: "Contain", value: "contain" }
1869
+ { label: "Cover (Square)", value: "cover-square" },
1870
+ { label: "Contain", value: "contain" },
1871
+ { label: "Contain (Square)", value: "contain-square" }
907
1872
  ],
908
1873
  admin: {
909
1874
  description: "How the hero image should be sized within the section."
910
1875
  }
911
1876
  },
1877
+ {
1878
+ name: "backgroundImageCornerStyle",
1879
+ type: "select",
1880
+ defaultValue: "rounded",
1881
+ options: [
1882
+ { label: "Rounded", value: "rounded" },
1883
+ { label: "Square", value: "square" }
1884
+ ],
1885
+ admin: {
1886
+ description: "How the hero image corners should appear."
1887
+ }
1888
+ },
912
1889
  {
913
1890
  name: "backgroundImagePosition",
914
1891
  type: "select",
@@ -928,7 +1905,114 @@ var HeroBlock = {
928
1905
  name: "backgroundColor",
929
1906
  type: "text",
930
1907
  admin: {
931
- description: "Optional background color override (example: #124a37)."
1908
+ description: "Optional background color override (example: #334b63)."
1909
+ }
1910
+ },
1911
+ {
1912
+ name: "backgroundOverlayMode",
1913
+ type: "select",
1914
+ defaultValue: "none",
1915
+ options: [
1916
+ { label: "None", value: "none" },
1917
+ { label: "Solid", value: "solid" },
1918
+ { label: "Gradient", value: "gradient" }
1919
+ ],
1920
+ admin: {
1921
+ description: "Optional overlay on top of the hero image (applies when an image is present)."
1922
+ }
1923
+ },
1924
+ {
1925
+ name: "backgroundOverlayOpacity",
1926
+ type: "number",
1927
+ defaultValue: 45,
1928
+ min: 0,
1929
+ max: 100,
1930
+ admin: {
1931
+ description: "Overlay opacity (0-100).",
1932
+ step: 1
1933
+ }
1934
+ },
1935
+ {
1936
+ name: "backgroundOverlayColor",
1937
+ type: "text",
1938
+ admin: {
1939
+ description: "Overlay solid color (example: #000000). Used when Overlay Mode is Solid."
1940
+ }
1941
+ },
1942
+ {
1943
+ name: "backgroundOverlayGradientFrom",
1944
+ type: "text",
1945
+ admin: {
1946
+ description: "Gradient overlay start color (example: #334b63). Used when Overlay Mode is Gradient."
1947
+ }
1948
+ },
1949
+ {
1950
+ name: "backgroundOverlayGradientTo",
1951
+ type: "text",
1952
+ admin: {
1953
+ description: "Gradient overlay end color (example: #496582). Used when Overlay Mode is Gradient."
1954
+ }
1955
+ },
1956
+ {
1957
+ name: "backgroundOverlayGradientAngle",
1958
+ type: "text",
1959
+ admin: {
1960
+ description: "Gradient overlay angle in degrees (0-360). Used when Overlay Mode is Gradient."
1961
+ }
1962
+ },
1963
+ {
1964
+ name: "backgroundOverlayGradientFromStrength",
1965
+ type: "number",
1966
+ defaultValue: 100,
1967
+ min: 0,
1968
+ max: 100,
1969
+ admin: {
1970
+ description: "Gradient start strength (0-100). Set to 0 for transparent.",
1971
+ step: 1
1972
+ }
1973
+ },
1974
+ {
1975
+ name: "backgroundOverlayGradientToStrength",
1976
+ type: "number",
1977
+ defaultValue: 100,
1978
+ min: 0,
1979
+ max: 100,
1980
+ admin: {
1981
+ description: "Gradient end strength (0-100). Set to 0 for transparent.",
1982
+ step: 1
1983
+ }
1984
+ },
1985
+ {
1986
+ name: "backgroundOverlayGradientStart",
1987
+ type: "number",
1988
+ defaultValue: 0,
1989
+ min: 0,
1990
+ max: 100,
1991
+ admin: {
1992
+ description: "Where the gradient starts (0-100).",
1993
+ step: 1
1994
+ }
1995
+ },
1996
+ {
1997
+ name: "backgroundOverlayGradientEnd",
1998
+ type: "number",
1999
+ defaultValue: 100,
2000
+ min: 0,
2001
+ max: 100,
2002
+ admin: {
2003
+ description: "Where the gradient ends (0-100).",
2004
+ step: 1
2005
+ }
2006
+ },
2007
+ {
2008
+ name: "backgroundOverlayGradientFeather",
2009
+ type: "number",
2010
+ defaultValue: 100,
2011
+ min: 0,
2012
+ max: 100,
2013
+ admin: {
2014
+ description: "How soft the transition is (0 = hard edge, 100 = smooth).",
2015
+ step: 1
932
2016
  }
933
2017
  },
934
2018
  {
@@ -946,6 +2030,36 @@ var HeroBlock = {
946
2030
  }
947
2031
  ]
948
2032
  },
2033
+ {
2034
+ name: "heroHeight",
2035
+ type: "select",
2036
+ defaultValue: "sm",
2037
+ options: [
2038
+ {
2039
+ label: "Small",
2040
+ value: "sm"
2041
+ },
2042
+ {
2043
+ label: "Medium (Half Screen)",
2044
+ value: "md"
2045
+ },
2046
+ {
2047
+ label: "Full Screen",
2048
+ value: "full"
2049
+ }
2050
+ ],
2051
+ admin: {
2052
+ description: "Controls the vertical height of the hero section."
2053
+ }
2054
+ },
2055
+ {
2056
+ name: "settings",
2057
+ type: "json",
2058
+ admin: {
2059
+ description: "Internal builder settings schema v2.",
2060
+ hidden: true
2061
+ }
2062
+ },
949
2063
  ...sectionStyleFields()
950
2064
  ]
951
2065
  };
@@ -991,12 +2105,95 @@ var LogoWallBlock = {
991
2105
  relationTo: "media",
992
2106
  required: false
993
2107
  },
2108
+ {
2109
+ name: "imageURL",
2110
+ type: "text",
2111
+ admin: {
2112
+ description: "Optional direct image URL for this logo when using an external asset."
2113
+ }
2114
+ },
2115
+ {
2116
+ name: "imageHeight",
2117
+ type: "number",
2118
+ defaultValue: 64,
2119
+ min: 24,
2120
+ max: 200,
2121
+ admin: {
2122
+ description: "Overrides the logo image height (in pixels).",
2123
+ step: 4
2124
+ }
2125
+ },
2126
+ {
2127
+ name: "imageFit",
2128
+ type: "select",
2129
+ defaultValue: "contain",
2130
+ options: [
2131
+ { label: "Cover", value: "cover" },
2132
+ { label: "Contain", value: "contain" }
2133
+ ]
2134
+ },
2135
+ {
2136
+ name: "imageCornerStyle",
2137
+ type: "select",
2138
+ defaultValue: "rounded",
2139
+ options: [
2140
+ { label: "Rounded", value: "rounded" },
2141
+ { label: "Square", value: "square" }
2142
+ ]
2143
+ },
2144
+ {
2145
+ name: "imagePosition",
2146
+ type: "select",
2147
+ defaultValue: "center",
2148
+ options: [
2149
+ { label: "Center", value: "center" },
2150
+ { label: "Top", value: "top" },
2151
+ { label: "Bottom", value: "bottom" },
2152
+ { label: "Left", value: "left" },
2153
+ { label: "Right", value: "right" }
2154
+ ]
2155
+ },
2156
+ {
2157
+ name: "imagePositionX",
2158
+ type: "number",
2159
+ min: 0,
2160
+ max: 100,
2161
+ admin: {
2162
+ description: "Optional custom horizontal focus (0-100). Overrides Image Position when set.",
2163
+ step: 1
2164
+ }
2165
+ },
2166
+ {
2167
+ name: "imagePositionY",
2168
+ type: "number",
2169
+ min: 0,
2170
+ max: 100,
2171
+ admin: {
2172
+ description: "Optional custom vertical focus (0-100). Overrides Image Position when set.",
2173
+ step: 1
2174
+ }
2175
+ },
994
2176
  {
995
2177
  name: "href",
996
2178
  type: "text"
2179
+ },
2180
+ {
2181
+ name: "settings",
2182
+ type: "json",
2183
+ admin: {
2184
+ hidden: true
2185
+ }
997
2186
  }
998
2187
  ]
999
2188
  },
2189
+ {
2190
+ name: "settings",
2191
+ type: "json",
2192
+ admin: {
2193
+ description: "Internal builder settings schema v2.",
2194
+ hidden: true
2195
+ }
2196
+ },
1000
2197
  ...sectionStyleFields()
1001
2198
  ]
1002
2199
  };
@@ -1020,7 +2217,14 @@ var MediaBlock = {
1020
2217
  name: "image",
1021
2218
  type: "upload",
1022
2219
  relationTo: "media",
1023
- required: true
2220
+ required: false
2221
+ },
2222
+ {
2223
+ name: "imageURL",
2224
+ type: "text",
2225
+ admin: {
2226
+ description: "Optional direct image URL when this section should use an external image."
2227
+ }
1024
2228
  },
1025
2229
  {
1026
2230
  name: "caption",
@@ -1041,6 +2245,44 @@ var MediaBlock = {
1041
2245
  }
1042
2246
  ]
1043
2247
  },
2248
+ {
2249
+ name: "imageFit",
2250
+ type: "select",
2251
+ defaultValue: "cover",
2252
+ options: [
2253
+ { label: "Cover", value: "cover" },
2254
+ { label: "Contain", value: "contain" }
2255
+ ]
2256
+ },
2257
+ {
2258
+ name: "imageCornerStyle",
2259
+ type: "select",
2260
+ defaultValue: "rounded",
2261
+ options: [
2262
+ { label: "Rounded", value: "rounded" },
2263
+ { label: "Square", value: "square" }
2264
+ ]
2265
+ },
2266
+ {
2267
+ name: "imagePosition",
2268
+ type: "select",
2269
+ defaultValue: "center",
2270
+ options: [
2271
+ { label: "Center", value: "center" },
2272
+ { label: "Top", value: "top" },
2273
+ { label: "Bottom", value: "bottom" },
2274
+ { label: "Left", value: "left" },
2275
+ { label: "Right", value: "right" }
2276
+ ]
2277
+ },
2278
+ {
2279
+ name: "settings",
2280
+ type: "json",
2281
+ admin: {
2282
+ description: "Internal builder settings schema v2.",
2283
+ hidden: true
2284
+ }
2285
+ },
1044
2286
  ...sectionStyleFields()
1045
2287
  ]
1046
2288
  };
@@ -1060,6 +2302,21 @@ var RichTextBlock = {
1060
2302
  plural: "Rich Text Sections"
1061
2303
  },
1062
2304
  fields: [
2305
+ {
2306
+ name: "variant",
2307
+ type: "select",
2308
+ defaultValue: "default",
2309
+ options: [
2310
+ {
2311
+ label: "Default",
2312
+ value: "default"
2313
+ },
2314
+ {
2315
+ label: "Quote Banner",
2316
+ value: "quoteBanner"
2317
+ }
2318
+ ]
2319
+ },
1063
2320
  {
1064
2321
  name: "title",
1065
2322
  type: "text"
@@ -1069,6 +2326,54 @@ var RichTextBlock = {
1069
2326
  type: "richText",
1070
2327
  required: true
1071
2328
  },
2329
+ {
2330
+ name: "statsItems",
2331
+ type: "array",
2332
+ fields: [
2333
+ {
2334
+ name: "value",
2335
+ type: "text",
2336
+ required: true
2337
+ },
2338
+ {
2339
+ name: "label",
2340
+ type: "text",
2341
+ required: true
2342
+ }
2343
+ ]
2344
+ },
2345
+ {
2346
+ name: "cards",
2347
+ type: "array",
2348
+ fields: [
2349
+ {
2350
+ name: "eyebrow",
2351
+ type: "text"
2352
+ },
2353
+ {
2354
+ name: "title",
2355
+ type: "text",
2356
+ required: true
2357
+ },
2358
+ {
2359
+ name: "description",
2360
+ type: "textarea"
2361
+ },
2362
+ {
2363
+ name: "media",
2364
+ type: "upload",
2365
+ relationTo: "media",
2366
+ required: false
2367
+ },
2368
+ {
2369
+ name: "imageURL",
2370
+ type: "text",
2371
+ admin: {
2372
+ description: "Optional direct image URL when this card should use an external image."
2373
+ }
2374
+ }
2375
+ ]
2376
+ },
1072
2377
  {
1073
2378
  name: "width",
1074
2379
  type: "select",
@@ -1084,6 +2389,14 @@ var RichTextBlock = {
1084
2389
  }
1085
2390
  ]
1086
2391
  },
2392
+ {
2393
+ name: "settings",
2394
+ type: "json",
2395
+ admin: {
2396
+ description: "Internal builder settings schema v2.",
2397
+ hidden: true
2398
+ }
2399
+ },
1087
2400
  ...sectionStyleFields()
1088
2401
  ]
1089
2402
  };
@@ -1131,9 +2444,24 @@ var StatsBlock = {
1131
2444
  {
1132
2445
  name: "description",
1133
2446
  type: "text"
2447
+ },
2448
+ {
2449
+ name: "settings",
2450
+ type: "json",
2451
+ admin: {
2452
+ hidden: true
2453
+ }
1134
2454
  }
1135
2455
  ]
1136
2456
  },
2457
+ {
2458
+ name: "settings",
2459
+ type: "json",
2460
+ admin: {
2461
+ description: "Internal builder settings schema v2.",
2462
+ hidden: true
2463
+ }
2464
+ },
1137
2465
  ...sectionStyleFields()
1138
2466
  ]
1139
2467
  };
@@ -1158,11 +2486,41 @@ var TestimonialsBlock = {
1158
2486
  type: "text",
1159
2487
  required: true
1160
2488
  },
2489
+ {
2490
+ name: "visibleCount",
2491
+ type: "number",
2492
+ defaultValue: 3,
2493
+ min: 1,
2494
+ max: 6,
2495
+ admin: {
2496
+ description: "How many testimonials to show at once.",
2497
+ step: 1
2498
+ }
2499
+ },
2500
+ {
2501
+ name: "autoRotate",
2502
+ type: "checkbox",
2503
+ defaultValue: true,
2504
+ admin: {
2505
+ description: "Automatically rotates through all testimonials."
2506
+ }
2507
+ },
2508
+ {
2509
+ name: "rotateIntervalSeconds",
2510
+ type: "number",
2511
+ defaultValue: 7,
2512
+ min: 2,
2513
+ max: 30,
2514
+ admin: {
2515
+ description: "How often to rotate (in seconds).",
2516
+ step: 1
2517
+ }
2518
+ },
1161
2519
  {
1162
2520
  name: "items",
1163
2521
  type: "array",
1164
2522
  minRows: 1,
1165
- maxRows: 6,
2523
+ maxRows: 30,
1166
2524
  fields: [
1167
2525
  {
1168
2526
  name: "quote",
@@ -1177,9 +2535,35 @@ var TestimonialsBlock = {
1177
2535
  {
1178
2536
  name: "location",
1179
2537
  type: "text"
2538
+ },
2539
+ {
2540
+ name: "rating",
2541
+ type: "number",
2542
+ defaultValue: 5,
2543
+ min: 1,
2544
+ max: 5,
2545
+ admin: {
2546
+ description: "Star rating (1-5).",
2547
+ step: 1
2548
+ }
2549
+ },
2550
+ {
2551
+ name: "settings",
2552
+ type: "json",
2553
+ admin: {
2554
+ hidden: true
2555
+ }
1180
2556
  }
1181
2557
  ]
1182
2558
  },
2559
+ {
2560
+ name: "settings",
2561
+ type: "json",
2562
+ admin: {
2563
+ description: "Internal builder settings schema v2.",
2564
+ hidden: true
2565
+ }
2566
+ },
1183
2567
  ...sectionStyleFields()
1184
2568
  ]
1185
2569
  };
@@ -1244,18 +2628,18 @@ var sectionPresets = [
1244
2628
  blocks: [
1245
2629
  {
1246
2630
  blockType: "hero",
1247
- kicker: "Licensed + Insured",
1248
- headline: "Expert Tree Care for Central Texas",
1249
- subheadline: "Reliable trimming, safe removals, and stump grinding for residential and commercial properties.",
1250
- primaryLabel: "Get Your Free Quote",
2631
+ kicker: "Trusted Team",
2632
+ headline: "A clear headline for your primary offer",
2633
+ subheadline: "Explain what you offer, who it is for, and why it matters in one strong supporting sentence.",
2634
+ primaryLabel: "Get Started",
1251
2635
  primaryHref: "/contact",
1252
- secondaryLabel: "See Our Work",
1253
- secondaryHref: "/portfolio"
2636
+ secondaryLabel: "Learn More",
2637
+ secondaryHref: "/about"
1254
2638
  },
1255
2639
  {
1256
2640
  blockType: "cta",
1257
- headline: "Need a quote this week?",
1258
- description: "Call (512) 555-0149 or request an on-site estimate online.",
2641
+ headline: "Ready for the next step?",
2642
+ description: "Use a short call to action with a direct path to contact or purchase.",
1259
2643
  buttonLabel: "Contact Us",
1260
2644
  buttonHref: "/contact",
1261
2645
  style: "light"
@@ -1273,19 +2657,19 @@ var sectionPresets = [
1273
2657
  variant: "cards",
1274
2658
  items: [
1275
2659
  {
1276
- title: "Tree Trimming & Pruning",
1277
- description: "Canopy balancing, deadwood removal, and seasonal pruning.",
1278
- icon: "Trim"
2660
+ title: "Service One",
2661
+ description: "Briefly describe this offer or outcome.",
2662
+ icon: "01"
1279
2663
  },
1280
2664
  {
1281
- title: "Safe Tree Removal",
1282
- description: "Controlled removal for hazardous or unstable trees.",
1283
- icon: "Remove"
2665
+ title: "Service Two",
2666
+ description: "Briefly describe this offer or outcome.",
2667
+ icon: "02"
1284
2668
  },
1285
2669
  {
1286
- title: "Stump Grinding",
1287
- description: "Below-grade stump grinding and cleanup.",
1288
- icon: "Stump"
2670
+ title: "Service Three",
2671
+ description: "Briefly describe this offer or outcome.",
2672
+ icon: "03"
1289
2673
  }
1290
2674
  ]
1291
2675
  }
@@ -1301,14 +2685,16 @@ var sectionPresets = [
1301
2685
  title: "What Homeowners Say",
1302
2686
  items: [
1303
2687
  {
1304
- quote: "Great communication, fair pricing, and the cleanup was perfect. We will use them again.",
1305
- name: "Katie M.",
1306
- location: "Austin, TX"
2688
+ quote: "The experience was smooth, thoughtful, and exactly what we hoped for.",
2689
+ name: "Customer Name",
2690
+ location: "City, ST",
2691
+ rating: 5
1307
2692
  },
1308
2693
  {
1309
- quote: "They removed a dangerous limb over our driveway quickly and safely.",
1310
- name: "James R.",
1311
- location: "Round Rock, TX"
2694
+ quote: "Fast communication, strong service, and a result we would happily recommend.",
2695
+ name: "Customer Name",
2696
+ location: "City, ST",
2697
+ rating: 5
1312
2698
  }
1313
2699
  ]
1314
2700
  },
@@ -1317,12 +2703,12 @@ var sectionPresets = [
1317
2703
  title: "Common Questions",
1318
2704
  items: [
1319
2705
  {
1320
- question: "How quickly can you schedule service?",
1321
- answer: "Most estimate requests are scheduled within 24 hours."
2706
+ question: "How quickly do you respond?",
2707
+ answer: "Replace this with a concise answer to a common customer question."
1322
2708
  },
1323
2709
  {
1324
- question: "Do you provide cleanup?",
1325
- answer: "Yes. Debris haul-off and cleanup are included in quoted scopes."
2710
+ question: "What is included?",
2711
+ answer: "Replace this with another concise answer that reduces buying friction."
1326
2712
  }
1327
2713
  ]
1328
2714
  }
@@ -1336,13 +2722,13 @@ var sectionPresets = [
1336
2722
  {
1337
2723
  blockType: "formEmbed",
1338
2724
  title: "Request a Quote",
1339
- description: "Share your project details and we will follow up quickly.",
2725
+ description: "Share a few details and your team can follow up with next steps.",
1340
2726
  formType: "quote"
1341
2727
  },
1342
2728
  {
1343
2729
  blockType: "bookingEmbed",
1344
2730
  title: "Prefer to book a consultation?",
1345
- description: "Choose a time window and we will confirm availability.",
2731
+ description: "Offer an alternative scheduling path for visitors who prefer to book directly.",
1346
2732
  buttonLabel: "Book Consultation",
1347
2733
  buttonHref: "/contact"
1348
2734
  }
@@ -1354,14 +2740,14 @@ var templateStarterPresets = {
1354
2740
  {
1355
2741
  blockType: "hero",
1356
2742
  headline: "Contact Us",
1357
- subheadline: "Request a quote, ask a question, or book a consultation window.",
1358
- primaryLabel: "Call (512) 555-0149",
1359
- primaryHref: "tel:+15125550149"
2743
+ subheadline: "Tell visitors exactly how to reach you and what to expect next.",
2744
+ primaryLabel: "Email Us",
2745
+ primaryHref: "mailto:hello@example.com"
1360
2746
  },
1361
2747
  {
1362
2748
  blockType: "formEmbed",
1363
2749
  title: "Request a Quote",
1364
- description: "Tell us about your project and we will follow up quickly.",
2750
+ description: "Use this space for a form embed or lead capture flow.",
1365
2751
  formType: "quote"
1366
2752
  },
1367
2753
  {
@@ -1369,8 +2755,8 @@ var templateStarterPresets = {
1369
2755
  title: "Common Questions",
1370
2756
  items: [
1371
2757
  {
1372
- question: "How quickly can you provide an estimate?",
1373
- answer: "Most estimates are scheduled within 24 hours."
2758
+ question: "How quickly will you respond?",
2759
+ answer: "Replace with the answer that best fits your operating process."
1374
2760
  }
1375
2761
  ]
1376
2762
  }
@@ -1379,27 +2765,27 @@ var templateStarterPresets = {
1379
2765
  {
1380
2766
  blockType: "hero",
1381
2767
  kicker: "Locally Owned",
1382
- headline: "Expert Tree Care for Central Texas",
1383
- subheadline: "Premium trimming, removal, and cleanup with safety-first execution.",
1384
- primaryLabel: "Get Your Free Quote",
2768
+ headline: "Lead with your strongest offer",
2769
+ subheadline: "Support the headline with a concise sentence that clarifies benefits and audience.",
2770
+ primaryLabel: "Get Started",
1385
2771
  primaryHref: "/contact",
1386
2772
  secondaryLabel: "View Services",
1387
2773
  secondaryHref: "/services"
1388
2774
  },
1389
2775
  {
1390
2776
  blockType: "featureGrid",
1391
- title: "Why Homeowners Choose Us",
2777
+ title: "Why clients choose us",
1392
2778
  variant: "highlight",
1393
2779
  items: [
1394
- { title: "Transparent Pricing", description: "Clear written estimates.", icon: "01" },
1395
- { title: "Safety-First Crew", description: "Property protection and planning.", icon: "02" },
1396
- { title: "Fast Scheduling", description: "Quick estimates and service windows.", icon: "03" }
2780
+ { title: "Clear Value", description: "Explain your first differentiator.", icon: "01" },
2781
+ { title: "Reliable Process", description: "Explain your second differentiator.", icon: "02" },
2782
+ { title: "Strong Results", description: "Explain your third differentiator.", icon: "03" }
1397
2783
  ]
1398
2784
  },
1399
2785
  {
1400
2786
  blockType: "cta",
1401
- headline: "Need a quote this week?",
1402
- description: "Call (512) 555-0149 or request an estimate online.",
2787
+ headline: "Ready to take the next step?",
2788
+ description: "Add a direct conversion prompt with a clear primary action.",
1403
2789
  buttonLabel: "Contact Us",
1404
2790
  buttonHref: "/contact",
1405
2791
  style: "light"
@@ -1408,9 +2794,9 @@ var templateStarterPresets = {
1408
2794
  services: [
1409
2795
  {
1410
2796
  blockType: "hero",
1411
- headline: "Tree Services Built for Safety and Curb Appeal",
1412
- subheadline: "Core offerings first, with clear scopes and scheduling.",
1413
- primaryLabel: "Schedule Estimate",
2797
+ headline: "Services Built Around Your Process",
2798
+ subheadline: "Summarize the core offerings with a short clarity-first introduction.",
2799
+ primaryLabel: "Request Info",
1414
2800
  primaryHref: "/contact"
1415
2801
  },
1416
2802
  {
@@ -1419,19 +2805,19 @@ var templateStarterPresets = {
1419
2805
  variant: "cards",
1420
2806
  items: [
1421
2807
  {
1422
- title: "Tree Trimming & Pruning",
1423
- description: "Selective pruning for structure, clearance, and health.",
1424
- icon: "Trim"
2808
+ title: "Service One",
2809
+ description: "Replace with a short description.",
2810
+ icon: "01"
1425
2811
  },
1426
2812
  {
1427
- title: "Tree Removal",
1428
- description: "Controlled removal for unstable or hazardous trees.",
1429
- icon: "Remove"
2813
+ title: "Service Two",
2814
+ description: "Replace with a short description.",
2815
+ icon: "02"
1430
2816
  },
1431
2817
  {
1432
- title: "Stump Grinding",
1433
- description: "Below-grade grinding and cleanup.",
1434
- icon: "Stump"
2818
+ title: "Service Three",
2819
+ description: "Replace with a short description.",
2820
+ icon: "03"
1435
2821
  }
1436
2822
  ]
1437
2823
  },
@@ -1440,8 +2826,8 @@ var templateStarterPresets = {
1440
2826
  title: "Frequently Asked Questions",
1441
2827
  items: [
1442
2828
  {
1443
- question: "Do you handle storm cleanup?",
1444
- answer: "Yes. We prioritize urgent hazards after severe weather."
2829
+ question: "Do you offer custom scopes?",
2830
+ answer: "Replace this with an answer that fits your business."
1445
2831
  }
1446
2832
  ]
1447
2833
  }
@@ -1512,7 +2898,8 @@ __export(nextjs_exports, {
1512
2898
  createPageQueries: () => createPageQueries,
1513
2899
  createPayloadClient: () => createPayloadClient,
1514
2900
  createSiteQueries: () => createSiteQueries,
1515
- resolveMedia: () => resolveMedia
2901
+ resolveMedia: () => resolveMedia,
2902
+ resolveSocialMediaLinks: () => resolveSocialMediaLinks
1516
2903
  });
1517
2904
 
1518
2905
  // src/nextjs/client/payload.ts
@@ -1530,43 +2917,696 @@ function createPayloadClient(config) {
1530
2917
 
1531
2918
  // src/nextjs/queries/pages.ts
1532
2919
  var import_cache = require("next/cache");
1533
- function normalizePath(segments) {
1534
- if (!segments || segments.length === 0) {
1535
- return "/";
1536
- }
1537
- const cleaned = segments.map((segment) => segment.trim()).filter(Boolean).join("/");
1538
- return cleaned.length > 0 ? `/${cleaned}` : "/";
1539
- }
1540
- async function queryPageByPath(payload, path2, draft) {
1541
- const result = await payload.find({
2920
+
2921
+ // src/studio/index.ts
2922
+ var studio_exports = {};
2923
+ __export(studio_exports, {
2924
+ assertStudioDocumentV1: () => assertStudioDocumentV1,
2925
+ compileStudioDocument: () => compileStudioDocument,
2926
+ createEmptyStudioDocument: () => createEmptyStudioDocument,
2927
+ createImageUploadOptimizationHook: () => createImageUploadOptimizationHook,
2928
+ createStudioRegistry: () => createStudioRegistry,
2929
+ migrateStudioDocument: () => migrateStudioDocument,
2930
+ validateStudioDocument: () => validateStudioDocument,
2931
+ withImageUploadOptimization: () => withImageUploadOptimization
2932
+ });
2933
+
2934
+ // src/studio/imageUploadOptimization.ts
2935
+ var import_promises = require("fs/promises");
2936
+ var DEFAULT_SUPPORTED_MIME_TYPES = [
2937
+ "image/jpeg",
2938
+ "image/jpg",
2939
+ "image/png",
2940
+ "image/webp",
2941
+ "image/avif",
2942
+ "image/tiff"
2943
+ ];
2944
+ var DEFAULT_OPTIONS = {
2945
+ avifQuality: 50,
2946
+ enforceSmallerForLossy: true,
2947
+ jpegQuality: 78,
2948
+ minBytes: 48 * 1024,
2949
+ minQualityFloor: 42,
2950
+ onlyIfSmaller: true,
2951
+ pngCompressionLevel: 9,
2952
+ skipAnimated: true,
2953
+ tiffQuality: 75,
2954
+ webpQuality: 78
2955
+ };
2956
+ var clamp = (value, min, max) => Math.max(min, Math.min(max, Math.round(value)));
2957
+ var isUploadMutationOperation = (operation) => operation === "create" || operation === "update" || operation === "updateByID";
2958
+ var isLossyMimeType = (mimetype) => mimetype === "image/jpeg" || mimetype === "image/jpg" || mimetype === "image/webp" || mimetype === "image/avif" || mimetype === "image/tiff";
2959
+ var readIncomingBuffer = async (file) => {
2960
+ if (file.tempFilePath) {
2961
+ return (0, import_promises.readFile)(file.tempFilePath);
2962
+ }
2963
+ return file.data;
2964
+ };
2965
+ var writeOptimizedBuffer = async (file, buffer) => {
2966
+ if (file.tempFilePath) {
2967
+ await (0, import_promises.unlink)(file.tempFilePath).catch(() => void 0);
2968
+ }
2969
+ return {
2970
+ ...file,
2971
+ data: buffer,
2972
+ size: buffer.length,
2973
+ tempFilePath: void 0
2974
+ };
2975
+ };
2976
+ var createImageUploadOptimizationHook = (options = {}) => {
2977
+ const supportedMimeTypes = new Set(options.supportedMimeTypes || DEFAULT_SUPPORTED_MIME_TYPES);
2978
+ const settings = { ...DEFAULT_OPTIONS, ...options };
2979
+ return async ({ operation, req }) => {
2980
+ if (!isUploadMutationOperation(operation)) {
2981
+ return;
2982
+ }
2983
+ const file = req.file;
2984
+ if (!file) {
2985
+ return;
2986
+ }
2987
+ if (!supportedMimeTypes.has(file.mimetype)) {
2988
+ return;
2989
+ }
2990
+ if (typeof file.size === "number" && file.size < settings.minBytes) {
2991
+ return;
2992
+ }
2993
+ const sharpFactory = req.payload?.config?.sharp;
2994
+ if (typeof sharpFactory !== "function") {
2995
+ return;
2996
+ }
2997
+ const originalBuffer = await readIncomingBuffer(file);
2998
+ if (!Buffer.isBuffer(originalBuffer) || originalBuffer.length === 0) {
2999
+ return;
3000
+ }
3001
+ const animatedInput = file.mimetype === "image/avif" || file.mimetype === "image/webp";
3002
+ const metadataProbe = await sharpFactory(
3003
+ originalBuffer,
3004
+ animatedInput ? {
3005
+ animated: true
3006
+ } : void 0
3007
+ ).metadata();
3008
+ if (settings.skipAnimated && typeof metadataProbe.pages === "number" && metadataProbe.pages > 1) {
3009
+ return;
3010
+ }
3011
+ const buildPipeline = (qualityOverride) => {
3012
+ let pipeline = sharpFactory(
3013
+ originalBuffer,
3014
+ animatedInput ? {
3015
+ animated: true
3016
+ } : void 0
3017
+ );
3018
+ switch (file.mimetype) {
3019
+ case "image/jpeg":
3020
+ case "image/jpg":
3021
+ pipeline = pipeline.jpeg({
3022
+ mozjpeg: true,
3023
+ progressive: true,
3024
+ quality: clamp(qualityOverride ?? settings.jpegQuality, 20, 100)
3025
+ });
3026
+ break;
3027
+ case "image/png":
3028
+ pipeline = pipeline.png({
3029
+ compressionLevel: clamp(settings.pngCompressionLevel, 0, 9),
3030
+ palette: false
3031
+ });
3032
+ break;
3033
+ case "image/webp":
3034
+ pipeline = pipeline.webp({
3035
+ quality: clamp(qualityOverride ?? settings.webpQuality, 20, 100)
3036
+ });
3037
+ break;
3038
+ case "image/avif":
3039
+ pipeline = pipeline.avif({
3040
+ quality: clamp(qualityOverride ?? settings.avifQuality, 20, 100)
3041
+ });
3042
+ break;
3043
+ case "image/tiff":
3044
+ pipeline = pipeline.tiff({
3045
+ quality: clamp(qualityOverride ?? settings.tiffQuality, 20, 100)
3046
+ });
3047
+ break;
3048
+ default:
3049
+ return null;
3050
+ }
3051
+ return pipeline;
3052
+ };
3053
+ const initialPipeline = buildPipeline();
3054
+ if (!initialPipeline) {
3055
+ return;
3056
+ }
3057
+ let optimizedBuffer = await initialPipeline.toBuffer();
3058
+ const shouldSweepQuality = settings.enforceSmallerForLossy && isLossyMimeType(file.mimetype);
3059
+ if (shouldSweepQuality) {
3060
+ 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;
3061
+ let bestBuffer = optimizedBuffer;
3062
+ let quality = clamp(initialQuality - 5, settings.minQualityFloor, 100);
3063
+ while (quality >= settings.minQualityFloor) {
3064
+ const retryPipeline = buildPipeline(quality);
3065
+ if (!retryPipeline) {
3066
+ break;
3067
+ }
3068
+ const retryBuffer = await retryPipeline.toBuffer();
3069
+ if (retryBuffer.length < bestBuffer.length) {
3070
+ bestBuffer = retryBuffer;
3071
+ }
3072
+ quality -= 5;
3073
+ }
3074
+ optimizedBuffer = bestBuffer;
3075
+ }
3076
+ if (settings.onlyIfSmaller && optimizedBuffer.length >= originalBuffer.length) {
3077
+ return;
3078
+ }
3079
+ req.file = await writeOptimizedBuffer(file, optimizedBuffer);
3080
+ };
3081
+ };
3082
+ var withImageUploadOptimization = (collection, options = {}) => {
3083
+ if (!collection.upload) {
3084
+ return collection;
3085
+ }
3086
+ const existingHooks = collection.hooks || {};
3087
+ const beforeOperation = existingHooks.beforeOperation || [];
3088
+ return {
3089
+ ...collection,
3090
+ hooks: {
3091
+ ...existingHooks,
3092
+ beforeOperation: [createImageUploadOptimizationHook(options), ...beforeOperation]
3093
+ }
3094
+ };
3095
+ };
3096
+
3097
+ // src/studio/index.ts
3098
+ var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
3099
+ var makeIssue = (message, path2, code = "studio.invalid") => ({
3100
+ code,
3101
+ message,
3102
+ path: path2,
3103
+ severity: "error"
3104
+ });
3105
+ var createEmptyStudioDocument = (title) => ({
3106
+ metadata: {},
3107
+ schemaVersion: 1,
3108
+ title,
3109
+ nodes: [],
3110
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3111
+ });
3112
+ function assertStudioDocumentV1(input) {
3113
+ if (!isRecord2(input)) {
3114
+ throw new Error("Studio document must be an object");
3115
+ }
3116
+ if (input.schemaVersion !== 1) {
3117
+ throw new Error("Unsupported studio schemaVersion");
3118
+ }
3119
+ if (!Array.isArray(input.nodes)) {
3120
+ throw new Error("Studio document nodes must be an array");
3121
+ }
3122
+ const nodes = input.nodes.map((node, index) => {
3123
+ if (!isRecord2(node)) {
3124
+ throw new Error(`Node at index ${index} must be an object`);
3125
+ }
3126
+ if (typeof node.id !== "string" || node.id.length === 0) {
3127
+ throw new Error(`Node at index ${index} has invalid id`);
3128
+ }
3129
+ if (typeof node.type !== "string" || node.type.length === 0) {
3130
+ throw new Error(`Node at index ${index} has invalid type`);
3131
+ }
3132
+ if (!isRecord2(node.data)) {
3133
+ throw new Error(`Node at index ${index} has invalid data`);
3134
+ }
3135
+ return {
3136
+ id: node.id,
3137
+ type: node.type,
3138
+ data: node.data
3139
+ };
3140
+ });
3141
+ return {
3142
+ metadata: isRecord2(input.metadata) ? input.metadata : void 0,
3143
+ schemaVersion: 1,
3144
+ title: typeof input.title === "string" ? input.title : void 0,
3145
+ nodes,
3146
+ updatedAt: typeof input.updatedAt === "string" ? input.updatedAt : void 0
3147
+ };
3148
+ }
3149
+ function createStudioRegistry(modules) {
3150
+ const moduleByID = new Map(modules.map((mod) => [mod.id, mod]));
3151
+ const nodeTypes = modules.flatMap((mod) => mod.nodeTypes);
3152
+ const nodeTypeByName = new Map(nodeTypes.map((definition) => [definition.type, definition]));
3153
+ return {
3154
+ getModuleByID: (id) => moduleByID.get(id),
3155
+ getNodeTypeByName: (type) => nodeTypeByName.get(type),
3156
+ listInspectorPanels: () => modules.flatMap((mod) => mod.inspectorPanels),
3157
+ listModules: () => [...modules],
3158
+ listPaletteGroups: () => modules.flatMap((mod) => mod.paletteGroups),
3159
+ listNodeTypes: () => [...nodeTypes]
3160
+ };
3161
+ }
3162
+ function validateStudioDocument(document, modules) {
3163
+ const issues = [];
3164
+ if (document.schemaVersion !== 1) {
3165
+ issues.push(makeIssue("Unsupported schema version", "schemaVersion", "studio.schemaVersion"));
3166
+ }
3167
+ if (!Array.isArray(document.nodes)) {
3168
+ issues.push(makeIssue("Nodes must be an array", "nodes", "studio.nodes"));
3169
+ return issues;
3170
+ }
3171
+ const registry = createStudioRegistry(modules);
3172
+ const nodeIDs = /* @__PURE__ */ new Set();
3173
+ document.nodes.forEach((node, index) => {
3174
+ if (!node.id) {
3175
+ issues.push(makeIssue("Node id is required", `nodes.${index}.id`, "studio.node.id"));
3176
+ }
3177
+ if (nodeIDs.has(node.id)) {
3178
+ issues.push(makeIssue("Node id must be unique", `nodes.${index}.id`, "studio.node.id.duplicate"));
3179
+ }
3180
+ nodeIDs.add(node.id);
3181
+ if (!registry.getNodeTypeByName(node.type)) {
3182
+ issues.push(makeIssue("Unsupported node type", `nodes.${index}.type`, "studio.node.type"));
3183
+ }
3184
+ });
3185
+ for (const module2 of modules) {
3186
+ for (const validate of module2.validators) {
3187
+ issues.push(...validate(document));
3188
+ }
3189
+ }
3190
+ return issues;
3191
+ }
3192
+ function compileStudioDocument(document, modules) {
3193
+ const issues = validateStudioDocument(document, modules);
3194
+ const compilerEntries = modules.filter((mod) => typeof mod.compiler?.compileNode === "function").map((mod) => mod.compiler?.compileNode);
3195
+ const layout = document.nodes.map((node) => {
3196
+ for (const compileNode of compilerEntries) {
3197
+ if (!compileNode) {
3198
+ continue;
3199
+ }
3200
+ const compiled = compileNode(node);
3201
+ if (compiled) {
3202
+ return compiled;
3203
+ }
3204
+ }
3205
+ return {
3206
+ id: node.id,
3207
+ blockType: node.type,
3208
+ ...node.data
3209
+ };
3210
+ });
3211
+ return {
3212
+ issues,
3213
+ layout
3214
+ };
3215
+ }
3216
+ function migrateStudioDocument(value, migrations) {
3217
+ const sorted = [...migrations].sort((a, b) => a.fromVersion - b.fromVersion);
3218
+ let current = value;
3219
+ for (const migration of sorted) {
3220
+ if (!isRecord2(current) || current.schemaVersion !== migration.fromVersion) {
3221
+ continue;
3222
+ }
3223
+ current = migration.migrate(current);
3224
+ }
3225
+ return assertStudioDocumentV1(current);
3226
+ }
3227
+
3228
+ // src/studio-pages/builder/settings-v2/types.ts
3229
+ var defaultBuilderBlockSettingsV2 = {
3230
+ advanced: {
3231
+ customClassName: "",
3232
+ editCopyInPanel: false,
3233
+ hideOnMobile: false
3234
+ },
3235
+ appearance: {
3236
+ contentBackgroundColor: "#ffffff",
3237
+ contentBackgroundMode: "none",
3238
+ contentGradientAngle: "135",
3239
+ contentGradientFrom: "#ffffff",
3240
+ contentGradientPreset: "none",
3241
+ contentGradientTo: "#f4f6f8",
3242
+ sectionBackgroundColor: "#ffffff",
3243
+ sectionBackgroundMode: "none",
3244
+ sectionGradientAngle: "135",
3245
+ sectionGradientFrom: "#334b63",
3246
+ sectionGradientPreset: "slate",
3247
+ sectionGradientTo: "#496582"
3248
+ },
3249
+ layout: {
3250
+ contentWidth: "inherit",
3251
+ linkHorizontalPadding: true,
3252
+ linkVerticalPadding: true,
3253
+ paddingBottomPt: null,
3254
+ paddingLeftPt: null,
3255
+ paddingRightPt: null,
3256
+ paddingTopPt: null,
3257
+ sectionPaddingX: "inherit",
3258
+ sectionPaddingY: "md"
3259
+ },
3260
+ media: {
3261
+ cornerStyle: "rounded",
3262
+ fit: "cover",
3263
+ height: null,
3264
+ position: "center",
3265
+ positionX: null,
3266
+ positionY: null
3267
+ },
3268
+ typography: {
3269
+ bodyAlign: "left",
3270
+ headingAlign: "left",
3271
+ letterSpacingPreset: "normal",
3272
+ lineHeightPreset: "normal",
3273
+ maxTextWidth: "auto"
3274
+ },
3275
+ version: 2
3276
+ };
3277
+ var defaultBuilderItemSettingsV2 = {
3278
+ layout: {
3279
+ contentWidth: "inherit",
3280
+ linkHorizontalPadding: true,
3281
+ linkVerticalPadding: true,
3282
+ paddingBottomPt: null,
3283
+ paddingLeftPt: null,
3284
+ paddingRightPt: null,
3285
+ paddingTopPt: null,
3286
+ sectionPaddingX: "inherit",
3287
+ sectionPaddingY: "md"
3288
+ },
3289
+ media: {
3290
+ cornerStyle: "rounded",
3291
+ fit: "cover",
3292
+ height: null,
3293
+ position: "center",
3294
+ positionX: null,
3295
+ positionY: null
3296
+ },
3297
+ typography: {
3298
+ bodyAlign: "left",
3299
+ headingAlign: "left",
3300
+ letterSpacingPreset: "normal",
3301
+ lineHeightPreset: "normal",
3302
+ maxTextWidth: "auto"
3303
+ },
3304
+ version: 2
3305
+ };
3306
+ var defaultBuilderThemeTokens = {
3307
+ colors: {
3308
+ accent: "#334b63",
3309
+ bodyText: "#425163",
3310
+ headingText: "#182332",
3311
+ surface: "#ffffff"
3312
+ },
3313
+ radii: {
3314
+ card: 16,
3315
+ panel: 14
3316
+ },
3317
+ spacing: {
3318
+ sectionGap: "md",
3319
+ sectionPadding: "md"
3320
+ },
3321
+ typography: {
3322
+ bodySize: "md",
3323
+ headingSize: "md"
3324
+ }
3325
+ };
3326
+
3327
+ // src/studio-pages/builder/adapters/settingsV2.ts
3328
+ var isRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
3329
+ var parsePercent = (value) => {
3330
+ if (typeof value === "number" && Number.isFinite(value)) {
3331
+ return Math.max(0, Math.min(100, value));
3332
+ }
3333
+ if (typeof value === "string" && value.trim().length > 0) {
3334
+ const parsed = Number(value);
3335
+ if (Number.isFinite(parsed)) {
3336
+ return Math.max(0, Math.min(100, parsed));
3337
+ }
3338
+ }
3339
+ return null;
3340
+ };
3341
+ var parsePixel = (value) => {
3342
+ if (typeof value === "number" && Number.isFinite(value)) {
3343
+ return value;
3344
+ }
3345
+ if (typeof value === "string" && value.trim().length > 0) {
3346
+ const parsed = Number(value);
3347
+ if (Number.isFinite(parsed)) {
3348
+ return parsed;
3349
+ }
3350
+ }
3351
+ return null;
3352
+ };
3353
+ var mergeSettings = (defaults, input) => {
3354
+ if (!isRecord3(input)) {
3355
+ return structuredClone(defaults);
3356
+ }
3357
+ const next = structuredClone(defaults);
3358
+ for (const [key, value] of Object.entries(input)) {
3359
+ if (isRecord3(value) && isRecord3(next[key])) {
3360
+ next[key] = mergeSettings(next[key], value);
3361
+ continue;
3362
+ }
3363
+ if (typeof value !== "undefined") {
3364
+ next[key] = value;
3365
+ }
3366
+ }
3367
+ return next;
3368
+ };
3369
+ var legacyBlockToV2Settings = (block) => {
3370
+ const current = structuredClone(defaultBuilderBlockSettingsV2);
3371
+ current.layout.contentWidth = block.contentWidth === "narrow" || block.contentWidth === "content" || block.contentWidth === "wide" || block.contentWidth === "full" || block.contentWidth === "inherit" ? block.contentWidth : current.layout.contentWidth;
3372
+ current.layout.sectionPaddingX = block.sectionPaddingX === "none" || block.sectionPaddingX === "sm" || block.sectionPaddingX === "md" || block.sectionPaddingX === "lg" || block.sectionPaddingX === "inherit" ? block.sectionPaddingX : current.layout.sectionPaddingX;
3373
+ current.layout.sectionPaddingY = block.sectionPaddingY === "none" || block.sectionPaddingY === "sm" || block.sectionPaddingY === "lg" ? block.sectionPaddingY : current.layout.sectionPaddingY;
3374
+ current.appearance.sectionBackgroundMode = block.sectionBackgroundMode === "none" || block.sectionBackgroundMode === "color" || block.sectionBackgroundMode === "gradient" ? block.sectionBackgroundMode : current.appearance.sectionBackgroundMode;
3375
+ current.appearance.sectionBackgroundColor = typeof block.sectionBackgroundColor === "string" ? block.sectionBackgroundColor : current.appearance.sectionBackgroundColor;
3376
+ current.appearance.sectionGradientPreset = typeof block.sectionGradientPreset === "string" ? block.sectionGradientPreset : current.appearance.sectionGradientPreset;
3377
+ current.appearance.sectionGradientFrom = typeof block.sectionGradientFrom === "string" ? block.sectionGradientFrom : current.appearance.sectionGradientFrom;
3378
+ current.appearance.sectionGradientTo = typeof block.sectionGradientTo === "string" ? block.sectionGradientTo : current.appearance.sectionGradientTo;
3379
+ current.appearance.sectionGradientAngle = typeof block.sectionGradientAngle === "string" ? block.sectionGradientAngle : current.appearance.sectionGradientAngle;
3380
+ current.appearance.contentBackgroundMode = block.contentBackgroundMode === "none" || block.contentBackgroundMode === "color" || block.contentBackgroundMode === "gradient" ? block.contentBackgroundMode : current.appearance.contentBackgroundMode;
3381
+ current.appearance.contentBackgroundColor = typeof block.contentBackgroundColor === "string" ? block.contentBackgroundColor : current.appearance.contentBackgroundColor;
3382
+ current.appearance.contentGradientPreset = typeof block.contentGradientPreset === "string" ? block.contentGradientPreset : current.appearance.contentGradientPreset;
3383
+ current.appearance.contentGradientFrom = typeof block.contentGradientFrom === "string" ? block.contentGradientFrom : current.appearance.contentGradientFrom;
3384
+ current.appearance.contentGradientTo = typeof block.contentGradientTo === "string" ? block.contentGradientTo : current.appearance.contentGradientTo;
3385
+ current.appearance.contentGradientAngle = typeof block.contentGradientAngle === "string" ? block.contentGradientAngle : current.appearance.contentGradientAngle;
3386
+ if (block.backgroundImageFit === "cover" || block.backgroundImageFit === "contain") {
3387
+ current.media.fit = block.backgroundImageFit;
3388
+ }
3389
+ if (block.imageFit === "cover" || block.imageFit === "contain") {
3390
+ current.media.fit = block.imageFit;
3391
+ }
3392
+ if (block.backgroundImageCornerStyle === "rounded" || block.backgroundImageCornerStyle === "square") {
3393
+ current.media.cornerStyle = block.backgroundImageCornerStyle;
3394
+ }
3395
+ if (block.imageCornerStyle === "rounded" || block.imageCornerStyle === "square") {
3396
+ current.media.cornerStyle = block.imageCornerStyle;
3397
+ }
3398
+ if (block.backgroundImagePosition === "top" || block.backgroundImagePosition === "bottom" || block.backgroundImagePosition === "left" || block.backgroundImagePosition === "right" || block.backgroundImagePosition === "center") {
3399
+ current.media.position = block.backgroundImagePosition;
3400
+ }
3401
+ if (block.imagePosition === "top" || block.imagePosition === "bottom" || block.imagePosition === "left" || block.imagePosition === "right" || block.imagePosition === "center") {
3402
+ current.media.position = block.imagePosition;
3403
+ }
3404
+ current.media.positionX = parsePercent(block.imagePositionX ?? block.backgroundImagePositionX);
3405
+ current.media.positionY = parsePercent(block.imagePositionY ?? block.backgroundImagePositionY);
3406
+ current.media.height = parsePixel(block.imageHeight);
3407
+ current.typography.headingAlign = block.textHeadingAlign === "left" || block.textHeadingAlign === "center" || block.textHeadingAlign === "right" || block.textHeadingAlign === "justify" ? block.textHeadingAlign : current.typography.headingAlign;
3408
+ current.typography.bodyAlign = block.textBodyAlign === "left" || block.textBodyAlign === "center" || block.textBodyAlign === "right" || block.textBodyAlign === "justify" ? block.textBodyAlign : current.typography.bodyAlign;
3409
+ current.typography.maxTextWidth = block.textMaxWidth === "auto" || block.textMaxWidth === "sm" || block.textMaxWidth === "md" || block.textMaxWidth === "lg" || block.textMaxWidth === "full" ? block.textMaxWidth : current.typography.maxTextWidth;
3410
+ current.typography.lineHeightPreset = block.textLineHeightPreset === "tight" || block.textLineHeightPreset === "normal" || block.textLineHeightPreset === "relaxed" ? block.textLineHeightPreset : current.typography.lineHeightPreset;
3411
+ current.typography.letterSpacingPreset = block.textLetterSpacingPreset === "tight" || block.textLetterSpacingPreset === "normal" || block.textLetterSpacingPreset === "relaxed" ? block.textLetterSpacingPreset : current.typography.letterSpacingPreset;
3412
+ current.advanced.editCopyInPanel = Boolean(block.editCopyInPanel ?? current.advanced.editCopyInPanel);
3413
+ current.advanced.customClassName = typeof block.customClassName === "string" ? block.customClassName : current.advanced.customClassName;
3414
+ current.advanced.hideOnMobile = Boolean(block.hideOnMobile ?? current.advanced.hideOnMobile);
3415
+ return mergeSettings(current, block.settings);
3416
+ };
3417
+ var v2SettingsToLegacyBlock = (blockWithSettings) => {
3418
+ const settings = legacyBlockToV2Settings(blockWithSettings);
3419
+ const next = {
3420
+ ...blockWithSettings,
3421
+ settings
3422
+ };
3423
+ const blockType = typeof next.blockType === "string" ? next.blockType : "";
3424
+ next.contentWidth = settings.layout.contentWidth;
3425
+ next.sectionPaddingX = settings.layout.sectionPaddingX;
3426
+ next.sectionPaddingY = settings.layout.sectionPaddingY;
3427
+ next.sectionBackgroundMode = settings.appearance.sectionBackgroundMode;
3428
+ next.sectionBackgroundColor = settings.appearance.sectionBackgroundColor;
3429
+ next.sectionGradientPreset = settings.appearance.sectionGradientPreset;
3430
+ next.sectionGradientFrom = settings.appearance.sectionGradientFrom;
3431
+ next.sectionGradientTo = settings.appearance.sectionGradientTo;
3432
+ next.sectionGradientAngle = settings.appearance.sectionGradientAngle;
3433
+ next.contentBackgroundMode = settings.appearance.contentBackgroundMode;
3434
+ next.contentBackgroundColor = settings.appearance.contentBackgroundColor;
3435
+ next.contentGradientPreset = settings.appearance.contentGradientPreset;
3436
+ next.contentGradientFrom = settings.appearance.contentGradientFrom;
3437
+ next.contentGradientTo = settings.appearance.contentGradientTo;
3438
+ next.contentGradientAngle = settings.appearance.contentGradientAngle;
3439
+ next.textHeadingAlign = settings.typography.headingAlign;
3440
+ next.textBodyAlign = settings.typography.bodyAlign;
3441
+ next.textMaxWidth = settings.typography.maxTextWidth;
3442
+ next.textLineHeightPreset = settings.typography.lineHeightPreset;
3443
+ next.textLetterSpacingPreset = settings.typography.letterSpacingPreset;
3444
+ next.editCopyInPanel = settings.advanced.editCopyInPanel;
3445
+ next.customClassName = settings.advanced.customClassName;
3446
+ next.hideOnMobile = settings.advanced.hideOnMobile;
3447
+ delete next.backgroundImagePositionX;
3448
+ delete next.backgroundImagePositionY;
3449
+ delete next.imageHeight;
3450
+ delete next.imagePositionX;
3451
+ delete next.imagePositionY;
3452
+ if (blockType === "hero") {
3453
+ next.backgroundImageFit = settings.media.fit;
3454
+ next.backgroundImageCornerStyle = settings.media.cornerStyle;
3455
+ next.backgroundImagePosition = settings.media.position;
3456
+ } else if (blockType === "media") {
3457
+ next.imageFit = settings.media.fit;
3458
+ next.imageCornerStyle = settings.media.cornerStyle;
3459
+ next.imagePosition = settings.media.position;
3460
+ }
3461
+ if (Array.isArray(next.items)) {
3462
+ next.items = next.items.map((rawItem) => isRecord3(rawItem) ? v2SettingsToLegacyItem(rawItem) : rawItem);
3463
+ }
3464
+ return next;
3465
+ };
3466
+ var legacyItemToV2Settings = (item) => {
3467
+ const current = structuredClone(defaultBuilderItemSettingsV2);
3468
+ if (item.imageFit === "cover" || item.imageFit === "contain") {
3469
+ current.media.fit = item.imageFit;
3470
+ }
3471
+ if (item.imageCornerStyle === "rounded" || item.imageCornerStyle === "square") {
3472
+ current.media.cornerStyle = item.imageCornerStyle;
3473
+ }
3474
+ if (item.imagePosition === "top" || item.imagePosition === "bottom" || item.imagePosition === "left" || item.imagePosition === "right" || item.imagePosition === "center") {
3475
+ current.media.position = item.imagePosition;
3476
+ }
3477
+ current.media.positionX = parsePercent(item.imagePositionX);
3478
+ current.media.positionY = parsePercent(item.imagePositionY);
3479
+ current.media.height = parsePixel(item.imageHeight);
3480
+ return mergeSettings(current, item.settings);
3481
+ };
3482
+ var v2SettingsToLegacyItem = (itemWithSettings) => {
3483
+ const settings = legacyItemToV2Settings(itemWithSettings);
3484
+ return {
3485
+ ...itemWithSettings,
3486
+ imageCornerStyle: settings.media.cornerStyle,
3487
+ imageFit: settings.media.fit,
3488
+ imageHeight: settings.media.height,
3489
+ imagePosition: settings.media.position,
3490
+ imagePositionX: settings.media.positionX,
3491
+ imagePositionY: settings.media.positionY,
3492
+ settings
3493
+ };
3494
+ };
3495
+ var migrateBlockToSettingsV2 = (block) => {
3496
+ const withLegacyMirrors = v2SettingsToLegacyBlock(block);
3497
+ if (!Array.isArray(withLegacyMirrors.items)) {
3498
+ return withLegacyMirrors;
3499
+ }
3500
+ return {
3501
+ ...withLegacyMirrors,
3502
+ items: withLegacyMirrors.items.map((rawItem) => isRecord3(rawItem) ? v2SettingsToLegacyItem(rawItem) : rawItem)
3503
+ };
3504
+ };
3505
+
3506
+ // src/studio-pages/document.ts
3507
+ var ensureNodeID = (value, index) => {
3508
+ if (typeof value === "string" && value.trim().length > 0) {
3509
+ return value.trim();
3510
+ }
3511
+ return `node-${index + 1}`;
3512
+ };
3513
+ var layoutToStudioDocument = (layout, title, metadata) => {
3514
+ const nodes = layout.filter((block) => typeof block.blockType === "string").map((rawBlock, index) => {
3515
+ const block = migrateBlockToSettingsV2(rawBlock);
3516
+ const blockType = String(block.blockType);
3517
+ const { id, blockType: _ignoredBlockType, ...data } = block;
3518
+ return {
3519
+ id: ensureNodeID(id, index),
3520
+ type: blockType,
3521
+ data
3522
+ };
3523
+ });
3524
+ return {
3525
+ metadata,
3526
+ schemaVersion: 1,
3527
+ title,
3528
+ nodes,
3529
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3530
+ };
3531
+ };
3532
+ var studioDocumentToLayout = (document) => document.nodes.map(
3533
+ (node) => migrateBlockToSettingsV2({
3534
+ id: node.id,
3535
+ blockType: node.type,
3536
+ ...node.data
3537
+ })
3538
+ );
3539
+ var createDefaultStudioDocument = (title) => ({
3540
+ metadata: {},
3541
+ schemaVersion: 1,
3542
+ title,
3543
+ nodes: [],
3544
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3545
+ });
3546
+
3547
+ // src/nextjs/queries/pages.ts
3548
+ var PAGE_QUERY_CACHE_VERSION = "v4-published-only-public";
3549
+ function withStudioDocumentLayout(page) {
3550
+ if (!page) {
3551
+ return null;
3552
+ }
3553
+ try {
3554
+ const studioDocument = assertStudioDocumentV1(page.studioDocument);
3555
+ const compiledLayout = studioDocumentToLayout(studioDocument);
3556
+ if (Array.isArray(compiledLayout) && compiledLayout.length > 0) {
3557
+ return {
3558
+ ...page,
3559
+ layout: compiledLayout
3560
+ };
3561
+ }
3562
+ } catch {
3563
+ }
3564
+ return page;
3565
+ }
3566
+ function normalizePath(segments) {
3567
+ if (!segments || segments.length === 0) {
3568
+ return "/";
3569
+ }
3570
+ const cleaned = segments.map((segment) => segment.trim()).filter(Boolean).join("/");
3571
+ return cleaned.length > 0 ? `/${cleaned}` : "/";
3572
+ }
3573
+ async function queryPageByPath(payload, path2, draft) {
3574
+ const pathWhere = {
3575
+ path: {
3576
+ equals: path2
3577
+ }
3578
+ };
3579
+ const publishedWhere = {
3580
+ _status: {
3581
+ equals: "published"
3582
+ }
3583
+ };
3584
+ const result = await payload.find({
1542
3585
  collection: "pages",
1543
3586
  depth: 2,
1544
3587
  draft,
1545
3588
  limit: 1,
1546
3589
  overrideAccess: false,
1547
- where: {
1548
- path: {
1549
- equals: path2
1550
- }
1551
- }
3590
+ where: draft ? pathWhere : { and: [pathWhere, publishedWhere] }
1552
3591
  });
1553
3592
  if (result.docs.length > 0) {
1554
- return result.docs[0] || null;
3593
+ return withStudioDocumentLayout(result.docs[0] || null);
1555
3594
  }
1556
3595
  if (path2 === "/") {
3596
+ const homeWhere = {
3597
+ slug: {
3598
+ equals: "home"
3599
+ }
3600
+ };
1557
3601
  const homeResult = await payload.find({
1558
3602
  collection: "pages",
1559
3603
  depth: 2,
1560
3604
  draft,
1561
3605
  limit: 1,
1562
3606
  overrideAccess: false,
1563
- where: {
1564
- slug: {
1565
- equals: "home"
1566
- }
1567
- }
3607
+ where: draft ? homeWhere : { and: [homeWhere, publishedWhere] }
1568
3608
  });
1569
- return homeResult.docs[0] || null;
3609
+ return withStudioDocumentLayout(homeResult.docs[0] || null);
1570
3610
  }
1571
3611
  return null;
1572
3612
  }
@@ -1576,7 +3616,7 @@ function createPageQueries(getPayloadClient, contentTag = "website-content") {
1576
3616
  const payload = await getPayloadClient();
1577
3617
  return queryPageByPath(payload, path2, false);
1578
3618
  },
1579
- ["page-by-path"],
3619
+ ["page-by-path", PAGE_QUERY_CACHE_VERSION],
1580
3620
  { tags: [contentTag] }
1581
3621
  );
1582
3622
  async function getPageBySegments(segments, draft = false) {
@@ -1656,6 +3696,18 @@ function createSiteQueries(getPayloadClient, contentTag = "website-content") {
1656
3696
  ["footer-global"],
1657
3697
  { tags: [contentTag] }
1658
3698
  );
3699
+ const getSocialMediaCached = (0, import_cache2.unstable_cache)(
3700
+ async () => {
3701
+ const payload = await getPayloadClient();
3702
+ const socialMedia = await payload.findGlobal({
3703
+ slug: "social-media",
3704
+ depth: 1
3705
+ });
3706
+ return socialMedia;
3707
+ },
3708
+ ["social-media-global"],
3709
+ { tags: [contentTag] }
3710
+ );
1659
3711
  async function getSiteSettings(draft = false) {
1660
3712
  if (draft) {
1661
3713
  const payload = await getPayloadClient();
@@ -1692,10 +3744,23 @@ function createSiteQueries(getPayloadClient, contentTag = "website-content") {
1692
3744
  }
1693
3745
  return getFooterCached();
1694
3746
  }
3747
+ async function getSocialMedia(draft = false) {
3748
+ if (draft) {
3749
+ const payload = await getPayloadClient();
3750
+ const socialMedia = await payload.findGlobal({
3751
+ slug: "social-media",
3752
+ depth: 1,
3753
+ draft: true
3754
+ });
3755
+ return socialMedia;
3756
+ }
3757
+ return getSocialMediaCached();
3758
+ }
1695
3759
  return {
1696
3760
  getSiteSettings,
1697
3761
  getHeader,
1698
- getFooter
3762
+ getFooter,
3763
+ getSocialMedia
1699
3764
  };
1700
3765
  }
1701
3766
 
@@ -1704,60 +3769,465 @@ function resolveMedia(media) {
1704
3769
  if (!media) {
1705
3770
  return null;
1706
3771
  }
1707
- if (typeof media === "number" || typeof media === "string") {
1708
- return null;
3772
+ if (typeof media === "number" || typeof media === "string") {
3773
+ return null;
3774
+ }
3775
+ if (media.url) {
3776
+ return {
3777
+ url: media.url,
3778
+ alt: media.alt || ""
3779
+ };
3780
+ }
3781
+ if (media.filename) {
3782
+ return {
3783
+ url: `/media/${media.filename}`,
3784
+ alt: media.alt || ""
3785
+ };
3786
+ }
3787
+ return null;
3788
+ }
3789
+
3790
+ // src/nextjs/utilities/socialMedia.ts
3791
+ function resolveSocialMediaLinks(data) {
3792
+ const profiles = data?.profiles;
3793
+ if (!profiles || typeof profiles !== "object") {
3794
+ return [];
3795
+ }
3796
+ return SOCIAL_MEDIA_PLATFORMS.reduce((acc, platform) => {
3797
+ const profile = profiles[platform];
3798
+ if (!profile || typeof profile !== "object") {
3799
+ return acc;
3800
+ }
3801
+ const url = typeof profile.url === "string" ? profile.url.trim() : "";
3802
+ if (!url) {
3803
+ return acc;
3804
+ }
3805
+ const icon = typeof profile.icon === "string" && profile.icon.trim().length > 0 ? profile.icon.trim() : SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM[platform];
3806
+ acc.push({
3807
+ icon,
3808
+ label: SOCIAL_MEDIA_PLATFORM_LABELS[platform],
3809
+ platform,
3810
+ url
3811
+ });
3812
+ return acc;
3813
+ }, []);
3814
+ }
3815
+
3816
+ // src/studio-pages/index.ts
3817
+ var studio_pages_exports = {};
3818
+ __export(studio_pages_exports, {
3819
+ createDefaultStudioDocument: () => createDefaultStudioDocument,
3820
+ defaultBuilderThemeTokens: () => defaultBuilderThemeTokens,
3821
+ layoutToStudioDocument: () => layoutToStudioDocument,
3822
+ pageInspectorPanels: () => pageInspectorPanels,
3823
+ pageNodeTypes: () => pageNodeTypes,
3824
+ pagePaletteGroups: () => pagePaletteGroups,
3825
+ pageStudioModuleManifest: () => pageStudioModuleManifest,
3826
+ resolveBuilderThemeTokens: () => resolveBuilderThemeTokens,
3827
+ studioDocumentToLayout: () => studioDocumentToLayout
3828
+ });
3829
+
3830
+ // src/studio-pages/builder/settings-v2/themeTokens.ts
3831
+ var isRecord4 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
3832
+ var merge = (base, next) => {
3833
+ if (!next || !isRecord4(next)) {
3834
+ return base;
3835
+ }
3836
+ const merged = { ...base };
3837
+ for (const [key, value] of Object.entries(next)) {
3838
+ if (isRecord4(value) && isRecord4(merged[key])) {
3839
+ merged[key] = merge(merged[key], value);
3840
+ continue;
3841
+ }
3842
+ if (typeof value !== "undefined") {
3843
+ merged[key] = value;
3844
+ }
3845
+ }
3846
+ return merged;
3847
+ };
3848
+ var resolveBuilderThemeTokens = (layers) => {
3849
+ const withSite = merge(defaultBuilderThemeTokens, layers.site);
3850
+ const withPage = merge(withSite, layers.page);
3851
+ return merge(withPage, layers.block);
3852
+ };
3853
+
3854
+ // src/studio-pages/builder/settings-v2/BlockInspectorRenderer.tsx
3855
+ var import_react2 = require("react");
3856
+
3857
+ // src/studio-pages/builder/ui/Accordion.tsx
3858
+ var import_react = require("react");
3859
+ var import_jsx_runtime4 = require("react/jsx-runtime");
3860
+
3861
+ // src/studio-pages/builder/ui/ImageControls.tsx
3862
+ var import_jsx_runtime5 = require("react/jsx-runtime");
3863
+
3864
+ // src/studio-pages/builder/settings-v2/inspectorSchema.ts
3865
+ var alignOptions = [
3866
+ { label: "Left", value: "left" },
3867
+ { label: "Center", value: "center" },
3868
+ { label: "Right", value: "right" },
3869
+ { label: "Justify", value: "justify" }
3870
+ ];
3871
+ var layoutFieldSet = [];
3872
+ var typographyFieldSet = [
3873
+ {
3874
+ group: "typography",
3875
+ key: "settings.typography.headingAlign",
3876
+ label: "Heading Alignment",
3877
+ options: alignOptions,
3878
+ tags: ["text", "align", "heading"],
3879
+ type: "select"
3880
+ },
3881
+ {
3882
+ group: "typography",
3883
+ key: "settings.typography.bodyAlign",
3884
+ label: "Body Alignment",
3885
+ options: alignOptions,
3886
+ tags: ["text", "align", "paragraph"],
3887
+ type: "select"
3888
+ },
3889
+ {
3890
+ group: "typography",
3891
+ key: "settings.typography.maxTextWidth",
3892
+ label: "Text Width",
3893
+ options: [
3894
+ { label: "Auto", value: "auto" },
3895
+ { label: "Small", value: "sm" },
3896
+ { label: "Medium", value: "md" },
3897
+ { label: "Large", value: "lg" },
3898
+ { label: "Full", value: "full" }
3899
+ ],
3900
+ tags: ["readability", "measure", "line length"],
3901
+ type: "select"
3902
+ },
3903
+ {
3904
+ advanced: true,
3905
+ group: "typography",
3906
+ key: "settings.typography.lineHeightPreset",
3907
+ label: "Line Height",
3908
+ options: [
3909
+ { label: "Tight", value: "tight" },
3910
+ { label: "Normal", value: "normal" },
3911
+ { label: "Relaxed", value: "relaxed" }
3912
+ ],
3913
+ type: "select"
3914
+ },
3915
+ {
3916
+ advanced: true,
3917
+ group: "typography",
3918
+ key: "settings.typography.letterSpacingPreset",
3919
+ label: "Letter Spacing",
3920
+ options: [
3921
+ { label: "Tight", value: "tight" },
3922
+ { label: "Normal", value: "normal" },
3923
+ { label: "Relaxed", value: "relaxed" }
3924
+ ],
3925
+ type: "select"
3926
+ }
3927
+ ];
3928
+ var styleFieldSet = [];
3929
+ var commonAdvanced = [
3930
+ {
3931
+ group: "advanced",
3932
+ inlineEditable: false,
3933
+ key: "settings.advanced.editCopyInPanel",
3934
+ label: "Edit Copy In Panel",
3935
+ tags: ["inline", "copy", "text"],
3936
+ type: "checkbox"
3937
+ },
3938
+ {
3939
+ advanced: true,
3940
+ group: "advanced",
3941
+ key: "settings.advanced.hideOnMobile",
3942
+ label: "Hide On Mobile",
3943
+ type: "checkbox"
3944
+ },
3945
+ {
3946
+ advanced: true,
3947
+ group: "advanced",
3948
+ key: "settings.advanced.customClassName",
3949
+ label: "Custom Class Name",
3950
+ type: "text"
1709
3951
  }
1710
- if (media.url) {
1711
- return {
1712
- url: media.url,
1713
- alt: media.alt || ""
1714
- };
3952
+ ];
3953
+ var mediaFieldSet = [
3954
+ {
3955
+ group: "media",
3956
+ key: "settings.media.fit",
3957
+ label: "Image Fit",
3958
+ options: [
3959
+ { label: "Cover", value: "cover" },
3960
+ { label: "Contain", value: "contain" }
3961
+ ],
3962
+ tags: ["image", "media"],
3963
+ type: "select"
3964
+ },
3965
+ {
3966
+ group: "media",
3967
+ key: "settings.media.cornerStyle",
3968
+ label: "Image Corners",
3969
+ options: [
3970
+ { label: "Rounded", value: "rounded" },
3971
+ { label: "Square", value: "square" }
3972
+ ],
3973
+ tags: ["image", "radius", "corners"],
3974
+ type: "select"
1715
3975
  }
1716
- if (media.filename) {
1717
- return {
1718
- url: `/media/${media.filename}`,
1719
- alt: media.alt || ""
1720
- };
3976
+ ];
3977
+ var inspectorDefinitionByBlockType = {
3978
+ beforeAfter: {
3979
+ blockType: "beforeAfter",
3980
+ fields: [
3981
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3982
+ { group: "basics", inlineEditable: true, key: "subtitle", label: "Subtitle", type: "textarea" },
3983
+ { group: "basics", key: "itemsPerRow", label: "Items Per Row", max: 4, min: 1, type: "number" },
3984
+ ...layoutFieldSet,
3985
+ ...typographyFieldSet,
3986
+ ...styleFieldSet,
3987
+ ...commonAdvanced
3988
+ ]
3989
+ },
3990
+ bookingEmbed: {
3991
+ blockType: "bookingEmbed",
3992
+ fields: [
3993
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3994
+ { group: "basics", inlineEditable: true, key: "description", label: "Description", type: "textarea" },
3995
+ { group: "basics", inlineEditable: false, key: "buttonLabel", label: "Button Label", type: "text" },
3996
+ { group: "basics", inlineEditable: false, key: "buttonHref", label: "Button URL", type: "text" },
3997
+ ...layoutFieldSet,
3998
+ ...typographyFieldSet,
3999
+ ...styleFieldSet,
4000
+ ...commonAdvanced
4001
+ ]
4002
+ },
4003
+ cta: {
4004
+ blockType: "cta",
4005
+ fields: [
4006
+ { group: "basics", inlineEditable: true, key: "headline", label: "Headline", type: "text" },
4007
+ { group: "basics", inlineEditable: true, key: "description", label: "Description", type: "textarea" },
4008
+ { group: "basics", inlineEditable: false, key: "buttonLabel", label: "Button Label", type: "text" },
4009
+ { group: "basics", inlineEditable: false, key: "buttonHref", label: "Button URL", type: "text" },
4010
+ {
4011
+ group: "basics",
4012
+ key: "style",
4013
+ label: "Variant",
4014
+ options: [
4015
+ { label: "Light", value: "light" },
4016
+ { label: "Dark", value: "dark" }
4017
+ ],
4018
+ type: "select"
4019
+ },
4020
+ ...layoutFieldSet,
4021
+ ...typographyFieldSet,
4022
+ ...styleFieldSet,
4023
+ ...commonAdvanced
4024
+ ]
4025
+ },
4026
+ faq: {
4027
+ blockType: "faq",
4028
+ fields: [
4029
+ { group: "basics", inlineEditable: true, key: "eyebrow", label: "Eyebrow", type: "text" },
4030
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
4031
+ ...layoutFieldSet,
4032
+ ...typographyFieldSet,
4033
+ ...styleFieldSet,
4034
+ ...commonAdvanced
4035
+ ]
4036
+ },
4037
+ featureGrid: {
4038
+ blockType: "featureGrid",
4039
+ fields: [
4040
+ { group: "basics", inlineEditable: true, key: "eyebrow", label: "Eyebrow", type: "text" },
4041
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
4042
+ {
4043
+ group: "basics",
4044
+ key: "variant",
4045
+ label: "Variant",
4046
+ options: [
4047
+ { label: "Cards", value: "cards" },
4048
+ { label: "Highlight", value: "highlight" },
4049
+ { label: "Split List", value: "splitList" },
4050
+ { label: "Panels", value: "panels" },
4051
+ { label: "Catalog", value: "catalog" },
4052
+ { label: "Contact Split", value: "contact" }
4053
+ ],
4054
+ type: "select"
4055
+ },
4056
+ { group: "basics", key: "itemsPerRow", label: "Items Per Row", max: 6, min: 1, type: "number" },
4057
+ ...layoutFieldSet,
4058
+ ...typographyFieldSet,
4059
+ ...mediaFieldSet,
4060
+ ...styleFieldSet,
4061
+ ...commonAdvanced
4062
+ ]
4063
+ },
4064
+ formEmbed: {
4065
+ blockType: "formEmbed",
4066
+ fields: [
4067
+ { group: "basics", inlineEditable: true, key: "eyebrow", label: "Eyebrow", type: "text" },
4068
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
4069
+ { group: "basics", inlineEditable: true, key: "description", label: "Description", type: "textarea" },
4070
+ {
4071
+ group: "basics",
4072
+ key: "formType",
4073
+ label: "Form Type",
4074
+ options: [{ label: "Quote", value: "quote" }],
4075
+ type: "select"
4076
+ },
4077
+ ...layoutFieldSet,
4078
+ ...typographyFieldSet,
4079
+ ...styleFieldSet,
4080
+ ...commonAdvanced
4081
+ ]
4082
+ },
4083
+ hero: {
4084
+ blockType: "hero",
4085
+ fields: [
4086
+ { group: "basics", inlineEditable: true, key: "kicker", label: "Kicker", type: "text" },
4087
+ { group: "basics", inlineEditable: true, key: "headline", label: "Headline", type: "text" },
4088
+ { group: "basics", inlineEditable: true, key: "subheadline", label: "Subheadline", type: "textarea" },
4089
+ {
4090
+ group: "basics",
4091
+ key: "variant",
4092
+ label: "Variant",
4093
+ options: [
4094
+ { label: "Default", value: "default" },
4095
+ { label: "Centered", value: "centered" }
4096
+ ],
4097
+ type: "select"
4098
+ },
4099
+ {
4100
+ group: "basics",
4101
+ key: "heroHeight",
4102
+ label: "Hero Height",
4103
+ options: [
4104
+ { label: "Small", value: "sm" },
4105
+ { label: "Medium (Half Screen)", value: "md" },
4106
+ { label: "Full Screen", value: "full" }
4107
+ ],
4108
+ type: "select"
4109
+ },
4110
+ ...layoutFieldSet,
4111
+ ...typographyFieldSet,
4112
+ ...mediaFieldSet,
4113
+ ...styleFieldSet,
4114
+ ...commonAdvanced
4115
+ ]
4116
+ },
4117
+ logoWall: {
4118
+ blockType: "logoWall",
4119
+ fields: [
4120
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
4121
+ { group: "basics", inlineEditable: true, key: "subtitle", label: "Subtitle", type: "textarea" },
4122
+ ...layoutFieldSet,
4123
+ ...typographyFieldSet,
4124
+ ...mediaFieldSet,
4125
+ ...styleFieldSet,
4126
+ ...commonAdvanced
4127
+ ]
4128
+ },
4129
+ media: {
4130
+ blockType: "media",
4131
+ fields: [
4132
+ { group: "basics", inlineEditable: true, key: "caption", label: "Caption", type: "text" },
4133
+ {
4134
+ group: "basics",
4135
+ key: "size",
4136
+ label: "Size",
4137
+ options: [
4138
+ { label: "Default", value: "default" },
4139
+ { label: "Wide", value: "wide" }
4140
+ ],
4141
+ type: "select"
4142
+ },
4143
+ ...layoutFieldSet,
4144
+ ...typographyFieldSet,
4145
+ ...mediaFieldSet,
4146
+ ...styleFieldSet,
4147
+ ...commonAdvanced
4148
+ ]
4149
+ },
4150
+ richText: {
4151
+ blockType: "richText",
4152
+ fields: [
4153
+ {
4154
+ group: "basics",
4155
+ key: "variant",
4156
+ label: "Variant",
4157
+ options: [
4158
+ { label: "Default", value: "default" },
4159
+ { label: "Quote Banner", value: "quoteBanner" }
4160
+ ],
4161
+ type: "select"
4162
+ },
4163
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
4164
+ {
4165
+ group: "basics",
4166
+ key: "width",
4167
+ label: "Rich Text Width",
4168
+ options: [
4169
+ { label: "Normal", value: "normal" },
4170
+ { label: "Narrow", value: "narrow" }
4171
+ ],
4172
+ type: "select"
4173
+ },
4174
+ ...layoutFieldSet,
4175
+ ...typographyFieldSet,
4176
+ ...styleFieldSet,
4177
+ ...commonAdvanced
4178
+ ]
4179
+ },
4180
+ stats: {
4181
+ blockType: "stats",
4182
+ fields: [
4183
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
4184
+ { group: "basics", inlineEditable: true, key: "subtitle", label: "Subtitle", type: "textarea" },
4185
+ ...layoutFieldSet,
4186
+ ...typographyFieldSet,
4187
+ ...styleFieldSet,
4188
+ ...commonAdvanced
4189
+ ]
4190
+ },
4191
+ testimonials: {
4192
+ blockType: "testimonials",
4193
+ fields: [
4194
+ { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
4195
+ { group: "basics", key: "visibleCount", label: "Visible At Once", max: 6, min: 1, type: "number" },
4196
+ { group: "basics", key: "autoRotate", label: "Auto Rotate", type: "checkbox" },
4197
+ {
4198
+ advanced: true,
4199
+ group: "advanced",
4200
+ key: "rotateIntervalSeconds",
4201
+ label: "Rotate Interval Seconds",
4202
+ max: 30,
4203
+ min: 2,
4204
+ type: "number"
4205
+ },
4206
+ ...layoutFieldSet,
4207
+ ...typographyFieldSet,
4208
+ ...styleFieldSet,
4209
+ ...commonAdvanced
4210
+ ]
1721
4211
  }
1722
- return null;
1723
- }
4212
+ };
1724
4213
 
1725
- // src/studio/index.ts
1726
- var studio_exports = {};
1727
- __export(studio_exports, {
1728
- assertStudioDocumentV1: () => assertStudioDocumentV1,
1729
- compileStudioDocument: () => compileStudioDocument,
1730
- createEmptyStudioDocument: () => createEmptyStudioDocument,
1731
- createStudioRegistry: () => createStudioRegistry,
1732
- migrateStudioDocument: () => migrateStudioDocument,
1733
- validateStudioDocument: () => validateStudioDocument
1734
- });
1735
- var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
1736
- var makeIssue = (message, path2, code = "studio.invalid") => ({
1737
- code,
1738
- message,
1739
- path: path2,
1740
- severity: "error"
1741
- });
1742
- var createEmptyStudioDocument = (title) => ({
1743
- metadata: {},
1744
- schemaVersion: 1,
1745
- title,
1746
- nodes: [],
1747
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1748
- });
1749
- function assertStudioDocumentV1(input) {
1750
- if (!isRecord(input)) {
4214
+ // src/studio-pages/builder/settings-v2/BlockInspectorRenderer.tsx
4215
+ var import_jsx_runtime6 = require("react/jsx-runtime");
4216
+
4217
+ // src/studio-pages/migrations.ts
4218
+ var isRecord5 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
4219
+ var assertPageStudioDocumentV1 = (value) => {
4220
+ if (!isRecord5(value)) {
1751
4221
  throw new Error("Studio document must be an object");
1752
4222
  }
1753
- if (input.schemaVersion !== 1) {
4223
+ if (value.schemaVersion !== 1) {
1754
4224
  throw new Error("Unsupported studio schemaVersion");
1755
4225
  }
1756
- if (!Array.isArray(input.nodes)) {
4226
+ if (!Array.isArray(value.nodes)) {
1757
4227
  throw new Error("Studio document nodes must be an array");
1758
4228
  }
1759
- const nodes = input.nodes.map((node, index) => {
1760
- if (!isRecord(node)) {
4229
+ const nodes = value.nodes.map((node, index) => {
4230
+ if (!isRecord5(node)) {
1761
4231
  throw new Error(`Node at index ${index} must be an object`);
1762
4232
  }
1763
4233
  if (typeof node.id !== "string" || node.id.length === 0) {
@@ -1766,116 +4236,50 @@ function assertStudioDocumentV1(input) {
1766
4236
  if (typeof node.type !== "string" || node.type.length === 0) {
1767
4237
  throw new Error(`Node at index ${index} has invalid type`);
1768
4238
  }
1769
- if (!isRecord(node.data)) {
4239
+ if (!isRecord5(node.data)) {
1770
4240
  throw new Error(`Node at index ${index} has invalid data`);
1771
4241
  }
1772
4242
  return {
4243
+ data: node.data,
1773
4244
  id: node.id,
1774
- type: node.type,
1775
- data: node.data
4245
+ type: node.type
1776
4246
  };
1777
4247
  });
1778
4248
  return {
1779
- metadata: isRecord(input.metadata) ? input.metadata : void 0,
1780
- schemaVersion: 1,
1781
- title: typeof input.title === "string" ? input.title : void 0,
4249
+ metadata: isRecord5(value.metadata) ? value.metadata : void 0,
1782
4250
  nodes,
1783
- updatedAt: typeof input.updatedAt === "string" ? input.updatedAt : void 0
4251
+ schemaVersion: 1,
4252
+ title: typeof value.title === "string" ? value.title : void 0,
4253
+ updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : void 0
1784
4254
  };
1785
- }
1786
- function createStudioRegistry(modules) {
1787
- const moduleByID = new Map(modules.map((mod) => [mod.id, mod]));
1788
- const nodeTypes = modules.flatMap((mod) => mod.nodeTypes);
1789
- const nodeTypeByName = new Map(nodeTypes.map((definition) => [definition.type, definition]));
4255
+ };
4256
+ var migratePageNodeToSettingsV2 = (node) => {
4257
+ const normalized = migrateBlockToSettingsV2({
4258
+ blockType: node.type,
4259
+ ...node.data
4260
+ });
4261
+ const { blockType: _ignoredBlockType, ...data } = normalized;
1790
4262
  return {
1791
- getModuleByID: (id) => moduleByID.get(id),
1792
- getNodeTypeByName: (type) => nodeTypeByName.get(type),
1793
- listInspectorPanels: () => modules.flatMap((mod) => mod.inspectorPanels),
1794
- listModules: () => [...modules],
1795
- listPaletteGroups: () => modules.flatMap((mod) => mod.paletteGroups),
1796
- listNodeTypes: () => [...nodeTypes]
4263
+ ...node,
4264
+ data
1797
4265
  };
1798
- }
1799
- function validateStudioDocument(document, modules) {
1800
- const issues = [];
1801
- if (document.schemaVersion !== 1) {
1802
- issues.push(makeIssue("Unsupported schema version", "schemaVersion", "studio.schemaVersion"));
1803
- }
1804
- if (!Array.isArray(document.nodes)) {
1805
- issues.push(makeIssue("Nodes must be an array", "nodes", "studio.nodes"));
1806
- return issues;
1807
- }
1808
- const registry = createStudioRegistry(modules);
1809
- const nodeIDs = /* @__PURE__ */ new Set();
1810
- document.nodes.forEach((node, index) => {
1811
- if (!node.id) {
1812
- issues.push(makeIssue("Node id is required", `nodes.${index}.id`, "studio.node.id"));
1813
- }
1814
- if (nodeIDs.has(node.id)) {
1815
- issues.push(makeIssue("Node id must be unique", `nodes.${index}.id`, "studio.node.id.duplicate"));
1816
- }
1817
- nodeIDs.add(node.id);
1818
- if (!registry.getNodeTypeByName(node.type)) {
1819
- issues.push(makeIssue("Unsupported node type", `nodes.${index}.type`, "studio.node.type"));
1820
- }
1821
- });
1822
- for (const module2 of modules) {
1823
- for (const validate of module2.validators) {
1824
- issues.push(...validate(document));
1825
- }
1826
- }
1827
- return issues;
1828
- }
1829
- function compileStudioDocument(document, modules) {
1830
- const issues = validateStudioDocument(document, modules);
1831
- const compilerEntries = modules.filter((mod) => typeof mod.compiler?.compileNode === "function").map((mod) => mod.compiler?.compileNode);
1832
- const layout = document.nodes.map((node) => {
1833
- for (const compileNode of compilerEntries) {
1834
- if (!compileNode) {
1835
- continue;
1836
- }
1837
- const compiled = compileNode(node);
1838
- if (compiled) {
1839
- return compiled;
1840
- }
1841
- }
1842
- return {
1843
- id: node.id,
1844
- blockType: node.type,
1845
- ...node.data
1846
- };
1847
- });
4266
+ };
4267
+ var migratePageDocumentSettingsToV2 = (value) => {
4268
+ const current = assertPageStudioDocumentV1(value);
1848
4269
  return {
1849
- issues,
1850
- layout
4270
+ ...current,
4271
+ nodes: current.nodes.map(migratePageNodeToSettingsV2)
1851
4272
  };
1852
- }
1853
- function migrateStudioDocument(value, migrations) {
1854
- const sorted = [...migrations].sort((a, b) => a.fromVersion - b.fromVersion);
1855
- if (isRecord(value) && value.schemaVersion === 1) {
1856
- return assertStudioDocumentV1(value);
1857
- }
1858
- let current = value;
1859
- for (const migration of sorted) {
1860
- if (!isRecord(current) || current.schemaVersion !== migration.fromVersion) {
1861
- continue;
1862
- }
1863
- current = migration.migrate(current);
4273
+ };
4274
+ var pageStudioMigrations = [
4275
+ {
4276
+ fromVersion: 1,
4277
+ migrate: migratePageDocumentSettingsToV2,
4278
+ toVersion: 1
1864
4279
  }
1865
- return assertStudioDocumentV1(current);
1866
- }
4280
+ ];
1867
4281
 
1868
4282
  // src/studio-pages/index.ts
1869
- var studio_pages_exports = {};
1870
- __export(studio_pages_exports, {
1871
- createDefaultStudioDocument: () => createDefaultStudioDocument,
1872
- layoutToStudioDocument: () => layoutToStudioDocument,
1873
- pageInspectorPanels: () => pageInspectorPanels,
1874
- pageNodeTypes: () => pageNodeTypes,
1875
- pagePaletteGroups: () => pagePaletteGroups,
1876
- pageStudioModuleManifest: () => pageStudioModuleManifest,
1877
- studioDocumentToLayout: () => studioDocumentToLayout
1878
- });
1879
4283
  var withSectionStyleDefaults = (value) => ({
1880
4284
  ...sectionStyleDefaults,
1881
4285
  ...value
@@ -1889,9 +4293,13 @@ var defaultNodeData = {
1889
4293
  title: "Book a Time"
1890
4294
  },
1891
4295
  beforeAfter: withSectionStyleDefaults({
4296
+ itemsPerRow: 2,
1892
4297
  items: [
1893
4298
  {
1894
4299
  description: "Before and after result summary.",
4300
+ imageCornerStyle: "rounded",
4301
+ imageFit: "cover",
4302
+ imagePosition: "center",
1895
4303
  label: "Project One"
1896
4304
  }
1897
4305
  ],
@@ -1900,11 +4308,14 @@ var defaultNodeData = {
1900
4308
  }),
1901
4309
  cta: {
1902
4310
  ...withSectionStyleDefaults({}),
1903
- backgroundColor: "#1f684f",
4311
+ backgroundColor: "",
4312
+ bullets: [],
1904
4313
  buttonHref: "/contact",
1905
4314
  buttonLabel: "Contact Us",
1906
4315
  description: "Optional supporting copy.",
4316
+ eyebrow: "",
1907
4317
  headline: "Ready to get started?",
4318
+ imageURL: "",
1908
4319
  style: "light"
1909
4320
  },
1910
4321
  faq: {
@@ -1914,11 +4325,13 @@ var defaultNodeData = {
1914
4325
  },
1915
4326
  featureGrid: {
1916
4327
  ...withSectionStyleDefaults({}),
4328
+ itemsPerRow: 3,
1917
4329
  items: [
1918
- { description: "Explain this point.", icon: "01", title: "Feature One" },
1919
- { description: "Explain this point.", icon: "02", title: "Feature Two" },
1920
- { description: "Explain this point.", icon: "03", title: "Feature Three" }
4330
+ { description: "Explain this point.", iconType: "badge", icon: "01", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature One" },
4331
+ { description: "Explain this point.", iconType: "badge", icon: "02", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature Two" },
4332
+ { description: "Explain this point.", iconType: "badge", icon: "03", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature Three" }
1921
4333
  ],
4334
+ subtitle: "",
1922
4335
  title: "Section Title",
1923
4336
  variant: "cards"
1924
4337
  },
@@ -1926,13 +4339,28 @@ var defaultNodeData = {
1926
4339
  ...withSectionStyleDefaults({}),
1927
4340
  description: "Collect lead details from visitors.",
1928
4341
  formType: "quote",
4342
+ submitLabel: "Submit",
1929
4343
  title: "Request a Quote"
1930
4344
  },
1931
4345
  hero: {
1932
4346
  ...withSectionStyleDefaults({}),
1933
4347
  backgroundColor: "",
4348
+ backgroundImageURL: "",
4349
+ backgroundOverlayMode: "none",
4350
+ backgroundOverlayOpacity: 45,
4351
+ backgroundOverlayColor: "#000000",
4352
+ backgroundOverlayGradientFrom: "#334b63",
4353
+ backgroundOverlayGradientTo: "#496582",
4354
+ backgroundOverlayGradientAngle: "135",
4355
+ backgroundOverlayGradientFromStrength: 100,
4356
+ backgroundOverlayGradientToStrength: 100,
4357
+ backgroundOverlayGradientStart: 0,
4358
+ backgroundOverlayGradientEnd: 100,
4359
+ backgroundOverlayGradientFeather: 100,
4360
+ backgroundImageCornerStyle: "rounded",
1934
4361
  backgroundImageFit: "cover",
1935
4362
  backgroundImagePosition: "center",
4363
+ heroHeight: "sm",
1936
4364
  headline: "New Hero Section",
1937
4365
  kicker: "Optional kicker",
1938
4366
  primaryHref: "/contact",
@@ -1945,19 +4373,23 @@ var defaultNodeData = {
1945
4373
  media: {
1946
4374
  ...withSectionStyleDefaults({}),
1947
4375
  caption: "Add a caption",
4376
+ imageCornerStyle: "rounded",
4377
+ imageFit: "cover",
4378
+ imagePosition: "center",
1948
4379
  size: "default"
1949
4380
  },
1950
4381
  logoWall: withSectionStyleDefaults({
1951
4382
  items: [
1952
- { name: "Trusted Partner 1" },
1953
- { name: "Trusted Partner 2" },
1954
- { name: "Trusted Partner 3" }
4383
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 1" },
4384
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 2" },
4385
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 3" }
1955
4386
  ],
1956
- subtitle: "Trusted by teams and homeowners across Central Texas.",
1957
- title: "Trusted by Local Organizations"
4387
+ subtitle: "Show logos from trusted associations, partners, or collaborators.",
4388
+ title: "Trusted by Great Partners"
1958
4389
  }),
1959
4390
  richText: {
1960
4391
  ...withSectionStyleDefaults({}),
4392
+ cards: [],
1961
4393
  content: {
1962
4394
  root: {
1963
4395
  children: [
@@ -1987,19 +4419,23 @@ var defaultNodeData = {
1987
4419
  version: 1
1988
4420
  }
1989
4421
  },
4422
+ statsItems: [],
1990
4423
  title: "Section Heading",
1991
4424
  width: "normal"
1992
4425
  },
1993
4426
  testimonials: {
1994
4427
  ...withSectionStyleDefaults({}),
1995
- items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here." }],
1996
- title: "What Customers Say"
4428
+ autoRotate: true,
4429
+ items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here.", rating: 5 }],
4430
+ rotateIntervalSeconds: 7,
4431
+ title: "What Customers Say",
4432
+ visibleCount: 3
1997
4433
  },
1998
4434
  stats: withSectionStyleDefaults({
1999
4435
  items: [
2000
- { description: "Average response time", label: "Same-Day Quotes", value: "24h" },
2001
- { description: "Projects completed", label: "Completed Jobs", value: "1,200+" },
2002
- { description: "Client satisfaction score", label: "Satisfaction", value: "4.9/5" }
4436
+ { description: "Average first response", label: "Response Time", value: "24h" },
4437
+ { description: "Client satisfaction score", label: "Satisfaction", value: "4.9/5" },
4438
+ { description: "Recent projects or clients served", label: "Recent Work", value: "150+" }
2003
4439
  ],
2004
4440
  subtitle: "Highlight measurable outcomes to build trust quickly.",
2005
4441
  title: "Performance Highlights"
@@ -2023,7 +4459,11 @@ var pageNodeTypes = Object.keys(defaultNodeData).map((type) => ({
2023
4459
  type,
2024
4460
  displayName: nodeTypeLabels[type] || type,
2025
4461
  description: `Page node for ${nodeTypeLabels[type] || type}`,
2026
- getDefaultData: () => structuredClone(defaultNodeData[type])
4462
+ getDefaultData: () => {
4463
+ const migrated = migrateBlockToSettingsV2(structuredClone(defaultNodeData[type]));
4464
+ const { blockType: _ignoredBlockType, ...data } = migrated;
4465
+ return data;
4466
+ }
2027
4467
  }));
2028
4468
  var validatePageDocument = (document) => {
2029
4469
  const issues = [];
@@ -2075,17 +4515,24 @@ var pagePaletteGroups = [
2075
4515
  ]
2076
4516
  }
2077
4517
  ];
2078
- var pageInspectorPanels = Object.keys(defaultNodeData).map((nodeType) => ({
2079
- id: `${nodeType}-panel`,
2080
- label: `${nodeTypeLabels[nodeType] || nodeType} Settings`,
4518
+ var pageInspectorPanelRegistry = Object.keys(defaultNodeData).map((nodeType) => ({
2081
4519
  nodeType,
2082
- fields: [
2083
- {
2084
- key: "title",
2085
- label: "Title",
2086
- type: "text"
2087
- }
2088
- ]
4520
+ panelID: `${nodeType}-panel`,
4521
+ panelLabel: `${nodeTypeLabels[nodeType] || nodeType} Settings`
4522
+ }));
4523
+ var resolvePanelFields = (nodeType) => inspectorDefinitionByBlockType[nodeType]?.fields.map((field) => ({
4524
+ advanced: field.advanced,
4525
+ group: field.group,
4526
+ inlineEditable: field.inlineEditable,
4527
+ key: field.key,
4528
+ label: field.label,
4529
+ type: field.type
4530
+ })) || [];
4531
+ var pageInspectorPanels = pageInspectorPanelRegistry.map((entry) => ({
4532
+ fields: resolvePanelFields(entry.nodeType),
4533
+ id: entry.panelID,
4534
+ label: entry.panelLabel,
4535
+ nodeType: entry.nodeType
2089
4536
  }));
2090
4537
  var pageStudioModuleManifest = {
2091
4538
  id: "studio-pages",
@@ -2095,13 +4542,18 @@ var pageStudioModuleManifest = {
2095
4542
  paletteGroups: pagePaletteGroups,
2096
4543
  inspectorPanels: pageInspectorPanels,
2097
4544
  validators: [validatePageDocument],
2098
- migrations: [],
4545
+ migrations: pageStudioMigrations,
2099
4546
  compiler: {
2100
- compileNode: (node) => ({
2101
- id: node.id,
2102
- blockType: node.type,
2103
- ...node.data
2104
- })
4547
+ compileNode: (node) => {
4548
+ const normalized = migrateBlockToSettingsV2({
4549
+ blockType: node.type,
4550
+ ...node.data
4551
+ });
4552
+ return {
4553
+ id: node.id,
4554
+ ...normalized
4555
+ };
4556
+ }
2105
4557
  },
2106
4558
  permissions: [
2107
4559
  { action: "read", role: "admin" },
@@ -2114,42 +4566,6 @@ var pageStudioModuleManifest = {
2114
4566
  { action: "publish", role: "editor" }
2115
4567
  ]
2116
4568
  };
2117
- var ensureNodeID = (inputID, index) => {
2118
- if (typeof inputID === "string" && inputID.length > 0) {
2119
- return inputID;
2120
- }
2121
- return `node-${index + 1}`;
2122
- };
2123
- var layoutToStudioDocument = (layout, title, metadata) => {
2124
- const nodes = layout.filter((block) => typeof block.blockType === "string").map((block, index) => {
2125
- const blockType = String(block.blockType);
2126
- const { id, blockType: _, ...data } = block;
2127
- return {
2128
- id: ensureNodeID(id, index),
2129
- type: blockType,
2130
- data
2131
- };
2132
- });
2133
- return {
2134
- metadata,
2135
- schemaVersion: 1,
2136
- title,
2137
- nodes,
2138
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2139
- };
2140
- };
2141
- var studioDocumentToLayout = (document) => document.nodes.map((node) => ({
2142
- id: node.id,
2143
- blockType: node.type,
2144
- ...node.data
2145
- }));
2146
- var createDefaultStudioDocument = (title) => ({
2147
- metadata: {},
2148
- schemaVersion: 1,
2149
- title,
2150
- nodes: [],
2151
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2152
- });
2153
4569
  // Annotate the CommonJS export names for ESM import in node:
2154
4570
  0 && (module.exports = {
2155
4571
  admin,