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

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 (36) hide show
  1. package/package.json +17 -17
  2. package/src/lib/jobs/ai-business-profile-prefill.js +6 -3
  3. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
  4. package/src/lib/jobs/ai-job-site-datasets.js +5 -3
  5. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  6. package/src/lib/jobs/ai-job-text-step.js +2 -1
  7. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  8. package/src/lib/jobs/ai-layout-stock-photos.d.ts +97 -4
  9. package/src/lib/jobs/ai-layout-stock-photos.js +635 -154
  10. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  11. package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.d.ts +120 -0
  12. package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js +410 -0
  13. package/src/lib/jobs/fixtures/ai-site-agl4092-datasets.js.map +1 -0
  14. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.d.ts +26 -0
  15. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js +714 -0
  16. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-recordings.js.map +1 -0
  17. package/src/lib/layout-language/ai-layout-pictures.d.ts +9 -1
  18. package/src/lib/layout-language/ai-layout-pictures.js +14 -5
  19. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  20. package/src/lib/model/ai-plan-said-sections.d.ts +12 -3
  21. package/src/lib/model/ai-plan-said-sections.js +22 -5
  22. package/src/lib/model/ai-plan-said-sections.js.map +1 -1
  23. package/src/lib/model/ai-site-context.d.ts +6 -2
  24. package/src/lib/model/ai-site-context.js +8 -2
  25. package/src/lib/model/ai-site-context.js.map +1 -1
  26. package/src/lib/runtime/ai-dataset-generation.d.ts +26 -5
  27. package/src/lib/runtime/ai-dataset-generation.js +110 -9
  28. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  29. package/src/lib/runtime/ai-doctrine-validators.js +2 -1
  30. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  31. package/src/lib/runtime/ai-doctrine.d.ts +5 -4
  32. package/src/lib/runtime/ai-doctrine.js +2 -1
  33. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  34. package/src/lib/server/assist-build.d.ts +15 -1
  35. package/src/lib/server/assist-build.js +29 -3
  36. package/src/lib/server/assist-build.js.map +1 -1
@@ -1,4 +1,5 @@
1
1
  import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
2
3
  /**
3
4
  * @license
4
5
  * Copyright 2026 Aglyn LLC
@@ -154,6 +155,22 @@ import { aiStockBusinessNoun } from "./ai-stock-business-nouns.js";
154
155
  * searched only with its world; a record's cover is searched in its world
155
156
  * first; and a listed world widens only where the library knew nothing of
156
157
  * it, never an outdoor or a trade world to an office.
158
+ * 11. **The thing, the place and the trade** (the 2026-10-28 live check,
159
+ * AGL-4092: a bare foot tagged "body part" for a parts shop's "Engine and
160
+ * air", a transmission chain for its "Brake Pad Set", a cat tagged "hungry"
161
+ * for a taco trailer's "Come hungry", a European alley for "Around town", a
162
+ * taco bowl salad for "Race day", and a man posing with a street bike tagged
163
+ * "susuki" in a motocross team's band of pictures). A picture of a thing is
164
+ * held to the business's own world, never any word of its type; a feeling,
165
+ * a time or a way of being somewhere is never a thing, and "around town" is
166
+ * the business's customers out there; a broad search's photo is OF the
167
+ * world among its first tags, never a "taco bowl"; an animal, a toy or a
168
+ * bare body part never leads a photo naming nothing of the world; a trade's
169
+ * things are held to the world it serves, never to "part"; a product takes
170
+ * a shoot no other product shows; a motocross business's world is
171
+ * off-road; and a maker is named one letter off ("susuki") and by its
172
+ * gear's and tires' names. Still by the tags: a mark only the pixels show
173
+ * is placed.
157
174
  *
158
175
  * A slot the library could not fill because it never answered — down, or
159
176
  * out of time before its first answer — or because the site's library
@@ -245,6 +262,43 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
245
262
  * world ("dirt bike parts"), never alone: "parts" answered a canoe on a
246
263
  * river, tagged "part", for a dirt bike shop's "Parts for the ranch".
247
264
  */ const WORLD_OBJECTS = words('part parts gear gears machine machines equipment spare spares accessory accessories');
265
+ /**
266
+ * Words of a picture's caption that are never the thing it shows (AGL-4092):
267
+ * a feeling, a time, a way of being somewhere. "Come hungry" read its object
268
+ * as "hungry" and took a cat tagged "hungry"; "Around town" read "town" and
269
+ * took a European alley. A caption left with no thing is a picture of the
270
+ * site's own world. A personal record is a feat, never a thing: "Deadlift PR
271
+ * night" is of the deadlift.
272
+ */ const NON_THINGS = words('come hungry happy ready welcome day days night nights time times today tonight weekend weekends season seasons ' + 'around town hours hour way moment moments memories pr pb');
273
+ /**
274
+ * Where a business is, said as a place to go (AGL-4092): "Around town", "At
275
+ * events". Never a street of houses: a picture of it shows the business's
276
+ * customers out where it is, or its stand.
277
+ */ const PLACE_PHRASES = /\b(?:around town|in town|downtown|on the road|out and about|at (?:the )?events|neighbou?rhoods?)\b/i;
278
+ /** What a picture of a business out where it is shows, as stems: its people, a line, a street stand (AGL-4092). */ const PLACE_NEEDS = [
279
+ 'people',
280
+ 'crowd',
281
+ 'customer',
282
+ 'customers',
283
+ 'queue',
284
+ 'line',
285
+ 'street food',
286
+ 'food truck',
287
+ 'market',
288
+ 'festival'
289
+ ];
290
+ /** A street that is someone else's, never where a business is found (AGL-4092): a European alley for "Around town". */ const FOREIGN_STREET = [
291
+ 'alley',
292
+ 'cobblestone',
293
+ 'village',
294
+ 'old town',
295
+ 'historic',
296
+ 'castle',
297
+ 'cathedral',
298
+ 'europe',
299
+ 'landscape',
300
+ 'architecture'
301
+ ];
248
302
  /**
249
303
  * The verbs a business type's clause opens with when it says what a customer
250
304
  * may ask of it (AGL-4065): "takes quote requests", "books repair
@@ -606,7 +660,26 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
606
660
  * named only in the phrase that makes it the maker's. Read from the tags and
607
661
  * the address, so a logo no tag names still shows: the live check is where
608
662
  * that is seen.
609
- */ const MAKERS = [
663
+ */ /** The one-word makers of riding gear and tires (AGL-4092), also never said in a photo's description ({@link BRANDS}). */ const GEAR_MAKER_WORDS = [
664
+ 'alpinestars',
665
+ 'oneal',
666
+ 'leatt',
667
+ 'arai',
668
+ 'shoei',
669
+ 'agv',
670
+ 'oakley',
671
+ 'renthal',
672
+ 'fmf',
673
+ 'acerbis',
674
+ 'dunlop',
675
+ 'michelin',
676
+ 'pirelli',
677
+ 'bridgestone',
678
+ 'metzeler',
679
+ 'maxxis',
680
+ 'kenda'
681
+ ];
682
+ const MAKERS = [
610
683
  'porsche',
611
684
  'ferrari',
612
685
  'lamborghini',
@@ -656,9 +729,58 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
656
729
  'samsung',
657
730
  'starbucks',
658
731
  'mcdonalds',
659
- 'mcdonald'
732
+ 'mcdonald',
733
+ // A rider's gear and a bike's tires carry their makers' marks as often as
734
+ // the bike does (AGL-4092). A maker whose name is a common word ("fox",
735
+ // "shift", "thor", "fly", "bell") is named only in its phrase.
736
+ ...GEAR_MAKER_WORDS,
737
+ 'fox racing',
738
+ 'shift mx',
739
+ 'shift racing',
740
+ 'thor mx',
741
+ 'fly racing',
742
+ 'troy lee',
743
+ 'bell helmets',
744
+ 'pro taper'
660
745
  ];
661
746
  /** The makers as stems and stem phrases, as a hit's words are read. */ const MAKER_STEMS = new Set(MAKERS.map((maker)=>stemList(maker).join(' ')));
747
+ /**
748
+ * The one-word makers long enough that a tag one letter off still names them
749
+ * (AGL-4092): Pixabay's "susuki" is a Suzuki. Never a common word one letter
750
+ * off a maker: "barley" is not a Harley, nor "rental" a Renthal.
751
+ */ const FUZZY_MAKERS = [
752
+ ...MAKER_STEMS
753
+ ].filter((maker)=>!maker.includes(' ') && maker.length >= 6);
754
+ /** Words one letter off a maker that are words of their own (AGL-4092). */ const MAKER_LOOKALIKES = words('barley parley harlem rental samsun ducat phone suzuka');
755
+ /** Whether two words are one edit apart: a letter changed, added, dropped, or two side by side swapped. */ function oneEditApart(a, b) {
756
+ if (a === b) return false;
757
+ if (Math.abs(a.length - b.length) > 1) return false;
758
+ if (a.length === b.length) {
759
+ const differ = [
760
+ ...a
761
+ ].flatMap((letter, index)=>letter === b[index] ? [] : [
762
+ index
763
+ ]);
764
+ if (differ.length === 1) return true;
765
+ const [first, second] = differ;
766
+ return differ.length === 2 && second === first + 1 && a[first] === b[second] && a[second] === b[first];
767
+ }
768
+ const [short, long] = a.length < b.length ? [
769
+ a,
770
+ b
771
+ ] : [
772
+ b,
773
+ a
774
+ ];
775
+ let index = 0;
776
+ while(index < short.length && short[index] === long[index])index += 1;
777
+ return short.slice(index) === long.slice(index + 1);
778
+ }
779
+ /** The maker a word names one letter off, else `null` ({@link FUZZY_MAKERS}). */ function fuzzyMaker(word) {
780
+ var _FUZZY_MAKERS_find;
781
+ if (word.length < 5 || MAKER_LOOKALIKES.has(word) || MAKER_STEMS.has(word)) return null;
782
+ return (_FUZZY_MAKERS_find = FUZZY_MAKERS.find((maker)=>oneEditApart(word, maker))) != null ? _FUZZY_MAKERS_find : null;
783
+ }
662
784
  /**
663
785
  * A phrase's words and each pair of them in turn, as stems (AGL-4065): "dirt
664
786
  * bike" names the motocross world where "bike" alone names a bicycle's too.
@@ -681,11 +803,12 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
681
803
  /** The words a photo names a maker by: its tags, those of what it suits left out, its description and its page address. */ function aiStockMakerTerms(photo) {
682
804
  var _photo_tags, _photo_alt;
683
805
  const tags = ((_photo_tags = photo.tags) != null ? _photo_tags : []).filter((tag)=>!SUITS.test(tag));
806
+ // "O'Neal" is one name, never "neal" (AGL-4092).
684
807
  return termsOf([
685
808
  ...tags,
686
809
  (_photo_alt = photo.alt) != null ? _photo_alt : '',
687
810
  pageSlug(photo.pageUrl)
688
- ]);
811
+ ].map((text)=>text.replace(/['’]/g, '')));
689
812
  }
690
813
  /**
691
814
  * Hands are never a picture's subject (AGL-3660): "hand-poured", "handmade"
@@ -937,6 +1060,149 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
937
1060
  'dirt bike tire',
938
1061
  'motocross tire'
939
1062
  ]
1063
+ },
1064
+ {
1065
+ // A brake pad is a brake's (AGL-4092): "Brake Pad Set" found nothing by its
1066
+ // pad, then searched "part" alone and took a transmission chain.
1067
+ objects: [
1068
+ 'brake',
1069
+ 'pad',
1070
+ 'rotor',
1071
+ 'caliper'
1072
+ ],
1073
+ within: [
1074
+ 'motocross',
1075
+ 'motorcycle',
1076
+ 'motorbike',
1077
+ 'dirtbike',
1078
+ 'dirt bike',
1079
+ 'enduro',
1080
+ 'atv'
1081
+ ],
1082
+ kin: [
1083
+ 'brake',
1084
+ 'brakes',
1085
+ 'brake pad',
1086
+ 'brake pads',
1087
+ 'brake disc',
1088
+ 'brake caliper',
1089
+ 'disc brake',
1090
+ 'brake rotor'
1091
+ ],
1092
+ queries: [
1093
+ 'motorcycle brake disc',
1094
+ 'dirt bike brake'
1095
+ ]
1096
+ },
1097
+ {
1098
+ // An engine's air is its filter's (AGL-4092): "Engine and air" tiled a
1099
+ // collection with a bare foot tagged "body part".
1100
+ objects: [
1101
+ 'engine',
1102
+ 'filter',
1103
+ 'air',
1104
+ 'carburetor'
1105
+ ],
1106
+ within: [
1107
+ 'motocross',
1108
+ 'motorcycle',
1109
+ 'motorbike',
1110
+ 'dirtbike',
1111
+ 'dirt bike',
1112
+ 'enduro',
1113
+ 'atv'
1114
+ ],
1115
+ kin: [
1116
+ 'engine',
1117
+ 'air filter',
1118
+ 'motorcycle engine',
1119
+ 'dirt bike engine',
1120
+ 'carburetor'
1121
+ ],
1122
+ queries: [
1123
+ 'dirt bike engine',
1124
+ 'motorcycle engine'
1125
+ ]
1126
+ },
1127
+ {
1128
+ // A taco trailer is a food truck, never a tank truck at a rest stop (AGL-4092).
1129
+ objects: [
1130
+ 'trailer',
1131
+ 'truck'
1132
+ ],
1133
+ within: [
1134
+ 'taco',
1135
+ 'taqueria',
1136
+ 'burrito',
1137
+ 'mexican food',
1138
+ 'street food',
1139
+ 'food truck'
1140
+ ],
1141
+ kin: [
1142
+ 'food truck',
1143
+ 'food trailer',
1144
+ 'taco truck',
1145
+ 'taco trailer',
1146
+ 'street food truck'
1147
+ ],
1148
+ queries: [
1149
+ 'taco truck',
1150
+ 'food truck'
1151
+ ]
1152
+ },
1153
+ {
1154
+ // A taco trailer's chips are tortilla chips, never crisps or a cookie's (AGL-4092).
1155
+ objects: [
1156
+ 'chip'
1157
+ ],
1158
+ within: [
1159
+ 'taco',
1160
+ 'taqueria',
1161
+ 'burrito',
1162
+ 'mexican food',
1163
+ 'street food',
1164
+ 'food truck'
1165
+ ],
1166
+ kin: [
1167
+ 'tortilla chip',
1168
+ 'tortilla chips',
1169
+ 'chips and salsa',
1170
+ 'chips salsa',
1171
+ 'nachos',
1172
+ 'nacho'
1173
+ ],
1174
+ queries: [
1175
+ 'tortilla chips salsa',
1176
+ 'nachos'
1177
+ ]
1178
+ },
1179
+ {
1180
+ // A fountain drink is a soda, never a well's fountain (AGL-4092).
1181
+ objects: [
1182
+ 'drink',
1183
+ 'soda'
1184
+ ],
1185
+ within: [
1186
+ 'taco',
1187
+ 'taqueria',
1188
+ 'burrito',
1189
+ 'mexican food',
1190
+ 'street food',
1191
+ 'food truck'
1192
+ ],
1193
+ // Never "fountain drink" itself: a well's page address says it word for word.
1194
+ kin: [
1195
+ 'soda',
1196
+ 'soft drink',
1197
+ 'iced tea',
1198
+ 'agua fresca',
1199
+ 'lemonade',
1200
+ 'soda cup'
1201
+ ],
1202
+ queries: [
1203
+ 'soda cup',
1204
+ 'agua fresca'
1205
+ ]
940
1206
  }
941
1207
  ];
942
1208
  /** An object's kin where the site or the subject is of its world, else `null`. */ function objectKin(object, world) {
@@ -1834,7 +2100,28 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
1834
2100
  'highway',
1835
2101
  'asphalt',
1836
2102
  'traffic'
1837
- ]
2103
+ ],
2104
+ // A motocross team's "Gate drop" took a man posing with a street bike, tagged "man, model, motorcycle" (AGL-4092).
2105
+ wider: {
2106
+ words: [
2107
+ 'motorcycle',
2108
+ 'motorcycles',
2109
+ 'motorbike',
2110
+ 'motorbikes',
2111
+ 'moto'
2112
+ ],
2113
+ shunLead: [
2114
+ 'model',
2115
+ 'urban',
2116
+ 'street',
2117
+ 'road bike',
2118
+ 'road racing',
2119
+ 'sportbike',
2120
+ 'superbike',
2121
+ 'studio',
2122
+ 'posing'
2123
+ ]
2124
+ }
1838
2125
  },
1839
2126
  {
1840
2127
  // A parts shop of the motocross world: its parts, tires and riding gear,
@@ -1936,6 +2223,11 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
1936
2223
  'taco truck cooking',
1937
2224
  'tacos on the griddle'
1938
2225
  ],
2226
+ out: [
2227
+ 'food truck crowd',
2228
+ 'street food customers',
2229
+ 'taco stand customers'
2230
+ ],
1939
2231
  shunLead: [
1940
2232
  'buffet',
1941
2233
  'hotel',
@@ -2208,11 +2500,31 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
2208
2500
  ...lead
2209
2501
  ].some((term)=>term === stem || term.includes(' ') && term.split(' ').includes(stem) && ownTerms.has(term))) lead.add(stem);
2210
2502
  }
2503
+ // A wider world's names lead only where the business's own words say them
2504
+ // (AGL-4092): a motocross team's world is off-road, never "motorcycle" alone.
2505
+ for (const group of leading){
2506
+ if (!group.wider) continue;
2507
+ const wider = group.wider.words.map(groupTerm);
2508
+ if (wider.some((term)=>ownTerms.has(term))) continue;
2509
+ for (const term of wider)lead.delete(term);
2510
+ for (const word of group.wider.shunLead)shunLead.add(groupTerm(word));
2511
+ }
2211
2512
  // A trade's world, named by the world it serves: "dirt bike" for a dirt bike parts shop.
2212
2513
  const served = (craftGroup == null ? void 0 : craftGroup.of) ? groups.filter((group)=>group !== craftGroup).flatMap((group)=>group.match).filter((word)=>typeTerms.includes(groupTerm(word))).sort((a, b)=>positionOf(groupTerm(a)) - positionOf(groupTerm(b)))[0] : undefined;
2213
2514
  if (!broad.length) broad.push(business, craft);
2214
2515
  // A part or gear names no world on its own: a dirt bike shop's part, never a canoe's (AGL-4065).
2215
2516
  const ofAWorld = (term)=>term.length > 1 && !term.split(' ').every((word)=>WORLD_OBJECTS.has(word));
2517
+ // A trade's things are held to the world it serves, never to its own word (AGL-4092).
2518
+ const tradeNames = new Set(((craftGroup == null ? void 0 : craftGroup.of) ? craftGroup.match : []).map(groupTerm));
2519
+ const held = (craftGroup == null ? void 0 : craftGroup.of) ? [
2520
+ ...core
2521
+ ].filter((term)=>ofAWorld(term) && !tradeNames.has(term)) : [];
2522
+ const out = [
2523
+ ...new Set(leading.flatMap((group)=>{
2524
+ var _group_out;
2525
+ return (_group_out = group.out) != null ? _group_out : [];
2526
+ }))
2527
+ ];
2216
2528
  return _extends({
2217
2529
  business,
2218
2530
  craft,
@@ -2226,6 +2538,10 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
2226
2538
  ].filter((stem)=>stem.length > 1)
2227
2539
  }, served ? {
2228
2540
  leadWord: served
2541
+ } : {}, held.length ? {
2542
+ held
2543
+ } : {}, out.length ? {
2544
+ out
2229
2545
  } : {}, {
2230
2546
  broad: [
2231
2547
  ...new Set(broad.map((query)=>query.trim()).filter(Boolean))
@@ -2312,10 +2628,12 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
2312
2628
  * The thing a subject shows, one singular word: its last word that is not
2313
2629
  * a set or a wrapper of it — "bowl" from "nesting bowl set", "candle" from
2314
2630
  * "candle gift set", "vase" from "bud vase". Its last word when every word
2315
- * is one ("gift set").
2631
+ * is one ("gift set"). A feeling, a time or a way of being somewhere is
2632
+ * never one ({@link NON_THINGS}, AGL-4092): "Come hungry" and "Around town"
2633
+ * show no thing, and "Race day" is of the race.
2316
2634
  */ export function aiStockObjectWord(subject) {
2317
2635
  var _ref, _reverse_find;
2318
- const list = subject.split(/\s+/).filter(Boolean);
2636
+ const list = subject.split(/\s+/).filter((word)=>word && !NON_THINGS.has(word));
2319
2637
  const object = (_ref = (_reverse_find = [
2320
2638
  ...list
2321
2639
  ].reverse().find((word)=>!WRAPPERS.has(word))) != null ? _reverse_find : list[list.length - 1]) != null ? _ref : '';
@@ -3091,7 +3409,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3091
3409
  * product names it. A size never names the product: "Bath Gift Set Starter
3092
3410
  * Size" is a bath gift set.
3093
3411
  */ export function aiStockProductSearches(slot, craft, domain = [], fallback = {}) {
3094
- var _ref, _subjects_, _fallback_core, _ref1, _fallback_broad, _fallback_broad1;
3412
+ var _ref, _subjects_, _fallback_held, _fallback_core, _fallback_leadWord, _ref1, _fallback_broad, _fallback_broad1;
3095
3413
  var _slot_product;
3096
3414
  const orientation = aiStockOrientation(slot.aspect);
3097
3415
  const size = orientation === 'vertical' ? {
@@ -3117,23 +3435,31 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3117
3435
  // A product of the shop's world beside its category is held to either:
3118
3436
  // a soap shop's body lotion names "lotion", never "soap".
3119
3437
  const ownWorld = stemList(own).filter((stem)=>world.has(stem) && !WRAPPERS.has(stem) && !craftStems.includes(stem));
3120
- const category = craft && ownWorld.length ? [
3438
+ // A trade's product is held to the world it serves, never to its craft
3439
+ // (AGL-4092): a parts shop's "part" named a transmission's "mechanical part".
3440
+ const served = [
3441
+ ...new Set(((_fallback_held = fallback.held) != null ? _fallback_held : []).map(stemPhrase).filter(Boolean))
3442
+ ];
3443
+ const trade = served.length > 0;
3444
+ const category = trade ? served : craft && ownWorld.length ? [
3121
3445
  ...craftStems,
3122
3446
  ...ownWorld
3123
3447
  ] : [];
3124
- const core = [
3448
+ const core = trade ? served : [
3125
3449
  ...new Set([
3126
3450
  ...craftStems,
3127
3451
  ...(_fallback_core = fallback.core) != null ? _fallback_core : []
3128
3452
  ])
3129
3453
  ];
3454
+ // A trade's product is named by the world it serves: "dirt bike skid plate", never "part skid plate".
3455
+ const prefix = trade ? (_fallback_leadWord = fallback.leadWord) != null ? _fallback_leadWord : '' : craft;
3130
3456
  const shun = ((_ref1 = kin == null ? void 0 : kin.shun) != null ? _ref1 : []).map(aiStockStem);
3131
3457
  const queries = [];
3132
3458
  for (const subject of subjects){
3133
3459
  var _ref2;
3134
3460
  const stems = stemList(subject);
3135
3461
  const qualifier = (_ref2 = kin == null ? void 0 : kin.qualifier) != null ? _ref2 : '';
3136
- const query = kin ? !qualifier || stems.includes(aiStockStem(qualifier)) ? subject : kin.after ? `${subject} ${qualifier}` : `${qualifier} ${subject}` : craft && !stems.some((stem)=>world.has(stem)) ? `${craft} ${subject}` : subject;
3462
+ const query = kin ? !qualifier || stems.includes(aiStockStem(qualifier)) ? subject : kin.after ? `${subject} ${qualifier}` : `${qualifier} ${subject}` : prefix && !stems.some((stem)=>world.has(stem)) ? `${prefix} ${subject}` : subject;
3137
3463
  queries.push({
3138
3464
  query,
3139
3465
  subject,
@@ -3157,7 +3483,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3157
3483
  object,
3158
3484
  lead: true
3159
3485
  });
3160
- else if (craft) queries.push({
3486
+ else if (craft && !trade) queries.push({
3161
3487
  query: craft,
3162
3488
  subject: own || craft,
3163
3489
  object: craft,
@@ -3220,6 +3546,8 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3220
3546
  tier: entry.tier
3221
3547
  } : {}, shun.length ? {
3222
3548
  shun
3549
+ } : {}, trade ? {
3550
+ served
3223
3551
  } : {});
3224
3552
  if (entry.broad) {
3225
3553
  const heldTo = [
@@ -3236,9 +3564,11 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3236
3564
  kin: kinPhrases
3237
3565
  } : {}, heldTo.length ? {
3238
3566
  category: heldTo
3239
- } : {}) : core.length ? {
3567
+ } : {}) : core.length ? _extends({
3240
3568
  category: core
3241
- } : {}));
3569
+ }, trade ? {
3570
+ leadOwn: true
3571
+ } : {}) : {}));
3242
3572
  continue;
3243
3573
  }
3244
3574
  searches.push(_extends({}, common, entry.lead ? {
@@ -3249,7 +3579,9 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3249
3579
  object: entry.object
3250
3580
  } : {}, kinPhrases.length && entry.object === object ? {
3251
3581
  kin: kinPhrases
3252
- } : {}, craft ? {
3582
+ } : {}, trade ? {
3583
+ strict: true
3584
+ } : craft ? {
3253
3585
  requires: craft
3254
3586
  } : {}, category.length ? {
3255
3587
  category
@@ -3264,7 +3596,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3264
3596
  * in the site's world: "teacher classroom" for a "Math instructor" (AGL-3856).
3265
3597
  */ export function aiStockSearchesFor(slot, terms, subject, part, sectionName = '') {
3266
3598
  var _terms_core, _terms_leadWord, _terms_making, _ref, _ref1, _terms_setting, _terms_setting1, _ref2, _terms_maker, _terms_maker1, _ref3;
3267
- var _aiStockPeopleRole, _slot_record, _slot_record1;
3599
+ var _terms_held, _aiStockPeopleRole, _slot_record, _terms_out, _slot_record1;
3268
3600
  const orientation = aiStockOrientation(slot.aspect);
3269
3601
  const size = slot.role === 'hero' ? {
3270
3602
  minWidth: 1600
@@ -3323,11 +3655,22 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3323
3655
  // own object among the first tags, and searched by its object's kin, never
3324
3656
  // the category's world alone (AGL-3660).
3325
3657
  const collection = part === 'collection' && Boolean(terms.craft) && !work;
3326
- const broad = kin && collection || work ? [] : terms.broad.filter(shown).map((query)=>({
3658
+ // A picture's broad searches find a photo OF the business's own world,
3659
+ // never one naming it in a word of a tag (AGL-4092); a hero's first tag
3660
+ // already names it.
3661
+ const broad = kin && collection || work ? [] : terms.broad.filter(shown).map((query)=>_extends({
3327
3662
  query,
3328
3663
  broad: true,
3329
3664
  category: collection
3665
+ }, slot.role === 'hero' ? {} : {
3666
+ own: true
3330
3667
  }));
3668
+ // A trade's things are held to the world it serves, never to its craft (AGL-4092).
3669
+ const heldTo = ((_terms_held = terms.held) == null ? void 0 : _terms_held.length) ? terms.held : null;
3670
+ // A caption that names no thing (AGL-4092): "Come hungry" is a picture of
3671
+ // the site's own world, and "Around town" of its customers out where it is.
3672
+ const thingless = Boolean(subject) && !object && !work;
3673
+ const out = thingless && slot.role !== 'hero' && PLACE_PHRASES.test(slot.alt);
3331
3674
  // The maker at work, held to the craft: never "hands" (AGL-3660).
3332
3675
  const makers = (work ? [] : (_terms_making = terms.making) != null ? _terms_making : []).filter(shown).map((query)=>({
3333
3676
  query,
@@ -3353,7 +3696,17 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3353
3696
  // (AGL-4065): "handyman drywall" before "drywall", which took an architect's
3354
3697
  // house with a family before its studs.
3355
3698
  const record = Boolean(slot.record) && !work && !ofTheWorld && Boolean(leadWord);
3356
- const thing = (!subject ? [] : ofTheWorld ? [
3699
+ const thing = (!subject ? [] : thingless ? out ? (((_terms_out = terms.out) == null ? void 0 : _terms_out.length) ? terms.out : [
3700
+ `${leadWord} customers`.trim()
3701
+ ]).map((query)=>({
3702
+ query,
3703
+ out: true
3704
+ })) : [
3705
+ {
3706
+ query: `${leadWord} ${subject}`.trim(),
3707
+ own: true
3708
+ }
3709
+ ] : ofTheWorld ? [
3357
3710
  {
3358
3711
  query: `${leadWord} ${subjectStems.some((stem)=>!leadStems.includes(stem)) ? subject.split(/\s+/).filter((word)=>!leadStems.includes(aiStockStem(word))).join(' ') : ''}`.trim(),
3359
3712
  held: true,
@@ -3549,7 +3902,10 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3549
3902
  kin: kinPhrases
3550
3903
  } : {}, held && collection || raw.leads ? {
3551
3904
  lead: true
3552
- } : {}, terms.craft && !raw.ofWorld && (raw.category || raw.held && collection) ? _extends({
3905
+ } : {}, terms.craft && !raw.ofWorld && (raw.category || raw.held && collection) ? heldTo ? {
3906
+ category: heldTo,
3907
+ served: heldTo
3908
+ } : _extends({
3553
3909
  requires: terms.craft
3554
3910
  }, ownWorld.length ? {
3555
3911
  category: [
@@ -3562,7 +3918,18 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3562
3918
  category: core
3563
3919
  } : {}, slot.role === 'hero' ? {
3564
3920
  first: true
3565
- } : {}, refuseLead.length ? {
3921
+ } : {}, raw.own ? {
3922
+ leadOwn: true
3923
+ } : {}, raw.out ? {
3924
+ broad: true,
3925
+ needs: PLACE_NEEDS.map(stemPhrase),
3926
+ refuseLead: [
3927
+ ...new Set([
3928
+ ...refuseLead,
3929
+ ...FOREIGN_STREET.map(stemPhrase)
3930
+ ])
3931
+ ]
3932
+ } : refuseLead.length ? {
3566
3933
  refuseLead
3567
3934
  } : {}, work ? {
3568
3935
  worldless: true
@@ -3641,17 +4008,61 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3641
4008
  };
3642
4009
  }
3643
4010
  /** Whether a phrase is among a hit's phrases, word for word. */ const among = (phrases, phrase)=>phrases.some((found)=>` ${found} `.includes(` ${phrase} `));
4011
+ /**
4012
+ * Whether one of a hit's first three tags is OF a world (AGL-4092): it names
4013
+ * the world, and its last word, or its last two, is a word of the world or of
4014
+ * its domain. "Street tacos" and "dirt bike tire" are; "taco bowl" is a bowl,
4015
+ * "body part" a part. Read from the tags alone: the page address repeats them
4016
+ * run together, and a run of words has no head.
4017
+ */ function leadsWithWorld(tags, world, domain) {
4018
+ return tags.slice(0, 3).some((tag)=>{
4019
+ var _stems_;
4020
+ const stems = stemList(tag);
4021
+ if (!withPairs(stems).some((term)=>world.has(term))) return false;
4022
+ return [
4023
+ (_stems_ = stems[stems.length - 1]) != null ? _stems_ : '',
4024
+ stems.slice(-2).join(' ')
4025
+ ].some((term)=>Boolean(term) && (world.has(term) || domain.has(term)));
4026
+ });
4027
+ }
4028
+ /**
4029
+ * What a picture of a business's world is never OF unless its words name it
4030
+ * (AGL-4092): an animal or a toy (as a portfolio's record never is,
4031
+ * {@link RECORD_ODD_LEADS}), or a bare body part. Each group is let in whole
4032
+ * by any of its words in `said` — the slot's words, its section's, the
4033
+ * brief's — so a dog groomer's pictures show dogs and cats alike. As stems;
4034
+ * the body's left out where `body` is false (a portrait's face).
4035
+ */ export function aiStockOddLeads(said, options = {}) {
4036
+ const named = new Set(said.flatMap((text)=>stemList(text)));
4037
+ const groups = [
4038
+ 'animal animals cat cats kitten kittens dog dogs puppy puppies pet pets bird birds frog frogs duck ducks horse horses ' + 'cow cows rabbit rabbits bunny',
4039
+ 'toy toys figurine figurines lego doll dolls teddy puppet miniature',
4040
+ ...options.body === false ? [] : [
4041
+ `${[
4042
+ ...BODY_PARTS
4043
+ ].join(' ')} toe toes barefoot`
4044
+ ]
4045
+ ].map((group)=>[
4046
+ ...new Set(stemList(group))
4047
+ ]);
4048
+ return groups.filter((group)=>!group.some((stem)=>named.has(stem))).flat();
4049
+ }
3644
4050
  /**
3645
4051
  * Whether a hit carries a topic its site never named ({@link SENSITIVE_TOPICS}):
3646
4052
  * a cross, a grave, a flag, a gun, a glass of wine, a child, a mannequin.
3647
4053
  */ export function aiStockSensitive(photo, named = []) {
3648
4054
  const { words: found } = hitWords(photo);
3649
4055
  const site = new Set(named);
3650
- // A maker's name, in its tags or its page address, unless the site names the maker (AGL-4065).
4056
+ // A maker's name, in its tags or its page address, unless the site names the maker (AGL-4065),
4057
+ // or a word one letter off it: "susuki" is a Suzuki (AGL-4092).
3651
4058
  const makers = aiStockMakerTerms(photo);
3652
4059
  if ([
3653
4060
  ...MAKER_STEMS
3654
4061
  ].some((maker)=>makers.has(maker) && !site.has(maker))) return true;
4062
+ for (const term of makers){
4063
+ const maker = term.includes(' ') ? null : fuzzyMaker(term);
4064
+ if (maker && !site.has(maker) && !site.has(term)) return true;
4065
+ }
3655
4066
  return SENSITIVE_STEMS.some((topic)=>[
3656
4067
  ...topic.words
3657
4068
  ].some((word)=>found.has(word)) && ![
@@ -3669,13 +4080,15 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3669
4080
  * the subject's two-word phrase 3, each of its other words 1, and the
3670
4081
  * site's world 1, plus a quarter when it is among the first tags.
3671
4082
  */ export function aiStockRelevance(photo, judgement) {
3672
- 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;
3673
- var _judgement_roleScene, _judgement_roleRefuse, _judgement_away, _judgement_world;
4083
+ 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, _photo_tags, _judgement_roles;
4084
+ var _judgement_served, _judgement_roleScene, _judgement_roleRefuse, _judgement_away, _judgement_world, _judgement_oddLeads;
3674
4085
  const hit = hitWords(photo);
3675
4086
  if (hit.rejected) return 0;
3676
4087
  if (aiStockSensitive(photo, judgement.named)) return 0;
3677
4088
  // Glass art is not pottery: a rival craft's photo names this craft's own words.
3678
4089
  for (const rival of (_judgement_rivals = judgement.rivals) != null ? _judgement_rivals : [])if (rival.words.some((word)=>hit.all.has(word)) && !rival.core.some((word)=>hit.all.has(word))) return 0;
4090
+ // A trade's thing is of the world it serves, whatever else names it (AGL-4092).
4091
+ if (((_judgement_served = judgement.served) == null ? void 0 : _judgement_served.length) && !judgement.served.some((term)=>hit.all.has(stemPhrase(term)))) return 0;
3679
4092
  const subject = stemList((_judgement_subject = judgement.subject) != null ? _judgement_subject : '');
3680
4093
  // What the slot or the brief names lets a shunned word, a room or a fragrance in.
3681
4094
  const owned = new Set([
@@ -3691,17 +4104,17 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3691
4104
  // A teacher is shown teaching: a classroom, a lecture or students among the
3692
4105
  // first tags, and never a model, a pose or a painting (AGL-3856).
3693
4106
  if (((_judgement_roleScene = judgement.roleScene) == null ? void 0 : _judgement_roleScene.length) || ((_judgement_roleRefuse = judgement.roleRefuse) == null ? void 0 : _judgement_roleRefuse.length)) {
3694
- var _photo_tags, _photo_tags1, _judgement_roleRefuse1;
4107
+ var _photo_tags1, _photo_tags2, _judgement_roleRefuse1;
3695
4108
  var _judgement_roleScene1;
3696
- const first = new Set(stemList(((_photo_tags = photo.tags) != null ? _photo_tags : []).slice(0, AI_STOCK_ROLE_TAGS_READ).join(' ')));
4109
+ const first = new Set(stemList(((_photo_tags1 = photo.tags) != null ? _photo_tags1 : []).slice(0, AI_STOCK_ROLE_TAGS_READ).join(' ')));
3697
4110
  if (((_judgement_roleScene1 = judgement.roleScene) == null ? void 0 : _judgement_roleScene1.length) && !judgement.roleScene.some((stem)=>first.has(stem))) return 0;
3698
- const read = new Set(stemList(((_photo_tags1 = photo.tags) != null ? _photo_tags1 : []).slice(0, ALT_TAGS_READ).join(' ')));
4111
+ const read = new Set(stemList(((_photo_tags2 = photo.tags) != null ? _photo_tags2 : []).slice(0, ALT_TAGS_READ).join(' ')));
3699
4112
  if (((_judgement_roleRefuse1 = judgement.roleRefuse) != null ? _judgement_roleRefuse1 : []).some((stem)=>read.has(stem))) return 0;
3700
4113
  }
3701
4114
  // A Blue Ridge couple is never on a beach, however its later tags name a mountain (AGL-3942).
3702
4115
  if ((_judgement_away = judgement.away) == null ? void 0 : _judgement_away.length) {
3703
- var _photo_tags2;
3704
- const read = new Set(stemList(((_photo_tags2 = photo.tags) != null ? _photo_tags2 : []).slice(0, ALT_TAGS_READ).join(' ')));
4116
+ var _photo_tags3;
4117
+ const read = new Set(stemList(((_photo_tags3 = photo.tags) != null ? _photo_tags3 : []).slice(0, ALT_TAGS_READ).join(' ')));
3705
4118
  if (judgement.away.some((stem)=>read.has(stem))) return 0;
3706
4119
  }
3707
4120
  // A wedding day shows its people: a close-up of roses tagged "wedding" never fills it.
@@ -3748,6 +4161,16 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3748
4161
  const ownWorld = demanded || kinNamed || own.some((stem)=>hit.all.has(stem));
3749
4162
  const worldLead = (anyOf.length ? anyOf.some((stem)=>hit.lead.has(stem)) : required.length > 0 && required.every((stem)=>hit.lead.has(stem))) || kinLead || domain.some((stem)=>hit.lead.has(stem));
3750
4163
  if (judgement.leadWorld && !worldLead) return 0;
4164
+ // One of its first tags is OF the business's own world (AGL-4092): "tacos",
4165
+ // "street tacos", "dirt bike" lead with it; "taco bowl", "body part" and a
4166
+ // man posing with a "motorcycle" for a motocross team do not.
4167
+ if (judgement.leadOwn && own.length && !kinLead && !leadsWithWorld((_photo_tags = photo.tags) != null ? _photo_tags : [], new Set(own), new Set(domain))) return 0;
4168
+ // A cat, a toy or a bare foot leads it, and nothing of the business's own world does (AGL-4092).
4169
+ if (((_judgement_oddLeads = judgement.oddLeads) == null ? void 0 : _judgement_oddLeads.length) && own.length) {
4170
+ var _photo_tags4;
4171
+ const first = termsOf(((_photo_tags4 = photo.tags) != null ? _photo_tags4 : []).slice(0, 3));
4172
+ if (judgement.oddLeads.some((stem)=>first.has(stem)) && !own.some((stem)=>first.has(stem))) return 0;
4173
+ }
3751
4174
  // The first tag alone, never the page's address: Pixabay's slug repeats a
3752
4175
  // late tag ("coffee-latte-art-candle-…"). A site whose words name no world
3753
4176
  // holds its hero to none.
@@ -3778,12 +4201,23 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3778
4201
  const leading = kin.length ? kinLead : hit.lead.has(object);
3779
4202
  // A lifestyle scene whose object is a last tag is not a photo OF it.
3780
4203
  if (judgement.lead && !leading) return 0;
3781
- if (domain.length && !world && !phrased) return 0;
4204
+ // The business's own world, never any word of its type (AGL-4092): a cat
4205
+ // tagged "hungry" named "breakfast", and a European alley "street", of
4206
+ // "Taco food trailer: street and breakfast tacos".
4207
+ if (own.length && !ownWorld && !phrased) return 0;
3782
4208
  score += leading ? 4 : 2;
3783
4209
  if (kinNamed) score += 3;
3784
4210
  if (firstNamed(kin.length ? kin : [
3785
4211
  object
3786
4212
  ])) score += 1;
4213
+ // A product's photo is OF it: named by its first or second tag, over one naming it fourth (AGL-4092).
4214
+ if (judgement.product) {
4215
+ var _photo_tags5;
4216
+ const firstTwo = ((_photo_tags5 = photo.tags) != null ? _photo_tags5 : []).slice(0, 2).map((tag)=>stemList(tag).join(' '));
4217
+ if ((kin.length ? kin : [
4218
+ object
4219
+ ]).some((phrase)=>among(firstTwo, phrase))) score += 2;
4220
+ }
3787
4221
  } else if (own.length) {
3788
4222
  if (!ownWorld) return 0;
3789
4223
  }
@@ -3843,13 +4277,15 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
3843
4277
  * A search's ranked hits with those of a shoot no slot shows yet first among
3844
4278
  * the best (AGL-3856): a team's three cards are three people where the
3845
4279
  * library has them, never one model three times while another fits as well.
3846
- * Every other order is kept.
3847
- */ export function aiStockFreshShootsFirst(ranked, shown, judgement) {
4280
+ * Every other order is kept. A product's are of a shoot no slot shows yet
4281
+ * first among ALL its accepted hits (`whole`, AGL-4092): Berm & Bolt's two
4282
+ * tires were one contributor's shoot, 32 uploads apart.
4283
+ */ export function aiStockFreshShootsFirst(ranked, shown, judgement, options = {}) {
3848
4284
  if (!shown.length || ranked.length < 2) return [
3849
4285
  ...ranked
3850
4286
  ];
3851
4287
  const best = aiStockRelevance(ranked[0], judgement);
3852
- const top = ranked.filter((photo)=>aiStockRelevance(photo, judgement) === best);
4288
+ const top = options.whole ? ranked : ranked.filter((photo)=>aiStockRelevance(photo, judgement) === best);
3853
4289
  const fresh = top.filter((photo)=>!shown.some((other)=>aiStockSameShoot(photo, other)));
3854
4290
  if (!fresh.length || fresh[0] === ranked[0]) return [
3855
4291
  ...ranked
@@ -3984,8 +4420,8 @@ function jobPhotos(key) {
3984
4420
  /**
3985
4421
  * Makers' names a tag gives, never what a photo shows to someone who cannot
3986
4422
  * see it (AGL-3856): "A photo-camera and a sony".
3987
- */ const BRANDS = words('sony canon cannon nikon fujifilm fuji leica olympus panasonic kodak polaroid hasselblad pentax gopro dji ' + 'apple iphone ipad macbook imac samsung huawei android google microsoft windows dell lenovo nintendo playstation ' + 'xbox nike adidas puma reebok peloton bmw mercedes audi porsche ferrari lamborghini tesla ford toyota honda ' + 'volkswagen vw jeep chevrolet coca-cola pepsi starbucks mcdonalds ikea lego barbie disney altman ' + // The vehicle makers a moto site's photos are tagged with (AGL-4065).
3988
- 'yamaha kawasaki suzuki ktm ducati harley harley-davidson husqvarna gasgas nissan subaru mazda chevy dodge reebok ' + 'petri zenit praktica yashica minolta mamiya rolleiflex voigtlander contax ricoh konica lomo lomography holga zeiss');
4423
+ */ const BRANDS = words('sony canon cannon nikon fujifilm fuji leica olympus panasonic kodak polaroid hasselblad pentax gopro dji ' + 'apple iphone ipad macbook imac samsung huawei android google microsoft windows dell lenovo nintendo playstation ' + 'xbox nike adidas puma reebok peloton bmw mercedes audi porsche ferrari lamborghini tesla ford toyota honda ' + 'volkswagen vw jeep chevrolet coca-cola pepsi starbucks mcdonalds ikea lego barbie disney altman ' + // The vehicle makers a moto site's photos are tagged with (AGL-4065), and its gear's and tires' (AGL-4092).
4424
+ 'yamaha kawasaki suzuki ktm ducati harley harley-davidson husqvarna gasgas nissan subaru mazda chevy dodge reebok ' + `${GEAR_MAKER_WORDS.join(' ')} ` + 'petri zenit praktica yashica minolta mamiya rolleiflex voigtlander contax ricoh konica lomo lomography holga zeiss');
3989
4425
  /** Stopwords a description still says, as the people a photo shows: "a family", "a person". */ const SAID_PEOPLE = words('people person family');
3990
4426
  /** 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');
3991
4427
  /** "-ing" words that are things, and take no article: "clothing". */ const UNCOUNTED_ING = words('clothing bedding frosting icing flooring stuffing seasoning filling');
@@ -4426,6 +4862,9 @@ const EXTENSIONS = {
4426
4862
  'kin',
4427
4863
  'lead',
4428
4864
  'leadWorld',
4865
+ 'leadOwn',
4866
+ 'oddLeads',
4867
+ 'served',
4429
4868
  'first',
4430
4869
  'category',
4431
4870
  'strict',
@@ -5750,6 +6189,147 @@ const WORKSPACE = {
5750
6189
  if (!aiLayoutSpeaksOtherThanEnglish(input.language)) return '';
5751
6190
  return aiStockBusinessNoun(input.business) || aiStockPhotoWordsSubject((_input_photoWords = input.photoWords) != null ? _input_photoWords : '') || ((_input_kindWords = input.kindWords) != null ? _input_kindWords : '').trim();
5752
6191
  }
6192
+ /**
6193
+ * How a search's hits are judged for a slot (AGL-3660), as the source judges
6194
+ * them: by the search's own words, held to the world it was asked for — the
6195
+ * site's own (`site`), or a wider one where the site's found nothing
6196
+ * (`world`, AGL-3923). What lets a sensitive topic, an animal or a body part
6197
+ * in is always the site's own words. On a site for adults alone no hit names
6198
+ * a child; a portrait of one of the business's people names an adult, never
6199
+ * leads with a child, and is preferred in its role (AGL-3856). Any other
6200
+ * picture never leads with an animal, a toy or a bare body part its words
6201
+ * and the brief do not name, unless its first tags name the business's own
6202
+ * world too (AGL-4092): Pit Row Tacos' "Come hungry" took a cat. Pure.
6203
+ */ export function aiStockJudgementFor(slot, request, subject, site, sectionName = '', world = site) {
6204
+ var _request_refuse, _ref, _request_subject, _request_shun, _request_refuseLead;
6205
+ var _slot_product, _world_lead, _request_allow, _request_shun1, _request_leadAny, _request_served, _request_needs, _request_refuseLead1, _request_away, _request_needs1, _role_scene, _role_refuse;
6206
+ const refuse = [
6207
+ ...(_request_refuse = request.refuse) != null ? _request_refuse : [],
6208
+ ...site.adults ? CHILD_STEMS : []
6209
+ ];
6210
+ const role = slot.people ? aiStockPeopleRole(slot.alt, sectionName) : null;
6211
+ // A portrait's face is its own, and a craft whose pictures are people at a moment shows their hands and rings (AGL-4092).
6212
+ const oddLeads = slot.people ? [] : aiStockOddLeads([
6213
+ slot.alt,
6214
+ ...(_ref = (_slot_product = slot.product) == null ? void 0 : _slot_product.subjects) != null ? _ref : [],
6215
+ sectionName,
6216
+ ...site.named
6217
+ ], {
6218
+ body: slot.role !== 'about' && !site.scenes
6219
+ });
6220
+ return _extends({
6221
+ subject: (_request_subject = request.subject) != null ? _request_subject : request.broad ? '' : subject,
6222
+ domain: world.domain
6223
+ }, ((_world_lead = world.lead) == null ? void 0 : _world_lead.length) ? {
6224
+ world: world.lead
6225
+ } : {}, {
6226
+ strict: slot.role === 'gallery' && !request.broad || !!request.requires || !!request.strict,
6227
+ named: ((_request_allow = request.allow) == null ? void 0 : _request_allow.length) ? [
6228
+ ...new Set([
6229
+ ...site.named,
6230
+ ...request.allow
6231
+ ])
6232
+ ] : site.named,
6233
+ rivals: world.rivals
6234
+ }, world.shun.length || ((_request_shun1 = request.shun) == null ? void 0 : _request_shun1.length) ? {
6235
+ shun: [
6236
+ ...new Set([
6237
+ ...world.shun,
6238
+ ...(_request_shun = request.shun) != null ? _request_shun : []
6239
+ ])
6240
+ ]
6241
+ } : {}, world.shunLead.length ? {
6242
+ shunLead: world.shunLead
6243
+ } : {}, request.product ? {
6244
+ product: true
6245
+ } : {}, ((_request_leadAny = request.leadAny) == null ? void 0 : _request_leadAny.length) ? {
6246
+ leadAny: request.leadAny
6247
+ } : {}, request.requires ? {
6248
+ requires: request.requires
6249
+ } : {}, request.category ? {
6250
+ category: request.category
6251
+ } : {}, request.object ? {
6252
+ object: request.object
6253
+ } : {}, request.kin ? {
6254
+ kin: request.kin
6255
+ } : {}, request.lead ? {
6256
+ lead: true
6257
+ } : {}, request.leadWorld ? {
6258
+ leadWorld: true
6259
+ } : {}, request.leadOwn ? {
6260
+ leadOwn: true
6261
+ } : {}, ((_request_served = request.served) == null ? void 0 : _request_served.length) ? {
6262
+ served: request.served
6263
+ } : {}, request.first ? {
6264
+ first: true
6265
+ } : {}, refuse.length ? {
6266
+ refuse: [
6267
+ ...new Set(refuse)
6268
+ ]
6269
+ } : {}, ((_request_needs = request.needs) == null ? void 0 : _request_needs.length) ? {
6270
+ needs: request.needs
6271
+ } : {}, ((_request_refuseLead1 = request.refuseLead) == null ? void 0 : _request_refuseLead1.length) ? {
6272
+ refuseLead: request.refuseLead
6273
+ } : {}, ((_request_away = request.away) == null ? void 0 : _request_away.length) ? {
6274
+ away: request.away
6275
+ } : {}, oddLeads.length ? {
6276
+ oddLeads
6277
+ } : {}, request.worldless ? {
6278
+ domain: [],
6279
+ rivals: []
6280
+ } : {}, slot.people ? _extends({
6281
+ needs: ((_request_needs1 = request.needs) == null ? void 0 : _request_needs1.length) ? request.needs : (role == null ? void 0 : role.required) ? role.kin : ADULT_PEOPLE_STEMS,
6282
+ refuseLead: [
6283
+ ...new Set([
6284
+ ...CHILD_STEMS,
6285
+ ...(_request_refuseLead = request.refuseLead) != null ? _request_refuseLead : []
6286
+ ])
6287
+ ]
6288
+ }, role ? {
6289
+ roles: role.kin
6290
+ } : {}, (role == null ? void 0 : (_role_scene = role.scene) == null ? void 0 : _role_scene.length) ? {
6291
+ roleScene: role.scene
6292
+ } : {}, (role == null ? void 0 : (_role_refuse = role.refuse) == null ? void 0 : _role_refuse.length) ? {
6293
+ roleRefuse: aiStockRoleRefusals(role, slot.alt)
6294
+ } : {}) : {});
6295
+ }
6296
+ /**
6297
+ * The craft's own photos, the last thing a slot of a craft's site is filled
6298
+ * from (AGL-3660): its broad searches, any hit naming the craft itself. Never
6299
+ * a desk, a laptop or a living room for a soap shop. A picture that is not a
6300
+ * hero nor a product takes only a photo OF the business's own world among its
6301
+ * first tags (AGL-4092): a taco trailer's "Race day" took a chicken salad
6302
+ * tagged "taco bowl". Pure.
6303
+ */ export function aiStockCraftSearches(slot, world) {
6304
+ // A portfolio's record of the work is never filled by the craft's own
6305
+ // photos (AGL-3856), nor is any picture of a site that shows its work (AGL-3942).
6306
+ if (!world.core.length || aiStockShowsTheWork(slot, world) || aiStockShowsItsWork(slot, world)) return [];
6307
+ const orientation = aiStockOrientation(slot.aspect);
6308
+ const size = slot.role === 'hero' ? {
6309
+ minWidth: 1600
6310
+ } : orientation === 'vertical' ? {
6311
+ minHeight: 900
6312
+ } : {
6313
+ minWidth: 900
6314
+ };
6315
+ return world.broad.map((query)=>{
6316
+ var _world_held;
6317
+ return _extends({
6318
+ query: clip(query),
6319
+ orientation
6320
+ }, size, {
6321
+ broad: true,
6322
+ category: world.core
6323
+ }, slot.role === 'hero' ? {
6324
+ first: true
6325
+ } : slot.product && !((_world_held = world.held) == null ? void 0 : _world_held.length) ? {} : {
6326
+ leadOwn: true
6327
+ }, slot.product ? {
6328
+ product: true,
6329
+ tier: 'weak'
6330
+ } : {});
6331
+ });
6332
+ }
5753
6333
  /**
5754
6334
  * The source for one page, or `null` when this deployment has no stock
5755
6335
  * photo library or no media door, which leaves every slot to the starters.
@@ -5927,123 +6507,10 @@ const WORKSPACE = {
5927
6507
  * hit names a child; a portrait of one of the business's people names an
5928
6508
  * adult, never leads with a child, and is preferred in its role (AGL-3856).
5929
6509
  */ const judgementFor = (slot, request, subject)=>{
5930
- var _request_refuse, _input_sectionNames_slot_sectionIndex, _worldOf_get, _request_refuseLead;
5931
- var _request_needs, _role_scene, _role_refuse;
5932
- const refuse = [
5933
- ...(_request_refuse = request.refuse) != null ? _request_refuse : [],
5934
- ...terms.adults ? CHILD_STEMS : []
5935
- ];
5936
- const role = slot.people ? aiStockPeopleRole(slot.alt, (_input_sectionNames_slot_sectionIndex = input.sectionNames[slot.sectionIndex]) != null ? _input_sectionNames_slot_sectionIndex : '') : null;
5937
- return _extends({}, judgementOf(slot, request, subject, (_worldOf_get = worldOf.get(request)) != null ? _worldOf_get : terms), refuse.length ? {
5938
- refuse: [
5939
- ...new Set(refuse)
5940
- ]
5941
- } : {}, slot.people ? _extends({
5942
- needs: ((_request_needs = request.needs) == null ? void 0 : _request_needs.length) ? request.needs : (role == null ? void 0 : role.required) ? role.kin : ADULT_PEOPLE_STEMS,
5943
- refuseLead: [
5944
- ...new Set([
5945
- ...CHILD_STEMS,
5946
- ...(_request_refuseLead = request.refuseLead) != null ? _request_refuseLead : []
5947
- ])
5948
- ]
5949
- }, role ? {
5950
- roles: role.kin
5951
- } : {}, (role == null ? void 0 : (_role_scene = role.scene) == null ? void 0 : _role_scene.length) ? {
5952
- roleScene: role.scene
5953
- } : {}, (role == null ? void 0 : (_role_refuse = role.refuse) == null ? void 0 : _role_refuse.length) ? {
5954
- roleRefuse: aiStockRoleRefusals(role, slot.alt)
5955
- } : {}) : {});
5956
- };
5957
- /**
5958
- * A search's own judgement, as its words set it, held to the world it was
5959
- * asked for: the site's own, or a wider one where the site's found nothing
5960
- * (AGL-3923). What lets a sensitive topic in is always the site's own words.
5961
- */ const judgementOf = (slot, request, subject, world)=>{
5962
- var _request_subject, _request_shun;
5963
- var _world_lead, _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs, _request_refuseLead, _request_away;
5964
- return _extends({
5965
- subject: (_request_subject = request.subject) != null ? _request_subject : request.broad ? '' : subject,
5966
- domain: world.domain
5967
- }, ((_world_lead = world.lead) == null ? void 0 : _world_lead.length) ? {
5968
- world: world.lead
5969
- } : {}, {
5970
- strict: slot.role === 'gallery' && !request.broad || !!request.requires || !!request.strict,
5971
- named: ((_request_allow = request.allow) == null ? void 0 : _request_allow.length) ? [
5972
- ...new Set([
5973
- ...terms.named,
5974
- ...request.allow
5975
- ])
5976
- ] : terms.named,
5977
- rivals: world.rivals
5978
- }, world.shun.length || ((_request_shun1 = request.shun) == null ? void 0 : _request_shun1.length) ? {
5979
- shun: [
5980
- ...new Set([
5981
- ...world.shun,
5982
- ...(_request_shun = request.shun) != null ? _request_shun : []
5983
- ])
5984
- ]
5985
- } : {}, world.shunLead.length ? {
5986
- shunLead: world.shunLead
5987
- } : {}, request.product ? {
5988
- product: true
5989
- } : {}, ((_request_leadAny = request.leadAny) == null ? void 0 : _request_leadAny.length) ? {
5990
- leadAny: request.leadAny
5991
- } : {}, request.requires ? {
5992
- requires: request.requires
5993
- } : {}, request.category ? {
5994
- category: request.category
5995
- } : {}, request.object ? {
5996
- object: request.object
5997
- } : {}, request.kin ? {
5998
- kin: request.kin
5999
- } : {}, request.lead ? {
6000
- lead: true
6001
- } : {}, request.leadWorld ? {
6002
- leadWorld: true
6003
- } : {}, request.first ? {
6004
- first: true
6005
- } : {}, ((_request_refuse = request.refuse) == null ? void 0 : _request_refuse.length) ? {
6006
- refuse: request.refuse
6007
- } : {}, ((_request_needs = request.needs) == null ? void 0 : _request_needs.length) ? {
6008
- needs: request.needs
6009
- } : {}, ((_request_refuseLead = request.refuseLead) == null ? void 0 : _request_refuseLead.length) ? {
6010
- refuseLead: request.refuseLead
6011
- } : {}, ((_request_away = request.away) == null ? void 0 : _request_away.length) ? {
6012
- away: request.away
6013
- } : {}, request.worldless ? {
6014
- domain: [],
6015
- rivals: []
6016
- } : {});
6017
- };
6018
- /**
6019
- * The craft's own photos, the last thing a slot of a craft's site is
6020
- * filled from (AGL-3660): its broad searches, any hit naming the craft
6021
- * itself. Never a desk, a laptop or a living room for a soap shop.
6022
- */ const craftSearches = (slot, world = terms)=>{
6023
- // A portfolio's record of the work is never filled by the craft's own
6024
- // photos (AGL-3856), nor is any picture of a site that shows its work (AGL-3942).
6025
- if (!world.core.length || aiStockShowsTheWork(slot, world) || aiStockShowsItsWork(slot, world)) return [];
6026
- const orientation = aiStockOrientation(slot.aspect);
6027
- const size = slot.role === 'hero' ? {
6028
- minWidth: 1600
6029
- } : orientation === 'vertical' ? {
6030
- minHeight: 900
6031
- } : {
6032
- minWidth: 900
6033
- };
6034
- return world.broad.map((query)=>_extends({
6035
- query: clip(query),
6036
- orientation
6037
- }, size, {
6038
- broad: true,
6039
- category: world.core
6040
- }, slot.role === 'hero' ? {
6041
- first: true
6042
- } : {}, slot.product ? {
6043
- product: true,
6044
- tier: 'weak'
6045
- } : {}));
6510
+ var _input_sectionNames_slot_sectionIndex, _worldOf_get;
6511
+ return aiStockJudgementFor(slot, request, subject, terms, (_input_sectionNames_slot_sectionIndex = input.sectionNames[slot.sectionIndex]) != null ? _input_sectionNames_slot_sectionIndex : '', (_worldOf_get = worldOf.get(request)) != null ? _worldOf_get : terms);
6046
6512
  };
6513
+ const craftSearches = (slot, world = terms)=>aiStockCraftSearches(slot, world);
6047
6514
  /**
6048
6515
  * A slot's searches once its own world found it nothing (AGL-3923), in
6049
6516
  * order: the kind of site's world, the starter's, the slot's own words
@@ -6063,10 +6530,16 @@ const WORKSPACE = {
6063
6530
  };
6064
6531
  for (const { rung, terms: world } of wider){
6065
6532
  if (rung === 'generic') continue;
6533
+ // A wider world is the site's only as a kind of site: its photos are held to it as before (AGL-4092).
6066
6534
  ask(world, [
6067
6535
  ...aiStockSearchesFor(slot, world, subject, part, sectionName),
6068
6536
  ...craftSearches(slot, world)
6069
- ]);
6537
+ ].map((_0)=>{
6538
+ let { leadOwn: _own } = _0, request = _object_without_properties_loose(_0, [
6539
+ "leadOwn"
6540
+ ]);
6541
+ return request;
6542
+ }));
6070
6543
  }
6071
6544
  const orientation = aiStockOrientation(slot.aspect);
6072
6545
  const size = slot.role === 'hero' ? {
@@ -6129,10 +6602,14 @@ const WORKSPACE = {
6129
6602
  const subject = english ? '' : aiStockSubjectWords(slot.alt, sectionName);
6130
6603
  let choice = null;
6131
6604
  const requests = [
6132
- ...slot.product ? aiStockProductSearches(slot, terms.craft, terms.domain, {
6605
+ ...slot.product ? aiStockProductSearches(slot, terms.craft, terms.domain, _extends({
6133
6606
  broad: terms.broad,
6134
6607
  core: terms.core
6135
- }) : aiStockSearchesFor(slot, terms, subject, aiStockSlotPart(slot, sectionName), sectionName),
6608
+ }, terms.held ? {
6609
+ held: terms.held
6610
+ } : {}, terms.leadWord ? {
6611
+ leadWord: terms.leadWord
6612
+ } : {})) : aiStockSearchesFor(slot, terms, subject, aiStockSlotPart(slot, sectionName), sectionName),
6136
6613
  ...craftSearches(slot)
6137
6614
  ];
6138
6615
  /** Whether the library answered any of the slot's own searches with a photo at all (AGL-4065). */ let knew = false;
@@ -6155,8 +6632,12 @@ const WORKSPACE = {
6155
6632
  }
6156
6633
  const judgement = judgementFor(slot, request, subject);
6157
6634
  const ranked = aiStockRank(found, blocked, `${input.seed}:${index}:${request.query}`, judgement);
6158
- // A team's portraits are of different people where the hits allow (AGL-3856).
6159
- return slot.people && !repeat ? aiStockFreshShootsFirst(ranked, shown, judgement) : ranked;
6635
+ // A team's portraits are of different people where the hits allow (AGL-3856),
6636
+ // and a shop's products of different shoots (AGL-4092).
6637
+ if (repeat || !(slot.people || slot.product)) return ranked;
6638
+ return aiStockFreshShootsFirst(ranked, shown, judgement, slot.product ? {
6639
+ whole: true
6640
+ } : {});
6160
6641
  };
6161
6642
  /** The first of these searches with a hit to take: a new one, or one the job already shows. */ const choose = async (tried, repeat)=>{
6162
6643
  for (const request of tried){