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

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 (104) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-job-links.d.ts +6 -0
  3. package/src/lib/components/ai-job-links.js +9 -0
  4. package/src/lib/components/ai-job-links.js.map +1 -1
  5. package/src/lib/components/ai-site-build-page.component.js +41 -3
  6. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  7. package/src/lib/components/ai-site-start-card.component.js +91 -8
  8. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  9. package/src/lib/jobs/ai-job-admission.d.ts +2 -1
  10. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  11. package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
  12. package/src/lib/jobs/ai-job-email-step.js +116 -5
  13. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  14. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  15. package/src/lib/jobs/ai-job-layout-language.js +3 -1
  16. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  17. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +2 -0
  18. package/src/lib/jobs/ai-job-layout-site-pages.js +23 -11
  19. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  20. package/src/lib/jobs/ai-job-layout-step.js +3 -2
  21. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  22. package/src/lib/jobs/ai-job-page-language.d.ts +16 -0
  23. package/src/lib/jobs/ai-job-page-language.js +35 -6
  24. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  25. package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
  26. package/src/lib/jobs/ai-job-page-step.js +18 -4
  27. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  28. package/src/lib/jobs/ai-job-plan-step.d.ts +34 -0
  29. package/src/lib/jobs/ai-job-plan-step.js +106 -14
  30. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  31. package/src/lib/jobs/ai-job-site-content.d.ts +6 -6
  32. package/src/lib/jobs/ai-job-site-content.js +6 -12
  33. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  34. package/src/lib/jobs/ai-job-site-step.d.ts +11 -0
  35. package/src/lib/jobs/ai-job-site-step.js +96 -44
  36. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  37. package/src/lib/jobs/ai-jobs.d.ts +8 -4
  38. package/src/lib/jobs/ai-jobs.js +19 -2
  39. package/src/lib/jobs/ai-jobs.js.map +1 -1
  40. package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
  41. package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
  42. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
  43. package/src/lib/jobs/ai-site-publish.d.ts +11 -0
  44. package/src/lib/jobs/ai-site-publish.js +31 -1
  45. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  46. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
  47. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
  48. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
  49. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
  50. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
  51. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
  52. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
  53. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
  54. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
  55. package/src/lib/layout-language/ai-layout-compiler.d.ts +29 -0
  56. package/src/lib/layout-language/ai-layout-compiler.js +698 -12
  57. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  58. package/src/lib/layout-language/ai-layout-design.d.ts +107 -0
  59. package/src/lib/layout-language/ai-layout-design.js +197 -0
  60. package/src/lib/layout-language/ai-layout-design.js.map +1 -0
  61. package/src/lib/layout-language/ai-layout-frame.d.ts +6 -0
  62. package/src/lib/layout-language/ai-layout-frame.js +22 -6
  63. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  64. package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
  65. package/src/lib/layout-language/ai-layout-language.js +15 -1
  66. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  67. package/src/lib/layout-language/ai-layout-links.d.ts +11 -0
  68. package/src/lib/layout-language/ai-layout-links.js +4 -0
  69. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  70. package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
  71. package/src/lib/layout-language/ai-layout-pictures.js +80 -17
  72. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  73. package/src/lib/model/ai-job-notice.d.ts +9 -1
  74. package/src/lib/model/ai-job-notice.js +23 -0
  75. package/src/lib/model/ai-job-notice.js.map +1 -1
  76. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  77. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  78. package/src/lib/model/ai-site-build-progress.d.ts +12 -2
  79. package/src/lib/model/ai-site-build-progress.js +78 -4
  80. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  81. package/src/lib/model/ai-site-job.d.ts +209 -22
  82. package/src/lib/model/ai-site-job.js +247 -20
  83. package/src/lib/model/ai-site-job.js.map +1 -1
  84. package/src/lib/model/ai-site-kinds.js +6 -1
  85. package/src/lib/model/ai-site-kinds.js.map +1 -1
  86. package/src/lib/model/ai-site-look.d.ts +21 -0
  87. package/src/lib/model/ai-site-look.js +63 -14
  88. package/src/lib/model/ai-site-look.js.map +1 -1
  89. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -1
  90. package/src/lib/runtime/ai-doctrine-validators.js +227 -4
  91. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  92. package/src/lib/runtime/ai-doctrine.js +5 -3
  93. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  94. package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
  95. package/src/lib/runtime/ai-palette.generated.js +18 -2
  96. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  97. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  98. package/src/lib/server/ai-jobs-route.js +15 -3
  99. package/src/lib/server/ai-jobs-route.js.map +1 -1
  100. package/src/lib/server/assist-edit.js +5 -2
  101. package/src/lib/server/assist-edit.js.map +1 -1
  102. package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
  103. package/src/lib/usage/free-ai-credits-left.js +62 -0
  104. package/src/lib/usage/free-ai-credits-left.js.map +1 -0
@@ -22,6 +22,7 @@ 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 { aiLayoutDesignChoices, aiLayoutGroupVariant } from "./ai-layout-design.js";
25
26
  import { aiLayoutResolveLink } from "./ai-layout-links.js";
26
27
  import { AI_LAYOUT_FRAMES, AiLayoutTreeBuilder } from "./ai-layout-tree.js";
27
28
  /** A section root's id where the caller gives none. */ function sectionIdOf(index) {
@@ -69,7 +70,14 @@ const componentOf = (targets, id)=>{
69
70
  formSection: formSectionOf(sections, plan, targets),
70
71
  pageIcon: pageIconOf(sections),
71
72
  formsPlaced: new Set(),
72
- itemIds: plan.sections.map(()=>[])
73
+ itemIds: plan.sections.map(()=>[]),
74
+ design: options.design ? {
75
+ input: options.design,
76
+ choices: aiLayoutDesignChoices(options.design)
77
+ } : null,
78
+ pictures: 0,
79
+ features: 0,
80
+ splitAt: -2
73
81
  };
74
82
  const roots = plan.sections.map((_, index)=>{
75
83
  var _sections_index;
@@ -106,8 +114,8 @@ const componentOf = (targets, id)=>{
106
114
  }
107
115
  // ── A section ─────────────────────────────────────────────────────────────
108
116
  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;
117
+ var _raw_band, _ref, _ref1;
118
+ var _raw_blocks_find, _page_plan_sections_index, _placedHere_, _page_plan_sections_index_name, _page_plan_sections_index1, _page_options_sectionIds;
111
119
  const at = `sections[${index}]`;
112
120
  const band = settleBand(page, (_raw_band = raw.band) != null ? _raw_band : 'plain', at);
113
121
  const scope = {
@@ -123,7 +131,15 @@ function compileSection(page, raw, index) {
123
131
  sections: page.plan.sections.map((section)=>section.name),
124
132
  from: index,
125
133
  formSection: page.formSection
126
- }
134
+ },
135
+ heading: (_raw_blocks_find = raw.blocks.find((block)=>block.kind === 'heading')) == null ? void 0 : _raw_blocks_find.text,
136
+ words: [
137
+ (_ref = (_page_plan_sections_index = page.plan.sections[index]) == null ? void 0 : _page_plan_sections_index.name) != null ? _ref : '',
138
+ ...raw.blocks.filter((block)=>block.kind === 'heading' || block.kind === 'eyebrow').map((block)=>{
139
+ var _block_text;
140
+ return (_block_text = block.text) != null ? _block_text : '';
141
+ })
142
+ ].join(' ')
127
143
  };
128
144
  // A quote is a gap the owner fills, and a published page shows no gap (AGL-3660).
129
145
  const blocks = placePlanned(scope, raw).filter((block)=>{
@@ -134,6 +150,11 @@ function compileSection(page, raw, index) {
134
150
  });
135
151
  return false;
136
152
  });
153
+ // The site's design draws the sections it has an arrangement for (AGL-3660).
154
+ if (page.design) {
155
+ const designed = designSection(scope, raw, blocks);
156
+ if (designed) return designed;
157
+ }
137
158
  const cols = raw.cols && raw.cols.length >= 2 ? raw.cols : null;
138
159
  // Above the row, the row's columns, and below it.
139
160
  const head = [];
@@ -280,13 +301,13 @@ function compileSection(page, raw, index) {
280
301
  }
281
302
  }
282
303
  if (!parts.length) {
283
- var _page_plan_sections_index1;
304
+ var _page_plan_sections_index2;
284
305
  // Nothing the design gave this section could be shown: it keeps its
285
306
  // place on the page under the name its plan gives it.
286
307
  const fallback = compileFlow(scope, [
287
308
  {
288
309
  kind: 'heading',
289
- text: ((_page_plan_sections_index1 = page.plan.sections[index]) == null ? void 0 : _page_plan_sections_index1.name) || page.plan.title
310
+ text: ((_page_plan_sections_index2 = page.plan.sections[index]) == null ? void 0 : _page_plan_sections_index2.name) || page.plan.title
290
311
  }
291
312
  ], 'full');
292
313
  if (fallback) parts.push(fallback);
@@ -308,7 +329,7 @@ function compileSection(page, raw, index) {
308
329
  }, [
309
330
  content
310
331
  ], '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}`;
332
+ 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
333
  return tree.add('section', _extends({
313
334
  element: 'section',
314
335
  ariaLabel: aiLayoutFitText(name, 'note') || `Section ${index + 1}`
@@ -316,7 +337,7 @@ function compileSection(page, raw, index) {
316
337
  colorScheme: 'dark'
317
338
  } : {}), bandSx(band), [
318
339
  container
319
- ], 'section', (_ref = (_page_options_sectionIds = page.options.sectionIds) == null ? void 0 : _page_options_sectionIds[index]) != null ? _ref : sectionIdOf(index));
340
+ ], 'section', (_ref1 = (_page_options_sectionIds = page.options.sectionIds) == null ? void 0 : _page_options_sectionIds[index]) != null ? _ref1 : sectionIdOf(index));
320
341
  }
321
342
  /**
322
343
  * A component block's prop values as a card item: its title-like prop as the
@@ -650,12 +671,12 @@ function compileBlock(scope, block, room) {
650
671
  }
651
672
  }
652
673
  /** Secondary words' color: muted text, or the band's own on a brand band. */ function muted(scope) {
653
- return scope.band === 'brand' ? null : {
674
+ return scope.band === 'brand' || scope.overPhoto ? null : {
654
675
  color: 'text.secondary'
655
676
  };
656
677
  }
657
678
  /** 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 : {
679
+ return scope.band === 'brand' || scope.overPhoto ? null : {
659
680
  color: 'primary.main'
660
681
  };
661
682
  }
@@ -713,7 +734,8 @@ function compileButton(scope, block) {
713
734
  return null;
714
735
  }
715
736
  const style = (_block_style = block.style) != null ? _block_style : 'primary';
716
- const onBrand = scope.band === 'brand';
737
+ // Over a photo, a quiet or outlined button takes the words' own color, as on a brand band.
738
+ const onBrand = scope.band === 'brand' || !!scope.overPhoto && style !== 'primary';
717
739
  const look = style === 'quiet' ? {
718
740
  variant: 'text',
719
741
  color: onBrand ? 'inherit' : 'primary'
@@ -760,6 +782,9 @@ export function destinationProps(destination) {
760
782
  href: destination.href,
761
783
  target: '_blank'
762
784
  };
785
+ if (destination.kind === 'path') return {
786
+ href: destination.href
787
+ };
763
788
  return {};
764
789
  }
765
790
  /**
@@ -968,7 +993,24 @@ function group(scope, block, room) {
968
993
  const perRow = across(items.length, room, block.kind);
969
994
  const placed = componentOf(scope.page.targets, block.to);
970
995
  if (placed) return instances(scope, placed, items, perRow);
971
- if (rule1(scope)) return compactGroup(scope, block, items, perRow);
996
+ const design = scope.page.design;
997
+ if (rule1(scope)) {
998
+ var _scope_words;
999
+ // Picture cards the design draws are the compiler's, not a block typed out
1000
+ // by hand (`repeatsCompiled`, AGL-3660); every other repeat stays compact.
1001
+ const variant = design && block.kind === 'cards' && block.style !== 'quiet' ? aiLayoutGroupVariant(design.input, design.choices, (_scope_words = scope.words) != null ? _scope_words : '') : null;
1002
+ const pictured = (variant === 'pictures' || variant === 'articles') && room === 'full' && picturesLeft(scope.page) >= items.length;
1003
+ return pictured && designedGroup(scope, variant, items, room) || compactGroup(scope, block, items, perRow);
1004
+ }
1005
+ if (design && block.kind === 'steps' && (design.choices.steps === 'timeline' || room !== 'full')) {
1006
+ return timeline(scope, items);
1007
+ }
1008
+ if (design && block.kind === 'cards' && block.style !== 'quiet') {
1009
+ var _scope_groupStyle, _scope_words1;
1010
+ const variant = (_scope_groupStyle = scope.groupStyle) != null ? _scope_groupStyle : aiLayoutGroupVariant(design.input, design.choices, (_scope_words1 = scope.words) != null ? _scope_words1 : '');
1011
+ const drawn = designedGroup(scope, variant, items, room);
1012
+ if (drawn) return drawn;
1013
+ }
972
1014
  switch(block.kind){
973
1015
  case 'steps':
974
1016
  return layOut(scope, noted(scope, items.map((item, position)=>step(scope, item, position))), perRow, '4', 'steps');
@@ -1230,6 +1272,650 @@ function instances(scope, placed, items, perRow) {
1230
1272
  spacing: '1.5'
1231
1273
  }, null, noted(scope, panels), 'faq');
1232
1274
  }
1275
+ // ── The designer layer (AGL-3660) ─────────────────────────────────────────
1276
+ //
1277
+ // What `ai-layout-design.ts` chose for the site, drawn: the hero, a section
1278
+ // of words beside a picture, a closing band over a photo, a heading beside
1279
+ // its items, and groups drawn as pictures, articles, a menu or open ruled
1280
+ // columns. Every variant is built from the same palette elements and theme
1281
+ // tokens as the rest of the compiler, so the doctrine holds by construction.
1282
+ /** The most pictures a designed page carries, the model's and the stand-ins together. */ export const AI_LAYOUT_MAX_DESIGN_PICTURES = 8;
1283
+ /** The block kinds a section of words holds. */ const WORD_KINDS = new Set([
1284
+ 'eyebrow',
1285
+ 'heading',
1286
+ 'lede',
1287
+ 'text',
1288
+ 'note',
1289
+ 'button'
1290
+ ]);
1291
+ /** How many more pictures the page has room for. */ function picturesLeft(page) {
1292
+ return AI_LAYOUT_MAX_DESIGN_PICTURES - page.images - page.pictures;
1293
+ }
1294
+ /** Whether a heading block has words left once fitted: one with none is no heading. */ function speaksHeading(scope, block) {
1295
+ return block.kind === 'heading' && !!aiLayoutWords(block.text, 'heading', scope.page.targets.facts);
1296
+ }
1297
+ /** The section root every designed section is written under, as `compileSection` writes its own. */ function designedRoot(scope, children, sx, dark) {
1298
+ var _ref;
1299
+ var _page_plan_sections_index_name, _page_plan_sections_index, _page_options_sectionIds;
1300
+ const { page, index } = scope;
1301
+ 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}`;
1302
+ return page.tree.add('section', _extends({
1303
+ element: 'section',
1304
+ ariaLabel: aiLayoutFitText(name, 'note') || `Section ${index + 1}`
1305
+ }, dark ? {
1306
+ colorScheme: 'dark'
1307
+ } : {}, index === 0 && scope.overPhoto ? {
1308
+ underHeader: true
1309
+ } : {}), sx, children, 'section', (_ref = (_page_options_sectionIds = page.options.sectionIds) == null ? void 0 : _page_options_sectionIds[index]) != null ? _ref : sectionIdOf(index));
1310
+ }
1311
+ /**
1312
+ * A picture the design places: the model's own where it described one, else
1313
+ * a stand-in — marked decorative, since no one described it, with the
1314
+ * section's words as what a stock search looks for. Either is filled by
1315
+ * `ai-layout-pictures.ts` after the page is checked, as any slot is.
1316
+ */ function designPicture(scope, picture, frameSx, role = 'designFrame', imageSx = {}) {
1317
+ const tree = scope.page.tree;
1318
+ // The picture fills its frame by its own size props, so the frame alone
1319
+ // carries a style: a page of many pictures repeats no inline style (rule 16).
1320
+ const image = designImage(scope, picture, {
1321
+ width: '100%',
1322
+ height: '100%'
1323
+ }, Object.keys(imageSx).length ? imageSx : null);
1324
+ return tree.add('muiBox', null, _extends({
1325
+ position: 'relative',
1326
+ overflow: 'hidden',
1327
+ bgcolor: 'action.hover'
1328
+ }, frameSx), [
1329
+ image
1330
+ ], role);
1331
+ }
1332
+ /**
1333
+ * The `image` a designed picture is: its alt, its fit and, for a stand-in,
1334
+ * decorative. Its slot is filled after the page is checked.
1335
+ */ function designImage(scope, picture, props, sx) {
1336
+ const { page } = scope;
1337
+ if (picture.given) page.images += 1;
1338
+ else page.pictures += 1;
1339
+ return page.tree.add('image', _extends({
1340
+ alt: picture.alt,
1341
+ objectFit: 'cover'
1342
+ }, picture.given ? {} : {
1343
+ decorative: true
1344
+ }, props), sx, null, 'image');
1345
+ }
1346
+ /** What a stand-in picture is of, for a stock search: the section's subject, else the site's. */ function standInAlt(scope, words) {
1347
+ var _scope_page_plan_sections_scope_index;
1348
+ 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';
1349
+ }
1350
+ /** A grid row of sized cells, as `compileRow` writes one, from ids already compiled. */ function designRow(scope, cells, spacing, alignItems = 'center') {
1351
+ const tree = scope.page.tree;
1352
+ return tree.add('muiGrid', {
1353
+ container: true,
1354
+ spacing
1355
+ }, {
1356
+ alignItems
1357
+ }, cells.map((cell)=>{
1358
+ var _cell_sx;
1359
+ return tree.add('muiGrid', {
1360
+ size: cell.size
1361
+ }, (_cell_sx = cell.sx) != null ? _cell_sx : null, [
1362
+ cell.id
1363
+ ], 'column');
1364
+ }), 'row');
1365
+ }
1366
+ /** A section's own arrangement where the design has one; `null` to draw it the compiler's first way. */ function designSection(scope, raw, blocks) {
1367
+ var _scope_words, _ref, _ref1;
1368
+ var _group_items, _group_items1;
1369
+ const { page, index } = scope;
1370
+ const design = page.design;
1371
+ if (!design) return null;
1372
+ const images = blocks.filter((block)=>block.kind === 'image');
1373
+ const words = blocks.filter((block)=>block.kind !== 'image');
1374
+ const ofWords = words.length > 0 && words.every((block)=>WORD_KINDS.has(block.kind));
1375
+ if (index === 0) {
1376
+ var _words_find;
1377
+ if (!ofWords || images.length > 1 || picturesLeft(page) < 1) return null;
1378
+ const flow = [
1379
+ ...words
1380
+ ];
1381
+ if (!flow.some((block)=>speaksHeading(scope, block))) {
1382
+ flow.unshift({
1383
+ kind: 'heading',
1384
+ text: page.plan.title
1385
+ });
1386
+ page.settled.push({
1387
+ at: scope.at,
1388
+ what: "no heading; the page's title written as its h1"
1389
+ });
1390
+ }
1391
+ // A designed hero's title is display-sized; beside a photo, in half the
1392
+ // page, it is the page's title size, so a long one still reads in a few lines.
1393
+ const lead = flow.findIndex((block)=>speaksHeading(scope, block));
1394
+ const _flow_lead = flow[lead], { style: _style } = _flow_lead, title = _object_without_properties_loose(_flow_lead, [
1395
+ "style"
1396
+ ]);
1397
+ flow[lead] = design.choices.hero === 'split' ? title : _extends({}, title, {
1398
+ style: 'large'
1399
+ });
1400
+ const given = images[0] ? aiLayoutFitText(images[0].text, 'alt') : '';
1401
+ return hero(scope, raw, flow, given ? {
1402
+ alt: given,
1403
+ given: true
1404
+ } : {
1405
+ alt: standInAlt(scope, (_words_find = words.find((block)=>speaksHeading(scope, block))) == null ? void 0 : _words_find.text),
1406
+ given: false
1407
+ });
1408
+ }
1409
+ if (ofWords && !images.length && words.some((block)=>speaksHeading(scope, block))) {
1410
+ const last = index === page.plan.sections.length - 1;
1411
+ const asks = words.some((block)=>block.kind === 'button');
1412
+ const strong = scope.band === 'brand' || scope.band === 'dark';
1413
+ const told = words.some((block)=>block.kind === 'text' || block.kind === 'lede');
1414
+ if (last && asks && (strong || scope.centered) && design.choices.coverClose && picturesLeft(page) >= 1) {
1415
+ return coverBand(scope, words);
1416
+ }
1417
+ if (design.choices.features && told && page.features < 2 && picturesLeft(page) >= 1 && !(last && asks && strong)) {
1418
+ return feature(scope, words);
1419
+ }
1420
+ return null;
1421
+ }
1422
+ // A heading over one group of items: the heading beside them.
1423
+ if (!design.choices.splitHeads || images.length || page.splitAt === index - 1) return null;
1424
+ const at = words.findIndex((block)=>block.kind === 'cards' || block.kind === 'steps');
1425
+ if (at <= 0) return null;
1426
+ const head = words.slice(0, at);
1427
+ const group = words[at];
1428
+ const tail = words.slice(at + 1);
1429
+ const variant = aiLayoutGroupVariant(design.input, design.choices, (_scope_words = scope.words) != null ? _scope_words : '');
1430
+ 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') {
1431
+ return null;
1432
+ }
1433
+ return splitSection(scope, head, group, tail);
1434
+ }
1435
+ /** The first section, in the site's hero arrangement. */ function hero(scope, raw, flow, picture) {
1436
+ const { page } = scope;
1437
+ const tree = page.tree;
1438
+ const design = page.design;
1439
+ const home = design.input.home;
1440
+ const variant = design.choices.hero;
1441
+ page.settled.push({
1442
+ at: scope.at,
1443
+ what: `drawn as a ${variant} hero`
1444
+ });
1445
+ if (variant === 'cover') {
1446
+ scope.band = 'dark';
1447
+ scope.centered = raw.align === 'center';
1448
+ return cover(scope, flow, picture, home ? {
1449
+ xs: '78vh',
1450
+ md: '86vh'
1451
+ } : {
1452
+ xs: '46vh',
1453
+ md: '54vh'
1454
+ });
1455
+ }
1456
+ const band = scope.band;
1457
+ scope.centered = false;
1458
+ if (variant === 'split') {
1459
+ const words = compileFlow(scope, flow, 'half');
1460
+ const photo = designPicture(scope, picture, {
1461
+ aspectRatio: home ? {
1462
+ xs: '4 / 3',
1463
+ md: '4 / 5'
1464
+ } : {
1465
+ xs: '4 / 3',
1466
+ md: '1 / 1'
1467
+ },
1468
+ borderRadius: 2
1469
+ });
1470
+ const right = design.choices.featureLeft;
1471
+ const row = designRow(scope, [
1472
+ _extends({
1473
+ id: words,
1474
+ size: 'xs:12 md:6'
1475
+ }, right ? {
1476
+ sx: {
1477
+ order: {
1478
+ xs: 0,
1479
+ md: 1
1480
+ }
1481
+ }
1482
+ } : {}),
1483
+ {
1484
+ id: photo,
1485
+ size: 'xs:12 md:6'
1486
+ }
1487
+ ], '8');
1488
+ const container = tree.add('muiContainer', {
1489
+ maxWidth: 'lg'
1490
+ }, {
1491
+ py: {
1492
+ xs: 6,
1493
+ md: 10
1494
+ }
1495
+ }, [
1496
+ row
1497
+ ], 'container');
1498
+ return designedRoot(scope, [
1499
+ container
1500
+ ], bandSx(band), band === 'dark');
1501
+ }
1502
+ // Editorial: display type across the page, the rest in a row under it, a wide photo below.
1503
+ const top = flow.filter((block)=>block.kind === 'eyebrow' || block.kind === 'heading');
1504
+ const rest = flow.filter((block)=>block.kind !== 'eyebrow' && block.kind !== 'heading');
1505
+ const lede = rest.filter((block)=>block.kind !== 'button');
1506
+ const actions = rest.filter((block)=>block.kind === 'button');
1507
+ const headId = compileFlow(scope, top, 'full');
1508
+ const ledeId = compileFlow(scope, lede, 'half');
1509
+ const actionsId = compileFlow(scope, actions, 'half');
1510
+ const under = ledeId && actionsId ? designRow(scope, [
1511
+ {
1512
+ id: ledeId,
1513
+ size: 'xs:12 md:7'
1514
+ },
1515
+ {
1516
+ id: actionsId,
1517
+ size: 'xs:12 md:5',
1518
+ sx: {
1519
+ display: 'flex',
1520
+ justifyContent: {
1521
+ xs: 'flex-start',
1522
+ md: 'flex-end'
1523
+ }
1524
+ }
1525
+ }
1526
+ ], '4', 'flex-end') : ledeId != null ? ledeId : actionsId;
1527
+ const photo = designPicture(scope, picture, {
1528
+ aspectRatio: home ? {
1529
+ xs: '4 / 3',
1530
+ md: '21 / 9'
1531
+ } : {
1532
+ xs: '16 / 9',
1533
+ md: '3 / 1'
1534
+ },
1535
+ borderRadius: 2
1536
+ });
1537
+ const stack = tree.add('muiStack', {
1538
+ spacing: '6',
1539
+ useFlexGap: true
1540
+ }, null, [
1541
+ headId,
1542
+ under,
1543
+ photo
1544
+ ], 'content');
1545
+ const container = tree.add('muiContainer', {
1546
+ maxWidth: 'lg'
1547
+ }, {
1548
+ pt: {
1549
+ xs: 8,
1550
+ md: 12
1551
+ },
1552
+ pb: {
1553
+ xs: 6,
1554
+ md: 10
1555
+ }
1556
+ }, [
1557
+ stack
1558
+ ], 'container');
1559
+ return designedRoot(scope, [
1560
+ container
1561
+ ], bandSx(band), band === 'dark');
1562
+ }
1563
+ /** 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) {
1564
+ const tree = scope.page.tree;
1565
+ // The photo dimmed over black: the scrim that keeps the words over it readable.
1566
+ const photo = designPicture(scope, picture, {
1567
+ position: 'absolute',
1568
+ top: 0,
1569
+ left: 0,
1570
+ width: '100%',
1571
+ height: '100%',
1572
+ bgcolor: 'common.black'
1573
+ }, 'coverPhoto', {
1574
+ opacity: 0.42
1575
+ });
1576
+ scope.overPhoto = true;
1577
+ const words = compileFlow(scope, flow, 'full');
1578
+ const measure = tree.add('muiBox', null, scope.centered ? {
1579
+ maxWidth: {
1580
+ xs: '100%',
1581
+ md: '75%'
1582
+ },
1583
+ mx: 'auto'
1584
+ } : {
1585
+ maxWidth: {
1586
+ xs: '100%',
1587
+ md: '62%'
1588
+ }
1589
+ }, [
1590
+ words
1591
+ ], 'measure');
1592
+ const container = tree.add('muiContainer', {
1593
+ maxWidth: 'lg'
1594
+ }, {
1595
+ position: 'relative',
1596
+ zIndex: 1,
1597
+ py: {
1598
+ xs: 10,
1599
+ md: 14
1600
+ }
1601
+ }, [
1602
+ measure
1603
+ ], 'container');
1604
+ return designedRoot(scope, [
1605
+ photo,
1606
+ container
1607
+ ], {
1608
+ position: 'relative',
1609
+ overflow: 'hidden',
1610
+ display: 'flex',
1611
+ alignItems: {
1612
+ xs: 'flex-end',
1613
+ md: 'center'
1614
+ },
1615
+ minHeight
1616
+ }, true);
1617
+ }
1618
+ /** A closing section of words over a photo. */ function coverBand(scope, words) {
1619
+ scope.band = 'dark';
1620
+ scope.centered = true;
1621
+ scope.page.settled.push({
1622
+ at: scope.at,
1623
+ what: 'drawn over a photo'
1624
+ });
1625
+ return cover(scope, words, {
1626
+ alt: standInAlt(scope),
1627
+ given: false
1628
+ }, {
1629
+ xs: '56vh',
1630
+ md: '64vh'
1631
+ });
1632
+ }
1633
+ /** A section of words beside a picture, the side turning section by section. */ function feature(scope, words) {
1634
+ const { page } = scope;
1635
+ const design = page.design;
1636
+ const left = design.choices.featureLeft !== (page.features % 2 === 1);
1637
+ page.features += 1;
1638
+ page.settled.push({
1639
+ at: scope.at,
1640
+ what: `set beside a picture on the ${left ? 'left' : 'right'}`
1641
+ });
1642
+ scope.centered = false;
1643
+ const flow = compileFlow(scope, words, 'half');
1644
+ const photo = designPicture(scope, {
1645
+ alt: standInAlt(scope),
1646
+ given: false
1647
+ }, {
1648
+ aspectRatio: {
1649
+ xs: '4 / 3',
1650
+ md: page.features === 1 ? '4 / 5' : '1 / 1'
1651
+ },
1652
+ borderRadius: 2
1653
+ });
1654
+ const row = designRow(scope, left ? [
1655
+ {
1656
+ id: photo,
1657
+ size: 'xs:12 md:6'
1658
+ },
1659
+ {
1660
+ id: flow,
1661
+ size: 'xs:12 md:6'
1662
+ }
1663
+ ] : [
1664
+ {
1665
+ id: flow,
1666
+ size: 'xs:12 md:6'
1667
+ },
1668
+ {
1669
+ id: photo,
1670
+ size: 'xs:12 md:6'
1671
+ }
1672
+ ], '8');
1673
+ const container = page.tree.add('muiContainer', {
1674
+ maxWidth: 'lg'
1675
+ }, {
1676
+ py: _extends({}, SECTION_PADDING)
1677
+ }, [
1678
+ row
1679
+ ], 'container');
1680
+ return designedRoot(scope, [
1681
+ container
1682
+ ], bandSx(scope.band), scope.band === 'dark');
1683
+ }
1684
+ /** A heading beside its items: the words in a narrow column, the items ruled in the wide one. */ function splitSection(scope, head, group, tail) {
1685
+ const { page } = scope;
1686
+ scope.centered = false;
1687
+ scope.groupStyle = 'ruled';
1688
+ page.splitAt = scope.index;
1689
+ page.settled.push({
1690
+ at: scope.at,
1691
+ what: 'the heading set beside its items'
1692
+ });
1693
+ const words = compileFlow(scope, [
1694
+ ...head,
1695
+ ...tail
1696
+ ], 'half');
1697
+ const items = compileFlow(scope, [
1698
+ group
1699
+ ], 'half');
1700
+ if (!words || !items) {
1701
+ const flow = [
1702
+ words,
1703
+ items
1704
+ ].filter((id)=>!!id);
1705
+ const container = page.tree.add('muiContainer', {
1706
+ maxWidth: 'lg'
1707
+ }, {
1708
+ py: _extends({}, SECTION_PADDING)
1709
+ }, flow, 'container');
1710
+ return designedRoot(scope, [
1711
+ container
1712
+ ], bandSx(scope.band), scope.band === 'dark');
1713
+ }
1714
+ const row = designRow(scope, [
1715
+ {
1716
+ id: words,
1717
+ size: 'xs:12 md:5'
1718
+ },
1719
+ {
1720
+ id: items,
1721
+ size: 'xs:12 md:7'
1722
+ }
1723
+ ], '8', 'flex-start');
1724
+ const container = page.tree.add('muiContainer', {
1725
+ maxWidth: 'lg'
1726
+ }, {
1727
+ py: _extends({}, SECTION_PADDING)
1728
+ }, [
1729
+ row
1730
+ ], 'container');
1731
+ return designedRoot(scope, [
1732
+ container
1733
+ ], bandSx(scope.band), scope.band === 'dark');
1734
+ }
1735
+ /**
1736
+ * Items under rules, one a line, each opening under a thin rule in the
1737
+ * text's own color. One style key an element, so a long list repeats no
1738
+ * multi-key inline style (rule 16); and no Stack `divider`, which a
1739
+ * published page's renderer hands a Stack as one child and so never draws.
1740
+ */ function ruledList(scope, ids, role) {
1741
+ const tree = scope.page.tree;
1742
+ return tree.add('muiStack', {
1743
+ spacing: '2'
1744
+ }, null, ids.map((id)=>tree.add('muiBox', null, {
1745
+ borderTop: 1
1746
+ }, [
1747
+ tree.add('muiBox', null, {
1748
+ pt: 2
1749
+ }, [
1750
+ id
1751
+ ], 'ruledWords')
1752
+ ], 'ruledLine')), role);
1753
+ }
1754
+ /** Numbered items as a ruled list: each number beside its title and words. */ function timeline(scope, items) {
1755
+ const tree = scope.page.tree;
1756
+ const facts = scope.page.targets.facts;
1757
+ const rows = items.map((item, position)=>tree.add('muiStack', {
1758
+ direction: 'row',
1759
+ spacing: '3',
1760
+ alignItems: 'baseline'
1761
+ }, null, [
1762
+ // The numbers keep one column, so every title starts at the same line.
1763
+ tree.add('muiBox', null, {
1764
+ flex: '0 0 3.5rem'
1765
+ }, [
1766
+ tree.add('muiTypography', {
1767
+ children: String(position + 1).padStart(2, '0'),
1768
+ variant: 'h4',
1769
+ component: 'p'
1770
+ }, accent(scope), null, 'number')
1771
+ ], 'numberColumn'),
1772
+ tree.add('muiStack', {
1773
+ spacing: '1'
1774
+ }, null, [
1775
+ item.title.trim() ? tree.add('muiTypography', {
1776
+ children: aiLayoutWords(item.title, 'itemTitle', facts),
1777
+ variant: 'h5',
1778
+ component: itemElement(scope)
1779
+ }, null, null, 'title') : null,
1780
+ item.text.trim() ? tree.add('muiTypography', {
1781
+ children: aiLayoutWords(item.text, 'itemText', facts),
1782
+ variant: 'body1'
1783
+ }, muted(scope), null, 'text') : null
1784
+ ], 'stepWords')
1785
+ ], 'step'));
1786
+ return ruledList(scope, noted(scope, rows), 'timeline');
1787
+ }
1788
+ /** A group drawn the design's way; `null` to draw it as the theme's cards. */ function designedGroup(scope, variant, items, room) {
1789
+ const { page } = scope;
1790
+ const tree = page.tree;
1791
+ const facts = page.targets.facts;
1792
+ const title = (item, type = 'h5')=>item.title.trim() ? tree.add('muiTypography', {
1793
+ children: aiLayoutWords(item.title, 'itemTitle', facts),
1794
+ variant: type,
1795
+ component: itemElement(scope)
1796
+ }, null, null, 'title') : null;
1797
+ const words = (item, type = 'body1')=>item.text.trim() ? tree.add('muiTypography', {
1798
+ children: aiLayoutWords(item.text, 'itemText', facts),
1799
+ variant: type
1800
+ }, muted(scope), null, 'text') : null;
1801
+ if (variant === 'cards') return null;
1802
+ const pictured = variant === 'pictures' || variant === 'articles';
1803
+ if (variant === 'ruled' || pictured && (room !== 'full' || picturesLeft(page) < items.length)) {
1804
+ const perRow = across(items.length, room, 'cards');
1805
+ if (perRow <= 1) {
1806
+ // Stacked, the rules run between the items.
1807
+ const ids = items.map((item)=>tree.add('muiStack', {
1808
+ spacing: '1'
1809
+ }, null, [
1810
+ title(item),
1811
+ words(item)
1812
+ ], 'ruled'));
1813
+ return ruledList(scope, noted(scope, ids), 'ruledItems');
1814
+ }
1815
+ // Side by side, each column opens under a rule in the text's own color.
1816
+ const ids = items.map((item)=>tree.add('muiBox', null, {
1817
+ borderTop: 1
1818
+ }, [
1819
+ tree.add('muiStack', {
1820
+ spacing: '1'
1821
+ }, {
1822
+ pt: 2.5
1823
+ }, [
1824
+ title(item),
1825
+ words(item)
1826
+ ], 'ruledWords')
1827
+ ], 'ruled'));
1828
+ return layOut(scope, noted(scope, ids), perRow, '5', 'ruledItems');
1829
+ }
1830
+ if (variant === 'menu') {
1831
+ const ids = items.map((item)=>tree.add('muiStack', {
1832
+ spacing: '0.5'
1833
+ }, null, [
1834
+ title(item, 'h6'),
1835
+ words(item, 'body2')
1836
+ ], 'dish'));
1837
+ noted(scope, ids);
1838
+ // Two columns of lines under rules, the first half and the second.
1839
+ const half = room === 'full' && ids.length >= 4 ? Math.ceil(ids.length / 2) : ids.length;
1840
+ const lists = [
1841
+ ids.slice(0, half),
1842
+ ids.slice(half)
1843
+ ].filter((list)=>list.length).map((list)=>ruledList(scope, list, 'menu'));
1844
+ if (lists.length === 1) return lists[0];
1845
+ // On a phone the two columns read as one list, the second opening under
1846
+ // its own rule a line's gap below the first; side by side, a gutter.
1847
+ const [first, second] = lists;
1848
+ return tree.add('muiGrid', {
1849
+ container: true,
1850
+ rowSpacing: '2',
1851
+ columnSpacing: '8'
1852
+ }, {
1853
+ alignItems: 'flex-start'
1854
+ }, [
1855
+ tree.add('muiGrid', {
1856
+ size: 'xs:12 md:6'
1857
+ }, null, [
1858
+ first
1859
+ ], 'column'),
1860
+ tree.add('muiGrid', {
1861
+ size: 'xs:12 md:6'
1862
+ }, null, [
1863
+ second
1864
+ ], 'column')
1865
+ ], 'menuRow');
1866
+ }
1867
+ // Pictures over each item's title and words.
1868
+ const design = page.design;
1869
+ const mosaic = variant === 'pictures' && design.choices.pictures === 'mosaic' && items.length >= 2;
1870
+ const portrait = design.input.kind === 'photography';
1871
+ const perRow = items.length === 4 || items.length === 2 ? 2 : Math.min(3, items.length);
1872
+ page.settled.push({
1873
+ at: scope.at,
1874
+ what: `${items.length} items drawn as ${mosaic ? 'a mosaic of pictures' : variant}`
1875
+ });
1876
+ const cells = items.map((item, position)=>{
1877
+ // A mosaic's rows turn wide-narrow, narrow-wide; an odd one out spans the row.
1878
+ const row = Math.floor(position / 2);
1879
+ const lone = mosaic && position === items.length - 1 && items.length % 2 === 1;
1880
+ const wide = mosaic && !lone && position % 2 === row % 2;
1881
+ const span = !mosaic ? 12 / perRow : lone ? 12 : wide ? 7 : 5;
1882
+ const aspect = !mosaic ? variant === 'articles' ? '3 / 2' : portrait ? '4 / 5' : '4 / 3' : lone ? '21 / 9' : wide ? '4 / 3' : '4 / 5';
1883
+ // The picture is its own frame: its shape its one style, so a grid of them repeats no inline style.
1884
+ const photo = designImage(scope, {
1885
+ alt: standInAlt(scope, item.title || item.text),
1886
+ given: false
1887
+ }, {
1888
+ width: '100%'
1889
+ }, {
1890
+ aspectRatio: {
1891
+ xs: '4 / 3',
1892
+ md: aspect
1893
+ }
1894
+ });
1895
+ const id = tree.add('muiStack', {
1896
+ spacing: '1.5'
1897
+ }, null, [
1898
+ photo,
1899
+ title(item, variant === 'articles' ? 'h5' : 'h6'),
1900
+ words(item, 'body2')
1901
+ ], variant === 'articles' ? 'article' : 'work');
1902
+ return {
1903
+ id,
1904
+ size: span === 12 ? 'xs:12 sm:12' : `xs:12 ${span === 6 ? 'sm' : 'md'}:${span}`
1905
+ };
1906
+ });
1907
+ noted(scope, cells.map((cell)=>cell.id));
1908
+ return tree.add('muiGrid', {
1909
+ container: true,
1910
+ spacing: mosaic ? '5' : '4'
1911
+ }, {
1912
+ alignItems: 'flex-start'
1913
+ }, cells.map((cell)=>tree.add('muiGrid', {
1914
+ size: cell.size
1915
+ }, null, [
1916
+ cell.id
1917
+ ], 'cell')), variant);
1918
+ }
1233
1919
  /** The id the compiled page's root is stored under. */ export const AI_LAYOUT_PAGE_ROOT_ID = CANVAS_ROOT_ELEMENT_ID;
1234
1920
 
1235
1921
  //# sourceMappingURL=ai-layout-compiler.js.map