@aglyn/plugins-ai 1.0.0-beta.236 → 1.0.0-beta.238

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 (164) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-credits-prompt.component.d.ts +44 -0
  3. package/src/lib/components/ai-credits-prompt.component.js +117 -0
  4. package/src/lib/components/ai-credits-prompt.component.js.map +1 -0
  5. package/src/lib/components/ai-job-cancel.component.d.ts +38 -0
  6. package/src/lib/components/ai-job-cancel.component.js +156 -0
  7. package/src/lib/components/ai-job-cancel.component.js.map +1 -0
  8. package/src/lib/components/ai-job-plan.component.d.ts +5 -4
  9. package/src/lib/components/ai-job-plan.component.js +48 -12
  10. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  11. package/src/lib/components/ai-job-requests.d.ts +10 -0
  12. package/src/lib/components/ai-job-requests.js +12 -4
  13. package/src/lib/components/ai-job-requests.js.map +1 -1
  14. package/src/lib/components/ai-jobs-list-page.component.js +141 -72
  15. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  16. package/src/lib/components/ai-site-build-page.component.js +48 -9
  17. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  18. package/src/lib/components/ai-site-start-card.component.js +68 -22
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
  21. package/src/lib/components/ai-usage-strip.component.js +40 -9
  22. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  23. package/src/lib/components/assist-build-card.component.d.ts +3 -1
  24. package/src/lib/components/assist-build-card.component.js +58 -7
  25. package/src/lib/components/assist-build-card.component.js.map +1 -1
  26. package/src/lib/components/assist-jobs-drawer.component.js +66 -22
  27. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  28. package/src/lib/components/assist-panel.component.js +89 -52
  29. package/src/lib/components/assist-panel.component.js.map +1 -1
  30. package/src/lib/jobs/ai-job-admission.d.ts +22 -2
  31. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  32. package/src/lib/jobs/ai-job-build-step.d.ts +24 -11
  33. package/src/lib/jobs/ai-job-build-step.js +54 -35
  34. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
  36. package/src/lib/jobs/ai-job-cancel.js +102 -0
  37. package/src/lib/jobs/ai-job-cancel.js.map +1 -0
  38. package/src/lib/jobs/ai-job-component-step.js +4 -1
  39. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-drafts.js +2 -0
  41. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  42. package/src/lib/jobs/ai-job-form-step.js +4 -1
  43. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
  45. package/src/lib/jobs/ai-job-layout-language.js +13 -1
  46. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  47. package/src/lib/jobs/ai-job-layout-step.js +6 -1
  48. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  49. package/src/lib/jobs/ai-job-page-language.js +12 -0
  50. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  51. package/src/lib/jobs/ai-job-page-step.d.ts +13 -0
  52. package/src/lib/jobs/ai-job-page-step.js +46 -4
  53. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-plan-step.d.ts +42 -0
  55. package/src/lib/jobs/ai-job-plan-step.js +118 -18
  56. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-site-content.d.ts +49 -3
  58. package/src/lib/jobs/ai-job-site-content.js +196 -37
  59. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  60. package/src/lib/jobs/ai-job-site-step.js +49 -16
  61. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-template-step.js +4 -1
  63. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  64. package/src/lib/jobs/ai-job-text-step.d.ts +8 -0
  65. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  66. package/src/lib/jobs/ai-jobs.d.ts +55 -13
  67. package/src/lib/jobs/ai-jobs.js +213 -41
  68. package/src/lib/jobs/ai-jobs.js.map +1 -1
  69. package/src/lib/jobs/ai-layout-stock-photos.d.ts +194 -34
  70. package/src/lib/jobs/ai-layout-stock-photos.js +861 -146
  71. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  72. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
  73. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
  74. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
  75. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
  76. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
  77. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
  78. package/src/lib/layout-language/ai-layout-compiler.d.ts +33 -0
  79. package/src/lib/layout-language/ai-layout-compiler.js +381 -18
  80. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  81. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  82. package/src/lib/layout-language/ai-layout-design.js +5 -3
  83. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  84. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  85. package/src/lib/layout-language/ai-layout-frame.js +58 -3
  86. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  87. package/src/lib/layout-language/ai-layout-gaps.js +3 -1
  88. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  89. package/src/lib/layout-language/ai-layout-language.js +1 -1
  90. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-listings.d.ts +49 -1
  92. package/src/lib/layout-language/ai-layout-listings.js +117 -47
  93. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  94. package/src/lib/layout-language/ai-layout-pictures.d.ts +10 -1
  95. package/src/lib/layout-language/ai-layout-pictures.js +5 -2
  96. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  97. package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
  98. package/src/lib/layout-language/ai-layout-storefront.js +223 -0
  99. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
  100. package/src/lib/model/ai-build-job.d.ts +39 -0
  101. package/src/lib/model/ai-build-job.js +100 -1
  102. package/src/lib/model/ai-build-job.js.map +1 -1
  103. package/src/lib/model/ai-build-progress.d.ts +8 -0
  104. package/src/lib/model/ai-build-progress.js +35 -0
  105. package/src/lib/model/ai-build-progress.js.map +1 -1
  106. package/src/lib/model/ai-credit-estimate.d.ts +249 -0
  107. package/src/lib/model/ai-credit-estimate.js +278 -0
  108. package/src/lib/model/ai-credit-estimate.js.map +1 -0
  109. package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
  110. package/src/lib/model/ai-job-cancel-copy.js +53 -0
  111. package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
  112. package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
  113. package/src/lib/model/ai-job-failure-copy.js +8 -3
  114. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  115. package/src/lib/model/ai-jobs.types.d.ts +35 -0
  116. package/src/lib/model/ai-jobs.types.js.map +1 -1
  117. package/src/lib/model/ai-site-build-progress.js +7 -4
  118. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  119. package/src/lib/model/ai-site-job.d.ts +76 -22
  120. package/src/lib/model/ai-site-job.js +125 -23
  121. package/src/lib/model/ai-site-job.js.map +1 -1
  122. package/src/lib/model/ai-site-kinds.d.ts +4 -1
  123. package/src/lib/model/ai-site-kinds.js +35 -7
  124. package/src/lib/model/ai-site-kinds.js.map +1 -1
  125. package/src/lib/runtime/ai-doctrine-validators.d.ts +37 -1
  126. package/src/lib/runtime/ai-doctrine-validators.js +161 -7
  127. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  128. package/src/lib/runtime/ai-doctrine.js +4 -3
  129. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  130. package/src/lib/runtime/ai-node-tree.d.ts +11 -0
  131. package/src/lib/runtime/ai-node-tree.js +26 -0
  132. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  133. package/src/lib/runtime/ai-palette.generated.js +592 -14
  134. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  135. package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
  136. package/src/lib/server/ai-jobs-cancel.js +27 -9
  137. package/src/lib/server/ai-jobs-cancel.js.map +1 -1
  138. package/src/lib/server/ai-jobs-gate.d.ts +12 -2
  139. package/src/lib/server/ai-jobs-gate.js +20 -9
  140. package/src/lib/server/ai-jobs-gate.js.map +1 -1
  141. package/src/lib/server/ai-jobs-resume.js +93 -35
  142. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  143. package/src/lib/server/ai-jobs-route.js +45 -4
  144. package/src/lib/server/ai-jobs-route.js.map +1 -1
  145. package/src/lib/server/assist-chat.js +4 -1
  146. package/src/lib/server/assist-chat.js.map +1 -1
  147. package/src/lib/server/assist-edit.js +3 -1
  148. package/src/lib/server/assist-edit.js.map +1 -1
  149. package/src/lib/server/assist-music.d.ts +36 -0
  150. package/src/lib/server/assist-music.js +36 -0
  151. package/src/lib/server/assist-music.js.map +1 -0
  152. package/src/lib/server.js +6 -0
  153. package/src/lib/server.js.map +1 -1
  154. package/src/lib/usage/ai-usage-meter.d.ts +2 -1
  155. package/src/lib/usage/ai-usage-meter.js +17 -1
  156. package/src/lib/usage/ai-usage-meter.js.map +1 -1
  157. package/src/lib/usage/ai-usage-wire.d.ts +6 -0
  158. package/src/lib/usage/ai-usage-wire.js.map +1 -1
  159. package/src/lib/usage/assist-usage.d.ts +7 -0
  160. package/src/lib/usage/assist-usage.js +14 -4
  161. package/src/lib/usage/assist-usage.js.map +1 -1
  162. package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
  163. package/src/lib/usage/free-ai-credits-left.js +4 -2
  164. package/src/lib/usage/free-ai-credits-left.js.map +1 -1
@@ -22,8 +22,9 @@ 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
+ import { aiLayoutDesignChoices, aiLayoutFamily, aiLayoutGroupVariant } from "./ai-layout-design.js";
26
26
  import { AI_LAYOUT_FEATURED_RECORDS, AI_LAYOUT_LISTING_ELEMENTS, aiLayoutListingAt } from "./ai-layout-listings.js";
27
+ import { aiStorefrontItemIcons } from "./ai-layout-storefront.js";
27
28
  import { aiLayoutRenamedLabel, aiLayoutResolveLink } from "./ai-layout-links.js";
28
29
  import { AI_LAYOUT_FRAMES, AiLayoutTreeBuilder } from "./ai-layout-tree.js";
29
30
  /**
@@ -166,6 +167,9 @@ function compileSection(page, raw, index) {
166
167
  if (raw.blocks.some((block)=>block.kind === 'quotes') && !blocks.some((block)=>AI_LAYOUT_GROUP_KINDS.has(block.kind) || block.kind === 'component')) {
167
168
  page.quotesOnly.push(index);
168
169
  }
170
+ // A store's Shop page opens on its grid (AGL-3676): its first section is a
171
+ // short title bar, never a hero, so the products start above the fold.
172
+ if (index === 0 && shopGridAt(page) > 0) return shopTitle(scope, blocks);
169
173
  // A section the site's records fill — its catalog, its blog — shows them
170
174
  // through the element that keeps them, around the words the design gave it (AGL-3676).
171
175
  const listed = aiLayoutListingAt((_page_targets_listings = page.targets.listings) != null ? _page_targets_listings : [], page.targets.pageId, index);
@@ -385,7 +389,11 @@ function compileSection(page, raw, index) {
385
389
  }
386
390
  /** A band the page has room for: past two brand or two dark bands, a soft one instead. */ function settleBand(page, band, at) {
387
391
  if (band !== 'brand' && band !== 'dark') return band;
388
- if (page.strong[band] >= AI_LAYOUT_MAX_STRONG_BANDS) {
392
+ // A storefront keeps one dark band at most (AGL-3676): the beta.237 Willow
393
+ // Wick home ran its text bands dark, and read as big empty dark slabs
394
+ // between the products.
395
+ const most = band === 'dark' && page.design && aiLayoutFamily(page.design.input.kind) === 'retail' ? 1 : AI_LAYOUT_MAX_STRONG_BANDS;
396
+ if (page.strong[band] >= most) {
389
397
  page.settled.push({
390
398
  at: `${at}.band`,
391
399
  what: `a third ${band} band drawn soft`
@@ -819,8 +827,39 @@ export function destinationProps(destination) {
819
827
  * text (rule 9), and no source is set here: the compiler stays pure, and
820
828
  * `ai-layout-pictures.ts` fills the slot with a photo the site serves itself
821
829
  * once the page is checked.
822
- */ function image(scope, block, room) {
830
+ */ /**
831
+ * The site kinds whose galleries open in a lightbox by default (AGL-3717): a
832
+ * portfolio's or a photographer's work is looked at, not skimmed, and a
833
+ * visitor who presses a picture expects to see it large and step through the
834
+ * rest. Every other kind opens a picture only where the design says so.
835
+ */ export const AI_LIGHTBOX_GALLERY_KINDS = new Set([
836
+ 'portfolio',
837
+ 'photography'
838
+ ]);
839
+ /** The `to` that opens an image block's picture in a lightbox (AGL-3717). */ export const AI_LAYOUT_LIGHTBOX_TO = 'lightbox';
840
+ /** Whether this page's galleries open in a lightbox by default. */ export function aiOpensGalleriesInLightbox(page) {
841
+ var _page_design;
842
+ const kind = (_page_design = page.design) == null ? void 0 : _page_design.input.kind;
843
+ return Boolean(kind && AI_LIGHTBOX_GALLERY_KINDS.has(kind));
844
+ }
845
+ /**
846
+ * The Image props that make a picture open in a lightbox: as one gallery
847
+ * with the section's other pictures, named for the section, and with the
848
+ * item's title as its caption. The Image element reads exactly these.
849
+ */ function lightboxProps(scope, caption) {
850
+ var _scope_page_plan_sections_scope_index;
851
+ const name = 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') || `Gallery ${scope.index + 1}`;
852
+ const words = aiLayoutFitText(caption, 'alt');
853
+ return _extends({
854
+ lightbox: true,
855
+ lightboxGallery: name
856
+ }, words ? {
857
+ lightboxCaption: words
858
+ } : {});
859
+ }
860
+ function image(scope, block, room) {
823
861
  var _aiIconOfWord;
862
+ var _block_to;
824
863
  const { page } = scope;
825
864
  const alt = aiLayoutFitText(block.text, 'alt');
826
865
  if (!alt) return null;
@@ -840,10 +879,13 @@ export function destinationProps(destination) {
840
879
  }, {
841
880
  color: 'primary.main'
842
881
  }, null, 'imageIcon');
843
- const picture = tree.add('image', {
882
+ // `to: lightbox` opens the picture large (AGL-3717); one picture is its
883
+ // own gallery, so it opens alone unless the section has more.
884
+ const opensLarge = ((_block_to = block.to) == null ? void 0 : _block_to.trim().toLowerCase()) === AI_LAYOUT_LIGHTBOX_TO;
885
+ const picture = tree.add('image', _extends({
844
886
  alt,
845
887
  objectFit: 'cover'
846
- }, {
888
+ }, opensLarge ? lightboxProps(scope) : {}), {
847
889
  position: 'absolute',
848
890
  top: 0,
849
891
  left: 0,
@@ -1020,11 +1062,21 @@ function group(scope, block, room) {
1020
1062
  const placed = componentOf(scope.page.targets, block.to);
1021
1063
  if (placed) return instances(scope, placed, items, perRow);
1022
1064
  const design = scope.page.design;
1065
+ const retail = !!design && aiLayoutFamily(design.input.kind) === 'retail';
1066
+ // A store's making, shown: its steps as pictures over their words (AGL-3676),
1067
+ // where the page has the pictures for them.
1068
+ if (retail && block.kind === 'steps' && room === 'full' && items.length >= 2 && items.length <= 4 && picturesLeft(scope.page) >= items.length) {
1069
+ const drawn = designedGroup(scope, 'articles', items, room);
1070
+ if (drawn) return drawn;
1071
+ }
1023
1072
  if (rule1(scope)) {
1024
1073
  var _scope_words;
1025
1074
  // 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.
1075
+ // by hand (`repeatsCompiled`, AGL-3660), and so are a storefront's icon
1076
+ // columns (AGL-3676): the beta.237 Willow Wick home, on a paid plan, drew
1077
+ // every other group compact — four bands of a title and a sentence.
1027
1078
  const variant = design && block.kind === 'cards' && block.style !== 'quiet' ? aiLayoutGroupVariant(design.input, design.choices, (_scope_words = scope.words) != null ? _scope_words : '') : null;
1079
+ if (variant === 'icons') return iconColumns(scope, items, room);
1028
1080
  const pictured = (variant === 'pictures' || variant === 'articles') && room === 'full' && picturesLeft(scope.page) >= items.length;
1029
1081
  return pictured && designedGroup(scope, variant, items, room) || compactGroup(scope, block, items, perRow);
1030
1082
  }
@@ -1423,6 +1475,7 @@ function instances(scope, placed, items, perRow) {
1423
1475
  flow[lead] = design.choices.hero === 'split' ? title : _extends({}, title, {
1424
1476
  style: 'large'
1425
1477
  });
1478
+ sellingHero(scope, flow);
1426
1479
  const given = images[0] ? aiLayoutFitText(images[0].text, 'alt') : '';
1427
1480
  return hero(scope, raw, flow, given ? {
1428
1481
  alt: given,
@@ -1825,6 +1878,7 @@ function instances(scope, placed, items, perRow) {
1825
1878
  variant: type
1826
1879
  }, muted(scope), null, 'text') : null;
1827
1880
  if (variant === 'cards') return null;
1881
+ if (variant === 'icons') return iconColumns(scope, items, room);
1828
1882
  const pictured = variant === 'pictures' || variant === 'articles';
1829
1883
  if (variant === 'ruled' || pictured && (room !== 'full' || picturesLeft(page) < items.length)) {
1830
1884
  const perRow = across(items.length, room, 'cards');
@@ -1907,12 +1961,19 @@ function instances(scope, placed, items, perRow) {
1907
1961
  const span = !mosaic ? 12 / perRow : lone ? 12 : wide ? 7 : 5;
1908
1962
  const aspect = !mosaic ? variant === 'articles' ? '3 / 2' : portrait ? '4 / 5' : '4 / 3' : lone ? '21 / 9' : wide ? '4 / 3' : '4 / 5';
1909
1963
  // The picture is its own frame: its shape its one style, so a grid of them repeats no inline style.
1964
+ // A portfolio's or a photographer's gallery opens in a lightbox, its
1965
+ // pictures one gallery with the item's title as each caption (AGL-3717).
1966
+ const opens = variant === 'pictures' && aiOpensGalleriesInLightbox(page);
1967
+ // A store's collection tiles open its Shop page (AGL-3676).
1968
+ const shop = variant === 'pictures' && !opens && aiLayoutFamily(design.input.kind) === 'retail' ? shopPageOf(page) : null;
1910
1969
  const photo = designImage(scope, {
1911
1970
  alt: standInAlt(scope, item.title || item.text),
1912
1971
  given: false
1913
- }, {
1972
+ }, _extends({
1914
1973
  width: '100%'
1915
- }, {
1974
+ }, opens ? lightboxProps(scope, item.title) : {}, shop ? {
1975
+ screenId: shop
1976
+ } : {}), {
1916
1977
  aspectRatio: {
1917
1978
  xs: '4 / 3',
1918
1979
  md: aspect
@@ -1978,18 +2039,24 @@ function instances(scope, placed, items, perRow) {
1978
2039
  style: 'secondary'
1979
2040
  } : null;
1980
2041
  const index = listing.placements.find((placement)=>placement.role === 'index' && placement.screenId !== page.targets.pageId);
1981
- return index ? {
2042
+ if (!index) return null;
2043
+ return {
1982
2044
  kind: 'button',
1983
- text: 'Shop all',
2045
+ text: listing.kind === 'tracks' ? 'All music' : 'Shop all',
1984
2046
  to: `page:${index.screenId}`,
1985
2047
  style: 'secondary'
1986
- } : null;
2048
+ };
1987
2049
  }
1988
2050
  function listingSection(scope, blocks, placed) {
1989
2051
  var _ref;
1990
2052
  var _page_plan_sections_index_name, _page_plan_sections_index, _page_options_sectionIds;
1991
2053
  const { page, index, at } = scope;
1992
2054
  const { listing, role } = placed;
2055
+ if (listing.kind === 'reviews') return reviewsSection(scope, blocks);
2056
+ if (listing.kind === 'signup') return signupSection(scope, blocks);
2057
+ // The grid straight under a Shop page's title bar opens on the products:
2058
+ // the title bar already heads the page (AGL-3676).
2059
+ const underTitle = listing.kind === 'products' && role === 'index' && index === 1 && shopGridAt(page) === 1;
1993
2060
  const left = blocks.filter((block)=>LISTING_REPLACES.has(block.kind));
1994
2061
  if (left.length) {
1995
2062
  page.settled.push({
@@ -1997,7 +2064,7 @@ function listingSection(scope, blocks, placed) {
1997
2064
  what: `${left.map((block)=>block.kind).join(', ')} left out: the section lists the site's own ${listing.kind}`
1998
2065
  });
1999
2066
  }
2000
- const kept = blocks.filter((block)=>!LISTING_REPLACES.has(block.kind)).map((_0)=>{
2067
+ const kept = underTitle ? [] : blocks.filter((block)=>!LISTING_REPLACES.has(block.kind)).map((_0)=>{
2001
2068
  let { col: _col } = _0, block = _object_without_properties_loose(_0, [
2002
2069
  "col"
2003
2070
  ]);
@@ -2011,7 +2078,7 @@ function listingSection(scope, blocks, placed) {
2011
2078
  all
2012
2079
  ];
2013
2080
  }
2014
- if (!words.some((block)=>speaksHeading(scope, block))) {
2081
+ if (!underTitle && !words.some((block)=>speaksHeading(scope, block))) {
2015
2082
  var _page_plan_sections_index1;
2016
2083
  // Every band of records is headed: the plan's name for it, or on a first section the page's title.
2017
2084
  words.unshift({
@@ -2064,7 +2131,13 @@ function listingSection(scope, blocks, placed) {
2064
2131
  } : {}), null, parts, 'content');
2065
2132
  const container = tree.add('muiContainer', {
2066
2133
  maxWidth: 'lg'
2067
- }, {
2134
+ }, underTitle ? {
2135
+ pt: {
2136
+ xs: 2,
2137
+ md: 3
2138
+ },
2139
+ pb: _extends({}, SECTION_PADDING)
2140
+ } : {
2068
2141
  py: index === 0 ? _extends({}, HERO_PADDING) : _extends({}, SECTION_PADDING)
2069
2142
  }, [
2070
2143
  content
@@ -2075,10 +2148,268 @@ function listingSection(scope, blocks, placed) {
2075
2148
  ariaLabel: sectionLabel(name, index)
2076
2149
  }, scope.band === 'dark' ? {
2077
2150
  colorScheme: 'dark'
2078
- } : {}), bandSx(scope.band), [
2151
+ } : {}), underTitle ? null : bandSx(scope.band), [
2079
2152
  container
2080
2153
  ], 'section', (_ref = (_page_options_sectionIds = page.options.sectionIds) == null ? void 0 : _page_options_sectionIds[index]) != null ? _ref : sectionIdOf(index));
2081
2154
  }
2155
+ /** What a store's reviews are headed with where neither the design nor the plan says. */ export const AI_LAYOUT_REVIEWS_HEADING = 'What our customers say';
2156
+ /**
2157
+ * A store's own customer reviews (AGL-3676): the commerce plugin's Product
2158
+ * reviews in its store-wide scope, carrying the section's heading itself.
2159
+ * It lists approved reviews with their star ratings, and on a published page
2160
+ * with none — every store that just opened — it shows nothing, heading and
2161
+ * all, so the band takes no room until a shopper has written one. No review
2162
+ * is ever written here: the design's quotes, if it gave any, were left out.
2163
+ */ function reviewsSection(scope, blocks) {
2164
+ var _page_plan_sections_index;
2165
+ const { page, index, at } = scope;
2166
+ const tree = page.tree;
2167
+ const given = blocks.find((block)=>speaksHeading(scope, block));
2168
+ const heading = aiLayoutWords(given == null ? void 0 : given.text, 'heading', page.targets.facts) || aiLayoutFitText((_page_plan_sections_index = page.plan.sections[index]) == null ? void 0 : _page_plan_sections_index.name, 'heading') || AI_LAYOUT_REVIEWS_HEADING;
2169
+ page.settled.push({
2170
+ at,
2171
+ what: "the store's own reviews placed: shown once shoppers leave them, never written for them"
2172
+ });
2173
+ const id = tree.add(AI_LAYOUT_LISTING_ELEMENTS.reviews, {
2174
+ scope: 'store',
2175
+ heading,
2176
+ maxItems: String(AI_LAYOUT_FEATURED_RECORDS.reviews)
2177
+ }, // The padding is the element's, so a store with no reviews yet keeps no empty band.
2178
+ {
2179
+ py: _extends({}, SECTION_PADDING)
2180
+ }, null, 'reviews');
2181
+ // The reviews are what the section shows: its shoppers' to write.
2182
+ noted(scope, [
2183
+ id,
2184
+ id
2185
+ ]);
2186
+ const container = tree.add('muiContainer', {
2187
+ maxWidth: 'lg'
2188
+ }, null, [
2189
+ id
2190
+ ], 'container');
2191
+ return designedRoot(scope, [
2192
+ container
2193
+ ], bandSx(scope.band), scope.band === 'dark');
2194
+ }
2195
+ /** What a store's sign-up is headed with where neither the design nor the plan says. */ export const AI_LAYOUT_SIGNUP_HEADING = 'Join our newsletter';
2196
+ /**
2197
+ * A store's newsletter sign-up (AGL-3676): the design's heading and words
2198
+ * beside the commerce plugin's Newsletter signup field, or over it on a
2199
+ * centered band. Any form or button the design drew for it is the field's.
2200
+ */ function signupSection(scope, blocks) {
2201
+ const { page, index, at } = scope;
2202
+ const tree = page.tree;
2203
+ const flow = blocks.filter((block)=>WORD_KINDS.has(block.kind) && block.kind !== 'button').map((_0)=>{
2204
+ let { col: _col } = _0, block = _object_without_properties_loose(_0, [
2205
+ "col"
2206
+ ]);
2207
+ return block;
2208
+ });
2209
+ if (!flow.some((block)=>speaksHeading(scope, block))) {
2210
+ var _page_plan_sections_index;
2211
+ flow.unshift({
2212
+ kind: 'heading',
2213
+ text: ((_page_plan_sections_index = page.plan.sections[index]) == null ? void 0 : _page_plan_sections_index.name) || AI_LAYOUT_SIGNUP_HEADING
2214
+ });
2215
+ }
2216
+ page.settled.push({
2217
+ at,
2218
+ what: "the store's newsletter sign-up placed"
2219
+ });
2220
+ const field = tree.add(AI_LAYOUT_LISTING_ELEMENTS.signup, {
2221
+ buttonLabel: 'Subscribe'
2222
+ }, null, null, 'signup');
2223
+ noted(scope, [
2224
+ field,
2225
+ field
2226
+ ]);
2227
+ const words = compileFlow(scope, flow, scope.centered ? 'full' : 'half');
2228
+ const body = scope.centered ? tree.add('muiStack', {
2229
+ spacing: '4',
2230
+ alignItems: 'center'
2231
+ }, null, [
2232
+ words,
2233
+ tree.add('muiContainer', {
2234
+ maxWidth: 'sm',
2235
+ disableGutters: true
2236
+ }, null, [
2237
+ field
2238
+ ], 'measure')
2239
+ ], 'content') : designRow(scope, [
2240
+ ...words ? [
2241
+ {
2242
+ id: words,
2243
+ size: 'xs:12 md:6'
2244
+ }
2245
+ ] : [],
2246
+ {
2247
+ id: field,
2248
+ size: 'xs:12 md:6'
2249
+ }
2250
+ ], '6');
2251
+ const container = tree.add('muiContainer', {
2252
+ maxWidth: 'lg'
2253
+ }, {
2254
+ py: _extends({}, SECTION_PADDING)
2255
+ }, [
2256
+ body
2257
+ ], 'container');
2258
+ return designedRoot(scope, [
2259
+ container
2260
+ ], bandSx(scope.band), scope.band === 'dark');
2261
+ }
2262
+ /** A store's Shop page's grid section on this page, by plan index; -1 where this page is not its Shop page. */ function shopGridAt(page) {
2263
+ var _page_targets_listings, _ref;
2264
+ var _products_placements_find;
2265
+ const products = ((_page_targets_listings = page.targets.listings) != null ? _page_targets_listings : []).find((listing)=>listing.kind === 'products');
2266
+ return (_ref = products == null ? void 0 : (_products_placements_find = products.placements.find((placement)=>placement.role === 'index' && placement.screenId === page.targets.pageId)) == null ? void 0 : _products_placements_find.section) != null ? _ref : -1;
2267
+ }
2268
+ /** A store's Shop page, by its id, where this page is another page of the store; `null` otherwise. */ function shopPageOf(page) {
2269
+ var _page_targets_listings;
2270
+ var _products_placements_find;
2271
+ const products = ((_page_targets_listings = page.targets.listings) != null ? _page_targets_listings : []).find((listing)=>listing.kind === 'products');
2272
+ const shop = products == null ? void 0 : (_products_placements_find = products.placements.find((placement)=>placement.role === 'index')) == null ? void 0 : _products_placements_find.screenId;
2273
+ return shop && shop !== page.targets.pageId && page.targets.pages.some((entry)=>entry.id === shop) ? shop : null;
2274
+ }
2275
+ /**
2276
+ * A Shop page's opening, as a storefront's collection page opens (AGL-3676):
2277
+ * the page's title and one line under it, on the page's own ground, with
2278
+ * little room around them — no photo and no buttons — so the grid that
2279
+ * follows starts above the fold. The beta.237 Willow Wick /shop opened on a
2280
+ * tall candle photo and its words, the products below the fold.
2281
+ */ function shopTitle(scope, blocks) {
2282
+ var _ref;
2283
+ const { page, at } = scope;
2284
+ const left = blocks.filter((block)=>![
2285
+ 'eyebrow',
2286
+ 'heading',
2287
+ 'lede',
2288
+ 'text'
2289
+ ].includes(block.kind));
2290
+ if (left.length) {
2291
+ page.settled.push({
2292
+ at,
2293
+ what: `${left.map((block)=>block.kind).join(', ')} left out: a Shop page opens on its title and its grid`
2294
+ });
2295
+ }
2296
+ const heading = blocks.find((block)=>speaksHeading(scope, block));
2297
+ const line = blocks.find((block)=>block.kind === 'lede' || block.kind === 'text');
2298
+ const eyebrow = blocks.find((block)=>block.kind === 'eyebrow');
2299
+ const flow = [
2300
+ ...eyebrow ? [
2301
+ eyebrow
2302
+ ] : [],
2303
+ {
2304
+ kind: 'heading',
2305
+ text: (_ref = heading == null ? void 0 : heading.text) != null ? _ref : page.plan.title
2306
+ },
2307
+ ...line ? [
2308
+ {
2309
+ kind: 'lede',
2310
+ text: line.text
2311
+ }
2312
+ ] : []
2313
+ ];
2314
+ scope.band = 'plain';
2315
+ scope.centered = false;
2316
+ page.settled.push({
2317
+ at,
2318
+ what: "drawn as the Shop page's title bar, the grid under it"
2319
+ });
2320
+ const words = compileFlow(scope, flow, 'half');
2321
+ const container = page.tree.add('muiContainer', {
2322
+ maxWidth: 'lg'
2323
+ }, {
2324
+ pt: {
2325
+ xs: 5,
2326
+ md: 7
2327
+ },
2328
+ pb: {
2329
+ xs: 1,
2330
+ md: 2
2331
+ }
2332
+ }, [
2333
+ words
2334
+ ], 'container');
2335
+ return designedRoot(scope, [
2336
+ container
2337
+ ], null, false);
2338
+ }
2339
+ /**
2340
+ * A storefront's home hero sells (AGL-3676): it opens the shop. Where the
2341
+ * design gave it no button to the Shop page, "Shop all" leads its buttons,
2342
+ * and any other primary button steps back to secondary beside it.
2343
+ */ function sellingHero(scope, flow) {
2344
+ const design = scope.page.design;
2345
+ if (!(design == null ? void 0 : design.input.home) || aiLayoutFamily(design.input.kind) !== 'retail') return;
2346
+ const shop = shopPageOf(scope.page);
2347
+ if (!shop || flow.some((block)=>block.kind === 'button' && block.to === `page:${shop}`)) return;
2348
+ const firstButton = flow.findIndex((block)=>block.kind === 'button');
2349
+ for (const [at, block] of flow.entries()){
2350
+ var _block_style;
2351
+ if (block.kind === 'button' && ((_block_style = block.style) != null ? _block_style : 'primary') === 'primary') flow[at] = _extends({}, block, {
2352
+ style: 'secondary'
2353
+ });
2354
+ }
2355
+ const shopAll = {
2356
+ kind: 'button',
2357
+ text: AI_LAYOUT_SHOP_ALL,
2358
+ to: `page:${shop}`,
2359
+ style: 'primary'
2360
+ };
2361
+ if (firstButton === -1) flow.push(shopAll);
2362
+ else flow.splice(firstButton, 0, shopAll);
2363
+ scope.page.settled.push({
2364
+ at: scope.at,
2365
+ what: `the hero opens the shop: "${AI_LAYOUT_SHOP_ALL}" leads its buttons`
2366
+ });
2367
+ }
2368
+ /** The button a storefront's hero opens its shop with. */ export const AI_LAYOUT_SHOP_ALL = 'Shop all';
2369
+ /**
2370
+ * A storefront's reasons to buy as open columns, each led by an icon over its
2371
+ * title and words (AGL-3676): the icon the design named for it, else one its
2372
+ * words suggest (shipping a truck, returns the arrows, small batch the hand),
2373
+ * never the same twice in a row. One style key an element, so a row repeats
2374
+ * no multi-key inline style (rule 16).
2375
+ */ function iconColumns(scope, items, room) {
2376
+ const { page } = scope;
2377
+ const tree = page.tree;
2378
+ const facts = page.targets.facts;
2379
+ const iconWords = aiStorefrontItemIcons(items, (word)=>!!aiIconOfWord(word));
2380
+ const perRow = across(items.length, room, 'cards');
2381
+ const textAlign = scope.centered ? {
2382
+ align: 'center'
2383
+ } : {};
2384
+ page.settled.push({
2385
+ at: scope.at,
2386
+ what: `${items.length} items drawn as icon columns`
2387
+ });
2388
+ const ids = items.map((item, position)=>{
2389
+ var _aiIconOfWord;
2390
+ const icon = (_aiIconOfWord = aiIconOfWord(iconWords[position])) != null ? _aiIconOfWord : AI_ICON_LIBRARY.star;
2391
+ return tree.add('muiStack', _extends({
2392
+ spacing: '1.5'
2393
+ }, scope.centered ? {
2394
+ alignItems: 'center'
2395
+ } : {}), null, [
2396
+ tree.add(AI_ICON_COMPONENT_ID, {
2397
+ iconId: icon.id,
2398
+ size: '32'
2399
+ }, accent(scope), null, 'icon'),
2400
+ item.title.trim() ? tree.add('muiTypography', _extends({
2401
+ children: aiLayoutWords(item.title, 'itemTitle', facts),
2402
+ variant: 'h6',
2403
+ component: itemElement(scope)
2404
+ }, textAlign), null, null, 'title') : null,
2405
+ item.text.trim() ? tree.add('muiTypography', _extends({
2406
+ children: aiLayoutWords(item.text, 'itemText', facts),
2407
+ variant: 'body2'
2408
+ }, textAlign), muted(scope), null, 'text') : null
2409
+ ], 'iconItem');
2410
+ });
2411
+ return layOut(scope, noted(scope, ids), perRow, '5', 'icons');
2412
+ }
2082
2413
  /**
2083
2414
  * The element that lists the records, bound to them: the Product grid over
2084
2415
  * the site's catalog, or Collection Entries over its blog with one post's
@@ -2090,6 +2421,22 @@ function listingSection(scope, blocks, placed) {
2090
2421
  const shown = featured ? AI_LAYOUT_FEATURED_RECORDS[listing.kind] : 12;
2091
2422
  const element = AI_LAYOUT_LISTING_ELEMENTS[listing.kind];
2092
2423
  let id;
2424
+ if (listing.kind === 'tracks') {
2425
+ // An empty player (AGL-3716): its "add your tracks" state, which the owner
2426
+ // fills from their own media library. No source, ever — the section's
2427
+ // heading and words are the model's, the recordings the owner's.
2428
+ id = tree.add(element, null, null, null, 'player');
2429
+ page.settled.push({
2430
+ at: scope.at,
2431
+ what: 'an empty music player placed for the owner’s own tracks'
2432
+ });
2433
+ // The player is what the section shows: its tracks are the owner's to add.
2434
+ noted(scope, [
2435
+ id,
2436
+ id
2437
+ ]);
2438
+ return id;
2439
+ }
2093
2440
  if (listing.kind === 'products') {
2094
2441
  id = tree.add(element, _extends({
2095
2442
  source: 'all',
@@ -2098,11 +2445,22 @@ function listingSection(scope, blocks, placed) {
2098
2445
  }, featured ? {
2099
2446
  maxItems: String(shown)
2100
2447
  } : {
2101
- pageSize: String(shown)
2448
+ pageSize: String(shown),
2449
+ showSort: true,
2450
+ showCategories: true
2102
2451
  }, {
2103
2452
  cardStyle: 'photo',
2104
- emptyText: 'New products are on their way.'
2105
- }), null, null, 'products');
2453
+ // A storefront's cards sell from the grid (AGL-3676): Add to cart on a
2454
+ // product with one variant, Choose options on one with several.
2455
+ quickAdd: true,
2456
+ // A store that opens before its first products are in says so, with a
2457
+ // way to hear when they land (AGL-3676): never placeholder products.
2458
+ emptyTitle: AI_LAYOUT_STORE_EMPTY.title,
2459
+ emptyText: listing.emptyAction ? AI_LAYOUT_STORE_EMPTY.text : AI_LAYOUT_STORE_EMPTY.textAlone
2460
+ }, listing.emptyAction ? {
2461
+ emptyActionLabel: listing.emptyAction.label,
2462
+ emptyActionHref: listing.emptyAction.href
2463
+ } : {}), null, null, 'products');
2106
2464
  } else {
2107
2465
  id = postsElement(scope, listing, featured ? shown : COLLECTION_INDEX_POSTS);
2108
2466
  }
@@ -2113,6 +2471,11 @@ function listingSection(scope, blocks, placed) {
2113
2471
  }, ()=>id));
2114
2472
  return id;
2115
2473
  }
2474
+ /** What a store's Product grid says while the store lists nothing yet (AGL-3676). */ export const AI_LAYOUT_STORE_EMPTY = {
2475
+ title: 'New pieces are on the way',
2476
+ text: 'Our first pieces are being finished now. Get in touch and we will let you know the moment they land.',
2477
+ /** Said where the site has no page to get in touch on. */ textAlone: 'Our first pieces are being finished now. Check back soon.'
2478
+ };
2116
2479
  /** The most posts a page of the site lists where it is the writing's own page. */ const COLLECTION_INDEX_POSTS = 9;
2117
2480
  /** 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
2481
  var _page_design;