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

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 (60) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-credits-card.component.d.ts +4 -3
  3. package/src/lib/components/ai-credits-card.component.js +25 -8
  4. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  5. package/src/lib/components/ai-job-links.d.ts +6 -0
  6. package/src/lib/components/ai-job-links.js +9 -0
  7. package/src/lib/components/ai-job-links.js.map +1 -1
  8. package/src/lib/components/ai-products-hub-card.component.d.ts +6 -0
  9. package/src/lib/components/ai-products-hub-card.component.js +21 -6
  10. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  11. package/src/lib/components/ai-site-build-page.component.js +3 -2
  12. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  13. package/src/lib/components/ai-store-finish-card.component.d.ts +14 -3
  14. package/src/lib/components/ai-store-finish-card.component.js +67 -9
  15. package/src/lib/components/ai-store-finish-card.component.js.map +1 -1
  16. package/src/lib/components/assist-build-card.component.js +3 -2
  17. package/src/lib/components/assist-build-card.component.js.map +1 -1
  18. package/src/lib/jobs/ai-job-build-step.d.ts +3 -0
  19. package/src/lib/jobs/ai-job-build-step.js +8 -2
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-form-step.d.ts +9 -0
  22. package/src/lib/jobs/ai-job-form-step.js +21 -2
  23. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-page-language.d.ts +26 -9
  25. package/src/lib/jobs/ai-job-page-language.js +46 -3
  26. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  27. package/src/lib/jobs/ai-job-page-step.js +4 -2
  28. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  29. package/src/lib/jobs/ai-job-site-look.d.ts +2 -1
  30. package/src/lib/jobs/ai-job-site-look.js +6 -2
  31. package/src/lib/jobs/ai-job-site-look.js.map +1 -1
  32. package/src/lib/jobs/ai-job-site-step.js +11 -6
  33. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  34. package/src/lib/jobs/ai-layout-owner-photos.d.ts +57 -0
  35. package/src/lib/jobs/ai-layout-owner-photos.js +110 -0
  36. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -0
  37. package/src/lib/jobs/ai-layout-stock-photos.d.ts +51 -10
  38. package/src/lib/jobs/ai-layout-stock-photos.js +812 -121
  39. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  40. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.d.ts +29 -0
  41. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.js +291 -0
  42. package/src/lib/jobs/fixtures/ai-stock-photo-agl3942-recordings.js.map +1 -0
  43. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.d.ts +4 -0
  44. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js +87 -1
  45. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js.map +1 -1
  46. package/src/lib/jobs/fixtures/ai-stock-photo-mexicano-recordings.d.ts +31 -0
  47. package/src/lib/jobs/fixtures/ai-stock-photo-mexicano-recordings.js +325 -0
  48. package/src/lib/jobs/fixtures/ai-stock-photo-mexicano-recordings.js.map +1 -0
  49. package/src/lib/layout-language/ai-layout-motion.d.ts +60 -7
  50. package/src/lib/layout-language/ai-layout-motion.js +231 -4
  51. package/src/lib/layout-language/ai-layout-motion.js.map +1 -1
  52. package/src/lib/layout-language/ai-layout-pictures.d.ts +8 -0
  53. package/src/lib/layout-language/ai-layout-pictures.js +68 -4
  54. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  55. package/src/lib/model/ai-site-look.d.ts +10 -1
  56. package/src/lib/model/ai-site-look.js +14 -3
  57. package/src/lib/model/ai-site-look.js.map +1 -1
  58. package/src/lib/native-nav.d.ts +26 -0
  59. package/src/lib/native-nav.js +58 -0
  60. package/src/lib/native-nav.js.map +1 -0
@@ -1541,8 +1541,14 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
1541
1541
  ]
1542
1542
  });
1543
1543
  }
1544
+ /**
1545
+ * Words that say a picture is one of a set, never what it shows (AGL-3923):
1546
+ * the beta.266 Mexicano portfolio's gallery was "Project one" to "Project
1547
+ * four", and searched "project" (students at a whiteboard), "project four"
1548
+ * (lavender) and "four" (a running track's lane numbers).
1549
+ */ const PLACEHOLDER_SUBJECTS = words('project projects piece pieces item items example examples sample samples entry entries untitled work works ' + 'selected recent latest ' + 'four five six seven eight nine ten first second third fourth fifth sixth seventh eighth ninth tenth');
1544
1550
  /** A phrase's content words without its filler; all filler, and nothing is left. */ function subjectOf(list) {
1545
- return list.filter((word)=>!FILLER.has(word));
1551
+ return list.filter((word)=>!FILLER.has(word) && !PLACEHOLDER_SUBJECTS.has(word));
1546
1552
  }
1547
1553
  /**
1548
1554
  * A picture's subject in a few words (AGL-3660): the noun phrase its alt
@@ -1979,7 +1985,56 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1979
1985
  const said = texts.filter(Boolean).join(' . ');
1980
1986
  return ADULT_AUDIENCE.test(said) && !YOUNG_AUDIENCE.test(said);
1981
1987
  }
1982
- /** 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 = [
1988
+ /**
1989
+ * Words a photo by the sea is tagged with: never one set in the mountains, a
1990
+ * forest or a city (AGL-3942). Never "palm" or "wave", a hand's as often.
1991
+ */ const SEASIDE = [
1992
+ 'beach',
1993
+ 'beaches',
1994
+ 'sea',
1995
+ 'seaside',
1996
+ 'seashore',
1997
+ 'ocean',
1998
+ 'coast',
1999
+ 'coastal',
2000
+ 'shore',
2001
+ 'shoreline',
2002
+ 'surf',
2003
+ 'waves',
2004
+ 'caribbean',
2005
+ 'tropical',
2006
+ 'island',
2007
+ 'lagoon',
2008
+ 'bay',
2009
+ 'pier'
2010
+ ];
2011
+ /** Words a photo of a desert is tagged with. */ const DESERT = [
2012
+ 'desert',
2013
+ 'dune',
2014
+ 'dunes',
2015
+ 'cactus',
2016
+ 'sahara'
2017
+ ];
2018
+ /** Words a photo of a city is tagged with. */ const CITYSCAPE = [
2019
+ 'city',
2020
+ 'urban',
2021
+ 'downtown',
2022
+ 'street',
2023
+ 'skyline',
2024
+ 'skyscraper',
2025
+ 'skyscrapers',
2026
+ 'subway',
2027
+ 'metro'
2028
+ ];
2029
+ /**
2030
+ * Where a picture of a moment is set, by the words that name it, the word a
2031
+ * search and a photo say it with, whether it is a place a photo can show on
2032
+ * its own (a landscape, not a season), and the words of the places it is
2033
+ * not, which none of a photo's first tags may name (AGL-3942): the beta.267
2034
+ * Copper Hollow Film & Portrait galleries, a Blue Ridge photographer's,
2035
+ * showed two couples on a beach, tagged "beach, couple, nature, mountain"
2036
+ * and "romantic wedding couple, the beach".
2037
+ */ const SCENE_SETTINGS = [
1983
2038
  {
1984
2039
  words: [
1985
2040
  'mountain',
@@ -1992,7 +2047,13 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1992
2047
  'overlook',
1993
2048
  'parkway'
1994
2049
  ],
1995
- said: 'mountains'
2050
+ said: 'mountains',
2051
+ landscape: true,
2052
+ away: [
2053
+ ...SEASIDE,
2054
+ ...DESERT,
2055
+ ...CITYSCAPE
2056
+ ]
1996
2057
  },
1997
2058
  {
1998
2059
  words: [
@@ -2000,7 +2061,14 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2000
2061
  'autumn',
2001
2062
  'foliage'
2002
2063
  ],
2003
- said: 'autumn'
2064
+ said: 'autumn',
2065
+ away: [
2066
+ 'winter',
2067
+ 'snow',
2068
+ 'snowy',
2069
+ 'spring',
2070
+ 'blossom'
2071
+ ]
2004
2072
  },
2005
2073
  {
2006
2074
  words: [
@@ -2008,7 +2076,12 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2008
2076
  'snow',
2009
2077
  'snowy'
2010
2078
  ],
2011
- said: 'winter'
2079
+ said: 'winter',
2080
+ away: [
2081
+ ...SEASIDE,
2082
+ 'summer',
2083
+ 'tropical'
2084
+ ]
2012
2085
  },
2013
2086
  {
2014
2087
  words: [
@@ -2018,7 +2091,15 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2018
2091
  'shore',
2019
2092
  'ocean'
2020
2093
  ],
2021
- said: 'beach'
2094
+ said: 'beach',
2095
+ landscape: true,
2096
+ away: [
2097
+ 'snow',
2098
+ 'snowy',
2099
+ 'ski',
2100
+ 'skiing',
2101
+ ...CITYSCAPE
2102
+ ]
2022
2103
  },
2023
2104
  {
2024
2105
  words: [
@@ -2026,7 +2107,13 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2026
2107
  'woods',
2027
2108
  'woodland'
2028
2109
  ],
2029
- said: 'forest'
2110
+ said: 'forest',
2111
+ landscape: true,
2112
+ away: [
2113
+ ...SEASIDE,
2114
+ ...DESERT,
2115
+ ...CITYSCAPE
2116
+ ]
2030
2117
  },
2031
2118
  {
2032
2119
  words: [
@@ -2034,7 +2121,14 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2034
2121
  'blossom',
2035
2122
  'blossoms'
2036
2123
  ],
2037
- said: 'spring'
2124
+ said: 'spring',
2125
+ away: [
2126
+ 'winter',
2127
+ 'snow',
2128
+ 'snowy',
2129
+ 'autumn',
2130
+ 'fall'
2131
+ ]
2038
2132
  },
2039
2133
  {
2040
2134
  words: [
@@ -2042,16 +2136,27 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2042
2136
  'urban',
2043
2137
  'downtown'
2044
2138
  ],
2045
- said: 'city'
2139
+ said: 'city',
2140
+ landscape: true,
2141
+ away: [
2142
+ ...SEASIDE,
2143
+ ...DESERT
2144
+ ]
2046
2145
  }
2047
2146
  ];
2147
+ /** The stems of the places a setting is not ({@link SCENE_SETTINGS}); none for no setting. */ export function aiStockSettingAway(setting) {
2148
+ const entry = SCENE_SETTINGS.find((candidate)=>candidate.said === setting);
2149
+ return (entry == null ? void 0 : entry.away) ? [
2150
+ ...new Set(entry.away.map(aiStockStem))
2151
+ ] : [];
2152
+ }
2048
2153
  /**
2049
2154
  * The scene a picture's words name (AGL-3856): the people at a moment, and
2050
2155
  * where — "Ridgetop Elopement" a couple in the mountains, "Mountain Wedding
2051
2156
  * Day" a wedding couple in the mountains, "Family Session in the Fall" a
2052
2157
  * family in autumn — read from every word, never only the first clause.
2053
2158
  * `null` where it names no people.
2054
- */ export function aiStockScene(text) {
2159
+ */ export function aiStockScene(text, siteSetting = '') {
2055
2160
  const named = sceneWords(text);
2056
2161
  const scene = PEOPLE_SCENES.find((entry)=>entry.words.some((word)=>named.has(word)));
2057
2162
  if (!scene) return null;
@@ -2074,7 +2179,9 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2074
2179
  shun: scene.shun.filter((word)=>!named.has(word)).map(aiStockStem),
2075
2180
  named: [
2076
2181
  ...new Set(stemList(text))
2077
- ]
2182
+ ],
2183
+ // Searched where its own words set it; never shown anywhere the site is not.
2184
+ away: aiStockSettingAway(setting || siteSetting)
2078
2185
  };
2079
2186
  }
2080
2187
  /** A text's lowercase words, letters only. */ const sceneWords = (text)=>new Set(text.toLowerCase().split(/[^a-zÀ-ɏ]+/).filter(Boolean));
@@ -2125,9 +2232,53 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2125
2232
  shun: scene.shun.filter((word)=>!all.has(word)).map(aiStockStem),
2126
2233
  named: [
2127
2234
  ...new Set(stemList(texts.join(' ')))
2128
- ]
2235
+ ],
2236
+ away: aiStockSettingAway(setting)
2129
2237
  };
2130
2238
  }
2239
+ /**
2240
+ * The work a craft whose pictures are people at a moment shows (AGL-3942):
2241
+ * the scenes its site's own words name, in the order they name them, each
2242
+ * set where the site is — a wedding photographer "for couples planning
2243
+ * mountain weddings, and families" shows a wedding couple in the mountains
2244
+ * and a family in the mountains. A family session still never shows a
2245
+ * bride, whatever else the site names. Empty where its words name no people.
2246
+ */ export function aiStockWorkScenes(text, setting = '') {
2247
+ const list = text.toLowerCase().split(/[^a-zÀ-ɏ]+/).filter(Boolean);
2248
+ const found = PEOPLE_SCENES.map((scene)=>({
2249
+ scene,
2250
+ at: list.findIndex((word)=>scene.words.includes(word))
2251
+ })).filter((entry)=>entry.at >= 0).sort((a, b)=>a.at - b.at);
2252
+ return found.map(({ scene })=>{
2253
+ const named = new Set(list.filter((word)=>scene.words.includes(word)));
2254
+ const query = scene.query(named);
2255
+ return {
2256
+ queries: [
2257
+ ...new Set([
2258
+ setting ? `${query} ${setting}` : '',
2259
+ query
2260
+ ].filter(Boolean))
2261
+ ],
2262
+ subject: [
2263
+ scene.subject,
2264
+ setting
2265
+ ].filter(Boolean).join(' '),
2266
+ object: scene.subject,
2267
+ lead: scene.lead.filter((phrase)=>!RECORD_SCENE_UNPEOPLED.has(phrase)).map((phrase)=>stemList(phrase).join(' ')),
2268
+ people: scene.people.map(aiStockStem),
2269
+ shun: scene.shun.map(aiStockStem),
2270
+ named: [
2271
+ ...new Set([
2272
+ ...named
2273
+ ].map(aiStockStem))
2274
+ ],
2275
+ away: aiStockSettingAway(setting)
2276
+ };
2277
+ });
2278
+ }
2279
+ /** 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) {
2280
+ return SCENE_SETTINGS.some((entry)=>entry.said === setting && entry.landscape === true);
2281
+ }
2131
2282
  /**
2132
2283
  * A scene's lead words a record's cover never leads with alone
2133
2284
  * ({@link aiStockRecordScene}): the moment with no one in it (a wedding's table
@@ -2367,7 +2518,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2367
2518
  * asks first for the role its words or its list's name (`sectionName`) say,
2368
2519
  * in the site's world: "teacher classroom" for a "Math instructor" (AGL-3856).
2369
2520
  */ export function aiStockSearchesFor(slot, terms, subject, part, sectionName = '') {
2370
- var _terms_core, _terms_making, _ref, _ref1, _terms_setting, _ref2, _terms_maker, _ref3;
2521
+ var _terms_core, _terms_making, _ref, _ref1, _terms_setting, _terms_setting1, _ref2, _terms_maker, _terms_maker1, _ref3;
2371
2522
  var _aiStockPeopleRole, _slot_record, _slot_record1;
2372
2523
  const orientation = aiStockOrientation(slot.aspect);
2373
2524
  const size = slot.role === 'hero' ? {
@@ -2383,6 +2534,21 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2383
2534
  // searched "photographer mountain elopements" and placed a press
2384
2535
  // photographer, a toy frog with a camera and a cat (AGL-3856).
2385
2536
  const work = aiStockShowsTheWork(slot, terms);
2537
+ // Every other picture of such a craft's site shows its work where a page
2538
+ // would show the maker (AGL-3942): the beta.267 Copper Hollow Film &
2539
+ // Portrait about page searched "photographer at work" and "photographer
2540
+ // camera", and every picture on it was a stock photographer.
2541
+ const itsWork = !work && aiStockShowsItsWork(slot, terms);
2542
+ /** Whether a query names the craft by its own name, the profession's noun: "wedding photographer". */ const namesCraft = (query)=>stemList(query).some((stem)=>{
2543
+ var _terms_core;
2544
+ return ((_terms_core = terms.core) != null ? _terms_core : []).includes(stem);
2545
+ });
2546
+ /** 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)=>{
2547
+ var _terms_maker;
2548
+ return ((_terms_maker = terms.maker) != null ? _terms_maker : []).includes(stem);
2549
+ });
2550
+ /** A rung of the maker's own photos, left out on a site that shows its work. */ const shown = (query)=>!itsWork || !namesMaker(query);
2551
+ /** 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);
2386
2552
  const object = aiStockObjectWord(subject);
2387
2553
  const subjectStems = stemList(subject);
2388
2554
  const kin = object ? objectKin(object, [
@@ -2403,13 +2569,13 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2403
2569
  // own object among the first tags, and searched by its object's kin, never
2404
2570
  // the category's world alone (AGL-3660).
2405
2571
  const collection = part === 'collection' && Boolean(terms.craft) && !work;
2406
- const broad = kin && collection || work ? [] : terms.broad.map((query)=>({
2572
+ const broad = kin && collection || work ? [] : terms.broad.filter(shown).map((query)=>({
2407
2573
  query,
2408
2574
  broad: true,
2409
2575
  category: collection
2410
2576
  }));
2411
2577
  // The maker at work, held to the craft: never "hands" (AGL-3660).
2412
- const making = (work ? [] : (_terms_making = terms.making) != null ? _terms_making : []).map((query)=>({
2578
+ const makers = (work ? [] : (_terms_making = terms.making) != null ? _terms_making : []).filter(shown).map((query)=>({
2413
2579
  query,
2414
2580
  broad: true,
2415
2581
  category: true
@@ -2418,10 +2584,18 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2418
2584
  const roleWord = slot.people ? (_ref = (_aiStockPeopleRole = aiStockPeopleRole(slot.alt, sectionName)) == null ? void 0 : _aiStockPeopleRole.query) != null ? _ref : '' : '';
2419
2585
  const role = roleWord ? stemList(terms.business).includes(roleWord) ? terms.business : `${roleWord} ${terms.business}`.trim() : '';
2420
2586
  // A scene's people are read from every word of the picture's, never its first clause alone.
2421
- 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;
2587
+ 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;
2588
+ // The site's own scenes, turned per slot so a page's pictures show each of them.
2589
+ const sceneList = [];
2590
+ if (itsWork) {
2591
+ var _terms_work;
2592
+ const own = (_terms_work = terms.work) != null ? _terms_work : [];
2593
+ const turn = own.length ? aiLayoutSeedNumber(`${sectionName}|${slot.alt}`) % own.length : 0;
2594
+ sceneList.push(...own.slice(turn), ...own.slice(0, turn));
2595
+ }
2422
2596
  // The work's own subject is never qualified by the craft: "getting ready", never "photographer getting ready".
2423
2597
  const own = work ? (phrase)=>phrase : qualify;
2424
- const thing = subject ? [
2598
+ const thing = (subject ? [
2425
2599
  {
2426
2600
  query: own(subject),
2427
2601
  held: true
@@ -2435,7 +2609,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2435
2609
  held: true
2436
2610
  }
2437
2611
  ]
2438
- ] : [];
2612
+ ] : []).filter((query)=>said(query.query));
2439
2613
  // Never the maker, the camera, an animal or a toy, unless the record names it.
2440
2614
  const recordNamed = work ? new Set(stemList(`${slot.alt} ${(_ref2 = (_slot_record1 = slot.record) == null ? void 0 : _slot_record1.category) != null ? _ref2 : ''}`)) : null;
2441
2615
  const refuseLead = recordNamed ? [
@@ -2446,12 +2620,23 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2446
2620
  ].map(aiStockStem)
2447
2621
  ])
2448
2622
  ].filter((stem)=>!recordNamed.has(stem)) : [];
2623
+ // Where the maker's rungs were, a site that shows its work searches its work.
2624
+ const shows = {
2625
+ query: '',
2626
+ work: true
2627
+ };
2628
+ const making = itsWork ? [
2629
+ shows
2630
+ ] : makers;
2449
2631
  const queries = slot.role === 'hero' ? [
2450
2632
  {
2451
2633
  query: terms.business,
2452
2634
  broad: true
2453
2635
  },
2454
- ...broad
2636
+ ...broad,
2637
+ ...itsWork ? [
2638
+ shows
2639
+ ] : []
2455
2640
  ] : slot.role === 'about' ? [
2456
2641
  ...role ? [
2457
2642
  {
@@ -2461,50 +2646,109 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2461
2646
  }
2462
2647
  ] : [],
2463
2648
  ...part === 'story' ? making : [],
2464
- {
2465
- query: `${terms.business} ${subject}`,
2466
- broad: true,
2467
- people: true
2468
- },
2469
- {
2470
- query: terms.business,
2471
- broad: true,
2472
- people: true
2473
- },
2649
+ ...[
2650
+ {
2651
+ query: `${terms.business} ${subject}`,
2652
+ broad: true,
2653
+ people: true
2654
+ },
2655
+ {
2656
+ query: terms.business,
2657
+ broad: true,
2658
+ people: true
2659
+ }
2660
+ ].filter((query)=>said(query.query)),
2474
2661
  ...part === 'story' ? [] : making,
2475
2662
  ...broad
2476
2663
  ] : [
2477
2664
  ...part === 'story' ? making : [],
2478
2665
  ...scene ? [] : thing,
2666
+ ...itsWork ? [
2667
+ shows
2668
+ ] : [],
2479
2669
  ...broad
2480
2670
  ];
2481
2671
  const seen = new Set();
2482
2672
  const searches = [];
2483
2673
  // A picture of people at a moment asks for them, and where, before anything
2484
2674
  // else, and takes only a photo leading with them (AGL-3856): never the
2485
- // roses of a "Mountain Wedding Day", nor a bride for a family session.
2486
- for (const query of (_ref3 = scene == null ? void 0 : scene.queries) != null ? _ref3 : []){
2487
- seen.add(`${query}|false`);
2488
- searches.push(_extends({
2675
+ // roses of a "Mountain Wedding Day", nor a bride for a family session. A
2676
+ // picture of the work never leads with the maker or a camera (AGL-3942).
2677
+ const workLead = itsWork ? (_terms_maker1 = terms.maker) != null ? _terms_maker1 : [] : [];
2678
+ // The site's work is held to its own scene, never to the craft's world.
2679
+ const sceneSearch = (one, query, ofWork)=>{
2680
+ const refused = [
2681
+ ...new Set([
2682
+ ...refuseLead,
2683
+ ...workLead.filter((stem)=>!one.named.includes(stem))
2684
+ ])
2685
+ ];
2686
+ return _extends({
2489
2687
  query,
2490
2688
  orientation
2491
2689
  }, size, {
2492
2690
  strict: true,
2493
- subject: scene.subject,
2494
- object: scene.object,
2495
- kin: scene.lead,
2691
+ subject: one.subject,
2692
+ object: one.object,
2693
+ kin: one.lead,
2496
2694
  lead: true,
2497
- needs: scene.people,
2498
- allow: scene.named
2499
- }, scene.shun.length ? {
2500
- refuse: scene.shun
2501
- } : {}, refuseLead.length ? {
2502
- refuseLead
2503
- } : {}, work ? {
2695
+ needs: one.people,
2696
+ allow: one.named
2697
+ }, one.shun.length ? {
2698
+ refuse: one.shun
2699
+ } : {}, refused.length ? {
2700
+ refuseLead: refused
2701
+ } : {}, one.away.length ? {
2702
+ away: one.away
2703
+ } : {}, work || ofWork ? {
2504
2704
  worldless: true
2505
- } : {}));
2705
+ } : {});
2706
+ };
2707
+ for (const query of (_ref3 = scene == null ? void 0 : scene.queries) != null ? _ref3 : []){
2708
+ seen.add(`${query}|false`);
2709
+ searches.push(sceneSearch(scene, query, false));
2506
2710
  }
2711
+ /** The site's own scenes, then the place itself where it is a landscape: "mountains". */ const workSearches = ()=>{
2712
+ var _ref;
2713
+ var _SCENE_SETTINGS_find;
2714
+ const found = [];
2715
+ for (const one of sceneList)for (const query of one.queries){
2716
+ if (seen.has(`${query}|false`)) continue;
2717
+ seen.add(`${query}|false`);
2718
+ found.push(sceneSearch(one, query, true));
2719
+ }
2720
+ const place = terms.setting && aiStockSettingIsLandscape(terms.setting) ? terms.setting : '';
2721
+ if (!place || seen.has(`${place}|false`)) return found;
2722
+ seen.add(`${place}|false`);
2723
+ const away = aiStockSettingAway(place);
2724
+ const stems = (_ref = (_SCENE_SETTINGS_find = SCENE_SETTINGS.find((entry)=>entry.said === place)) == null ? void 0 : _SCENE_SETTINGS_find.words.map(aiStockStem)) != null ? _ref : [];
2725
+ found.push(_extends({
2726
+ query: place,
2727
+ orientation
2728
+ }, size, {
2729
+ strict: true,
2730
+ subject: place,
2731
+ object: aiStockStem(place),
2732
+ kin: [
2733
+ ...new Set(stems)
2734
+ ],
2735
+ lead: true
2736
+ }, workLead.length ? {
2737
+ refuseLead: [
2738
+ ...workLead
2739
+ ]
2740
+ } : {}, away.length ? {
2741
+ away
2742
+ } : {}, {
2743
+ worldless: true
2744
+ }));
2745
+ return found;
2746
+ };
2507
2747
  for (const raw of queries){
2748
+ if (raw.work) {
2749
+ searches.push(...workSearches());
2750
+ continue;
2751
+ }
2508
2752
  const query = clip(raw.query);
2509
2753
  const key = `${query}|${raw.people === true}`;
2510
2754
  if (!query || seen.has(key)) continue;
@@ -2540,6 +2784,17 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2540
2784
  }
2541
2785
  return searches;
2542
2786
  }
2787
+ /**
2788
+ * Whether a picture of a craft's site whose pictures are people at a moment
2789
+ * shows that work wherever a page would show the maker (AGL-3942): an about
2790
+ * picture, a story of the craft, an intro's cover. Not a record (which shows
2791
+ * its own moment, {@link aiStockShowsTheWork}), a portrait of the business's
2792
+ * people, nor a product; and only where the site's words name the work.
2793
+ */ export function aiStockShowsItsWork(slot, terms) {
2794
+ var _ref;
2795
+ var _terms_work;
2796
+ 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;
2797
+ }
2543
2798
  /**
2544
2799
  * Whether a slot is a portfolio's record of a craft whose work is people at a
2545
2800
  * moment (AGL-3856): a photographer's "Mountain Elopements" shows an
@@ -2622,7 +2877,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2622
2877
  * site's world 1, plus a quarter when it is among the first tags.
2623
2878
  */ export function aiStockRelevance(photo, judgement) {
2624
2879
  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;
2625
- var _judgement_roleScene, _judgement_roleRefuse;
2880
+ var _judgement_roleScene, _judgement_roleRefuse, _judgement_away;
2626
2881
  const hit = hitWords(photo);
2627
2882
  if (hit.rejected) return 0;
2628
2883
  if (aiStockSensitive(photo, judgement.named)) return 0;
@@ -2650,6 +2905,12 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2650
2905
  const read = new Set(stemList(((_photo_tags1 = photo.tags) != null ? _photo_tags1 : []).slice(0, ALT_TAGS_READ).join(' ')));
2651
2906
  if (((_judgement_roleRefuse1 = judgement.roleRefuse) != null ? _judgement_roleRefuse1 : []).some((stem)=>read.has(stem))) return 0;
2652
2907
  }
2908
+ // A Blue Ridge couple is never on a beach, however its later tags name a mountain (AGL-3942).
2909
+ if ((_judgement_away = judgement.away) == null ? void 0 : _judgement_away.length) {
2910
+ var _photo_tags2;
2911
+ const read = new Set(stemList(((_photo_tags2 = photo.tags) != null ? _photo_tags2 : []).slice(0, ALT_TAGS_READ).join(' ')));
2912
+ if (judgement.away.some((stem)=>read.has(stem))) return 0;
2913
+ }
2653
2914
  // A wedding day shows its people: a close-up of roses tagged "wedding" never fills it.
2654
2915
  const needs = (_judgement_needs = judgement.needs) != null ? _judgement_needs : [];
2655
2916
  if (needs.length && !needs.some((word)=>hit.all.has(word))) return 0;
@@ -2848,7 +3109,9 @@ function jobPhotos(key) {
2848
3109
  * Words a tag uses for how a photo looks or feels, never a thing it shows
2849
3110
  * (AGL-3856): "extravagant" of "wedding dress, bride, extravagant", "moody"
2850
3111
  * of "forest, human, photographer", a season or a people's name.
2851
- */ 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).
3112
+ */ 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).
3113
+ '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).
3114
+ 'together alone apart away abroad ahead nearby outside inside upstairs downstairs ' + // Adjectives a tag gives alone, never a thing: "students, multicultural" (AGL-3856).
2852
3115
  '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".
2853
3116
  'silhouette silhouettes cutout perspective bokeh closeup ' + // A holiday names when, never a thing: "tet market" is a market.
2854
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');
@@ -2865,7 +3128,7 @@ function jobPhotos(key) {
2865
3128
  * doing ("read", "lift", "fitness", "a workout"), a word for the photo itself
2866
3129
  * ("photo", "spot"). Left out, never said with an article: "A bookwork and a
2867
3130
  * read", "A kettlebell and fitness", "A board and training" said nothing.
2868
- */ 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');
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');
2869
3132
  /** 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');
2870
3133
  /** 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));
2871
3134
  /**
@@ -2977,6 +3240,8 @@ function jobPhotos(key) {
2977
3240
  if (/^(?:out|up|off|down|away|on|in)$/.test(list[list.length - 1])) return null;
2978
3241
  if (list.some((word)=>BRANDS.has(word))) return null;
2979
3242
  if (list.every((word)=>proper.has(word))) return list.map((word)=>`${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
3243
+ // Two people named side by side are both shown: "bride groom" is a bride and groom (AGL-3942).
3244
+ if (list.length === 2 && list.every((word)=>PEOPLE_NOUNS.has(word)) && list[0] !== list[1]) return `${articleFor(list[0])} ${list[0]} and ${list[1]}`;
2980
3245
  const last = list[list.length - 1];
2981
3246
  // An idea or a doing, never a thing: "law", "feedback", "customer satisfaction", "participation", "weightlifting".
2982
3247
  if (ABSTRACT_NOUNS.has(last) || ABSTRACT_NOUNS.has(aiStockStem(last)) || saysAnIdea(last)) return null;
@@ -2984,10 +3249,18 @@ function jobPhotos(key) {
2984
3249
  if (doing) return null;
2985
3250
  const phrase = list.join(' ');
2986
3251
  const plural = PLURAL_NOUNS.has(last) || aiStockStem(last) !== last && !/^(?:lens|canvas|atlas|gas|bus)$/.test(last);
2987
- if (plural || MASS_NOUNS.has(last) || STOPWORDS.has(list[0]) && !SAID_PEOPLE.has(list[0])) return phrase;
2988
- return `${articleFor(phrase)} ${phrase}`;
3252
+ if (plural || MASS_NOUNS.has(last) || STOPWORDS.has(list[0]) && !SAID_PEOPLE.has(list[0])) return spelled(phrase);
3253
+ return `${articleFor(phrase)} ${spelled(phrase)}`;
2989
3254
  }
2990
- /** "a" or "an" before a phrase. */ const articleFor = (phrase)=>/^(?:[aeio]|u(?!ni|s[eu]))/.test(phrase) ? 'an' : 'a';
3255
+ /** 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');
3256
+ /** 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(' ');
3257
+ /** "a" or "an" before a phrase, an initialism by how its first letter is said: "an SLR", "a DSLR". */ const articleFor = (phrase)=>{
3258
+ var _phrase_split_;
3259
+ const first = (_phrase_split_ = phrase.split(' ')[0]) != null ? _phrase_split_ : '';
3260
+ if (INITIALISMS.has(first)) return /^[aefhilmnorsx]/.test(first) ? 'an' : 'a';
3261
+ return /^(?:[aeio]|u(?!ni|s[eu]))/.test(phrase) ? 'an' : 'a';
3262
+ };
3263
+ /** 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;
2991
3264
  /** Whether two words name one thing, one inside the other: "law" and "lawyer", "cam" and "camera". */ function nearSynonyms(a, b) {
2992
3265
  const [short, long] = a.length <= b.length ? [
2993
3266
  a,
@@ -3122,7 +3395,9 @@ function jobPhotos(key) {
3122
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;
3123
3396
  phrase = phrase ? `${phrase} ${preposition} ${where}` : where;
3124
3397
  }
3125
- if (!phrase) return null;
3398
+ // Never a dangling article or preposition, whatever a tag left behind.
3399
+ phrase = phrase.replace(DANGLING_END, '').trim();
3400
+ if (!phrase || /^(?:a|an|the)$/i.test(phrase)) return null;
3126
3401
  const out = `${phrase.charAt(0).toUpperCase()}${phrase.slice(1)}`;
3127
3402
  return out.length > AI_STOCK_ALT_MAX ? out.slice(0, AI_STOCK_ALT_MAX).replace(/[\s,]+\S*$/, '') : out;
3128
3403
  }
@@ -3254,13 +3529,18 @@ const EXTENSIONS = {
3254
3529
  * post — so no slot searches by words another slot is held against. A site for
3255
3530
  * adults alone ({@link aiStockAdultsOnly}) is told so, whatever its world.
3256
3531
  */ export function aiStockSourceTerms(input) {
3257
- var _input_audience;
3532
+ var _input_audience, _input_audience1;
3258
3533
  const terms = sourceWorldTerms(input);
3259
3534
  // Where the site's own words set it: a photographer "in the Blue Ridge" for "mountain weddings" (AGL-3856).
3260
3535
  const setting = aiStockSetting([
3261
3536
  input.business,
3262
3537
  (_input_audience = input.audience) != null ? _input_audience : ''
3263
3538
  ].join(' . '));
3539
+ // A craft whose pictures are people at a moment shows the moments its site names (AGL-3942).
3540
+ const work = terms.scenes ? aiStockWorkScenes([
3541
+ input.business,
3542
+ (_input_audience1 = input.audience) != null ? _input_audience1 : ''
3543
+ ].join(' . '), setting) : [];
3264
3544
  return _extends({}, terms, aiStockAdultsOnly([
3265
3545
  input.audience,
3266
3546
  input.name,
@@ -3269,6 +3549,8 @@ const EXTENSIONS = {
3269
3549
  adults: true
3270
3550
  } : {}, setting ? {
3271
3551
  setting
3552
+ } : {}, work.length ? {
3553
+ work
3272
3554
  } : {});
3273
3555
  }
3274
3556
  /** What a source's photos are searched by: its world, as {@link aiStockSourceTerms} reads it. */ function sourceWorldTerms(input) {
@@ -3438,6 +3720,16 @@ const EXTENSIONS = {
3438
3720
  broad: [
3439
3721
  'artist studio',
3440
3722
  'creative workspace'
3723
+ ],
3724
+ // "Artist" names a bee and a tattooist as often as a painter (AGL-3923):
3725
+ // the beta.266 Mexicano hero's first pick was "artist bee, giant artist bee".
3726
+ shunLead: [
3727
+ 'bee',
3728
+ 'insect',
3729
+ 'insects',
3730
+ 'tattoo',
3731
+ 'makeup',
3732
+ 'cosmetics'
3441
3733
  ]
3442
3734
  },
3443
3735
  studio: {
@@ -3789,7 +4081,7 @@ const EXTENSIONS = {
3789
4081
  }
3790
4082
  /** What a kind of site's pictures are searched and judged by ({@link KIND_SUBJECTS}); `null` for a kind not listed. */ export function aiStockKindTerms(kind) {
3791
4083
  var _entry_broad;
3792
- var _entry_kin;
4084
+ var _entry_kin, _entry_shunLead;
3793
4085
  const entry = KIND_SUBJECTS[kind];
3794
4086
  if (!entry) return null;
3795
4087
  const base = aiStockSiteTerms(entry.subject);
@@ -3812,7 +4104,14 @@ const EXTENSIONS = {
3812
4104
  ...kin
3813
4105
  ])
3814
4106
  ]
3815
- });
4107
+ }, ((_entry_shunLead = entry.shunLead) == null ? void 0 : _entry_shunLead.length) ? {
4108
+ shunLead: [
4109
+ ...new Set([
4110
+ ...base.shunLead,
4111
+ ...entry.shunLead.map(aiStockStem)
4112
+ ])
4113
+ ]
4114
+ } : {});
3816
4115
  }
3817
4116
  /**
3818
4117
  * The kind of site each starter is the shape of (`AiSiteKind.starter`): what
@@ -3824,38 +4123,142 @@ const EXTENSIONS = {
3824
4123
  portfolio: 'portfolio',
3825
4124
  landing: 'landing'
3826
4125
  };
4126
+ const OFFICE = {
4127
+ kin: [
4128
+ 'office',
4129
+ 'workspace',
4130
+ 'desk',
4131
+ 'laptop',
4132
+ 'computer',
4133
+ 'meeting',
4134
+ 'business',
4135
+ 'work',
4136
+ 'workplace',
4137
+ 'team',
4138
+ 'notebook',
4139
+ 'coffee',
4140
+ 'teamwork',
4141
+ 'conference'
4142
+ ],
4143
+ broad: [
4144
+ 'office workspace',
4145
+ 'business meeting',
4146
+ 'workspace desk laptop'
4147
+ ]
4148
+ };
4149
+ const WORKSPACE = {
4150
+ kin: [
4151
+ 'workspace',
4152
+ 'desk',
4153
+ 'laptop',
4154
+ 'notebook',
4155
+ 'coffee',
4156
+ 'office',
4157
+ 'book',
4158
+ 'books',
4159
+ 'workplace'
4160
+ ],
4161
+ broad: [
4162
+ 'workspace desk laptop',
4163
+ 'notebook coffee desk'
4164
+ ]
4165
+ };
3827
4166
  /**
3828
- * The last searches a picture tries, for each starter (AGL-3923): plain,
3829
- * common photos of where such a site's work is done, which every library
3830
- * holds many of, held only to their own words. Never a person's face, a
3831
- * child, a product or a place a business could be mistaken for.
3832
- */ const STARTER_GENERIC = {
3833
- business: {
3834
- subject: 'office',
4167
+ * The last searches a picture tries, for each kind of site the guided start
4168
+ * offers (AGL-3923): plain, common photos of where that kind's work happens,
4169
+ * which every library holds many of, held to their own words. Never a face
4170
+ * as the lead, never a child, never a product nor a rival business: a
4171
+ * restaurant's are its tables and plates, never an office. A kind with no set
4172
+ * keeps a slot nothing fits without a photo (AGL-3831).
4173
+ */ const KIND_GENERIC = {
4174
+ business: OFFICE,
4175
+ trades: {
4176
+ kin: [
4177
+ 'tools',
4178
+ 'tool',
4179
+ 'toolbox',
4180
+ 'workbench',
4181
+ 'workshop',
4182
+ 'construction',
4183
+ 'repair',
4184
+ 'renovation',
4185
+ 'drill',
4186
+ 'hammer',
4187
+ 'wrench',
4188
+ 'roof',
4189
+ 'house',
4190
+ 'carpentry'
4191
+ ],
4192
+ broad: [
4193
+ 'tools workbench',
4194
+ 'home renovation',
4195
+ 'construction tools'
4196
+ ]
4197
+ },
4198
+ professional: {
3835
4199
  kin: [
3836
4200
  'office',
3837
- 'workspace',
3838
4201
  'desk',
3839
- 'laptop',
3840
- 'computer',
4202
+ 'documents',
4203
+ 'document',
3841
4204
  'meeting',
3842
- 'business',
3843
- 'work',
4205
+ 'conference',
4206
+ 'laptop',
3844
4207
  'workplace',
3845
- 'team',
4208
+ 'business',
3846
4209
  'notebook',
3847
- 'coffee',
3848
- 'teamwork',
3849
- 'conference'
4210
+ 'paperwork',
4211
+ 'consulting'
3850
4212
  ],
3851
4213
  broad: [
3852
- 'office workspace',
4214
+ 'office desk documents',
3853
4215
  'business meeting',
3854
- 'workspace desk laptop'
4216
+ 'conference room'
4217
+ ]
4218
+ },
4219
+ wellness: {
4220
+ kin: [
4221
+ 'spa',
4222
+ 'massage',
4223
+ 'wellness',
4224
+ 'relaxation',
4225
+ 'relax',
4226
+ 'towel',
4227
+ 'towels',
4228
+ 'candle',
4229
+ 'stones',
4230
+ 'therapy',
4231
+ 'treatment',
4232
+ 'aromatherapy'
4233
+ ],
4234
+ broad: [
4235
+ 'spa stones towel',
4236
+ 'massage room',
4237
+ 'wellness spa'
4238
+ ]
4239
+ },
4240
+ restaurant: {
4241
+ kin: [
4242
+ 'restaurant',
4243
+ 'table',
4244
+ 'dining',
4245
+ 'food',
4246
+ 'plate',
4247
+ 'dish',
4248
+ 'meal',
4249
+ 'kitchen',
4250
+ 'cafe',
4251
+ 'dinner',
4252
+ 'lunch',
4253
+ 'cuisine'
4254
+ ],
4255
+ broad: [
4256
+ 'restaurant interior',
4257
+ 'restaurant table food',
4258
+ 'food plate'
3855
4259
  ]
3856
4260
  },
3857
- 'physical-shop': {
3858
- subject: 'shop',
4261
+ store: {
3859
4262
  kin: [
3860
4263
  'shop',
3861
4264
  'store',
@@ -3874,7 +4277,6 @@ const EXTENSIONS = {
3874
4277
  ]
3875
4278
  },
3876
4279
  portfolio: {
3877
- subject: 'studio',
3878
4280
  kin: [
3879
4281
  'studio',
3880
4282
  'workspace',
@@ -3884,17 +4286,235 @@ const EXTENSIONS = {
3884
4286
  'creative',
3885
4287
  'sketch',
3886
4288
  'drawing',
3887
- 'notebook',
4289
+ 'paint',
4290
+ 'painting',
4291
+ 'brush',
4292
+ 'brushes',
4293
+ 'canvas',
4294
+ 'pencil',
4295
+ 'pencils',
3888
4296
  'paper'
3889
4297
  ],
3890
4298
  broad: [
4299
+ 'art supplies',
4300
+ 'creative workspace',
4301
+ 'art studio'
4302
+ ]
4303
+ },
4304
+ studio: {
4305
+ kin: [
4306
+ 'design',
4307
+ 'studio',
4308
+ 'workspace',
4309
+ 'desk',
4310
+ 'laptop',
4311
+ 'sketch',
4312
+ 'creative',
4313
+ 'office',
4314
+ 'drawing',
4315
+ 'pencil'
4316
+ ],
4317
+ broad: [
4318
+ 'design studio workspace',
3891
4319
  'creative workspace',
3892
- 'art studio',
3893
4320
  'workspace desk laptop'
3894
4321
  ]
3895
4322
  },
4323
+ photography: {
4324
+ kin: [
4325
+ 'camera',
4326
+ 'cameras',
4327
+ 'photography',
4328
+ 'lens',
4329
+ 'photographer',
4330
+ 'studio',
4331
+ 'film',
4332
+ 'tripod'
4333
+ ],
4334
+ broad: [
4335
+ 'camera lens',
4336
+ 'photography studio',
4337
+ 'vintage camera'
4338
+ ]
4339
+ },
4340
+ blog: {
4341
+ kin: [
4342
+ 'notebook',
4343
+ 'desk',
4344
+ 'writing',
4345
+ 'laptop',
4346
+ 'coffee',
4347
+ 'journal',
4348
+ 'book',
4349
+ 'books',
4350
+ 'workspace',
4351
+ 'pen',
4352
+ 'typewriter'
4353
+ ],
4354
+ broad: [
4355
+ 'writing desk notebook',
4356
+ 'laptop coffee desk'
4357
+ ]
4358
+ },
4359
+ events: {
4360
+ kin: [
4361
+ 'event',
4362
+ 'venue',
4363
+ 'wedding',
4364
+ 'reception',
4365
+ 'party',
4366
+ 'celebration',
4367
+ 'table',
4368
+ 'decoration',
4369
+ 'banquet',
4370
+ 'hall',
4371
+ 'flowers'
4372
+ ],
4373
+ broad: [
4374
+ 'event venue decoration',
4375
+ 'banquet table',
4376
+ 'party decoration'
4377
+ ]
4378
+ },
4379
+ fitness: {
4380
+ kin: [
4381
+ 'gym',
4382
+ 'fitness',
4383
+ 'weights',
4384
+ 'dumbbell',
4385
+ 'dumbbells',
4386
+ 'barbell',
4387
+ 'kettlebell',
4388
+ 'workout',
4389
+ 'exercise',
4390
+ 'training'
4391
+ ],
4392
+ broad: [
4393
+ 'gym equipment',
4394
+ 'dumbbells gym',
4395
+ 'fitness gym'
4396
+ ]
4397
+ },
4398
+ yoga: {
4399
+ kin: [
4400
+ 'yoga',
4401
+ 'mat',
4402
+ 'meditation',
4403
+ 'studio',
4404
+ 'relaxation',
4405
+ 'candle',
4406
+ 'zen',
4407
+ 'mindfulness'
4408
+ ],
4409
+ broad: [
4410
+ 'yoga mat',
4411
+ 'yoga studio',
4412
+ 'meditation candle'
4413
+ ]
4414
+ },
4415
+ beauty: {
4416
+ kin: [
4417
+ 'salon',
4418
+ 'beauty',
4419
+ 'hair',
4420
+ 'makeup',
4421
+ 'cosmetics',
4422
+ 'nails',
4423
+ 'manicure',
4424
+ 'spa',
4425
+ 'mirror',
4426
+ 'brush',
4427
+ 'brushes'
4428
+ ],
4429
+ broad: [
4430
+ 'beauty salon interior',
4431
+ 'makeup brushes cosmetics',
4432
+ 'hair salon chair'
4433
+ ]
4434
+ },
4435
+ realestate: {
4436
+ kin: [
4437
+ 'house',
4438
+ 'home',
4439
+ 'interior',
4440
+ 'living',
4441
+ 'room',
4442
+ 'property',
4443
+ 'architecture',
4444
+ 'apartment',
4445
+ 'kitchen',
4446
+ 'estate'
4447
+ ],
4448
+ broad: [
4449
+ 'house exterior',
4450
+ 'home interior living room',
4451
+ 'modern kitchen interior'
4452
+ ]
4453
+ },
4454
+ education: {
4455
+ kin: [
4456
+ 'classroom',
4457
+ 'books',
4458
+ 'book',
4459
+ 'library',
4460
+ 'school',
4461
+ 'desk',
4462
+ 'education',
4463
+ 'learning',
4464
+ 'study',
4465
+ 'studying',
4466
+ 'notebook',
4467
+ 'chalkboard',
4468
+ 'blackboard',
4469
+ 'lecture'
4470
+ ],
4471
+ broad: [
4472
+ 'library books',
4473
+ 'classroom',
4474
+ 'study desk books'
4475
+ ]
4476
+ },
4477
+ nonprofit: {
4478
+ kin: [
4479
+ 'volunteer',
4480
+ 'volunteers',
4481
+ 'volunteering',
4482
+ 'charity',
4483
+ 'donation',
4484
+ 'donations',
4485
+ 'community',
4486
+ 'helping',
4487
+ 'help',
4488
+ 'hands'
4489
+ ],
4490
+ broad: [
4491
+ 'volunteers charity',
4492
+ 'donation box',
4493
+ 'community volunteering'
4494
+ ]
4495
+ },
4496
+ music: {
4497
+ kin: [
4498
+ 'music',
4499
+ 'guitar',
4500
+ 'piano',
4501
+ 'instrument',
4502
+ 'instruments',
4503
+ 'concert',
4504
+ 'stage',
4505
+ 'microphone',
4506
+ 'drums',
4507
+ 'studio',
4508
+ 'vinyl'
4509
+ ],
4510
+ broad: [
4511
+ 'guitar instrument',
4512
+ 'concert stage lights',
4513
+ 'music studio microphone'
4514
+ ]
4515
+ },
4516
+ personal: WORKSPACE,
3896
4517
  landing: {
3897
- subject: 'office',
3898
4518
  kin: [
3899
4519
  'office',
3900
4520
  'workspace',
@@ -3914,13 +4534,47 @@ const EXTENSIONS = {
3914
4534
  'workspace desk laptop',
3915
4535
  'business meeting'
3916
4536
  ]
3917
- }
4537
+ },
4538
+ 'coming-soon': WORKSPACE
4539
+ };
4540
+ /** The kind of site whose common photos each starter falls back to. */ const STARTER_GENERIC_KIND = {
4541
+ business: 'business',
4542
+ 'physical-shop': 'store',
4543
+ portfolio: 'portfolio',
4544
+ landing: 'landing'
3918
4545
  };
4546
+ /** Stems no common photo leads with: a face, a portrait (AGL-3923). */ const FACE_STEMS = [
4547
+ 'face',
4548
+ 'portrait',
4549
+ 'selfie',
4550
+ 'headshot'
4551
+ ].map(aiStockStem);
3919
4552
  /** The starter a kind of site follows ({@link STARTER_KINDS}); a kind not listed follows the business starter. */ function starterOfKind(kind) {
3920
4553
  var _ref;
3921
4554
  var _aiSiteKind;
3922
4555
  const starter = (_ref = (_aiSiteKind = aiSiteKind(kind)) == null ? void 0 : _aiSiteKind.starter) != null ? _ref : 'business';
3923
- return STARTER_GENERIC[starter] ? starter : 'business';
4556
+ return STARTER_KINDS[starter] ? starter : 'business';
4557
+ }
4558
+ /** A kind of site's common photos as a world to search and judge by ({@link KIND_GENERIC}); `null` for a kind with no set. */ export function aiStockKindGenericTerms(kind) {
4559
+ const set = KIND_GENERIC[kind];
4560
+ if (!set) return null;
4561
+ const kin = [
4562
+ ...new Set(set.kin.map(aiStockStem))
4563
+ ];
4564
+ return {
4565
+ business: '',
4566
+ craft: '',
4567
+ domain: kin,
4568
+ broad: [
4569
+ ...set.broad
4570
+ ],
4571
+ making: [],
4572
+ named: [],
4573
+ rivals: [],
4574
+ core: kin,
4575
+ shun: [],
4576
+ shunLead: []
4577
+ };
3924
4578
  }
3925
4579
  /**
3926
4580
  * Whether a site's world is only its own words (AGL-3923): no craft this
@@ -3936,24 +4590,46 @@ const EXTENSIONS = {
3936
4590
  ];
3937
4591
  return terms.broad.length > 0 && terms.broad.length === own.length && terms.broad.every((query, index)=>query === own[index]);
3938
4592
  }
4593
+ /**
4594
+ * Whether a site's world agrees with the kind of site the person picked
4595
+ * (AGL-3923): it was read from the kind, or names one of the kind's own
4596
+ * words. A gym whose owner picked Business & services does not, and is never
4597
+ * given an office.
4598
+ */ function worldOfKind(terms, kind) {
4599
+ var _ref;
4600
+ var _aiStockKindGenericTerms;
4601
+ const own = aiStockKindTerms(kind);
4602
+ if (!own) return false;
4603
+ if (own.craft === terms.craft && own.broad.join('|') === terms.broad.join('|')) return true;
4604
+ const words = new Set([
4605
+ ...own.domain,
4606
+ ...(_ref = (_aiStockKindGenericTerms = aiStockKindGenericTerms(kind)) == null ? void 0 : _aiStockKindGenericTerms.domain) != null ? _ref : []
4607
+ ]);
4608
+ return terms.domain.some((stem)=>words.has(stem));
4609
+ }
3939
4610
  /**
3940
4611
  * What a picture is searched by once nothing narrower found it a photo
3941
- * (AGL-3923), widest last: the kind of site the person picked, the kind its
3942
- * starter is the shape of, and the starter's own common photos
3943
- * ({@link STARTER_GENERIC}). Only a site whose world is its own words alone
3944
- * ({@link aiStockWorldIsUnlisted}) widens: a craft this module lists, or a
3945
- * world read from the kind of site, keeps its slots to that world, and a slot
3946
- * nothing in it fits keeps no photo rather than an office (AGL-3831). A rung
3947
- * that would search what an earlier one did is left out.
4612
+ * (AGL-3923), widest last.
4613
+ *
4614
+ * - A site whose world is its own words alone ({@link aiStockWorldIsUnlisted})
4615
+ * widens to the kind of site the person picked, the kind its starter is the
4616
+ * shape of, and last the kind's own common photos ({@link KIND_GENERIC}),
4617
+ * else its starter's.
4618
+ * - Any other site widens only to its kind's own common photos, and only
4619
+ * where its world agrees with that kind ({@link worldOfKind}): a portfolio
4620
+ * read from its kind takes art supplies before no photo at all, and a
4621
+ * barbecue caterer whose kind says nothing of food never takes an office
4622
+ * (AGL-3831). A kind with no set keeps a slot nothing fits without a photo.
4623
+ *
4624
+ * A rung that would search what an earlier one did is left out.
3948
4625
  *
3949
4626
  * The beta.265 Kasvora Marketing Solutions start (job BK3r4I8bF1) typed its
3950
4627
  * business as "Freelencing site offer services": its world was the one word
3951
4628
  * "freelencing", which no library tags, so its cover hero searched it, found
3952
4629
  * nothing, and shipped a flat dark band with no photo.
3953
4630
  */ export function aiStockWiderTerms(terms, kind) {
3954
- var _STARTER_KINDS_starter;
4631
+ var _STARTER_KINDS_starter, _aiStockKindGenericTerms, _STARTER_GENERIC_KIND_starter;
3955
4632
  const rungs = [];
3956
- if (!aiStockWorldIsUnlisted(terms)) return rungs;
3957
4633
  const seen = new Set([
3958
4634
  JSON.stringify([
3959
4635
  terms.craft,
@@ -3975,25 +4651,14 @@ const EXTENSIONS = {
3975
4651
  terms: found
3976
4652
  });
3977
4653
  };
4654
+ if (!aiStockWorldIsUnlisted(terms)) {
4655
+ if (kind && worldOfKind(terms, kind)) add('generic', aiStockKindGenericTerms(kind));
4656
+ return rungs;
4657
+ }
3978
4658
  const starter = starterOfKind(kind);
3979
4659
  if (kind) add('kind', aiStockKindTerms(kind));
3980
4660
  add('starter', aiStockKindTerms((_STARTER_KINDS_starter = STARTER_KINDS[starter]) != null ? _STARTER_KINDS_starter : 'business'));
3981
- const generic = STARTER_GENERIC[starter];
3982
- const kin = generic.kin.map(aiStockStem);
3983
- add('generic', {
3984
- business: '',
3985
- craft: '',
3986
- domain: kin,
3987
- broad: [
3988
- ...generic.broad
3989
- ],
3990
- making: [],
3991
- named: [],
3992
- rivals: [],
3993
- core: kin,
3994
- shun: [],
3995
- shunLead: []
3996
- });
4661
+ add('generic', (_aiStockKindGenericTerms = aiStockKindGenericTerms(kind)) != null ? _aiStockKindGenericTerms : aiStockKindGenericTerms((_STARTER_GENERIC_KIND_starter = STARTER_GENERIC_KIND[starter]) != null ? _STARTER_GENERIC_KIND_starter : 'business'));
3997
4662
  return rungs;
3998
4663
  }
3999
4664
  /**
@@ -4050,6 +4715,7 @@ const EXTENSIONS = {
4050
4715
  const terms = aiStockSourceTerms(input);
4051
4716
  // Where a picture's own world finds nothing, the wider worlds it tries (AGL-3923).
4052
4717
  const wider = aiStockWiderTerms(terms, (_input_kind = input.kind) != null ? _input_kind : '');
4718
+ const unlisted = aiStockWorldIsUnlisted(terms);
4053
4719
  const proper = aiStockProperWords(input.business);
4054
4720
  return async (slots)=>{
4055
4721
  var _deps_budgetMs, _input_avoid;
@@ -4094,6 +4760,7 @@ const EXTENSIONS = {
4094
4760
  delete request.allow;
4095
4761
  delete request.refuseLead;
4096
4762
  delete request.worldless;
4763
+ delete request.away;
4097
4764
  const key = JSON.stringify([
4098
4765
  request.query,
4099
4766
  request.orientation,
@@ -4263,7 +4930,7 @@ const EXTENSIONS = {
4263
4930
  * (AGL-3923). What lets a sensitive topic in is always the site's own words.
4264
4931
  */ const judgementOf = (slot, request, subject, world)=>{
4265
4932
  var _request_subject, _request_shun;
4266
- var _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs, _request_refuseLead;
4933
+ var _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs, _request_refuseLead, _request_away;
4267
4934
  return _extends({
4268
4935
  subject: (_request_subject = request.subject) != null ? _request_subject : request.broad ? '' : subject,
4269
4936
  domain: world.domain,
@@ -4308,6 +4975,8 @@ const EXTENSIONS = {
4308
4975
  needs: request.needs
4309
4976
  } : {}, ((_request_refuseLead = request.refuseLead) == null ? void 0 : _request_refuseLead.length) ? {
4310
4977
  refuseLead: request.refuseLead
4978
+ } : {}, ((_request_away = request.away) == null ? void 0 : _request_away.length) ? {
4979
+ away: request.away
4311
4980
  } : {}, request.worldless ? {
4312
4981
  domain: [],
4313
4982
  rivals: []
@@ -4318,8 +4987,9 @@ const EXTENSIONS = {
4318
4987
  * filled from (AGL-3660): its broad searches, any hit naming the craft
4319
4988
  * itself. Never a desk, a laptop or a living room for a soap shop.
4320
4989
  */ const craftSearches = (slot, world = terms)=>{
4321
- // A portfolio's record of the work is never filled by the craft's own photos (AGL-3856).
4322
- if (!world.core.length || aiStockShowsTheWork(slot, world)) return [];
4990
+ // A portfolio's record of the work is never filled by the craft's own
4991
+ // photos (AGL-3856), nor is any picture of a site that shows its work (AGL-3942).
4992
+ if (!world.core.length || aiStockShowsTheWork(slot, world) || aiStockShowsItsWork(slot, world)) return [];
4323
4993
  const orientation = aiStockOrientation(slot.aspect);
4324
4994
  const size = slot.role === 'hero' ? {
4325
4995
  minWidth: 1600
@@ -4348,6 +5018,7 @@ const EXTENSIONS = {
4348
5018
  * judged by the world it was asked for. A product, a portrait of the
4349
5019
  * business's people and a record's cover keep their own fallbacks.
4350
5020
  */ const widerSearches = (slot, subject, sectionName)=>{
5021
+ var _wider_find;
4351
5022
  if (!wider.length || slot.product || slot.people || slot.record) return [];
4352
5023
  const part = aiStockSlotPart(slot, sectionName);
4353
5024
  const asked = [];
@@ -4364,15 +5035,21 @@ const EXTENSIONS = {
4364
5035
  ...craftSearches(slot, world)
4365
5036
  ]);
4366
5037
  }
4367
- if (subject) {
4368
- const orientation = aiStockOrientation(slot.aspect);
4369
- const size = slot.role === 'hero' ? {
4370
- minWidth: 1600
4371
- } : orientation === 'vertical' ? {
4372
- minHeight: 900
4373
- } : {
4374
- minWidth: 900
4375
- };
5038
+ const orientation = aiStockOrientation(slot.aspect);
5039
+ const size = slot.role === 'hero' ? {
5040
+ minWidth: 1600
5041
+ } : orientation === 'vertical' ? {
5042
+ minHeight: 900
5043
+ } : {
5044
+ minWidth: 900
5045
+ };
5046
+ // The slot's own words, only where the site's world is its own words
5047
+ // alone, held to the kind's common world (AGL-3923), never to no world:
5048
+ // the beta.267 Brightvale closing band's "ready talk marketing" was
5049
+ // answered with a tin-can telephone, and its pets-and-owners answers
5050
+ // name no office either.
5051
+ const common = (_wider_find = wider.find((rung)=>rung.rung === 'generic')) == null ? void 0 : _wider_find.terms;
5052
+ if (subject && unlisted && common) {
4376
5053
  const object = aiStockObjectWord(subject);
4377
5054
  const own = [
4378
5055
  ...new Set([
@@ -4380,16 +5057,30 @@ const EXTENSIONS = {
4380
5057
  object
4381
5058
  ].map(clip).filter(Boolean))
4382
5059
  ];
4383
- ask(terms, own.map((query)=>_extends({
5060
+ ask(common, own.map((query)=>_extends({
4384
5061
  query,
4385
5062
  orientation
4386
5063
  }, size, {
4387
5064
  subject,
4388
- strict: true,
4389
- worldless: true
5065
+ strict: true
4390
5066
  })));
4391
5067
  }
4392
- for (const { rung, terms: world } of wider)if (rung === 'generic') ask(world, craftSearches(slot, world));
5068
+ // The kind's common photos: OF its world among their first tags, never
5069
+ // led by a face, never of a child (AGL-3923).
5070
+ for (const { rung, terms: world } of wider)if (rung === 'generic') ask(world, world.broad.map((query)=>_extends({
5071
+ query: clip(query),
5072
+ orientation
5073
+ }, size, {
5074
+ broad: true,
5075
+ category: world.core
5076
+ }, slot.role === 'hero' ? {
5077
+ first: true
5078
+ } : {
5079
+ leadWorld: true
5080
+ }, {
5081
+ refuse: CHILD_STEMS,
5082
+ refuseLead: FACE_STEMS
5083
+ })));
4393
5084
  return asked;
4394
5085
  };
4395
5086
  // 1. Choose, slot by slot, so the page's choices never collide: each