@aglyn/plugins-ai 1.0.0-beta.233 → 1.0.0-beta.235

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 (199) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +25 -0
  3. package/src/lib/activity/ai-activity-actions.js +85 -4
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-brief-dialog.component.js +46 -2
  6. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  7. package/src/lib/components/ai-campaign-create.component.js +1 -1
  8. package/src/lib/components/ai-campaign-create.component.js.map +1 -1
  9. package/src/lib/components/ai-credits-card.component.js +3 -3
  10. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  11. package/src/lib/components/ai-crm-email-draft.component.js +1 -1
  12. package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
  13. package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
  14. package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
  15. package/src/lib/components/ai-crm-record-card.component.js +1 -1
  16. package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-automation.component.js +4 -2
  18. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  19. package/src/lib/components/ai-describe-org-automation.component.js +1 -1
  20. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
  21. package/src/lib/components/ai-experiment-cards.component.js +2 -2
  22. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  23. package/src/lib/components/ai-explain-automation.component.js +1 -1
  24. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  25. package/src/lib/components/ai-insight-card.component.js +3 -1
  26. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  27. package/src/lib/components/ai-job-links.d.ts +6 -0
  28. package/src/lib/components/ai-job-links.js +9 -0
  29. package/src/lib/components/ai-job-links.js.map +1 -1
  30. package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
  31. package/src/lib/components/ai-jobs-list-page.component.js +15 -2
  32. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  33. package/src/lib/components/ai-logic.component.js +5 -3
  34. package/src/lib/components/ai-logic.component.js.map +1 -1
  35. package/src/lib/components/ai-media-create.component.js +88 -16
  36. package/src/lib/components/ai-media-create.component.js.map +1 -1
  37. package/src/lib/components/ai-model-selector.component.js +8 -0
  38. package/src/lib/components/ai-model-selector.component.js.map +1 -1
  39. package/src/lib/components/ai-overlay-cards.component.js +2 -2
  40. package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
  41. package/src/lib/components/ai-product-copy-card.component.js +2 -1
  42. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  43. package/src/lib/components/ai-products-hub-card.component.js +2 -1
  44. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  45. package/src/lib/components/ai-revise-automation.component.js +1 -1
  46. package/src/lib/components/ai-revise-automation.component.js.map +1 -1
  47. package/src/lib/components/ai-seo-fields-card.component.js +7 -3
  48. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-batch-card.component.js +7 -4
  50. package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
  51. package/src/lib/components/ai-site-build-page.component.js +48 -6
  52. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  53. package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
  54. package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
  55. package/src/lib/components/ai-site-start-card.component.js +152 -36
  56. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  57. package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
  58. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  59. package/src/lib/components/ai-upsell-dialog.component.js +17 -3
  60. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  61. package/src/lib/components/ai-usage-strip.component.js +13 -2
  62. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  63. package/src/lib/components/assist-build-card.component.js +20 -3
  64. package/src/lib/components/assist-build-card.component.js.map +1 -1
  65. package/src/lib/components/assist-jobs-drawer.component.js +7 -1
  66. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  67. package/src/lib/components/billing-ai-allotments.component.js +2 -2
  68. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  69. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  70. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  71. package/src/lib/components/billing-assist-overage-card.component.js +2 -2
  72. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  73. package/src/lib/components/host-ai-allotments.component.js +2 -2
  74. package/src/lib/components/host-ai-allotments.component.js.map +1 -1
  75. package/src/lib/components/member-ai-allotment-card.component.js +2 -2
  76. package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
  77. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  78. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  79. package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
  80. package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
  81. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
  82. package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
  83. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  84. package/src/lib/jobs/ai-job-admission.d.ts +2 -1
  85. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  86. package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
  87. package/src/lib/jobs/ai-job-email-step.js +116 -5
  88. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  89. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  90. package/src/lib/jobs/ai-job-layout-language.js +6 -1
  91. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  92. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +14 -0
  93. package/src/lib/jobs/ai-job-layout-site-pages.js +43 -11
  94. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  95. package/src/lib/jobs/ai-job-layout-step.js +3 -2
  96. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  97. package/src/lib/jobs/ai-job-page-language.d.ts +26 -1
  98. package/src/lib/jobs/ai-job-page-language.js +75 -13
  99. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  100. package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
  101. package/src/lib/jobs/ai-job-page-step.js +32 -7
  102. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-plan-step.d.ts +34 -0
  104. package/src/lib/jobs/ai-job-plan-step.js +106 -14
  105. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-site-content.d.ts +81 -13
  107. package/src/lib/jobs/ai-job-site-content.js +210 -43
  108. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  109. package/src/lib/jobs/ai-job-site-step.d.ts +19 -0
  110. package/src/lib/jobs/ai-job-site-step.js +152 -51
  111. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  112. package/src/lib/jobs/ai-jobs.d.ts +8 -4
  113. package/src/lib/jobs/ai-jobs.js +19 -2
  114. package/src/lib/jobs/ai-jobs.js.map +1 -1
  115. package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
  116. package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
  117. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
  118. package/src/lib/jobs/ai-site-publish.d.ts +11 -0
  119. package/src/lib/jobs/ai-site-publish.js +31 -1
  120. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  121. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
  122. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
  123. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
  124. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
  125. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
  126. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
  127. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
  128. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
  129. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
  130. package/src/lib/layout-language/ai-layout-compiler.d.ts +37 -0
  131. package/src/lib/layout-language/ai-layout-compiler.js +1003 -19
  132. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  133. package/src/lib/layout-language/ai-layout-design.d.ts +114 -0
  134. package/src/lib/layout-language/ai-layout-design.js +209 -0
  135. package/src/lib/layout-language/ai-layout-design.js.map +1 -0
  136. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  137. package/src/lib/layout-language/ai-layout-frame.js +33 -8
  138. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  139. package/src/lib/layout-language/ai-layout-gaps.js +18 -2
  140. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  141. package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
  142. package/src/lib/layout-language/ai-layout-language.js +15 -1
  143. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-links.d.ts +36 -0
  145. package/src/lib/layout-language/ai-layout-links.js +26 -2
  146. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  147. package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
  148. package/src/lib/layout-language/ai-layout-listings.js +187 -0
  149. package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
  150. package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
  151. package/src/lib/layout-language/ai-layout-pictures.js +80 -17
  152. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  153. package/src/lib/model/ai-job-notice.d.ts +9 -1
  154. package/src/lib/model/ai-job-notice.js +23 -0
  155. package/src/lib/model/ai-job-notice.js.map +1 -1
  156. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  157. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  158. package/src/lib/model/ai-site-build-progress.d.ts +12 -2
  159. package/src/lib/model/ai-site-build-progress.js +104 -15
  160. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  161. package/src/lib/model/ai-site-job.d.ts +236 -24
  162. package/src/lib/model/ai-site-job.js +269 -22
  163. package/src/lib/model/ai-site-job.js.map +1 -1
  164. package/src/lib/model/ai-site-kinds.d.ts +30 -8
  165. package/src/lib/model/ai-site-kinds.js +735 -116
  166. package/src/lib/model/ai-site-kinds.js.map +1 -1
  167. package/src/lib/model/ai-site-look.d.ts +21 -0
  168. package/src/lib/model/ai-site-look.js +63 -14
  169. package/src/lib/model/ai-site-look.js.map +1 -1
  170. package/src/lib/model/ai-site-start-seo.d.ts +30 -0
  171. package/src/lib/model/ai-site-start-seo.js +132 -11
  172. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  173. package/src/lib/model/ai-site-start.d.ts +2 -2
  174. package/src/lib/model/ai-site-start.js +3 -3
  175. package/src/lib/model/ai-site-start.js.map +1 -1
  176. package/src/lib/plugin.js +15 -3
  177. package/src/lib/plugin.js.map +1 -1
  178. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -1
  179. package/src/lib/runtime/ai-doctrine-validators.js +227 -4
  180. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  181. package/src/lib/runtime/ai-doctrine.js +5 -3
  182. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  183. package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
  184. package/src/lib/runtime/ai-palette.generated.js +31 -5
  185. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  186. package/src/lib/server/ai-admin-conversations.d.ts +9 -0
  187. package/src/lib/server/ai-admin-conversations.js +151 -9
  188. package/src/lib/server/ai-admin-conversations.js.map +1 -1
  189. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  190. package/src/lib/server/ai-jobs-route.js +15 -3
  191. package/src/lib/server/ai-jobs-route.js.map +1 -1
  192. package/src/lib/server/assist-edit.js +5 -2
  193. package/src/lib/server/assist-edit.js.map +1 -1
  194. package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
  195. package/src/lib/usage/free-ai-credits-left.js +62 -0
  196. package/src/lib/usage/free-ai-credits-left.js.map +1 -0
  197. package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
  198. package/src/lib/usage/staff-org-ai-conversations.js +45 -0
  199. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
@@ -22,8 +22,18 @@ import { AI_INSTANCE_REF_PROP } from "../runtime/ai-node-tree.js";
22
22
  import { aiPageScrollInteraction } from "../runtime/ai-page-links.js";
23
23
  import { aiLayoutFitText, aiLayoutQuoteGap, aiLayoutWords } from "./ai-layout-copy.js";
24
24
  import { AI_LAYOUT_GROUP_KINDS, AI_LAYOUT_MAX_ITEMS } from "./ai-layout-language.js";
25
- import { aiLayoutResolveLink } from "./ai-layout-links.js";
25
+ import { aiLayoutDesignChoices, aiLayoutGroupVariant } from "./ai-layout-design.js";
26
+ import { AI_LAYOUT_FEATURED_RECORDS, AI_LAYOUT_LISTING_ELEMENTS, aiLayoutListingAt } from "./ai-layout-listings.js";
27
+ import { aiLayoutRenamedLabel, aiLayoutResolveLink } from "./ai-layout-links.js";
26
28
  import { AI_LAYOUT_FRAMES, AiLayoutTreeBuilder } from "./ai-layout-tree.js";
29
+ /**
30
+ * What a section is called to a screen reader: its plan name, with any note
31
+ * the plan left in brackets taken out. A live blog plan (2026-10-09) named a
32
+ * section "reader notes and kind words [to be added by owner]"; carried into
33
+ * the label it read as a gap, and the page's gap pass took the section out.
34
+ */ function sectionLabel(name, index) {
35
+ return aiLayoutFitText(name.replace(/\[[^\]]*\]/g, ' ').replace(/\s+/g, ' ').trim(), 'note') || `Section ${index + 1}`;
36
+ }
27
37
  /** A section root's id where the caller gives none. */ function sectionIdOf(index) {
28
38
  return `section-${index + 1}`;
29
39
  }
@@ -69,7 +79,15 @@ const componentOf = (targets, id)=>{
69
79
  formSection: formSectionOf(sections, plan, targets),
70
80
  pageIcon: pageIconOf(sections),
71
81
  formsPlaced: new Set(),
72
- itemIds: plan.sections.map(()=>[])
82
+ itemIds: plan.sections.map(()=>[]),
83
+ design: options.design ? {
84
+ input: options.design,
85
+ choices: aiLayoutDesignChoices(options.design)
86
+ } : null,
87
+ pictures: 0,
88
+ features: 0,
89
+ splitAt: -2,
90
+ quotesOnly: []
73
91
  };
74
92
  const roots = plan.sections.map((_, index)=>{
75
93
  var _sections_index;
@@ -87,7 +105,8 @@ const componentOf = (targets, id)=>{
87
105
  sectionRoots: roots,
88
106
  scrollTo: page.scrollTo,
89
107
  settled: page.settled,
90
- itemIds: page.itemIds
108
+ itemIds: page.itemIds,
109
+ quotesOnly: page.quotesOnly
91
110
  };
92
111
  }
93
112
  /** The first icon the design names anywhere, else a sparkle: what an empty picture shows. */ function pageIconOf(sections) {
@@ -106,8 +125,8 @@ const componentOf = (targets, id)=>{
106
125
  }
107
126
  // ── A section ─────────────────────────────────────────────────────────────
108
127
  function compileSection(page, raw, index) {
109
- var _raw_band, _ref;
110
- var _placedHere_, _page_plan_sections_index_name, _page_plan_sections_index, _page_options_sectionIds;
128
+ var _raw_band, _ref, _page_targets_listings, _ref1;
129
+ var _raw_blocks_find, _page_plan_sections_index, _placedHere_, _page_plan_sections_index_name, _page_plan_sections_index1, _page_options_sectionIds;
111
130
  const at = `sections[${index}]`;
112
131
  const band = settleBand(page, (_raw_band = raw.band) != null ? _raw_band : 'plain', at);
113
132
  const scope = {
@@ -123,7 +142,15 @@ function compileSection(page, raw, index) {
123
142
  sections: page.plan.sections.map((section)=>section.name),
124
143
  from: index,
125
144
  formSection: page.formSection
126
- }
145
+ },
146
+ heading: (_raw_blocks_find = raw.blocks.find((block)=>block.kind === 'heading')) == null ? void 0 : _raw_blocks_find.text,
147
+ words: [
148
+ (_ref = (_page_plan_sections_index = page.plan.sections[index]) == null ? void 0 : _page_plan_sections_index.name) != null ? _ref : '',
149
+ ...raw.blocks.filter((block)=>block.kind === 'heading' || block.kind === 'eyebrow').map((block)=>{
150
+ var _block_text;
151
+ return (_block_text = block.text) != null ? _block_text : '';
152
+ })
153
+ ].join(' ')
127
154
  };
128
155
  // A quote is a gap the owner fills, and a published page shows no gap (AGL-3660).
129
156
  const blocks = placePlanned(scope, raw).filter((block)=>{
@@ -134,6 +161,20 @@ function compileSection(page, raw, index) {
134
161
  });
135
162
  return false;
136
163
  });
164
+ // A section whose only items were those quotes has none left to show, and is
165
+ // not asked for them again: no answer can give words the brief did not (AGL-3676).
166
+ if (raw.blocks.some((block)=>block.kind === 'quotes') && !blocks.some((block)=>AI_LAYOUT_GROUP_KINDS.has(block.kind) || block.kind === 'component')) {
167
+ page.quotesOnly.push(index);
168
+ }
169
+ // A section the site's records fill — its catalog, its blog — shows them
170
+ // through the element that keeps them, around the words the design gave it (AGL-3676).
171
+ const listed = aiLayoutListingAt((_page_targets_listings = page.targets.listings) != null ? _page_targets_listings : [], page.targets.pageId, index);
172
+ if (listed) return listingSection(scope, blocks, listed);
173
+ // The site's design draws the sections it has an arrangement for (AGL-3660).
174
+ if (page.design) {
175
+ const designed = designSection(scope, raw, blocks);
176
+ if (designed) return designed;
177
+ }
137
178
  const cols = raw.cols && raw.cols.length >= 2 ? raw.cols : null;
138
179
  // Above the row, the row's columns, and below it.
139
180
  const head = [];
@@ -280,13 +321,13 @@ function compileSection(page, raw, index) {
280
321
  }
281
322
  }
282
323
  if (!parts.length) {
283
- var _page_plan_sections_index1;
324
+ var _page_plan_sections_index2;
284
325
  // Nothing the design gave this section could be shown: it keeps its
285
326
  // place on the page under the name its plan gives it.
286
327
  const fallback = compileFlow(scope, [
287
328
  {
288
329
  kind: 'heading',
289
- text: ((_page_plan_sections_index1 = page.plan.sections[index]) == null ? void 0 : _page_plan_sections_index1.name) || page.plan.title
330
+ text: ((_page_plan_sections_index2 = page.plan.sections[index]) == null ? void 0 : _page_plan_sections_index2.name) || page.plan.title
290
331
  }
291
332
  ], 'full');
292
333
  if (fallback) parts.push(fallback);
@@ -308,15 +349,15 @@ function compileSection(page, raw, index) {
308
349
  }, [
309
350
  content
310
351
  ], 'container');
311
- const name = ((_page_plan_sections_index = page.plan.sections[index]) == null ? void 0 : (_page_plan_sections_index_name = _page_plan_sections_index.name) == null ? void 0 : _page_plan_sections_index_name.trim()) || `Section ${index + 1}`;
352
+ const name = ((_page_plan_sections_index1 = page.plan.sections[index]) == null ? void 0 : (_page_plan_sections_index_name = _page_plan_sections_index1.name) == null ? void 0 : _page_plan_sections_index_name.trim()) || `Section ${index + 1}`;
312
353
  return tree.add('section', _extends({
313
354
  element: 'section',
314
- ariaLabel: aiLayoutFitText(name, 'note') || `Section ${index + 1}`
355
+ ariaLabel: sectionLabel(name, index)
315
356
  }, band === 'dark' ? {
316
357
  colorScheme: 'dark'
317
358
  } : {}), bandSx(band), [
318
359
  container
319
- ], 'section', (_ref = (_page_options_sectionIds = page.options.sectionIds) == null ? void 0 : _page_options_sectionIds[index]) != null ? _ref : sectionIdOf(index));
360
+ ], 'section', (_ref1 = (_page_options_sectionIds = page.options.sectionIds) == null ? void 0 : _page_options_sectionIds[index]) != null ? _ref1 : sectionIdOf(index));
320
361
  }
321
362
  /**
322
363
  * A component block's prop values as a card item: its title-like prop as the
@@ -650,12 +691,12 @@ function compileBlock(scope, block, room) {
650
691
  }
651
692
  }
652
693
  /** Secondary words' color: muted text, or the band's own on a brand band. */ function muted(scope) {
653
- return scope.band === 'brand' ? null : {
694
+ return scope.band === 'brand' || scope.overPhoto ? null : {
654
695
  color: 'text.secondary'
655
696
  };
656
697
  }
657
698
  /** An accent's color: the brand's, or the band's own on a brand band. */ function accent(scope) {
658
- return scope.band === 'brand' ? null : {
699
+ return scope.band === 'brand' || scope.overPhoto ? null : {
659
700
  color: 'primary.main'
660
701
  };
661
702
  }
@@ -702,18 +743,25 @@ function words(scope, block, place, variant, props, sx) {
702
743
  }
703
744
  function compileButton(scope, block) {
704
745
  var _block_style;
705
- const label = aiLayoutFitText(block.text, 'label');
706
- if (!label) return null;
707
- const destination = aiLayoutResolveLink(block.to, label, scope.link, scope.page.targets);
746
+ const asked = aiLayoutFitText(block.text, 'label');
747
+ if (!asked) return null;
748
+ const destination = aiLayoutResolveLink(block.to, asked, scope.link, scope.page.targets);
708
749
  if (!destination) {
709
750
  scope.page.settled.push({
710
751
  at: scope.at,
711
- what: `the button "${label}" has nowhere to go; left out`
752
+ what: `the button "${asked}" has nowhere to go; left out`
712
753
  });
713
754
  return null;
714
755
  }
756
+ // A button to a page merged into the blog names the blog (AGL-3676).
757
+ const label = aiLayoutRenamedLabel(asked, destination);
758
+ if (label !== asked) scope.page.settled.push({
759
+ at: scope.at,
760
+ what: `the button "${asked}" named for the blog: "${label}"`
761
+ });
715
762
  const style = (_block_style = block.style) != null ? _block_style : 'primary';
716
- const onBrand = scope.band === 'brand';
763
+ // Over a photo, a quiet or outlined button takes the words' own color, as on a brand band.
764
+ const onBrand = scope.band === 'brand' || !!scope.overPhoto && style !== 'primary';
717
765
  const look = style === 'quiet' ? {
718
766
  variant: 'text',
719
767
  color: onBrand ? 'inherit' : 'primary'
@@ -760,6 +808,9 @@ export function destinationProps(destination) {
760
808
  href: destination.href,
761
809
  target: '_blank'
762
810
  };
811
+ if (destination.kind === 'path') return {
812
+ href: destination.href
813
+ };
763
814
  return {};
764
815
  }
765
816
  /**
@@ -968,7 +1019,24 @@ function group(scope, block, room) {
968
1019
  const perRow = across(items.length, room, block.kind);
969
1020
  const placed = componentOf(scope.page.targets, block.to);
970
1021
  if (placed) return instances(scope, placed, items, perRow);
971
- if (rule1(scope)) return compactGroup(scope, block, items, perRow);
1022
+ const design = scope.page.design;
1023
+ if (rule1(scope)) {
1024
+ var _scope_words;
1025
+ // Picture cards the design draws are the compiler's, not a block typed out
1026
+ // by hand (`repeatsCompiled`, AGL-3660); every other repeat stays compact.
1027
+ const variant = design && block.kind === 'cards' && block.style !== 'quiet' ? aiLayoutGroupVariant(design.input, design.choices, (_scope_words = scope.words) != null ? _scope_words : '') : null;
1028
+ const pictured = (variant === 'pictures' || variant === 'articles') && room === 'full' && picturesLeft(scope.page) >= items.length;
1029
+ return pictured && designedGroup(scope, variant, items, room) || compactGroup(scope, block, items, perRow);
1030
+ }
1031
+ if (design && block.kind === 'steps' && (design.choices.steps === 'timeline' || room !== 'full')) {
1032
+ return timeline(scope, items);
1033
+ }
1034
+ if (design && block.kind === 'cards' && block.style !== 'quiet') {
1035
+ var _scope_groupStyle, _scope_words1;
1036
+ const variant = (_scope_groupStyle = scope.groupStyle) != null ? _scope_groupStyle : aiLayoutGroupVariant(design.input, design.choices, (_scope_words1 = scope.words) != null ? _scope_words1 : '');
1037
+ const drawn = designedGroup(scope, variant, items, room);
1038
+ if (drawn) return drawn;
1039
+ }
972
1040
  switch(block.kind){
973
1041
  case 'steps':
974
1042
  return layOut(scope, noted(scope, items.map((item, position)=>step(scope, item, position))), perRow, '4', 'steps');
@@ -1230,6 +1298,922 @@ function instances(scope, placed, items, perRow) {
1230
1298
  spacing: '1.5'
1231
1299
  }, null, noted(scope, panels), 'faq');
1232
1300
  }
1301
+ // ── The designer layer (AGL-3660) ─────────────────────────────────────────
1302
+ //
1303
+ // What `ai-layout-design.ts` chose for the site, drawn: the hero, a section
1304
+ // of words beside a picture, a closing band over a photo, a heading beside
1305
+ // its items, and groups drawn as pictures, articles, a menu or open ruled
1306
+ // columns. Every variant is built from the same palette elements and theme
1307
+ // tokens as the rest of the compiler, so the doctrine holds by construction.
1308
+ /** The most pictures a designed page carries, the model's and the stand-ins together. */ export const AI_LAYOUT_MAX_DESIGN_PICTURES = 8;
1309
+ /** The block kinds a section of words holds. */ const WORD_KINDS = new Set([
1310
+ 'eyebrow',
1311
+ 'heading',
1312
+ 'lede',
1313
+ 'text',
1314
+ 'note',
1315
+ 'button'
1316
+ ]);
1317
+ /** How many more pictures the page has room for. */ function picturesLeft(page) {
1318
+ return AI_LAYOUT_MAX_DESIGN_PICTURES - page.images - page.pictures;
1319
+ }
1320
+ /** Whether a heading block has words left once fitted: one with none is no heading. */ function speaksHeading(scope, block) {
1321
+ return block.kind === 'heading' && !!aiLayoutWords(block.text, 'heading', scope.page.targets.facts);
1322
+ }
1323
+ /** The section root every designed section is written under, as `compileSection` writes its own. */ function designedRoot(scope, children, sx, dark) {
1324
+ var _ref;
1325
+ var _page_plan_sections_index_name, _page_plan_sections_index, _page_options_sectionIds;
1326
+ const { page, index } = scope;
1327
+ const name = ((_page_plan_sections_index = page.plan.sections[index]) == null ? void 0 : (_page_plan_sections_index_name = _page_plan_sections_index.name) == null ? void 0 : _page_plan_sections_index_name.trim()) || `Section ${index + 1}`;
1328
+ return page.tree.add('section', _extends({
1329
+ element: 'section',
1330
+ ariaLabel: sectionLabel(name, index)
1331
+ }, dark ? {
1332
+ colorScheme: 'dark'
1333
+ } : {}, index === 0 && scope.overPhoto ? {
1334
+ underHeader: true
1335
+ } : {}), sx, children, 'section', (_ref = (_page_options_sectionIds = page.options.sectionIds) == null ? void 0 : _page_options_sectionIds[index]) != null ? _ref : sectionIdOf(index));
1336
+ }
1337
+ /**
1338
+ * A picture the design places: the model's own where it described one, else
1339
+ * a stand-in — marked decorative, since no one described it, with the
1340
+ * section's words as what a stock search looks for. Either is filled by
1341
+ * `ai-layout-pictures.ts` after the page is checked, as any slot is.
1342
+ */ function designPicture(scope, picture, frameSx, role = 'designFrame', imageSx = {}) {
1343
+ const tree = scope.page.tree;
1344
+ // The picture fills its frame by its own size props, so the frame alone
1345
+ // carries a style: a page of many pictures repeats no inline style (rule 16).
1346
+ const image = designImage(scope, picture, {
1347
+ width: '100%',
1348
+ height: '100%'
1349
+ }, Object.keys(imageSx).length ? imageSx : null);
1350
+ return tree.add('muiBox', null, _extends({
1351
+ position: 'relative',
1352
+ overflow: 'hidden',
1353
+ bgcolor: 'action.hover'
1354
+ }, frameSx), [
1355
+ image
1356
+ ], role);
1357
+ }
1358
+ /**
1359
+ * The `image` a designed picture is: its alt, its fit and, for a stand-in,
1360
+ * decorative. Its slot is filled after the page is checked.
1361
+ */ function designImage(scope, picture, props, sx) {
1362
+ const { page } = scope;
1363
+ if (picture.given) page.images += 1;
1364
+ else page.pictures += 1;
1365
+ return page.tree.add('image', _extends({
1366
+ alt: picture.alt,
1367
+ objectFit: 'cover'
1368
+ }, picture.given ? {} : {
1369
+ decorative: true
1370
+ }, props), sx, null, 'image');
1371
+ }
1372
+ /** What a stand-in picture is of, for a stock search: the section's subject, else the site's. */ function standInAlt(scope, words) {
1373
+ var _scope_page_plan_sections_scope_index;
1374
+ return aiLayoutFitText(words, 'alt') || aiLayoutFitText(scope.heading, 'alt') || aiLayoutFitText((_scope_page_plan_sections_scope_index = scope.page.plan.sections[scope.index]) == null ? void 0 : _scope_page_plan_sections_scope_index.name, 'alt') || aiLayoutFitText(scope.page.plan.title, 'alt') || 'A photo';
1375
+ }
1376
+ /** A grid row of sized cells, as `compileRow` writes one, from ids already compiled. */ function designRow(scope, cells, spacing, alignItems = 'center') {
1377
+ const tree = scope.page.tree;
1378
+ return tree.add('muiGrid', {
1379
+ container: true,
1380
+ spacing
1381
+ }, {
1382
+ alignItems
1383
+ }, cells.map((cell)=>{
1384
+ var _cell_sx;
1385
+ return tree.add('muiGrid', {
1386
+ size: cell.size
1387
+ }, (_cell_sx = cell.sx) != null ? _cell_sx : null, [
1388
+ cell.id
1389
+ ], 'column');
1390
+ }), 'row');
1391
+ }
1392
+ /** A section's own arrangement where the design has one; `null` to draw it the compiler's first way. */ function designSection(scope, raw, blocks) {
1393
+ var _scope_words, _ref, _ref1;
1394
+ var _group_items, _group_items1;
1395
+ const { page, index } = scope;
1396
+ const design = page.design;
1397
+ if (!design) return null;
1398
+ const images = blocks.filter((block)=>block.kind === 'image');
1399
+ const words = blocks.filter((block)=>block.kind !== 'image');
1400
+ const ofWords = words.length > 0 && words.every((block)=>WORD_KINDS.has(block.kind));
1401
+ if (index === 0) {
1402
+ var _words_find;
1403
+ if (!ofWords || images.length > 1 || picturesLeft(page) < 1) return null;
1404
+ const flow = [
1405
+ ...words
1406
+ ];
1407
+ if (!flow.some((block)=>speaksHeading(scope, block))) {
1408
+ flow.unshift({
1409
+ kind: 'heading',
1410
+ text: page.plan.title
1411
+ });
1412
+ page.settled.push({
1413
+ at: scope.at,
1414
+ what: "no heading; the page's title written as its h1"
1415
+ });
1416
+ }
1417
+ // A designed hero's title is display-sized; beside a photo, in half the
1418
+ // page, it is the page's title size, so a long one still reads in a few lines.
1419
+ const lead = flow.findIndex((block)=>speaksHeading(scope, block));
1420
+ const _flow_lead = flow[lead], { style: _style } = _flow_lead, title = _object_without_properties_loose(_flow_lead, [
1421
+ "style"
1422
+ ]);
1423
+ flow[lead] = design.choices.hero === 'split' ? title : _extends({}, title, {
1424
+ style: 'large'
1425
+ });
1426
+ const given = images[0] ? aiLayoutFitText(images[0].text, 'alt') : '';
1427
+ return hero(scope, raw, flow, given ? {
1428
+ alt: given,
1429
+ given: true
1430
+ } : {
1431
+ alt: standInAlt(scope, (_words_find = words.find((block)=>speaksHeading(scope, block))) == null ? void 0 : _words_find.text),
1432
+ given: false
1433
+ });
1434
+ }
1435
+ if (ofWords && !images.length && words.some((block)=>speaksHeading(scope, block))) {
1436
+ const last = index === page.plan.sections.length - 1;
1437
+ const asks = words.some((block)=>block.kind === 'button');
1438
+ const strong = scope.band === 'brand' || scope.band === 'dark';
1439
+ const told = words.some((block)=>block.kind === 'text' || block.kind === 'lede');
1440
+ if (last && asks && (strong || scope.centered) && design.choices.coverClose && picturesLeft(page) >= 1) {
1441
+ return coverBand(scope, words);
1442
+ }
1443
+ if (design.choices.features && told && page.features < 2 && picturesLeft(page) >= 1 && !(last && asks && strong)) {
1444
+ return feature(scope, words);
1445
+ }
1446
+ return null;
1447
+ }
1448
+ // A heading over one group of items: the heading beside them.
1449
+ if (!design.choices.splitHeads || images.length || page.splitAt === index - 1) return null;
1450
+ const at = words.findIndex((block)=>block.kind === 'cards' || block.kind === 'steps');
1451
+ if (at <= 0) return null;
1452
+ const head = words.slice(0, at);
1453
+ const group = words[at];
1454
+ const tail = words.slice(at + 1);
1455
+ const variant = aiLayoutGroupVariant(design.input, design.choices, (_scope_words = scope.words) != null ? _scope_words : '');
1456
+ if (!head.every((block)=>WORD_KINDS.has(block.kind) && block.kind !== 'button') || !head.some((block)=>speaksHeading(scope, block)) || !tail.every((block)=>WORD_KINDS.has(block.kind)) || ((_ref = (_group_items = group.items) == null ? void 0 : _group_items.length) != null ? _ref : 0) < 2 || ((_ref1 = (_group_items1 = group.items) == null ? void 0 : _group_items1.length) != null ? _ref1 : 0) > 6 || group.to || group.kind === 'cards' && variant !== 'cards' && variant !== 'ruled') {
1457
+ return null;
1458
+ }
1459
+ return splitSection(scope, head, group, tail);
1460
+ }
1461
+ /** The first section, in the site's hero arrangement. */ function hero(scope, raw, flow, picture) {
1462
+ const { page } = scope;
1463
+ const tree = page.tree;
1464
+ const design = page.design;
1465
+ const home = design.input.home;
1466
+ const variant = design.choices.hero;
1467
+ page.settled.push({
1468
+ at: scope.at,
1469
+ what: `drawn as a ${variant} hero`
1470
+ });
1471
+ if (variant === 'cover') {
1472
+ scope.band = 'dark';
1473
+ scope.centered = raw.align === 'center';
1474
+ return cover(scope, flow, picture, home ? {
1475
+ xs: '78vh',
1476
+ md: '86vh'
1477
+ } : {
1478
+ xs: '46vh',
1479
+ md: '54vh'
1480
+ });
1481
+ }
1482
+ const band = scope.band;
1483
+ scope.centered = false;
1484
+ if (variant === 'split') {
1485
+ const words = compileFlow(scope, flow, 'half');
1486
+ const photo = designPicture(scope, picture, {
1487
+ aspectRatio: home ? {
1488
+ xs: '4 / 3',
1489
+ md: '4 / 5'
1490
+ } : {
1491
+ xs: '4 / 3',
1492
+ md: '1 / 1'
1493
+ },
1494
+ borderRadius: 2
1495
+ });
1496
+ const right = design.choices.featureLeft;
1497
+ const row = designRow(scope, [
1498
+ _extends({
1499
+ id: words,
1500
+ size: 'xs:12 md:6'
1501
+ }, right ? {
1502
+ sx: {
1503
+ order: {
1504
+ xs: 0,
1505
+ md: 1
1506
+ }
1507
+ }
1508
+ } : {}),
1509
+ {
1510
+ id: photo,
1511
+ size: 'xs:12 md:6'
1512
+ }
1513
+ ], '8');
1514
+ const container = tree.add('muiContainer', {
1515
+ maxWidth: 'lg'
1516
+ }, {
1517
+ py: {
1518
+ xs: 6,
1519
+ md: 10
1520
+ }
1521
+ }, [
1522
+ row
1523
+ ], 'container');
1524
+ return designedRoot(scope, [
1525
+ container
1526
+ ], bandSx(band), band === 'dark');
1527
+ }
1528
+ // Editorial: display type across the page, the rest in a row under it, a wide photo below.
1529
+ const top = flow.filter((block)=>block.kind === 'eyebrow' || block.kind === 'heading');
1530
+ const rest = flow.filter((block)=>block.kind !== 'eyebrow' && block.kind !== 'heading');
1531
+ const lede = rest.filter((block)=>block.kind !== 'button');
1532
+ const actions = rest.filter((block)=>block.kind === 'button');
1533
+ const headId = compileFlow(scope, top, 'full');
1534
+ const ledeId = compileFlow(scope, lede, 'half');
1535
+ const actionsId = compileFlow(scope, actions, 'half');
1536
+ const under = ledeId && actionsId ? designRow(scope, [
1537
+ {
1538
+ id: ledeId,
1539
+ size: 'xs:12 md:7'
1540
+ },
1541
+ {
1542
+ id: actionsId,
1543
+ size: 'xs:12 md:5',
1544
+ sx: {
1545
+ display: 'flex',
1546
+ justifyContent: {
1547
+ xs: 'flex-start',
1548
+ md: 'flex-end'
1549
+ }
1550
+ }
1551
+ }
1552
+ ], '4', 'flex-end') : ledeId != null ? ledeId : actionsId;
1553
+ const photo = designPicture(scope, picture, {
1554
+ aspectRatio: home ? {
1555
+ xs: '4 / 3',
1556
+ md: '21 / 9'
1557
+ } : {
1558
+ xs: '16 / 9',
1559
+ md: '3 / 1'
1560
+ },
1561
+ borderRadius: 2
1562
+ });
1563
+ const stack = tree.add('muiStack', {
1564
+ spacing: '6',
1565
+ useFlexGap: true
1566
+ }, null, [
1567
+ headId,
1568
+ under,
1569
+ photo
1570
+ ], 'content');
1571
+ const container = tree.add('muiContainer', {
1572
+ maxWidth: 'lg'
1573
+ }, {
1574
+ pt: {
1575
+ xs: 8,
1576
+ md: 12
1577
+ },
1578
+ pb: {
1579
+ xs: 6,
1580
+ md: 10
1581
+ }
1582
+ }, [
1583
+ stack
1584
+ ], 'container');
1585
+ return designedRoot(scope, [
1586
+ container
1587
+ ], bandSx(band), band === 'dark');
1588
+ }
1589
+ /** Words over a full-bleed photo, on a scrim that keeps them readable, in the site's dark scheme. */ function cover(scope, flow, picture, minHeight) {
1590
+ const tree = scope.page.tree;
1591
+ // The photo dimmed over black: the scrim that keeps the words over it readable.
1592
+ const photo = designPicture(scope, picture, {
1593
+ position: 'absolute',
1594
+ top: 0,
1595
+ left: 0,
1596
+ width: '100%',
1597
+ height: '100%',
1598
+ bgcolor: 'common.black'
1599
+ }, 'coverPhoto', {
1600
+ opacity: 0.42
1601
+ });
1602
+ scope.overPhoto = true;
1603
+ const words = compileFlow(scope, flow, 'full');
1604
+ const measure = tree.add('muiBox', null, scope.centered ? {
1605
+ maxWidth: {
1606
+ xs: '100%',
1607
+ md: '75%'
1608
+ },
1609
+ mx: 'auto'
1610
+ } : {
1611
+ maxWidth: {
1612
+ xs: '100%',
1613
+ md: '62%'
1614
+ }
1615
+ }, [
1616
+ words
1617
+ ], 'measure');
1618
+ const container = tree.add('muiContainer', {
1619
+ maxWidth: 'lg'
1620
+ }, {
1621
+ position: 'relative',
1622
+ zIndex: 1,
1623
+ py: {
1624
+ xs: 10,
1625
+ md: 14
1626
+ }
1627
+ }, [
1628
+ measure
1629
+ ], 'container');
1630
+ return designedRoot(scope, [
1631
+ photo,
1632
+ container
1633
+ ], {
1634
+ position: 'relative',
1635
+ overflow: 'hidden',
1636
+ display: 'flex',
1637
+ alignItems: {
1638
+ xs: 'flex-end',
1639
+ md: 'center'
1640
+ },
1641
+ minHeight
1642
+ }, true);
1643
+ }
1644
+ /** A closing section of words over a photo. */ function coverBand(scope, words) {
1645
+ scope.band = 'dark';
1646
+ scope.centered = true;
1647
+ scope.page.settled.push({
1648
+ at: scope.at,
1649
+ what: 'drawn over a photo'
1650
+ });
1651
+ return cover(scope, words, {
1652
+ alt: standInAlt(scope),
1653
+ given: false
1654
+ }, {
1655
+ xs: '56vh',
1656
+ md: '64vh'
1657
+ });
1658
+ }
1659
+ /** A section of words beside a picture, the side turning section by section. */ function feature(scope, words) {
1660
+ const { page } = scope;
1661
+ const design = page.design;
1662
+ const left = design.choices.featureLeft !== (page.features % 2 === 1);
1663
+ page.features += 1;
1664
+ page.settled.push({
1665
+ at: scope.at,
1666
+ what: `set beside a picture on the ${left ? 'left' : 'right'}`
1667
+ });
1668
+ scope.centered = false;
1669
+ const flow = compileFlow(scope, words, 'half');
1670
+ const photo = designPicture(scope, {
1671
+ alt: standInAlt(scope),
1672
+ given: false
1673
+ }, {
1674
+ aspectRatio: {
1675
+ xs: '4 / 3',
1676
+ md: page.features === 1 ? '4 / 5' : '1 / 1'
1677
+ },
1678
+ borderRadius: 2
1679
+ });
1680
+ const row = designRow(scope, left ? [
1681
+ {
1682
+ id: photo,
1683
+ size: 'xs:12 md:6'
1684
+ },
1685
+ {
1686
+ id: flow,
1687
+ size: 'xs:12 md:6'
1688
+ }
1689
+ ] : [
1690
+ {
1691
+ id: flow,
1692
+ size: 'xs:12 md:6'
1693
+ },
1694
+ {
1695
+ id: photo,
1696
+ size: 'xs:12 md:6'
1697
+ }
1698
+ ], '8');
1699
+ const container = page.tree.add('muiContainer', {
1700
+ maxWidth: 'lg'
1701
+ }, {
1702
+ py: _extends({}, SECTION_PADDING)
1703
+ }, [
1704
+ row
1705
+ ], 'container');
1706
+ return designedRoot(scope, [
1707
+ container
1708
+ ], bandSx(scope.band), scope.band === 'dark');
1709
+ }
1710
+ /** A heading beside its items: the words in a narrow column, the items ruled in the wide one. */ function splitSection(scope, head, group, tail) {
1711
+ const { page } = scope;
1712
+ scope.centered = false;
1713
+ scope.groupStyle = 'ruled';
1714
+ page.splitAt = scope.index;
1715
+ page.settled.push({
1716
+ at: scope.at,
1717
+ what: 'the heading set beside its items'
1718
+ });
1719
+ const words = compileFlow(scope, [
1720
+ ...head,
1721
+ ...tail
1722
+ ], 'half');
1723
+ const items = compileFlow(scope, [
1724
+ group
1725
+ ], 'half');
1726
+ if (!words || !items) {
1727
+ const flow = [
1728
+ words,
1729
+ items
1730
+ ].filter((id)=>!!id);
1731
+ const container = page.tree.add('muiContainer', {
1732
+ maxWidth: 'lg'
1733
+ }, {
1734
+ py: _extends({}, SECTION_PADDING)
1735
+ }, flow, 'container');
1736
+ return designedRoot(scope, [
1737
+ container
1738
+ ], bandSx(scope.band), scope.band === 'dark');
1739
+ }
1740
+ const row = designRow(scope, [
1741
+ {
1742
+ id: words,
1743
+ size: 'xs:12 md:5'
1744
+ },
1745
+ {
1746
+ id: items,
1747
+ size: 'xs:12 md:7'
1748
+ }
1749
+ ], '8', 'flex-start');
1750
+ const container = page.tree.add('muiContainer', {
1751
+ maxWidth: 'lg'
1752
+ }, {
1753
+ py: _extends({}, SECTION_PADDING)
1754
+ }, [
1755
+ row
1756
+ ], 'container');
1757
+ return designedRoot(scope, [
1758
+ container
1759
+ ], bandSx(scope.band), scope.band === 'dark');
1760
+ }
1761
+ /**
1762
+ * Items under rules, one a line, each opening under a thin rule in the
1763
+ * text's own color. One style key an element, so a long list repeats no
1764
+ * multi-key inline style (rule 16); and no Stack `divider`, which a
1765
+ * published page's renderer hands a Stack as one child and so never draws.
1766
+ */ function ruledList(scope, ids, role) {
1767
+ const tree = scope.page.tree;
1768
+ return tree.add('muiStack', {
1769
+ spacing: '2'
1770
+ }, null, ids.map((id)=>tree.add('muiBox', null, {
1771
+ borderTop: 1
1772
+ }, [
1773
+ tree.add('muiBox', null, {
1774
+ pt: 2
1775
+ }, [
1776
+ id
1777
+ ], 'ruledWords')
1778
+ ], 'ruledLine')), role);
1779
+ }
1780
+ /** Numbered items as a ruled list: each number beside its title and words. */ function timeline(scope, items) {
1781
+ const tree = scope.page.tree;
1782
+ const facts = scope.page.targets.facts;
1783
+ const rows = items.map((item, position)=>tree.add('muiStack', {
1784
+ direction: 'row',
1785
+ spacing: '3',
1786
+ alignItems: 'baseline'
1787
+ }, null, [
1788
+ // The numbers keep one column, so every title starts at the same line.
1789
+ tree.add('muiBox', null, {
1790
+ flex: '0 0 3.5rem'
1791
+ }, [
1792
+ tree.add('muiTypography', {
1793
+ children: String(position + 1).padStart(2, '0'),
1794
+ variant: 'h4',
1795
+ component: 'p'
1796
+ }, accent(scope), null, 'number')
1797
+ ], 'numberColumn'),
1798
+ tree.add('muiStack', {
1799
+ spacing: '1'
1800
+ }, null, [
1801
+ item.title.trim() ? tree.add('muiTypography', {
1802
+ children: aiLayoutWords(item.title, 'itemTitle', facts),
1803
+ variant: 'h5',
1804
+ component: itemElement(scope)
1805
+ }, null, null, 'title') : null,
1806
+ item.text.trim() ? tree.add('muiTypography', {
1807
+ children: aiLayoutWords(item.text, 'itemText', facts),
1808
+ variant: 'body1'
1809
+ }, muted(scope), null, 'text') : null
1810
+ ], 'stepWords')
1811
+ ], 'step'));
1812
+ return ruledList(scope, noted(scope, rows), 'timeline');
1813
+ }
1814
+ /** A group drawn the design's way; `null` to draw it as the theme's cards. */ function designedGroup(scope, variant, items, room) {
1815
+ const { page } = scope;
1816
+ const tree = page.tree;
1817
+ const facts = page.targets.facts;
1818
+ const title = (item, type = 'h5')=>item.title.trim() ? tree.add('muiTypography', {
1819
+ children: aiLayoutWords(item.title, 'itemTitle', facts),
1820
+ variant: type,
1821
+ component: itemElement(scope)
1822
+ }, null, null, 'title') : null;
1823
+ const words = (item, type = 'body1')=>item.text.trim() ? tree.add('muiTypography', {
1824
+ children: aiLayoutWords(item.text, 'itemText', facts),
1825
+ variant: type
1826
+ }, muted(scope), null, 'text') : null;
1827
+ if (variant === 'cards') return null;
1828
+ const pictured = variant === 'pictures' || variant === 'articles';
1829
+ if (variant === 'ruled' || pictured && (room !== 'full' || picturesLeft(page) < items.length)) {
1830
+ const perRow = across(items.length, room, 'cards');
1831
+ if (perRow <= 1) {
1832
+ // Stacked, the rules run between the items.
1833
+ const ids = items.map((item)=>tree.add('muiStack', {
1834
+ spacing: '1'
1835
+ }, null, [
1836
+ title(item),
1837
+ words(item)
1838
+ ], 'ruled'));
1839
+ return ruledList(scope, noted(scope, ids), 'ruledItems');
1840
+ }
1841
+ // Side by side, each column opens under a rule in the text's own color.
1842
+ const ids = items.map((item)=>tree.add('muiBox', null, {
1843
+ borderTop: 1
1844
+ }, [
1845
+ tree.add('muiStack', {
1846
+ spacing: '1'
1847
+ }, {
1848
+ pt: 2.5
1849
+ }, [
1850
+ title(item),
1851
+ words(item)
1852
+ ], 'ruledWords')
1853
+ ], 'ruled'));
1854
+ return layOut(scope, noted(scope, ids), perRow, '5', 'ruledItems');
1855
+ }
1856
+ if (variant === 'menu') {
1857
+ const ids = items.map((item)=>tree.add('muiStack', {
1858
+ spacing: '0.5'
1859
+ }, null, [
1860
+ title(item, 'h6'),
1861
+ words(item, 'body2')
1862
+ ], 'dish'));
1863
+ noted(scope, ids);
1864
+ // Two columns of lines under rules, the first half and the second.
1865
+ const half = room === 'full' && ids.length >= 4 ? Math.ceil(ids.length / 2) : ids.length;
1866
+ const lists = [
1867
+ ids.slice(0, half),
1868
+ ids.slice(half)
1869
+ ].filter((list)=>list.length).map((list)=>ruledList(scope, list, 'menu'));
1870
+ if (lists.length === 1) return lists[0];
1871
+ // On a phone the two columns read as one list, the second opening under
1872
+ // its own rule a line's gap below the first; side by side, a gutter.
1873
+ const [first, second] = lists;
1874
+ return tree.add('muiGrid', {
1875
+ container: true,
1876
+ rowSpacing: '2',
1877
+ columnSpacing: '8'
1878
+ }, {
1879
+ alignItems: 'flex-start'
1880
+ }, [
1881
+ tree.add('muiGrid', {
1882
+ size: 'xs:12 md:6'
1883
+ }, null, [
1884
+ first
1885
+ ], 'column'),
1886
+ tree.add('muiGrid', {
1887
+ size: 'xs:12 md:6'
1888
+ }, null, [
1889
+ second
1890
+ ], 'column')
1891
+ ], 'menuRow');
1892
+ }
1893
+ // Pictures over each item's title and words.
1894
+ const design = page.design;
1895
+ const mosaic = variant === 'pictures' && design.choices.pictures === 'mosaic' && items.length >= 2;
1896
+ const portrait = design.input.kind === 'photography';
1897
+ const perRow = items.length === 4 || items.length === 2 ? 2 : Math.min(3, items.length);
1898
+ page.settled.push({
1899
+ at: scope.at,
1900
+ what: `${items.length} items drawn as ${mosaic ? 'a mosaic of pictures' : variant}`
1901
+ });
1902
+ const cells = items.map((item, position)=>{
1903
+ // A mosaic's rows turn wide-narrow, narrow-wide; an odd one out spans the row.
1904
+ const row = Math.floor(position / 2);
1905
+ const lone = mosaic && position === items.length - 1 && items.length % 2 === 1;
1906
+ const wide = mosaic && !lone && position % 2 === row % 2;
1907
+ const span = !mosaic ? 12 / perRow : lone ? 12 : wide ? 7 : 5;
1908
+ const aspect = !mosaic ? variant === 'articles' ? '3 / 2' : portrait ? '4 / 5' : '4 / 3' : lone ? '21 / 9' : wide ? '4 / 3' : '4 / 5';
1909
+ // The picture is its own frame: its shape its one style, so a grid of them repeats no inline style.
1910
+ const photo = designImage(scope, {
1911
+ alt: standInAlt(scope, item.title || item.text),
1912
+ given: false
1913
+ }, {
1914
+ width: '100%'
1915
+ }, {
1916
+ aspectRatio: {
1917
+ xs: '4 / 3',
1918
+ md: aspect
1919
+ }
1920
+ });
1921
+ const id = tree.add('muiStack', {
1922
+ spacing: '1.5'
1923
+ }, null, [
1924
+ photo,
1925
+ title(item, variant === 'articles' ? 'h5' : 'h6'),
1926
+ words(item, 'body2')
1927
+ ], variant === 'articles' ? 'article' : 'work');
1928
+ return {
1929
+ id,
1930
+ size: span === 12 ? 'xs:12 sm:12' : `xs:12 ${span === 6 ? 'sm' : 'md'}:${span}`
1931
+ };
1932
+ });
1933
+ noted(scope, cells.map((cell)=>cell.id));
1934
+ return tree.add('muiGrid', {
1935
+ container: true,
1936
+ spacing: mosaic ? '5' : '4'
1937
+ }, {
1938
+ alignItems: 'flex-start'
1939
+ }, cells.map((cell)=>tree.add('muiGrid', {
1940
+ size: cell.size
1941
+ }, null, [
1942
+ cell.id
1943
+ ], 'cell')), variant);
1944
+ }
1945
+ // ── A section the site's records fill (AGL-3676) ──────────────────────────
1946
+ //
1947
+ // The section's words — its eyebrow, heading and lede — are the design's;
1948
+ // what it lists is the site's: its products through the commerce plugin's
1949
+ // Product grid, its posts through the content plugin's Collection Entries.
1950
+ // Any group the design drew for those records (cards naming products, a list
1951
+ // of post titles) stands in for them, and is left out.
1952
+ /** The block kinds a listing stands in for: the records the design described in words. */ const LISTING_REPLACES = new Set([
1953
+ 'cards',
1954
+ 'steps',
1955
+ 'stats',
1956
+ 'quotes',
1957
+ 'list',
1958
+ 'image',
1959
+ 'component'
1960
+ ]);
1961
+ /** The tokens a post's card binds, filled per post when the page renders. */ export const AI_LAYOUT_POST_CARD_TOKENS = [
1962
+ '{{entry.title}}',
1963
+ '{{entry.excerpt}}',
1964
+ '{{entry.date}}',
1965
+ '{{entry.author}}',
1966
+ '{{entry.url}}',
1967
+ '{{entry.coverImage}}'
1968
+ ];
1969
+ /** The tokens a post's card puts in a link or a picture, which the page's store admits whole. */ export const AI_LAYOUT_POST_ADDRESS_TOKENS = [
1970
+ '{{entry.url}}',
1971
+ '{{entry.coverImage}}'
1972
+ ];
1973
+ /** The button under a featured band that opens the whole list, where the site has one and the design gave none. */ function listingAllButton(page, listing) {
1974
+ if (listing.kind === 'posts') return listing.href ? {
1975
+ kind: 'button',
1976
+ text: 'All posts',
1977
+ to: listing.href,
1978
+ style: 'secondary'
1979
+ } : null;
1980
+ const index = listing.placements.find((placement)=>placement.role === 'index' && placement.screenId !== page.targets.pageId);
1981
+ return index ? {
1982
+ kind: 'button',
1983
+ text: 'Shop all',
1984
+ to: `page:${index.screenId}`,
1985
+ style: 'secondary'
1986
+ } : null;
1987
+ }
1988
+ function listingSection(scope, blocks, placed) {
1989
+ var _ref;
1990
+ var _page_plan_sections_index_name, _page_plan_sections_index, _page_options_sectionIds;
1991
+ const { page, index, at } = scope;
1992
+ const { listing, role } = placed;
1993
+ const left = blocks.filter((block)=>LISTING_REPLACES.has(block.kind));
1994
+ if (left.length) {
1995
+ page.settled.push({
1996
+ at,
1997
+ what: `${left.map((block)=>block.kind).join(', ')} left out: the section lists the site's own ${listing.kind}`
1998
+ });
1999
+ }
2000
+ const kept = blocks.filter((block)=>!LISTING_REPLACES.has(block.kind)).map((_0)=>{
2001
+ let { col: _col } = _0, block = _object_without_properties_loose(_0, [
2002
+ "col"
2003
+ ]);
2004
+ return block;
2005
+ });
2006
+ const words = kept.filter((block)=>block.kind !== 'button' && block.kind !== 'form');
2007
+ let actions = kept.filter((block)=>block.kind === 'button');
2008
+ if (!actions.length && role === 'featured') {
2009
+ const all = listingAllButton(page, listing);
2010
+ if (all) actions = [
2011
+ all
2012
+ ];
2013
+ }
2014
+ if (!words.some((block)=>speaksHeading(scope, block))) {
2015
+ var _page_plan_sections_index1;
2016
+ // Every band of records is headed: the plan's name for it, or on a first section the page's title.
2017
+ words.unshift({
2018
+ kind: 'heading',
2019
+ text: index === 0 ? page.plan.title : ((_page_plan_sections_index1 = page.plan.sections[index]) == null ? void 0 : _page_plan_sections_index1.name) || page.plan.title
2020
+ });
2021
+ }
2022
+ page.settled.push({
2023
+ at,
2024
+ what: `lists the site's ${listing.kind} (${role})`
2025
+ });
2026
+ const tree = page.tree;
2027
+ const centered = scope.centered;
2028
+ const head = compileFlow(scope, words, centered ? 'full' : 'half');
2029
+ const link = compileFlow(scope, actions, 'half');
2030
+ // An editorial band's head: the words on the left, the way to the whole list on the right.
2031
+ const top = head && link && !centered ? designRow(scope, [
2032
+ {
2033
+ id: head,
2034
+ size: 'xs:12 md:8'
2035
+ },
2036
+ {
2037
+ id: link,
2038
+ size: 'xs:12 md:4',
2039
+ sx: {
2040
+ display: 'flex',
2041
+ justifyContent: {
2042
+ xs: 'flex-start',
2043
+ md: 'flex-end'
2044
+ }
2045
+ }
2046
+ }
2047
+ ], '3', 'flex-end') : head;
2048
+ const records = listingElement(scope, listing, role);
2049
+ const parts = [
2050
+ top ? centered ? tree.add('muiContainer', {
2051
+ maxWidth: 'md',
2052
+ disableGutters: true
2053
+ }, null, [
2054
+ top
2055
+ ], 'measure') : top : null,
2056
+ records,
2057
+ centered || !head ? link : null
2058
+ ].filter((id)=>!!id);
2059
+ const content = tree.add('muiStack', _extends({
2060
+ spacing: '6',
2061
+ useFlexGap: true
2062
+ }, centered ? {
2063
+ alignItems: 'center'
2064
+ } : {}), null, parts, 'content');
2065
+ const container = tree.add('muiContainer', {
2066
+ maxWidth: 'lg'
2067
+ }, {
2068
+ py: index === 0 ? _extends({}, HERO_PADDING) : _extends({}, SECTION_PADDING)
2069
+ }, [
2070
+ content
2071
+ ], 'container');
2072
+ const name = ((_page_plan_sections_index = page.plan.sections[index]) == null ? void 0 : (_page_plan_sections_index_name = _page_plan_sections_index.name) == null ? void 0 : _page_plan_sections_index_name.trim()) || `Section ${index + 1}`;
2073
+ return tree.add('section', _extends({
2074
+ element: 'section',
2075
+ ariaLabel: sectionLabel(name, index)
2076
+ }, scope.band === 'dark' ? {
2077
+ colorScheme: 'dark'
2078
+ } : {}), bandSx(scope.band), [
2079
+ container
2080
+ ], 'section', (_ref = (_page_options_sectionIds = page.options.sectionIds) == null ? void 0 : _page_options_sectionIds[index]) != null ? _ref : sectionIdOf(index));
2081
+ }
2082
+ /**
2083
+ * The element that lists the records, bound to them: the Product grid over
2084
+ * the site's catalog, or Collection Entries over its blog with one post's
2085
+ * card as the template it repeats. Its records count as the section's items.
2086
+ */ function listingElement(scope, listing, role) {
2087
+ const { page } = scope;
2088
+ const tree = page.tree;
2089
+ const featured = role === 'featured';
2090
+ const shown = featured ? AI_LAYOUT_FEATURED_RECORDS[listing.kind] : 12;
2091
+ const element = AI_LAYOUT_LISTING_ELEMENTS[listing.kind];
2092
+ let id;
2093
+ if (listing.kind === 'products') {
2094
+ id = tree.add(element, _extends({
2095
+ source: 'all',
2096
+ sort: 'newest',
2097
+ columns: String(featured ? Math.min(AI_LAYOUT_FEATURED_RECORDS.products, Math.max(3, listing.records.length)) : 3)
2098
+ }, featured ? {
2099
+ maxItems: String(shown)
2100
+ } : {
2101
+ pageSize: String(shown)
2102
+ }, {
2103
+ cardStyle: 'photo',
2104
+ emptyText: 'New products are on their way.'
2105
+ }), null, null, 'products');
2106
+ } else {
2107
+ id = postsElement(scope, listing, featured ? shown : COLLECTION_INDEX_POSTS);
2108
+ }
2109
+ // What a visitor sees here is the site's records, however many it keeps:
2110
+ // the section shows its planned items when it has any to show.
2111
+ noted(scope, Array.from({
2112
+ length: Math.max(2, Math.min(shown, listing.records.length))
2113
+ }, ()=>id));
2114
+ return id;
2115
+ }
2116
+ /** The most posts a page of the site lists where it is the writing's own page. */ const COLLECTION_INDEX_POSTS = 9;
2117
+ /** The blog's posts as the site's design lists them: a grid of covers, or a ruled list with the dates. */ function postsElement(scope, listing, limit) {
2118
+ var _page_design;
2119
+ const { page } = scope;
2120
+ const tree = page.tree;
2121
+ const ruled = ((_page_design = page.design) == null ? void 0 : _page_design.choices.writing) === 'ruled';
2122
+ const titleElement = itemElement(scope);
2123
+ const cover = (sx)=>tree.add('image', {
2124
+ src: '{{entry.coverImage}}',
2125
+ alt: '{{entry.title}}',
2126
+ href: '{{entry.url}}',
2127
+ objectFit: 'cover',
2128
+ width: '100%',
2129
+ loading: 'lazy'
2130
+ }, sx, null, 'postCover');
2131
+ const meta = tree.add('muiTypography', {
2132
+ children: '{{entry.date}} · {{entry.author}}',
2133
+ variant: 'caption',
2134
+ component: 'p'
2135
+ }, muted(scope), null, 'postMeta');
2136
+ const title = tree.add('muiTypography', {
2137
+ children: '{{entry.title}}',
2138
+ variant: ruled ? 'h5' : 'h6',
2139
+ component: titleElement
2140
+ }, null, null, 'postTitle');
2141
+ const excerpt = tree.add('muiTypography', {
2142
+ children: '{{entry.excerpt}}',
2143
+ variant: 'body2'
2144
+ }, muted(scope), null, 'postExcerpt');
2145
+ const more = tree.add('muiScreenLink', {
2146
+ children: 'Read the post',
2147
+ href: '{{entry.url}}',
2148
+ renderAs: 'link',
2149
+ color: 'inherit'
2150
+ }, {
2151
+ alignSelf: 'flex-start'
2152
+ }, null, 'postLink');
2153
+ page.settled.push({
2154
+ at: scope.at,
2155
+ what: `posts listed as ${ruled ? 'a ruled list' : 'a grid of covers'}`
2156
+ });
2157
+ const card = ruled ? tree.add('muiBox', null, {
2158
+ borderTop: 1,
2159
+ pt: 3
2160
+ }, [
2161
+ tree.add('muiStack', {
2162
+ direction: 'row',
2163
+ spacing: '3',
2164
+ alignItems: 'flex-start'
2165
+ }, null, [
2166
+ tree.add('muiStack', {
2167
+ spacing: '1'
2168
+ }, {
2169
+ flex: '1 1 auto',
2170
+ minWidth: 0
2171
+ }, [
2172
+ meta,
2173
+ title,
2174
+ excerpt,
2175
+ more
2176
+ ], 'postWords'),
2177
+ tree.add('muiBox', null, {
2178
+ flex: '0 0 auto',
2179
+ width: {
2180
+ xs: '30%',
2181
+ md: '24%'
2182
+ }
2183
+ }, [
2184
+ cover({
2185
+ aspectRatio: '4 / 3',
2186
+ borderRadius: 2
2187
+ })
2188
+ ], 'postThumb')
2189
+ ], 'postRow')
2190
+ ], 'post') : tree.add('muiStack', {
2191
+ spacing: '1.5'
2192
+ }, null, [
2193
+ cover({
2194
+ aspectRatio: '3 / 2',
2195
+ borderRadius: 2
2196
+ }),
2197
+ meta,
2198
+ title,
2199
+ excerpt,
2200
+ more
2201
+ ], 'post');
2202
+ return tree.add(AI_LAYOUT_LISTING_ELEMENTS.posts, {
2203
+ collectionSlug: listing.collectionSlug,
2204
+ entriesLimit: String(limit),
2205
+ spacing: ruled ? '3' : '0'
2206
+ }, ruled ? null : {
2207
+ display: 'grid',
2208
+ gridTemplateColumns: {
2209
+ xs: '1fr',
2210
+ md: 'repeat(3, 1fr)'
2211
+ },
2212
+ gap: 5
2213
+ }, [
2214
+ card
2215
+ ], 'posts');
2216
+ }
1233
2217
  /** The id the compiled page's root is stored under. */ export const AI_LAYOUT_PAGE_ROOT_ID = CANVAS_ROOT_ELEMENT_ID;
1234
2218
 
1235
2219
  //# sourceMappingURL=ai-layout-compiler.js.map