@aglyn/plugins-ai 1.0.0-beta.292 → 1.0.0-beta.293

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 (149) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-site-import-job.component.js +5 -4
  3. package/src/lib/components/ai-site-import-job.component.js.map +1 -1
  4. package/src/lib/components/ai-site-import-start.component.js +3 -3
  5. package/src/lib/components/ai-site-import-start.component.js.map +1 -1
  6. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  7. package/src/lib/components/ai-site-start-card.component.js +129 -29
  8. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  9. package/src/lib/components/ai-site-start-steps.component.d.ts +15 -3
  10. package/src/lib/components/ai-site-start-steps.component.js +42 -25
  11. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  12. package/src/lib/components/ai-site-style-previews.d.ts +17 -0
  13. package/src/lib/components/ai-site-style-previews.js +26 -95
  14. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  15. package/src/lib/declarations.server.js +22 -0
  16. package/src/lib/declarations.server.js.map +1 -1
  17. package/src/lib/jobs/ai-job-import-step.d.ts +21 -2
  18. package/src/lib/jobs/ai-job-import-step.js +179 -49
  19. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  20. package/src/lib/jobs/ai-job-layout-language.d.ts +27 -3
  21. package/src/lib/jobs/ai-job-layout-language.js +156 -113
  22. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  23. package/src/lib/jobs/ai-job-layout-step.js +56 -17
  24. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  25. package/src/lib/jobs/ai-job-page-language.d.ts +25 -15
  26. package/src/lib/jobs/ai-job-page-language.js +120 -66
  27. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  28. package/src/lib/jobs/ai-job-page-step.js +27 -7
  29. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  30. package/src/lib/jobs/ai-job-site-datasets.d.ts +12 -4
  31. package/src/lib/jobs/ai-job-site-datasets.js +36 -16
  32. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  33. package/src/lib/jobs/ai-jobs.js +6 -2
  34. package/src/lib/jobs/ai-jobs.js.map +1 -1
  35. package/src/lib/jobs/ai-layout-stock-photos.d.ts +50 -1
  36. package/src/lib/jobs/ai-layout-stock-photos.js +586 -35
  37. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  38. package/src/lib/jobs/ai-site-import-compile.d.ts +147 -0
  39. package/src/lib/jobs/ai-site-import-compile.js +1143 -0
  40. package/src/lib/jobs/ai-site-import-compile.js.map +1 -0
  41. package/src/lib/jobs/ai-site-import-document.d.ts +9 -36
  42. package/src/lib/jobs/ai-site-import-document.js +23 -489
  43. package/src/lib/jobs/ai-site-import-document.js.map +1 -1
  44. package/src/lib/jobs/ai-site-import-look.d.ts +54 -7
  45. package/src/lib/jobs/ai-site-import-look.js +163 -52
  46. package/src/lib/jobs/ai-site-import-look.js.map +1 -1
  47. package/src/lib/jobs/ai-site-import-publish.d.ts +7 -1
  48. package/src/lib/jobs/ai-site-import-publish.js +19 -5
  49. package/src/lib/jobs/ai-site-import-publish.js.map +1 -1
  50. package/src/lib/jobs/ai-site-late-photos.js +4 -2
  51. package/src/lib/jobs/ai-site-late-photos.js.map +1 -1
  52. package/src/lib/jobs/ai-site-publish.d.ts +10 -1
  53. package/src/lib/jobs/ai-site-publish.js +8 -3
  54. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  55. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.d.ts +23 -0
  56. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.js +677 -0
  57. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.js.map +1 -0
  58. package/src/lib/layout-language/ai-layout-compiler.d.ts +26 -0
  59. package/src/lib/layout-language/ai-layout-compiler.js +677 -33
  60. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  61. package/src/lib/layout-language/ai-layout-frame-hollows.js +5 -1
  62. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -1
  63. package/src/lib/layout-language/ai-layout-frame-source.d.ts +196 -0
  64. package/src/lib/layout-language/ai-layout-frame-source.js +726 -0
  65. package/src/lib/layout-language/ai-layout-frame-source.js.map +1 -0
  66. package/src/lib/layout-language/ai-layout-frame.d.ts +22 -0
  67. package/src/lib/layout-language/ai-layout-frame.js +121 -29
  68. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  69. package/src/lib/layout-language/ai-layout-language.d.ts +103 -37
  70. package/src/lib/layout-language/ai-layout-language.js +85 -120
  71. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  72. package/src/lib/model/ai-credit-estimate.d.ts +11 -7
  73. package/src/lib/model/ai-credit-estimate.js +11 -7
  74. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  75. package/src/lib/model/ai-site-import.d.ts +307 -13
  76. package/src/lib/model/ai-site-import.js +227 -19
  77. package/src/lib/model/ai-site-import.js.map +1 -1
  78. package/src/lib/model/ai-site-look.d.ts +65 -1
  79. package/src/lib/model/ai-site-look.js +141 -23
  80. package/src/lib/model/ai-site-look.js.map +1 -1
  81. package/src/lib/model/ai-site-photos.d.ts +2 -0
  82. package/src/lib/model/ai-site-photos.js.map +1 -1
  83. package/src/lib/model/ai-site-start-steps.d.ts +4 -3
  84. package/src/lib/model/ai-site-start-steps.js +11 -3
  85. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  86. package/src/lib/model/ai-site-start.d.ts +23 -2
  87. package/src/lib/model/ai-site-start.js +25 -3
  88. package/src/lib/model/ai-site-start.js.map +1 -1
  89. package/src/lib/providers/routing.d.ts +11 -0
  90. package/src/lib/providers/routing.js +23 -0
  91. package/src/lib/providers/routing.js.map +1 -1
  92. package/src/lib/runtime/ai-doctrine-validators.d.ts +4 -2
  93. package/src/lib/runtime/ai-doctrine-validators.js +7 -2
  94. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  95. package/src/lib/runtime/ai-icon-library.d.ts +4 -0
  96. package/src/lib/runtime/ai-icon-library.js +4 -0
  97. package/src/lib/runtime/ai-icon-library.js.map +1 -1
  98. package/src/lib/runtime/ai-node-tree.js +5 -1
  99. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  100. package/src/lib/runtime/ai-palette.d.ts +2 -0
  101. package/src/lib/runtime/ai-palette.generated.js +19 -3
  102. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  103. package/src/lib/runtime/ai-palette.js +2 -1
  104. package/src/lib/runtime/ai-palette.js.map +1 -1
  105. package/src/lib/site-import/fixtures/site-layout-recordings.d.ts +50 -0
  106. package/src/lib/site-import/fixtures/site-layout-recordings.js +86 -0
  107. package/src/lib/site-import/fixtures/site-layout-recordings.js.map +1 -0
  108. package/src/lib/site-import/site-import-crawl.d.ts +18 -1
  109. package/src/lib/site-import/site-import-crawl.js +70 -10
  110. package/src/lib/site-import/site-import-crawl.js.map +1 -1
  111. package/src/lib/site-import/site-import-css.d.ts +110 -0
  112. package/src/lib/site-import/site-import-css.js +826 -0
  113. package/src/lib/site-import/site-import-css.js.map +1 -0
  114. package/src/lib/site-import/site-import-html.d.ts +116 -1
  115. package/src/lib/site-import/site-import-html.js +538 -46
  116. package/src/lib/site-import/site-import-html.js.map +1 -1
  117. package/src/lib/site-import/site-import-layout.d.ts +79 -0
  118. package/src/lib/site-import/site-import-layout.js +1608 -0
  119. package/src/lib/site-import/site-import-layout.js.map +1 -0
  120. package/src/lib/site-import/site-import-nav.d.ts +40 -0
  121. package/src/lib/site-import/site-import-nav.js +437 -0
  122. package/src/lib/site-import/site-import-nav.js.map +1 -0
  123. package/src/lib/site-import/site-import-page.d.ts +8 -1
  124. package/src/lib/site-import/site-import-page.js +24 -2
  125. package/src/lib/site-import/site-import-page.js.map +1 -1
  126. package/src/lib/assets/site-styles/beauty.webp +0 -0
  127. package/src/lib/assets/site-styles/blog.webp +0 -0
  128. package/src/lib/assets/site-styles/business.webp +0 -0
  129. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  130. package/src/lib/assets/site-styles/education.webp +0 -0
  131. package/src/lib/assets/site-styles/events.webp +0 -0
  132. package/src/lib/assets/site-styles/fitness.webp +0 -0
  133. package/src/lib/assets/site-styles/landing.webp +0 -0
  134. package/src/lib/assets/site-styles/music.webp +0 -0
  135. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  136. package/src/lib/assets/site-styles/personal.webp +0 -0
  137. package/src/lib/assets/site-styles/photography.webp +0 -0
  138. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  139. package/src/lib/assets/site-styles/professional.webp +0 -0
  140. package/src/lib/assets/site-styles/realestate.webp +0 -0
  141. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  142. package/src/lib/assets/site-styles/store.webp +0 -0
  143. package/src/lib/assets/site-styles/studio.webp +0 -0
  144. package/src/lib/assets/site-styles/trades.webp +0 -0
  145. package/src/lib/assets/site-styles/wellness.webp +0 -0
  146. package/src/lib/assets/site-styles/yoga.webp +0 -0
  147. package/src/lib/site-import/site-import-review.d.ts +0 -40
  148. package/src/lib/site-import/site-import-review.js +0 -149
  149. package/src/lib/site-import/site-import-review.js.map +0 -1
@@ -19,6 +19,7 @@ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_wi
19
19
  import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
20
20
  import { AI_ICON_COMPONENT_ID, aiIconOfWord, AI_ICON_LIBRARY } from "../runtime/ai-icon-library.js";
21
21
  import { AI_INSTANCE_REF_PROP } from "../runtime/ai-node-tree.js";
22
+ import { AI_TEXT_LIMITS } from "../runtime/ai-palette.js";
22
23
  import { aiPageScrollInteraction } from "../runtime/ai-page-links.js";
23
24
  import { aiLayoutFitText, aiLayoutQuoteGap, aiLayoutWords } from "./ai-layout-copy.js";
24
25
  import { AI_LAYOUT_GROUP_KINDS, AI_LAYOUT_MAX_ITEMS } from "./ai-layout-language.js";
@@ -29,6 +30,9 @@ import { aiLayoutContactLabel, aiLayoutWithContactLines } from "./ai-layout-cont
29
30
  import { AI_LAYOUT_LABELS_EN, aiLayoutLabel } from "./ai-layout-labels.js";
30
31
  import { aiLayoutRenamedLabel, aiLayoutResolveLink } from "./ai-layout-links.js";
31
32
  import { aiLayoutSpans, AI_LAYOUT_FRAMES, AiLayoutTreeBuilder } from "./ai-layout-tree.js";
33
+ /** Words exactly as given, one space between them: how a page brought in shows them (AGL-4107). */ export function aiLayoutAsWritten(value) {
34
+ return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
35
+ }
32
36
  /**
33
37
  * What a section is called to a screen reader: its plan name, with any note
34
38
  * the plan left in brackets taken out. A live blog plan (2026-10-09) named a
@@ -41,6 +45,7 @@ import { aiLayoutSpans, AI_LAYOUT_FRAMES, AiLayoutTreeBuilder } from "./ai-layou
41
45
  return `section-${index + 1}`;
42
46
  }
43
47
  /** The most pictures a page carries; each beyond it is one more empty slot to fill. */ export const AI_LAYOUT_MAX_IMAGES = 3;
48
+ /** The most characters a heading style holds; a longer heading of a page brought in is set as a subtitle (AGL-4107). */ export const AI_LAYOUT_HEADLINE_MAX_CHARS = AI_TEXT_LIMITS.headline;
44
49
  /** The most `brand` or `dark` bands a page shows; more and none stands out. */ export const AI_LAYOUT_MAX_STRONG_BANDS = 2;
45
50
  const SECTION_PADDING = {
46
51
  xs: 8,
@@ -74,8 +79,9 @@ function formSectionOf(sections, plan, targets) {
74
79
  const tree = new AiLayoutTreeBuilder();
75
80
  const formSection = formSectionOf(designed, plan, targets);
76
81
  // The page a visitor opens to get in touch shows the phone and email the
77
- // business gave, as links, wherever the design did not (AGL-3979).
78
- const sections = aiLayoutWithContactLines(designed, formSection, targets.contact);
82
+ // business gave, as links, wherever the design did not (AGL-3979). A page
83
+ // brought in shows what its source shows, and nothing more (AGL-4107).
84
+ const sections = options.verbatim ? designed : aiLayoutWithContactLines(designed, formSection, targets.contact);
79
85
  const page = {
80
86
  tree,
81
87
  plan,
@@ -184,12 +190,14 @@ function sectionScope(page, raw, index) {
184
190
  var _page_design, _raw_blocks_find, _page_plan_sections_index;
185
191
  const at = `sections[${index}]`;
186
192
  const asked = (_raw_band = raw.band) != null ? _raw_band : 'plain';
187
- const ruled = rhythmBand(page, index, asked);
193
+ // A page brought in keeps the source's bands, every one (AGL-4107).
194
+ const verbatim = page.options.verbatim === true;
195
+ const ruled = verbatim ? asked : rhythmBand(page, index, asked);
188
196
  if (ruled !== asked) page.settled.push({
189
197
  at: `${at}.band`,
190
198
  what: `drawn ${ruled}: the page's ${(_page_design = page.design) == null ? void 0 : _page_design.choices.bandRhythm} band rhythm`
191
199
  });
192
- const band = settleBand(page, ruled, at);
200
+ const band = verbatim ? asked : settleBand(page, ruled, at);
193
201
  return {
194
202
  page,
195
203
  index,
@@ -198,7 +206,7 @@ function sectionScope(page, raw, index) {
198
206
  centered: raw.align === 'center',
199
207
  level: index === 0 ? 1 : 2,
200
208
  headed: false,
201
- itemsLeft: AI_LAYOUT_MAX_ITEMS,
209
+ itemsLeft: verbatim ? Number.POSITIVE_INFINITY : AI_LAYOUT_MAX_ITEMS,
202
210
  link: {
203
211
  sections: page.plan.sections.map((section)=>section.name),
204
212
  from: index,
@@ -218,9 +226,11 @@ function drawSection(page, raw, index, scope) {
218
226
  var _page_targets_listings, _ref;
219
227
  var _placedHere_, _page_plan_sections_index_name, _page_plan_sections_index, _page_options_sectionIds;
220
228
  const { at, band } = scope;
221
- // A quote is a gap the owner fills, and a published page shows no gap (AGL-3660).
229
+ const verbatim = page.options.verbatim === true;
230
+ // A quote is a gap the owner fills, and a published page shows no gap
231
+ // (AGL-3660); a page brought in quotes its own customers' words (AGL-4107).
222
232
  const blocks = placePlanned(scope, raw).filter((block)=>{
223
- if (block.kind !== 'quotes') return true;
233
+ if (block.kind !== 'quotes' || verbatim) return true;
224
234
  page.settled.push({
225
235
  at,
226
236
  what: 'a quotes group left out: a published page shows no customer words the brief did not give'
@@ -229,19 +239,23 @@ function drawSection(page, raw, index, scope) {
229
239
  });
230
240
  // A section whose only items were those quotes has none left to show, and is
231
241
  // not asked for them again: no answer can give words the brief did not (AGL-3676).
232
- if (raw.blocks.some((block)=>block.kind === 'quotes') && !blocks.some((block)=>AI_LAYOUT_GROUP_KINDS.has(block.kind) || block.kind === 'component')) {
242
+ if (!verbatim && raw.blocks.some((block)=>block.kind === 'quotes') && !blocks.some((block)=>AI_LAYOUT_GROUP_KINDS.has(block.kind) || block.kind === 'component')) {
233
243
  page.quotesOnly.push(index);
234
244
  }
235
245
  // A store's Shop page opens on its grid (AGL-3676): its first section is a
236
246
  // short title bar, never a hero, so the products start above the fold.
237
- if (index === 0 && shopGridAt(page) > 0) return shopTitle(scope, blocks);
247
+ if (!verbatim && index === 0 && shopGridAt(page) > 0) return shopTitle(scope, blocks);
238
248
  // A section the site's records fill — its catalog, its blog — shows them
239
249
  // through the element that keeps them, around the words the design gave it (AGL-3676).
240
- const listed = aiLayoutListingAt((_page_targets_listings = page.targets.listings) != null ? _page_targets_listings : [], page.targets.pageId, index);
250
+ const listed = verbatim ? null : aiLayoutListingAt((_page_targets_listings = page.targets.listings) != null ? _page_targets_listings : [], page.targets.pageId, index);
241
251
  if (listed) return listingSection(scope, blocks, listed);
242
252
  // The site's design draws the sections it has an arrangement for (AGL-3660),
243
- // the third band in a row with no picture beside one (AGL-4065).
244
- if (page.design) {
253
+ // the third band in a row with no picture beside one (AGL-4065). A page
254
+ // brought in takes only its hero, around the source's own picture (AGL-4107).
255
+ if (verbatim) {
256
+ const opening = index === 0 && raw.hero ? importedHero(scope, raw, blocks) : null;
257
+ if (opening) return opening;
258
+ } else if (page.design) {
245
259
  const beside = rhythmSection(scope, raw, blocks);
246
260
  if (beside) return beside;
247
261
  const designed = designSection(scope, raw, blocks);
@@ -281,7 +295,7 @@ function drawSection(page, raw, index, scope) {
281
295
  entry.column[row]
282
296
  ] : [])).flat();
283
297
  const repeated = ((_placedHere_ = placedHere[0]) == null ? void 0 : _placedHere_.kind) === 'component' ? componentOf(page.targets, placedHere[0].to) : null;
284
- if (filled.length >= 2 && repeated && placedHere.every((block)=>block.kind === 'component' && block.to === repeated.id)) {
298
+ if (!verbatim && filled.length >= 2 && repeated && placedHere.every((block)=>block.kind === 'component' && block.to === repeated.id)) {
285
299
  head.push({
286
300
  kind: 'cards',
287
301
  to: repeated.id,
@@ -296,7 +310,7 @@ function drawSection(page, raw, index, scope) {
296
310
  // A row whose columns each hold only one kind of group — a card a column —
297
311
  // is that group, laid out by the compiler rather than column by column.
298
312
  const groups = filled.flatMap((entry)=>entry.column);
299
- if (filled.length >= 2 && groups.every((block)=>isItemGroup(block) && block.kind === groups[0].kind && block.to === groups[0].to)) {
313
+ if (!verbatim && filled.length >= 2 && groups.every((block)=>isItemGroup(block) && block.kind === groups[0].kind && block.to === groups[0].to)) {
300
314
  head.push(_extends({}, groups[0], {
301
315
  col: undefined,
302
316
  items: groups.flatMap((block)=>{
@@ -312,7 +326,7 @@ function drawSection(page, raw, index, scope) {
312
326
  }
313
327
  // A row whose columns all say the same kind of thing, three or more across,
314
328
  // is a group of cards: one design, the words the columns gave.
315
- if (filled.length >= 3 && sameShape(filled.map((entry)=>entry.column))) {
329
+ if (!verbatim && filled.length >= 3 && sameShape(filled.map((entry)=>entry.column))) {
316
330
  head.push(...columnsAsCards(filled.map((entry)=>entry.column)));
317
331
  page.settled.push({
318
332
  at,
@@ -329,7 +343,7 @@ function drawSection(page, raw, index, scope) {
329
343
  // heading, the first section always carries the page's one h1, and a
330
344
  // heading written after a group whose titles sit under it moves above it.
331
345
  const inOrder = ()=>head.concat(filled.flatMap((entry)=>entry.column), tail);
332
- const speaks = (block)=>block.kind === 'heading' && !!aiLayoutWords(block.text, 'heading', page.targets.facts, page.options.language);
346
+ const speaks = (block)=>block.kind === 'heading' && !!(verbatim ? aiLayoutAsWritten(block.text) : aiLayoutWords(block.text, 'heading', page.targets.facts, page.options.language));
333
347
  for (const list of [
334
348
  head,
335
349
  tail,
@@ -349,7 +363,7 @@ function drawSection(page, raw, index, scope) {
349
363
  }
350
364
  const order = inOrder();
351
365
  const first = order.findIndex(speaks);
352
- if (first > 0 && order.slice(0, first).some((block)=>block.kind === 'cards' || block.kind === 'steps' || block.kind === 'component')) {
366
+ if (!verbatim && first > 0 && order.slice(0, first).some((block)=>block.kind === 'cards' || block.kind === 'steps' || block.kind === 'component')) {
353
367
  const lead = order[first];
354
368
  for (const list of [
355
369
  head,
@@ -484,10 +498,12 @@ function drawSection(page, raw, index, scope) {
484
498
  /** A group whose items sit side by side. */ function isItemGroup(block) {
485
499
  return block.kind === 'cards' || block.kind === 'steps' || block.kind === 'stats' || block.kind === 'quotes';
486
500
  }
487
- /** Whether a block spans more than a reading column: a row of several items. */ function isWideGroup(block) {
501
+ /** Whether a block spans more than a reading column: a row of several items, or a table, tabs or logos of a page brought in. */ function isWideGroup(block) {
488
502
  var _ref;
489
503
  var _block_items;
490
- return (block.kind === 'cards' || block.kind === 'steps' || block.kind === 'stats' || block.kind === 'quotes') && ((_ref = (_block_items = block.items) == null ? void 0 : _block_items.length) != null ? _ref : 0) >= 2;
504
+ if (block.kind === 'table' || block.kind === 'tabs' || block.kind === 'logos') return true;
505
+ return (block.kind === 'cards' || block.kind === 'steps' || block.kind === 'stats' || block.kind === 'quotes') && ((_ref = (_block_items = block.items) == null ? void 0 : _block_items.length) != null ? _ref : 0) >= 2 && // A page brought in lays a group out as its source does: one to a row reads at a reading width.
506
+ block.columns !== 1;
491
507
  }
492
508
  /** A full-width flow centered on a wide section is held to a reading width. */ function centered(scope, flowId, wide) {
493
509
  var _flow_nodes;
@@ -828,7 +844,7 @@ function compileBlock(scope, block, room) {
828
844
  case 'component':
829
845
  return component(scope, block);
830
846
  case 'list':
831
- return list(scope, block);
847
+ return list(scope, block, room);
832
848
  case 'faq':
833
849
  return faq(scope, block);
834
850
  case 'cards':
@@ -836,6 +852,12 @@ function compileBlock(scope, block, room) {
836
852
  case 'stats':
837
853
  case 'quotes':
838
854
  return group(scope, block, room);
855
+ case 'table':
856
+ return table(scope, block);
857
+ case 'tabs':
858
+ return tabs(scope, block, room);
859
+ case 'logos':
860
+ return logos(scope, block);
839
861
  default:
840
862
  return null;
841
863
  }
@@ -864,8 +886,15 @@ function align(scope) {
864
886
  align: 'center'
865
887
  } : {};
866
888
  }
889
+ /**
890
+ * Words as a page shows them: fitted to their place at a clean boundary, or,
891
+ * on a page brought in (AGL-4107), exactly as the source wrote them.
892
+ */ function say(scope, value, place) {
893
+ if (scope.page.options.verbatim) return aiLayoutAsWritten(value);
894
+ return aiLayoutWords(value, place, scope.page.targets.facts, scope.page.options.language);
895
+ }
867
896
  function words(scope, block, place, variant, props, sx) {
868
- const text = aiLayoutWords(block.text, place, scope.page.targets.facts, scope.page.options.language);
897
+ const text = say(scope, block.text, place);
869
898
  if (!text) return null;
870
899
  return scope.page.tree.add('muiTypography', _extends({
871
900
  children: text,
@@ -884,10 +913,13 @@ function words(scope, block, place, variant, props, sx) {
884
913
  const own = !scope.headed;
885
914
  const level = own ? scope.level : scope.level + 1;
886
915
  const place = level === 1 ? 'title' : own ? 'heading' : 'itemTitle';
887
- const text = aiLayoutWords(block.text, place, scope.page.targets.facts, scope.page.options.language);
916
+ const text = say(scope, block.text, place);
888
917
  if (!text) return null;
889
918
  scope.headed = scope.headed || own;
890
- const variant = level === 1 ? block.style === 'large' ? 'displayXl' : 'h1' : own ? 'h2' : 'h4';
919
+ // A source's heading longer than a heading style holds is set in a subtitle
920
+ // style, still a heading at its level (AGL-4107): every word of it is kept.
921
+ const long = scope.page.options.verbatim === true && text.length > AI_LAYOUT_HEADLINE_MAX_CHARS;
922
+ const variant = long ? 'subtitle1' : level === 1 ? block.style === 'large' ? 'displayXl' : 'h1' : own ? 'h2' : 'h4';
891
923
  return scope.page.tree.add('muiTypography', _extends({
892
924
  children: text,
893
925
  variant,
@@ -902,19 +934,25 @@ function words(scope, block, place, variant, props, sx) {
902
934
  }
903
935
  function compileButton(scope, block) {
904
936
  var _block_style;
937
+ const verbatim = scope.page.options.verbatim === true;
905
938
  // Words that carry the phone or the email are kept whole (AGL-3979).
906
- const asked = aiLayoutContactLabel(block.text, scope.page.targets.contact) || aiLayoutFitText(block.text, 'label', scope.page.options.language);
939
+ const asked = verbatim ? aiLayoutAsWritten(block.text) : aiLayoutContactLabel(block.text, scope.page.targets.contact) || aiLayoutFitText(block.text, 'label', scope.page.options.language);
907
940
  if (!asked) return null;
908
- const destination = aiLayoutResolveLink(block.to, asked, scope.link, scope.page.targets);
941
+ const destination = verbatim ? aiLayoutImportedDestination(block.to, scope.page.plan.sections.length) : aiLayoutResolveLink(block.to, asked, scope.link, scope.page.targets);
909
942
  if (!destination) {
943
+ // A page brought in keeps the words of a link that goes nowhere here (AGL-4107).
944
+ if (verbatim) return words(scope, block, 'text', 'body1', {}, muted(scope));
910
945
  scope.page.settled.push({
911
946
  at: scope.at,
912
947
  what: `the button "${asked}" has nowhere to go; left out`
913
948
  });
914
949
  return null;
915
950
  }
951
+ // A link of a page brought in longer than a button's label holds is its
952
+ // words, as a link (AGL-4107): every word kept, and it still goes there.
953
+ if (verbatim && asked.length > AI_TEXT_LIMITS.button) return linkedWords(scope, asked, destination);
916
954
  // A button to a page merged into the blog names the blog (AGL-3676).
917
- const label = aiLayoutRenamedLabel(asked, destination);
955
+ const label = verbatim ? asked : aiLayoutRenamedLabel(asked, destination);
918
956
  if (label !== asked) scope.page.settled.push({
919
957
  at: scope.at,
920
958
  what: `the button "${asked}" named for the blog: "${label}"`
@@ -944,6 +982,42 @@ function compileButton(scope, block) {
944
982
  linkTo(scope, id, destination);
945
983
  return id;
946
984
  }
985
+ /**
986
+ * Where a link of a page brought in goes (AGL-4107), as its compile wrote it:
987
+ * `page:<id>` a page of the site by its id — any page the import plans, which
988
+ * a visitor reaches once that page is live and sees as words until then —
989
+ * `#<n>` section n of this page, a `mailto:` or `tel:` link, or the
990
+ * `https:` address the source linked. Anything else goes nowhere.
991
+ */ export function aiLayoutImportedDestination(to, sections) {
992
+ const value = (to != null ? to : '').trim();
993
+ if (!value) return null;
994
+ const page = /^page:([A-Za-z0-9_-]{1,128})$/.exec(value);
995
+ if (page) return {
996
+ kind: 'page',
997
+ screenId: page[1]
998
+ };
999
+ const section = /^#(\d{1,3})$/.exec(value);
1000
+ if (section) {
1001
+ const index = Number(section[1]) - 1;
1002
+ return index >= 0 && index < sections ? {
1003
+ kind: 'section',
1004
+ index
1005
+ } : null;
1006
+ }
1007
+ if (/^mailto:[^\s@]+@[^\s@]+$/i.test(value) || /^tel:\+?[\d\s().-]{3,}$/i.test(value)) return {
1008
+ kind: 'contact',
1009
+ href: value
1010
+ };
1011
+ try {
1012
+ const url = new URL(value);
1013
+ return url.protocol === 'https:' ? {
1014
+ kind: 'href',
1015
+ href: url.toString()
1016
+ } : null;
1017
+ } catch (unused) {
1018
+ return null;
1019
+ }
1020
+ }
947
1021
  /**
948
1022
  * A link to a section of this page carries the platform's Scroll to element
949
1023
  * interaction to that section's root (AGL-3097), by the id the page stores it
@@ -1017,6 +1091,8 @@ function image(scope, block, room) {
1017
1091
  var _aiIconOfWord;
1018
1092
  var _block_to;
1019
1093
  const { page } = scope;
1094
+ // A page brought in places the source's own picture, as the library holds it (AGL-4107).
1095
+ if (page.options.verbatim) return block.picture ? sourcePicture(scope, block.picture, room) : null;
1020
1096
  const alt = aiLayoutFitText(block.text, 'alt', scope.page.options.language);
1021
1097
  if (!alt) return null;
1022
1098
  if (page.images >= maxImages(page)) {
@@ -1212,6 +1288,7 @@ function takeItems(scope, items) {
1212
1288
  }
1213
1289
  function group(scope, block, room) {
1214
1290
  var _block_items;
1291
+ if (scope.page.options.verbatim) return importedGroup(scope, block, room);
1215
1292
  const items = takeItems(scope, cleanItems(scope, (_block_items = block.items) != null ? _block_items : []));
1216
1293
  if (!items.length) return null;
1217
1294
  const perRow = across(items.length, room, block.kind);
@@ -1406,8 +1483,9 @@ function instances(scope, placed, items, perRow) {
1406
1483
  content
1407
1484
  ], 'quoteCard');
1408
1485
  }
1409
- /** A list: check-marked lines, or plain lines where rule 1 holds and the list is long. */ function list(scope, block) {
1486
+ /** A list: check-marked lines, or plain lines where rule 1 holds and the list is long. */ function list(scope, block, room = 'full') {
1410
1487
  var _block_items, _block_items1, _aiIconOfWord;
1488
+ if (scope.page.options.verbatim) return importedList(scope, block, room);
1411
1489
  const tree = scope.page.tree;
1412
1490
  const facts = scope.page.targets.facts;
1413
1491
  // A list whose lines go somewhere is a list of links: each line that
@@ -1471,6 +1549,7 @@ function instances(scope, placed, items, perRow) {
1471
1549
  }
1472
1550
  /** Questions and answers: an accordion each, or question-over-answer pairs where rule 1 holds. */ function faq(scope, block) {
1473
1551
  var _block_items;
1552
+ if (scope.page.options.verbatim) return importedFaq(scope, block);
1474
1553
  const tree = scope.page.tree;
1475
1554
  const facts = scope.page.targets.facts;
1476
1555
  const items = takeItems(scope, cleanItems(scope, (_block_items = block.items) != null ? _block_items : []).filter((item)=>!!aiLayoutWords(item.title, 'question', facts, scope.page.options.language)));
@@ -1531,7 +1610,7 @@ function instances(scope, placed, items, perRow) {
1531
1610
  return AI_LAYOUT_MAX_DESIGN_PICTURES - page.images - page.pictures;
1532
1611
  }
1533
1612
  /** Whether a heading block has words left once fitted: one with none is no heading. */ function speaksHeading(scope, block) {
1534
- return block.kind === 'heading' && !!aiLayoutWords(block.text, 'heading', scope.page.targets.facts, scope.page.options.language);
1613
+ return block.kind === 'heading' && !!say(scope, block.text, 'heading');
1535
1614
  }
1536
1615
  /** The section root every designed section is written under, as `compileSection` writes its own. */ function designedRoot(scope, children, sx, dark) {
1537
1616
  var _ref;
@@ -1575,7 +1654,20 @@ function instances(scope, placed, items, perRow) {
1575
1654
  const { page } = scope;
1576
1655
  if (picture.given) page.images += 1;
1577
1656
  else page.pictures += 1;
1578
- return page.tree.add('image', _extends({
1657
+ // A page brought in places the source's own picture (AGL-4107), described as
1658
+ // the source described it, and decorative where it was not described.
1659
+ const described = picture.alt ? {
1660
+ alt: picture.alt
1661
+ } : {
1662
+ decorative: true
1663
+ };
1664
+ return page.tree.add('image', picture.src ? _extends({
1665
+ src: picture.src
1666
+ }, described, {
1667
+ objectFit: 'cover'
1668
+ }, page.images > 1 ? {
1669
+ loading: 'lazy'
1670
+ } : {}, props) : _extends({
1579
1671
  alt: picture.alt,
1580
1672
  objectFit: 'cover'
1581
1673
  }, picture.given ? {} : {
@@ -1675,14 +1767,15 @@ function instances(scope, placed, items, perRow) {
1675
1767
  }
1676
1768
  return splitSection(scope, head, group, tail);
1677
1769
  }
1678
- /** The first section, in the site's hero arrangement. */ function hero(scope, flow, picture) {
1770
+ /** The first section, in the site's hero arrangement. */ function hero(scope, flow, picture, /** The hero a page brought in opens as (AGL-4107), over the site's own. */ asked) {
1679
1771
  const { page } = scope;
1680
1772
  const tree = page.tree;
1681
1773
  const design = page.design;
1682
1774
  const home = design.input.home;
1683
- // A collage holds two pictures: on a page with room for one, it is drawn split.
1684
- const chosen = design.choices.hero;
1685
- const variant = chosen === 'collage' && picturesLeft(page) < 2 ? 'split' : chosen;
1775
+ // A collage holds two pictures: on a page with room for one, it is drawn
1776
+ // split, and so it is on a page brought in, which shows only its own.
1777
+ const chosen = asked != null ? asked : design.choices.hero;
1778
+ const variant = chosen === 'collage' && (picturesLeft(page) < 2 || page.options.verbatim) ? 'split' : chosen;
1686
1779
  page.settled.push({
1687
1780
  at: scope.at,
1688
1781
  what: `drawn as a ${variant} hero`
@@ -3351,6 +3444,557 @@ function listingSection(scope, blocks, placed) {
3351
3444
  card
3352
3445
  ], 'records');
3353
3446
  }
3447
+ // ── A page brought in (AGL-4107) ──────────────────────────────────────────
3448
+ //
3449
+ // What a page brought in from its owner's own website draws that a designed
3450
+ // page never does: the source's own pictures and icons, every item of every
3451
+ // group with all its parts (its figure, its paragraphs, its bullet points,
3452
+ // its own links), its tables, its tabs and its strips of logos. Every word is
3453
+ // the source's, as written, and every element is the palette's with the
3454
+ // theme's tokens, as the rest of the compiler draws: a card is the theme's
3455
+ // Card, a question the Accordion, a link the Button. One style key on each
3456
+ // repeated element, so a page of many items repeats no inline style (rule 16).
3457
+ /** The source's picture, at its own shape, rounded as the theme rounds a frame. */ function sourcePicture(scope, picture, role = 'image') {
3458
+ const { page } = scope;
3459
+ page.images += 1;
3460
+ const destination = picture.to ? aiLayoutImportedDestination(picture.to, page.plan.sections.length) : null;
3461
+ return page.tree.add('image', _extends({
3462
+ src: picture.src
3463
+ }, picture.alt ? {
3464
+ alt: picture.alt
3465
+ } : {
3466
+ decorative: true
3467
+ }, {
3468
+ objectFit: 'cover'
3469
+ }, picture.small ? {
3470
+ width: AI_LAYOUT_PORTRAIT_SIZE,
3471
+ height: AI_LAYOUT_PORTRAIT_SIZE
3472
+ } : {}, page.images > 1 ? {
3473
+ loading: 'lazy'
3474
+ } : {}, destination ? destinationProps(destination) : {}), {
3475
+ borderRadius: picture.small ? '50%' : 2
3476
+ }, null, role);
3477
+ }
3478
+ /** The size a byline's portrait is drawn at on a page brought in (AGL-4107): the Image element's own size field, as an author sets it. */ export const AI_LAYOUT_PORTRAIT_SIZE = '4rem';
3479
+ /** An item's picture, over its words: the card's own top, at one shape across its group. */ function itemPicture(scope, picture) {
3480
+ const { page } = scope;
3481
+ page.images += 1;
3482
+ return page.tree.add('image', _extends({
3483
+ src: picture.src
3484
+ }, picture.alt ? {
3485
+ alt: picture.alt
3486
+ } : {
3487
+ decorative: true
3488
+ }, {
3489
+ objectFit: 'cover'
3490
+ }, page.images > 1 ? {
3491
+ loading: 'lazy'
3492
+ } : {}), {
3493
+ aspectRatio: '16 / 9'
3494
+ }, null, 'itemPicture');
3495
+ }
3496
+ /** An icon of a page brought in: the source's own drawing, else the library icon its word names. */ function sourceIcon(scope, own, size, sx, role = 'icon') {
3497
+ const tree = scope.page.tree;
3498
+ if (own.iconPath) return tree.add(AI_ICON_COMPONENT_ID, {
3499
+ iconPath: own.iconPath,
3500
+ size
3501
+ }, sx, null, role);
3502
+ const icon = aiIconOfWord(own.icon);
3503
+ return icon ? tree.add(AI_ICON_COMPONENT_ID, {
3504
+ iconId: icon.id,
3505
+ size
3506
+ }, sx, null, role) : null;
3507
+ }
3508
+ /** Lines of words as paragraphs of one style, each its own element. */ function paragraphs(scope, lines, variant, sx, role = 'text') {
3509
+ const tree = scope.page.tree;
3510
+ return lines.map((line)=>aiLayoutAsWritten(line)).filter(Boolean).map((line)=>tree.add('muiTypography', _extends({
3511
+ children: line,
3512
+ variant
3513
+ }, align(scope)), sx, null, role));
3514
+ }
3515
+ /**
3516
+ * Words of a page brought in that are a link longer than a button's label
3517
+ * holds (AGL-4107): the words in the brand color, inside a Link Container
3518
+ * that goes where the source's link went.
3519
+ */ function linkedWords(scope, text, destination) {
3520
+ const tree = scope.page.tree;
3521
+ const words = tree.add('muiTypography', _extends({
3522
+ children: text,
3523
+ variant: 'body1'
3524
+ }, align(scope)), accent(scope), null, 'linkWords');
3525
+ if (destination.kind === 'section') {
3526
+ linkTo(scope, words, destination);
3527
+ return words;
3528
+ }
3529
+ return tree.add('muiLinkBox', _extends({}, destinationProps(destination), {
3530
+ ariaLabel: text.slice(0, AI_TEXT_LIMITS.label)
3531
+ }, destination.kind === 'href' ? {
3532
+ newTab: true
3533
+ } : {}), null, [
3534
+ words
3535
+ ], 'longLink');
3536
+ }
3537
+ /** An item's bullet points: each led by the source's mark where it drew one, else one under another. */ function bullets(scope, lines, iconPath) {
3538
+ const tree = scope.page.tree;
3539
+ const said = lines.map((line)=>aiLayoutAsWritten(line)).filter(Boolean);
3540
+ if (!said.length) return null;
3541
+ if (!iconPath) {
3542
+ const rows = said.map((line)=>tree.add('muiTypography', {
3543
+ children: line,
3544
+ variant: 'body2'
3545
+ }, null, null, 'line'));
3546
+ return tree.add('muiStack', {
3547
+ spacing: '1'
3548
+ }, null, rows, 'bullets');
3549
+ }
3550
+ const rows = said.map((line)=>tree.add('muiStack', {
3551
+ direction: 'row',
3552
+ spacing: '1',
3553
+ alignItems: 'flex-start'
3554
+ }, null, [
3555
+ sourceIcon(scope, {
3556
+ iconPath
3557
+ }, '20', accent(scope), 'check'),
3558
+ tree.add('muiTypography', {
3559
+ children: line,
3560
+ variant: 'body2'
3561
+ }, null, null, 'line')
3562
+ ], 'row'));
3563
+ return tree.add('muiStack', {
3564
+ spacing: '1'
3565
+ }, null, rows, 'bullets');
3566
+ }
3567
+ /** An item's own calls to action, side by side, each as the source drew it. */ function itemLinks(scope, links) {
3568
+ const tree = scope.page.tree;
3569
+ const ids = links.map((link)=>{
3570
+ var _link_style;
3571
+ return compileButton(scope, _extends({
3572
+ kind: 'button',
3573
+ text: link.text
3574
+ }, link.to ? {
3575
+ to: link.to
3576
+ } : {}, {
3577
+ style: (_link_style = link.style) != null ? _link_style : 'quiet'
3578
+ }));
3579
+ }).filter((id)=>!!id);
3580
+ if (!ids.length) return null;
3581
+ if (ids.length === 1) return ids[0];
3582
+ return tree.add('muiStack', {
3583
+ direction: 'row',
3584
+ spacing: '2',
3585
+ useFlexGap: true,
3586
+ flexWrap: 'wrap'
3587
+ }, null, ids, 'actions');
3588
+ }
3589
+ /** How many items of a page brought in sit side by side: as the source lays them out, within the room. */ function importedAcross(block, count, room) {
3590
+ const source = block.columns && block.columns > 0 ? block.columns : count <= 3 ? count : 3;
3591
+ const wide = Math.max(1, Math.min(4, source, count));
3592
+ if (room === 'narrow') return 1;
3593
+ if (room === 'half') return Math.min(2, wide);
3594
+ return wide;
3595
+ }
3596
+ /** Whether an item has anything to show. */ function itemShows(item) {
3597
+ var _item_figure, _item_more, _item_list, _item_links;
3598
+ return Boolean(item.title.trim() || item.text.trim() || ((_item_figure = item.figure) == null ? void 0 : _item_figure.trim()) || ((_item_more = item.more) == null ? void 0 : _item_more.length) || ((_item_list = item.list) == null ? void 0 : _item_list.length) || ((_item_links = item.links) == null ? void 0 : _item_links.length) || item.picture || item.iconPath);
3599
+ }
3600
+ /**
3601
+ * A group of a page brought in: every item of it, each with all its parts,
3602
+ * laid out in the source's columns and drawn the way its design answer chose
3603
+ * (`variant`): the theme's cards, open columns under a rule, open columns led
3604
+ * by their icons, or pictures over their words.
3605
+ */ function importedGroup(scope, block, room) {
3606
+ var _block_items;
3607
+ const items = ((_block_items = block.items) != null ? _block_items : []).filter(itemShows);
3608
+ if (!items.length) return null;
3609
+ const perRow = importedAcross(block, items.length, room);
3610
+ const ids = items.map((item)=>importedItem(scope, block, item));
3611
+ return layOut(scope, noted(scope, ids), perRow, block.kind === 'stats' ? '4' : '3', block.kind === 'cards' ? 'cards' : block.kind);
3612
+ }
3613
+ /** The style an item of a group is drawn in: the design answer's, else the source's own box or none. */ function itemStyle(block) {
3614
+ if (block.variant) return block.variant;
3615
+ if (block.kind === 'stats') return 'open';
3616
+ return block.style === 'quiet' ? 'open' : 'cards';
3617
+ }
3618
+ /** One item of a group of a page brought in, every part of it in the source's order. */ function importedItem(scope, block, item) {
3619
+ var _tree_nodes_shown_, _item_links;
3620
+ const tree = scope.page.tree;
3621
+ const style = itemStyle(block);
3622
+ const kind = block.kind;
3623
+ const parts = [];
3624
+ const title = aiLayoutAsWritten(item.title);
3625
+ const figure = aiLayoutAsWritten(item.figure);
3626
+ const titleNode = ()=>title ? tree.add('muiTypography', _extends({
3627
+ children: title,
3628
+ variant: itemTitleVariant(scope, 'h5'),
3629
+ component: itemElement(scope)
3630
+ }, align(scope)), null, null, 'title') : null;
3631
+ if (kind === 'quotes') {
3632
+ var _item_more;
3633
+ parts.push(tree.add(AI_ICON_COMPONENT_ID, {
3634
+ iconId: AI_ICON_LIBRARY.quote.id,
3635
+ size: '32'
3636
+ }, {
3637
+ color: 'primary.main'
3638
+ }, null, 'quoteMark'));
3639
+ parts.push(...paragraphs(scope, [
3640
+ item.text,
3641
+ ...(_item_more = item.more) != null ? _item_more : []
3642
+ ], 'body1', null, 'quote'));
3643
+ if (title) parts.push(tree.add('muiTypography', _extends({
3644
+ children: title,
3645
+ variant: 'subtitle2',
3646
+ component: 'p'
3647
+ }, align(scope)), null, null, 'attribution'));
3648
+ } else if (kind === 'stats') {
3649
+ var _item_more1;
3650
+ const lead = figure || title;
3651
+ if (lead) parts.push(tree.add('muiTypography', _extends({
3652
+ children: lead,
3653
+ variant: 'h2',
3654
+ component: 'p'
3655
+ }, align(scope)), accent(scope), null, 'figure'));
3656
+ if (figure && title) parts.push(titleNode());
3657
+ parts.push(...paragraphs(scope, [
3658
+ item.text,
3659
+ ...(_item_more1 = item.more) != null ? _item_more1 : []
3660
+ ], 'body2', muted(scope), 'label'));
3661
+ } else {
3662
+ var _item_more2;
3663
+ var _item_list, _item_links1;
3664
+ if (item.picture) parts.push(itemPicture(scope, item.picture));
3665
+ parts.push(sourceIcon(scope, item, '32', style === 'cards' ? {
3666
+ color: 'primary.main'
3667
+ } : accent(scope)));
3668
+ const number = figure ? tree.add('muiTypography', _extends({
3669
+ children: figure,
3670
+ variant: kind === 'steps' ? 'h3' : 'h4',
3671
+ component: 'p'
3672
+ }, align(scope)), accent(scope), null, kind === 'steps' ? 'number' : 'figure') : null;
3673
+ // A step's number leads it; a plan's price follows its name.
3674
+ if (kind === 'steps') parts.push(number, titleNode());
3675
+ else parts.push(titleNode(), number);
3676
+ parts.push(...paragraphs(scope, [
3677
+ item.text,
3678
+ ...(_item_more2 = item.more) != null ? _item_more2 : []
3679
+ ], 'body1', muted(scope)));
3680
+ if ((_item_list = item.list) == null ? void 0 : _item_list.length) parts.push(bullets(scope, item.list, item.listIconPath));
3681
+ if ((_item_links1 = item.links) == null ? void 0 : _item_links1.length) parts.push(itemLinks(scope, item.links));
3682
+ }
3683
+ const shown = parts.filter((id)=>!!id);
3684
+ // The picture of a card is its top, edge to edge; the rest sit in its padding.
3685
+ const picture = style === 'cards' || style === 'articles' ? shown[0] && ((_tree_nodes_shown_ = tree.nodes[shown[0]]) == null ? void 0 : _tree_nodes_shown_.componentId) === 'image' ? shown.shift() : null : null;
3686
+ const stack = tree.add('muiStack', _extends({
3687
+ spacing: '1.5'
3688
+ }, scope.centered && (kind === 'stats' || style === 'icons') ? {
3689
+ alignItems: 'center'
3690
+ } : {}), null, shown, kind === 'steps' ? 'step' : kind === 'stats' ? 'stat' : 'cardStack');
3691
+ let drawn;
3692
+ if (style === 'cards' || style === 'articles') {
3693
+ const content = tree.add('muiCardContent', null, {
3694
+ p: 3
3695
+ }, [
3696
+ stack
3697
+ ], 'cardContent');
3698
+ drawn = tree.add('muiCard', null, {
3699
+ height: '100%'
3700
+ }, picture ? [
3701
+ picture,
3702
+ content
3703
+ ] : [
3704
+ content
3705
+ ], kind === 'quotes' ? 'quoteCard' : 'card');
3706
+ } else if (style === 'ruled') {
3707
+ drawn = tree.add('muiBox', null, {
3708
+ borderTop: 1
3709
+ }, [
3710
+ tree.add('muiBox', null, {
3711
+ pt: 2.5
3712
+ }, [
3713
+ stack
3714
+ ], 'ruledWords')
3715
+ ], 'ruled');
3716
+ } else {
3717
+ drawn = stack;
3718
+ }
3719
+ // An item the source made one link as a whole is one here (AGL-4107): its
3720
+ // words keep their places, and pressing anywhere on it goes where it went.
3721
+ const whole = !((_item_links = item.links) == null ? void 0 : _item_links.length) && item.to ? aiLayoutImportedDestination(item.to, scope.page.plan.sections.length) : null;
3722
+ if (!whole || whole.kind === 'section') return drawn;
3723
+ return tree.add('muiLinkBox', _extends({}, destinationProps(whole), title ? {
3724
+ ariaLabel: title.slice(0, 200)
3725
+ } : {}, whole.kind === 'href' ? {
3726
+ newTab: true
3727
+ } : {}), {
3728
+ height: '100%'
3729
+ }, [
3730
+ drawn
3731
+ ], 'linked');
3732
+ }
3733
+ /** A list of a page brought in: each line as written, led by the source's mark where it drew one, a link where it was one. */ function importedList(scope, block, room) {
3734
+ var _block_items, _block_columns;
3735
+ const tree = scope.page.tree;
3736
+ const items = ((_block_items = block.items) != null ? _block_items : []).filter((item)=>aiLayoutAsWritten(item.title || item.text));
3737
+ if (!items.length) return null;
3738
+ const rows = items.map((item)=>{
3739
+ const line = aiLayoutAsWritten([
3740
+ item.title,
3741
+ item.text
3742
+ ].filter((part)=>part.trim()).join(' '));
3743
+ const destination = item.to ? aiLayoutImportedDestination(item.to, scope.page.plan.sections.length) : null;
3744
+ const words = !destination ? tree.add('muiTypography', {
3745
+ children: line,
3746
+ variant: 'body1'
3747
+ }, null, null, 'line') : line.length > AI_TEXT_LIMITS.button ? linkedWords(scope, line, destination) : tree.add('muiScreenLink', _extends({
3748
+ children: line,
3749
+ renderAs: 'link',
3750
+ color: 'inherit'
3751
+ }, destinationProps(destination)), null, null, 'link');
3752
+ if (destination && line.length <= AI_TEXT_LIMITS.button) linkTo(scope, words, destination);
3753
+ const mark = item.iconPath || item.icon ? sourceIcon(scope, item, '22', accent(scope), 'check') : null;
3754
+ return mark ? tree.add('muiStack', {
3755
+ direction: 'row',
3756
+ spacing: '1.5',
3757
+ alignItems: 'flex-start'
3758
+ }, null, [
3759
+ mark,
3760
+ words
3761
+ ], 'row') : words;
3762
+ });
3763
+ noted(scope, rows);
3764
+ const perRow = importedAcross(_extends({}, block, {
3765
+ columns: (_block_columns = block.columns) != null ? _block_columns : 1
3766
+ }), rows.length, room);
3767
+ if (perRow > 1) return layOut(scope, rows, perRow, '2', 'list');
3768
+ return tree.add('muiStack', {
3769
+ spacing: '1.5'
3770
+ }, null, rows, 'list');
3771
+ }
3772
+ /** Questions and answers of a page brought in: an accordion each, the first open, every answer's paragraphs in it. */ function importedFaq(scope, block) {
3773
+ var _block_items;
3774
+ const tree = scope.page.tree;
3775
+ const items = ((_block_items = block.items) != null ? _block_items : []).filter((item)=>aiLayoutAsWritten(item.title));
3776
+ if (!items.length) return null;
3777
+ const panels = items.map((item, position)=>{
3778
+ var _item_more;
3779
+ var _item_list, _item_links;
3780
+ const answer = paragraphs(scope, [
3781
+ item.text,
3782
+ ...(_item_more = item.more) != null ? _item_more : []
3783
+ ], 'body1', {
3784
+ color: 'text.secondary'
3785
+ }, 'answer');
3786
+ if ((_item_list = item.list) == null ? void 0 : _item_list.length) answer.push(bullets(scope, item.list, item.listIconPath));
3787
+ if ((_item_links = item.links) == null ? void 0 : _item_links.length) {
3788
+ const links = itemLinks(scope, item.links);
3789
+ if (links) answer.push(links);
3790
+ }
3791
+ const shown = answer.filter(Boolean);
3792
+ return tree.add('muiAccordion', _extends({
3793
+ disableGutters: true
3794
+ }, position === 0 ? {
3795
+ defaultExpanded: true
3796
+ } : {}), null, [
3797
+ tree.add('muiAccordionSummary', {
3798
+ children: aiLayoutAsWritten(item.title)
3799
+ }, null, null, 'question'),
3800
+ shown.length ? tree.add('muiAccordionDetails', null, null, [
3801
+ shown.length === 1 ? shown[0] : tree.add('muiStack', {
3802
+ spacing: '1.5'
3803
+ }, null, shown, 'answerStack')
3804
+ ], 'details') : null
3805
+ ], 'panel');
3806
+ });
3807
+ noted(scope, panels);
3808
+ return tree.add('muiStack', {
3809
+ spacing: '1.5'
3810
+ }, null, panels, 'faq');
3811
+ }
3812
+ /** A table's cell as the Table element's grid holds it: one line, its pipes escaped. */ function tableCell(text) {
3813
+ return aiLayoutAsWritten(text).replace(/\|/g, '\\|');
3814
+ }
3815
+ /** A table of a page brought in: the theme's Table element, every cell's words as written. */ function table(scope, block) {
3816
+ var _block_rows;
3817
+ const rows = ((_block_rows = block.rows) != null ? _block_rows : []).filter((row)=>row.cells.some((cell)=>aiLayoutAsWritten(cell)));
3818
+ if (!rows.length) return null;
3819
+ const width = Math.max(...rows.map((row)=>row.cells.length));
3820
+ const grid = rows.map((row)=>`| ${Array.from({
3821
+ length: width
3822
+ }, (_, at)=>{
3823
+ var _row_cells_at;
3824
+ return tableCell((_row_cells_at = row.cells[at]) != null ? _row_cells_at : '');
3825
+ }).join(' | ')} |`).join('\n');
3826
+ return scope.page.tree.add('dataTable', {
3827
+ rows: grid,
3828
+ headerRow: block.header === true
3829
+ }, null, null, 'table');
3830
+ }
3831
+ /** A label the Tabs element can name a panel by: its words on one line, with no comma it would split at. */ function tabLabel(label) {
3832
+ const text = aiLayoutAsWritten(label);
3833
+ return text && !text.includes(',') && text.length <= AI_TEXT_LIMITS.label ? text : null;
3834
+ }
3835
+ /**
3836
+ * Tabs of a page brought in: the Tabs element with a panel for each tab,
3837
+ * opening on the one the source shows open, where every tab has its panel.
3838
+ * A source whose script draws its other panels only when clicked gives a
3839
+ * page the open one alone (aglyn.com's plan tabs): then its labels are drawn
3840
+ * as the strip they are — the open one filled, the rest outlined — over the
3841
+ * panel the source showed, so every label and every word is on the page and
3842
+ * no tab opens onto nothing.
3843
+ */ function tabs(scope, block, room) {
3844
+ var _block_tabs;
3845
+ const tree = scope.page.tree;
3846
+ const given = ((_block_tabs = block.tabs) != null ? _block_tabs : []).filter((tab)=>aiLayoutAsWritten(tab.label) || tab.blocks.length);
3847
+ if (!given.length) return null;
3848
+ const labels = given.map((tab)=>tabLabel(tab.label));
3849
+ const panels = given.map((tab)=>tab.blocks.length ? compileFlow(scope, tab.blocks, room) : null);
3850
+ const whole = given.length >= 2 && given.length <= 8 && labels.every((label)=>!!label) && new Set(labels.map((label)=>label.toLowerCase())).size === labels.length && panels.every(Boolean);
3851
+ if (whole) {
3852
+ const open = given.findIndex((tab)=>tab.selected);
3853
+ return tree.add('muiTabs', _extends({
3854
+ labels: labels.join('\n'),
3855
+ variant: 'scrollable'
3856
+ }, open > 0 ? {
3857
+ opensOn: labels[open]
3858
+ } : {}), null, given.map((_, at)=>tree.add('muiTabPanel', {
3859
+ label: labels[at]
3860
+ }, null, [
3861
+ panels[at]
3862
+ ], 'tabPanel')), 'tabs');
3863
+ }
3864
+ const strip = given.map((tab)=>({
3865
+ text: aiLayoutAsWritten(tab.label),
3866
+ selected: tab.selected === true
3867
+ })).filter((tab)=>tab.text).map((tab)=>tab.selected ? tree.add('muiPaper', {
3868
+ variant: 'elevation',
3869
+ elevation: '0'
3870
+ }, {
3871
+ bgcolor: 'primary.main'
3872
+ }, [
3873
+ tree.add('muiBox', null, {
3874
+ py: 0.75,
3875
+ color: 'primary.contrastText'
3876
+ }, [
3877
+ tree.add('muiTypography', {
3878
+ children: tab.text,
3879
+ variant: 'body2'
3880
+ }, {
3881
+ px: 2
3882
+ }, null, 'tabLabel')
3883
+ ], 'tabWords')
3884
+ ], 'tab') : chip(scope, tab.text, 'tab'));
3885
+ const parts = [
3886
+ strip.length ? tree.add('muiStack', _extends({
3887
+ direction: 'row',
3888
+ spacing: '1',
3889
+ useFlexGap: true,
3890
+ flexWrap: 'wrap'
3891
+ }, scope.centered ? {
3892
+ justifyContent: 'center'
3893
+ } : {}), null, strip, 'tabStrip') : null,
3894
+ ...panels
3895
+ ].filter((id)=>!!id);
3896
+ if (!parts.length) return null;
3897
+ return parts.length === 1 ? parts[0] : tree.add('muiStack', {
3898
+ spacing: '4'
3899
+ }, null, parts, 'tabs');
3900
+ }
3901
+ /** A short line on an outlined surface of the theme's own shape: a label, a tag, a tab the source draws as one. */ function chip(scope, words, role) {
3902
+ const tree = scope.page.tree;
3903
+ return tree.add('muiPaper', {
3904
+ variant: 'outlined'
3905
+ }, {
3906
+ py: 0.75
3907
+ }, [
3908
+ tree.add('muiTypography', {
3909
+ children: words,
3910
+ variant: 'body2'
3911
+ }, {
3912
+ px: 2
3913
+ }, null, `${role}Label`)
3914
+ ], role);
3915
+ }
3916
+ /** A strip of logos of a page brought in: each picture at one height, or each short line as a label on an outlined surface. */ function logos(scope, block) {
3917
+ var _block_items;
3918
+ var _block_items1;
3919
+ const tree = scope.page.tree;
3920
+ const ids = ((_block_items = block.items) != null ? _block_items : []).flatMap((item)=>{
3921
+ if (item.picture) {
3922
+ scope.page.images += 1;
3923
+ return [
3924
+ tree.add('image', _extends({
3925
+ src: item.picture.src
3926
+ }, item.picture.alt ? {
3927
+ alt: item.picture.alt
3928
+ } : {
3929
+ decorative: true
3930
+ }, {
3931
+ objectFit: 'contain',
3932
+ width: 'auto'
3933
+ }, scope.page.images > 1 ? {
3934
+ loading: 'lazy'
3935
+ } : {}), {
3936
+ height: {
3937
+ xs: 32,
3938
+ md: 40
3939
+ }
3940
+ }, null, 'logo')
3941
+ ];
3942
+ }
3943
+ const words = aiLayoutAsWritten([
3944
+ item.title,
3945
+ item.text
3946
+ ].filter((part)=>part.trim()).join(' '));
3947
+ return words ? [
3948
+ chip(scope, words, 'chip')
3949
+ ] : [];
3950
+ });
3951
+ if (!ids.length) return null;
3952
+ return tree.add('muiStack', _extends({
3953
+ direction: 'row',
3954
+ spacing: ((_block_items1 = block.items) == null ? void 0 : _block_items1.some((item)=>item.picture)) ? '5' : '1.5',
3955
+ useFlexGap: true,
3956
+ flexWrap: 'wrap',
3957
+ alignItems: 'center'
3958
+ }, scope.centered ? {
3959
+ justifyContent: 'center'
3960
+ } : {}), null, noted(scope, ids), 'logos');
3961
+ }
3962
+ /**
3963
+ * The opening of a page brought in, as the hero its design answer chose
3964
+ * around the source's own picture (AGL-4107): its words and one picture of
3965
+ * the source's, nothing else. Any other opening is drawn as its source lays
3966
+ * it out, its picture where the source placed it.
3967
+ */ function importedHero(scope, raw, blocks) {
3968
+ const { page } = scope;
3969
+ if (!page.design || !raw.hero) return null;
3970
+ const pictures = blocks.filter((block)=>block.kind === 'image' && block.picture);
3971
+ if (pictures.length !== 1) return null;
3972
+ const flow = blocks.filter((block)=>block.kind !== 'image');
3973
+ if (!flow.length || !flow.every((block)=>WORD_KINDS.has(block.kind))) return null;
3974
+ if (!flow.some((block)=>speaksHeading(scope, block))) {
3975
+ flow.unshift({
3976
+ kind: 'heading',
3977
+ text: page.plan.title
3978
+ });
3979
+ page.settled.push({
3980
+ at: scope.at,
3981
+ what: "no heading; the page's title written as its h1"
3982
+ });
3983
+ }
3984
+ const lead = flow.findIndex((block)=>speaksHeading(scope, block));
3985
+ const _flow_lead = flow[lead], { style: _style } = _flow_lead, title = _object_without_properties_loose(_flow_lead, [
3986
+ "style"
3987
+ ]);
3988
+ flow[lead] = AI_LAYOUT_HALF_HEROES.has(raw.hero) ? title : _extends({}, title, {
3989
+ style: 'large'
3990
+ });
3991
+ const picture = pictures[0].picture;
3992
+ return hero(scope, flow, {
3993
+ alt: picture.alt,
3994
+ given: true,
3995
+ src: picture.src
3996
+ }, raw.hero);
3997
+ }
3354
3998
  /** The id the compiled page's root is stored under. */ export const AI_LAYOUT_PAGE_ROOT_ID = CANVAS_ROOT_ELEMENT_ID;
3355
3999
 
3356
4000
  //# sourceMappingURL=ai-layout-compiler.js.map