@aglyn/plugins-ai 1.0.0-beta.237 → 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 (128) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-credits-prompt.component.d.ts +11 -4
  3. package/src/lib/components/ai-credits-prompt.component.js +84 -34
  4. package/src/lib/components/ai-credits-prompt.component.js.map +1 -1
  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.js +12 -1
  9. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  10. package/src/lib/components/ai-jobs-list-page.component.js +141 -72
  11. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  12. package/src/lib/components/ai-site-build-page.component.js +22 -2
  13. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  14. package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
  15. package/src/lib/components/ai-usage-strip.component.js +40 -9
  16. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  17. package/src/lib/components/assist-jobs-drawer.component.js +12 -16
  18. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  19. package/src/lib/components/assist-panel.component.js +87 -51
  20. package/src/lib/components/assist-panel.component.js.map +1 -1
  21. package/src/lib/jobs/ai-job-build-step.js +4 -1
  22. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  23. package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
  24. package/src/lib/jobs/ai-job-cancel.js +102 -0
  25. package/src/lib/jobs/ai-job-cancel.js.map +1 -0
  26. package/src/lib/jobs/ai-job-component-step.js +4 -1
  27. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  28. package/src/lib/jobs/ai-job-drafts.js +2 -0
  29. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  30. package/src/lib/jobs/ai-job-form-step.js +4 -1
  31. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  32. package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
  33. package/src/lib/jobs/ai-job-layout-language.js +13 -1
  34. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  35. package/src/lib/jobs/ai-job-layout-step.js +6 -1
  36. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-page-language.js +8 -0
  38. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  39. package/src/lib/jobs/ai-job-page-step.js +12 -1
  40. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  41. package/src/lib/jobs/ai-job-plan-step.d.ts +20 -1
  42. package/src/lib/jobs/ai-job-plan-step.js +49 -9
  43. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-site-content.d.ts +15 -0
  45. package/src/lib/jobs/ai-job-site-content.js +66 -7
  46. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  47. package/src/lib/jobs/ai-job-site-step.js +4 -1
  48. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  49. package/src/lib/jobs/ai-job-template-step.js +4 -1
  50. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  51. package/src/lib/jobs/ai-jobs.d.ts +37 -12
  52. package/src/lib/jobs/ai-jobs.js +184 -28
  53. package/src/lib/jobs/ai-jobs.js.map +1 -1
  54. package/src/lib/jobs/ai-layout-stock-photos.d.ts +143 -70
  55. package/src/lib/jobs/ai-layout-stock-photos.js +649 -176
  56. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  57. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
  58. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
  59. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
  60. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
  61. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
  62. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
  63. package/src/lib/layout-language/ai-layout-compiler.d.ts +6 -0
  64. package/src/lib/layout-language/ai-layout-compiler.js +304 -8
  65. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  66. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  67. package/src/lib/layout-language/ai-layout-design.js +5 -3
  68. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  69. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  70. package/src/lib/layout-language/ai-layout-frame.js +58 -3
  71. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  72. package/src/lib/layout-language/ai-layout-listings.d.ts +13 -1
  73. package/src/lib/layout-language/ai-layout-listings.js +61 -46
  74. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  75. package/src/lib/layout-language/ai-layout-pictures.d.ts +8 -0
  76. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  77. package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
  78. package/src/lib/layout-language/ai-layout-storefront.js +223 -0
  79. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
  80. package/src/lib/model/ai-build-job.d.ts +9 -1
  81. package/src/lib/model/ai-build-job.js +19 -5
  82. package/src/lib/model/ai-build-job.js.map +1 -1
  83. package/src/lib/model/ai-build-progress.d.ts +2 -2
  84. package/src/lib/model/ai-build-progress.js +6 -4
  85. package/src/lib/model/ai-build-progress.js.map +1 -1
  86. package/src/lib/model/ai-credit-estimate.d.ts +69 -2
  87. package/src/lib/model/ai-credit-estimate.js +91 -3
  88. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  89. package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
  90. package/src/lib/model/ai-job-cancel-copy.js +53 -0
  91. package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
  92. package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
  93. package/src/lib/model/ai-job-failure-copy.js +8 -3
  94. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  95. package/src/lib/model/ai-jobs.types.d.ts +20 -0
  96. package/src/lib/model/ai-jobs.types.js.map +1 -1
  97. package/src/lib/model/ai-site-build-progress.js +7 -4
  98. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  99. package/src/lib/model/ai-site-job.d.ts +19 -2
  100. package/src/lib/model/ai-site-job.js +29 -8
  101. package/src/lib/model/ai-site-job.js.map +1 -1
  102. package/src/lib/model/ai-site-kinds.d.ts +4 -1
  103. package/src/lib/model/ai-site-kinds.js +34 -6
  104. package/src/lib/model/ai-site-kinds.js.map +1 -1
  105. package/src/lib/runtime/ai-doctrine-validators.d.ts +37 -1
  106. package/src/lib/runtime/ai-doctrine-validators.js +151 -6
  107. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  108. package/src/lib/runtime/ai-doctrine.js +4 -3
  109. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  110. package/src/lib/runtime/ai-palette.generated.js +53 -7
  111. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  112. package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
  113. package/src/lib/server/ai-jobs-cancel.js +27 -9
  114. package/src/lib/server/ai-jobs-cancel.js.map +1 -1
  115. package/src/lib/server/ai-jobs-gate.d.ts +12 -2
  116. package/src/lib/server/ai-jobs-gate.js +20 -9
  117. package/src/lib/server/ai-jobs-gate.js.map +1 -1
  118. package/src/lib/usage/ai-usage-meter.d.ts +2 -1
  119. package/src/lib/usage/ai-usage-meter.js +17 -1
  120. package/src/lib/usage/ai-usage-meter.js.map +1 -1
  121. package/src/lib/usage/ai-usage-wire.d.ts +6 -0
  122. package/src/lib/usage/ai-usage-wire.js.map +1 -1
  123. package/src/lib/usage/assist-usage.d.ts +7 -0
  124. package/src/lib/usage/assist-usage.js +14 -4
  125. package/src/lib/usage/assist-usage.js.map +1 -1
  126. package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
  127. package/src/lib/usage/free-ai-credits-left.js +4 -2
  128. 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`
@@ -1054,11 +1062,21 @@ function group(scope, block, room) {
1054
1062
  const placed = componentOf(scope.page.targets, block.to);
1055
1063
  if (placed) return instances(scope, placed, items, perRow);
1056
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
+ }
1057
1072
  if (rule1(scope)) {
1058
1073
  var _scope_words;
1059
1074
  // Picture cards the design draws are the compiler's, not a block typed out
1060
- // 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.
1061
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);
1062
1080
  const pictured = (variant === 'pictures' || variant === 'articles') && room === 'full' && picturesLeft(scope.page) >= items.length;
1063
1081
  return pictured && designedGroup(scope, variant, items, room) || compactGroup(scope, block, items, perRow);
1064
1082
  }
@@ -1457,6 +1475,7 @@ function instances(scope, placed, items, perRow) {
1457
1475
  flow[lead] = design.choices.hero === 'split' ? title : _extends({}, title, {
1458
1476
  style: 'large'
1459
1477
  });
1478
+ sellingHero(scope, flow);
1460
1479
  const given = images[0] ? aiLayoutFitText(images[0].text, 'alt') : '';
1461
1480
  return hero(scope, raw, flow, given ? {
1462
1481
  alt: given,
@@ -1859,6 +1878,7 @@ function instances(scope, placed, items, perRow) {
1859
1878
  variant: type
1860
1879
  }, muted(scope), null, 'text') : null;
1861
1880
  if (variant === 'cards') return null;
1881
+ if (variant === 'icons') return iconColumns(scope, items, room);
1862
1882
  const pictured = variant === 'pictures' || variant === 'articles';
1863
1883
  if (variant === 'ruled' || pictured && (room !== 'full' || picturesLeft(page) < items.length)) {
1864
1884
  const perRow = across(items.length, room, 'cards');
@@ -1944,12 +1964,16 @@ function instances(scope, placed, items, perRow) {
1944
1964
  // A portfolio's or a photographer's gallery opens in a lightbox, its
1945
1965
  // pictures one gallery with the item's title as each caption (AGL-3717).
1946
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;
1947
1969
  const photo = designImage(scope, {
1948
1970
  alt: standInAlt(scope, item.title || item.text),
1949
1971
  given: false
1950
1972
  }, _extends({
1951
1973
  width: '100%'
1952
- }, opens ? lightboxProps(scope, item.title) : {}), {
1974
+ }, opens ? lightboxProps(scope, item.title) : {}, shop ? {
1975
+ screenId: shop
1976
+ } : {}), {
1953
1977
  aspectRatio: {
1954
1978
  xs: '4 / 3',
1955
1979
  md: aspect
@@ -2028,6 +2052,11 @@ function listingSection(scope, blocks, placed) {
2028
2052
  var _page_plan_sections_index_name, _page_plan_sections_index, _page_options_sectionIds;
2029
2053
  const { page, index, at } = scope;
2030
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;
2031
2060
  const left = blocks.filter((block)=>LISTING_REPLACES.has(block.kind));
2032
2061
  if (left.length) {
2033
2062
  page.settled.push({
@@ -2035,7 +2064,7 @@ function listingSection(scope, blocks, placed) {
2035
2064
  what: `${left.map((block)=>block.kind).join(', ')} left out: the section lists the site's own ${listing.kind}`
2036
2065
  });
2037
2066
  }
2038
- 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)=>{
2039
2068
  let { col: _col } = _0, block = _object_without_properties_loose(_0, [
2040
2069
  "col"
2041
2070
  ]);
@@ -2049,7 +2078,7 @@ function listingSection(scope, blocks, placed) {
2049
2078
  all
2050
2079
  ];
2051
2080
  }
2052
- if (!words.some((block)=>speaksHeading(scope, block))) {
2081
+ if (!underTitle && !words.some((block)=>speaksHeading(scope, block))) {
2053
2082
  var _page_plan_sections_index1;
2054
2083
  // Every band of records is headed: the plan's name for it, or on a first section the page's title.
2055
2084
  words.unshift({
@@ -2102,7 +2131,13 @@ function listingSection(scope, blocks, placed) {
2102
2131
  } : {}), null, parts, 'content');
2103
2132
  const container = tree.add('muiContainer', {
2104
2133
  maxWidth: 'lg'
2105
- }, {
2134
+ }, underTitle ? {
2135
+ pt: {
2136
+ xs: 2,
2137
+ md: 3
2138
+ },
2139
+ pb: _extends({}, SECTION_PADDING)
2140
+ } : {
2106
2141
  py: index === 0 ? _extends({}, HERO_PADDING) : _extends({}, SECTION_PADDING)
2107
2142
  }, [
2108
2143
  content
@@ -2113,10 +2148,268 @@ function listingSection(scope, blocks, placed) {
2113
2148
  ariaLabel: sectionLabel(name, index)
2114
2149
  }, scope.band === 'dark' ? {
2115
2150
  colorScheme: 'dark'
2116
- } : {}), bandSx(scope.band), [
2151
+ } : {}), underTitle ? null : bandSx(scope.band), [
2117
2152
  container
2118
2153
  ], 'section', (_ref = (_page_options_sectionIds = page.options.sectionIds) == null ? void 0 : _page_options_sectionIds[index]) != null ? _ref : sectionIdOf(index));
2119
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
+ }
2120
2413
  /**
2121
2414
  * The element that lists the records, bound to them: the Product grid over
2122
2415
  * the site's catalog, or Collection Entries over its blog with one post's
@@ -2157,6 +2450,9 @@ function listingSection(scope, blocks, placed) {
2157
2450
  showCategories: true
2158
2451
  }, {
2159
2452
  cardStyle: 'photo',
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,
2160
2456
  // A store that opens before its first products are in says so, with a
2161
2457
  // way to hear when they land (AGL-3676): never placeholder products.
2162
2458
  emptyTitle: AI_LAYOUT_STORE_EMPTY.title,