@aglyn/plugins-ai 1.0.0-beta.270 → 1.0.0-beta.272

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 (281) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +4 -3
  3. package/src/lib/activity/ai-activity-actions.js +4 -0
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/assets/site-styles/beauty.webp +0 -0
  6. package/src/lib/assets/site-styles/blog.webp +0 -0
  7. package/src/lib/assets/site-styles/business.webp +0 -0
  8. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  9. package/src/lib/assets/site-styles/education.webp +0 -0
  10. package/src/lib/assets/site-styles/events.webp +0 -0
  11. package/src/lib/assets/site-styles/fitness.webp +0 -0
  12. package/src/lib/assets/site-styles/landing.webp +0 -0
  13. package/src/lib/assets/site-styles/music.webp +0 -0
  14. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  15. package/src/lib/assets/site-styles/personal.webp +0 -0
  16. package/src/lib/assets/site-styles/photography.webp +0 -0
  17. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  18. package/src/lib/assets/site-styles/professional.webp +0 -0
  19. package/src/lib/assets/site-styles/realestate.webp +0 -0
  20. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  21. package/src/lib/assets/site-styles/store.webp +0 -0
  22. package/src/lib/assets/site-styles/studio.webp +0 -0
  23. package/src/lib/assets/site-styles/trades.webp +0 -0
  24. package/src/lib/assets/site-styles/wellness.webp +0 -0
  25. package/src/lib/assets/site-styles/yoga.webp +0 -0
  26. package/src/lib/components/ai-credits-card.component.d.ts +4 -3
  27. package/src/lib/components/ai-credits-card.component.js +15 -15
  28. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  29. package/src/lib/components/ai-job-input-form.component.d.ts +36 -0
  30. package/src/lib/components/ai-job-input-form.component.js +100 -0
  31. package/src/lib/components/ai-job-input-form.component.js.map +1 -0
  32. package/src/lib/components/ai-job-requests.d.ts +4 -0
  33. package/src/lib/components/ai-job-requests.js +4 -0
  34. package/src/lib/components/ai-job-requests.js.map +1 -1
  35. package/src/lib/components/ai-site-build-page.component.js +18 -0
  36. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  37. package/src/lib/components/ai-site-import-job.component.d.ts +41 -0
  38. package/src/lib/components/ai-site-import-job.component.js +707 -0
  39. package/src/lib/components/ai-site-import-job.component.js.map +1 -0
  40. package/src/lib/components/ai-site-import-start.component.d.ts +64 -0
  41. package/src/lib/components/ai-site-import-start.component.js +335 -0
  42. package/src/lib/components/ai-site-import-start.component.js.map +1 -0
  43. package/src/lib/components/ai-site-ownership-proof.component.d.ts +42 -0
  44. package/src/lib/components/ai-site-ownership-proof.component.js +353 -0
  45. package/src/lib/components/ai-site-ownership-proof.component.js.map +1 -0
  46. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  47. package/src/lib/components/ai-site-start-card.component.js +261 -119
  48. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-start-steps.component.d.ts +43 -2
  50. package/src/lib/components/ai-site-start-steps.component.js +651 -15
  51. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  52. package/src/lib/components/ai-site-start-tracking.d.ts +10 -0
  53. package/src/lib/components/ai-site-start-tracking.js +14 -0
  54. package/src/lib/components/ai-site-start-tracking.js.map +1 -1
  55. package/src/lib/components/ai-site-style-previews.js +26 -22
  56. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  57. package/src/lib/components/ai-upsell-dialog.component.d.ts +7 -5
  58. package/src/lib/components/ai-upsell-dialog.component.js +8 -6
  59. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  60. package/src/lib/components/assist-build-card.component.js +11 -1
  61. package/src/lib/components/assist-build-card.component.js.map +1 -1
  62. package/src/lib/components/assist-jobs-drawer.component.js +9 -1
  63. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  64. package/src/lib/components/assist-panel.component.js +8 -4
  65. package/src/lib/components/assist-panel.component.js.map +1 -1
  66. package/src/lib/components/staff-org-ai-card.component.js +4 -7
  67. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  68. package/src/lib/declarations.d.ts +2 -2
  69. package/src/lib/declarations.js +9 -5
  70. package/src/lib/declarations.js.map +1 -1
  71. package/src/lib/jobs/ai-build-capabilities.js +3 -1
  72. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  73. package/src/lib/jobs/ai-business-profile-prefill.d.ts +1 -1
  74. package/src/lib/jobs/ai-business-profile-prefill.js +14 -3
  75. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
  76. package/src/lib/jobs/ai-job-build-step.js +5 -3
  77. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  78. package/src/lib/jobs/ai-job-drafts.d.ts +14 -0
  79. package/src/lib/jobs/ai-job-drafts.js +24 -0
  80. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  81. package/src/lib/jobs/ai-job-form-step.d.ts +15 -1
  82. package/src/lib/jobs/ai-job-form-step.js +75 -8
  83. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  84. package/src/lib/jobs/ai-job-import-step.d.ts +136 -0
  85. package/src/lib/jobs/ai-job-import-step.js +809 -0
  86. package/src/lib/jobs/ai-job-import-step.js.map +1 -0
  87. package/src/lib/jobs/ai-job-page-language.d.ts +52 -1
  88. package/src/lib/jobs/ai-job-page-language.js +99 -5
  89. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  90. package/src/lib/jobs/ai-job-page-step.d.ts +19 -2
  91. package/src/lib/jobs/ai-job-page-step.js +42 -18
  92. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-plan-step.d.ts +25 -2
  94. package/src/lib/jobs/ai-job-plan-step.js +103 -29
  95. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-settings-step.js +4 -3
  97. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  98. package/src/lib/jobs/ai-job-site-datasets.d.ts +27 -0
  99. package/src/lib/jobs/ai-job-site-datasets.js +140 -14
  100. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  101. package/src/lib/jobs/ai-job-site-look.js +18 -5
  102. package/src/lib/jobs/ai-job-site-look.js.map +1 -1
  103. package/src/lib/jobs/ai-job-site-step.d.ts +16 -1
  104. package/src/lib/jobs/ai-job-site-step.js +95 -22
  105. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-text-step.d.ts +15 -1
  107. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  108. package/src/lib/jobs/ai-job-tracking-step.d.ts +14 -3
  109. package/src/lib/jobs/ai-job-tracking-step.js +28 -5
  110. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  111. package/src/lib/jobs/ai-jobs.d.ts +13 -1
  112. package/src/lib/jobs/ai-jobs.js +77 -10
  113. package/src/lib/jobs/ai-jobs.js.map +1 -1
  114. package/src/lib/jobs/ai-layout-owner-photos.d.ts +16 -1
  115. package/src/lib/jobs/ai-layout-owner-photos.js +47 -0
  116. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
  117. package/src/lib/jobs/ai-layout-stock-photos.d.ts +27 -2
  118. package/src/lib/jobs/ai-layout-stock-photos.js +190 -19
  119. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  120. package/src/lib/jobs/ai-site-inspiration-signals.d.ts +98 -0
  121. package/src/lib/jobs/ai-site-inspiration-signals.js +585 -0
  122. package/src/lib/jobs/ai-site-inspiration-signals.js.map +1 -0
  123. package/src/lib/jobs/ai-site-inspiration.d.ts +157 -0
  124. package/src/lib/jobs/ai-site-inspiration.js +346 -0
  125. package/src/lib/jobs/ai-site-inspiration.js.map +1 -0
  126. package/src/lib/jobs/ai-site-publish.d.ts +26 -1
  127. package/src/lib/jobs/ai-site-publish.js +162 -13
  128. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  129. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +77 -3
  130. package/src/lib/jobs/ai-theme-brand-inputs.js +135 -31
  131. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  132. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.d.ts +97 -0
  133. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js +137 -0
  134. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js.map +1 -0
  135. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.d.ts +38 -0
  136. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js +103 -0
  137. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js.map +1 -0
  138. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.d.ts +25 -0
  139. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js +174 -0
  140. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js.map +1 -0
  141. package/src/lib/layout-language/ai-layout-compiler.d.ts +1 -1
  142. package/src/lib/layout-language/ai-layout-compiler.js +438 -25
  143. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-contact.d.ts +57 -0
  145. package/src/lib/layout-language/ai-layout-contact.js +137 -0
  146. package/src/lib/layout-language/ai-layout-contact.js.map +1 -0
  147. package/src/lib/layout-language/ai-layout-copy.d.ts +12 -0
  148. package/src/lib/layout-language/ai-layout-copy.js +34 -2
  149. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  150. package/src/lib/layout-language/ai-layout-design.d.ts +65 -8
  151. package/src/lib/layout-language/ai-layout-design.js +411 -97
  152. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  153. package/src/lib/layout-language/ai-layout-frame.js +23 -0
  154. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  155. package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
  156. package/src/lib/layout-language/ai-layout-labels.js +12 -6
  157. package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
  158. package/src/lib/layout-language/ai-layout-language.d.ts +3 -2
  159. package/src/lib/layout-language/ai-layout-language.js +4 -3
  160. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  161. package/src/lib/layout-language/ai-layout-links.d.ts +13 -0
  162. package/src/lib/layout-language/ai-layout-links.js +30 -0
  163. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  164. package/src/lib/layout-language/ai-layout-pictures.d.ts +49 -6
  165. package/src/lib/layout-language/ai-layout-pictures.js +145 -22
  166. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  167. package/src/lib/model/ai-build-job.d.ts +16 -1
  168. package/src/lib/model/ai-build-job.js +40 -0
  169. package/src/lib/model/ai-build-job.js.map +1 -1
  170. package/src/lib/model/ai-credit-estimate.d.ts +20 -0
  171. package/src/lib/model/ai-credit-estimate.js +13 -0
  172. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  173. package/src/lib/model/ai-job-activity.d.ts +2 -2
  174. package/src/lib/model/ai-job-activity.js +6 -4
  175. package/src/lib/model/ai-job-activity.js.map +1 -1
  176. package/src/lib/model/ai-job-input.d.ts +33 -0
  177. package/src/lib/model/ai-job-input.js +59 -0
  178. package/src/lib/model/ai-job-input.js.map +1 -0
  179. package/src/lib/model/ai-job-notice.d.ts +4 -0
  180. package/src/lib/model/ai-job-notice.js +28 -4
  181. package/src/lib/model/ai-job-notice.js.map +1 -1
  182. package/src/lib/model/ai-jobs.types.d.ts +69 -3
  183. package/src/lib/model/ai-jobs.types.js +2 -1
  184. package/src/lib/model/ai-jobs.types.js.map +1 -1
  185. package/src/lib/model/ai-site-details.d.ts +122 -0
  186. package/src/lib/model/ai-site-details.js +300 -0
  187. package/src/lib/model/ai-site-details.js.map +1 -0
  188. package/src/lib/model/ai-site-entity.d.ts +181 -0
  189. package/src/lib/model/ai-site-entity.js +390 -0
  190. package/src/lib/model/ai-site-entity.js.map +1 -0
  191. package/src/lib/model/ai-site-import-view.d.ts +130 -0
  192. package/src/lib/model/ai-site-import-view.js +182 -0
  193. package/src/lib/model/ai-site-import-view.js.map +1 -0
  194. package/src/lib/model/ai-site-import.d.ts +302 -0
  195. package/src/lib/model/ai-site-import.js +526 -0
  196. package/src/lib/model/ai-site-import.js.map +1 -0
  197. package/src/lib/model/ai-site-inspiration.d.ts +72 -0
  198. package/src/lib/model/ai-site-inspiration.js +109 -0
  199. package/src/lib/model/ai-site-inspiration.js.map +1 -0
  200. package/src/lib/model/ai-site-job.d.ts +13 -1
  201. package/src/lib/model/ai-site-job.js +15 -2
  202. package/src/lib/model/ai-site-job.js.map +1 -1
  203. package/src/lib/model/ai-site-kinds.js +109 -21
  204. package/src/lib/model/ai-site-kinds.js.map +1 -1
  205. package/src/lib/model/ai-site-look.d.ts +10 -0
  206. package/src/lib/model/ai-site-look.js +24 -0
  207. package/src/lib/model/ai-site-look.js.map +1 -1
  208. package/src/lib/model/ai-site-start-seo.d.ts +19 -5
  209. package/src/lib/model/ai-site-start-seo.js +25 -7
  210. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  211. package/src/lib/model/ai-site-start-steps.d.ts +9 -3
  212. package/src/lib/model/ai-site-start-steps.js +67 -5
  213. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  214. package/src/lib/model/ai-site-start.d.ts +81 -4
  215. package/src/lib/model/ai-site-start.js +207 -13
  216. package/src/lib/model/ai-site-start.js.map +1 -1
  217. package/src/lib/model/ai-upsell-copy.js +4 -1
  218. package/src/lib/model/ai-upsell-copy.js.map +1 -1
  219. package/src/lib/plan-entitlements.d.ts +4 -3
  220. package/src/lib/plan-entitlements.js +3 -8
  221. package/src/lib/plan-entitlements.js.map +1 -1
  222. package/src/lib/runtime/ai-dataset-generation.d.ts +8 -1
  223. package/src/lib/runtime/ai-dataset-generation.js +12 -2
  224. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  225. package/src/lib/runtime/ai-doctrine-validators.d.ts +26 -33
  226. package/src/lib/runtime/ai-doctrine-validators.js +54 -44
  227. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  228. package/src/lib/runtime/ai-doctrine.js +1 -1
  229. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  230. package/src/lib/runtime/ai-node-tree.d.ts +7 -0
  231. package/src/lib/runtime/ai-node-tree.js +9 -1
  232. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  233. package/src/lib/server/ai-create-route.d.ts +1 -1
  234. package/src/lib/server/ai-create-route.js +3 -4
  235. package/src/lib/server/ai-create-route.js.map +1 -1
  236. package/src/lib/server/ai-jobs-resume.js +27 -3
  237. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  238. package/src/lib/server/ai-jobs-route.js +18 -0
  239. package/src/lib/server/ai-jobs-route.js.map +1 -1
  240. package/src/lib/server/ai-site-import-route.d.ts +89 -0
  241. package/src/lib/server/ai-site-import-route.js +356 -0
  242. package/src/lib/server/ai-site-import-route.js.map +1 -0
  243. package/src/lib/server.js +14 -0
  244. package/src/lib/server.js.map +1 -1
  245. package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +45 -0
  246. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +313 -0
  247. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -0
  248. package/src/lib/site-import/fixtures/site-import-wave-recordings.d.ts +50 -0
  249. package/src/lib/site-import/fixtures/site-import-wave-recordings.js +217 -0
  250. package/src/lib/site-import/fixtures/site-import-wave-recordings.js.map +1 -0
  251. package/src/lib/site-import/site-import-crawl.d.ts +163 -0
  252. package/src/lib/site-import/site-import-crawl.js +467 -0
  253. package/src/lib/site-import/site-import-crawl.js.map +1 -0
  254. package/src/lib/site-import/site-import-flag.d.ts +23 -0
  255. package/src/lib/site-import/site-import-flag.js +23 -0
  256. package/src/lib/site-import/site-import-flag.js.map +1 -0
  257. package/src/lib/site-import/site-import-html.d.ts +94 -0
  258. package/src/lib/site-import/site-import-html.js +462 -0
  259. package/src/lib/site-import/site-import-html.js.map +1 -0
  260. package/src/lib/site-import/site-import-images.d.ts +54 -0
  261. package/src/lib/site-import/site-import-images.js +204 -0
  262. package/src/lib/site-import/site-import-images.js.map +1 -0
  263. package/src/lib/site-import/site-import-page.d.ts +48 -0
  264. package/src/lib/site-import/site-import-page.js +150 -0
  265. package/src/lib/site-import/site-import-page.js.map +1 -0
  266. package/src/lib/site-import/site-import-review.d.ts +40 -0
  267. package/src/lib/site-import/site-import-review.js +149 -0
  268. package/src/lib/site-import/site-import-review.js.map +1 -0
  269. package/src/lib/site-import/site-import-robots.d.ts +52 -0
  270. package/src/lib/site-import/site-import-robots.js +126 -0
  271. package/src/lib/site-import/site-import-robots.js.map +1 -0
  272. package/src/lib/usage/assist-credits.d.ts +4 -4
  273. package/src/lib/usage/assist-credits.js +4 -4
  274. package/src/lib/usage/assist-credits.js.map +1 -1
  275. package/src/lib/usage/staff-org-ai-view.js +6 -3
  276. package/src/lib/usage/staff-org-ai-view.js.map +1 -1
  277. package/src/lib/usage/staff-org-ai.d.ts +4 -2
  278. package/src/lib/usage/staff-org-ai.js +7 -7
  279. package/src/lib/usage/staff-org-ai.js.map +1 -1
  280. package/src/lib/usage/staff-org-usage-ai.js +2 -2
  281. package/src/lib/usage/staff-org-usage-ai.js.map +1 -1
@@ -49,9 +49,24 @@ export declare function aiOwnerPhotoFits(slot: AiLayoutPictureSlot): boolean;
49
49
  * search, in slot order.
50
50
  */
51
51
  export declare function aiAssignOwnerPhotos(slots: readonly AiLayoutPictureSlot[], photos: readonly PluginMediaAsset[], avoid?: ReadonlySet<string>): Array<AiLayoutPicturePhoto | null>;
52
+ /** The unit input carrying the photos a page brought in from the owner's own site (AGL-3976). */
53
+ export declare const AI_LAYOUT_PAGE_PHOTOS_INPUT = "pagePhotos";
54
+ /** The page's own photos a job's inputs carry, read defensively: library assets with their sizes only. */
55
+ export declare function aiLayoutPagePhotosOf(inputs: Readonly<Record<string, unknown>> | null | undefined): PluginMediaAsset[];
56
+ /**
57
+ * The photos a page brought in from the owner's own site placed first
58
+ * (AGL-3976): the page's own pictures, already in the site's library, fill
59
+ * the slots they fit, in the same order an owner's photos do and with no
60
+ * job-wide repeat rule, since a team photo the old site showed twice is the
61
+ * owner's to show twice. The slots left go to `next`.
62
+ */
63
+ export declare function aiLayoutPagePhotoSource(photos: readonly PluginMediaAsset[], next: AiLayoutPictureSource | null): AiLayoutPictureSource | null;
52
64
  /**
53
65
  * The source for one page: the owner's photos for the slots they fit, the
54
66
  * stock library for the rest. `null` only when neither can answer — no
55
67
  * media door and no stock library — which leaves every slot to the starters.
68
+ * A page a site import brings in (AGL-3976) places its own pictures first.
56
69
  */
57
- export declare function aiLayoutSitePhotoSource(input: AiLayoutStockPhotoSourceInput, deps?: AiLayoutOwnerPhotoDeps): AiLayoutPictureSource | null;
70
+ export declare function aiLayoutSitePhotoSource(input: AiLayoutStockPhotoSourceInput & {
71
+ pagePhotos?: readonly PluginMediaAsset[];
72
+ }, deps?: AiLayoutOwnerPhotoDeps): AiLayoutPictureSource | null;
@@ -67,11 +67,58 @@ function area(photo) {
67
67
  }
68
68
  return answers;
69
69
  }
70
+ /** The unit input carrying the photos a page brought in from the owner's own site (AGL-3976). */ export const AI_LAYOUT_PAGE_PHOTOS_INPUT = 'pagePhotos';
71
+ /** The page's own photos a job's inputs carry, read defensively: library assets with their sizes only. */ export function aiLayoutPagePhotosOf(inputs) {
72
+ const raw = inputs == null ? void 0 : inputs[AI_LAYOUT_PAGE_PHOTOS_INPUT];
73
+ if (!Array.isArray(raw)) return [];
74
+ const photos = [];
75
+ for (const entry of raw){
76
+ if (!entry || typeof entry !== 'object') continue;
77
+ const { mediaId, src, width, height, alt } = entry;
78
+ if (typeof mediaId !== 'string' || typeof src !== 'string' || !src.startsWith('media:')) continue;
79
+ if (typeof width !== 'number' || typeof height !== 'number' || width <= 0 || height <= 0) continue;
80
+ photos.push(_extends({
81
+ mediaId,
82
+ src,
83
+ width,
84
+ height
85
+ }, typeof alt === 'string' && alt.trim() ? {
86
+ alt: alt.trim()
87
+ } : {}));
88
+ }
89
+ return photos;
90
+ }
91
+ /**
92
+ * The photos a page brought in from the owner's own site placed first
93
+ * (AGL-3976): the page's own pictures, already in the site's library, fill
94
+ * the slots they fit, in the same order an owner's photos do and with no
95
+ * job-wide repeat rule, since a team photo the old site showed twice is the
96
+ * owner's to show twice. The slots left go to `next`.
97
+ */ export function aiLayoutPagePhotoSource(photos, next) {
98
+ if (!photos.length) return next;
99
+ return async (slots)=>{
100
+ const answers = aiAssignOwnerPhotos(slots, photos);
101
+ if (!next) return answers;
102
+ const open = slots.filter((_slot, index)=>!answers[index]);
103
+ if (!open.length) return answers;
104
+ const found = await next(open);
105
+ let index = 0;
106
+ return answers.map((answer)=>{
107
+ var _ref;
108
+ return (_ref = answer != null ? answer : found[index++]) != null ? _ref : null;
109
+ });
110
+ };
111
+ }
70
112
  /**
71
113
  * The source for one page: the owner's photos for the slots they fit, the
72
114
  * stock library for the rest. `null` only when neither can answer — no
73
115
  * media door and no stock library — which leaves every slot to the starters.
116
+ * A page a site import brings in (AGL-3976) places its own pictures first.
74
117
  */ export function aiLayoutSitePhotoSource(input, deps = {}) {
118
+ var _input_pagePhotos;
119
+ return aiLayoutPagePhotoSource((_input_pagePhotos = input.pagePhotos) != null ? _input_pagePhotos : [], aiLayoutOwnerAndStockSource(input, deps));
120
+ }
121
+ function aiLayoutOwnerAndStockSource(input, deps) {
75
122
  var _deps_stock, _deps_ingest;
76
123
  var _ingest_listOwnerPhotos;
77
124
  const stock = ((_deps_stock = deps.stock) != null ? _deps_stock : aiLayoutStockPhotoSource)(input, deps);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-layout-owner-photos.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 type { PluginMediaAsset } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport { pluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport type {\n AiLayoutPicturePhoto,\n AiLayoutPictureSlot,\n AiLayoutPictureSource,\n} from '../layout-language/ai-layout-pictures'\nimport {\n aiLayoutStockPhotoSource,\n type AiLayoutStockPhotoDeps,\n type AiLayoutStockPhotoSourceInput,\n} from './ai-layout-stock-photos'\n\n/**\n * THE BUSINESS'S OWN PHOTOS COME FIRST (AGL-3957).\n *\n * A site built for an owner at a booth or on a doorstep has, by the time its\n * pages are drawn, the photos they sent from their phone through a site\n * photo link: their van, their shop, their work. A photo of the owner's own\n * work beats any stock photo, so a page's picture slots are filled from\n * those first, and only the slots left over are searched for in the stock\n * library, the way they were before.\n *\n * Which photo goes where is decided by shape and size, not by subject: an\n * owner's photo says nothing a search could read. The hero takes the widest\n * photo among the largest; the about picture takes the next largest; the\n * gallery takes the rest, newest first, each photo once on the page and once\n * across the job (`avoid`). A product's photo, a person's portrait and a\n * record's cover are of one named thing, which a photo of the business in\n * general is not, so those slots go to the stock search untouched.\n */\n\nexport interface AiLayoutOwnerPhotoDeps extends AiLayoutStockPhotoDeps {\n /** The stock source the leftover slots go to; the real one otherwise. Specs hand in a fake. */\n stock?: typeof aiLayoutStockPhotoSource\n /** The most owner photos read for one page. */\n limit?: number\n}\n\nconst OWNER_PHOTOS_LIMIT = 60\n\n/** A slot an owner's photo may fill: one of the business in general, not of one named thing. */\nexport function aiOwnerPhotoFits(slot: AiLayoutPictureSlot): boolean {\n return !slot.product && !slot.people && !slot.record\n}\n\n/** Fill order: the hero first, so the biggest photo is the first one seen; then about; then the gallery in document order. */\nconst ROLE_RANK = { hero: 0, about: 1, gallery: 2 } as const\n\nfunction area(photo: PluginMediaAsset): number {\n return (photo.width ?? 0) * (photo.height ?? 0)\n}\n\n/**\n * Assigns the owner's photos to the slots, in the order above. Pure, so a\n * spec can hold the choice itself: `null` for a slot left to the stock\n * search, in slot order.\n */\nexport function aiAssignOwnerPhotos(\n slots: readonly AiLayoutPictureSlot[],\n photos: readonly PluginMediaAsset[],\n avoid: ReadonlySet<string> = new Set(),\n): Array<AiLayoutPicturePhoto | null> {\n const pool = photos\n .filter((photo) => photo.src && (photo.width ?? 0) > 0 && (photo.height ?? 0) > 0 && !avoid.has(photo.src))\n .sort((a, b) => area(b) - area(a))\n const answers: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n const used = new Set<string>()\n const order = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot }) => aiOwnerPhotoFits(slot))\n .sort((a, b) => ROLE_RANK[a.slot.role] - ROLE_RANK[b.slot.role] || a.index - b.index)\n for (const { slot, index } of order) {\n const free = pool.filter((photo) => !used.has(photo.src))\n if (!free.length) break\n // The hero wants the widest of the three largest; a portrait phone photo\n // in a wide frame is the one mismatch a visitor sees first.\n const pick =\n slot.role === 'hero'\n ? [...free.slice(0, 3)].sort(\n (a, b) => (b.width ?? 0) / (b.height ?? 1) - (a.width ?? 0) / (a.height ?? 1),\n )[0]\n : free[0]\n if (!pick) break\n used.add(pick.src)\n answers[index] = {\n src: pick.src,\n width: pick.width as number,\n height: pick.height as number,\n ...(pick.alt ? { alt: pick.alt } : {}),\n }\n }\n return answers\n}\n\n/**\n * The source for one page: the owner's photos for the slots they fit, the\n * stock library for the rest. `null` only when neither can answer — no\n * media door and no stock library — which leaves every slot to the starters.\n */\nexport function aiLayoutSitePhotoSource(\n input: AiLayoutStockPhotoSourceInput,\n deps: AiLayoutOwnerPhotoDeps = {},\n): AiLayoutPictureSource | null {\n const stock = (deps.stock ?? aiLayoutStockPhotoSource)(input, deps)\n const ingest = (deps.ingest ?? pluginMediaIngest)()\n const list = ingest?.listOwnerPhotos?.bind(ingest)\n if (!list || !input.hostId) return stock\n return async (slots) => {\n let photos: PluginMediaAsset[] = []\n try {\n photos = await list({ hostId: input.hostId, limit: deps.limit ?? OWNER_PHOTOS_LIMIT })\n } catch (error) {\n console.warn('ai layout pictures: the owner photos could not be read; stock photos fill the page', { error })\n }\n const answers = aiAssignOwnerPhotos(slots, photos, new Set(input.avoid ?? []))\n if (!stock) return answers\n const open = slots.filter((_slot, index) => !answers[index])\n if (!open.length) return answers\n // The stock source sees only the slots the owner's photos left, so its\n // own no-repeat rules and search budget are spent on those alone.\n const found = await stock(open)\n let next = 0\n return answers.map((answer) => answer ?? found[next++] ?? null)\n }\n}\n"],"names":["pluginMediaIngest","aiLayoutStockPhotoSource","OWNER_PHOTOS_LIMIT","aiOwnerPhotoFits","slot","product","people","record","ROLE_RANK","hero","about","gallery","area","photo","width","height","aiAssignOwnerPhotos","slots","photos","avoid","Set","pool","filter","src","has","sort","a","b","answers","map","used","order","index","role","free","length","pick","slice","add","alt","aiLayoutSitePhotoSource","input","deps","ingest","stock","list","listOwnerPhotos","bind","hostId","limit","error","console","warn","open","_slot","found","next","answer"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-layout-owner-photos.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 type { PluginMediaAsset } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport { pluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport type {\n AiLayoutPicturePhoto,\n AiLayoutPictureSlot,\n AiLayoutPictureSource,\n} from '../layout-language/ai-layout-pictures'\nimport {\n aiLayoutStockPhotoSource,\n type AiLayoutStockPhotoDeps,\n type AiLayoutStockPhotoSourceInput,\n} from './ai-layout-stock-photos'\n\n/**\n * THE BUSINESS'S OWN PHOTOS COME FIRST (AGL-3957).\n *\n * A site built for an owner at a booth or on a doorstep has, by the time its\n * pages are drawn, the photos they sent from their phone through a site\n * photo link: their van, their shop, their work. A photo of the owner's own\n * work beats any stock photo, so a page's picture slots are filled from\n * those first, and only the slots left over are searched for in the stock\n * library, the way they were before.\n *\n * Which photo goes where is decided by shape and size, not by subject: an\n * owner's photo says nothing a search could read. The hero takes the widest\n * photo among the largest; the about picture takes the next largest; the\n * gallery takes the rest, newest first, each photo once on the page and once\n * across the job (`avoid`). A product's photo, a person's portrait and a\n * record's cover are of one named thing, which a photo of the business in\n * general is not, so those slots go to the stock search untouched.\n */\n\nexport interface AiLayoutOwnerPhotoDeps extends AiLayoutStockPhotoDeps {\n /** The stock source the leftover slots go to; the real one otherwise. Specs hand in a fake. */\n stock?: typeof aiLayoutStockPhotoSource\n /** The most owner photos read for one page. */\n limit?: number\n}\n\nconst OWNER_PHOTOS_LIMIT = 60\n\n/** A slot an owner's photo may fill: one of the business in general, not of one named thing. */\nexport function aiOwnerPhotoFits(slot: AiLayoutPictureSlot): boolean {\n return !slot.product && !slot.people && !slot.record\n}\n\n/** Fill order: the hero first, so the biggest photo is the first one seen; then about; then the gallery in document order. */\nconst ROLE_RANK = { hero: 0, about: 1, gallery: 2 } as const\n\nfunction area(photo: PluginMediaAsset): number {\n return (photo.width ?? 0) * (photo.height ?? 0)\n}\n\n/**\n * Assigns the owner's photos to the slots, in the order above. Pure, so a\n * spec can hold the choice itself: `null` for a slot left to the stock\n * search, in slot order.\n */\nexport function aiAssignOwnerPhotos(\n slots: readonly AiLayoutPictureSlot[],\n photos: readonly PluginMediaAsset[],\n avoid: ReadonlySet<string> = new Set(),\n): Array<AiLayoutPicturePhoto | null> {\n const pool = photos\n .filter((photo) => photo.src && (photo.width ?? 0) > 0 && (photo.height ?? 0) > 0 && !avoid.has(photo.src))\n .sort((a, b) => area(b) - area(a))\n const answers: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n const used = new Set<string>()\n const order = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot }) => aiOwnerPhotoFits(slot))\n .sort((a, b) => ROLE_RANK[a.slot.role] - ROLE_RANK[b.slot.role] || a.index - b.index)\n for (const { slot, index } of order) {\n const free = pool.filter((photo) => !used.has(photo.src))\n if (!free.length) break\n // The hero wants the widest of the three largest; a portrait phone photo\n // in a wide frame is the one mismatch a visitor sees first.\n const pick =\n slot.role === 'hero'\n ? [...free.slice(0, 3)].sort(\n (a, b) => (b.width ?? 0) / (b.height ?? 1) - (a.width ?? 0) / (a.height ?? 1),\n )[0]\n : free[0]\n if (!pick) break\n used.add(pick.src)\n answers[index] = {\n src: pick.src,\n width: pick.width as number,\n height: pick.height as number,\n ...(pick.alt ? { alt: pick.alt } : {}),\n }\n }\n return answers\n}\n\n/** The unit input carrying the photos a page brought in from the owner's own site (AGL-3976). */\nexport const AI_LAYOUT_PAGE_PHOTOS_INPUT = 'pagePhotos'\n\n/** The page's own photos a job's inputs carry, read defensively: library assets with their sizes only. */\nexport function aiLayoutPagePhotosOf(inputs: Readonly<Record<string, unknown>> | null | undefined): PluginMediaAsset[] {\n const raw = inputs?.[AI_LAYOUT_PAGE_PHOTOS_INPUT]\n if (!Array.isArray(raw)) return []\n const photos: PluginMediaAsset[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { mediaId, src, width, height, alt } = entry as Record<string, unknown>\n if (typeof mediaId !== 'string' || typeof src !== 'string' || !src.startsWith('media:')) continue\n if (typeof width !== 'number' || typeof height !== 'number' || width <= 0 || height <= 0) continue\n photos.push({ mediaId, src, width, height, ...(typeof alt === 'string' && alt.trim() ? { alt: alt.trim() } : {}) })\n }\n return photos\n}\n\n/**\n * The photos a page brought in from the owner's own site placed first\n * (AGL-3976): the page's own pictures, already in the site's library, fill\n * the slots they fit, in the same order an owner's photos do and with no\n * job-wide repeat rule, since a team photo the old site showed twice is the\n * owner's to show twice. The slots left go to `next`.\n */\nexport function aiLayoutPagePhotoSource(\n photos: readonly PluginMediaAsset[],\n next: AiLayoutPictureSource | null,\n): AiLayoutPictureSource | null {\n if (!photos.length) return next\n return async (slots) => {\n const answers = aiAssignOwnerPhotos(slots, photos)\n if (!next) return answers\n const open = slots.filter((_slot, index) => !answers[index])\n if (!open.length) return answers\n const found = await next(open)\n let index = 0\n return answers.map((answer) => answer ?? found[index++] ?? null)\n }\n}\n\n/**\n * The source for one page: the owner's photos for the slots they fit, the\n * stock library for the rest. `null` only when neither can answer — no\n * media door and no stock library — which leaves every slot to the starters.\n * A page a site import brings in (AGL-3976) places its own pictures first.\n */\nexport function aiLayoutSitePhotoSource(\n input: AiLayoutStockPhotoSourceInput & { pagePhotos?: readonly PluginMediaAsset[] },\n deps: AiLayoutOwnerPhotoDeps = {},\n): AiLayoutPictureSource | null {\n return aiLayoutPagePhotoSource(input.pagePhotos ?? [], aiLayoutOwnerAndStockSource(input, deps))\n}\n\nfunction aiLayoutOwnerAndStockSource(\n input: AiLayoutStockPhotoSourceInput,\n deps: AiLayoutOwnerPhotoDeps,\n): AiLayoutPictureSource | null {\n const stock = (deps.stock ?? aiLayoutStockPhotoSource)(input, deps)\n const ingest = (deps.ingest ?? pluginMediaIngest)()\n const list = ingest?.listOwnerPhotos?.bind(ingest)\n if (!list || !input.hostId) return stock\n return async (slots) => {\n let photos: PluginMediaAsset[] = []\n try {\n photos = await list({ hostId: input.hostId, limit: deps.limit ?? OWNER_PHOTOS_LIMIT })\n } catch (error) {\n console.warn('ai layout pictures: the owner photos could not be read; stock photos fill the page', { error })\n }\n const answers = aiAssignOwnerPhotos(slots, photos, new Set(input.avoid ?? []))\n if (!stock) return answers\n const open = slots.filter((_slot, index) => !answers[index])\n if (!open.length) return answers\n // The stock source sees only the slots the owner's photos left, so its\n // own no-repeat rules and search budget are spent on those alone.\n const found = await stock(open)\n let next = 0\n return answers.map((answer) => answer ?? found[next++] ?? null)\n }\n}\n"],"names":["pluginMediaIngest","aiLayoutStockPhotoSource","OWNER_PHOTOS_LIMIT","aiOwnerPhotoFits","slot","product","people","record","ROLE_RANK","hero","about","gallery","area","photo","width","height","aiAssignOwnerPhotos","slots","photos","avoid","Set","pool","filter","src","has","sort","a","b","answers","map","used","order","index","role","free","length","pick","slice","add","alt","AI_LAYOUT_PAGE_PHOTOS_INPUT","aiLayoutPagePhotosOf","inputs","raw","Array","isArray","entry","mediaId","startsWith","push","trim","aiLayoutPagePhotoSource","next","open","_slot","found","answer","aiLayoutSitePhotoSource","input","deps","pagePhotos","aiLayoutOwnerAndStockSource","ingest","stock","list","listOwnerPhotos","bind","hostId","limit","error","console","warn"],"mappings":"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"}
@@ -17,7 +17,7 @@
17
17
  import { type PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest';
18
18
  import { type StockPhoto, type StockPhotoOrientation, type StockPhotoProvider, type StockPhotoSearchRequest } from '@aglyn/aglyn/plugin-manager/stock-photo-provider';
19
19
  import type { NodesMap } from '@aglyn/aglyn/types/nodes';
20
- import { type AiLayoutPictureSlot, type AiLayoutPictureSource } from '../layout-language/ai-layout-pictures';
20
+ import { type AiLayoutPicturePhoto, type AiLayoutPictureSlot, type AiLayoutPictureSource } from '../layout-language/ai-layout-pictures';
21
21
  import type { AiBuildPlan } from '../model/ai-build-plan';
22
22
  /**
23
23
  * STOCK PHOTOS FOR A LANGUAGE PAGE'S PICTURE SLOTS (AGL-3660).
@@ -178,6 +178,14 @@ export declare const AI_STOCK_PHOTO_PAGES_INPUT = "stockPhotoPages";
178
178
  * (AGL-3616) — which a page does not place again.
179
179
  */
180
180
  export declare const AI_STOCK_PHOTO_AVOID_INPUT = "stockPhotoAvoid";
181
+ /**
182
+ * The unit job input holding the photos another part of the job placed
183
+ * outside its pages — a dataset's records — with their size and words
184
+ * (AGL-3988): what a picture the library failed takes before any starter.
185
+ */
186
+ export declare const AI_STOCK_PHOTO_JOB_PHOTOS_INPUT = "stockPhotoJobPhotos";
187
+ /** The job photos a unit input holds ({@link AI_STOCK_PHOTO_JOB_PHOTOS_INPUT}): library photos with a size; anything else is dropped. */
188
+ export declare function aiStockJobPhotosOf(value: unknown): AiLayoutPicturePhoto[];
181
189
  /**
182
190
  * A word's singular, for matching only: "bowls" and "bowl", "vases" and
183
191
  * "vase", "berries" and "berry", "dishes" and "dish" meet. Both sides of a
@@ -591,6 +599,13 @@ export declare function aiStockPick(photos: readonly StockPhoto[], used: Readonl
591
599
  * not the job's, and are not listed.
592
600
  */
593
601
  export declare function aiStockPlacedSrcs(nodes: NodesMap | null | undefined): string[];
602
+ /**
603
+ * The library photos a stored page shows, each as a slot takes it (AGL-3988):
604
+ * its source, its size and what it shows. A photo the job placed on another
605
+ * page is what a slot the library failed takes before any starter
606
+ * (`aiLayoutJobPhotoPicks`). Each photo once; one without a size is left out.
607
+ */
608
+ export declare function aiStockPlacedPhotos(nodes: NodesMap | null | undefined): AiLayoutPicturePhoto[];
594
609
  /** Forgets every job's placed photos; for specs. */
595
610
  export declare function aiStockForgetJobPhotos(): void;
596
611
  /**
@@ -612,7 +627,17 @@ export declare function aiStockForgetJobPhotos(): void;
612
627
  * - a second thing comes only from the first two tags and where no place is
613
628
  * said, what a person holds only from the first three, where it is set
614
629
  * from the first ten;
615
- * - no word is said twice: never "an elderly couple and an elderly".
630
+ * - no word is said twice: never "an elderly couple and an elderly";
631
+ * - the sky, the clouds and the light are no thing anyone holds, and the
632
+ * weather is said as where it is: "a couple in the fog at a wedding",
633
+ * "a couple in the snow in the mountains", never "a couple with a sky at
634
+ * sunset" or "a snow in the mountains"; a feeling ("care", "worry") is no
635
+ * thing either; whoever else is about ("tourists") is never said beside a
636
+ * family; a parent beside a child is said with them ("a father with a
637
+ * girl"); a tag that is a phrase of its own ("couple on bench") is read as
638
+ * its parts; a time of day comes after where ("in the mountains at
639
+ * sunset"), and a bench out of doors says where it stands ("on a bench by
640
+ * the lake") (AGL-3988).
616
641
  *
617
642
  * `null` where no tag names a thing.
618
643
  */
@@ -178,6 +178,31 @@ import { aiStockBusinessNoun } from "./ai-stock-business-nouns.js";
178
178
  * part of the job already shows outside its pages — a dataset's records
179
179
  * (AGL-3616) — which a page does not place again.
180
180
  */ export const AI_STOCK_PHOTO_AVOID_INPUT = 'stockPhotoAvoid';
181
+ /**
182
+ * The unit job input holding the photos another part of the job placed
183
+ * outside its pages — a dataset's records — with their size and words
184
+ * (AGL-3988): what a picture the library failed takes before any starter.
185
+ */ export const AI_STOCK_PHOTO_JOB_PHOTOS_INPUT = 'stockPhotoJobPhotos';
186
+ /** The job photos a unit input holds ({@link AI_STOCK_PHOTO_JOB_PHOTOS_INPUT}): library photos with a size; anything else is dropped. */ export function aiStockJobPhotosOf(value) {
187
+ if (!Array.isArray(value)) return [];
188
+ return value.flatMap((entry)=>{
189
+ const photo = entry;
190
+ const src = typeof (photo == null ? void 0 : photo.src) === 'string' ? photo.src : '';
191
+ const width = Number(photo == null ? void 0 : photo.width);
192
+ const height = Number(photo == null ? void 0 : photo.height);
193
+ if (!src.startsWith('media:') || !(width > 0) || !(height > 0)) return [];
194
+ const alt = typeof (photo == null ? void 0 : photo.alt) === 'string' ? photo.alt.trim() : '';
195
+ return [
196
+ _extends({
197
+ src,
198
+ width,
199
+ height
200
+ }, alt ? {
201
+ alt
202
+ } : {})
203
+ ];
204
+ });
205
+ }
181
206
  const words = (list)=>new Set(list.trim().split(/\s+/));
182
207
  /** Words that carry no subject. */ const STOPWORDS = words('a an the and or of in on at to for with from by into onto over under near our your their my his her its ' + 'this that these those is are be being was were who whom which where while as about than then very just ' + 'small local best new top quality premium family-owned family owned independent professional ' + 'business company services service page photo picture image showing shows show ' + 'someone people person one two three some many several');
183
208
  /**
@@ -3078,6 +3103,35 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3078
3103
  }
3079
3104
  return out;
3080
3105
  }
3106
+ /**
3107
+ * The library photos a stored page shows, each as a slot takes it (AGL-3988):
3108
+ * its source, its size and what it shows. A photo the job placed on another
3109
+ * page is what a slot the library failed takes before any starter
3110
+ * (`aiLayoutJobPhotoPicks`). Each photo once; one without a size is left out.
3111
+ */ export function aiStockPlacedPhotos(nodes) {
3112
+ const map = nodes != null ? nodes : {};
3113
+ const out = new Map();
3114
+ for (const node of Object.values(map)){
3115
+ var _node_props, _node_props1, _node_props2, _node_props3;
3116
+ if ((node == null ? void 0 : node.componentId) !== 'image') continue;
3117
+ const src = (_node_props = node.props) == null ? void 0 : _node_props['src'];
3118
+ const width = Number((_node_props1 = node.props) == null ? void 0 : _node_props1['intrinsicWidth']);
3119
+ const height = Number((_node_props2 = node.props) == null ? void 0 : _node_props2['intrinsicHeight']);
3120
+ if (typeof src !== 'string' || !src.startsWith('media:') || out.has(src)) continue;
3121
+ if (!(width > 0) || !(height > 0)) continue;
3122
+ const alt = typeof ((_node_props3 = node.props) == null ? void 0 : _node_props3['alt']) === 'string' ? node.props['alt'].trim() : '';
3123
+ out.set(src, _extends({
3124
+ src,
3125
+ width,
3126
+ height
3127
+ }, alt ? {
3128
+ alt
3129
+ } : {}));
3130
+ }
3131
+ return [
3132
+ ...out.values()
3133
+ ];
3134
+ }
3081
3135
  /**
3082
3136
  * What this process has placed for each job, by source key and asset src,
3083
3137
  * with the scope (the page's seed) that placed it (AGL-3660). It spans the
@@ -3114,13 +3168,34 @@ function jobPhotos(key) {
3114
3168
  'together alone apart away abroad ahead nearby outside inside upstairs downstairs ' + // Adjectives a tag gives alone, never a thing: "students, multicultural" (AGL-3856).
3115
3169
  'multicultural multiethnic multiracial diverse international cultural traditional modern classic classical ' + 'domestic wild tropical rural urban ' + // How the photo was made, never what it shows: "cutout, perspective", "men, silhouettes".
3116
3170
  'silhouette silhouettes cutout perspective bokeh closeup ' + // A holiday names when, never a thing: "tet market" is a market.
3117
- 'tet christmas easter halloween diwali ramadan hanukkah thanksgiving ' + 'indian asian african american european caucasian vietnamese japanese chinese korean thai latino latina hispanic ' + 'japan vietnam viet nam thailand india china korea germany france italy spain england britain uk usa america ' + 'canada mexico brazil australia africa europe asia');
3171
+ 'tet christmas easter halloween diwali ramadan hanukkah thanksgiving ' + 'indian asian african american european caucasian vietnamese japanese chinese korean thai latino latina hispanic ' + 'japan vietnam viet nam thailand india china korea germany france italy spain england britain uk usa america ' + 'canada mexico brazil australia africa europe asia ' + // How a scene feels or looks, never a thing in it (AGL-3988): "mountain,
3172
+ // snow, winter, cold, couple, color, landscape" is a couple in the snow.
3173
+ 'cold warm hot chilly freezing frozen icy color colors colour colours landscape landscapes ' + 'panorama panoramic vista scene scenes');
3174
+ /**
3175
+ * The sky and the light over a scene, never a thing in it (AGL-3988): "A
3176
+ * couple with a sky at sunset" and "A child with clouds by a lake" were a
3177
+ * couple at sunset and a child by a lake.
3178
+ */ const SCENERY = words('sky skies cloud clouds sun sunshine sunlight daylight light shadow shadows horizon');
3179
+ /**
3180
+ * The weather a scene is in, said as where (AGL-3988): "A couple with a fog
3181
+ * at a wedding" was a couple in the fog, "A snow in the mountains" a couple
3182
+ * in the snow.
3183
+ */ const WEATHER = {
3184
+ fog: 'in the fog',
3185
+ mist: 'in the mist',
3186
+ haze: 'in the haze',
3187
+ rain: 'in the rain',
3188
+ snow: 'in the snow',
3189
+ snowfall: 'in the snow',
3190
+ snowflakes: 'in the snow',
3191
+ wind: 'in the wind'
3192
+ };
3118
3193
  /**
3119
3194
  * Words a tag uses for what a person or a thing is like, said before it
3120
3195
  * rather than as a thing of its own (AGL-3856): "a confident lawyer", "an
3121
3196
  * elderly couple", "an analog camera", never "a lawyer, law and a confident".
3122
3197
  */ const QUALITIES = words('confident professional successful serious smart busy creative proud skilled experienced determined focused ' + 'senior junior expert friendly cheerful smiling elderly analog analogue digital mirrorless');
3123
- /** What a tag names that takes no article: "chalk" of "board, chalk". */ const MASS_NOUNS = words('food coffee tea bread wood water wax soap clay money fabric yarn wool furniture jewelry jewellery ' + 'pottery glass paper music art hair makeup chalk paint ink dust stone marble concrete steel metal ' + 'skincare equipment decor fashion wildlife weather traffic wine beer chocolate rice sugar flour milk ' + 'cheese honey oil ice fire light sunlight dough sourdough pastry dessert people foliage greenery faculty staff ' + 'clothing bedding frosting icing flooring');
3198
+ /** What a tag names that takes no article: "chalk" of "board, chalk". */ const MASS_NOUNS = words('food coffee tea bread wood water wax soap clay money fabric yarn wool furniture jewelry jewellery ' + 'pottery glass paper music art hair makeup chalk paint ink dust stone marble concrete steel metal ' + 'skincare equipment decor wildlife weather traffic wine beer chocolate rice sugar flour milk ' + 'cheese honey oil ice fire light sunlight dough sourdough pastry dessert people foliage greenery faculty staff ' + 'clothing bedding frosting icing flooring');
3124
3199
  /** Nouns plural without an "-s": said without an article, "women in a classroom". */ const PLURAL_NOUNS = words('men women people children feet teeth mice geese sheep fish');
3125
3200
  /**
3126
3201
  * What a tag names that no photo shows as a thing of its own (AGL-3856): an
@@ -3128,7 +3203,8 @@ function jobPhotos(key) {
3128
3203
  * doing ("read", "lift", "fitness", "a workout"), a word for the photo itself
3129
3204
  * ("photo", "spot"). Left out, never said with an article: "A bookwork and a
3130
3205
  * read", "A kettlebell and fitness", "A board and training" said nothing.
3131
- */ const ABSTRACT_NOUNS = words('law justice advocacy business feedback review news return communication communicate discussion dialog conversation ' + 'success career work job satisfaction quality concept idea strategy marketing education technology health support ' + 'confirmation opinion rating evaluation planning project presentation optimization information internet finance ' + 'management leadership teamwork motivation growth strength service services help advice consulting consultation ' + 'marriage photography architecture travel transport transportation industry company corporate social e-business ' + 'fitness workout workouts exercise exercises crossfit sport sports skill skills knowledge exam exams lecture ' + 'session lesson course hobby leisure power energy wellness talent formation participation ' + 'press show day days night year years time holiday month months week weeks morning evening afternoon season seasons ' + 'kiss hug embrace ' + 'photo photos picture pictures image images shot shots shoot shoots spot spots ' + 'read write learn study lift move remove focus relax think sit smile laugh eat swim teach enjoy celebrate explore ' + 'hike can');
3206
+ */ const ABSTRACT_NOUNS = words('law justice advocacy business feedback review news return communication communicate discussion dialog conversation ' + 'success career work job satisfaction quality concept idea strategy marketing education technology health support ' + 'confirmation opinion rating evaluation planning project presentation optimization information internet finance ' + 'management leadership teamwork motivation growth strength service services help advice consulting consultation ' + 'marriage photography architecture travel transport transportation industry company corporate social e-business ' + 'fitness workout workouts exercise exercises crossfit sport sports skill skills knowledge exam exams lecture ' + 'session lesson course hobby leisure power energy wellness talent formation participation ' + 'press show day days night year years time holiday month months week weeks morning evening afternoon season seasons ' + 'kiss hug embrace ' + // A feeling, never a thing anyone holds (AGL-3988): "A father with a care".
3207
+ 'care worry trust hope joy peace fear faith grief pride comfort safety bliss delight passion desire feeling feelings ' + 'moment moments memory memories fashion style ' + 'photo photos picture pictures image images shot shots shoot shoots spot spots ' + 'read write learn study lift move remove focus relax think sit smile laugh eat swim teach enjoy celebrate explore ' + 'hike can');
3132
3208
  /** Words ending as an idea does that are things: "a station", "a city", "a ship". */ const CONCRETE_ABSTRACT_ENDINGS = words('station mansion lotion potion ornament instrument apartment monument pavement basement garment compartment ' + 'equipment cement city university facility ship warship spaceship airship');
3133
3209
  /** Whether a word is an idea or a doing by how it ends: "participation", "unity", "empowerment". */ const saysAnIdea = (word)=>/(?:tion|sion|ment|ness|ity|ship|ism)s?$/.test(word) && !CONCRETE_ABSTRACT_ENDINGS.has(aiStockStem(word));
3134
3210
  /**
@@ -3138,8 +3214,20 @@ function jobPhotos(key) {
3138
3214
  /** Stopwords a description still says, as the people a photo shows: "a family", "a person". */ const SAID_PEOPLE = words('people person family');
3139
3215
  /** Words ending in "-ing" that are things, and take an article: "a wedding", "a ring". */ const COUNTED_ING = words('wedding ring earring building painting drawing ceiling string swing king sibling wing dumpling pudding sapling');
3140
3216
  /** "-ing" words that are things, and take no article: "clothing". */ const UNCOUNTED_ING = words('clothing bedding frosting icing flooring stuffing seasoning filling');
3141
- /** People a photo shows; the generic ones give way to a role ("man" to "businessman"). */ const PEOPLE_NOUNS = words('man men woman women person people human humans boy boys girl girls guy guys lady ladies child children kid kids ' + 'baby family couple bride groom schoolgirl schoolboy teen teenager student students graduate athlete athletes ' + 'businessman businesswoman businessmen lawyer attorney doctor dentist nurse chef cook baker barista ' + 'trainer coach teacher teachers instructor lecturer professor tutor worker workers entrepreneur model ' + 'photographer potter artist florist mother father parent parents grandmother grandfather grandparents ' + 'faculty staff team participants volunteer volunteers musician dancer leader manager owner founder ' + 'journalist journalists reporter reporters master masters lovers newlywed newlyweds husband wife fiance fiancee spouse');
3142
- /** People said by no role: a model is whoever posed (AGL-3856), "a woman" of "woman, model, show, teacher". */ const GENERIC_PEOPLE = words('man men woman women person people human humans guy guys lady ladies adult boy boys girl girls model models');
3217
+ /** People a photo shows; the generic ones give way to a role ("man" to "businessman"). */ const PEOPLE_NOUNS = words('man men woman women person people human humans boy boys girl girls guy guys lady ladies child children kid kids ' + 'baby family couple bride groom schoolgirl schoolboy teen teenager student students graduate athlete athletes ' + 'businessman businesswoman businessmen lawyer attorney doctor dentist nurse chef cook baker barista ' + 'trainer coach teacher teachers instructor lecturer professor tutor worker workers entrepreneur model ' + 'photographer potter artist florist mother father parent parents grandmother grandfather grandparents ' + 'faculty staff team participants volunteer volunteers musician dancer leader manager owner founder ' + 'journalist journalists reporter reporters master masters lovers newlywed newlyweds husband wife fiance fiancee spouse ' + // Whoever else is about (AGL-3988): "A family with tourists" was a family.
3218
+ 'tourist tourists traveler travelers traveller travellers hiker hikers visitor visitors friend friends crowd ' + // A family's people, said as a parent with a child (AGL-3988).
3219
+ 'dad dads mom moms mum mums son sons daughter daughters toddler toddlers');
3220
+ /** People said by no role: a model is whoever posed (AGL-3856), "a woman" of "woman, model, show, teacher". */ const GENERIC_PEOPLE = words('man men woman women person people human humans guy guys lady ladies adult boy boys girl girls model models ' + 'tourist tourists traveler travelers traveller travellers hiker hikers visitor visitors friend friends crowd');
3221
+ /** A parent a photo shows, said with the child beside them: "A father with a girl" (AGL-3988). */ const PARENTS = words('father mother dad mom mum parent parents grandfather grandmother grandparent grandparents');
3222
+ /** A child a photo shows beside a parent (AGL-3988). */ const CHILDREN = words('child children kid kids baby babies toddler toddlers girl girls boy boys son sons daughter daughters');
3223
+ /** How a family's word is said: "a dad" is a father (AGL-3988). */ const SAID_AS = {
3224
+ dad: 'father',
3225
+ dads: 'fathers',
3226
+ mom: 'mother',
3227
+ moms: 'mothers',
3228
+ mum: 'mother',
3229
+ mums: 'mothers'
3230
+ };
3143
3231
  /**
3144
3232
  * A body's parts, never a thing a photo is described by (AGL-3856): "A girl
3145
3233
  * with a hand" said nothing, and "A body in a gym" is a gym.
@@ -3209,13 +3297,46 @@ function jobPhotos(key) {
3209
3297
  cliff: 'on',
3210
3298
  river: 'by',
3211
3299
  lake: 'by',
3300
+ lakeside: 'by',
3301
+ lakeshore: 'by',
3302
+ sea: 'by',
3303
+ ocean: 'by',
3304
+ seaside: 'by',
3305
+ coast: 'on',
3306
+ shore: 'on',
3307
+ sundown: 'at',
3212
3308
  waterfall: 'at'
3213
3309
  };
3214
3310
  /** Places that are things too, a photo's subject where nothing else is: "a desk in a classroom". */ const OBJECT_PLACES = words('desk table counter bridge bed sofa couch bench');
3215
- /** Places said without an article: "in court", "at sunset". */ const BARE_PLACES = words('court sunset sunrise dusk dawn');
3311
+ /** Places said without an article: "in court", "at sunset". */ const BARE_PLACES = words('court sunset sunrise sundown dusk dawn');
3216
3312
  /** Places said in words of their own: a mountain tag is "in the mountains". */ const PLACES_SAID = {
3217
- mountain: 'the mountains'
3313
+ mountain: 'the mountains',
3314
+ lakeside: 'the lake',
3315
+ lakeshore: 'the lake',
3316
+ sea: 'the sea',
3317
+ ocean: 'the ocean',
3318
+ seaside: 'the sea',
3319
+ coast: 'the coast',
3320
+ shore: 'the shore'
3218
3321
  };
3322
+ /** Places that say when, said after where: "a couple in the mountains at sunset" (AGL-3988). */ const TIME_PLACES = words('sunset sunrise sundown dusk dawn');
3323
+ /** Places out of doors, said after a thing to sit on: "a couple on a bench by the lake" (AGL-3988). */ const OUTDOORS = words('forest field meadow valley mountain hill cliff river lake lakeside lakeshore waterfall beach park garden trail sea ocean seaside coast shore');
3324
+ /** Words inside a tag that say one thing is where another is: "couple on bench" (AGL-3988). */ const TAG_PREPOSITIONS = words('on in at by with under near beside');
3325
+ /**
3326
+ * A tag that is a phrase of its own, read as its parts (AGL-3988): "couple
3327
+ * on bench" is a couple, and a bench they are on. The beta.270 Sugar Hollow
3328
+ * About page read it whole, as a place, and said "A lakeside on a couple on
3329
+ * bench".
3330
+ */ function tagParts(tag) {
3331
+ const list = tag.toLowerCase().trim().split(/\s+/);
3332
+ const at = list.findIndex((word, index)=>index > 0 && index < list.length - 1 && TAG_PREPOSITIONS.has(word));
3333
+ return at > 0 ? [
3334
+ list.slice(0, at).join(' '),
3335
+ list.slice(at + 1).join(' ')
3336
+ ] : [
3337
+ tag
3338
+ ];
3339
+ }
3219
3340
  /** How many of a photo's first tags its description reads: the things and people it is OF. */ const ALT_TAGS_READ = 8;
3220
3341
  /** How far a photo's tags are read for where it is set, said last: "in a gym" of a kettlebell's seventh tag. */ const ALT_PLACE_TAGS_READ = 10;
3221
3342
  /** A tag's words, a word for the photo itself taken off a compound: "photo-camera" is a camera. */ function tagWords(tag) {
@@ -3290,15 +3411,31 @@ function jobPhotos(key) {
3290
3411
  * - a second thing comes only from the first two tags and where no place is
3291
3412
  * said, what a person holds only from the first three, where it is set
3292
3413
  * from the first ten;
3293
- * - no word is said twice: never "an elderly couple and an elderly".
3414
+ * - no word is said twice: never "an elderly couple and an elderly";
3415
+ * - the sky, the clouds and the light are no thing anyone holds, and the
3416
+ * weather is said as where it is: "a couple in the fog at a wedding",
3417
+ * "a couple in the snow in the mountains", never "a couple with a sky at
3418
+ * sunset" or "a snow in the mountains"; a feeling ("care", "worry") is no
3419
+ * thing either; whoever else is about ("tourists") is never said beside a
3420
+ * family; a parent beside a child is said with them ("a father with a
3421
+ * girl"); a tag that is a phrase of its own ("couple on bench") is read as
3422
+ * its parts; a time of day comes after where ("in the mountains at
3423
+ * sunset"), and a bench out of doors says where it stands ("on a bench by
3424
+ * the lake") (AGL-3988).
3294
3425
  *
3295
3426
  * `null` where no tag names a thing.
3296
3427
  */ export function aiStockTagsAlt(tags, proper = new Set()) {
3428
+ var _ref;
3297
3429
  const parts = [];
3298
3430
  let quality = '';
3299
3431
  let portrait = false;
3300
- for (const [index, tag] of tags.slice(0, ALT_PLACE_TAGS_READ).entries()){
3301
- var _ref;
3432
+ /** The weather it is in, said as where ({@link WEATHER}). */ let weather = '';
3433
+ const read = tags.slice(0, ALT_PLACE_TAGS_READ).flatMap((tag, index)=>tagParts(tag).map((part)=>({
3434
+ tag: part,
3435
+ index
3436
+ })));
3437
+ for (const { tag, index } of read){
3438
+ var _ref1;
3302
3439
  var _bare_split_pop;
3303
3440
  const lower = tag.toLowerCase().trim();
3304
3441
  const reading = index < ALT_TAGS_READ;
@@ -3310,11 +3447,18 @@ function jobPhotos(key) {
3310
3447
  if (reading) quality || (quality = lower);
3311
3448
  continue;
3312
3449
  }
3313
- const said = describedTag(tag, proper);
3450
+ // The sky is never a thing a photo is described by; the weather is where it is.
3451
+ if (SCENERY.has(lower)) continue;
3452
+ if (WEATHER[lower]) {
3453
+ if (reading) weather || (weather = WEATHER[lower]);
3454
+ continue;
3455
+ }
3456
+ const family = SAID_AS[lower];
3457
+ const said = family ? `${articleFor(family)} ${family}`.replace(/^an? (?=\S+s$)/, '') : describedTag(tag, proper);
3314
3458
  if (!said) continue;
3315
3459
  const bare = said.replace(/^an?\s+/, '');
3316
3460
  const stems = stemList(bare);
3317
- const head = (_ref = (_bare_split_pop = bare.split(/\s+/).pop()) == null ? void 0 : _bare_split_pop.toLowerCase()) != null ? _ref : '';
3461
+ const head = (_ref1 = (_bare_split_pop = bare.split(/\s+/).pop()) == null ? void 0 : _bare_split_pop.toLowerCase()) != null ? _ref1 : '';
3318
3462
  if (!head) continue;
3319
3463
  const headStem = aiStockStem(head);
3320
3464
  const kind = PEOPLE_NOUNS.has(head) || PEOPLE_NOUNS.has(headStem) ? 'person' : PLACES[headStem] ? 'place' : 'thing';
@@ -3358,7 +3502,12 @@ function jobPhotos(key) {
3358
3502
  // silhouettes, camera, nature, photographer" men with a camera.
3359
3503
  const leads = (part)=>portrait || part.index < ALT_PERSON_TAGS_READ || !things.length;
3360
3504
  const role = posed ? undefined : people.find((part)=>!GENERIC_PEOPLE.has(part.head) && part.index < ALT_ROLE_TAGS_READ && leads(part));
3361
- const person = role != null ? role : people.find(leads);
3505
+ // A parent beside a child is said with them (AGL-3988): "A father with a
3506
+ // girl", never "a child with clouds" of "child, clouds, dad, dock, father".
3507
+ const parent = people.find((part)=>PARENTS.has(part.head) && part.index < ALT_ROLE_TAGS_READ);
3508
+ const child = people.find((part)=>CHILDREN.has(part.head) && part.index < ALT_ROLE_TAGS_READ);
3509
+ const family = parent && child && (!role || role === parent || role === child) ? parent : undefined;
3510
+ const person = (_ref = family != null ? family : role) != null ? _ref : people.find(leads);
3362
3511
  // Nothing but places: a desk is the thing in a classroom; "a school in a classroom" says one place.
3363
3512
  if (!person && !things.length && places.length > 1) {
3364
3513
  const object = places.find((part)=>OBJECT_PLACES.has(part.head));
@@ -3370,7 +3519,7 @@ function jobPhotos(key) {
3370
3519
  } else places = places.slice(0, 1);
3371
3520
  }
3372
3521
  const place = places[0];
3373
- if (!person && !things.length && !place) return null;
3522
+ if (!person && !things.length && !place && !weather) return null;
3374
3523
  /** Every word said so far, so none is said twice. */ const said = new Set();
3375
3524
  const fresh = (part)=>{
3376
3525
  const list = stemList(part.said.replace(/^an?\s+/, ''));
@@ -3382,18 +3531,40 @@ function jobPhotos(key) {
3382
3531
  let phrase = '';
3383
3532
  if (person && fresh(person)) {
3384
3533
  phrase = portrait ? `a portrait of ${person.said}` : qualified(person);
3385
- const held = things.find((part)=>part.index <= 2);
3534
+ const held = family && child && child !== person ? child : things.find((part)=>part.index <= 2);
3386
3535
  if (!portrait && held && fresh(held)) phrase += ` ${CLOTHING.has(held.head) ? 'in' : 'with'} ${held.said}`;
3387
3536
  } else if (things[0] && fresh(things[0])) {
3388
3537
  phrase = qualified(things[0]);
3389
3538
  const second = things[1];
3390
3539
  if (second && second.index <= 1 && !place && fresh(second)) phrase += ` and ${second.said}`;
3391
3540
  }
3541
+ /** A place as it is said alone: "a classroom", "the mountains", "sunset". */ const placeOf = (part)=>{
3542
+ var _PLACES_SAID_part_head;
3543
+ return (_PLACES_SAID_part_head = PLACES_SAID[part.head]) != null ? _PLACES_SAID_part_head : BARE_PLACES.has(part.head) && /^an? \S+$/.test(part.said) ? part.head : part.said;
3544
+ };
3545
+ /** A place as it is said after what is there: "in the mountains", "at sunset", "on a bench". */ const whereOf = (part)=>{
3546
+ var _PLACES_part_head;
3547
+ return `${(_PLACES_part_head = PLACES[part.head]) != null ? _PLACES_part_head : 'in'} ${placeOf(part)}`;
3548
+ };
3549
+ // The weather is said before where (AGL-3988): "a couple in the fog at a
3550
+ // wedding"; alone, it is what the photo shows: "snow in the mountains".
3551
+ if (weather && phrase) phrase += ` ${weather}`;
3552
+ else if (weather) phrase = weather.replace(/^in the /, '');
3392
3553
  if (place && fresh(place)) {
3393
- var _PLACES_place_head, _PLACES_SAID_place_head;
3394
- const preposition = (_PLACES_place_head = PLACES[place.head]) != null ? _PLACES_place_head : 'in';
3395
- const where = (_PLACES_SAID_place_head = PLACES_SAID[place.head]) != null ? _PLACES_SAID_place_head : BARE_PLACES.has(place.head) && /^an? \S+$/.test(place.said) ? place.head : place.said;
3396
- phrase = phrase ? `${phrase} ${preposition} ${where}` : where;
3554
+ // A second place where the first is a time of day or a bench out of
3555
+ // doors: "a couple in the mountains at sunset", "a couple on a bench by
3556
+ // the lake"; never a bed in a room.
3557
+ const second = places.find((part)=>part !== place && !OBJECT_PLACES.has(part.head) && !TIME_PLACES.has(part.head) && (TIME_PLACES.has(place.head) || place.head === 'bench' && OUTDOORS.has(part.head)));
3558
+ const after = second && fresh(second) ? second : null;
3559
+ const [first, last] = after && TIME_PLACES.has(place.head) ? [
3560
+ after,
3561
+ place
3562
+ ] : [
3563
+ place,
3564
+ after
3565
+ ];
3566
+ const tail = last ? ` ${whereOf(last)}` : '';
3567
+ phrase = phrase ? `${phrase} ${whereOf(first)}${tail}` : `${placeOf(first)}${tail}`;
3397
3568
  }
3398
3569
  // Never a dangling article or preposition, whatever a tag left behind.
3399
3570
  phrase = phrase.replace(DANGLING_END, '').trim();