@aglyn/plugins-ai 1.0.0-beta.284 → 1.0.0-beta.287

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 (210) hide show
  1. package/package.json +17 -17
  2. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  3. package/src/lib/assets/site-styles/professional.webp +0 -0
  4. package/src/lib/assets/site-styles/store.webp +0 -0
  5. package/src/lib/assets/site-styles/trades.webp +0 -0
  6. package/src/lib/components/ai-theme-proposal-card.component.js +12 -3
  7. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  8. package/src/lib/declarations.js +5 -2
  9. package/src/lib/declarations.js.map +1 -1
  10. package/src/lib/declarations.server.js +12 -0
  11. package/src/lib/declarations.server.js.map +1 -1
  12. package/src/lib/jobs/ai-build-capabilities.d.ts +3 -1
  13. package/src/lib/jobs/ai-build-capabilities.js +27 -2
  14. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  15. package/src/lib/jobs/ai-image-reads.d.ts +59 -0
  16. package/src/lib/jobs/ai-image-reads.js +138 -0
  17. package/src/lib/jobs/ai-image-reads.js.map +1 -0
  18. package/src/lib/jobs/ai-job-build-step.d.ts +29 -0
  19. package/src/lib/jobs/ai-job-build-step.js +83 -12
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-duplicate-step.d.ts +2 -1
  22. package/src/lib/jobs/ai-job-duplicate-step.js +6 -6
  23. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-import-step.d.ts +4 -2
  25. package/src/lib/jobs/ai-job-import-step.js +15 -11
  26. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  27. package/src/lib/jobs/ai-job-insight-step.d.ts +10 -0
  28. package/src/lib/jobs/ai-job-insight-step.js +23 -5
  29. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  30. package/src/lib/jobs/ai-job-layout-language.js +2 -1
  31. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-media-step.d.ts +2 -1
  33. package/src/lib/jobs/ai-job-media-step.js +6 -8
  34. package/src/lib/jobs/ai-job-media-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-navigation-step.d.ts +2 -1
  36. package/src/lib/jobs/ai-job-navigation-step.js +7 -9
  37. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
  38. package/src/lib/jobs/ai-job-page-language.js +7 -1
  39. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  40. package/src/lib/jobs/ai-job-page-sections.js +16 -3
  41. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  42. package/src/lib/jobs/ai-job-page-step.js +9 -1
  43. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-plan-step.d.ts +30 -8
  45. package/src/lib/jobs/ai-job-plan-step.js +78 -13
  46. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  47. package/src/lib/jobs/ai-job-privacy-step.d.ts +4 -2
  48. package/src/lib/jobs/ai-job-privacy-step.js +14 -15
  49. package/src/lib/jobs/ai-job-privacy-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-products-step.js +5 -3
  51. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  52. package/src/lib/jobs/ai-job-settings-step.d.ts +2 -1
  53. package/src/lib/jobs/ai-job-settings-step.js +7 -8
  54. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-site-content.d.ts +34 -1
  56. package/src/lib/jobs/ai-job-site-content.js +67 -8
  57. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  58. package/src/lib/jobs/ai-job-site-datasets.d.ts +53 -3
  59. package/src/lib/jobs/ai-job-site-datasets.js +298 -30
  60. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  61. package/src/lib/jobs/ai-job-site-step.js +6 -3
  62. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  63. package/src/lib/jobs/ai-job-site-store-pages.js +3 -3
  64. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -1
  65. package/src/lib/jobs/ai-job-tracking-step.d.ts +2 -1
  66. package/src/lib/jobs/ai-job-tracking-step.js +7 -8
  67. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  68. package/src/lib/jobs/ai-jobs-beat.d.ts +11 -7
  69. package/src/lib/jobs/ai-jobs-beat.js +18 -8
  70. package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
  71. package/src/lib/jobs/ai-jobs-notify.d.ts +66 -1
  72. package/src/lib/jobs/ai-jobs-notify.js +136 -3
  73. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  74. package/src/lib/jobs/ai-layout-stock-photos.d.ts +59 -4
  75. package/src/lib/jobs/ai-layout-stock-photos.js +897 -81
  76. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  77. package/src/lib/jobs/ai-product-image.d.ts +14 -0
  78. package/src/lib/jobs/ai-product-image.js +73 -21
  79. package/src/lib/jobs/ai-product-image.js.map +1 -1
  80. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +9 -1
  81. package/src/lib/jobs/ai-theme-brand-inputs.js +28 -2
  82. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  83. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.d.ts +175 -0
  84. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js +601 -0
  85. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js.map +1 -0
  86. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.d.ts +46 -0
  87. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js +2862 -0
  88. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js.map +1 -0
  89. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.d.ts +74 -0
  90. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js +888 -0
  91. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js.map +1 -0
  92. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.d.ts +29 -0
  93. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js +81 -0
  94. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js.map +1 -0
  95. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.d.ts +87 -0
  96. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js +225 -0
  97. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js.map +1 -0
  98. package/src/lib/layout-language/ai-layout-compiler.d.ts +11 -0
  99. package/src/lib/layout-language/ai-layout-compiler.js +194 -18
  100. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  101. package/src/lib/layout-language/ai-layout-copy.d.ts +37 -0
  102. package/src/lib/layout-language/ai-layout-copy.js +383 -3
  103. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  104. package/src/lib/layout-language/ai-layout-design.d.ts +19 -0
  105. package/src/lib/layout-language/ai-layout-design.js +31 -11
  106. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  107. package/src/lib/layout-language/ai-layout-listings.d.ts +7 -1
  108. package/src/lib/layout-language/ai-layout-listings.js +9 -1
  109. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  110. package/src/lib/layout-language/ai-layout-pictures.d.ts +30 -0
  111. package/src/lib/layout-language/ai-layout-pictures.js +131 -3
  112. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  113. package/src/lib/layout-language/ai-layout-storefront.d.ts +18 -4
  114. package/src/lib/layout-language/ai-layout-storefront.js +100 -20
  115. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -1
  116. package/src/lib/model/ai-build-job.d.ts +7 -0
  117. package/src/lib/model/ai-build-job.js +36 -7
  118. package/src/lib/model/ai-build-job.js.map +1 -1
  119. package/src/lib/model/ai-build-plan.d.ts +28 -4
  120. package/src/lib/model/ai-build-plan.js +49 -9
  121. package/src/lib/model/ai-build-plan.js.map +1 -1
  122. package/src/lib/model/ai-build-progress.js +3 -1
  123. package/src/lib/model/ai-build-progress.js.map +1 -1
  124. package/src/lib/model/ai-job-notice.d.ts +8 -0
  125. package/src/lib/model/ai-job-notice.js +33 -0
  126. package/src/lib/model/ai-job-notice.js.map +1 -1
  127. package/src/lib/model/ai-job-plan-lines.d.ts +2 -1
  128. package/src/lib/model/ai-job-plan-lines.js +6 -1
  129. package/src/lib/model/ai-job-plan-lines.js.map +1 -1
  130. package/src/lib/model/ai-jobs.types.d.ts +8 -0
  131. package/src/lib/model/ai-jobs.types.js.map +1 -1
  132. package/src/lib/model/ai-permissions.d.ts +13 -0
  133. package/src/lib/model/ai-permissions.js +29 -1
  134. package/src/lib/model/ai-permissions.js.map +1 -1
  135. package/src/lib/model/ai-products.d.ts +18 -0
  136. package/src/lib/model/ai-products.js +32 -0
  137. package/src/lib/model/ai-products.js.map +1 -1
  138. package/src/lib/model/ai-site-inspiration.d.ts +3 -2
  139. package/src/lib/model/ai-site-inspiration.js +3 -2
  140. package/src/lib/model/ai-site-inspiration.js.map +1 -1
  141. package/src/lib/plugin.js +4 -13
  142. package/src/lib/plugin.js.map +1 -1
  143. package/src/lib/providers/anthropic.js +9 -2
  144. package/src/lib/providers/anthropic.js.map +1 -1
  145. package/src/lib/providers/catalog.js +14 -5
  146. package/src/lib/providers/catalog.js.map +1 -1
  147. package/src/lib/providers/contract.d.ts +24 -1
  148. package/src/lib/providers/contract.js.map +1 -1
  149. package/src/lib/providers/routing.js +5 -2
  150. package/src/lib/providers/routing.js.map +1 -1
  151. package/src/lib/runtime/ai-dataset-generation.d.ts +99 -1
  152. package/src/lib/runtime/ai-dataset-generation.js +251 -3
  153. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  154. package/src/lib/runtime/ai-dev-replay.d.ts +1 -0
  155. package/src/lib/runtime/ai-dev-replay.js +4 -2
  156. package/src/lib/runtime/ai-dev-replay.js.map +1 -1
  157. package/src/lib/runtime/ai-doctrine-validators.d.ts +47 -3
  158. package/src/lib/runtime/ai-doctrine-validators.js +343 -27
  159. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  160. package/src/lib/runtime/ai-doctrine.js +8 -2
  161. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  162. package/src/lib/runtime/ai-icon-library.d.ts +27 -1
  163. package/src/lib/runtime/ai-icon-library.js +30 -3
  164. package/src/lib/runtime/ai-icon-library.js.map +1 -1
  165. package/src/lib/runtime/ai-products-generation.d.ts +11 -1
  166. package/src/lib/runtime/ai-products-generation.js +51 -4
  167. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  168. package/src/lib/runtime/assist-answer-cache.d.ts +2 -0
  169. package/src/lib/runtime/assist-answer-cache.js +5 -2
  170. package/src/lib/runtime/assist-answer-cache.js.map +1 -1
  171. package/src/lib/server/ai-allotments.js +7 -2
  172. package/src/lib/server/ai-allotments.js.map +1 -1
  173. package/src/lib/server/ai-experiment-versions.js +5 -5
  174. package/src/lib/server/ai-experiment-versions.js.map +1 -1
  175. package/src/lib/server/ai-host-permissions-roster.d.ts +25 -0
  176. package/src/lib/server/ai-host-permissions-roster.js +33 -0
  177. package/src/lib/server/ai-host-permissions-roster.js.map +1 -0
  178. package/src/lib/server/ai-host-permissions.js +8 -6
  179. package/src/lib/server/ai-host-permissions.js.map +1 -1
  180. package/src/lib/server/ai-image-reads-route.d.ts +17 -0
  181. package/src/lib/server/ai-image-reads-route.js +106 -0
  182. package/src/lib/server/ai-image-reads-route.js.map +1 -0
  183. package/src/lib/server/ai-seo-apply.js +5 -5
  184. package/src/lib/server/ai-seo-apply.js.map +1 -1
  185. package/src/lib/server/ai-site-import-route.js +7 -4
  186. package/src/lib/server/ai-site-import-route.js.map +1 -1
  187. package/src/lib/server/assist-build.js +1 -0
  188. package/src/lib/server/assist-build.js.map +1 -1
  189. package/src/lib/server/assist-chat.d.ts +11 -7
  190. package/src/lib/server/assist-chat.js +185 -48
  191. package/src/lib/server/assist-chat.js.map +1 -1
  192. package/src/lib/server/assist-docs-query.d.ts +51 -0
  193. package/src/lib/server/assist-docs-query.js +57 -0
  194. package/src/lib/server/assist-docs-query.js.map +1 -0
  195. package/src/lib/server/assist-jobs-context.d.ts +102 -0
  196. package/src/lib/server/assist-jobs-context.js +227 -0
  197. package/src/lib/server/assist-jobs-context.js.map +1 -0
  198. package/src/lib/server/assist-view-context.d.ts +8 -4
  199. package/src/lib/server/assist-view-context.js +12 -7
  200. package/src/lib/server/assist-view-context.js.map +1 -1
  201. package/src/lib/server.js +12 -0
  202. package/src/lib/server.js.map +1 -1
  203. package/src/lib/tools/ai-dataset-tool.d.ts +11 -1
  204. package/src/lib/tools/ai-dataset-tool.js +24 -7
  205. package/src/lib/tools/ai-dataset-tool.js.map +1 -1
  206. package/src/lib/tools/ai-duplicate-resource-tool.js +5 -6
  207. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  208. package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +0 -32
  209. package/src/lib/components/ai-collaborator-permissions-column.component.js +0 -132
  210. package/src/lib/components/ai-collaborator-permissions-column.component.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-pictures.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DEFAULT_SITE_IMAGES } from '@aglyn/aglyn/app-utils/default-site'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiSiteIsStarterSrc, type AiSiteStarterSlot, type AiStockRefusedPicks } from '../model/ai-site-photos'\nimport { aiLayoutLabel, aiLayoutSpeaksOtherThanEnglish } from './ai-layout-labels'\n\n/**\n * THE PICTURES OF A LANGUAGE PAGE (AGL-3660).\n *\n * The compiler writes a picture slot as a frame holding an icon and an\n * `image` with alt text and no source (`ai-layout-compiler.ts`), and stays\n * pure: it never looks a photo up. This module is what runs after it, on the\n * stored page — it finds each empty slot, decides what kind of picture the\n * slot is, and fills it with a photo the site can serve from its own origin.\n *\n * Three sources fill a slot, in order:\n *\n * 1. A stock photo searched for by the slot's own description and copied into\n * the site's media library (`jobs/ai-layout-stock-photos.ts`), when the\n * deployment has a stock-photo key.\n * 2. Where the library failed the slot — down, refusing, out of time, or its\n * photo not copied — one of the photos the same job already placed on\n * this site, on its other pages, its datasets' records or this page's\n * other slots ({@link aiLayoutJobPhotoPicks}, AGL-3988): each was found\n * for the site's own world, so a wedding photographer's home page shows a\n * wedding and never an office.\n * 3. Where the job has no photo of its own — no stock-photo key, or the\n * library failed it before the job copied any — one of the\n * STARTER PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`,\n * served at `/_static/starter/` by the tenant and the console alike,\n * credited in the CREDITS file beside them). They are the platform's own\n * files, so a visitor's browser asks no other host for them, and a\n * starter reads better than no picture when there is no library. On a\n * site start, which never places a photo its page already shows, a slot\n * none of the job's other photos fits yet keeps one too, and the start's\n * late photo pass gives it one of the job's own photos once every page\n * is built (`jobs/ai-site-late-photos.ts`).\n *\n * Where the library answered and nothing in it fits a slot\n * ({@link AI_LAYOUT_NO_PHOTO}), the slot never takes a starter: the beta.257\n * Copper Kettle Catering start (2026-10-15) opened a barbecue caterer's home\n * page on the starters' pottery workbench (AGL-3831). Nor does it ship empty\n * (AGL-3856): its picture is taken out, frame and all\n * ({@link aiLayoutDropEmptyPictures}). A cover's frame is a black scrim laid\n * over the whole section, and with nothing in it the beta.261 Synchronicity\n * Gym hero read as a solid black block; a split's picture card is a rounded\n * gray box, and the beta.262 Adult Hybrid Learning Center hero read as\n * broken. Without them the section shows its own band, a cover's section\n * still pinned to the site's dark scheme so the words written for a dark\n * photo stay readable, and a split's words take the row's width.\n *\n * A slot keeps the alt text the model wrote only when it describes the photo\n * placed: a stock photo that is not what it says is described by what it is\n * (`aiStockPhotoAlt`), and a starter photo always by its own words, since\n * the model never wrote for it (AGL-3831). A stand-in the compiler drew where\n * the model described no picture is decorative while empty; the photo placed\n * there is described by its own words and is no longer decorative (AGL-3856).\n */\n\n/** What a picture slot is for, read from where it sits and what it says. */\nexport type AiLayoutPictureRole = 'hero' | 'about' | 'gallery'\n\n/** One empty picture slot of a stored page, in document order. */\nexport interface AiLayoutPictureSlot {\n /** The `image` node. */\n imageId: string\n /** The frame around it, which sets the slot's shape. */\n frameId: string | null\n /** The placeholder icon sharing the frame, taken out once a photo fills it. */\n iconId: string | null\n alt: string\n /** The frame's shape as width over height; 1 when it names none. */\n aspect: number\n /** The plan index of the section it sits in, or -1. */\n sectionIndex: number\n role: AiLayoutPictureRole\n /**\n * A product's photo (AGL-3676): what it is of, most specific first — its\n * name, then what its photo should show — searched for with the shop's\n * category and held to it, so a wick trimmer is never a laptop on a desk.\n */\n product?: { subjects: readonly string[] }\n /**\n * A stand-in the compiler drew where the model described no picture\n * (AGL-3856): its words are what a search looks for, never what a photo\n * shows, so a photo placed there is described by its own, and is no longer\n * decorative once it is.\n */\n standIn?: boolean\n /**\n * A portrait of one of the business's people (AGL-3856): a record of a\n * team, staff or instructors list. Its photo shows an adult, in the role\n * its words or its list name where one is listed, never a child or a thing.\n */\n people?: boolean\n /**\n * A dataset record's cover (AGL-3856): what the record is filed under — the\n * value of its list's category-like field (\"Weddings and elopements\") —\n * read after its own name for what its photo is OF. A photographer's record\n * is the photographed moment, never the photographer or the camera.\n */\n record?: { category?: string }\n}\n\n/** A photo a slot is filled with. */\nexport interface AiLayoutPicturePhoto {\n /** The site-served address: a starter path or the media library's own. */\n src: string\n width: number\n height: number\n /**\n * What the photo shows, where the slot's own alt text does not say it: the\n * slot takes it in place of the words the model wrote before the photo was\n * found. Absent, the slot's words describe it.\n */\n alt?: string\n}\n\n/** Words that say a picture shows people, or a section is about them. */\nconst PEOPLE =\n /\\b(team|story|owner|owners|founder|founders|staff|crew|people|person|portrait|meet|family|families|who we are|our (?:team|crew|people|story)|therapist|counsel(?:l)?or|coach|doctor|dentist|stylist|trainer|smil(?:e|es|ing)|headshot)\\b/i\n\n/**\n * \"About\" as what a section or a picture is (AGL-3923): leading its words\n * (\"About us\", \"About the studio\"), never the preposition inside them. The\n * beta.267 Brightvale closing band, \"Ready to talk about your marketing?\",\n * was read as a picture of people and searched \"ready talk marketing\" among\n * portraits, which answered a tin-can telephone.\n */\nconst ABOUT = /^\\W*about\\b/i\n\n/**\n * An `aspectRatio` (`'16 / 9'`) as a number, or `null` where none is named.\n * A responsive one (`{ xs: '4 / 3', md: '4 / 5' }`, as a designed page's\n * pictures carry, AGL-3660) reads at the desktop breakpoint first: one photo\n * serves every width, and the wide screen is where its shape shows most.\n */\nfunction aspectOf(value: unknown): number | null {\n if (value && typeof value === 'object') {\n const byBreakpoint = value as Record<string, unknown>\n for (const breakpoint of ['md', 'lg', 'xl', 'sm', 'xs']) {\n const ratio = aspectOf(byBreakpoint[breakpoint])\n if (ratio !== null) return ratio\n }\n return null\n }\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*\\/\\s*(\\d+(?:\\.\\d+)?)\\s*$/.exec(String(value ?? ''))\n if (!match) return null\n const ratio = Number(match[1]) / Number(match[2])\n return Number.isFinite(ratio) && ratio > 0 ? ratio : null\n}\n\n/** The shape a full-bleed cover's photo is searched for: it fills a wide band. */\nconst COVER_ASPECT = 16 / 9\n\n/**\n * A slot's shape (AGL-3660): the image's own `aspectRatio` (a designed\n * picture card sets it on the image), else its frame's, else — for a frame\n * laid absolutely over its whole section, a designed photo cover — a wide\n * band's. 1 where nothing says.\n */\nfunction slotAspect(image: { sx?: Record<string, unknown> } | undefined, frame: { sx?: Record<string, unknown> } | undefined): number {\n const own = aspectOf(image?.sx?.['aspectRatio']) ?? aspectOf(frame?.sx?.['aspectRatio'])\n if (own !== null) return own\n const sx = frame?.sx\n if (sx?.['position'] === 'absolute' && sx['width'] === '100%' && sx['height'] === '100%') return COVER_ASPECT\n return 1\n}\n\n/** A slot's role: the first section's picture is the hero; people make an about picture. */\nexport function aiLayoutPictureRole(input: {\n sectionIndex: number\n sectionName: string\n alt: string\n}): AiLayoutPictureRole {\n if (input.sectionIndex === 0) return 'hero'\n if (PEOPLE.test(input.sectionName) || PEOPLE.test(input.alt) || ABOUT.test(input.sectionName) || ABOUT.test(input.alt)) return 'about'\n return 'gallery'\n}\n\n/**\n * Every empty picture slot of a stored page, walked from its root in\n * document order. A slot that already has a source — one the owner placed, or\n * an earlier pass filled — is not a slot.\n */\nexport function aiLayoutPictureSlots(\n nodes: NodesMap,\n rootId: string,\n sections: { ids: readonly string[]; names: readonly string[] },\n): AiLayoutPictureSlot[] {\n const map = nodes as unknown as Record<\n string,\n { componentId?: string; props?: Record<string, unknown>; sx?: Record<string, unknown>; nodes?: string[] }\n >\n const slots: AiLayoutPictureSlot[] = []\n const seen = new Set<string>()\n const visit = (id: string, parentId: string | null, sectionIndex: number) => {\n if (seen.has(id)) return\n seen.add(id)\n const node = map[id]\n if (!node) return\n const index = sections.ids.indexOf(id)\n const here = index >= 0 ? index : sectionIndex\n if (node.componentId === 'image') {\n const src = node.props?.['src']\n const alt = typeof node.props?.['alt'] === 'string' ? (node.props['alt'] as string).trim() : ''\n if ((typeof src !== 'string' || !src.trim()) && alt) {\n const frame = parentId ? map[parentId] : undefined\n const iconId =\n frame?.nodes?.find((child) => child !== id && map[child]?.componentId === 'icon') ?? null\n slots.push({\n imageId: id,\n frameId: frame ? parentId : null,\n iconId,\n alt,\n aspect: slotAspect(node, frame),\n sectionIndex: here,\n ...(node.props?.['decorative'] === true ? { standIn: true } : {}),\n role: aiLayoutPictureRole({\n sectionIndex: here,\n sectionName: here >= 0 ? (sections.names[here] ?? '') : '',\n alt,\n }),\n })\n }\n }\n for (const child of node.nodes ?? []) visit(child, id, here)\n }\n visit(rootId, null, -1)\n return slots\n}\n\n/** The starter photos, by name, each with the words that describe it. */\nexport const AI_LAYOUT_STARTER_PHOTOS = DEFAULT_SITE_IMAGES\n\n/**\n * A source's answer for a slot the library answered and nothing in it fits\n * (AGL-3831): the slot keeps its no-photo treatment rather than take a\n * starter. Its source is empty, so a caller reading `src` to fill something\n * that must hold a photo (a product's, a record's) falls back as it would on\n * `null`.\n */\nexport const AI_LAYOUT_NO_PHOTO: Readonly<AiLayoutPicturePhoto> = Object.freeze({ src: '', width: 0, height: 0 })\n\ntype StarterName = keyof typeof AI_LAYOUT_STARTER_PHOTOS\n\n/**\n * The starter photos each role draws from, best first. A hero draws from the\n * wide photos that read as a page's opening, an about picture leads with the\n * owner, and the rest are the gallery's.\n */\nconst STARTER_POOLS: Readonly<Record<AiLayoutPictureRole, readonly StarterName[]>> = {\n hero: ['hero', 'desk', 'craft'],\n about: ['about', 'hero'],\n gallery: ['desk', 'plants', 'craft'],\n}\n\nconst ALL_STARTERS = Object.keys(AI_LAYOUT_STARTER_PHOTOS) as StarterName[]\n\n/** A small stable number from a seed, so the same job always picks the same photos. */\nexport function aiLayoutSeedNumber(seed: string): number {\n let hash = 2166136261\n for (let index = 0; index < seed.length; index += 1) {\n hash ^= seed.charCodeAt(index)\n hash = Math.imul(hash, 16777619)\n }\n return hash >>> 0\n}\n\nfunction rotate<T>(list: readonly T[], by: number): T[] {\n if (!list.length) return []\n const start = by % list.length\n return [...list.slice(start), ...list.slice(0, start)]\n}\n\n/**\n * A starter photo for each slot (AGL-3660). No photo repeats on a page while\n * an unused one remains, and every pool turns by the seed, so sites do not\n * all open with the same photo while one job always picks the same.\n *\n * The slots are served by what they are for, not where they sit: the hero\n * first, then the pictures of people (an about picture leads with the owner),\n * then the rest. The live Juniper Clay start (2026-10-08) had six slots and\n * five starters, and the owner's photo went to a stoneware card before the\n * About section that described the artist asked for it. Only a page with more\n * slots than starters repeats one: the photo placed longest ago, never the\n * one in a slot beside it. The owner's portrait goes only to a picture of\n * people; a gallery picture never takes it, even to avoid a repeat.\n */\nexport function aiLayoutStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): AiLayoutPicturePhoto[] {\n const turn = aiLayoutSeedNumber(seed)\n const order: AiLayoutPictureRole[] = ['hero', 'about', 'gallery']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const names: Array<StarterName | undefined> = slots.map(() => undefined)\n /** When each starter was last placed, by its turn in the serving order. */\n const placedAt = new Map<StarterName, number>()\n served.forEach(({ slot, index }, step) => {\n // An about picture leads with the owner on every site; the others turn.\n const pool = slot.role === 'about' ? [...STARTER_POOLS.about] : rotate(STARTER_POOLS[slot.role], turn)\n const rest = rotate(\n ALL_STARTERS.filter((name) => !pool.includes(name)),\n turn,\n )\n // The owner's portrait is a picture of a person, and only a picture of\n // people takes it: never a vase in a gallery (AGL-3660). A portrait of\n // one of the business's people takes only people, repeated before a\n // workbench stands in for a teacher (AGL-3856).\n const candidates = slot.people\n ? [...STARTER_POOLS.about]\n : [...pool, ...rest].filter((name) => slot.role === 'about' || name !== 'about')\n const unused = candidates.find((candidate) => !placedAt.has(candidate))\n const beside = new Set([names[index - 1], names[index + 1]].filter(Boolean))\n const name =\n unused ??\n [...candidates]\n .sort((a, b) => (placedAt.get(a) ?? -1) - (placedAt.get(b) ?? -1))\n .find((candidate) => !beside.has(candidate)) ??\n candidates[0]\n names[index] = name\n placedAt.set(name, step)\n })\n return names.map((name) => {\n const photo = AI_LAYOUT_STARTER_PHOTOS[name as StarterName]\n return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n}\n\n/**\n * The starters that show no one's business (AGL-3923): a team at a table in a\n * bright office, a tidy desk. Never the workbench, the coffee shop's owner or\n * the houseplants, which read as another business's own photos (AGL-3831).\n */\nconst NEUTRAL_STARTERS: readonly StarterName[] = ['hero', 'desk']\n\n/**\n * A neutral starter for each of a list's records (AGL-3988), where the site\n * has a library and the job has no photo of its own: the two taken in turn\n * from the seed, so no record shows the photo of the one beside it. A card\n * of a listing cannot drop its frame as a page's picture does, so every\n * record takes one. Pure.\n */\nexport function aiLayoutNeutralStarterRun(count: number, seed: string): AiLayoutPicturePhoto[] {\n const pool = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(seed))\n return Array.from({ length: Math.max(0, count) }, (_, index) => {\n const photo = AI_LAYOUT_STARTER_PHOTOS[pool[index % pool.length] as StarterName]\n return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n}\n\n/**\n * A neutral starter for each slot a library could not fill, where the site\n * has a library (AGL-3923): each at most once on a page, the hero served\n * first and each pool turned by the seed, and none for a portrait of the\n * business's people or a product, nor for a slot past the two. A slot given\n * none keeps the no-photo treatment.\n */\nexport function aiLayoutNeutralStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): Array<AiLayoutPicturePhoto | null> {\n const pool = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(seed))\n const order: AiLayoutPictureRole[] = ['hero', 'gallery', 'about']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot }) => !slot.people && !slot.product)\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n served.forEach(({ index }, step) => {\n const name = pool[step]\n if (!name) return\n const photo = AI_LAYOUT_STARTER_PHOTOS[name]\n photos[index] = { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n return photos\n}\n\n/** A text's words a photo's own words may share: lowercase, four letters or more. */\nconst wordsOf = (text: string): Set<string> =>\n new Set(\n text\n .toLowerCase()\n .split(/[^a-zÀ-ɏ]+/)\n .filter((word) => word.length >= 4),\n )\n\n/**\n * The job's own photos for the slots a library could not fill (AGL-3988):\n * the photos THIS job already placed on this site — on its other pages, on\n * its datasets' records, and on this page — which were each found for the\n * site's own world, before any starter. The beta.270 Sugar Hollow\n * Photography start (2026-10-19) opened a wedding photographer's home page\n * on the starters' desk and an office team, and filed five wedding galleries\n * under a workbench, a windowsill and the same office, while the library\n * had already given the job three wedding and family photos.\n *\n * Each slot `need` names takes the one that fits it best: one this page does\n * not show yet before one it does, a wide photo for a hero, the shape\n * nearest its frame, and the most words shared with what the slot says it\n * shows; never the photo of the slot beside it. A product's photo and a\n * portrait of the business's people are of one named thing, which no other\n * photo is, so they take none. Among equals the seed picks, so one job\n * always picks the same. `null` for a slot given none. Pure.\n *\n * `repeats` is a site start's (AGL-3988), where a photo shown twice on one\n * page reads as a defect: `'never'` when its page is built, where a slot no\n * photo the page does not show yet fits keeps a starter for the start's\n * late photo pass; `'last'` in that pass, where the other choice is a starter\n * of another business, so a photo the page does not show comes first, then\n * one it shows elsewhere, and the photo beside the slot only after that.\n */\nexport function aiLayoutJobPhotoPicks(\n slots: readonly AiLayoutPictureSlot[],\n need: readonly boolean[],\n jobPhotos: readonly AiLayoutPicturePhoto[],\n placed: ReadonlyArray<AiLayoutPicturePhoto | null | undefined>,\n seed: string,\n options: { repeats?: 'never' | 'last' } = {},\n): Array<AiLayoutPicturePhoto | null> {\n const seen = new Set<string>()\n const pool = jobPhotos.filter((photo) => {\n if (!photo.src || !(photo.width > 0) || !(photo.height > 0) || seen.has(photo.src)) return false\n seen.add(photo.src)\n return true\n })\n const picks: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n if (!pool.length) return picks\n const shown = slots.map((_slot, index) => placed[index]?.src || '')\n const order: AiLayoutPictureRole[] = ['hero', 'about', 'gallery']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot, index }) => need[index] && !slot.product && !slot.people)\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const turn = aiLayoutSeedNumber(seed)\n for (const { slot, index } of served) {\n const beside = new Set([shown[index - 1], shown[index + 1]].filter(Boolean))\n const says = wordsOf(slot.alt)\n let best: { photo: AiLayoutPicturePhoto; score: number; rank: number } | null = null\n for (const [at, photo] of pool.entries()) {\n const nextTo = beside.has(photo.src)\n const uses = shown.filter((src) => src === photo.src).length\n if (nextTo && options.repeats !== 'last') continue\n if (uses && options.repeats === 'never') continue\n const aspect = photo.width / photo.height\n const shared = [...wordsOf(photo.alt ?? '')].filter((word) => says.has(word)).length\n const score =\n (nextTo ? -10_000 : 0) -\n (options.repeats === 'last' ? 1_000 : 100) * uses +\n (slot.role === 'hero' && aspect >= 1.3 ? 20 : 0) -\n 10 * Math.abs(Math.log(aspect / (slot.aspect > 0 ? slot.aspect : 1))) +\n 4 * shared\n // The seed turns the pool, so equals are taken in a per-job order.\n const rank = (at - (turn % pool.length) + pool.length) % pool.length\n if (!best || score > best.score || (score === best.score && rank < best.rank)) best = { photo, score, rank }\n }\n if (!best) continue\n picks[index] = { ...best.photo }\n shown[index] = best.photo.src\n }\n return picks\n}\n\n/**\n * The page with each slot filled by its photo: the `image` given its source,\n * its intrinsic size and how it loads (the hero eagerly, as the starter home\n * page loads its own), and the placeholder icon taken out of the frame. The\n * alt text is kept as the model wrote it unless the photo brings its own\n * words. A slot given no photo is left as the compiler wrote it.\n */\nexport function aiLayoutPlacePictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n): NodesMap {\n const next = { ...(nodes as unknown as Record<string, Record<string, unknown>>) }\n // Only the page's first picture loads eagerly (rule 16, AGL-3660): a hero\n // of several photos loaded them all eagerly, and a live Portfolio page was\n // refused for it (2026-10-10). The slots are in document order; every later\n // picture leaves `loading` unset, which the renderer loads when reached.\n const lead = slots.findIndex((_, index) => Boolean(photos[index] && next[slots[index].imageId]))\n slots.forEach((slot, index) => {\n const photo = photos[index]\n const node = next[slot.imageId]\n if (!photo || !node) return\n const props = { ...((node['props'] as Record<string, unknown> | undefined) ?? {}) }\n delete props['loading']\n // A photo placed is described by its words, never hidden from a screen\n // reader: a stand-in was decorative only while it showed nothing (AGL-3856).\n // One that brings no words of its own, such as an owner's upload with no\n // alt text, keeps the slot's (AGL-3979): a picture a visitor sees always\n // has a description.\n const alt = photo.alt?.trim() || (typeof props['alt'] === 'string' ? props['alt'].trim() : '')\n if (alt) delete props['decorative']\n next[slot.imageId] = {\n ...node,\n props: {\n ...props,\n src: photo.src,\n intrinsicWidth: photo.width,\n intrinsicHeight: photo.height,\n ...(alt ? { alt } : {}),\n ...(index === lead && slot.role === 'hero' ? { loading: 'eager' } : {}),\n },\n }\n if (slot.iconId && slot.frameId && next[slot.frameId]) {\n const frame = next[slot.frameId]\n next[slot.frameId] = {\n ...frame,\n nodes: ((frame['nodes'] as string[] | undefined) ?? []).filter((child) => child !== slot.iconId),\n }\n delete next[slot.iconId]\n }\n })\n return next as unknown as NodesMap\n}\n\n/** Whether a node is a grid row: a `muiGrid` container. */\nconst isGridRow = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && (node['props'] as Record<string, unknown> | undefined)?.['container'] === true\n\n/** Whether a node is a column of a grid row: a sized `muiGrid` item. */\nconst isGridColumn = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && !isGridRow(node)\n\n/**\n * The page without the pictures left with no photo (AGL-3856). A slot whose\n * photo is missing loses its image, its icon and its frame where the frame\n * holds nothing else, and so does every wrapper that holds nothing once it\n * is gone, up to its section. A cover's black scrim over nothing read as a\n * solid black block (the beta.261 Synchronicity Gym hero), and an empty\n * rounded gray card as a broken picture (the beta.262 Adult Hybrid Learning\n * Center hero). A column of a split row taken out leaves the words' column\n * the row's full width. A cover's section stays pinned to the site's dark\n * scheme, which paints that scheme's background and text colors, so the\n * words written for a dark photo stay readable. A slot a photo fills, a\n * starter's included, is kept.\n */\nexport function aiLayoutDropEmptyPictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n sectionIds: readonly string[] = [],\n): NodesMap {\n const map = nodes as unknown as Record<string, Record<string, unknown>>\n const empty = slots.filter((slot, index) => {\n if (photos[index]?.src) return false\n const src = (map[slot.imageId]?.['props'] as Record<string, unknown> | undefined)?.['src']\n return Boolean(map[slot.imageId]) && !(typeof src === 'string' && src.trim())\n })\n if (!empty.length) return nodes\n const next = { ...map }\n const children = (id: string) => (next[id]?.['nodes'] as string[] | undefined) ?? []\n const parentOf = (id: string) => Object.keys(next).find((key) => children(key).includes(id)) ?? null\n const remove = (id: string) => {\n const parent = parentOf(id)\n if (parent) next[parent] = { ...next[parent], nodes: children(parent).filter((child) => child !== id) }\n delete next[id]\n return parent\n }\n const sections = new Set(sectionIds)\n for (const slot of empty) {\n const own = new Set([slot.imageId, slot.iconId].filter(Boolean) as string[])\n const frameHoldsOnly = Boolean(slot.frameId && next[slot.frameId] && children(slot.frameId).every((child) => own.has(child)))\n let parent = remove(slot.imageId)\n if (slot.iconId && next[slot.iconId]) remove(slot.iconId)\n if (!frameHoldsOnly) continue\n // The frame, then each wrapper it leaves empty: never a section, nor the page's root.\n let at: string | null = slot.frameId\n while (at && next[at] && !children(at).length && !sections.has(at) && parentOf(at) !== null) {\n const column = isGridColumn(next[at])\n parent = remove(at)\n at = column ? null : parent\n }\n // A split row left with one column gives its words the row's width.\n if (parent && isGridRow(next[parent])) {\n const [only, ...rest] = children(parent)\n if (only && !rest.length && isGridColumn(next[only])) {\n const column = next[only] as Record<string, unknown>\n const sx = { ...((column['sx'] as Record<string, unknown> | undefined) ?? {}) }\n delete sx['order']\n next[only] = {\n ...column,\n props: { ...((column['props'] as Record<string, unknown> | undefined) ?? {}), size: 'xs:12' },\n sx: Object.keys(sx).length ? sx : null,\n }\n }\n }\n }\n return next as unknown as NodesMap\n}\n\n/**\n * A source of photos found for the slots themselves — a stock search copied\n * into the site's library — answering per slot, in order: a photo; or\n * {@link AI_LAYOUT_NO_PHOTO} where the library answered and nothing fits,\n * which leaves the slot without a photo; or `null` where the library could\n * not be asked, which takes a starter photo. It may throw, and then every\n * slot takes a starter.\n */\nexport type AiLayoutPictureSource = (\n slots: readonly AiLayoutPictureSlot[],\n report?: AiLayoutPictureReport,\n) => Promise<ReadonlyArray<AiLayoutPicturePhoto | null>>\n\n/**\n * What a source tells its caller beside its answers (AGL-3988): for each slot\n * whose fitting photo the library's image hosts would not hand over, the\n * picks it kept in hand and the search they came from, so the site start's\n * late photo pass can ask for them again once the burst is over. Keyed by\n * the slot itself, which every source hands on unchanged.\n */\nexport interface AiLayoutPictureReport {\n refused: Map<AiLayoutPictureSlot, AiStockRefusedPicks>\n}\n\n/**\n * Fills a stored page's empty picture slots (AGL-3660): from `source` where\n * one is given and it answers; a slot it answers with nothing fitting stays\n * without a photo and is taken out ({@link aiLayoutDropEmptyPictures}). A\n * slot it could not answer — the library down, out of time, or its photo not\n * copied — takes one of the photos the job already placed on this site or\n * this page just copied (`jobPhotos`, {@link aiLayoutJobPhotoPicks},\n * AGL-3988), and only where the job has none, a starter photo described by\n * its own words (AGL-3831). On a site start (`onStarters`), a photo the\n * page already shows is never placed again, a slot none of the others fits\n * yet keeps a neutral starter too, and `onStarters` is told of every\n * picture left on one, for the late photo pass. Never throws, and\n * never leaves a page worse than it came: a picture is decoration, and a\n * job never fails over one.\n */\nexport async function aiResolveLayoutPictures(\n nodes: NodesMap,\n input: {\n rootId: string\n sectionIds: readonly string[]\n sectionNames: readonly string[]\n /** A per-job word, so two sites do not open with the same photo. */\n seed: string\n source?: AiLayoutPictureSource | null\n /**\n * The photos this job already placed on this site (AGL-3988): its other\n * pages' and its datasets' records', each found for the site's world.\n * Taken before any starter where `source` could not fill a slot.\n */\n jobPhotos?: readonly AiLayoutPicturePhoto[]\n /** The site's language (AGL-3750); a starter photo's English words are not its own. */\n language?: string\n /**\n * Told the pictures left on a starter where the site has a library\n * (AGL-3988), with the picks the image hosts refused for each: what the\n * site start's late photo pass fills from the job's own photos. Given\n * only where such a pass follows: a failed slot then never takes a photo\n * the page already shows, and one none of the job's other photos fits\n * yet keeps a neutral starter for that pass, rather than lose its frame.\n */\n onStarters?: (slots: AiSiteStarterSlot[]) => void\n },\n): Promise<NodesMap> {\n try {\n const slots = aiLayoutPictureSlots(nodes, input.rootId, {\n ids: input.sectionIds,\n names: input.sectionNames,\n })\n if (!slots.length) return nodes\n let found: ReadonlyArray<AiLayoutPicturePhoto | null> = []\n const report: AiLayoutPictureReport = { refused: new Map() }\n if (input.source) {\n try {\n found = await input.source(slots, report)\n } catch (error) {\n console.warn('ai layout pictures: the stock source failed; the job’s own photos or starters fill the page', { error })\n found = []\n }\n }\n // The starters' words are English; a site in another language says it\n // shows an illustrative photo, true of any photo, as a stock photo's does.\n const otherLanguage = aiLayoutSpeaksOtherThanEnglish(input.language)\n const said = (photo: AiLayoutPicturePhoto): AiLayoutPicturePhoto =>\n otherLanguage ? { ...photo, alt: aiLayoutLabel('illustrativePhoto', input.language) } : photo\n /** The slots the source could not answer: no library, or it failed them. */\n const unanswered = slots.map((_slot, index) => !found[index])\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map((_slot, index) => {\n const answer = found[index]\n return answer?.src ? answer : null\n })\n // With a library, the job's own photos come before any starter (AGL-3988),\n // and a starter is only ever a neutral one (AGL-3923): the beta.267\n // Brightvale start, whose library refused its copies, showed a pottery\n // bench and a coffee shop's owner as a marketer's pictures. The job's\n // own are its other pages' and its records' photos, and the ones this\n // page just copied for its other slots: the beta.271 Laurel Fork home\n // page, the first page built, copied a wide wedding couple and a family\n // for two of its slots while its hero's picks were refused, and opened\n // on the starters' office team. A photo the page does not show yet is\n // still preferred, and never the one beside it.\n const own = input.source\n ? [...(input.jobPhotos ?? []), ...photos.filter((photo): photo is AiLayoutPicturePhoto => Boolean(photo))].filter(\n (photo) => photo.src && photo.width > 0 && photo.height > 0,\n )\n : []\n // On a site start (`onStarters`) a photo the page already shows is never\n // placed again here (AGL-3988): the beta.271 Laurel Fork home page opened\n // on the wedding couple its closing band shows. Such a slot keeps a\n // starter, and the start's late photo pass fills it.\n const lateStart = Boolean(input.source && input.onStarters)\n if (own.length) {\n aiLayoutJobPhotoPicks(slots, unanswered, own, photos, input.seed, lateStart ? { repeats: 'never' } : {}).forEach((pick, index) => {\n if (pick && unanswered[index]) photos[index] = pick\n })\n }\n // A failed slot takes a neutral starter only where the job had no photo\n // of its own before this page, as #1831 had it. On a site start, where\n // the job has photos of its own, every failed slot none of them fits yet\n // keeps one: that start's late photo pass gives it one of the job's own\n // once every page is built (AGL-3988), told of it by `onStarters`.\n // Anywhere else it keeps the no-photo treatment. With no library, every\n // starter, as before.\n const hadOwn = (input.jobPhotos ?? []).some((photo) => photo.src && photo.width > 0 && photo.height > 0)\n const starters = !input.source\n ? aiLayoutStarterPhotos(slots, input.seed)\n : lateStart && own.length\n ? aiLayoutNeutralStarterRun(slots.length, input.seed).map((photo, index) =>\n slots[index]?.people || slots[index]?.product ? null : photo,\n )\n : hadOwn && !lateStart\n ? []\n : aiLayoutNeutralStarterPhotos(slots, input.seed)\n starters.forEach((photo, index) => {\n if (photo && unanswered[index] && !photos[index]) photos[index] = said(photo)\n })\n // The page floor (AGL-3923; Zach, 2026-10-12: a starter photo before no\n // photo at all): a page the library answered that would show no picture\n // keeps one, in its hero, else its first slot: one of the job's own\n // photos where it has any (AGL-3988), else a neutral starter.\n if (input.source && !photos.some((photo) => photo?.src)) {\n const at = Math.max(0, slots.findIndex((slot) => slot.role === 'hero'))\n const lead = { ...(slots[at] as AiLayoutPictureSlot), product: undefined, people: undefined }\n const [pick] = aiLayoutJobPhotoPicks([lead], [true], own, [null], input.seed)\n const [floor] = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(input.seed))\n const starter = floor ? AI_LAYOUT_STARTER_PHOTOS[floor] : null\n if (pick) photos[at] = pick\n else if (starter) photos[at] = said({ src: starter.src, width: starter.width, height: starter.height, alt: starter.alt })\n }\n // The pictures left on a starter, for the late photo pass (AGL-3988).\n if (input.source && input.onStarters) {\n const left = slots.flatMap((slot, index): AiSiteStarterSlot[] => {\n if (!aiSiteIsStarterSrc(photos[index]?.src)) return []\n const refused = report.refused.get(slot)\n return [{ image: slot.imageId, words: slot.alt, role: slot.role, aspect: slot.aspect, ...(refused ? { refused } : {}) }]\n })\n if (left.length) input.onStarters(left)\n }\n return aiLayoutDropEmptyPictures(aiLayoutPlacePictures(nodes, slots, photos), slots, photos, input.sectionIds)\n } catch (error) {\n console.error('ai layout pictures: the page keeps its empty picture slots', { error })\n return nodes\n }\n}\n"],"names":["DEFAULT_SITE_IMAGES","aiSiteIsStarterSrc","aiLayoutLabel","aiLayoutSpeaksOtherThanEnglish","PEOPLE","ABOUT","aspectOf","value","byBreakpoint","breakpoint","ratio","match","exec","String","Number","isFinite","COVER_ASPECT","slotAspect","image","frame","own","sx","aiLayoutPictureRole","input","sectionIndex","test","sectionName","alt","aiLayoutPictureSlots","nodes","rootId","sections","map","slots","seen","Set","visit","id","parentId","node","has","add","index","ids","indexOf","here","componentId","src","props","trim","undefined","iconId","find","child","push","imageId","frameId","aspect","standIn","role","names","AI_LAYOUT_STARTER_PHOTOS","AI_LAYOUT_NO_PHOTO","Object","freeze","width","height","STARTER_POOLS","hero","about","gallery","ALL_STARTERS","keys","aiLayoutSeedNumber","seed","hash","length","charCodeAt","Math","imul","rotate","list","by","start","slice","aiLayoutStarterPhotos","turn","order","served","slot","sort","a","b","placedAt","Map","forEach","step","unused","pool","rest","filter","name","includes","candidates","people","candidate","beside","Boolean","get","set","photo","NEUTRAL_STARTERS","aiLayoutNeutralStarterRun","count","Array","from","max","_","aiLayoutNeutralStarterPhotos","product","photos","wordsOf","text","toLowerCase","split","word","aiLayoutJobPhotoPicks","need","jobPhotos","placed","options","picks","shown","_slot","says","best","at","entries","nextTo","uses","repeats","shared","score","abs","log","rank","aiLayoutPlacePictures","next","lead","findIndex","intrinsicWidth","intrinsicHeight","loading","isGridRow","isGridColumn","aiLayoutDropEmptyPictures","sectionIds","empty","children","parentOf","key","remove","parent","frameHoldsOnly","every","column","only","size","aiResolveLayoutPictures","sectionNames","found","report","refused","source","error","console","warn","otherLanguage","language","said","unanswered","answer","lateStart","onStarters","pick","hadOwn","some","starters","floor","starter","left","flatMap","words"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-pictures.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DEFAULT_SITE_IMAGES } from '@aglyn/aglyn/app-utils/default-site'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiSiteIsStarterSrc, type AiSiteStarterSlot, type AiStockRefusedPicks } from '../model/ai-site-photos'\nimport { aiLayoutLabel, aiLayoutSpeaksOtherThanEnglish } from './ai-layout-labels'\n\n/**\n * THE PICTURES OF A LANGUAGE PAGE (AGL-3660).\n *\n * The compiler writes a picture slot as a frame holding an icon and an\n * `image` with alt text and no source (`ai-layout-compiler.ts`), and stays\n * pure: it never looks a photo up. This module is what runs after it, on the\n * stored page — it finds each empty slot, decides what kind of picture the\n * slot is, and fills it with a photo the site can serve from its own origin.\n *\n * Three sources fill a slot, in order:\n *\n * 1. A stock photo searched for by the slot's own description and copied into\n * the site's media library (`jobs/ai-layout-stock-photos.ts`), when the\n * deployment has a stock-photo key.\n * 2. Where the library failed the slot — down, refusing, out of time, or its\n * photo not copied — one of the photos the same job already placed on\n * this site, on its other pages, its datasets' records or this page's\n * other slots ({@link aiLayoutJobPhotoPicks}, AGL-3988): each was found\n * for the site's own world, so a wedding photographer's home page shows a\n * wedding and never an office.\n * 3. Where the job has no photo of its own — no stock-photo key, or the\n * library failed it before the job copied any — one of the\n * STARTER PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`,\n * served at `/_static/starter/` by the tenant and the console alike,\n * credited in the CREDITS file beside them). They are the platform's own\n * files, so a visitor's browser asks no other host for them, and a\n * starter reads better than no picture when there is no library. On a\n * site start, which never places a photo its page already shows, a slot\n * none of the job's other photos fits yet keeps one too, and the start's\n * late photo pass gives it one of the job's own photos once every page\n * is built (`jobs/ai-site-late-photos.ts`).\n *\n * Where the library answered and nothing in it fits a slot\n * ({@link AI_LAYOUT_NO_PHOTO}), the slot never takes a starter: the beta.257\n * Copper Kettle Catering start (2026-10-15) opened a barbecue caterer's home\n * page on the starters' pottery workbench (AGL-3831). Nor does it ship empty\n * (AGL-3856): its picture is taken out, frame and all\n * ({@link aiLayoutDropEmptyPictures}). A cover's frame is a black scrim laid\n * over the whole section, and with nothing in it the beta.261 Synchronicity\n * Gym hero read as a solid black block; a split's picture card is a rounded\n * gray box, and the beta.262 Adult Hybrid Learning Center hero read as\n * broken. Without them the section shows its own band, a cover's section\n * still pinned to the site's dark scheme so the words written for a dark\n * photo stay readable, and a split's words take the row's width.\n *\n * A gallery picture the library found nothing fitting for takes one of the\n * job's own photos first, one the page does not show yet (AGL-4065), and a\n * band of pictures left with none at all is left out of the page, never\n * shown as a heading over captions ({@link aiLayoutDropEmptyPictureBands}):\n * the 2026-10-25 Rut Runners Racing home shipped \"The team at the track in\n * pictures\" as six captions and no picture, while its job had copied\n * thirteen motocross photos.\n *\n * A slot keeps the alt text the model wrote only when it describes the photo\n * placed: a stock photo that is not what it says is described by what it is\n * (`aiStockPhotoAlt`), and a starter photo always by its own words, since\n * the model never wrote for it (AGL-3831). A stand-in the compiler drew where\n * the model described no picture is decorative while empty; the photo placed\n * there is described by its own words and is no longer decorative (AGL-3856).\n */\n\n/** What a picture slot is for, read from where it sits and what it says. */\nexport type AiLayoutPictureRole = 'hero' | 'about' | 'gallery'\n\n/** One empty picture slot of a stored page, in document order. */\nexport interface AiLayoutPictureSlot {\n /** The `image` node. */\n imageId: string\n /** The frame around it, which sets the slot's shape. */\n frameId: string | null\n /** The placeholder icon sharing the frame, taken out once a photo fills it. */\n iconId: string | null\n alt: string\n /** The frame's shape as width over height; 1 when it names none. */\n aspect: number\n /** The plan index of the section it sits in, or -1. */\n sectionIndex: number\n role: AiLayoutPictureRole\n /**\n * A product's photo (AGL-3676): what it is of, most specific first — its\n * name, then what its photo should show — searched for with the shop's\n * category and held to it, so a wick trimmer is never a laptop on a desk.\n */\n product?: { subjects: readonly string[] }\n /**\n * A stand-in the compiler drew where the model described no picture\n * (AGL-3856): its words are what a search looks for, never what a photo\n * shows, so a photo placed there is described by its own, and is no longer\n * decorative once it is.\n */\n standIn?: boolean\n /**\n * A portrait of one of the business's people (AGL-3856): a record of a\n * team, staff or instructors list. Its photo shows an adult, in the role\n * its words or its list name where one is listed, never a child or a thing.\n */\n people?: boolean\n /**\n * A dataset record's cover (AGL-3856): what the record is filed under — the\n * value of its list's category-like field (\"Weddings and elopements\") —\n * read after its own name for what its photo is OF. A photographer's record\n * is the photographed moment, never the photographer or the camera.\n */\n record?: { category?: string }\n}\n\n/** A photo a slot is filled with. */\nexport interface AiLayoutPicturePhoto {\n /** The site-served address: a starter path or the media library's own. */\n src: string\n width: number\n height: number\n /**\n * What the photo shows, where the slot's own alt text does not say it: the\n * slot takes it in place of the words the model wrote before the photo was\n * found. Absent, the slot's words describe it.\n */\n alt?: string\n}\n\n/** Words that say a picture shows people, or a section is about them. */\nconst PEOPLE =\n /\\b(team|story|owner|owners|founder|founders|staff|crew|people|person|portrait|meet|family|families|who we are|our (?:team|crew|people|story)|therapist|counsel(?:l)?or|coach|doctor|dentist|stylist|trainer|smil(?:e|es|ing)|headshot)\\b/i\n\n/**\n * \"About\" as what a section or a picture is (AGL-3923): leading its words\n * (\"About us\", \"About the studio\"), never the preposition inside them. The\n * beta.267 Brightvale closing band, \"Ready to talk about your marketing?\",\n * was read as a picture of people and searched \"ready talk marketing\" among\n * portraits, which answered a tin-can telephone.\n */\nconst ABOUT = /^\\W*about\\b/i\n\n/**\n * An `aspectRatio` (`'16 / 9'`) as a number, or `null` where none is named.\n * A responsive one (`{ xs: '4 / 3', md: '4 / 5' }`, as a designed page's\n * pictures carry, AGL-3660) reads at the desktop breakpoint first: one photo\n * serves every width, and the wide screen is where its shape shows most.\n */\nfunction aspectOf(value: unknown): number | null {\n if (value && typeof value === 'object') {\n const byBreakpoint = value as Record<string, unknown>\n for (const breakpoint of ['md', 'lg', 'xl', 'sm', 'xs']) {\n const ratio = aspectOf(byBreakpoint[breakpoint])\n if (ratio !== null) return ratio\n }\n return null\n }\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*\\/\\s*(\\d+(?:\\.\\d+)?)\\s*$/.exec(String(value ?? ''))\n if (!match) return null\n const ratio = Number(match[1]) / Number(match[2])\n return Number.isFinite(ratio) && ratio > 0 ? ratio : null\n}\n\n/** The shape a full-bleed cover's photo is searched for: it fills a wide band. */\nconst COVER_ASPECT = 16 / 9\n\n/**\n * A slot's shape (AGL-3660): the image's own `aspectRatio` (a designed\n * picture card sets it on the image), else its frame's, else — for a frame\n * laid absolutely over its whole section, a designed photo cover — a wide\n * band's. 1 where nothing says.\n */\nfunction slotAspect(image: { sx?: Record<string, unknown> } | undefined, frame: { sx?: Record<string, unknown> } | undefined): number {\n const own = aspectOf(image?.sx?.['aspectRatio']) ?? aspectOf(frame?.sx?.['aspectRatio'])\n if (own !== null) return own\n const sx = frame?.sx\n if (sx?.['position'] === 'absolute' && sx['width'] === '100%' && sx['height'] === '100%') return COVER_ASPECT\n return 1\n}\n\n/** A slot's role: the first section's picture is the hero; people make an about picture. */\nexport function aiLayoutPictureRole(input: {\n sectionIndex: number\n sectionName: string\n alt: string\n}): AiLayoutPictureRole {\n if (input.sectionIndex === 0) return 'hero'\n if (PEOPLE.test(input.sectionName) || PEOPLE.test(input.alt) || ABOUT.test(input.sectionName) || ABOUT.test(input.alt)) return 'about'\n return 'gallery'\n}\n\n/**\n * Every empty picture slot of a stored page, walked from its root in\n * document order. A slot that already has a source — one the owner placed, or\n * an earlier pass filled — is not a slot.\n */\nexport function aiLayoutPictureSlots(\n nodes: NodesMap,\n rootId: string,\n sections: { ids: readonly string[]; names: readonly string[] },\n): AiLayoutPictureSlot[] {\n const map = nodes as unknown as Record<\n string,\n { componentId?: string; props?: Record<string, unknown>; sx?: Record<string, unknown>; nodes?: string[] }\n >\n const slots: AiLayoutPictureSlot[] = []\n const seen = new Set<string>()\n const visit = (id: string, parentId: string | null, sectionIndex: number) => {\n if (seen.has(id)) return\n seen.add(id)\n const node = map[id]\n if (!node) return\n const index = sections.ids.indexOf(id)\n const here = index >= 0 ? index : sectionIndex\n if (node.componentId === 'image') {\n const src = node.props?.['src']\n const alt = typeof node.props?.['alt'] === 'string' ? (node.props['alt'] as string).trim() : ''\n if ((typeof src !== 'string' || !src.trim()) && alt) {\n const frame = parentId ? map[parentId] : undefined\n const iconId =\n frame?.nodes?.find((child) => child !== id && map[child]?.componentId === 'icon') ?? null\n slots.push({\n imageId: id,\n frameId: frame ? parentId : null,\n iconId,\n alt,\n aspect: slotAspect(node, frame),\n sectionIndex: here,\n ...(node.props?.['decorative'] === true ? { standIn: true } : {}),\n role: aiLayoutPictureRole({\n sectionIndex: here,\n sectionName: here >= 0 ? (sections.names[here] ?? '') : '',\n alt,\n }),\n })\n }\n }\n for (const child of node.nodes ?? []) visit(child, id, here)\n }\n visit(rootId, null, -1)\n return slots\n}\n\n/** The starter photos, by name, each with the words that describe it. */\nexport const AI_LAYOUT_STARTER_PHOTOS = DEFAULT_SITE_IMAGES\n\n/**\n * A source's answer for a slot the library answered and nothing in it fits\n * (AGL-3831): the slot keeps its no-photo treatment rather than take a\n * starter. Its source is empty, so a caller reading `src` to fill something\n * that must hold a photo (a product's, a record's) falls back as it would on\n * `null`.\n */\nexport const AI_LAYOUT_NO_PHOTO: Readonly<AiLayoutPicturePhoto> = Object.freeze({ src: '', width: 0, height: 0 })\n\ntype StarterName = keyof typeof AI_LAYOUT_STARTER_PHOTOS\n\n/**\n * The starter photos each role draws from, best first. A hero draws from the\n * wide photos that read as a page's opening, an about picture leads with the\n * owner, and the rest are the gallery's.\n */\nconst STARTER_POOLS: Readonly<Record<AiLayoutPictureRole, readonly StarterName[]>> = {\n hero: ['hero', 'desk', 'craft'],\n about: ['about', 'hero'],\n gallery: ['desk', 'plants', 'craft'],\n}\n\nconst ALL_STARTERS = Object.keys(AI_LAYOUT_STARTER_PHOTOS) as StarterName[]\n\n/** A small stable number from a seed, so the same job always picks the same photos. */\nexport function aiLayoutSeedNumber(seed: string): number {\n let hash = 2166136261\n for (let index = 0; index < seed.length; index += 1) {\n hash ^= seed.charCodeAt(index)\n hash = Math.imul(hash, 16777619)\n }\n return hash >>> 0\n}\n\nfunction rotate<T>(list: readonly T[], by: number): T[] {\n if (!list.length) return []\n const start = by % list.length\n return [...list.slice(start), ...list.slice(0, start)]\n}\n\n/**\n * A starter photo for each slot (AGL-3660). No photo repeats on a page while\n * an unused one remains, and every pool turns by the seed, so sites do not\n * all open with the same photo while one job always picks the same.\n *\n * The slots are served by what they are for, not where they sit: the hero\n * first, then the pictures of people (an about picture leads with the owner),\n * then the rest. The live Juniper Clay start (2026-10-08) had six slots and\n * five starters, and the owner's photo went to a stoneware card before the\n * About section that described the artist asked for it. Only a page with more\n * slots than starters repeats one: the photo placed longest ago, never the\n * one in a slot beside it. The owner's portrait goes only to a picture of\n * people; a gallery picture never takes it, even to avoid a repeat.\n */\nexport function aiLayoutStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): AiLayoutPicturePhoto[] {\n const turn = aiLayoutSeedNumber(seed)\n const order: AiLayoutPictureRole[] = ['hero', 'about', 'gallery']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const names: Array<StarterName | undefined> = slots.map(() => undefined)\n /** When each starter was last placed, by its turn in the serving order. */\n const placedAt = new Map<StarterName, number>()\n served.forEach(({ slot, index }, step) => {\n // An about picture leads with the owner on every site; the others turn.\n const pool = slot.role === 'about' ? [...STARTER_POOLS.about] : rotate(STARTER_POOLS[slot.role], turn)\n const rest = rotate(\n ALL_STARTERS.filter((name) => !pool.includes(name)),\n turn,\n )\n // The owner's portrait is a picture of a person, and only a picture of\n // people takes it: never a vase in a gallery (AGL-3660). A portrait of\n // one of the business's people takes only people, repeated before a\n // workbench stands in for a teacher (AGL-3856).\n const candidates = slot.people\n ? [...STARTER_POOLS.about]\n : [...pool, ...rest].filter((name) => slot.role === 'about' || name !== 'about')\n const unused = candidates.find((candidate) => !placedAt.has(candidate))\n const beside = new Set([names[index - 1], names[index + 1]].filter(Boolean))\n const name =\n unused ??\n [...candidates]\n .sort((a, b) => (placedAt.get(a) ?? -1) - (placedAt.get(b) ?? -1))\n .find((candidate) => !beside.has(candidate)) ??\n candidates[0]\n names[index] = name\n placedAt.set(name, step)\n })\n return names.map((name) => {\n const photo = AI_LAYOUT_STARTER_PHOTOS[name as StarterName]\n return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n}\n\n/**\n * The starters that show no one's business (AGL-3923): a team at a table in a\n * bright office, a tidy desk. Never the workbench, the coffee shop's owner or\n * the houseplants, which read as another business's own photos (AGL-3831).\n */\nconst NEUTRAL_STARTERS: readonly StarterName[] = ['hero', 'desk']\n\n/**\n * A neutral starter for each of a list's records (AGL-3988), where the site\n * has a library and the job has no photo of its own: the two taken in turn\n * from the seed, so no record shows the photo of the one beside it. A card\n * of a listing cannot drop its frame as a page's picture does, so every\n * record takes one. Pure.\n */\nexport function aiLayoutNeutralStarterRun(count: number, seed: string): AiLayoutPicturePhoto[] {\n const pool = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(seed))\n return Array.from({ length: Math.max(0, count) }, (_, index) => {\n const photo = AI_LAYOUT_STARTER_PHOTOS[pool[index % pool.length] as StarterName]\n return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n}\n\n/**\n * A neutral starter for each slot a library could not fill, where the site\n * has a library (AGL-3923): each at most once on a page, the hero served\n * first and each pool turned by the seed, and none for a portrait of the\n * business's people or a product, nor for a slot past the two. A slot given\n * none keeps the no-photo treatment.\n */\nexport function aiLayoutNeutralStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): Array<AiLayoutPicturePhoto | null> {\n const pool = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(seed))\n const order: AiLayoutPictureRole[] = ['hero', 'gallery', 'about']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot }) => !slot.people && !slot.product)\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n served.forEach(({ index }, step) => {\n const name = pool[step]\n if (!name) return\n const photo = AI_LAYOUT_STARTER_PHOTOS[name]\n photos[index] = { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n return photos\n}\n\n/** A text's words a photo's own words may share: lowercase, four letters or more. */\nconst wordsOf = (text: string): Set<string> =>\n new Set(\n text\n .toLowerCase()\n .split(/[^a-zÀ-ɏ]+/)\n .filter((word) => word.length >= 4),\n )\n\n/**\n * The job's own photos for the slots a library could not fill (AGL-3988):\n * the photos THIS job already placed on this site — on its other pages, on\n * its datasets' records, and on this page — which were each found for the\n * site's own world, before any starter. The beta.270 Sugar Hollow\n * Photography start (2026-10-19) opened a wedding photographer's home page\n * on the starters' desk and an office team, and filed five wedding galleries\n * under a workbench, a windowsill and the same office, while the library\n * had already given the job three wedding and family photos.\n *\n * Each slot `need` names takes the one that fits it best: one this page does\n * not show yet before one it does, a wide photo for a hero, the shape\n * nearest its frame, and the most words shared with what the slot says it\n * shows; never the photo of the slot beside it. A product's photo and a\n * portrait of the business's people are of one named thing, which no other\n * photo is, so they take none. Among equals the seed picks, so one job\n * always picks the same. `null` for a slot given none. Pure.\n *\n * `repeats` is a site start's (AGL-3988), where a photo shown twice on one\n * page reads as a defect: `'never'` when its page is built, where a slot no\n * photo the page does not show yet fits keeps a starter for the start's\n * late photo pass; `'last'` in that pass, where the other choice is a starter\n * of another business, so a photo the page does not show comes first, then\n * one it shows elsewhere, and the photo beside the slot only after that.\n */\nexport function aiLayoutJobPhotoPicks(\n slots: readonly AiLayoutPictureSlot[],\n need: readonly boolean[],\n jobPhotos: readonly AiLayoutPicturePhoto[],\n placed: ReadonlyArray<AiLayoutPicturePhoto | null | undefined>,\n seed: string,\n options: { repeats?: 'never' | 'last' } = {},\n): Array<AiLayoutPicturePhoto | null> {\n const seen = new Set<string>()\n const pool = jobPhotos.filter((photo) => {\n if (!photo.src || !(photo.width > 0) || !(photo.height > 0) || seen.has(photo.src)) return false\n seen.add(photo.src)\n return true\n })\n const picks: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n if (!pool.length) return picks\n const shown = slots.map((_slot, index) => placed[index]?.src || '')\n const order: AiLayoutPictureRole[] = ['hero', 'about', 'gallery']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot, index }) => need[index] && !slot.product && !slot.people)\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const turn = aiLayoutSeedNumber(seed)\n for (const { slot, index } of served) {\n const beside = new Set([shown[index - 1], shown[index + 1]].filter(Boolean))\n const says = wordsOf(slot.alt)\n let best: { photo: AiLayoutPicturePhoto; score: number; rank: number } | null = null\n for (const [at, photo] of pool.entries()) {\n const nextTo = beside.has(photo.src)\n const uses = shown.filter((src) => src === photo.src).length\n if (nextTo && options.repeats !== 'last') continue\n if (uses && options.repeats === 'never') continue\n const aspect = photo.width / photo.height\n const shared = [...wordsOf(photo.alt ?? '')].filter((word) => says.has(word)).length\n const score =\n (nextTo ? -10_000 : 0) -\n (options.repeats === 'last' ? 1_000 : 100) * uses +\n (slot.role === 'hero' && aspect >= 1.3 ? 20 : 0) -\n 10 * Math.abs(Math.log(aspect / (slot.aspect > 0 ? slot.aspect : 1))) +\n 4 * shared\n // The seed turns the pool, so equals are taken in a per-job order.\n const rank = (at - (turn % pool.length) + pool.length) % pool.length\n if (!best || score > best.score || (score === best.score && rank < best.rank)) best = { photo, score, rank }\n }\n if (!best) continue\n picks[index] = { ...best.photo }\n shown[index] = best.photo.src\n }\n return picks\n}\n\n/**\n * The page with each slot filled by its photo: the `image` given its source,\n * its intrinsic size and how it loads (the hero eagerly, as the starter home\n * page loads its own), and the placeholder icon taken out of the frame. The\n * alt text is kept as the model wrote it unless the photo brings its own\n * words. A slot given no photo is left as the compiler wrote it.\n */\nexport function aiLayoutPlacePictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n): NodesMap {\n const next = { ...(nodes as unknown as Record<string, Record<string, unknown>>) }\n // Only the page's first picture loads eagerly (rule 16, AGL-3660): a hero\n // of several photos loaded them all eagerly, and a live Portfolio page was\n // refused for it (2026-10-10). The slots are in document order; every later\n // picture leaves `loading` unset, which the renderer loads when reached.\n const lead = slots.findIndex((_, index) => Boolean(photos[index] && next[slots[index].imageId]))\n slots.forEach((slot, index) => {\n const photo = photos[index]\n const node = next[slot.imageId]\n if (!photo || !node) return\n const props = { ...((node['props'] as Record<string, unknown> | undefined) ?? {}) }\n delete props['loading']\n // A photo placed is described by its words, never hidden from a screen\n // reader: a stand-in was decorative only while it showed nothing (AGL-3856).\n // One that brings no words of its own, such as an owner's upload with no\n // alt text, keeps the slot's (AGL-3979): a picture a visitor sees always\n // has a description.\n const alt = photo.alt?.trim() || (typeof props['alt'] === 'string' ? props['alt'].trim() : '')\n if (alt) delete props['decorative']\n next[slot.imageId] = {\n ...node,\n props: {\n ...props,\n src: photo.src,\n intrinsicWidth: photo.width,\n intrinsicHeight: photo.height,\n ...(alt ? { alt } : {}),\n ...(index === lead && slot.role === 'hero' ? { loading: 'eager' } : {}),\n },\n }\n if (slot.iconId && slot.frameId && next[slot.frameId]) {\n const frame = next[slot.frameId]\n next[slot.frameId] = {\n ...frame,\n nodes: ((frame['nodes'] as string[] | undefined) ?? []).filter((child) => child !== slot.iconId),\n }\n delete next[slot.iconId]\n }\n })\n return next as unknown as NodesMap\n}\n\n/** Whether a node is a grid row: a `muiGrid` container. */\nconst isGridRow = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && (node['props'] as Record<string, unknown> | undefined)?.['container'] === true\n\n/** Whether a node is a column of a grid row: a sized `muiGrid` item. */\nconst isGridColumn = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && !isGridRow(node)\n\n/**\n * The page without the pictures left with no photo (AGL-3856). A slot whose\n * photo is missing loses its image, its icon and its frame where the frame\n * holds nothing else, and so does every wrapper that holds nothing once it\n * is gone, up to its section. A cover's black scrim over nothing read as a\n * solid black block (the beta.261 Synchronicity Gym hero), and an empty\n * rounded gray card as a broken picture (the beta.262 Adult Hybrid Learning\n * Center hero). A column of a split row taken out leaves the words' column\n * the row's full width. A cover's section stays pinned to the site's dark\n * scheme, which paints that scheme's background and text colors, so the\n * words written for a dark photo stay readable. A slot a photo fills, a\n * starter's included, is kept.\n */\nexport function aiLayoutDropEmptyPictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n sectionIds: readonly string[] = [],\n): NodesMap {\n const map = nodes as unknown as Record<string, Record<string, unknown>>\n const empty = slots.filter((slot, index) => {\n if (photos[index]?.src) return false\n const src = (map[slot.imageId]?.['props'] as Record<string, unknown> | undefined)?.['src']\n return Boolean(map[slot.imageId]) && !(typeof src === 'string' && src.trim())\n })\n if (!empty.length) return nodes\n const next = { ...map }\n const children = (id: string) => (next[id]?.['nodes'] as string[] | undefined) ?? []\n const parentOf = (id: string) => Object.keys(next).find((key) => children(key).includes(id)) ?? null\n const remove = (id: string) => {\n const parent = parentOf(id)\n if (parent) next[parent] = { ...next[parent], nodes: children(parent).filter((child) => child !== id) }\n delete next[id]\n return parent\n }\n const sections = new Set(sectionIds)\n for (const slot of empty) {\n const own = new Set([slot.imageId, slot.iconId].filter(Boolean) as string[])\n const frameHoldsOnly = Boolean(slot.frameId && next[slot.frameId] && children(slot.frameId).every((child) => own.has(child)))\n let parent = remove(slot.imageId)\n if (slot.iconId && next[slot.iconId]) remove(slot.iconId)\n if (!frameHoldsOnly) continue\n // The frame, then each wrapper it leaves empty: never a section, nor the page's root.\n let at: string | null = slot.frameId\n while (at && next[at] && !children(at).length && !sections.has(at) && parentOf(at) !== null) {\n const column = isGridColumn(next[at])\n parent = remove(at)\n at = column ? null : parent\n }\n // A split row left with one column gives its words the row's width.\n if (parent && isGridRow(next[parent])) {\n const [only, ...rest] = children(parent)\n if (only && !rest.length && isGridColumn(next[only])) {\n const column = next[only] as Record<string, unknown>\n const sx = { ...((column['sx'] as Record<string, unknown> | undefined) ?? {}) }\n delete sx['order']\n next[only] = {\n ...column,\n props: { ...((column['props'] as Record<string, unknown> | undefined) ?? {}), size: 'xs:12' },\n sx: Object.keys(sx).length ? sx : null,\n }\n }\n }\n }\n return next as unknown as NodesMap\n}\n\n/**\n * A section's name that says it is a band of pictures (AGL-4065): \"The team\n * at the track in pictures\", \"Photo gallery\", \"Photos from race day\".\n */\nconst PICTURE_BAND =\n /\\b(?:in|through)\\s+(?:pictures|photos|photographs|images)\\b|\\b(?:photo|picture|image)\\s+galler(?:y|ies)\\b|\\bgalleries?\\b|^\\W*(?:photos|pictures|photographs|snapshots)\\b/i\n\n/** Components that only hold words, or lay them out: what a band of captions is made of. */\nconst WORDS_ONLY: ReadonlySet<string> = new Set([\n 'section',\n 'div',\n 'muiContainer',\n 'muiStack',\n 'muiGrid',\n 'muiBox',\n 'muiTypography',\n 'muiListItemText',\n 'muiList',\n 'muiListItem',\n 'muiDivider',\n 'muiButton',\n 'icon',\n])\n\n/**\n * Which slots are cells of a band of pictures (AGL-4065): two or more of one\n * section's pictures, each in a cell of the same grid, as the compiler draws\n * a group of pictures over their captions. Pure.\n */\nexport function aiLayoutPictureBandCells(nodes: NodesMap, slots: readonly AiLayoutPictureSlot[]): boolean[] {\n const map = nodes as unknown as Record<string, { componentId?: string; props?: Record<string, unknown>; nodes?: string[] }>\n const parent = new Map<string, string>()\n for (const [id, node] of Object.entries(map)) for (const child of node?.nodes ?? []) parent.set(child, id)\n /** The grid a picture sits in a cell of: the nearest container above it. */\n const gridOf = (id: string) => {\n let at = parent.get(id)\n while (at && !(map[at]?.componentId === 'muiGrid' && map[at]?.props?.['container'] === true)) at = parent.get(at)\n return at ?? null\n }\n const grids = slots.map((slot) => (slot.sectionIndex > 0 ? gridOf(slot.frameId ?? slot.imageId) : null))\n return slots.map((slot, index) => {\n const grid = grids[index]\n return Boolean(grid) && slots.filter((other, at) => other.sectionIndex === slot.sectionIndex && grids[at] === grid).length >= 2\n })\n}\n\n/**\n * The page without its bands of pictures that show no picture (AGL-4065): a\n * section, never the first, whose own name says it is pictures, or whose\n * every picture was a cell of one grid, and which holds nothing but words\n * once its empty pictures are gone. It is left out whole: never a heading\n * over captions. The 2026-10-25 Rut Runners Racing and Pit Row Tacos homes\n * shipped \"The team at the track in pictures\" and \"The trailer and the food\n * in pictures\" as six captions each. A band holding a photo, a record's\n * picture, a form or anything else that is not words is kept.\n */\nexport function aiLayoutDropEmptyPictureBands(\n nodes: NodesMap,\n input: {\n sectionIds: readonly string[]\n sectionNames: readonly string[]\n slots?: readonly AiLayoutPictureSlot[]\n photos?: ReadonlyArray<AiLayoutPicturePhoto | null>\n },\n): NodesMap {\n const map = nodes as unknown as Record<string, Record<string, unknown>>\n const children = (id: string) => (map[id]?.['nodes'] as string[] | undefined) ?? []\n const within = (id: string): string[] => [id, ...children(id).flatMap(within)]\n const slots = input.slots ?? []\n const photos = input.photos ?? []\n const parentOf = (id: string) => Object.keys(map).find((key) => children(key).includes(id)) ?? null\n const cells = aiLayoutPictureBandCells(nodes, slots)\n const dropped = input.sectionIds.filter((sectionId, index) => {\n if (index === 0 || !map[sectionId]) return false\n // Every picture it had was a cell of its band, and none is left with a photo.\n const own = slots.map((slot, at) => ({ slot, at })).filter(({ slot }) => slot.sectionIndex === index)\n const pictured = own.length > 0 && own.every(({ at }) => cells[at] && !photos[at]?.src)\n if (!pictured && !PICTURE_BAND.test(input.sectionNames[index] ?? '')) return false\n return within(sectionId).every((id) => WORDS_ONLY.has(String(map[id]?.['componentId'] ?? '')))\n })\n if (!dropped.length) return nodes\n const next = { ...map }\n for (const sectionId of dropped) {\n const parent = parentOf(sectionId)\n if (parent) next[parent] = { ...next[parent], nodes: ((next[parent]?.['nodes'] as string[] | undefined) ?? []).filter((child) => child !== sectionId) }\n for (const id of within(sectionId)) delete next[id]\n }\n return next as unknown as NodesMap\n}\n\n/**\n * A source of photos found for the slots themselves — a stock search copied\n * into the site's library — answering per slot, in order: a photo; or\n * {@link AI_LAYOUT_NO_PHOTO} where the library answered and nothing fits,\n * which leaves the slot without a photo; or `null` where the library could\n * not be asked, which takes a starter photo. It may throw, and then every\n * slot takes a starter.\n */\nexport type AiLayoutPictureSource = (\n slots: readonly AiLayoutPictureSlot[],\n report?: AiLayoutPictureReport,\n) => Promise<ReadonlyArray<AiLayoutPicturePhoto | null>>\n\n/**\n * What a source tells its caller beside its answers (AGL-3988): for each slot\n * whose fitting photo the library's image hosts would not hand over, the\n * picks it kept in hand and the search they came from, so the site start's\n * late photo pass can ask for them again once the burst is over. Keyed by\n * the slot itself, which every source hands on unchanged.\n */\nexport interface AiLayoutPictureReport {\n refused: Map<AiLayoutPictureSlot, AiStockRefusedPicks>\n}\n\n/**\n * Fills a stored page's empty picture slots (AGL-3660): from `source` where\n * one is given and it answers; a slot it answers with nothing fitting stays\n * without a photo and is taken out ({@link aiLayoutDropEmptyPictures}). A\n * slot it could not answer — the library down, out of time, or its photo not\n * copied — takes one of the photos the job already placed on this site or\n * this page just copied (`jobPhotos`, {@link aiLayoutJobPhotoPicks},\n * AGL-3988), and only where the job has none, a starter photo described by\n * its own words (AGL-3831). On a site start (`onStarters`), a photo the\n * page already shows is never placed again, a slot none of the others fits\n * yet keeps a neutral starter too, and `onStarters` is told of every\n * picture left on one, for the late photo pass. Never throws, and\n * never leaves a page worse than it came: a picture is decoration, and a\n * job never fails over one.\n */\nexport async function aiResolveLayoutPictures(\n nodes: NodesMap,\n input: {\n rootId: string\n sectionIds: readonly string[]\n sectionNames: readonly string[]\n /** A per-job word, so two sites do not open with the same photo. */\n seed: string\n source?: AiLayoutPictureSource | null\n /**\n * The photos this job already placed on this site (AGL-3988): its other\n * pages' and its datasets' records', each found for the site's world.\n * Taken before any starter where `source` could not fill a slot.\n */\n jobPhotos?: readonly AiLayoutPicturePhoto[]\n /** The site's language (AGL-3750); a starter photo's English words are not its own. */\n language?: string\n /**\n * Told the pictures left on a starter where the site has a library\n * (AGL-3988), with the picks the image hosts refused for each: what the\n * site start's late photo pass fills from the job's own photos. Given\n * only where such a pass follows: a failed slot then never takes a photo\n * the page already shows, and one none of the job's other photos fits\n * yet keeps a neutral starter for that pass, rather than lose its frame.\n */\n onStarters?: (slots: AiSiteStarterSlot[]) => void\n },\n): Promise<NodesMap> {\n try {\n const slots = aiLayoutPictureSlots(nodes, input.rootId, {\n ids: input.sectionIds,\n names: input.sectionNames,\n })\n // A band of pictures drawn with no picture at all is left out (AGL-4065).\n if (!slots.length) return aiLayoutDropEmptyPictureBands(nodes, input)\n let found: ReadonlyArray<AiLayoutPicturePhoto | null> = []\n const report: AiLayoutPictureReport = { refused: new Map() }\n if (input.source) {\n try {\n found = await input.source(slots, report)\n } catch (error) {\n console.warn('ai layout pictures: the stock source failed; the job’s own photos or starters fill the page', { error })\n found = []\n }\n }\n // The starters' words are English; a site in another language says it\n // shows an illustrative photo, true of any photo, as a stock photo's does.\n const otherLanguage = aiLayoutSpeaksOtherThanEnglish(input.language)\n const said = (photo: AiLayoutPicturePhoto): AiLayoutPicturePhoto =>\n otherLanguage ? { ...photo, alt: aiLayoutLabel('illustrativePhoto', input.language) } : photo\n /** The slots the source could not answer: no library, or it failed them. */\n const unanswered = slots.map((_slot, index) => !found[index])\n /**\n * The gallery pictures the library answered with nothing fitting\n * (AGL-4065): one of the job's own photos fills each, one the page does\n * not show yet; so does a cell of a band of pictures, whatever its words\n * say. Never a hero, a portrait of the business's people or a product,\n * which keep their no-photo treatment (AGL-3831).\n */\n const cells = aiLayoutPictureBandCells(nodes, slots)\n const fitless = slots.map(\n (slot, index) =>\n Boolean(found[index]) &&\n !found[index]?.src &&\n !slot.product &&\n !slot.people &&\n (slot.role === 'gallery' || (slot.role === 'about' && Boolean(cells[index]))),\n )\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map((_slot, index) => {\n const answer = found[index]\n return answer?.src ? answer : null\n })\n // With a library, the job's own photos come before any starter (AGL-3988),\n // and a starter is only ever a neutral one (AGL-3923): the beta.267\n // Brightvale start, whose library refused its copies, showed a pottery\n // bench and a coffee shop's owner as a marketer's pictures. The job's\n // own are its other pages' and its records' photos, and the ones this\n // page just copied for its other slots: the beta.271 Laurel Fork home\n // page, the first page built, copied a wide wedding couple and a family\n // for two of its slots while its hero's picks were refused, and opened\n // on the starters' office team. A photo the page does not show yet is\n // still preferred, and never the one beside it.\n const own = input.source\n ? [...(input.jobPhotos ?? []), ...photos.filter((photo): photo is AiLayoutPicturePhoto => Boolean(photo))].filter(\n (photo) => photo.src && photo.width > 0 && photo.height > 0,\n )\n : []\n // On a site start (`onStarters`) a photo the page already shows is never\n // placed again here (AGL-3988): the beta.271 Laurel Fork home page opened\n // on the wedding couple its closing band shows. Such a slot keeps a\n // starter, and the start's late photo pass fills it.\n const lateStart = Boolean(input.source && input.onStarters)\n if (own.length) {\n aiLayoutJobPhotoPicks(slots, unanswered, own, photos, input.seed, lateStart ? { repeats: 'never' } : {}).forEach((pick, index) => {\n if (pick && unanswered[index]) photos[index] = pick\n })\n // A gallery picture nothing fit takes a photo the page does not show yet, never one twice.\n aiLayoutJobPhotoPicks(slots, fitless, own, photos, input.seed, { repeats: 'never' }).forEach((pick, index) => {\n if (pick && fitless[index]) photos[index] = pick\n })\n }\n // A failed slot takes a neutral starter only where the job had no photo\n // of its own before this page, as #1831 had it. On a site start, where\n // the job has photos of its own, every failed slot none of them fits yet\n // keeps one: that start's late photo pass gives it one of the job's own\n // once every page is built (AGL-3988), told of it by `onStarters`.\n // Anywhere else it keeps the no-photo treatment. With no library, every\n // starter, as before.\n const hadOwn = (input.jobPhotos ?? []).some((photo) => photo.src && photo.width > 0 && photo.height > 0)\n const starters = !input.source\n ? aiLayoutStarterPhotos(slots, input.seed)\n : lateStart && own.length\n ? aiLayoutNeutralStarterRun(slots.length, input.seed).map((photo, index) =>\n slots[index]?.people || slots[index]?.product ? null : photo,\n )\n : hadOwn && !lateStart\n ? []\n : aiLayoutNeutralStarterPhotos(slots, input.seed)\n // On a site start, a gallery picture nothing fit that none of the job's\n // photos fills yet keeps a neutral starter too, for the late photo pass\n // (AGL-4065); anywhere else it is taken out.\n const holds = (index: number) => unanswered[index] || (fitless[index] && lateStart && own.length > 0)\n starters.forEach((photo, index) => {\n if (photo && holds(index) && !photos[index]) photos[index] = said(photo)\n })\n // The page floor (AGL-3923; Zach, 2026-10-12: a starter photo before no\n // photo at all): a page the library answered that would show no picture\n // keeps one, in its hero, else its first slot: one of the job's own\n // photos where it has any (AGL-3988), else a neutral starter.\n if (input.source && !photos.some((photo) => photo?.src)) {\n const at = Math.max(0, slots.findIndex((slot) => slot.role === 'hero'))\n const lead = { ...(slots[at] as AiLayoutPictureSlot), product: undefined, people: undefined }\n const [pick] = aiLayoutJobPhotoPicks([lead], [true], own, [null], input.seed)\n const [floor] = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(input.seed))\n const starter = floor ? AI_LAYOUT_STARTER_PHOTOS[floor] : null\n if (pick) photos[at] = pick\n else if (starter) photos[at] = said({ src: starter.src, width: starter.width, height: starter.height, alt: starter.alt })\n }\n // The pictures left on a starter, for the late photo pass (AGL-3988).\n if (input.source && input.onStarters) {\n const left = slots.flatMap((slot, index): AiSiteStarterSlot[] => {\n if (!aiSiteIsStarterSrc(photos[index]?.src)) return []\n const refused = report.refused.get(slot)\n return [{ image: slot.imageId, words: slot.alt, role: slot.role, aspect: slot.aspect, ...(refused ? { refused } : {}) }]\n })\n if (left.length) input.onStarters(left)\n }\n return aiLayoutDropEmptyPictureBands(aiLayoutDropEmptyPictures(aiLayoutPlacePictures(nodes, slots, photos), slots, photos, input.sectionIds), {\n ...input,\n slots,\n photos,\n })\n } catch (error) {\n console.error('ai layout pictures: the page keeps its empty picture slots', { error })\n return nodes\n }\n}\n"],"names":["DEFAULT_SITE_IMAGES","aiSiteIsStarterSrc","aiLayoutLabel","aiLayoutSpeaksOtherThanEnglish","PEOPLE","ABOUT","aspectOf","value","byBreakpoint","breakpoint","ratio","match","exec","String","Number","isFinite","COVER_ASPECT","slotAspect","image","frame","own","sx","aiLayoutPictureRole","input","sectionIndex","test","sectionName","alt","aiLayoutPictureSlots","nodes","rootId","sections","map","slots","seen","Set","visit","id","parentId","node","has","add","index","ids","indexOf","here","componentId","src","props","trim","undefined","iconId","find","child","push","imageId","frameId","aspect","standIn","role","names","AI_LAYOUT_STARTER_PHOTOS","AI_LAYOUT_NO_PHOTO","Object","freeze","width","height","STARTER_POOLS","hero","about","gallery","ALL_STARTERS","keys","aiLayoutSeedNumber","seed","hash","length","charCodeAt","Math","imul","rotate","list","by","start","slice","aiLayoutStarterPhotos","turn","order","served","slot","sort","a","b","placedAt","Map","forEach","step","unused","pool","rest","filter","name","includes","candidates","people","candidate","beside","Boolean","get","set","photo","NEUTRAL_STARTERS","aiLayoutNeutralStarterRun","count","Array","from","max","_","aiLayoutNeutralStarterPhotos","product","photos","wordsOf","text","toLowerCase","split","word","aiLayoutJobPhotoPicks","need","jobPhotos","placed","options","picks","shown","_slot","says","best","at","entries","nextTo","uses","repeats","shared","score","abs","log","rank","aiLayoutPlacePictures","next","lead","findIndex","intrinsicWidth","intrinsicHeight","loading","isGridRow","isGridColumn","aiLayoutDropEmptyPictures","sectionIds","empty","children","parentOf","key","remove","parent","frameHoldsOnly","every","column","only","size","PICTURE_BAND","WORDS_ONLY","aiLayoutPictureBandCells","gridOf","grids","grid","other","aiLayoutDropEmptyPictureBands","within","flatMap","cells","dropped","sectionId","pictured","sectionNames","aiResolveLayoutPictures","found","report","refused","source","error","console","warn","otherLanguage","language","said","unanswered","fitless","answer","lateStart","onStarters","pick","hadOwn","some","starters","holds","floor","starter","left","words"],"mappings":"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"}
@@ -71,12 +71,26 @@ export declare function aiStorefrontPlanScreens<S extends Pick<AiBuildPlanScreen
71
71
  gridSection: (sections: S['sections']) => number;
72
72
  }): S[];
73
73
  /**
74
- * The icon each of a store's reasons to buy shows (AGL-3676): its own where
75
- * the design named one, else one its words suggest, else the next of a few
76
- * neutral ones — never the same twice in one row.
74
+ * The icon each item of a row shows (AGL-3676, AGL-4065):
75
+ *
76
+ * - in a section of places ("Areas we serve"), the place marker, every one
77
+ * alike: a list of towns is one kind of thing, and the 2026-10-25 booth
78
+ * kit drew its towns with a marker, a star, a heart and a sparkle;
79
+ * - else its own where the design named one;
80
+ * - else one its title suggests, then one its words suggest — "On time" a
81
+ * clock, "English and Spanish" the translate mark — each once a row;
82
+ * - else one neutral icon, the same along the row: the check, or the
83
+ * sparkle on a calm or celebratory site. Never a random mix.
84
+ *
85
+ * `section` is what the section is called (its plan name and its headings);
86
+ * `kind` is the site's kind (`AI_SITE_KINDS`), absent on a page drawn with
87
+ * no design.
77
88
  */
78
89
  export declare function aiStorefrontItemIcons(items: ReadonlyArray<{
79
90
  title: string;
80
91
  text: string;
81
92
  icon?: string;
82
- }>, known: (word: string | undefined) => boolean): string[];
93
+ }>, known: (word: string | undefined) => boolean, options?: {
94
+ section?: string;
95
+ kind?: string;
96
+ }): string[];
@@ -140,8 +140,13 @@ function storefrontHome(screen) {
140
140
  sections
141
141
  });
142
142
  }
143
- // ── Icons for a store's reasons to buy ────────────────────────────────────
144
- /** Words that suggest an icon from the platform's icon library, first match wins. */ const ICON_WORDS = [
143
+ // ── Icons for a row's items: a store's reasons to buy, any site's reasons, its places ──
144
+ /**
145
+ * Words that suggest an icon from the platform's icon library, first match
146
+ * wins: a store's reasons (AGL-3676), then a service business's (AGL-4065) —
147
+ * its languages, its water and its tools, its time and its prices, its
148
+ * riders and its sponsors.
149
+ */ const ICON_WORDS = [
145
150
  [
146
151
  /\b(ship|shipping|shipped|deliver\w*|dispatch\w*|post(?:age|ed)?|courier)\b/i,
147
152
  'truck'
@@ -154,6 +159,30 @@ function storefrontHome(screen) {
154
159
  /\b(gifts?|gifting|wrapped|wrapping|boxed)\b/i,
155
160
  'gift'
156
161
  ],
162
+ [
163
+ /\b(languages?|bilingual|spanish|español|english and|translat\w*)\b/i,
164
+ 'translate'
165
+ ],
166
+ [
167
+ /\b(water|watering|irrigation|sprinklers?|drip)\b/i,
168
+ 'water'
169
+ ],
170
+ [
171
+ /\b(on[- ]time|punctual|hours|time)\b/i,
172
+ 'clock'
173
+ ],
174
+ [
175
+ /\b(schedules?|scheduling|calendar|dates?|seasons?|seasonal)\b/i,
176
+ 'calendar'
177
+ ],
178
+ [
179
+ /\b(prices?|pricing|estimates?|quotes?|costs?|upfront|fees?)\b/i,
180
+ 'receipt'
181
+ ],
182
+ [
183
+ /\b(repairs?|fix\w*|tools?|wrench\w*|trades?|drywall|carpentry|mechanics?)\b/i,
184
+ 'tools'
185
+ ],
157
186
  [
158
187
  /\b(natural|soy|organic|plant|vegan|botanical|clean|non-toxic|eco|sustainab\w*|cotton|wood)\b/i,
159
188
  'leaf'
@@ -163,7 +192,7 @@ function storefrontHome(screen) {
163
192
  'care'
164
193
  ],
165
194
  [
166
- /\b(guarantee\w*|secure|safe|safety|warranty|protected)\b/i,
195
+ /\b(guarantee\w*|secure|safe|safety|warranty|protected|protect\w*|helmets?|insured|licensed)\b/i,
167
196
  'shield'
168
197
  ],
169
198
  [
@@ -178,28 +207,76 @@ function storefrontHome(screen) {
178
207
  /\b(scents?|fragrances?|aroma\w*|candles?|glow|light|burn\w*)\b/i,
179
208
  'sparkle'
180
209
  ],
210
+ [
211
+ /\b(sponsors?|sponsorship|partners?|partnership)\b/i,
212
+ 'handshake'
213
+ ],
214
+ [
215
+ /\b(riders?|racing|racers?|races?|race[- ]day|motocross|podiums?|laps?)\b/i,
216
+ 'flag'
217
+ ],
218
+ [
219
+ /\b(famil(?:y|ies)|kids|children|youth|parents)\b/i,
220
+ 'people'
221
+ ],
222
+ [
223
+ /\b(phone|call)\b/i,
224
+ 'phone'
225
+ ],
226
+ [
227
+ /\b(e-?mail|messages?|inbox)\b/i,
228
+ 'mail'
229
+ ],
181
230
  [
182
231
  /\b(local|town|city|community)\b/i,
183
232
  'location'
184
233
  ],
185
234
  [
186
- /\b(support|help|questions?|chat)\b/i,
235
+ /\b(support|help|questions?|chat|answers?|advice|explain\w*)\b/i,
187
236
  'chat'
188
237
  ]
189
238
  ];
190
- /** The fallbacks an item takes in turn, so a row of icons never repeats one. */ const ICON_FALLBACKS = [
191
- 'star',
192
- 'heart',
239
+ /**
240
+ * The section names that say their items are places (AGL-4065): "Areas we
241
+ * serve", "Towns we serve", "Where we work", "Service area", "Our
242
+ * locations". A practice's or a studio's "areas" of work are not places.
243
+ */ const PLACES = /\b(?:(?:service|delivery|coverage) areas?|areas? (?:we (?:serve|cover|work)|served|covered)|towns?|cities|cit(?:y|ies) we|neighbou?rhoods?|where (?:we (?:work|serve|go|are)|to find us)|find us|locations?)\b/i;
244
+ /**
245
+ * The kinds whose rows take the sparkle where no word names an icon: a calm
246
+ * practice's, a salon's, a celebration's. Every other kind takes the check,
247
+ * and draws the sparkle and the heart nowhere their words do not ask for
248
+ * them (AGL-4065).
249
+ */ const SPARKLE_KINDS = new Set([
250
+ 'yoga',
251
+ 'wellness',
252
+ 'beauty',
253
+ 'events'
254
+ ]);
255
+ /** The icons only those kinds, and a store whose words name its scents and its glow, take from a word. */ const QUIET_ICONS = new Set([
193
256
  'sparkle',
194
- 'check',
195
- 'smile',
196
- 'award'
197
- ];
257
+ 'heart'
258
+ ]);
198
259
  /**
199
- * The icon each of a store's reasons to buy shows (AGL-3676): its own where
200
- * the design named one, else one its words suggest, else the next of a few
201
- * neutral ones — never the same twice in one row.
202
- */ export function aiStorefrontItemIcons(items, known) {
260
+ * The icon each item of a row shows (AGL-3676, AGL-4065):
261
+ *
262
+ * - in a section of places ("Areas we serve"), the place marker, every one
263
+ * alike: a list of towns is one kind of thing, and the 2026-10-25 booth
264
+ * kit drew its towns with a marker, a star, a heart and a sparkle;
265
+ * - else its own where the design named one;
266
+ * - else one its title suggests, then one its words suggest — "On time" a
267
+ * clock, "English and Spanish" the translate mark — each once a row;
268
+ * - else one neutral icon, the same along the row: the check, or the
269
+ * sparkle on a calm or celebratory site. Never a random mix.
270
+ *
271
+ * `section` is what the section is called (its plan name and its headings);
272
+ * `kind` is the site's kind (`AI_SITE_KINDS`), absent on a page drawn with
273
+ * no design.
274
+ */ export function aiStorefrontItemIcons(items, known, options = {}) {
275
+ if (options.section && PLACES.test(options.section) && known('location')) return items.map(()=>'location');
276
+ const sparkles = !!options.kind && SPARKLE_KINDS.has(options.kind);
277
+ const neutral = sparkles && known('sparkle') ? 'sparkle' : 'check';
278
+ // A page drawn with no design keeps every word's icon, as before.
279
+ const quiet = !!options.kind && !sparkles && options.kind !== 'store';
203
280
  const used = new Set();
204
281
  const designed = items.map((item)=>{
205
282
  if (item.icon && known(item.icon) && !used.has(item.icon)) {
@@ -208,13 +285,16 @@ function storefrontHome(screen) {
208
285
  }
209
286
  return '';
210
287
  });
211
- return designed.map((icon, index)=>{
212
- var _ref;
288
+ const suggest = (words)=>{
213
289
  var _ICON_WORDS_find;
290
+ return (_ICON_WORDS_find = ICON_WORDS.find(([pattern, word])=>pattern.test(words) && !used.has(word) && known(word) && !(quiet && QUIET_ICONS.has(word)))) == null ? void 0 : _ICON_WORDS_find[1];
291
+ };
292
+ return designed.map((icon, index)=>{
293
+ var _suggest;
214
294
  if (icon) return icon;
215
- const words = `${items[index].title} ${items[index].text}`;
216
- const suggested = (_ICON_WORDS_find = ICON_WORDS.find(([pattern, word])=>pattern.test(words) && !used.has(word) && known(word))) == null ? void 0 : _ICON_WORDS_find[1];
217
- const picked = (_ref = suggested != null ? suggested : ICON_FALLBACKS.find((word)=>!used.has(word) && known(word))) != null ? _ref : 'star';
295
+ const item = items[index];
296
+ const picked = (_suggest = suggest(item.title)) != null ? _suggest : suggest(`${item.title} ${item.text}`);
297
+ if (!picked) return neutral;
218
298
  used.add(picked);
219
299
  return picked;
220
300
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-storefront.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A STORE'S STOREFRONT (AGL-3676, second round).\n *\n * Zach, 2026-10-09, on the beta.237 Willow Wick Candles start (job\n * MtlXWjvt9P): \"again this … site does not look like a store front\". Its home\n * was a lifestyle hero, ONE row of featured products and four text-only bands\n * (\"Why soy, why small batch\", \"How our candles are made\", \"Made to be\n * loved\", \"Gifting and shipping\"); its Shop page opened with a tall hero and\n * the grid below the fold.\n *\n * A designer storefront (Shopify's Dawn, Craft, Sense; Squarespace's commerce\n * templates) is composed of a handful of PARTS, in a familiar order: a hero\n * that sells, the store's bestsellers, its collections as picture tiles, the\n * brand's story beside a photo, why to buy here with icons, what customers\n * say, and a newsletter sign-up. This module names those parts by the words a\n * plan gives its sections, so the plan step can make sure a store's home has\n * them and the compiler and the listings can draw each one its way.\n *\n * It is NOT a section library (Zach 10/7): the model still names, orders and\n * writes every section, and designs each in the layout language; what code\n * adds is a part the plan left out — named in plain words for the model to\n * design — and the platform elements that show the store's own records.\n *\n * Pure: names and plans only.\n */\n\nimport type { AiBuildPlanScreen, AiBuildPlanSection } from '../model/ai-build-plan'\n\n/** A part a storefront home is composed of. */\nexport type AiStorefrontPart = 'featured' | 'collections' | 'story' | 'values' | 'reviews' | 'signup'\n\n/** The section names that say each part, as a plan or a page heading words it. */\nexport const AI_STOREFRONT_PART_WORDS: Readonly<Record<AiStorefrontPart, RegExp>> = {\n featured:\n /\\b(bestsellers?|best[- ]sellers?|best[- ]selling|featured|new arrivals?|arrivals|just in|favou?rites?|signature|most loved|top picks?|our (?:candles|products|picks)|shop (?:all|now|the))\\b/i,\n collections: /\\b(collections?|categor(?:y|ies)|shop by|browse by|by scent|scents?|ranges?|families|occasions?|edits?)\\b/i,\n story: /\\b(story|about|our craft|craft|making|made|maker|process|how (?:we|it)|behind|studio|workshop)\\b/i,\n values: /\\b(why|promise|promises|values?|difference|shipping|returns?|guarantee|perks?|benefits?|care|small[- ]batch|ingredients?|sustainab\\w*)\\b/i,\n reviews: /\\b(reviews?|testimonials?|customers? (?:say|love)|what (?:people|customers|our customers) say|kind words|praise|loved by|social proof)\\b/i,\n signup: /\\b(newsletter|subscribe|sign[- ]?up|mailing list|join (?:us|the list|our list)|stay in touch|in the loop|first to know|inbox|letters? from)\\b/i,\n}\n\n/** Whether a planned section's name says a part. */\nexport function aiStorefrontSays(part: AiStorefrontPart, name: string): boolean {\n return AI_STOREFRONT_PART_WORDS[part].test(name)\n}\n\n/** The plan entry a part the plan left out is added as: plain words the page step designs. */\nconst ADDED: Readonly<Record<'featured' | 'collections' | 'reviews' | 'signup', AiBuildPlanSection>> = {\n featured: { name: 'Bestsellers', uses: [], items: 4 },\n collections: { name: 'Shop by collection', uses: [], items: 3 },\n reviews: { name: 'Customer reviews', uses: [], items: 0 },\n signup: { name: 'Newsletter sign-up', uses: [], items: 0 },\n}\n\n/** The most sections one page of a site start holds (`AI_SITE_MAX_SECTIONS`). */\nconst MOST_SECTIONS = 8\n\nconst firstSegment = (slug: string) => slug.trim().replace(/^\\/+/, '').split('/')[0].toLowerCase()\n\n/** A section that closes a page: its call to action, a contact or a sign-up band. */\nconst CLOSING = /\\b(cta|call to action|closing|close|contact|get in touch|visit)\\b/i\n\n/**\n * A store plan's screens with the storefront a shopper expects (AGL-3676):\n *\n * - THE HOME (paid starts only — the Free wall prices each section, and a\n * Free store sells nothing yet): its hero first, then any part the plan\n * left out, added under plain names while the page has room (eight\n * sections), in order of need: the bestsellers the platform lists,\n * right after the hero; the collections as picture tiles after them; the\n * newsletter sign-up as the page's last band; and customer reviews (the\n * store's real ones, which show only once shoppers leave them) before\n * the close.\n * - THE SHOP PAGE: grid first. Its product grid section moves to second,\n * straight under the page's short title, so the products open above the\n * fold; anything the plan put between them (an intro, a story) follows\n * the grid.\n *\n * Every section the plan named stays, in its order otherwise; nothing is\n * removed, and a page at eight sections is given nothing more.\n */\nexport function aiStorefrontPlanScreens<S extends Pick<AiBuildPlanScreen, 'slug' | 'title' | 'sections'>>(\n screens: readonly S[],\n options: { paid: boolean; isShopPage: (screen: S) => boolean; gridSection: (sections: S['sections']) => number },\n): S[] {\n return screens.map((screen) => {\n if (firstSegment(screen.slug) === '') return options.paid ? storefrontHome(screen) : screen\n if (!options.isShopPage(screen) || screen.sections.length < 3) return screen\n const grid = options.gridSection(screen.sections)\n if (grid <= 1) return screen\n const sections = [...screen.sections]\n const [moved] = sections.splice(grid, 1)\n sections.splice(1, 0, moved)\n return { ...screen, sections }\n })\n}\n\nfunction storefrontHome<S extends Pick<AiBuildPlanScreen, 'sections'>>(screen: S): S {\n const sections = [...screen.sections]\n if (!sections.length) return screen\n // A section about the collections that also names the bestsellers is the bestsellers'.\n const says = (part: AiStorefrontPart, name: string) =>\n aiStorefrontSays(part, name) && (part !== 'collections' || !aiStorefrontSays('featured', name))\n const has = (part: AiStorefrontPart) => sections.some((section, index) => index > 0 && says(part, section.name))\n const room = () => sections.length < MOST_SECTIONS\n // What a shopper meets first matters most, so the parts are added in that\n // order while the page has room: the bestsellers, the collections, the\n // sign-up, then the reviews — which show nothing until a shopper writes one.\n if (!has('featured') && room()) sections.splice(1, 0, { ...ADDED.featured })\n if (!has('collections') && room()) {\n // The collections follow the bestsellers.\n const featured = sections.findIndex((section, index) => index > 0 && says('featured', section.name))\n sections.splice(featured > 0 ? featured + 1 : 1, 0, { ...ADDED.collections })\n }\n // The newsletter is the page's last band, under any closing call to action.\n if (!has('signup') && room()) sections.push({ ...ADDED.signup })\n if (!has('reviews') && room()) {\n // The reviews come just before the page closes: above a closing sign-up\n // and above a closing call to action.\n let at = sections.length\n if (at - 1 > 0 && says('signup', sections[at - 1].name)) at -= 1\n if (at - 1 > 0 && CLOSING.test(sections[at - 1].name)) at -= 1\n sections.splice(at, 0, { ...ADDED.reviews })\n }\n return sections.length === screen.sections.length ? screen : { ...screen, sections }\n}\n\n// ── Icons for a store's reasons to buy ────────────────────────────────────\n\n/** Words that suggest an icon from the platform's icon library, first match wins. */\nconst ICON_WORDS: ReadonlyArray<readonly [RegExp, string]> = [\n [/\\b(ship|shipping|shipped|deliver\\w*|dispatch\\w*|post(?:age|ed)?|courier)\\b/i, 'truck'],\n [/\\b(returns?|refunds?|exchanges?)\\b/i, 'recycle'],\n [/\\b(gifts?|gifting|wrapped|wrapping|boxed)\\b/i, 'gift'],\n [/\\b(natural|soy|organic|plant|vegan|botanical|clean|non-toxic|eco|sustainab\\w*|cotton|wood)\\b/i, 'leaf'],\n [/\\b(hand|handmade|hand-poured|poured|small[- ]batch|batch|crafted|made|maker|artisan)\\b/i, 'care'],\n [/\\b(guarantee\\w*|secure|safe|safety|warranty|protected)\\b/i, 'shield'],\n [/\\b(fast|quick|same[- ]day|next[- ]day)\\b/i, 'fast'],\n [/\\b(quality|award\\w*|premium|finest)\\b/i, 'award'],\n [/\\b(scents?|fragrances?|aroma\\w*|candles?|glow|light|burn\\w*)\\b/i, 'sparkle'],\n [/\\b(local|town|city|community)\\b/i, 'location'],\n [/\\b(support|help|questions?|chat)\\b/i, 'chat'],\n]\n\n/** The fallbacks an item takes in turn, so a row of icons never repeats one. */\nconst ICON_FALLBACKS = ['star', 'heart', 'sparkle', 'check', 'smile', 'award'] as const\n\n/**\n * The icon each of a store's reasons to buy shows (AGL-3676): its own where\n * the design named one, else one its words suggest, else the next of a few\n * neutral ones — never the same twice in one row.\n */\nexport function aiStorefrontItemIcons(\n items: ReadonlyArray<{ title: string; text: string; icon?: string }>,\n known: (word: string | undefined) => boolean,\n): string[] {\n const used = new Set<string>()\n const designed = items.map((item) => {\n if (item.icon && known(item.icon) && !used.has(item.icon)) {\n used.add(item.icon)\n return item.icon\n }\n return ''\n })\n return designed.map((icon, index) => {\n if (icon) return icon\n const words = `${items[index].title} ${items[index].text}`\n const suggested = ICON_WORDS.find(([pattern, word]) => pattern.test(words) && !used.has(word) && known(word))?.[1]\n const picked = suggested ?? ICON_FALLBACKS.find((word) => !used.has(word) && known(word)) ?? 'star'\n used.add(picked)\n return picked\n })\n}\n"],"names":["AI_STOREFRONT_PART_WORDS","featured","collections","story","values","reviews","signup","aiStorefrontSays","part","name","test","ADDED","uses","items","MOST_SECTIONS","firstSegment","slug","trim","replace","split","toLowerCase","CLOSING","aiStorefrontPlanScreens","screens","options","map","screen","paid","storefrontHome","isShopPage","sections","length","grid","gridSection","moved","splice","says","has","some","section","index","room","findIndex","push","at","ICON_WORDS","ICON_FALLBACKS","aiStorefrontItemIcons","known","used","Set","designed","item","icon","add","suggested","words","title","text","find","pattern","word","picked"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-storefront.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A STORE'S STOREFRONT (AGL-3676, second round).\n *\n * Zach, 2026-10-09, on the beta.237 Willow Wick Candles start (job\n * MtlXWjvt9P): \"again this … site does not look like a store front\". Its home\n * was a lifestyle hero, ONE row of featured products and four text-only bands\n * (\"Why soy, why small batch\", \"How our candles are made\", \"Made to be\n * loved\", \"Gifting and shipping\"); its Shop page opened with a tall hero and\n * the grid below the fold.\n *\n * A designer storefront (Shopify's Dawn, Craft, Sense; Squarespace's commerce\n * templates) is composed of a handful of PARTS, in a familiar order: a hero\n * that sells, the store's bestsellers, its collections as picture tiles, the\n * brand's story beside a photo, why to buy here with icons, what customers\n * say, and a newsletter sign-up. This module names those parts by the words a\n * plan gives its sections, so the plan step can make sure a store's home has\n * them and the compiler and the listings can draw each one its way.\n *\n * It is NOT a section library (Zach 10/7): the model still names, orders and\n * writes every section, and designs each in the layout language; what code\n * adds is a part the plan left out — named in plain words for the model to\n * design — and the platform elements that show the store's own records.\n *\n * Pure: names and plans only.\n */\n\nimport type { AiBuildPlanScreen, AiBuildPlanSection } from '../model/ai-build-plan'\n\n/** A part a storefront home is composed of. */\nexport type AiStorefrontPart = 'featured' | 'collections' | 'story' | 'values' | 'reviews' | 'signup'\n\n/** The section names that say each part, as a plan or a page heading words it. */\nexport const AI_STOREFRONT_PART_WORDS: Readonly<Record<AiStorefrontPart, RegExp>> = {\n featured:\n /\\b(bestsellers?|best[- ]sellers?|best[- ]selling|featured|new arrivals?|arrivals|just in|favou?rites?|signature|most loved|top picks?|our (?:candles|products|picks)|shop (?:all|now|the))\\b/i,\n collections: /\\b(collections?|categor(?:y|ies)|shop by|browse by|by scent|scents?|ranges?|families|occasions?|edits?)\\b/i,\n story: /\\b(story|about|our craft|craft|making|made|maker|process|how (?:we|it)|behind|studio|workshop)\\b/i,\n values: /\\b(why|promise|promises|values?|difference|shipping|returns?|guarantee|perks?|benefits?|care|small[- ]batch|ingredients?|sustainab\\w*)\\b/i,\n reviews: /\\b(reviews?|testimonials?|customers? (?:say|love)|what (?:people|customers|our customers) say|kind words|praise|loved by|social proof)\\b/i,\n signup: /\\b(newsletter|subscribe|sign[- ]?up|mailing list|join (?:us|the list|our list)|stay in touch|in the loop|first to know|inbox|letters? from)\\b/i,\n}\n\n/** Whether a planned section's name says a part. */\nexport function aiStorefrontSays(part: AiStorefrontPart, name: string): boolean {\n return AI_STOREFRONT_PART_WORDS[part].test(name)\n}\n\n/** The plan entry a part the plan left out is added as: plain words the page step designs. */\nconst ADDED: Readonly<Record<'featured' | 'collections' | 'reviews' | 'signup', AiBuildPlanSection>> = {\n featured: { name: 'Bestsellers', uses: [], items: 4 },\n collections: { name: 'Shop by collection', uses: [], items: 3 },\n reviews: { name: 'Customer reviews', uses: [], items: 0 },\n signup: { name: 'Newsletter sign-up', uses: [], items: 0 },\n}\n\n/** The most sections one page of a site start holds (`AI_SITE_MAX_SECTIONS`). */\nconst MOST_SECTIONS = 8\n\nconst firstSegment = (slug: string) => slug.trim().replace(/^\\/+/, '').split('/')[0].toLowerCase()\n\n/** A section that closes a page: its call to action, a contact or a sign-up band. */\nconst CLOSING = /\\b(cta|call to action|closing|close|contact|get in touch|visit)\\b/i\n\n/**\n * A store plan's screens with the storefront a shopper expects (AGL-3676):\n *\n * - THE HOME (paid starts only — the Free wall prices each section, and a\n * Free store sells nothing yet): its hero first, then any part the plan\n * left out, added under plain names while the page has room (eight\n * sections), in order of need: the bestsellers the platform lists,\n * right after the hero; the collections as picture tiles after them; the\n * newsletter sign-up as the page's last band; and customer reviews (the\n * store's real ones, which show only once shoppers leave them) before\n * the close.\n * - THE SHOP PAGE: grid first. Its product grid section moves to second,\n * straight under the page's short title, so the products open above the\n * fold; anything the plan put between them (an intro, a story) follows\n * the grid.\n *\n * Every section the plan named stays, in its order otherwise; nothing is\n * removed, and a page at eight sections is given nothing more.\n */\nexport function aiStorefrontPlanScreens<S extends Pick<AiBuildPlanScreen, 'slug' | 'title' | 'sections'>>(\n screens: readonly S[],\n options: { paid: boolean; isShopPage: (screen: S) => boolean; gridSection: (sections: S['sections']) => number },\n): S[] {\n return screens.map((screen) => {\n if (firstSegment(screen.slug) === '') return options.paid ? storefrontHome(screen) : screen\n if (!options.isShopPage(screen) || screen.sections.length < 3) return screen\n const grid = options.gridSection(screen.sections)\n if (grid <= 1) return screen\n const sections = [...screen.sections]\n const [moved] = sections.splice(grid, 1)\n sections.splice(1, 0, moved)\n return { ...screen, sections }\n })\n}\n\nfunction storefrontHome<S extends Pick<AiBuildPlanScreen, 'sections'>>(screen: S): S {\n const sections = [...screen.sections]\n if (!sections.length) return screen\n // A section about the collections that also names the bestsellers is the bestsellers'.\n const says = (part: AiStorefrontPart, name: string) =>\n aiStorefrontSays(part, name) && (part !== 'collections' || !aiStorefrontSays('featured', name))\n const has = (part: AiStorefrontPart) => sections.some((section, index) => index > 0 && says(part, section.name))\n const room = () => sections.length < MOST_SECTIONS\n // What a shopper meets first matters most, so the parts are added in that\n // order while the page has room: the bestsellers, the collections, the\n // sign-up, then the reviews — which show nothing until a shopper writes one.\n if (!has('featured') && room()) sections.splice(1, 0, { ...ADDED.featured })\n if (!has('collections') && room()) {\n // The collections follow the bestsellers.\n const featured = sections.findIndex((section, index) => index > 0 && says('featured', section.name))\n sections.splice(featured > 0 ? featured + 1 : 1, 0, { ...ADDED.collections })\n }\n // The newsletter is the page's last band, under any closing call to action.\n if (!has('signup') && room()) sections.push({ ...ADDED.signup })\n if (!has('reviews') && room()) {\n // The reviews come just before the page closes: above a closing sign-up\n // and above a closing call to action.\n let at = sections.length\n if (at - 1 > 0 && says('signup', sections[at - 1].name)) at -= 1\n if (at - 1 > 0 && CLOSING.test(sections[at - 1].name)) at -= 1\n sections.splice(at, 0, { ...ADDED.reviews })\n }\n return sections.length === screen.sections.length ? screen : { ...screen, sections }\n}\n\n// ── Icons for a row's items: a store's reasons to buy, any site's reasons, its places ──\n\n/**\n * Words that suggest an icon from the platform's icon library, first match\n * wins: a store's reasons (AGL-3676), then a service business's (AGL-4065) —\n * its languages, its water and its tools, its time and its prices, its\n * riders and its sponsors.\n */\nconst ICON_WORDS: ReadonlyArray<readonly [RegExp, string]> = [\n [/\\b(ship|shipping|shipped|deliver\\w*|dispatch\\w*|post(?:age|ed)?|courier)\\b/i, 'truck'],\n [/\\b(returns?|refunds?|exchanges?)\\b/i, 'recycle'],\n [/\\b(gifts?|gifting|wrapped|wrapping|boxed)\\b/i, 'gift'],\n [/\\b(languages?|bilingual|spanish|español|english and|translat\\w*)\\b/i, 'translate'],\n [/\\b(water|watering|irrigation|sprinklers?|drip)\\b/i, 'water'],\n [/\\b(on[- ]time|punctual|hours|time)\\b/i, 'clock'],\n [/\\b(schedules?|scheduling|calendar|dates?|seasons?|seasonal)\\b/i, 'calendar'],\n [/\\b(prices?|pricing|estimates?|quotes?|costs?|upfront|fees?)\\b/i, 'receipt'],\n [/\\b(repairs?|fix\\w*|tools?|wrench\\w*|trades?|drywall|carpentry|mechanics?)\\b/i, 'tools'],\n [/\\b(natural|soy|organic|plant|vegan|botanical|clean|non-toxic|eco|sustainab\\w*|cotton|wood)\\b/i, 'leaf'],\n [/\\b(hand|handmade|hand-poured|poured|small[- ]batch|batch|crafted|made|maker|artisan)\\b/i, 'care'],\n [/\\b(guarantee\\w*|secure|safe|safety|warranty|protected|protect\\w*|helmets?|insured|licensed)\\b/i, 'shield'],\n [/\\b(fast|quick|same[- ]day|next[- ]day)\\b/i, 'fast'],\n [/\\b(quality|award\\w*|premium|finest)\\b/i, 'award'],\n [/\\b(scents?|fragrances?|aroma\\w*|candles?|glow|light|burn\\w*)\\b/i, 'sparkle'],\n [/\\b(sponsors?|sponsorship|partners?|partnership)\\b/i, 'handshake'],\n [/\\b(riders?|racing|racers?|races?|race[- ]day|motocross|podiums?|laps?)\\b/i, 'flag'],\n [/\\b(famil(?:y|ies)|kids|children|youth|parents)\\b/i, 'people'],\n [/\\b(phone|call)\\b/i, 'phone'],\n [/\\b(e-?mail|messages?|inbox)\\b/i, 'mail'],\n [/\\b(local|town|city|community)\\b/i, 'location'],\n [/\\b(support|help|questions?|chat|answers?|advice|explain\\w*)\\b/i, 'chat'],\n]\n\n/**\n * The section names that say their items are places (AGL-4065): \"Areas we\n * serve\", \"Towns we serve\", \"Where we work\", \"Service area\", \"Our\n * locations\". A practice's or a studio's \"areas\" of work are not places.\n */\nconst PLACES =\n /\\b(?:(?:service|delivery|coverage) areas?|areas? (?:we (?:serve|cover|work)|served|covered)|towns?|cities|cit(?:y|ies) we|neighbou?rhoods?|where (?:we (?:work|serve|go|are)|to find us)|find us|locations?)\\b/i\n\n/**\n * The kinds whose rows take the sparkle where no word names an icon: a calm\n * practice's, a salon's, a celebration's. Every other kind takes the check,\n * and draws the sparkle and the heart nowhere their words do not ask for\n * them (AGL-4065).\n */\nconst SPARKLE_KINDS: ReadonlySet<string> = new Set(['yoga', 'wellness', 'beauty', 'events'])\n\n/** The icons only those kinds, and a store whose words name its scents and its glow, take from a word. */\nconst QUIET_ICONS: ReadonlySet<string> = new Set(['sparkle', 'heart'])\n\n/**\n * The icon each item of a row shows (AGL-3676, AGL-4065):\n *\n * - in a section of places (\"Areas we serve\"), the place marker, every one\n * alike: a list of towns is one kind of thing, and the 2026-10-25 booth\n * kit drew its towns with a marker, a star, a heart and a sparkle;\n * - else its own where the design named one;\n * - else one its title suggests, then one its words suggest — \"On time\" a\n * clock, \"English and Spanish\" the translate mark — each once a row;\n * - else one neutral icon, the same along the row: the check, or the\n * sparkle on a calm or celebratory site. Never a random mix.\n *\n * `section` is what the section is called (its plan name and its headings);\n * `kind` is the site's kind (`AI_SITE_KINDS`), absent on a page drawn with\n * no design.\n */\nexport function aiStorefrontItemIcons(\n items: ReadonlyArray<{ title: string; text: string; icon?: string }>,\n known: (word: string | undefined) => boolean,\n options: { section?: string; kind?: string } = {},\n): string[] {\n if (options.section && PLACES.test(options.section) && known('location')) return items.map(() => 'location')\n const sparkles = !!options.kind && SPARKLE_KINDS.has(options.kind)\n const neutral = sparkles && known('sparkle') ? 'sparkle' : 'check'\n // A page drawn with no design keeps every word's icon, as before.\n const quiet = !!options.kind && !sparkles && options.kind !== 'store'\n const used = new Set<string>()\n const designed = items.map((item) => {\n if (item.icon && known(item.icon) && !used.has(item.icon)) {\n used.add(item.icon)\n return item.icon\n }\n return ''\n })\n const suggest = (words: string) =>\n ICON_WORDS.find(([pattern, word]) => pattern.test(words) && !used.has(word) && known(word) && !(quiet && QUIET_ICONS.has(word)))?.[1]\n return designed.map((icon, index) => {\n if (icon) return icon\n const item = items[index] as { title: string; text: string }\n const picked = suggest(item.title) ?? suggest(`${item.title} ${item.text}`)\n if (!picked) return neutral\n used.add(picked)\n return picked\n })\n}\n"],"names":["AI_STOREFRONT_PART_WORDS","featured","collections","story","values","reviews","signup","aiStorefrontSays","part","name","test","ADDED","uses","items","MOST_SECTIONS","firstSegment","slug","trim","replace","split","toLowerCase","CLOSING","aiStorefrontPlanScreens","screens","options","map","screen","paid","storefrontHome","isShopPage","sections","length","grid","gridSection","moved","splice","says","has","some","section","index","room","findIndex","push","at","ICON_WORDS","PLACES","SPARKLE_KINDS","Set","QUIET_ICONS","aiStorefrontItemIcons","known","sparkles","kind","neutral","quiet","used","designed","item","icon","add","suggest","words","find","pattern","word","picked","title","text"],"mappings":"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"}
@@ -60,6 +60,13 @@ export declare const AI_BUILD_CREATE_KINDS: readonly AiBuildPlanCreateKind[];
60
60
  * `items`: an item naming one is a plan that put it in the wrong list.
61
61
  */
62
62
  export declare const AI_BUILD_STRUCTURAL_OPS: readonly string[];
63
+ /**
64
+ * The operation that proposes a change to the site's theme (AGL-4077): the
65
+ * theme runner's proposal, which the owner applies in Setup → Theme. Planned
66
+ * as an item; a plan that names it as its `theme-change` creation, the
67
+ * doctrine's word for it, is built by the same operation rather than refused.
68
+ */
69
+ export declare const AI_BUILD_THEME_OP = "theme";
63
70
  /**
64
71
  * What a build hands an item it made before it (AGL-3728): its pages, by the
65
72
  * title the plan gave each and the id it was written under, and its layouts,