@universalcms/connect 0.13.3 → 0.18.0

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 (32) hide show
  1. package/README.md +6 -4
  2. package/dist/cli.mjs +7 -5
  3. package/dist/guided.mjs +79 -3
  4. package/dist/native-website-server.mjs +47 -16
  5. package/dist/native-website.mjs +28 -23
  6. package/dist/publication.css +5 -0
  7. package/dist/publication.mjs +10 -4
  8. package/dist/react-publication.mjs +23 -1
  9. package/dist/react-website.d.mts +1 -1
  10. package/dist/react-website.mjs +5 -3
  11. package/dist/templates/next-theme/app/[[...cmsPath]]/page.tsx +13 -4
  12. package/dist/templates/next-theme/app/api/cms-source/route.ts +10 -0
  13. package/dist/templates/next-theme/app/api/universalcms/[endpoint]/route.ts +2 -2
  14. package/dist/templates/next-theme/app/api/universalcms/form/route.ts +8 -0
  15. package/dist/templates/next-theme/app/cms-preview/page.tsx +28 -0
  16. package/dist/templates/next-theme/app/globals.css +94 -10
  17. package/dist/templates/next-theme/app/layout.tsx +11 -1
  18. package/dist/templates/next-theme/app/not-found.tsx +6 -3
  19. package/dist/templates/next-theme/app/robots.txt/route.ts +2 -2
  20. package/dist/templates/next-theme/app/sitemap.xml/route.ts +2 -2
  21. package/dist/templates/next-theme/components/site-chrome.tsx +38 -0
  22. package/dist/templates/next-theme/lib/universalcms.ts +11 -0
  23. package/dist/templates/next-theme/next.config.mjs +17 -1
  24. package/dist/theme-client.d.mts +4 -0
  25. package/dist/theme-client.mjs +70 -0
  26. package/dist/theme-preview.mjs +149 -0
  27. package/dist/theme.d.mts +9 -2
  28. package/dist/theme.mjs +190 -3
  29. package/dist/vendor/react-document.mjs +12 -3
  30. package/dist/vendor/react-preview-protocol.mjs +45 -0
  31. package/dist/website-schema.mjs +19 -4
  32. package/package.json +4 -2
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @universalcms/connect
2
2
 
3
3
  Local, deterministic website setup without AI. Universal CMS stays hosted; this
4
- package creates a website integration, not a CMS installation. Version 0.13.3 is
4
+ package creates a website integration, not a CMS installation. Version 0.18.0 is
5
5
  prepared locally; this repository does not establish its public npm availability.
6
6
 
7
7
  ## Quick start
@@ -20,6 +20,8 @@ connection (Next.js): the CMS manages menus, branding, SEO, analytics, scripts,
20
20
  consent and redirects while the site renders every page. Add
21
21
  `<CmsNavigation location="header" />` from `@universalcms/connect/react-website`
22
22
  where CMS menus should appear.
23
+ Add `--reset` to disconnect the folder first and connect it to another website
24
+ or organization.
23
25
  The individual commands below remain for developers and reviewed integrations.
24
26
 
25
27
  Supported templates: **Next.js 16 App Router on Node**, **React 19.2+ with
@@ -262,7 +264,7 @@ custom adapter still requires review, not forced overwriting.
262
264
  For an operator's downloaded artifact, the installation is:
263
265
 
264
266
  ```sh
265
- npm install --save-prod ./vendor/universalcms-connect-0.13.3.tgz
267
+ npm install --save-prod ./vendor/universalcms-connect-0.18.0.tgz
266
268
  ```
267
269
 
268
270
  Use your project's package manager. These paths are examples for downloaded
@@ -539,7 +541,7 @@ npx --no-install universalcms-connect init \
539
541
  --environment 22222222-2222-4222-8222-222222222222
540
542
  ```
541
543
 
542
- After a public release, `npx @universalcms/connect@0.13.3 init` is the equivalent
544
+ After a public release, `npx @universalcms/connect@0.18.0 init` is the equivalent
543
545
  registry entry point. Copy the real IDs/API origin from **Developer setup**.
544
546
  Missing values are prompted in a terminal; `--non-interactive` fails instead.
545
547
  Init prints a plan. Run the same command with `--apply` after reviewing it.
@@ -589,7 +591,7 @@ The CMS button reads the website's **`/api/cms-source`** endpoint. Current Next.
589
591
  prepares delivery scaffolding. React/Vite setup serves its reviewed public manifest through the normal dev command.
590
592
  Custom exporters need their explicit adapter; a delivery check alone is insufficient.
591
593
 
592
- Use the CMS's reviewed 0.13.3 package for the staged commands and current source
594
+ Use the CMS's reviewed 0.18.0 package for the staged commands and current source
593
595
  discovery. Review source diagnostics and complete public
594
596
  mappings for unsupported pages. Restart the website using its normal server command, then run:
595
597
 
package/dist/cli.mjs CHANGED
@@ -20,7 +20,7 @@ import { WEBSITE_SECTIONS } from './website-http.mjs';
20
20
  import { setupSource, applySourcePlan, syncSource, readSourceSettings } from './source-setup.mjs';
21
21
  import { processIo, runCreate, runGuided } from './guided.mjs';
22
22
 
23
- const help = `Universal CMS connector 0.13.3
23
+ const help = `Universal CMS connector 0.18.0
24
24
 
25
25
  Quick start (recommended) — run in your website folder:
26
26
  npx @universalcms/connect@latest --cms https://YOUR_CMS_ADDRESS
@@ -33,9 +33,11 @@ Quick start (recommended) — run in your website folder:
33
33
  --cms <origin> Your Universal CMS address (or UNIVERSALCMS_URL)
34
34
  --code <code> One-time code from Developer setup → Connect without a browser
35
35
  --details Show every file and technical message
36
+ --reset Disconnect this folder first, then connect it to another website or organization
36
37
 
37
38
  New website managed entirely from the CMS (pages, posts, menus, design):
38
39
  npx @universalcms/connect@latest create my-website --cms https://YOUR_CMS_ADDRESS
40
+ Add --from ../existing-site to bring over its pages and menus (Next.js).
39
41
 
40
42
  Individual steps (advanced):
41
43
  Usage: universalcms-connect <start|inspect-pages|review-pages|preflight|review-routes|review-layout|connect|setup|setup-settings|check-settings|server-key|setup-source|sync-source|finalize-source|analyze|init|check|check-source|check-publication> [options]
@@ -334,11 +336,11 @@ async function ask(prompt) {
334
336
 
335
337
  function guidedArguments(args) {
336
338
  if (args[0] === 'start') args = args.slice(1);
337
- const names = new Set(['--cms', '--code', '--project']), options = {};
339
+ const names = new Set(['--cms', '--code', '--project', '--from']), options = {};
338
340
  for (let index = 0; index < args.length; index++) {
339
341
  const name = args[index];
340
342
  if (Object.hasOwn(options, name)) throw new SetupError('Each option can be given once.');
341
- if (name === '--details') { options[name] = true; continue; }
343
+ if (name === '--details' || name === '--reset') { options[name] = true; continue; }
342
344
  if (!names.has(name)) throw new SetupError(`Unknown option ${JSON.stringify(name.slice(0, 40))}. Use --help for instructions.`);
343
345
  const value = args[++index];
344
346
  if (!value || value.startsWith('--')) throw new SetupError('An option is missing its value.');
@@ -355,7 +357,7 @@ async function main() {
355
357
  const options = guidedArguments(rest);
356
358
  if (!process.stdin.isTTY || !process.stderr.isTTY) throw new SetupError('This setup needs an interactive terminal.');
357
359
  const io = processIo({ ask, write: value => process.stdout.write(value) });
358
- await runCreate(process.cwd(), name ?? (await ask('Folder name for the new website: ')).trim(), { cms: options['--cms'], code: options['--code'] }, io);
360
+ await runCreate(process.cwd(), name ?? (await ask('Folder name for the new website: ')).trim(), { cms: options['--cms'], code: options['--code'], from: options['--from'] }, io);
359
361
  return;
360
362
  }
361
363
  if (first === undefined || first === 'start' || first.startsWith('--')) {
@@ -363,7 +365,7 @@ async function main() {
363
365
  // it expires in ten minutes, works once and never reaches project files.
364
366
  const options = guidedArguments(process.argv.slice(2));
365
367
  if (!process.stdin.isTTY || !process.stderr.isTTY) throw new SetupError('This setup needs an interactive terminal. Run it directly in your website folder.');
366
- await runGuided(options['--project'] ?? process.cwd(), { cms: options['--cms'], code: options['--code'], details: options['--details'] === true }, processIo({ ask, write: value => process.stdout.write(value) }));
368
+ await runGuided(options['--project'] ?? process.cwd(), { cms: options['--cms'], code: options['--code'], details: options['--details'] === true, reset: options['--reset'] === true }, processIo({ ask, write: value => process.stdout.write(value) }));
367
369
  return;
368
370
  }
369
371
  const { command, options } = argumentsFor(process.argv.slice(2));
package/dist/guided.mjs CHANGED
@@ -9,7 +9,7 @@
9
9
  import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
10
10
  import { createServer } from 'node:http';
11
11
  import { spawn } from 'node:child_process';
12
- import { cp, lstat, mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises';
12
+ import { chmod, cp, lstat, mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
13
13
  import path from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  import { analyze, applyPlan, projectRoot, readProjectFile, SetupError } from './core.mjs';
@@ -21,6 +21,7 @@ import { checkPublication } from './publication-check.mjs';
21
21
  import { finalizeSource } from './source-finalize.mjs';
22
22
  import { preflightSetup } from './preflight.mjs';
23
23
  import { planSettingsOnly, SETTINGS_ONLY_FILE } from './settings-only-setup.mjs';
24
+ import { discoverSite } from './site-discovery.mjs';
24
25
  import { applyEnvironment, planEnvironment } from './setup-environment.mjs';
25
26
  import { themeConfiguration } from './theme.mjs';
26
27
 
@@ -378,9 +379,15 @@ export async function runGuided(directory, options, io) {
378
379
  const analysis = await analyze(root).catch(() => ({}));
379
380
  const framework = typeof analysis.framework === 'string' && analysis.framework !== 'Unknown' ? analysis.framework : null;
380
381
  io.write(`✔ Found ${framework ? `a ${framework} website` : 'a website'} in ${path.basename(root)}\n`);
382
+ if (options.reset) await resetConnection(root, io);
381
383
 
382
384
  // A Universal CMS theme website only needs pairing and its private key.
383
385
  if (await readProjectFile(root, THEME_FILE) !== null) {
386
+ const connectedTheme = await readConnection(root).catch(() => null);
387
+ if (connectedTheme && !options.reset && !options.code && (await readSetupEnvironment(root)).keyConfigured && await readProjectFile(root, IMPORT_FILE) !== null) {
388
+ await closeImport(root, connectedTheme, io);
389
+ return themeDone(root, connectedTheme, await packageManager(root, manifest), false, io);
390
+ }
384
391
  const cms = cmsOrigin(options.cms ?? io.env.UNIVERSALCMS_URL ?? '') ?? fail('Add --cms with your Universal CMS address.');
385
392
  const approval = options.code ? { code: options.code } : await waitForBrowserApproval({ cms, io, request: { origin: suggestedOrigin(manifest, null) ?? `http://localhost:${THEME_PORT}`, project: path.basename(root).slice(0, 80), framework: 'Universal CMS theme' } });
386
393
  const paired = await redeem(cms, approval.code, approval.verifier, io.fetch);
@@ -429,7 +436,7 @@ export async function runGuided(directory, options, io) {
429
436
  : await waitForBrowserApproval({ cms, io, request: { origin: saved?.websiteOrigin ?? suggestedOrigin(manifest, framework) ?? '', project: path.basename(root).slice(0, 80), framework: framework ?? '' } });
430
437
  let mode = 'full';
431
438
  const mismatch = connection => saved && (saved.siteId !== connection.siteId || saved.environmentId !== connection.environmentId);
432
- const different = 'This folder is already connected to a different website or environment in the CMS. Choose the same website when approving, or ask your developer to review .universalcms/connection.json. Nothing was changed.';
439
+ const different = 'This folder is already connected to a different website or environment in the CMS. Choose the same website when approving, or run the command again with --reset to connect it to the other website. Nothing was changed.';
433
440
  // Check (and safely fix) the website before redeeming, so a blocked site
434
441
  // never leaves an unused key behind. The approval expires on its own.
435
442
  if (approval.scope && !installed) {
@@ -471,6 +478,41 @@ export const THEME_FILE = '.universalcms/theme.json';
471
478
  export const THEME_WEBSITE_FILE = '.universalcms/website.json';
472
479
  const THEME_PORT = 3100;
473
480
 
481
+ const RESET_FILES = ['.universalcms/connection.json', '.universalcms/settings-only.json', '.universalcms/discovered.json', '.universalcms/website.json'];
482
+ const RESET_ENV = /^\s*(?:export\s+)?(CMS_API_BASE_URL|CMS_SITE_ID|CMS_ENVIRONMENT_ID|CMS_LOCALE|CMS_API_KEY|CMS_WEBSITE_ORIGIN|CMS_SOURCE_ORIGIN|CMS_ADMIN_ORIGIN)\s*=/;
483
+
484
+ /**
485
+ * `--reset`: disconnect this folder from its CMS website so it can be connected
486
+ * to another website or organization. Removes only the saved connection and the
487
+ * private CMS_* values; generated integration code stays and is updated when
488
+ * the folder is connected again in the same run.
489
+ */
490
+ export async function resetConnection(directory, io) {
491
+ const root = await projectRoot(directory);
492
+ const settingsOnly = await readProjectFile(root, SETTINGS_ONLY_FILE) !== null, theme = await readProjectFile(root, THEME_FILE) !== null;
493
+ const full = !settingsOnly && !theme && (await readProjectFile(root, '.universalcms/publication.config.json') !== null || await readProjectFile(root, '.universalcms/website.integration.json') !== null);
494
+ if (full) fail('This folder has a full page integration (pages are bound to CMS content for one website). Moving it to another website needs your developer: the page bindings and imported content belong to the current website. Nothing was changed.');
495
+ const present = [];
496
+ for (const file of RESET_FILES) if (await readProjectFile(root, file) !== null) present.push(file);
497
+ const envText = await readProjectFile(root, '.env.local');
498
+ const envLines = envText === null ? [] : envText.split(/\r?\n/).filter(line => RESET_ENV.test(line));
499
+ const saved = await readConnection(root).catch(() => null);
500
+ if (!present.length && !envLines.length) { io.write('This folder is not connected to a CMS website. Nothing to reset.\n'); return false; }
501
+ io.write(`\nDisconnect this folder${saved ? ` from website ${saved.siteId} (${saved.websiteOrigin})` : ''}:\n`);
502
+ for (const file of present) io.write(` remove: ${file}\n`);
503
+ if (envLines.length) io.write(` remove from .env.local: ${envLines.map(line => line.split('=')[0].replace(/^\s*(export\s+)?/, '').trim()).join(', ')}\n`);
504
+ io.write('Your pages, design and other settings stay as they are. You will connect it to a website right after this.\n');
505
+ if (!yes(await io.ask('Disconnect? [Y/n] '))) fail('Nothing was changed.');
506
+ for (const file of present) await rm(path.join(root, file), { force: true });
507
+ if (envLines.length) {
508
+ const kept = envText.split(/(\r?\n)/).reduce((out, part, index, parts) => { if (index % 2 === 0 && !RESET_ENV.test(part)) out.push(part + (parts[index + 1] ?? '')); return out; }, []).join('');
509
+ await writeFile(path.join(root, '.env.local'), kept, { mode: 0o600 });
510
+ await chmod(path.join(root, '.env.local'), 0o600);
511
+ }
512
+ io.write('✔ Disconnected. The old website key still works until you revoke it in that website\'s Developer setup → Advanced → website keys.\n');
513
+ return true;
514
+ }
515
+
474
516
  /** Save the public theme connection and the private key after pairing. */
475
517
  async function connectTheme(root, paired, io) {
476
518
  const { connection } = paired;
@@ -491,6 +533,33 @@ function themeDone(root, connection, manager, created, io) {
491
533
  return { connected: true, mode: 'theme', done: true };
492
534
  }
493
535
 
536
+ export const IMPORT_FILE = '.universalcms/import.json';
537
+
538
+ /** Read a previous Next.js website's pages (text, headings, images, links) and menus, without running it. */
539
+ export async function collectPreviousSite(from, io) {
540
+ const oldRoot = await projectRoot(from).catch(() => fail(`The folder ${from} was not found. Point --from at your existing website folder (the one with package.json).`));
541
+ const manifest = await manifestOf(oldRoot);
542
+ if (!manifest.dependencies?.next && !manifest.devDependencies?.next) fail('Moving content automatically supports Next.js (App Router) websites in this version. Nothing was changed.');
543
+ const { collectSource } = await import('./source-collect.mjs');
544
+ const collected = await collectSource({ root: oldRoot, locale: 'en' });
545
+ const discovered = await discoverSite(oldRoot);
546
+ io.write(`✔ Found ${collected.entries.length} page${collected.entries.length === 1 ? '' : 's'} and ${discovered.navigation.header.length + discovered.navigation.footer.length} menu links in ${path.basename(oldRoot)}\n`);
547
+ if (collected.diagnostics?.unsupported) io.write(` (${collected.diagnostics.unsupported} page${collected.diagnostics.unsupported === 1 ? '' : 's'} with dynamic content were skipped — recreate them in the CMS)\n`);
548
+ return { entries: collected.entries, pages: discovered.pages, navigation: discovered.navigation };
549
+ }
550
+
551
+ /** After the CMS import, the copy of the previous website's content is removed. */
552
+ async function closeImport(root, connection, io) {
553
+ if (await readProjectFile(root, IMPORT_FILE) === null) return;
554
+ const setup = `${connection.adminOrigin}/sites/${connection.siteId}#setup?environmentId=${connection.environmentId}`;
555
+ io.write(`\nNext, bring your previous website's pages and menus into the CMS:\n Start this website (npm run dev), then open Developer setup:\n ${setup}\n Click “Connect & fetch content” and “Import into Header & footer”.\n`);
556
+ await io.openUrl(setup);
557
+ const answer = (await io.ask('\nDid the CMS show “Website content fetched”? [y/N] ')).trim().toLowerCase();
558
+ if (!['y', 'yes'].includes(answer)) { io.write('No problem — the import stays available. Run the same command in this folder again when it is done.\n'); return; }
559
+ await rm(path.join(root, IMPORT_FILE), { force: true });
560
+ io.write('✔ Import finished; the copied content was removed from this folder\n');
561
+ }
562
+
494
563
  /** `create <folder>`: start a new website rendered entirely from Universal CMS. */
495
564
  export async function runCreate(parent, name, options, io) {
496
565
  if (typeof name !== 'string' || !/^[a-z0-9][a-z0-9._-]{0,63}$/i.test(name)) fail('Choose a simple folder name for the new website, for example: my-website');
@@ -505,6 +574,7 @@ export async function runCreate(parent, name, options, io) {
505
574
  }
506
575
  })();
507
576
  io.write(`\nUniversal CMS · create a new website in ${name}\n`);
577
+ const previous = options.from ? await collectPreviousSite(path.resolve(parent, options.from), io) : null;
508
578
  // Copy into a temporary sibling first so a failed copy never leaves a half-made folder.
509
579
  const staging = `${root}.universalcms-${randomBytes(4).toString('hex')}`;
510
580
  await cp(fileURLToPath(new URL('./templates/next-theme/', import.meta.url)), staging, { recursive: true, errorOnExist: true, force: false });
@@ -525,7 +595,13 @@ export async function runCreate(parent, name, options, io) {
525
595
  const paired = await redeem(cms, approval.code, approval.verifier, io.fetch);
526
596
  io.write(`✔ Approved: ${paired.siteName || 'your website'} (${paired.connection.websiteOrigin})\n`);
527
597
  await connectTheme(root, paired, io);
528
- return themeDone(root, paired.connection, 'npm', true, io);
598
+ if (previous) {
599
+ await writeFile(path.join(root, IMPORT_FILE), `${JSON.stringify({ version: 1, siteId: paired.connection.siteId, environmentId: paired.connection.environmentId, ...previous })}\n`);
600
+ io.write('✔ Previous website content saved for import\n');
601
+ }
602
+ const result = themeDone(root, paired.connection, 'npm', true, io);
603
+ if (previous) io.write(`\n Then import your previous content: run “npx @universalcms/connect@latest” inside ${name} and follow the steps.\n`);
604
+ return result;
529
605
  }
530
606
 
531
607
  /** Ask the running website's settings endpoint, as the CMS admin does. */
@@ -11719,10 +11719,17 @@ function CmsForm({ definition, entryId, blockId, revisionId, variant, endpoint,
11719
11719
  // ../react/src/index.tsx
11720
11720
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
11721
11721
  var supported2 = /* @__PURE__ */ new Set(["paragraph", "heading", "quote", "list", "code", "image", "gallery", "button", "divider", "spacer", "faq", "table", "embed", "form", "section", "columns"]);
11722
- var styles = { align: ["left", "center", "right"], size: ["small", "normal", "large"], tone: ["default", "muted", "accent", "inverse"], spaceAbove: ["none", "small", "medium", "large"], spaceBelow: ["none", "small", "medium", "large"], hideOnMobile: [true], hideOnDesktop: [true] };
11722
+ var styles = { align: ["left", "center", "right"], size: ["small", "normal", "large"], tone: ["default", "muted", "accent", "inverse"], spaceAbove: ["none", "small", "medium", "large"], spaceBelow: ["none", "small", "medium", "large"], hideOnMobile: [true], hideOnDesktop: [true], padding: ["none", "small", "medium", "large"], radius: ["none", "small", "medium", "large", "pill"], border: [true], weight: ["normal", "medium", "bold"], maxWidth: ["narrow", "normal", "full"], animation: ["fade-up", "fade-in", "zoom-in", "slide-left"] };
11723
+ function blockColors(block2) {
11724
+ const style = record(block2.style), hex2 = (value) => typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value) ? value : void 0;
11725
+ const color = hex2(style.textColor), background = hex2(style.background);
11726
+ if (!color && !background) return void 0;
11727
+ return { ...color ? { "--cms-block-color": color, color } : {}, ...background ? { "--cms-block-background": background, background } : {} };
11728
+ }
11723
11729
  function blockClasses(block2) {
11724
11730
  const style = record(block2.style);
11725
- return ["cms-block", `cms-block-${text(block2.type)}`, ...Object.entries(styles).flatMap(([key, allowed]) => allowed.includes(style[key]) ? [`cms-${key}-${String(style[key])}`] : [])].join(" ");
11731
+ const mobile = record(style.mobile), phone = ["align", "size", "padding", "spaceAbove", "spaceBelow"];
11732
+ return ["cms-block", `cms-block-${text(block2.type)}`, ...Object.entries(styles).flatMap(([key, allowed]) => allowed.includes(style[key]) ? [`cms-${key}-${String(style[key])}`] : []), ...phone.flatMap((key) => styles[key].includes(mobile[key]) ? [`cms-m-${key}-${String(mobile[key])}`] : [])].join(" ");
11726
11733
  }
11727
11734
  function image(data, key) {
11728
11735
  const src = safeUrl(data.url, false);
@@ -11847,7 +11854,7 @@ function CmsDocument({ document: document2, components = {}, entryId, revisionId
11847
11854
  }
11848
11855
  }
11849
11856
  const anchor = text(record(block2.style).anchor);
11850
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { id: /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(anchor) ? anchor : void 0, className: `${blockClasses(block2)}${preview && id2 === selectedId ? " cms-selected" : ""}`, ...preview ? { "data-cms-block": id2, "data-cms-type": type } : {}, children: node }, id2 || index);
11857
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { id: /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(anchor) ? anchor : void 0, className: `${blockClasses(block2)}${preview && id2 === selectedId ? " cms-selected" : ""}`, style: blockColors(block2), ...preview ? { "data-cms-block": id2, "data-cms-type": type } : {}, children: node }, id2 || index);
11851
11858
  }
11852
11859
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: ["cms-document", className].filter(Boolean).join(" "), children: renderMany(document2.blocks, "root") });
11853
11860
  }
@@ -12790,9 +12797,10 @@ function publicEntry(value) {
12790
12797
  const raw = record2(value), entry2 = validatePublishedDocumentEntry(value, { kind: text2(raw.kind, 40), slug: text2(raw.slug, 160), locale: text2(raw.locale, 35) });
12791
12798
  return { ...entry2, ...Object.fromEntries(["path", "seo", "author", "categories", "tags", "translations", "canonicalUrl", "publishedAt"].map((name) => [name, raw[name]])) };
12792
12799
  }
12793
- function CmsWebsitePage({ scope, pathname, initialEntry, initialResult, initialSettings, chrome = true }) {
12800
+ var FormEndpoint = (0, import_react5.createContext)(void 0);
12801
+ function CmsWebsitePage({ scope, pathname, initialEntry, initialResult, initialSettings, chrome = true, formEndpoint }) {
12794
12802
  const path2 = pathname ?? (typeof window === "undefined" ? "/" : window.location.pathname);
12795
- return /* @__PURE__ */ import_react5.default.createElement(WebsitePageLoader, { key: `${scope}:${path2}`, scope, pathname: path2, initialEntry, initialResult, initialSettings, chrome });
12803
+ return /* @__PURE__ */ import_react5.default.createElement(FormEndpoint.Provider, { value: formEndpoint }, /* @__PURE__ */ import_react5.default.createElement(WebsitePageLoader, { key: `${scope}:${path2}`, scope, pathname: path2, initialEntry, initialResult, initialSettings, chrome }));
12796
12804
  }
12797
12805
  function WebsitePageLoader({ scope, pathname, initialEntry, initialResult, initialSettings, chrome }) {
12798
12806
  const [page, setPage] = (0, import_react5.useState)(() => {
@@ -12885,7 +12893,7 @@ function PublishedPage({ entry: entry2, pathname, related }) {
12885
12893
  }, [entry2, presentation.showTableOfContents]);
12886
12894
  const route2 = (0, import_react5.useMemo)(() => ({ identity: { kind: entry2.kind, slug: entry2.slug, locale: entry2.locale }, baseline: entry2.document, bindings: {} }), [entry2]);
12887
12895
  const publishedDate = Number.isFinite(Date.parse(entry2.publishedAt)) ? new Date(entry2.publishedAt).toLocaleDateString(entry2.locale) : "";
12888
- return /* @__PURE__ */ import_react5.default.createElement("main", { className: `cms-website-page cms-website-layout-${presentation.layout}` }, presentation.showBreadcrumbs && /* @__PURE__ */ import_react5.default.createElement("nav", { "aria-label": "Breadcrumbs" }, /* @__PURE__ */ import_react5.default.createElement(SafeLink, { url: "/" }, "Home"), /* @__PURE__ */ import_react5.default.createElement("span", { "aria-hidden": "true" }, " / "), /* @__PURE__ */ import_react5.default.createElement("span", { "aria-current": "page" }, entry2.document.title)), /* @__PURE__ */ import_react5.default.createElement("h1", null, entry2.document.title), entry2.document.excerpt && /* @__PURE__ */ import_react5.default.createElement("p", { className: "cms-website-excerpt" }, entry2.document.excerpt), presentation.showDate && publishedDate && /* @__PURE__ */ import_react5.default.createElement("time", { dateTime: entry2.publishedAt }, publishedDate), presentation.showFeaturedImage && presentation.featuredImage.url && /* @__PURE__ */ import_react5.default.createElement("img", { className: "cms-website-featured", src: presentation.featuredImage.url, alt: presentation.featuredImage.alt, referrerPolicy: "no-referrer" }), !!headings.length && /* @__PURE__ */ import_react5.default.createElement("nav", { className: "cms-website-toc", "aria-label": "Table of contents" }, /* @__PURE__ */ import_react5.default.createElement("h2", null, "On this page"), /* @__PURE__ */ import_react5.default.createElement("ul", null, headings.map((item) => /* @__PURE__ */ import_react5.default.createElement("li", { key: item.id }, /* @__PURE__ */ import_react5.default.createElement(SafeLink, { url: `${pathname}#${encodeURIComponent(item.id)}` }, item.title))))), /* @__PURE__ */ import_react5.default.createElement("div", { ref: content }, /* @__PURE__ */ import_react5.default.createElement(PublishedDocumentProvider, { initialEntry: entry2, pathname, route: route2 }, /* @__PURE__ */ import_react5.default.createElement(CmsPublishedDocument, null))), presentation.author && /* @__PURE__ */ import_react5.default.createElement("aside", { className: "cms-website-author", "aria-label": "About the author" }, presentation.author.avatarUrl && /* @__PURE__ */ import_react5.default.createElement("img", { src: presentation.author.avatarUrl, alt: "", loading: "lazy", referrerPolicy: "no-referrer" }), /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("h2", null, presentation.author.url ? /* @__PURE__ */ import_react5.default.createElement(SafeLink, { url: presentation.author.url }, presentation.author.name) : presentation.author.name), /* @__PURE__ */ import_react5.default.createElement("p", null, presentation.author.jobTitle), /* @__PURE__ */ import_react5.default.createElement("p", null, presentation.author.bio))), (presentation.categories.length > 0 || presentation.tags.length > 0) && /* @__PURE__ */ import_react5.default.createElement("nav", { className: "cms-website-terms", "aria-label": "Categories and tags" }, [...presentation.categories, ...presentation.tags].map((term2, index) => /* @__PURE__ */ import_react5.default.createElement(SafeLink, { key: `${term2.id}:${index}`, url: term2.path }, term2.name))), presentation.showShare && /* @__PURE__ */ import_react5.default.createElement(SharePage, { title: entry2.document.title, path: entry2.path || pathname }), presentation.showRelated && Array.isArray(related) && related.length > 0 && /* @__PURE__ */ import_react5.default.createElement("aside", { className: "cms-website-related" }, /* @__PURE__ */ import_react5.default.createElement("h2", null, "Related content"), /* @__PURE__ */ import_react5.default.createElement("div", { className: "cms-website-cards" }, related.slice(0, 6).map((value, index) => {
12896
+ return /* @__PURE__ */ import_react5.default.createElement("main", { className: `cms-website-page cms-website-layout-${presentation.layout}` }, presentation.showBreadcrumbs && /* @__PURE__ */ import_react5.default.createElement("nav", { "aria-label": "Breadcrumbs" }, /* @__PURE__ */ import_react5.default.createElement(SafeLink, { url: "/" }, "Home"), /* @__PURE__ */ import_react5.default.createElement("span", { "aria-hidden": "true" }, " / "), /* @__PURE__ */ import_react5.default.createElement("span", { "aria-current": "page" }, entry2.document.title)), /* @__PURE__ */ import_react5.default.createElement("h1", null, entry2.document.title), entry2.document.excerpt && /* @__PURE__ */ import_react5.default.createElement("p", { className: "cms-website-excerpt" }, entry2.document.excerpt), presentation.showDate && publishedDate && /* @__PURE__ */ import_react5.default.createElement("time", { dateTime: entry2.publishedAt }, publishedDate), presentation.showFeaturedImage && presentation.featuredImage.url && /* @__PURE__ */ import_react5.default.createElement("img", { className: "cms-website-featured", src: presentation.featuredImage.url, alt: presentation.featuredImage.alt, referrerPolicy: "no-referrer" }), !!headings.length && /* @__PURE__ */ import_react5.default.createElement("nav", { className: "cms-website-toc", "aria-label": "Table of contents" }, /* @__PURE__ */ import_react5.default.createElement("h2", null, "On this page"), /* @__PURE__ */ import_react5.default.createElement("ul", null, headings.map((item) => /* @__PURE__ */ import_react5.default.createElement("li", { key: item.id }, /* @__PURE__ */ import_react5.default.createElement(SafeLink, { url: `${pathname}#${encodeURIComponent(item.id)}` }, item.title))))), /* @__PURE__ */ import_react5.default.createElement("div", { ref: content }, /* @__PURE__ */ import_react5.default.createElement(PublishedDocumentProvider, { initialEntry: entry2, pathname, route: route2, formEndpoint: (0, import_react5.useContext)(FormEndpoint) }, /* @__PURE__ */ import_react5.default.createElement(CmsPublishedDocument, null))), presentation.author && /* @__PURE__ */ import_react5.default.createElement("aside", { className: "cms-website-author", "aria-label": "About the author" }, presentation.author.avatarUrl && /* @__PURE__ */ import_react5.default.createElement("img", { src: presentation.author.avatarUrl, alt: "", loading: "lazy", referrerPolicy: "no-referrer" }), /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("h2", null, presentation.author.url ? /* @__PURE__ */ import_react5.default.createElement(SafeLink, { url: presentation.author.url }, presentation.author.name) : presentation.author.name), /* @__PURE__ */ import_react5.default.createElement("p", null, presentation.author.jobTitle), /* @__PURE__ */ import_react5.default.createElement("p", null, presentation.author.bio))), (presentation.categories.length > 0 || presentation.tags.length > 0) && /* @__PURE__ */ import_react5.default.createElement("nav", { className: "cms-website-terms", "aria-label": "Categories and tags" }, [...presentation.categories, ...presentation.tags].map((term2, index) => /* @__PURE__ */ import_react5.default.createElement(SafeLink, { key: `${term2.id}:${index}`, url: term2.path }, term2.name))), presentation.showShare && /* @__PURE__ */ import_react5.default.createElement(SharePage, { title: entry2.document.title, path: entry2.path || pathname }), presentation.showRelated && Array.isArray(related) && related.length > 0 && /* @__PURE__ */ import_react5.default.createElement("aside", { className: "cms-website-related" }, /* @__PURE__ */ import_react5.default.createElement("h2", null, "Related content"), /* @__PURE__ */ import_react5.default.createElement("div", { className: "cms-website-cards" }, related.slice(0, 6).map((value, index) => {
12889
12897
  const row = record2(value);
12890
12898
  return publicHref(row.path)?.startsWith("/") ? /* @__PURE__ */ import_react5.default.createElement("article", { key: index }, /* @__PURE__ */ import_react5.default.createElement("h3", null, /* @__PURE__ */ import_react5.default.createElement(SafeLink, { url: row.path }, text2(row.title, 200))), /* @__PURE__ */ import_react5.default.createElement("p", null, text2(row.excerpt, 500))) : null;
12891
12899
  }))));
@@ -12987,8 +12995,15 @@ var styleValues = {
12987
12995
  size: ["small", "normal", "large"],
12988
12996
  tone: ["default", "muted", "accent", "inverse"],
12989
12997
  spaceAbove: ["none", "small", "medium", "large"],
12990
- spaceBelow: ["none", "small", "medium", "large"]
12998
+ spaceBelow: ["none", "small", "medium", "large"],
12999
+ padding: ["none", "small", "medium", "large"],
13000
+ radius: ["none", "small", "medium", "large", "pill"],
13001
+ weight: ["normal", "medium", "bold"],
13002
+ maxWidth: ["narrow", "normal", "full"],
13003
+ animation: ["fade-up", "fade-in", "zoom-in", "slide-left"]
12991
13004
  };
13005
+ var mobileStyle = (style) => !own(style, "mobile") || object(style.mobile) && Object.keys(style.mobile).every((key) => ["align", "size", "padding", "spaceAbove", "spaceBelow"].includes(key)) && ["align", "size", "padding", "spaceAbove", "spaceBelow"].every((key) => optionalEnum(style.mobile, key, styleValues[key]));
13006
+ var styleHex = (style, key) => !own(style, key) || typeof style[key] === "string" && /^#[0-9a-fA-F]{6}$/.test(style[key]);
12992
13007
  var optionalEnum = (value, key, choices) => !own(value, key) || choices.includes(value[key]);
12993
13008
  var optionalString = (value, key, max) => !own(value, key) || boundedString(value[key], max);
12994
13009
  var optionalBoolean = (value, key) => !own(value, key) || typeof value[key] === "boolean";
@@ -13043,11 +13058,11 @@ function validDocument(document2) {
13043
13058
  return values.every((block2) => {
13044
13059
  if (!object(block2) || !uuid2.test(block2.id) || ids.has(block2.id) || ids.size >= 400 || block2.version !== 1 || !boundedString(block2.text, 3e4)) return false;
13045
13060
  ids.add(block2.id);
13046
- if (block2.style !== void 0 && (!object(block2.style) || Object.entries(styleValues).some(([key, values2]) => !optionalEnum(block2.style, key, values2)) || !optionalBoolean(block2.style, "hideOnMobile") || !optionalBoolean(block2.style, "hideOnDesktop") || own(block2.style, "anchor") && (!boundedString(block2.style.anchor, 80) || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(block2.style.anchor)))) return false;
13061
+ if (block2.style !== void 0 && (!object(block2.style) || Object.entries(styleValues).some(([key, values2]) => !optionalEnum(block2.style, key, values2)) || !optionalBoolean(block2.style, "hideOnMobile") || !optionalBoolean(block2.style, "hideOnDesktop") || !optionalBoolean(block2.style, "border") || !styleHex(block2.style, "textColor") || !styleHex(block2.style, "background") || !mobileStyle(block2.style) || own(block2.style, "anchor") && (!boundedString(block2.style.anchor, 80) || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(block2.style.anchor)))) return false;
13047
13062
  if (leafTypes.has(block2.type)) return leafData(block2);
13048
13063
  const d = block2.data;
13049
13064
  if (!object(d)) return false;
13050
- if (block2.type === "section" && level === 0) return optionalEnum(d, "background", ["none", "muted", "accent", "dark"]) && optionalEnum(d, "padding", ["none", "small", "medium", "large"]) && optionalEnum(d, "width", ["normal", "wide", "full"]) && blocks(d.blocks, 1, 100);
13065
+ if (block2.type === "section" && level === 0) return optionalEnum(d, "background", ["none", "muted", "accent", "dark"]) && optionalEnum(d, "padding", ["none", "small", "medium", "large"]) && optionalEnum(d, "width", ["normal", "wide", "full"]) && (!own(d, "globalId") || typeof d.globalId === "string" && uuid2.test(d.globalId)) && blocks(d.blocks, 1, 100);
13051
13066
  if (block2.type === "columns" && level < 2) return optionalEnum(d, "layout", ["1-1", "1-2", "2-1", "1-1-1", "1-1-1-1"]) && optionalEnum(d, "gap", ["small", "medium", "large"]) && optionalBoolean(d, "stackOnMobile") && Array.isArray(d.columns) && d.columns.length === (d.layout ?? "1-1").split("-").length && d.columns.every((column) => blocks(column, 2, 50));
13052
13067
  return false;
13053
13068
  });
@@ -13078,12 +13093,13 @@ var dataKeys = {
13078
13093
  table: ["header", "rows"],
13079
13094
  embed: ["url", "provider", "aspect"],
13080
13095
  form: ["formKey", "submitLabel", "successMessage", "consentText"],
13081
- section: ["background", "padding", "width"],
13096
+ section: ["background", "padding", "width", "globalId"],
13082
13097
  columns: ["layout", "gap", "stackOnMobile"]
13083
13098
  };
13084
13099
  function publicBlock(block2) {
13085
13100
  const result = pick(block2, ["id", "type", "version", "text"]);
13086
- if (object(block2.style)) result.style = pick(block2.style, [...Object.keys(styleValues), "hideOnMobile", "hideOnDesktop", "anchor"]);
13101
+ if (object(block2.style)) result.style = pick(block2.style, [...Object.keys(styleValues), "hideOnMobile", "hideOnDesktop", "border", "textColor", "background", "anchor"]);
13102
+ if (result.style && object(block2.style.mobile)) result.style.mobile = pick(block2.style.mobile, ["align", "size", "padding", "spaceAbove", "spaceBelow"]);
13087
13103
  if (object(block2.data)) {
13088
13104
  const data = pick(block2.data, dataKeys[block2.type] ?? []);
13089
13105
  if (block2.type === "gallery") data.images = block2.data.images.map((image2) => pick(image2, ["id", "url", "alt", "width", "height", "srcset"]));
@@ -13849,8 +13865,8 @@ var require_util = __commonJS2({
13849
13865
  function uint8ArrayToBase64url(bytes) {
13850
13866
  return uint8ArrayToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
13851
13867
  }
13852
- function hexToUint8Array(hex) {
13853
- const cleanHex = hex.replace(/^0x/, "");
13868
+ function hexToUint8Array(hex2) {
13869
+ const cleanHex = hex2.replace(/^0x/, "");
13854
13870
  if (cleanHex.length % 2 !== 0) {
13855
13871
  throw new Error("Invalid hex string length");
13856
13872
  }
@@ -32044,7 +32060,7 @@ var require_schemas2 = __commonJS2({
32044
32060
  exports.jwt = jwt;
32045
32061
  exports.stringFormat = stringFormat;
32046
32062
  exports.hostname = hostname;
32047
- exports.hex = hex;
32063
+ exports.hex = hex2;
32048
32064
  exports.hash = hash;
32049
32065
  exports.number = number;
32050
32066
  exports.int = int;
@@ -32630,7 +32646,7 @@ var require_schemas2 = __commonJS2({
32630
32646
  function hostname(_params) {
32631
32647
  return core._stringFormat(exports.ZodCustomStringFormat, "hostname", core.regexes.hostname, _params);
32632
32648
  }
32633
- function hex(_params) {
32649
+ function hex2(_params) {
32634
32650
  return core._stringFormat(exports.ZodCustomStringFormat, "hex", core.regexes.hex, _params);
32635
32651
  }
32636
32652
  function hash(alg, params) {
@@ -34843,7 +34859,11 @@ var link2 = import_zod.z.object({ label: import_zod.z.string().trim().min(1).max
34843
34859
  var links = import_zod.z.array(link2).max(40);
34844
34860
  var headerLink = link2.extend({ url: link2.shape.url.optional(), children: import_zod.z.array(link2).max(20).optional() }).refine((value) => value.url !== void 0 || (value.children?.length ?? 0) > 0, { message: "Header links without dropdown children need a URL", path: ["url"] });
34845
34861
  var footerGroup = import_zod.z.object({ title: import_zod.z.string().trim().min(1).max(80), links: import_zod.z.array(link2).max(20) });
34862
+ var designFonts = ["system", "Inter", "Roboto", "Open Sans", "Lato", "Poppins", "Montserrat", "Nunito", "Merriweather", "Playfair Display", "Lora", "Source Serif 4"];
34863
+ var hex = import_zod.z.string().regex(/^#[0-9a-fA-F]{6}$/);
34864
+ var designColors = import_zod.z.object({ primary: hex, primaryText: hex, background: hex, surface: hex, text: hex, mutedText: hex, border: hex });
34846
34865
  var configurationSchemas = {
34866
+ design: import_zod.z.object({ colors: designColors, typography: import_zod.z.object({ bodyFont: import_zod.z.enum(designFonts), headingFont: import_zod.z.enum(designFonts), baseSize: import_zod.z.number().int().min(14).max(20), headingWeight: import_zod.z.union([import_zod.z.literal(500), import_zod.z.literal(600), import_zod.z.literal(700), import_zod.z.literal(800)]) }), layout: import_zod.z.object({ containerWidth: import_zod.z.number().int().min(720).max(1440), radius: import_zod.z.number().int().min(0).max(24), spacing: import_zod.z.enum(["compact", "comfortable", "spacious"]) }), buttons: import_zod.z.object({ style: import_zod.z.enum(["solid", "outline", "soft"]), shape: import_zod.z.enum(["square", "rounded", "pill"]), uppercase: import_zod.z.boolean() }), header: import_zod.z.object({ layout: import_zod.z.enum(["left", "centered", "split"]), sticky: import_zod.z.boolean(), ctaLabel: import_zod.z.string().max(40), ctaUrl: import_zod.z.string().max(2048) }).optional(), footer: import_zod.z.object({ layout: import_zod.z.enum(["simple", "centered", "columns"]), showSocial: import_zod.z.boolean() }).optional() }),
34847
34867
  branding: import_zod.z.object({ description: import_zod.z.string().max(2e3), logoUrl: url, faviconUrl: url, logoAlt: text3, accentColor: import_zod.z.string().regex(/^#[0-9a-fA-F]{6}$/) }),
34848
34868
  social: import_zod.z.object({ profiles: import_zod.z.array(socialProfile).max(20), alignment: import_zod.z.enum(["left", "center", "right"]).optional() }),
34849
34869
  seo: import_zod.z.object({
@@ -34904,6 +34924,15 @@ var styleSchema = import_zod2.z.object({
34904
34924
  spaceBelow: import_zod2.z.enum(["none", "small", "medium", "large"]).optional(),
34905
34925
  hideOnMobile: import_zod2.z.boolean().optional(),
34906
34926
  hideOnDesktop: import_zod2.z.boolean().optional(),
34927
+ textColor: import_zod2.z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
34928
+ background: import_zod2.z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
34929
+ padding: import_zod2.z.enum(["none", "small", "medium", "large"]).optional(),
34930
+ radius: import_zod2.z.enum(["none", "small", "medium", "large", "pill"]).optional(),
34931
+ border: import_zod2.z.boolean().optional(),
34932
+ weight: import_zod2.z.enum(["normal", "medium", "bold"]).optional(),
34933
+ maxWidth: import_zod2.z.enum(["narrow", "normal", "full"]).optional(),
34934
+ mobile: import_zod2.z.object({ align: import_zod2.z.enum(["left", "center", "right"]).optional(), size: import_zod2.z.enum(["small", "normal", "large"]).optional(), padding: import_zod2.z.enum(["none", "small", "medium", "large"]).optional(), spaceAbove: import_zod2.z.enum(["none", "small", "medium", "large"]).optional(), spaceBelow: import_zod2.z.enum(["none", "small", "medium", "large"]).optional() }).strict().optional(),
34935
+ animation: import_zod2.z.enum(["fade-up", "fade-in", "zoom-in", "slide-left"]).optional(),
34907
34936
  anchor: import_zod2.z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(80).optional()
34908
34937
  });
34909
34938
  var id = import_zod2.z.string().uuid();
@@ -34968,6 +34997,8 @@ var sectionSchema = block("section", import_zod2.z.object({
34968
34997
  background: import_zod2.z.enum(["none", "muted", "accent", "dark"]).default("none"),
34969
34998
  padding: import_zod2.z.enum(["none", "small", "medium", "large"]).default("medium"),
34970
34999
  width: import_zod2.z.enum(["normal", "wide", "full"]).default("normal"),
35000
+ /** Linked to a saved site section: websites show that section's current content. */
35001
+ globalId: import_zod2.z.string().uuid().optional(),
34971
35002
  blocks: import_zod2.z.array(import_zod2.z.union([leafSchema, columnsSchema])).max(100)
34972
35003
  }));
34973
35004
  var blockSchema = import_zod2.z.union([leafSchema, columnsSchema, sectionSchema]);
@@ -35172,7 +35203,7 @@ function serializeNativeWebsite(value) {
35172
35203
  var website_default = ".cms-website { --cms-website-accent: #2563eb; }\n.cms-website-header:empty, .cms-website-footer:empty { display: none; }\n.cms-website-header, .cms-website-footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem; padding: 1rem clamp(1rem, 4vw, 3rem); color: inherit; background: inherit; }\n.cms-website-header:not(:has(> *)), .cms-website-footer:not(:has(> *)) { display: none; }\n.cms-website-header { border-bottom: 1px solid color-mix(in srgb, currentColor 15%, transparent); }\n.cms-website-footer { border-top: 1px solid color-mix(in srgb, currentColor 15%, transparent); }\n.cms-website a { overflow-wrap: anywhere; }\n.cms-website-header a, .cms-website-footer a, .cms-website-supplement a, .cms-website-page a { color: var(--cms-website-accent); }\n.cms-website-branding { max-width: 40rem; }\n.cms-website-branding img { display: block; max-width: min(15rem, 70vw); max-height: 4rem; width: auto; height: auto; object-fit: contain; }\n.cms-website-branding p { margin: .5rem 0 0; font-size: .9rem; }\n.cms-website-navigation { max-width: 100%; }\n.cms-website-navigation ul, .cms-website-social, .cms-website-languages, .cms-website-terms { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1.25rem; margin: 0; padding: 0; list-style: none; }\n.cms-website-navigation a, .cms-website-navigation summary, .cms-website-social a, .cms-website-languages a, .cms-website-terms a { display: inline-flex; gap: .5rem; align-items: center; min-height: 2.75rem; }\n.cms-website-navigation details { position: relative; }\n.cms-website-navigation summary { cursor: pointer; }\n.cms-website-navigation summary::after { content: '\u2304'; margin-inline-start: .3rem; }\n.cms-website-navigation details > ul { position: absolute; z-index: 40; top: 100%; min-width: 12rem; max-width: min(20rem, 85vw); flex-direction: column; align-items: stretch; gap: 0; padding: .5rem 1rem; border: 1px solid #64748b; border-radius: .5rem; color: #0f172a; background: #fff; box-shadow: 0 .5rem 1.5rem #0002; }\n.cms-website-footer-groups { display: flex; gap: 2rem; flex-wrap: wrap; }\n.cms-website-footer-groups h2 { font-size: 1rem; margin: .75rem 0; }\n.cms-website-footer-groups ul { flex-direction: column; align-items: start; gap: 0; }\n.cms-website-social img { width: 1.25rem; height: 1.25rem; object-fit: contain; }\n.cms-website-align-left { justify-content: flex-start; }.cms-website-align-center { justify-content: center; }.cms-website-align-right { justify-content: flex-end; }\n.cms-website-languages [aria-current=page] { font-weight: 700; text-decoration: underline; }\n.cms-website-page { width: min(100% - 2rem, 72rem); margin: 0 auto; padding: 3rem 0; overflow-wrap: anywhere; }\n.cms-website-page.cms-website-layout-narrow, .cms-website-native.cms-website-layout-narrow { max-width: 48rem; margin-inline: auto; }\n.cms-website-page.cms-website-layout-wide { width: min(100% - 2rem, 90rem); }\n.cms-website-native.cms-website-layout-standard { max-width: 80rem; margin-inline: auto; }\n.cms-website-native.cms-website-layout-wide { max-width: 100%; }\n.cms-website-page > h1 { font-size: clamp(2rem, 5vw, 3.5rem); line-height: 1.15; margin: 1rem 0; }\n.cms-website-excerpt { font-size: 1.15rem; line-height: 1.7; }\n.cms-website-featured { display: block; width: 100%; height: auto; max-height: 36rem; object-fit: cover; margin: 1.5rem 0; border-radius: .75rem; }\n.cms-website-toc, .cms-website-author, .cms-website-related { margin-block: 2rem; padding: 1.25rem; border: 1px solid color-mix(in srgb, currentColor 20%, transparent); border-radius: .75rem; }\n.cms-website-toc h2, .cms-website-author h2, .cms-website-related h2 { font-size: 1.25rem; }\n.cms-website-author { display: flex; align-items: flex-start; gap: 1rem; }\n.cms-website-author img { width: 4rem; height: 4rem; object-fit: cover; border-radius: 50%; }\n.cms-website-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); gap: 1.5rem; }\n.cms-website-cards article { padding: 1rem; border: 1px solid color-mix(in srgb, currentColor 20%, transparent); border-radius: .5rem; }\n.cms-website-share { display: flex; gap: .75rem; align-items: center; margin-block: 1rem; }\n.cms-website-share button, .cms-website-preferences, .cms-website-consent button { min-height: 2.75rem; padding: .5rem 1rem; font: inherit; border: 1px solid #64748b; border-radius: .4rem; cursor: pointer; background: transparent; color: inherit; }\n.cms-website-supplement { margin: 1rem clamp(1rem, 4vw, 3rem); }\n.cms-website-consent { position: fixed; z-index: 1000; bottom: 1rem; width: min(32rem, calc(100vw - 2rem)); max-height: calc(100dvh - 2rem); overflow-y: auto; box-sizing: border-box; padding: 1.25rem; border: 1px solid #cbd5e1; border-radius: .75rem; background: #fff; color: #0f172a; box-shadow: 0 .5rem 3rem #0003; font: 1rem/1.5 system-ui, sans-serif; }\n.cms-website-consent h2 { margin: 0 0 .5rem; font-size: 1.25rem; }.cms-website-consent p { margin: .5rem 0; }\n.cms-website-consent fieldset { margin: 1rem 0; padding: .75rem; }.cms-website-consent label { display: flex; align-items: center; gap: .5rem; min-height: 2.75rem; }.cms-website-consent input { width: 1.2rem; height: 1.2rem; }\n.cms-website-consent-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }\n.cms-website-bottom-left { left: 1rem; }.cms-website-bottom-right { right: 1rem; }.cms-website-bottom-center { left: 50%; transform: translateX(-50%); }\n.cms-website-error { margin: 4rem auto; padding: 1rem; max-width: 48rem; }\n.cms-website :focus-visible { outline: 3px solid var(--cms-website-accent); outline-offset: 3px; }\n@media(max-width: 640px) { .cms-website-header, .cms-website-footer { align-items: stretch; flex-direction: column; }.cms-website-navigation details > ul { position: static; max-width: 100%; }.cms-website-page { padding-block: 1.5rem; }.cms-website-author { flex-wrap: wrap; } }\n";
35173
35204
 
35174
35205
  // native-css:/Users/jasmeet/Codex/Universal-CMS/integrations/react/src/styles.css
35175
- var styles_default = ".cms-document{overflow-wrap:anywhere}.cms-document img{max-width:100%;height:auto}.cms-document pre,.cms-table-scroll{overflow-x:auto}.cms-document figure{margin:0}.cms-document blockquote{border-inline-start:3px solid currentColor;padding-inline-start:1rem}.cms-document table{border-collapse:collapse;width:100%}.cms-document th,.cms-document td{border:1px solid #8886;padding:.6rem;text-align:start}.cms-gallery,.cms-columns{display:grid;gap:1rem}.cms-grid-2,.cms-layout-1-1{grid-template-columns:repeat(2,minmax(0,1fr))}.cms-grid-3,.cms-layout-1-1-1{grid-template-columns:repeat(3,minmax(0,1fr))}.cms-grid-4,.cms-layout-1-1-1-1{grid-template-columns:repeat(4,minmax(0,1fr))}.cms-layout-1-2{grid-template-columns:minmax(0,1fr) minmax(0,2fr)}.cms-layout-2-1{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}.cms-gap-small{gap:.5rem}.cms-gap-large{gap:2rem}.cms-embed{border:0;width:100%;height:auto}.cms-button{display:inline-block;padding:.65rem 1rem;border:1px solid currentColor;border-radius:.4rem}.cms-button-link{border:0;padding:0}.cms-form fieldset{border:0;padding:0;display:grid;gap:1rem}.cms-form-field{display:grid;gap:.35rem}.cms-form input,.cms-form textarea,.cms-form select{font:inherit;max-width:100%;padding:.6rem}.cms-form input[type=checkbox]{margin-inline-end:.5rem}.cms-selected{outline:2px solid #2563eb;outline-offset:3px}.cms-align-left{text-align:left}.cms-align-center{text-align:center}.cms-align-right{text-align:right}.cms-size-small{font-size:.875em}.cms-size-large{font-size:1.25em}.cms-tone-muted{opacity:.75}.cms-tone-accent{color:var(--cms-accent,#2563eb)}.cms-tone-inverse,.cms-section-dark{background:#20242c;color:#fff}.cms-section-muted{background:#8881}.cms-section-accent{background:var(--cms-accent,#2563eb);color:#fff}.cms-padding-small{padding:.75rem}.cms-padding-medium{padding:1.5rem}.cms-padding-large{padding:3rem}.cms-width-normal{max-width:70rem;margin-inline:auto}.cms-width-wide{max-width:90rem;margin-inline:auto}.cms-spaceAbove-small{margin-top:.5rem}.cms-spaceAbove-medium{margin-top:1rem}.cms-spaceAbove-large{margin-top:2rem}.cms-spaceBelow-small{margin-bottom:.5rem}.cms-spaceBelow-medium{margin-bottom:1rem}.cms-spaceBelow-large{margin-bottom:2rem}.cms-spaceAbove-none{margin-top:0}.cms-spaceBelow-none{margin-bottom:0}@media(min-width:641px){.cms-hideOnDesktop-true{display:none}}@media(max-width:640px){.cms-hideOnMobile-true{display:none}.cms-stack,.cms-gallery{grid-template-columns:minmax(0,1fr)}.cms-padding-large{padding:1.5rem}}\n\n.cms-document .cms-block,.cms-document .cms-column{min-width:0}.cms-block-paragraph>p,.cms-block-heading>h2,.cms-block-heading>h3,.cms-block-heading>h4,.cms-block-quote>blockquote,.cms-document figcaption,.cms-block-faq p{white-space:pre-line}.cms-image-align-left{text-align:left}.cms-image-align-center{text-align:center}.cms-image-align-right{text-align:right}.cms-image-align-full img{width:100%}.cms-document .cms-image-align-left img,.cms-document .cms-image-align-center img,.cms-document .cms-image-align-right img{display:inline-block}.cms-gallery img{width:100%;height:auto}.cms-table-scroll:focus-visible{outline:2px solid var(--cms-accent,#2563eb);outline-offset:3px}\n";
35206
+ var styles_default = ".cms-document{overflow-wrap:anywhere}.cms-document img{max-width:100%;height:auto}.cms-document pre,.cms-table-scroll{overflow-x:auto}.cms-document figure{margin:0}.cms-document blockquote{border-inline-start:3px solid currentColor;padding-inline-start:1rem}.cms-document table{border-collapse:collapse;width:100%}.cms-document th,.cms-document td{border:1px solid #8886;padding:.6rem;text-align:start}.cms-gallery,.cms-columns{display:grid;gap:1rem}.cms-grid-2,.cms-layout-1-1{grid-template-columns:repeat(2,minmax(0,1fr))}.cms-grid-3,.cms-layout-1-1-1{grid-template-columns:repeat(3,minmax(0,1fr))}.cms-grid-4,.cms-layout-1-1-1-1{grid-template-columns:repeat(4,minmax(0,1fr))}.cms-layout-1-2{grid-template-columns:minmax(0,1fr) minmax(0,2fr)}.cms-layout-2-1{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}.cms-gap-small{gap:.5rem}.cms-gap-large{gap:2rem}.cms-embed{border:0;width:100%;height:auto}.cms-button{display:inline-block;padding:.65rem 1rem;border:1px solid currentColor;border-radius:.4rem}.cms-button-link{border:0;padding:0}.cms-form fieldset{border:0;padding:0;display:grid;gap:1rem}.cms-form-field{display:grid;gap:.35rem}.cms-form input,.cms-form textarea,.cms-form select{font:inherit;max-width:100%;padding:.6rem}.cms-form input[type=checkbox]{margin-inline-end:.5rem}.cms-selected{outline:2px solid #2563eb;outline-offset:3px}.cms-align-left{text-align:left}.cms-align-center{text-align:center}.cms-align-right{text-align:right}.cms-size-small{font-size:.875em}.cms-size-large{font-size:1.25em}.cms-tone-muted{opacity:.75}.cms-tone-accent{color:var(--cms-accent,#2563eb)}.cms-tone-inverse,.cms-section-dark{background:#20242c;color:#fff}.cms-section-muted{background:#8881}.cms-section-accent{background:var(--cms-accent,#2563eb);color:#fff}.cms-padding-small{padding:.75rem}.cms-padding-medium{padding:1.5rem}.cms-padding-large{padding:3rem}.cms-width-normal{max-width:70rem;margin-inline:auto}.cms-width-wide{max-width:90rem;margin-inline:auto}.cms-spaceAbove-small{margin-top:.5rem}.cms-spaceAbove-medium{margin-top:1rem}.cms-spaceAbove-large{margin-top:2rem}.cms-spaceBelow-small{margin-bottom:.5rem}.cms-spaceBelow-medium{margin-bottom:1rem}.cms-spaceBelow-large{margin-bottom:2rem}.cms-spaceAbove-none{margin-top:0}.cms-spaceBelow-none{margin-bottom:0}@media(min-width:641px){.cms-hideOnDesktop-true{display:none}}@media(max-width:640px){.cms-hideOnMobile-true{display:none}.cms-stack,.cms-gallery{grid-template-columns:minmax(0,1fr)}.cms-padding-large{padding:1.5rem}}\n\n.cms-document .cms-block,.cms-document .cms-column{min-width:0}.cms-block-paragraph>p,.cms-block-heading>h2,.cms-block-heading>h3,.cms-block-heading>h4,.cms-block-quote>blockquote,.cms-document figcaption,.cms-block-faq p{white-space:pre-line}.cms-image-align-left{text-align:left}.cms-image-align-center{text-align:center}.cms-image-align-right{text-align:right}.cms-image-align-full img{width:100%}.cms-document .cms-image-align-left img,.cms-document .cms-image-align-center img,.cms-document .cms-image-align-right img{display:inline-block}.cms-gallery img{width:100%;height:auto}.cms-table-scroll:focus-visible{outline:2px solid var(--cms-accent,#2563eb);outline-offset:3px}\n\n.cms-preview-toolbar{display:flex;gap:2px;padding:3px;border-radius:8px;background:#1c1940;box-shadow:0 6px 18px rgb(0 0 0/18%)}.cms-preview-toolbar button{width:28px;height:28px;border:0;border-radius:6px;background:transparent;color:#fff;font:600 13px/1 system-ui,sans-serif;cursor:pointer}.cms-preview-toolbar button:hover{background:rgb(255 255 255/14%)}.cms-inline-editing{outline:none;cursor:text;caret-color:currentColor}\n.cms-padding-none{padding:0}.cms-padding-small{padding:.75rem}.cms-padding-medium{padding:1.5rem}.cms-padding-large{padding:3rem}.cms-radius-none{border-radius:0}.cms-radius-small{border-radius:.375rem}.cms-radius-medium{border-radius:.75rem}.cms-radius-large{border-radius:1.25rem}.cms-radius-pill{border-radius:999px}.cms-border-true{border:1px solid color-mix(in srgb,currentColor 18%,transparent)}.cms-weight-normal{font-weight:400}.cms-weight-medium{font-weight:500}.cms-weight-bold{font-weight:700}.cms-maxWidth-narrow{max-width:42rem;margin-inline:auto}.cms-maxWidth-normal{max-width:64rem;margin-inline:auto}.cms-maxWidth-full{max-width:none}\n@media (max-width:760px){.cms-m-align-left{text-align:left!important}.cms-m-align-center{text-align:center!important}.cms-m-align-right{text-align:right!important}.cms-m-size-small{font-size:.875em!important}.cms-m-size-normal{font-size:1em!important}.cms-m-size-large{font-size:1.25em!important}.cms-m-padding-none{padding:0!important}.cms-m-padding-small{padding:.75rem!important}.cms-m-padding-medium{padding:1.25rem!important}.cms-m-padding-large{padding:2.5rem!important}.cms-m-spaceAbove-none{margin-top:0!important}.cms-m-spaceAbove-small{margin-top:.5rem!important}.cms-m-spaceAbove-medium{margin-top:1.25rem!important}.cms-m-spaceAbove-large{margin-top:2.5rem!important}.cms-m-spaceBelow-none{margin-bottom:0!important}.cms-m-spaceBelow-small{margin-bottom:.5rem!important}.cms-m-spaceBelow-medium{margin-bottom:1.25rem!important}.cms-m-spaceBelow-large{margin-bottom:2.5rem!important}}\n.cms-preview-drag{display:grid;place-items:center;width:28px;height:28px;border-radius:6px;color:#fff;cursor:grab;font:600 14px/1 system-ui,sans-serif}.cms-preview-drag:hover{background:rgb(255 255 255/14%)}\n";
35176
35207
 
35177
35208
  // src/native-website-server.tsx
35178
35209
  function renderNativeWebsite(value, childrenHtml = "") {