@aglyn/plugins-ai 1.0.0-beta.269 → 1.0.0-beta.271

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 (237) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +1 -1
  3. package/src/lib/activity/ai-activity-actions.js +1 -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 +8 -6
  27. package/src/lib/components/ai-credits-card.component.js +39 -22
  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-links.d.ts +6 -0
  33. package/src/lib/components/ai-job-links.js +9 -0
  34. package/src/lib/components/ai-job-links.js.map +1 -1
  35. package/src/lib/components/ai-job-requests.d.ts +4 -0
  36. package/src/lib/components/ai-job-requests.js +4 -0
  37. package/src/lib/components/ai-job-requests.js.map +1 -1
  38. package/src/lib/components/ai-products-hub-card.component.d.ts +6 -0
  39. package/src/lib/components/ai-products-hub-card.component.js +21 -6
  40. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  41. package/src/lib/components/ai-site-build-page.component.js +3 -2
  42. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  43. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  44. package/src/lib/components/ai-site-start-card.component.js +99 -31
  45. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  46. package/src/lib/components/ai-site-start-steps.component.d.ts +43 -2
  47. package/src/lib/components/ai-site-start-steps.component.js +651 -15
  48. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  49. package/src/lib/components/ai-site-start-tracking.d.ts +10 -0
  50. package/src/lib/components/ai-site-start-tracking.js +14 -0
  51. package/src/lib/components/ai-site-start-tracking.js.map +1 -1
  52. package/src/lib/components/ai-site-style-previews.js +26 -22
  53. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  54. package/src/lib/components/ai-store-finish-card.component.d.ts +14 -3
  55. package/src/lib/components/ai-store-finish-card.component.js +67 -9
  56. package/src/lib/components/ai-store-finish-card.component.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 +14 -3
  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.d.ts +3 -0
  77. package/src/lib/jobs/ai-job-build-step.js +13 -5
  78. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  79. package/src/lib/jobs/ai-job-form-step.d.ts +24 -1
  80. package/src/lib/jobs/ai-job-form-step.js +93 -7
  81. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  82. package/src/lib/jobs/ai-job-page-language.d.ts +35 -11
  83. package/src/lib/jobs/ai-job-page-language.js +68 -2
  84. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  85. package/src/lib/jobs/ai-job-page-step.d.ts +17 -0
  86. package/src/lib/jobs/ai-job-page-step.js +40 -17
  87. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  88. package/src/lib/jobs/ai-job-plan-step.d.ts +25 -2
  89. package/src/lib/jobs/ai-job-plan-step.js +103 -29
  90. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  91. package/src/lib/jobs/ai-job-settings-step.js +4 -3
  92. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-site-datasets.d.ts +27 -0
  94. package/src/lib/jobs/ai-job-site-datasets.js +140 -14
  95. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  96. package/src/lib/jobs/ai-job-site-look.js +18 -5
  97. package/src/lib/jobs/ai-job-site-look.js.map +1 -1
  98. package/src/lib/jobs/ai-job-site-step.d.ts +16 -1
  99. package/src/lib/jobs/ai-job-site-step.js +95 -22
  100. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  101. package/src/lib/jobs/ai-job-text-step.d.ts +8 -1
  102. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-tracking-step.d.ts +14 -3
  104. package/src/lib/jobs/ai-job-tracking-step.js +28 -5
  105. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  106. package/src/lib/jobs/ai-jobs.d.ts +8 -1
  107. package/src/lib/jobs/ai-jobs.js +63 -8
  108. package/src/lib/jobs/ai-jobs.js.map +1 -1
  109. package/src/lib/jobs/ai-layout-owner-photos.d.ts +57 -0
  110. package/src/lib/jobs/ai-layout-owner-photos.js +110 -0
  111. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -0
  112. package/src/lib/jobs/ai-layout-stock-photos.d.ts +62 -4
  113. package/src/lib/jobs/ai-layout-stock-photos.js +523 -72
  114. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  115. package/src/lib/jobs/ai-site-inspiration-signals.d.ts +98 -0
  116. package/src/lib/jobs/ai-site-inspiration-signals.js +585 -0
  117. package/src/lib/jobs/ai-site-inspiration-signals.js.map +1 -0
  118. package/src/lib/jobs/ai-site-inspiration.d.ts +157 -0
  119. package/src/lib/jobs/ai-site-inspiration.js +346 -0
  120. package/src/lib/jobs/ai-site-inspiration.js.map +1 -0
  121. package/src/lib/jobs/ai-site-publish.d.ts +26 -1
  122. package/src/lib/jobs/ai-site-publish.js +162 -13
  123. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  124. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +16 -0
  125. package/src/lib/jobs/ai-theme-brand-inputs.js +18 -4
  126. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  127. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.d.ts +97 -0
  128. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js +137 -0
  129. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js.map +1 -0
  130. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.d.ts +38 -0
  131. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js +103 -0
  132. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js.map +1 -0
  133. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.d.ts +29 -0
  134. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.js +291 -0
  135. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.js.map +1 -0
  136. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.d.ts +25 -0
  137. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js +174 -0
  138. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js.map +1 -0
  139. package/src/lib/layout-language/ai-layout-compiler.d.ts +1 -1
  140. package/src/lib/layout-language/ai-layout-compiler.js +438 -25
  141. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  142. package/src/lib/layout-language/ai-layout-contact.d.ts +57 -0
  143. package/src/lib/layout-language/ai-layout-contact.js +137 -0
  144. package/src/lib/layout-language/ai-layout-contact.js.map +1 -0
  145. package/src/lib/layout-language/ai-layout-copy.d.ts +12 -0
  146. package/src/lib/layout-language/ai-layout-copy.js +34 -2
  147. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  148. package/src/lib/layout-language/ai-layout-design.d.ts +65 -8
  149. package/src/lib/layout-language/ai-layout-design.js +411 -97
  150. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  151. package/src/lib/layout-language/ai-layout-frame.js +23 -0
  152. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  153. package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
  154. package/src/lib/layout-language/ai-layout-labels.js +12 -6
  155. package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
  156. package/src/lib/layout-language/ai-layout-language.d.ts +3 -2
  157. package/src/lib/layout-language/ai-layout-language.js +4 -3
  158. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  159. package/src/lib/layout-language/ai-layout-links.d.ts +13 -0
  160. package/src/lib/layout-language/ai-layout-links.js +30 -0
  161. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  162. package/src/lib/layout-language/ai-layout-pictures.d.ts +49 -6
  163. package/src/lib/layout-language/ai-layout-pictures.js +145 -22
  164. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  165. package/src/lib/model/ai-build-job.d.ts +16 -1
  166. package/src/lib/model/ai-build-job.js +40 -0
  167. package/src/lib/model/ai-build-job.js.map +1 -1
  168. package/src/lib/model/ai-job-input.d.ts +33 -0
  169. package/src/lib/model/ai-job-input.js +59 -0
  170. package/src/lib/model/ai-job-input.js.map +1 -0
  171. package/src/lib/model/ai-job-notice.d.ts +4 -0
  172. package/src/lib/model/ai-job-notice.js +11 -1
  173. package/src/lib/model/ai-job-notice.js.map +1 -1
  174. package/src/lib/model/ai-jobs.types.d.ts +40 -1
  175. package/src/lib/model/ai-jobs.types.js.map +1 -1
  176. package/src/lib/model/ai-site-details.d.ts +122 -0
  177. package/src/lib/model/ai-site-details.js +300 -0
  178. package/src/lib/model/ai-site-details.js.map +1 -0
  179. package/src/lib/model/ai-site-entity.d.ts +181 -0
  180. package/src/lib/model/ai-site-entity.js +390 -0
  181. package/src/lib/model/ai-site-entity.js.map +1 -0
  182. package/src/lib/model/ai-site-inspiration.d.ts +72 -0
  183. package/src/lib/model/ai-site-inspiration.js +109 -0
  184. package/src/lib/model/ai-site-inspiration.js.map +1 -0
  185. package/src/lib/model/ai-site-job.d.ts +13 -1
  186. package/src/lib/model/ai-site-job.js +15 -2
  187. package/src/lib/model/ai-site-job.js.map +1 -1
  188. package/src/lib/model/ai-site-kinds.js +109 -21
  189. package/src/lib/model/ai-site-kinds.js.map +1 -1
  190. package/src/lib/model/ai-site-look.d.ts +10 -0
  191. package/src/lib/model/ai-site-look.js +24 -0
  192. package/src/lib/model/ai-site-look.js.map +1 -1
  193. package/src/lib/model/ai-site-start-seo.d.ts +19 -5
  194. package/src/lib/model/ai-site-start-seo.js +25 -7
  195. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  196. package/src/lib/model/ai-site-start-steps.d.ts +9 -3
  197. package/src/lib/model/ai-site-start-steps.js +67 -5
  198. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  199. package/src/lib/model/ai-site-start.d.ts +68 -4
  200. package/src/lib/model/ai-site-start.js +166 -13
  201. package/src/lib/model/ai-site-start.js.map +1 -1
  202. package/src/lib/model/ai-upsell-copy.js +4 -1
  203. package/src/lib/model/ai-upsell-copy.js.map +1 -1
  204. package/src/lib/native-nav.d.ts +26 -0
  205. package/src/lib/native-nav.js +58 -0
  206. package/src/lib/native-nav.js.map +1 -0
  207. package/src/lib/plan-entitlements.d.ts +4 -3
  208. package/src/lib/plan-entitlements.js +3 -8
  209. package/src/lib/plan-entitlements.js.map +1 -1
  210. package/src/lib/runtime/ai-dataset-generation.d.ts +8 -1
  211. package/src/lib/runtime/ai-dataset-generation.js +12 -2
  212. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  213. package/src/lib/runtime/ai-doctrine-validators.d.ts +26 -33
  214. package/src/lib/runtime/ai-doctrine-validators.js +54 -44
  215. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  216. package/src/lib/runtime/ai-doctrine.js +1 -1
  217. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  218. package/src/lib/runtime/ai-node-tree.d.ts +7 -0
  219. package/src/lib/runtime/ai-node-tree.js +9 -1
  220. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  221. package/src/lib/server/ai-create-route.d.ts +1 -1
  222. package/src/lib/server/ai-create-route.js +3 -4
  223. package/src/lib/server/ai-create-route.js.map +1 -1
  224. package/src/lib/server/ai-jobs-resume.js +27 -3
  225. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  226. package/src/lib/server/ai-jobs-route.js +7 -0
  227. package/src/lib/server/ai-jobs-route.js.map +1 -1
  228. package/src/lib/usage/assist-credits.d.ts +4 -4
  229. package/src/lib/usage/assist-credits.js +4 -4
  230. package/src/lib/usage/assist-credits.js.map +1 -1
  231. package/src/lib/usage/staff-org-ai-view.js +6 -3
  232. package/src/lib/usage/staff-org-ai-view.js.map +1 -1
  233. package/src/lib/usage/staff-org-ai.d.ts +4 -2
  234. package/src/lib/usage/staff-org-ai.js +7 -7
  235. package/src/lib/usage/staff-org-ai.js.map +1 -1
  236. package/src/lib/usage/staff-org-usage-ai.js +2 -2
  237. package/src/lib/usage/staff-org-usage-ai.js.map +1 -1
@@ -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
  /**
@@ -1985,7 +2010,56 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1985
2010
  const said = texts.filter(Boolean).join(' . ');
1986
2011
  return ADULT_AUDIENCE.test(said) && !YOUNG_AUDIENCE.test(said);
1987
2012
  }
1988
- /** Where a picture of a moment is set, by the words that name it, and the word a search and a photo say it with. */ const SCENE_SETTINGS = [
2013
+ /**
2014
+ * Words a photo by the sea is tagged with: never one set in the mountains, a
2015
+ * forest or a city (AGL-3942). Never "palm" or "wave", a hand's as often.
2016
+ */ const SEASIDE = [
2017
+ 'beach',
2018
+ 'beaches',
2019
+ 'sea',
2020
+ 'seaside',
2021
+ 'seashore',
2022
+ 'ocean',
2023
+ 'coast',
2024
+ 'coastal',
2025
+ 'shore',
2026
+ 'shoreline',
2027
+ 'surf',
2028
+ 'waves',
2029
+ 'caribbean',
2030
+ 'tropical',
2031
+ 'island',
2032
+ 'lagoon',
2033
+ 'bay',
2034
+ 'pier'
2035
+ ];
2036
+ /** Words a photo of a desert is tagged with. */ const DESERT = [
2037
+ 'desert',
2038
+ 'dune',
2039
+ 'dunes',
2040
+ 'cactus',
2041
+ 'sahara'
2042
+ ];
2043
+ /** Words a photo of a city is tagged with. */ const CITYSCAPE = [
2044
+ 'city',
2045
+ 'urban',
2046
+ 'downtown',
2047
+ 'street',
2048
+ 'skyline',
2049
+ 'skyscraper',
2050
+ 'skyscrapers',
2051
+ 'subway',
2052
+ 'metro'
2053
+ ];
2054
+ /**
2055
+ * Where a picture of a moment is set, by the words that name it, the word a
2056
+ * search and a photo say it with, whether it is a place a photo can show on
2057
+ * its own (a landscape, not a season), and the words of the places it is
2058
+ * not, which none of a photo's first tags may name (AGL-3942): the beta.267
2059
+ * Copper Hollow Film & Portrait galleries, a Blue Ridge photographer's,
2060
+ * showed two couples on a beach, tagged "beach, couple, nature, mountain"
2061
+ * and "romantic wedding couple, the beach".
2062
+ */ const SCENE_SETTINGS = [
1989
2063
  {
1990
2064
  words: [
1991
2065
  'mountain',
@@ -1998,7 +2072,13 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1998
2072
  'overlook',
1999
2073
  'parkway'
2000
2074
  ],
2001
- said: 'mountains'
2075
+ said: 'mountains',
2076
+ landscape: true,
2077
+ away: [
2078
+ ...SEASIDE,
2079
+ ...DESERT,
2080
+ ...CITYSCAPE
2081
+ ]
2002
2082
  },
2003
2083
  {
2004
2084
  words: [
@@ -2006,7 +2086,14 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2006
2086
  'autumn',
2007
2087
  'foliage'
2008
2088
  ],
2009
- said: 'autumn'
2089
+ said: 'autumn',
2090
+ away: [
2091
+ 'winter',
2092
+ 'snow',
2093
+ 'snowy',
2094
+ 'spring',
2095
+ 'blossom'
2096
+ ]
2010
2097
  },
2011
2098
  {
2012
2099
  words: [
@@ -2014,7 +2101,12 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2014
2101
  'snow',
2015
2102
  'snowy'
2016
2103
  ],
2017
- said: 'winter'
2104
+ said: 'winter',
2105
+ away: [
2106
+ ...SEASIDE,
2107
+ 'summer',
2108
+ 'tropical'
2109
+ ]
2018
2110
  },
2019
2111
  {
2020
2112
  words: [
@@ -2024,7 +2116,15 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2024
2116
  'shore',
2025
2117
  'ocean'
2026
2118
  ],
2027
- said: 'beach'
2119
+ said: 'beach',
2120
+ landscape: true,
2121
+ away: [
2122
+ 'snow',
2123
+ 'snowy',
2124
+ 'ski',
2125
+ 'skiing',
2126
+ ...CITYSCAPE
2127
+ ]
2028
2128
  },
2029
2129
  {
2030
2130
  words: [
@@ -2032,7 +2132,13 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2032
2132
  'woods',
2033
2133
  'woodland'
2034
2134
  ],
2035
- said: 'forest'
2135
+ said: 'forest',
2136
+ landscape: true,
2137
+ away: [
2138
+ ...SEASIDE,
2139
+ ...DESERT,
2140
+ ...CITYSCAPE
2141
+ ]
2036
2142
  },
2037
2143
  {
2038
2144
  words: [
@@ -2040,7 +2146,14 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2040
2146
  'blossom',
2041
2147
  'blossoms'
2042
2148
  ],
2043
- said: 'spring'
2149
+ said: 'spring',
2150
+ away: [
2151
+ 'winter',
2152
+ 'snow',
2153
+ 'snowy',
2154
+ 'autumn',
2155
+ 'fall'
2156
+ ]
2044
2157
  },
2045
2158
  {
2046
2159
  words: [
@@ -2048,16 +2161,27 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2048
2161
  'urban',
2049
2162
  'downtown'
2050
2163
  ],
2051
- said: 'city'
2164
+ said: 'city',
2165
+ landscape: true,
2166
+ away: [
2167
+ ...SEASIDE,
2168
+ ...DESERT
2169
+ ]
2052
2170
  }
2053
2171
  ];
2172
+ /** The stems of the places a setting is not ({@link SCENE_SETTINGS}); none for no setting. */ export function aiStockSettingAway(setting) {
2173
+ const entry = SCENE_SETTINGS.find((candidate)=>candidate.said === setting);
2174
+ return (entry == null ? void 0 : entry.away) ? [
2175
+ ...new Set(entry.away.map(aiStockStem))
2176
+ ] : [];
2177
+ }
2054
2178
  /**
2055
2179
  * The scene a picture's words name (AGL-3856): the people at a moment, and
2056
2180
  * where — "Ridgetop Elopement" a couple in the mountains, "Mountain Wedding
2057
2181
  * Day" a wedding couple in the mountains, "Family Session in the Fall" a
2058
2182
  * family in autumn — read from every word, never only the first clause.
2059
2183
  * `null` where it names no people.
2060
- */ export function aiStockScene(text) {
2184
+ */ export function aiStockScene(text, siteSetting = '') {
2061
2185
  const named = sceneWords(text);
2062
2186
  const scene = PEOPLE_SCENES.find((entry)=>entry.words.some((word)=>named.has(word)));
2063
2187
  if (!scene) return null;
@@ -2080,7 +2204,9 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2080
2204
  shun: scene.shun.filter((word)=>!named.has(word)).map(aiStockStem),
2081
2205
  named: [
2082
2206
  ...new Set(stemList(text))
2083
- ]
2207
+ ],
2208
+ // Searched where its own words set it; never shown anywhere the site is not.
2209
+ away: aiStockSettingAway(setting || siteSetting)
2084
2210
  };
2085
2211
  }
2086
2212
  /** A text's lowercase words, letters only. */ const sceneWords = (text)=>new Set(text.toLowerCase().split(/[^a-zÀ-ɏ]+/).filter(Boolean));
@@ -2131,9 +2257,53 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2131
2257
  shun: scene.shun.filter((word)=>!all.has(word)).map(aiStockStem),
2132
2258
  named: [
2133
2259
  ...new Set(stemList(texts.join(' ')))
2134
- ]
2260
+ ],
2261
+ away: aiStockSettingAway(setting)
2135
2262
  };
2136
2263
  }
2264
+ /**
2265
+ * The work a craft whose pictures are people at a moment shows (AGL-3942):
2266
+ * the scenes its site's own words name, in the order they name them, each
2267
+ * set where the site is — a wedding photographer "for couples planning
2268
+ * mountain weddings, and families" shows a wedding couple in the mountains
2269
+ * and a family in the mountains. A family session still never shows a
2270
+ * bride, whatever else the site names. Empty where its words name no people.
2271
+ */ export function aiStockWorkScenes(text, setting = '') {
2272
+ const list = text.toLowerCase().split(/[^a-zÀ-ɏ]+/).filter(Boolean);
2273
+ const found = PEOPLE_SCENES.map((scene)=>({
2274
+ scene,
2275
+ at: list.findIndex((word)=>scene.words.includes(word))
2276
+ })).filter((entry)=>entry.at >= 0).sort((a, b)=>a.at - b.at);
2277
+ return found.map(({ scene })=>{
2278
+ const named = new Set(list.filter((word)=>scene.words.includes(word)));
2279
+ const query = scene.query(named);
2280
+ return {
2281
+ queries: [
2282
+ ...new Set([
2283
+ setting ? `${query} ${setting}` : '',
2284
+ query
2285
+ ].filter(Boolean))
2286
+ ],
2287
+ subject: [
2288
+ scene.subject,
2289
+ setting
2290
+ ].filter(Boolean).join(' '),
2291
+ object: scene.subject,
2292
+ lead: scene.lead.filter((phrase)=>!RECORD_SCENE_UNPEOPLED.has(phrase)).map((phrase)=>stemList(phrase).join(' ')),
2293
+ people: scene.people.map(aiStockStem),
2294
+ shun: scene.shun.map(aiStockStem),
2295
+ named: [
2296
+ ...new Set([
2297
+ ...named
2298
+ ].map(aiStockStem))
2299
+ ],
2300
+ away: aiStockSettingAway(setting)
2301
+ };
2302
+ });
2303
+ }
2304
+ /** Whether a setting is a place a photo shows on its own, a landscape searched by its name: "mountains", never "autumn". */ export function aiStockSettingIsLandscape(setting) {
2305
+ return SCENE_SETTINGS.some((entry)=>entry.said === setting && entry.landscape === true);
2306
+ }
2137
2307
  /**
2138
2308
  * A scene's lead words a record's cover never leads with alone
2139
2309
  * ({@link aiStockRecordScene}): the moment with no one in it (a wedding's table
@@ -2373,7 +2543,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2373
2543
  * asks first for the role its words or its list's name (`sectionName`) say,
2374
2544
  * in the site's world: "teacher classroom" for a "Math instructor" (AGL-3856).
2375
2545
  */ export function aiStockSearchesFor(slot, terms, subject, part, sectionName = '') {
2376
- var _terms_core, _terms_making, _ref, _ref1, _terms_setting, _ref2, _terms_maker, _ref3;
2546
+ var _terms_core, _terms_making, _ref, _ref1, _terms_setting, _terms_setting1, _ref2, _terms_maker, _terms_maker1, _ref3;
2377
2547
  var _aiStockPeopleRole, _slot_record, _slot_record1;
2378
2548
  const orientation = aiStockOrientation(slot.aspect);
2379
2549
  const size = slot.role === 'hero' ? {
@@ -2389,6 +2559,21 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2389
2559
  // searched "photographer mountain elopements" and placed a press
2390
2560
  // photographer, a toy frog with a camera and a cat (AGL-3856).
2391
2561
  const work = aiStockShowsTheWork(slot, terms);
2562
+ // Every other picture of such a craft's site shows its work where a page
2563
+ // would show the maker (AGL-3942): the beta.267 Copper Hollow Film &
2564
+ // Portrait about page searched "photographer at work" and "photographer
2565
+ // camera", and every picture on it was a stock photographer.
2566
+ const itsWork = !work && aiStockShowsItsWork(slot, terms);
2567
+ /** Whether a query names the craft by its own name, the profession's noun: "wedding photographer". */ const namesCraft = (query)=>stemList(query).some((stem)=>{
2568
+ var _terms_core;
2569
+ return ((_terms_core = terms.core) != null ? _terms_core : []).includes(stem);
2570
+ });
2571
+ /** Whether a query names the maker, their tools or the craft: "photographer at work", "photographer camera". */ const namesMaker = (query)=>namesCraft(query) || stemList(query).some((stem)=>{
2572
+ var _terms_maker;
2573
+ return ((_terms_maker = terms.maker) != null ? _terms_maker : []).includes(stem);
2574
+ });
2575
+ /** A rung of the maker's own photos, left out on a site that shows its work. */ const shown = (query)=>!itsWork || !namesMaker(query);
2576
+ /** A search of the slot's own words, left out on such a site only where they name the profession. */ const said = (query)=>!itsWork || !namesCraft(query);
2392
2577
  const object = aiStockObjectWord(subject);
2393
2578
  const subjectStems = stemList(subject);
2394
2579
  const kin = object ? objectKin(object, [
@@ -2409,13 +2594,13 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2409
2594
  // own object among the first tags, and searched by its object's kin, never
2410
2595
  // the category's world alone (AGL-3660).
2411
2596
  const collection = part === 'collection' && Boolean(terms.craft) && !work;
2412
- const broad = kin && collection || work ? [] : terms.broad.map((query)=>({
2597
+ const broad = kin && collection || work ? [] : terms.broad.filter(shown).map((query)=>({
2413
2598
  query,
2414
2599
  broad: true,
2415
2600
  category: collection
2416
2601
  }));
2417
2602
  // The maker at work, held to the craft: never "hands" (AGL-3660).
2418
- const making = (work ? [] : (_terms_making = terms.making) != null ? _terms_making : []).map((query)=>({
2603
+ const makers = (work ? [] : (_terms_making = terms.making) != null ? _terms_making : []).filter(shown).map((query)=>({
2419
2604
  query,
2420
2605
  broad: true,
2421
2606
  category: true
@@ -2424,10 +2609,18 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2424
2609
  const roleWord = slot.people ? (_ref = (_aiStockPeopleRole = aiStockPeopleRole(slot.alt, sectionName)) == null ? void 0 : _aiStockPeopleRole.query) != null ? _ref : '' : '';
2425
2610
  const role = roleWord ? stemList(terms.business).includes(roleWord) ? terms.business : `${roleWord} ${terms.business}`.trim() : '';
2426
2611
  // A scene's people are read from every word of the picture's, never its first clause alone.
2427
- const scene = work ? aiStockRecordScene(slot.alt, (_ref1 = (_slot_record = slot.record) == null ? void 0 : _slot_record.category) != null ? _ref1 : '', (_terms_setting = terms.setting) != null ? _terms_setting : '') : terms.scenes && slot.role !== 'hero' && part !== 'collection' ? aiStockScene(slot.alt) : null;
2612
+ const scene = work ? aiStockRecordScene(slot.alt, (_ref1 = (_slot_record = slot.record) == null ? void 0 : _slot_record.category) != null ? _ref1 : '', (_terms_setting = terms.setting) != null ? _terms_setting : '') : terms.scenes && slot.role !== 'hero' && part !== 'collection' ? aiStockScene(slot.alt, (_terms_setting1 = terms.setting) != null ? _terms_setting1 : '') : null;
2613
+ // The site's own scenes, turned per slot so a page's pictures show each of them.
2614
+ const sceneList = [];
2615
+ if (itsWork) {
2616
+ var _terms_work;
2617
+ const own = (_terms_work = terms.work) != null ? _terms_work : [];
2618
+ const turn = own.length ? aiLayoutSeedNumber(`${sectionName}|${slot.alt}`) % own.length : 0;
2619
+ sceneList.push(...own.slice(turn), ...own.slice(0, turn));
2620
+ }
2428
2621
  // The work's own subject is never qualified by the craft: "getting ready", never "photographer getting ready".
2429
2622
  const own = work ? (phrase)=>phrase : qualify;
2430
- const thing = subject ? [
2623
+ const thing = (subject ? [
2431
2624
  {
2432
2625
  query: own(subject),
2433
2626
  held: true
@@ -2441,7 +2634,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2441
2634
  held: true
2442
2635
  }
2443
2636
  ]
2444
- ] : [];
2637
+ ] : []).filter((query)=>said(query.query));
2445
2638
  // Never the maker, the camera, an animal or a toy, unless the record names it.
2446
2639
  const recordNamed = work ? new Set(stemList(`${slot.alt} ${(_ref2 = (_slot_record1 = slot.record) == null ? void 0 : _slot_record1.category) != null ? _ref2 : ''}`)) : null;
2447
2640
  const refuseLead = recordNamed ? [
@@ -2452,12 +2645,23 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2452
2645
  ].map(aiStockStem)
2453
2646
  ])
2454
2647
  ].filter((stem)=>!recordNamed.has(stem)) : [];
2648
+ // Where the maker's rungs were, a site that shows its work searches its work.
2649
+ const shows = {
2650
+ query: '',
2651
+ work: true
2652
+ };
2653
+ const making = itsWork ? [
2654
+ shows
2655
+ ] : makers;
2455
2656
  const queries = slot.role === 'hero' ? [
2456
2657
  {
2457
2658
  query: terms.business,
2458
2659
  broad: true
2459
2660
  },
2460
- ...broad
2661
+ ...broad,
2662
+ ...itsWork ? [
2663
+ shows
2664
+ ] : []
2461
2665
  ] : slot.role === 'about' ? [
2462
2666
  ...role ? [
2463
2667
  {
@@ -2467,50 +2671,109 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2467
2671
  }
2468
2672
  ] : [],
2469
2673
  ...part === 'story' ? making : [],
2470
- {
2471
- query: `${terms.business} ${subject}`,
2472
- broad: true,
2473
- people: true
2474
- },
2475
- {
2476
- query: terms.business,
2477
- broad: true,
2478
- people: true
2479
- },
2674
+ ...[
2675
+ {
2676
+ query: `${terms.business} ${subject}`,
2677
+ broad: true,
2678
+ people: true
2679
+ },
2680
+ {
2681
+ query: terms.business,
2682
+ broad: true,
2683
+ people: true
2684
+ }
2685
+ ].filter((query)=>said(query.query)),
2480
2686
  ...part === 'story' ? [] : making,
2481
2687
  ...broad
2482
2688
  ] : [
2483
2689
  ...part === 'story' ? making : [],
2484
2690
  ...scene ? [] : thing,
2691
+ ...itsWork ? [
2692
+ shows
2693
+ ] : [],
2485
2694
  ...broad
2486
2695
  ];
2487
2696
  const seen = new Set();
2488
2697
  const searches = [];
2489
2698
  // A picture of people at a moment asks for them, and where, before anything
2490
2699
  // else, and takes only a photo leading with them (AGL-3856): never the
2491
- // roses of a "Mountain Wedding Day", nor a bride for a family session.
2492
- for (const query of (_ref3 = scene == null ? void 0 : scene.queries) != null ? _ref3 : []){
2493
- seen.add(`${query}|false`);
2494
- searches.push(_extends({
2700
+ // roses of a "Mountain Wedding Day", nor a bride for a family session. A
2701
+ // picture of the work never leads with the maker or a camera (AGL-3942).
2702
+ const workLead = itsWork ? (_terms_maker1 = terms.maker) != null ? _terms_maker1 : [] : [];
2703
+ // The site's work is held to its own scene, never to the craft's world.
2704
+ const sceneSearch = (one, query, ofWork)=>{
2705
+ const refused = [
2706
+ ...new Set([
2707
+ ...refuseLead,
2708
+ ...workLead.filter((stem)=>!one.named.includes(stem))
2709
+ ])
2710
+ ];
2711
+ return _extends({
2495
2712
  query,
2496
2713
  orientation
2497
2714
  }, size, {
2498
2715
  strict: true,
2499
- subject: scene.subject,
2500
- object: scene.object,
2501
- kin: scene.lead,
2716
+ subject: one.subject,
2717
+ object: one.object,
2718
+ kin: one.lead,
2502
2719
  lead: true,
2503
- needs: scene.people,
2504
- allow: scene.named
2505
- }, scene.shun.length ? {
2506
- refuse: scene.shun
2507
- } : {}, refuseLead.length ? {
2508
- refuseLead
2509
- } : {}, work ? {
2720
+ needs: one.people,
2721
+ allow: one.named
2722
+ }, one.shun.length ? {
2723
+ refuse: one.shun
2724
+ } : {}, refused.length ? {
2725
+ refuseLead: refused
2726
+ } : {}, one.away.length ? {
2727
+ away: one.away
2728
+ } : {}, work || ofWork ? {
2510
2729
  worldless: true
2511
- } : {}));
2730
+ } : {});
2731
+ };
2732
+ for (const query of (_ref3 = scene == null ? void 0 : scene.queries) != null ? _ref3 : []){
2733
+ seen.add(`${query}|false`);
2734
+ searches.push(sceneSearch(scene, query, false));
2512
2735
  }
2736
+ /** The site's own scenes, then the place itself where it is a landscape: "mountains". */ const workSearches = ()=>{
2737
+ var _ref;
2738
+ var _SCENE_SETTINGS_find;
2739
+ const found = [];
2740
+ for (const one of sceneList)for (const query of one.queries){
2741
+ if (seen.has(`${query}|false`)) continue;
2742
+ seen.add(`${query}|false`);
2743
+ found.push(sceneSearch(one, query, true));
2744
+ }
2745
+ const place = terms.setting && aiStockSettingIsLandscape(terms.setting) ? terms.setting : '';
2746
+ if (!place || seen.has(`${place}|false`)) return found;
2747
+ seen.add(`${place}|false`);
2748
+ const away = aiStockSettingAway(place);
2749
+ const stems = (_ref = (_SCENE_SETTINGS_find = SCENE_SETTINGS.find((entry)=>entry.said === place)) == null ? void 0 : _SCENE_SETTINGS_find.words.map(aiStockStem)) != null ? _ref : [];
2750
+ found.push(_extends({
2751
+ query: place,
2752
+ orientation
2753
+ }, size, {
2754
+ strict: true,
2755
+ subject: place,
2756
+ object: aiStockStem(place),
2757
+ kin: [
2758
+ ...new Set(stems)
2759
+ ],
2760
+ lead: true
2761
+ }, workLead.length ? {
2762
+ refuseLead: [
2763
+ ...workLead
2764
+ ]
2765
+ } : {}, away.length ? {
2766
+ away
2767
+ } : {}, {
2768
+ worldless: true
2769
+ }));
2770
+ return found;
2771
+ };
2513
2772
  for (const raw of queries){
2773
+ if (raw.work) {
2774
+ searches.push(...workSearches());
2775
+ continue;
2776
+ }
2514
2777
  const query = clip(raw.query);
2515
2778
  const key = `${query}|${raw.people === true}`;
2516
2779
  if (!query || seen.has(key)) continue;
@@ -2546,6 +2809,17 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2546
2809
  }
2547
2810
  return searches;
2548
2811
  }
2812
+ /**
2813
+ * Whether a picture of a craft's site whose pictures are people at a moment
2814
+ * shows that work wherever a page would show the maker (AGL-3942): an about
2815
+ * picture, a story of the craft, an intro's cover. Not a record (which shows
2816
+ * its own moment, {@link aiStockShowsTheWork}), a portrait of the business's
2817
+ * people, nor a product; and only where the site's words name the work.
2818
+ */ export function aiStockShowsItsWork(slot, terms) {
2819
+ var _ref;
2820
+ var _terms_work;
2821
+ return Boolean(terms.scenes) && ((_ref = (_terms_work = terms.work) == null ? void 0 : _terms_work.length) != null ? _ref : 0) > 0 && !slot.record && !slot.people && !slot.product;
2822
+ }
2549
2823
  /**
2550
2824
  * Whether a slot is a portfolio's record of a craft whose work is people at a
2551
2825
  * moment (AGL-3856): a photographer's "Mountain Elopements" shows an
@@ -2628,7 +2902,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2628
2902
  * site's world 1, plus a quarter when it is among the first tags.
2629
2903
  */ export function aiStockRelevance(photo, judgement) {
2630
2904
  var _judgement_rivals, _judgement_subject, _judgement_named, _judgement_shun, _judgement_refuse, _judgement_refuseLead, _judgement_needs, _judgement_shunLead, _judgement_leadAny, _judgement_kin, _judgement_requires, _judgement_category, _judgement_object, _judgement_domain, _judgement_roles;
2631
- var _judgement_roleScene, _judgement_roleRefuse;
2905
+ var _judgement_roleScene, _judgement_roleRefuse, _judgement_away;
2632
2906
  const hit = hitWords(photo);
2633
2907
  if (hit.rejected) return 0;
2634
2908
  if (aiStockSensitive(photo, judgement.named)) return 0;
@@ -2656,6 +2930,12 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2656
2930
  const read = new Set(stemList(((_photo_tags1 = photo.tags) != null ? _photo_tags1 : []).slice(0, ALT_TAGS_READ).join(' ')));
2657
2931
  if (((_judgement_roleRefuse1 = judgement.roleRefuse) != null ? _judgement_roleRefuse1 : []).some((stem)=>read.has(stem))) return 0;
2658
2932
  }
2933
+ // A Blue Ridge couple is never on a beach, however its later tags name a mountain (AGL-3942).
2934
+ if ((_judgement_away = judgement.away) == null ? void 0 : _judgement_away.length) {
2935
+ var _photo_tags2;
2936
+ const read = new Set(stemList(((_photo_tags2 = photo.tags) != null ? _photo_tags2 : []).slice(0, ALT_TAGS_READ).join(' ')));
2937
+ if (judgement.away.some((stem)=>read.has(stem))) return 0;
2938
+ }
2659
2939
  // A wedding day shows its people: a close-up of roses tagged "wedding" never fills it.
2660
2940
  const needs = (_judgement_needs = judgement.needs) != null ? _judgement_needs : [];
2661
2941
  if (needs.length && !needs.some((word)=>hit.all.has(word))) return 0;
@@ -2823,6 +3103,35 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2823
3103
  }
2824
3104
  return out;
2825
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
+ }
2826
3135
  /**
2827
3136
  * What this process has placed for each job, by source key and asset src,
2828
3137
  * with the scope (the page's seed) that placed it (AGL-3660). It spans the
@@ -2854,16 +3163,39 @@ function jobPhotos(key) {
2854
3163
  * Words a tag uses for how a photo looks or feels, never a thing it shows
2855
3164
  * (AGL-3856): "extravagant" of "wedding dress, bride, extravagant", "moody"
2856
3165
  * of "forest, human, photographer", a season or a people's name.
2857
- */ const DESCRIBING = words('love romance romantic luxury luxurious fancy extravagant happiness happy beauty emotion emotions mood lifestyle ' + 'concept aesthetic wallpaper background retro vintage antique old young cute sexy fun funny sad positive negative healthy fit ' + 'strong powerful homemade tasty yummy sweet sweetness nice dark bw monochrome macro sporty active married bridal ' + 'female male adult good bad poc bipoc moody sunny rainy foggy misty snowy cloudy stormy dreamy shiny trendy messy ' + 'casual blonde blond brunette muscular athletic clever intelligent concentrated curious lonely sleepy heavy ' + 'nature outdoors outdoor indoors indoor scenery scenic autumn fall winter spring summer ' + 'physical teenage traveling ' + // Adjectives a tag gives alone, never a thing: "students, multicultural" (AGL-3856).
3166
+ */ const DESCRIBING = words('love romance romantic luxury luxurious fancy extravagant happiness happy beauty emotion emotions mood lifestyle ' + 'concept aesthetic wallpaper background retro vintage antique old young cute sexy fun funny sad positive negative healthy fit ' + 'strong powerful homemade tasty yummy sweet sweetness nice dark bw monochrome macro sporty active married bridal ' + 'female male adult good bad poc bipoc moody sunny rainy foggy misty snowy cloudy stormy dreamy shiny trendy messy ' + 'casual blonde blond brunette muscular athletic clever intelligent concentrated curious lonely sleepy heavy ' + 'nature outdoors outdoor indoors indoor scenery scenic autumn fall winter spring summer ' + 'physical teenage traveling ' + // How a photo is laid out, never a thing it shows: "car wallpapers" is a car (AGL-3942).
3167
+ 'wallpaper wallpapers background backgrounds backdrop backdrops texture textures ' + // Words a tag gives that are no noun at all: "family, together" is a family (AGL-3942).
3168
+ 'together alone apart away abroad ahead nearby outside inside upstairs downstairs ' + // Adjectives a tag gives alone, never a thing: "students, multicultural" (AGL-3856).
2858
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".
2859
3170
  'silhouette silhouettes cutout perspective bokeh closeup ' + // A holiday names when, never a thing: "tet market" is a market.
2860
- '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
+ };
2861
3193
  /**
2862
3194
  * Words a tag uses for what a person or a thing is like, said before it
2863
3195
  * rather than as a thing of its own (AGL-3856): "a confident lawyer", "an
2864
3196
  * elderly couple", "an analog camera", never "a lawyer, law and a confident".
2865
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');
2866
- /** 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');
2867
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');
2868
3200
  /**
2869
3201
  * What a tag names that no photo shows as a thing of its own (AGL-3856): an
@@ -2871,7 +3203,8 @@ function jobPhotos(key) {
2871
3203
  * doing ("read", "lift", "fitness", "a workout"), a word for the photo itself
2872
3204
  * ("photo", "spot"). Left out, never said with an article: "A bookwork and a
2873
3205
  * read", "A kettlebell and fitness", "A board and training" said nothing.
2874
- */ 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 ' + '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');
2875
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');
2876
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));
2877
3210
  /**
@@ -2881,8 +3214,20 @@ function jobPhotos(key) {
2881
3214
  /** Stopwords a description still says, as the people a photo shows: "a family", "a person". */ const SAID_PEOPLE = words('people person family');
2882
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');
2883
3216
  /** "-ing" words that are things, and take no article: "clothing". */ const UNCOUNTED_ING = words('clothing bedding frosting icing flooring stuffing seasoning filling');
2884
- /** 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');
2885
- /** 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
+ };
2886
3231
  /**
2887
3232
  * A body's parts, never a thing a photo is described by (AGL-3856): "A girl
2888
3233
  * with a hand" said nothing, and "A body in a gym" is a gym.
@@ -2952,13 +3297,46 @@ function jobPhotos(key) {
2952
3297
  cliff: 'on',
2953
3298
  river: 'by',
2954
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',
2955
3308
  waterfall: 'at'
2956
3309
  };
2957
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');
2958
- /** 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');
2959
3312
  /** Places said in words of their own: a mountain tag is "in the mountains". */ const PLACES_SAID = {
2960
- 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'
2961
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
+ }
2962
3340
  /** How many of a photo's first tags its description reads: the things and people it is OF. */ const ALT_TAGS_READ = 8;
2963
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;
2964
3342
  /** A tag's words, a word for the photo itself taken off a compound: "photo-camera" is a camera. */ function tagWords(tag) {
@@ -2983,6 +3361,8 @@ function jobPhotos(key) {
2983
3361
  if (/^(?:out|up|off|down|away|on|in)$/.test(list[list.length - 1])) return null;
2984
3362
  if (list.some((word)=>BRANDS.has(word))) return null;
2985
3363
  if (list.every((word)=>proper.has(word))) return list.map((word)=>`${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
3364
+ // Two people named side by side are both shown: "bride groom" is a bride and groom (AGL-3942).
3365
+ if (list.length === 2 && list.every((word)=>PEOPLE_NOUNS.has(word)) && list[0] !== list[1]) return `${articleFor(list[0])} ${list[0]} and ${list[1]}`;
2986
3366
  const last = list[list.length - 1];
2987
3367
  // An idea or a doing, never a thing: "law", "feedback", "customer satisfaction", "participation", "weightlifting".
2988
3368
  if (ABSTRACT_NOUNS.has(last) || ABSTRACT_NOUNS.has(aiStockStem(last)) || saysAnIdea(last)) return null;
@@ -2990,10 +3370,18 @@ function jobPhotos(key) {
2990
3370
  if (doing) return null;
2991
3371
  const phrase = list.join(' ');
2992
3372
  const plural = PLURAL_NOUNS.has(last) || aiStockStem(last) !== last && !/^(?:lens|canvas|atlas|gas|bus)$/.test(last);
2993
- if (plural || MASS_NOUNS.has(last) || STOPWORDS.has(list[0]) && !SAID_PEOPLE.has(list[0])) return phrase;
2994
- return `${articleFor(phrase)} ${phrase}`;
3373
+ if (plural || MASS_NOUNS.has(last) || STOPWORDS.has(list[0]) && !SAID_PEOPLE.has(list[0])) return spelled(phrase);
3374
+ return `${articleFor(phrase)} ${spelled(phrase)}`;
2995
3375
  }
2996
- /** "a" or "an" before a phrase. */ const articleFor = (phrase)=>/^(?:[aeio]|u(?!ni|s[eu]))/.test(phrase) ? 'an' : 'a';
3376
+ /** Letters said one by one, written in capitals: "an SLR camera", "a DSLR" (AGL-3942). */ const INITIALISMS = words('slr dslr tv dj diy bbq suv atv uv vr');
3377
+ /** A phrase as a description writes it, its initialisms in capitals. */ const spelled = (phrase)=>phrase.split(' ').map((word)=>INITIALISMS.has(word) ? word.toUpperCase() : word).join(' ');
3378
+ /** "a" or "an" before a phrase, an initialism by how its first letter is said: "an SLR", "a DSLR". */ const articleFor = (phrase)=>{
3379
+ var _phrase_split_;
3380
+ const first = (_phrase_split_ = phrase.split(' ')[0]) != null ? _phrase_split_ : '';
3381
+ if (INITIALISMS.has(first)) return /^[aefhilmnorsx]/.test(first) ? 'an' : 'a';
3382
+ return /^(?:[aeio]|u(?!ni|s[eu]))/.test(phrase) ? 'an' : 'a';
3383
+ };
3384
+ /** Words a description never ends on: an article, a preposition, a conjunction. */ const DANGLING_END = /(?:\s+(?:a|an|the|and|or|with|in|on|at|by|of|for|to|from|into|onto))+$/i;
2997
3385
  /** Whether two words name one thing, one inside the other: "law" and "lawyer", "cam" and "camera". */ function nearSynonyms(a, b) {
2998
3386
  const [short, long] = a.length <= b.length ? [
2999
3387
  a,
@@ -3023,15 +3411,31 @@ function jobPhotos(key) {
3023
3411
  * - a second thing comes only from the first two tags and where no place is
3024
3412
  * said, what a person holds only from the first three, where it is set
3025
3413
  * from the first ten;
3026
- * - 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).
3027
3425
  *
3028
3426
  * `null` where no tag names a thing.
3029
3427
  */ export function aiStockTagsAlt(tags, proper = new Set()) {
3428
+ var _ref;
3030
3429
  const parts = [];
3031
3430
  let quality = '';
3032
3431
  let portrait = false;
3033
- for (const [index, tag] of tags.slice(0, ALT_PLACE_TAGS_READ).entries()){
3034
- 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;
3035
3439
  var _bare_split_pop;
3036
3440
  const lower = tag.toLowerCase().trim();
3037
3441
  const reading = index < ALT_TAGS_READ;
@@ -3043,11 +3447,18 @@ function jobPhotos(key) {
3043
3447
  if (reading) quality || (quality = lower);
3044
3448
  continue;
3045
3449
  }
3046
- 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);
3047
3458
  if (!said) continue;
3048
3459
  const bare = said.replace(/^an?\s+/, '');
3049
3460
  const stems = stemList(bare);
3050
- 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 : '';
3051
3462
  if (!head) continue;
3052
3463
  const headStem = aiStockStem(head);
3053
3464
  const kind = PEOPLE_NOUNS.has(head) || PEOPLE_NOUNS.has(headStem) ? 'person' : PLACES[headStem] ? 'place' : 'thing';
@@ -3091,7 +3502,12 @@ function jobPhotos(key) {
3091
3502
  // silhouettes, camera, nature, photographer" men with a camera.
3092
3503
  const leads = (part)=>portrait || part.index < ALT_PERSON_TAGS_READ || !things.length;
3093
3504
  const role = posed ? undefined : people.find((part)=>!GENERIC_PEOPLE.has(part.head) && part.index < ALT_ROLE_TAGS_READ && leads(part));
3094
- 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);
3095
3511
  // Nothing but places: a desk is the thing in a classroom; "a school in a classroom" says one place.
3096
3512
  if (!person && !things.length && places.length > 1) {
3097
3513
  const object = places.find((part)=>OBJECT_PLACES.has(part.head));
@@ -3103,7 +3519,7 @@ function jobPhotos(key) {
3103
3519
  } else places = places.slice(0, 1);
3104
3520
  }
3105
3521
  const place = places[0];
3106
- if (!person && !things.length && !place) return null;
3522
+ if (!person && !things.length && !place && !weather) return null;
3107
3523
  /** Every word said so far, so none is said twice. */ const said = new Set();
3108
3524
  const fresh = (part)=>{
3109
3525
  const list = stemList(part.said.replace(/^an?\s+/, ''));
@@ -3115,20 +3531,44 @@ function jobPhotos(key) {
3115
3531
  let phrase = '';
3116
3532
  if (person && fresh(person)) {
3117
3533
  phrase = portrait ? `a portrait of ${person.said}` : qualified(person);
3118
- const held = things.find((part)=>part.index <= 2);
3534
+ const held = family && child && child !== person ? child : things.find((part)=>part.index <= 2);
3119
3535
  if (!portrait && held && fresh(held)) phrase += ` ${CLOTHING.has(held.head) ? 'in' : 'with'} ${held.said}`;
3120
3536
  } else if (things[0] && fresh(things[0])) {
3121
3537
  phrase = qualified(things[0]);
3122
3538
  const second = things[1];
3123
3539
  if (second && second.index <= 1 && !place && fresh(second)) phrase += ` and ${second.said}`;
3124
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 /, '');
3125
3553
  if (place && fresh(place)) {
3126
- var _PLACES_place_head, _PLACES_SAID_place_head;
3127
- const preposition = (_PLACES_place_head = PLACES[place.head]) != null ? _PLACES_place_head : 'in';
3128
- 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;
3129
- 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}`;
3130
3568
  }
3131
- if (!phrase) return null;
3569
+ // Never a dangling article or preposition, whatever a tag left behind.
3570
+ phrase = phrase.replace(DANGLING_END, '').trim();
3571
+ if (!phrase || /^(?:a|an|the)$/i.test(phrase)) return null;
3132
3572
  const out = `${phrase.charAt(0).toUpperCase()}${phrase.slice(1)}`;
3133
3573
  return out.length > AI_STOCK_ALT_MAX ? out.slice(0, AI_STOCK_ALT_MAX).replace(/[\s,]+\S*$/, '') : out;
3134
3574
  }
@@ -3260,13 +3700,18 @@ const EXTENSIONS = {
3260
3700
  * post — so no slot searches by words another slot is held against. A site for
3261
3701
  * adults alone ({@link aiStockAdultsOnly}) is told so, whatever its world.
3262
3702
  */ export function aiStockSourceTerms(input) {
3263
- var _input_audience;
3703
+ var _input_audience, _input_audience1;
3264
3704
  const terms = sourceWorldTerms(input);
3265
3705
  // Where the site's own words set it: a photographer "in the Blue Ridge" for "mountain weddings" (AGL-3856).
3266
3706
  const setting = aiStockSetting([
3267
3707
  input.business,
3268
3708
  (_input_audience = input.audience) != null ? _input_audience : ''
3269
3709
  ].join(' . '));
3710
+ // A craft whose pictures are people at a moment shows the moments its site names (AGL-3942).
3711
+ const work = terms.scenes ? aiStockWorkScenes([
3712
+ input.business,
3713
+ (_input_audience1 = input.audience) != null ? _input_audience1 : ''
3714
+ ].join(' . '), setting) : [];
3270
3715
  return _extends({}, terms, aiStockAdultsOnly([
3271
3716
  input.audience,
3272
3717
  input.name,
@@ -3275,6 +3720,8 @@ const EXTENSIONS = {
3275
3720
  adults: true
3276
3721
  } : {}, setting ? {
3277
3722
  setting
3723
+ } : {}, work.length ? {
3724
+ work
3278
3725
  } : {});
3279
3726
  }
3280
3727
  /** What a source's photos are searched by: its world, as {@link aiStockSourceTerms} reads it. */ function sourceWorldTerms(input) {
@@ -4484,6 +4931,7 @@ const WORKSPACE = {
4484
4931
  delete request.allow;
4485
4932
  delete request.refuseLead;
4486
4933
  delete request.worldless;
4934
+ delete request.away;
4487
4935
  const key = JSON.stringify([
4488
4936
  request.query,
4489
4937
  request.orientation,
@@ -4653,7 +5101,7 @@ const WORKSPACE = {
4653
5101
  * (AGL-3923). What lets a sensitive topic in is always the site's own words.
4654
5102
  */ const judgementOf = (slot, request, subject, world)=>{
4655
5103
  var _request_subject, _request_shun;
4656
- var _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs, _request_refuseLead;
5104
+ var _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs, _request_refuseLead, _request_away;
4657
5105
  return _extends({
4658
5106
  subject: (_request_subject = request.subject) != null ? _request_subject : request.broad ? '' : subject,
4659
5107
  domain: world.domain,
@@ -4698,6 +5146,8 @@ const WORKSPACE = {
4698
5146
  needs: request.needs
4699
5147
  } : {}, ((_request_refuseLead = request.refuseLead) == null ? void 0 : _request_refuseLead.length) ? {
4700
5148
  refuseLead: request.refuseLead
5149
+ } : {}, ((_request_away = request.away) == null ? void 0 : _request_away.length) ? {
5150
+ away: request.away
4701
5151
  } : {}, request.worldless ? {
4702
5152
  domain: [],
4703
5153
  rivals: []
@@ -4708,8 +5158,9 @@ const WORKSPACE = {
4708
5158
  * filled from (AGL-3660): its broad searches, any hit naming the craft
4709
5159
  * itself. Never a desk, a laptop or a living room for a soap shop.
4710
5160
  */ const craftSearches = (slot, world = terms)=>{
4711
- // A portfolio's record of the work is never filled by the craft's own photos (AGL-3856).
4712
- if (!world.core.length || aiStockShowsTheWork(slot, world)) return [];
5161
+ // A portfolio's record of the work is never filled by the craft's own
5162
+ // photos (AGL-3856), nor is any picture of a site that shows its work (AGL-3942).
5163
+ if (!world.core.length || aiStockShowsTheWork(slot, world) || aiStockShowsItsWork(slot, world)) return [];
4713
5164
  const orientation = aiStockOrientation(slot.aspect);
4714
5165
  const size = slot.role === 'hero' ? {
4715
5166
  minWidth: 1600