@aglyn/plugins-ai 1.0.0-beta.264 → 1.0.0-beta.266

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 (98) hide show
  1. package/package.json +17 -17
  2. package/src/lib/assets/site-styles/beauty.webp +0 -0
  3. package/src/lib/assets/site-styles/blog.webp +0 -0
  4. package/src/lib/assets/site-styles/business.webp +0 -0
  5. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  6. package/src/lib/assets/site-styles/education.webp +0 -0
  7. package/src/lib/assets/site-styles/events.webp +0 -0
  8. package/src/lib/assets/site-styles/fitness.webp +0 -0
  9. package/src/lib/assets/site-styles/landing.webp +0 -0
  10. package/src/lib/assets/site-styles/music.webp +0 -0
  11. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  12. package/src/lib/assets/site-styles/personal.webp +0 -0
  13. package/src/lib/assets/site-styles/photography.webp +0 -0
  14. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  15. package/src/lib/assets/site-styles/professional.webp +0 -0
  16. package/src/lib/assets/site-styles/realestate.webp +0 -0
  17. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  18. package/src/lib/assets/site-styles/store.webp +0 -0
  19. package/src/lib/assets/site-styles/studio.webp +0 -0
  20. package/src/lib/assets/site-styles/trades.webp +0 -0
  21. package/src/lib/assets/site-styles/wellness.webp +0 -0
  22. package/src/lib/assets/site-styles/yoga.webp +0 -0
  23. package/src/lib/components/ai-site-start-card.component.d.ts +12 -0
  24. package/src/lib/components/ai-site-start-card.component.js +256 -236
  25. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  26. package/src/lib/components/ai-site-start-steps.component.d.ts +108 -0
  27. package/src/lib/components/ai-site-start-steps.component.js +451 -0
  28. package/src/lib/components/ai-site-start-steps.component.js.map +1 -0
  29. package/src/lib/components/ai-site-start-tracking.d.ts +114 -0
  30. package/src/lib/components/ai-site-start-tracking.js +277 -0
  31. package/src/lib/components/ai-site-start-tracking.js.map +1 -0
  32. package/src/lib/components/ai-site-style-previews.d.ts +26 -0
  33. package/src/lib/components/ai-site-style-previews.js +115 -0
  34. package/src/lib/components/ai-site-style-previews.js.map +1 -0
  35. package/src/lib/insights/ai-insight-digest.d.ts +9 -0
  36. package/src/lib/insights/ai-insight-digest.js +35 -11
  37. package/src/lib/insights/ai-insight-digest.js.map +1 -1
  38. package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
  39. package/src/lib/jobs/ai-job-page-language.js +20 -2
  40. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  41. package/src/lib/jobs/ai-job-plan-step.d.ts +43 -8
  42. package/src/lib/jobs/ai-job-plan-step.js +99 -18
  43. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-site-datasets.d.ts +82 -1
  45. package/src/lib/jobs/ai-job-site-datasets.js +276 -14
  46. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  47. package/src/lib/jobs/ai-layout-stock-photos.d.ts +108 -10
  48. package/src/lib/jobs/ai-layout-stock-photos.js +729 -87
  49. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  50. package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.d.ts +58 -0
  51. package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js +678 -0
  52. package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js.map +1 -0
  53. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.d.ts +33 -0
  54. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js +270 -0
  55. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js.map +1 -0
  56. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.d.ts +33 -0
  57. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js +277 -0
  58. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js.map +1 -0
  59. package/src/lib/layout-language/ai-layout-compiler.js +49 -12
  60. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  61. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  62. package/src/lib/layout-language/ai-layout-design.js +14 -13
  63. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  64. package/src/lib/layout-language/ai-layout-listings.d.ts +48 -0
  65. package/src/lib/layout-language/ai-layout-listings.js +91 -5
  66. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  67. package/src/lib/layout-language/ai-layout-pictures.d.ts +15 -0
  68. package/src/lib/layout-language/ai-layout-pictures.js +6 -2
  69. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  70. package/src/lib/model/ai-site-home-arcs.d.ts +75 -0
  71. package/src/lib/model/ai-site-home-arcs.js +741 -0
  72. package/src/lib/model/ai-site-home-arcs.js.map +1 -0
  73. package/src/lib/model/ai-site-inventory.d.ts +18 -0
  74. package/src/lib/model/ai-site-inventory.js.map +1 -1
  75. package/src/lib/model/ai-site-job.d.ts +9 -6
  76. package/src/lib/model/ai-site-job.js +8 -5
  77. package/src/lib/model/ai-site-job.js.map +1 -1
  78. package/src/lib/model/ai-site-kinds.js +24 -24
  79. package/src/lib/model/ai-site-kinds.js.map +1 -1
  80. package/src/lib/model/ai-site-start-steps.d.ts +67 -0
  81. package/src/lib/model/ai-site-start-steps.js +127 -0
  82. package/src/lib/model/ai-site-start-steps.js.map +1 -0
  83. package/src/lib/model/ai-site-start.js +4 -1
  84. package/src/lib/model/ai-site-start.js.map +1 -1
  85. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -0
  86. package/src/lib/runtime/ai-doctrine-validators.js +119 -2
  87. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  88. package/src/lib/runtime/ai-doctrine.d.ts +6 -1
  89. package/src/lib/runtime/ai-doctrine.js +9 -5
  90. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  91. package/src/lib/runtime/ai-palette.generated.js +5 -2
  92. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  93. package/src/lib/runtime/site-inventory.js +6 -0
  94. package/src/lib/runtime/site-inventory.js.map +1 -1
  95. package/src/lib/server/ai-insight-digest-route.js +2 -2
  96. package/src/lib/server/ai-insight-digest-route.js.map +1 -1
  97. package/src/lib/usage/assist-free-taste.js +3 -0
  98. package/src/lib/usage/assist-free-taste.js.map +1 -1
@@ -1291,6 +1291,19 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
1291
1291
  'photograph'
1292
1292
  ],
1293
1293
  scenes: true,
1294
+ maker: [
1295
+ 'photographer',
1296
+ 'photographers',
1297
+ 'camera',
1298
+ 'cameras',
1299
+ 'lens',
1300
+ 'lenses',
1301
+ 'tripod',
1302
+ 'dslr',
1303
+ 'slr',
1304
+ 'journalist',
1305
+ 'paparazzi'
1306
+ ],
1294
1307
  kin: [
1295
1308
  'photographer',
1296
1309
  'photography',
@@ -1490,6 +1503,16 @@ const words = (list)=>new Set(list.trim().split(/\s+/));
1490
1503
  ]
1491
1504
  }, groups.some((group)=>group.scenes) ? {
1492
1505
  scenes: true
1506
+ } : {}, groups.some((group)=>{
1507
+ var _group_maker;
1508
+ return (_group_maker = group.maker) == null ? void 0 : _group_maker.length;
1509
+ }) ? {
1510
+ maker: [
1511
+ ...new Set(groups.flatMap((group)=>{
1512
+ var _group_maker;
1513
+ return (_group_maker = group.maker) != null ? _group_maker : [];
1514
+ }).map(aiStockStem))
1515
+ ]
1493
1516
  } : {}, {
1494
1517
  named: [
1495
1518
  ...new Set(stemList(businessType))
@@ -1584,6 +1607,8 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1584
1607
  'mother',
1585
1608
  'father',
1586
1609
  'parents',
1610
+ 'mom',
1611
+ 'dad',
1587
1612
  'daughter',
1588
1613
  'son',
1589
1614
  'baby',
@@ -1625,6 +1650,8 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1625
1650
  'elope',
1626
1651
  'eloping',
1627
1652
  'bride',
1653
+ 'brides',
1654
+ 'bridal',
1628
1655
  'groom',
1629
1656
  'engagement',
1630
1657
  'engaged',
@@ -1632,11 +1659,18 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1632
1659
  'couples',
1633
1660
  'newlywed',
1634
1661
  'newlyweds',
1635
- 'honeymoon'
1662
+ 'honeymoon',
1663
+ 'ceremony',
1664
+ 'ceremonies',
1665
+ 'reception',
1666
+ 'receptions',
1667
+ 'vows'
1636
1668
  ],
1637
1669
  query: (named)=>[
1638
1670
  'wedding',
1671
+ 'weddings',
1639
1672
  'bride',
1673
+ 'bridal',
1640
1674
  'groom'
1641
1675
  ].some((word)=>named.has(word)) ? 'wedding couple' : 'couple',
1642
1676
  subject: 'couple',
@@ -1666,6 +1700,273 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1666
1700
  shun: []
1667
1701
  }
1668
1702
  ];
1703
+ /**
1704
+ * A portrait session (AGL-3856), read for a record's cover alone: "Milestone
1705
+ * Portraits" is a person's portrait, never a cat's. A page's "portrait of the
1706
+ * photographer" keeps its own words.
1707
+ */ const PORTRAIT_SCENE = {
1708
+ words: [
1709
+ 'portrait',
1710
+ 'portraits',
1711
+ 'headshot',
1712
+ 'headshots'
1713
+ ],
1714
+ query: ()=>'portrait',
1715
+ subject: 'portrait',
1716
+ lead: [
1717
+ 'portrait',
1718
+ 'woman',
1719
+ 'man',
1720
+ 'couple',
1721
+ 'person',
1722
+ 'people'
1723
+ ],
1724
+ people: [
1725
+ 'woman',
1726
+ 'women',
1727
+ 'man',
1728
+ 'men',
1729
+ 'couple',
1730
+ 'person',
1731
+ 'people',
1732
+ 'bride',
1733
+ 'groom',
1734
+ 'mother',
1735
+ 'father',
1736
+ 'family'
1737
+ ],
1738
+ shun: []
1739
+ };
1740
+ /**
1741
+ * Words a photo of a child is tagged with (AGL-3856), as stems. Pixabay tags
1742
+ * a schoolgirl "girl", "schoolgirl" or "school girl" far more often than
1743
+ * "child", so a site for adults that names a school let children in: the
1744
+ * beta.264 Brightpath Adult Learning Center's "Meet your teachers" showed a
1745
+ * teen graduate, a schoolgirl and kids.
1746
+ */ const CHILD_STEMS = [
1747
+ ...new Set(('child children childhood kid kids baby babies toddler toddlers infant infants newborn boy boys girl girls ' + 'schoolgirl schoolgirls schoolboy schoolboys schoolchild schoolchildren schoolkid schoolkids pupil pupils ' + 'teen teens teenager teenagers teenage preteen youth kindergarten preschool').split(' ').map(aiStockStem))
1748
+ ];
1749
+ /** Words of which a photo of one of the business's people names one: an adult, at work or in their role. */ const ADULT_PEOPLE_STEMS = [
1750
+ ...new Set(('man men woman women adult adults businessman businesswoman businessmen professional ' + 'professionals staff worker workers employee employees team colleague colleagues coworker coworkers ' + 'teacher teachers instructor instructors tutor tutors lecturer professor educator faculty coach coaches ' + 'trainer trainers chef chefs cook baker barista doctor doctors dentist nurse nurses hygienist therapist ' + 'physician stylist hairdresser barber artist musician photographer entrepreneur founder owner manager ' + 'leader speaker volunteer volunteers mechanic electrician plumber carpenter craftsman technician').split(' ').map(aiStockStem))
1751
+ ];
1752
+ /**
1753
+ * The roles a list of the business's people names (AGL-3856): the words that
1754
+ * name each, the words a photo of someone in it is tagged with, and the word
1755
+ * a search for them leads with, where the libraries know one. A teacher is
1756
+ * shown teaching, a coach coaching, never a student or a whistle. A team or
1757
+ * a staff is anyone at work: its words are preferred, never required.
1758
+ *
1759
+ * A role whose photos are often of someone merely tagged with it carries the
1760
+ * words of it being done, one of which the photo's first tags must name
1761
+ * (`scene`), and the words of something else being done, which none of them
1762
+ * may (`refuse`): the beta.265 Northgate Adult Learning Center's "Meet your
1763
+ * teachers" placed a woman painting at an easel ("artist, teacher of
1764
+ * drawing"), a model in a traditional dress ("woman, model, show, teacher")
1765
+ * and an elderly man's portrait ("teacher, professor, man, senior").
1766
+ */ const PEOPLE_ROLES = [
1767
+ {
1768
+ words: [
1769
+ 'instructor',
1770
+ 'teacher',
1771
+ 'tutor',
1772
+ 'educator',
1773
+ 'faculty',
1774
+ 'professor',
1775
+ 'lecturer',
1776
+ 'mentor'
1777
+ ],
1778
+ kin: [
1779
+ 'teacher',
1780
+ 'instructor',
1781
+ 'tutor',
1782
+ 'educator',
1783
+ 'professor',
1784
+ 'lecturer',
1785
+ 'teaching',
1786
+ 'lecture',
1787
+ 'faculty'
1788
+ ],
1789
+ query: 'teacher',
1790
+ scene: [
1791
+ 'teaching',
1792
+ 'teach',
1793
+ 'classroom',
1794
+ 'class',
1795
+ 'lecture',
1796
+ 'lesson',
1797
+ 'lessons',
1798
+ 'tutor',
1799
+ 'tutoring',
1800
+ 'blackboard',
1801
+ 'chalkboard',
1802
+ 'whiteboard',
1803
+ 'board',
1804
+ 'seminar',
1805
+ 'student',
1806
+ 'students',
1807
+ 'learner',
1808
+ 'learners',
1809
+ 'presentation'
1810
+ ],
1811
+ refuse: [
1812
+ 'model',
1813
+ 'modeling',
1814
+ 'fashion',
1815
+ 'pose',
1816
+ 'posing',
1817
+ 'beauty',
1818
+ 'glamour',
1819
+ 'cosplay',
1820
+ 'artist',
1821
+ 'artists',
1822
+ 'painter',
1823
+ 'painting',
1824
+ 'paint',
1825
+ 'easel',
1826
+ 'canvas',
1827
+ 'draw',
1828
+ 'draws',
1829
+ 'drawing',
1830
+ 'calligraphy'
1831
+ ]
1832
+ },
1833
+ {
1834
+ words: [
1835
+ 'coach',
1836
+ 'trainer'
1837
+ ],
1838
+ kin: [
1839
+ 'coach',
1840
+ 'coaching',
1841
+ 'trainer',
1842
+ 'instructor'
1843
+ ],
1844
+ query: 'coach'
1845
+ },
1846
+ {
1847
+ words: [
1848
+ 'chef',
1849
+ 'cook',
1850
+ 'baker'
1851
+ ],
1852
+ kin: [
1853
+ 'chef',
1854
+ 'cook',
1855
+ 'cooking',
1856
+ 'baker',
1857
+ 'baking'
1858
+ ],
1859
+ query: 'chef'
1860
+ },
1861
+ {
1862
+ words: [
1863
+ 'stylist',
1864
+ 'barber',
1865
+ 'hairdresser',
1866
+ 'colorist'
1867
+ ],
1868
+ kin: [
1869
+ 'stylist',
1870
+ 'barber',
1871
+ 'hairdresser',
1872
+ 'hairstylist',
1873
+ 'haircut'
1874
+ ],
1875
+ query: 'hairdresser'
1876
+ },
1877
+ {
1878
+ words: [
1879
+ 'doctor',
1880
+ 'dentist',
1881
+ 'hygienist',
1882
+ 'nurse',
1883
+ 'therapist',
1884
+ 'practitioner',
1885
+ 'physician'
1886
+ ],
1887
+ kin: [
1888
+ 'doctor',
1889
+ 'dentist',
1890
+ 'hygienist',
1891
+ 'nurse',
1892
+ 'therapist',
1893
+ 'physician',
1894
+ 'medical'
1895
+ ]
1896
+ },
1897
+ {
1898
+ words: [
1899
+ 'staff',
1900
+ 'team',
1901
+ 'crew',
1902
+ 'member',
1903
+ 'manager',
1904
+ 'founder',
1905
+ 'owner',
1906
+ 'partner',
1907
+ 'associate',
1908
+ 'employee'
1909
+ ],
1910
+ kin: [
1911
+ 'staff',
1912
+ 'team',
1913
+ 'worker',
1914
+ 'employee',
1915
+ 'colleague',
1916
+ 'coworker',
1917
+ 'professional',
1918
+ 'businessman',
1919
+ 'businesswoman',
1920
+ 'owner',
1921
+ 'manager'
1922
+ ],
1923
+ loose: true
1924
+ }
1925
+ ];
1926
+ /** How many of a photo's first tags show a role being done: "teacher, presentation, education, man, students" (AGL-3856). */ export const AI_STOCK_ROLE_TAGS_READ = 6;
1927
+ /**
1928
+ * The role a person's words say, else the first of the other texts that names
1929
+ * one (their list's name): "Online learning coach" of the "Instructors" list
1930
+ * is a coach. `null` where none names a listed role.
1931
+ */ export function aiStockPeopleRole(...texts) {
1932
+ for (const text of texts){
1933
+ var _role_query;
1934
+ const named = new Set(stemList(text));
1935
+ const role = PEOPLE_ROLES.find((entry)=>entry.words.some((word)=>named.has(aiStockStem(word))));
1936
+ if (role) return _extends({
1937
+ kin: [
1938
+ ...new Set(role.kin.map(aiStockStem))
1939
+ ],
1940
+ query: (_role_query = role.query) != null ? _role_query : '',
1941
+ required: !role.loose
1942
+ }, role.scene ? {
1943
+ scene: [
1944
+ ...new Set(role.scene.map(aiStockStem))
1945
+ ]
1946
+ } : {}, role.refuse ? {
1947
+ refuse: [
1948
+ ...new Set(role.refuse.map(aiStockStem))
1949
+ ]
1950
+ } : {});
1951
+ }
1952
+ return null;
1953
+ }
1954
+ /** A role's refusals, less what the person's own words name: a "Drawing instructor" may be drawing. */ export function aiStockRoleRefusals(role, words) {
1955
+ var _role_refuse;
1956
+ const named = new Set(stemList(words));
1957
+ return ((_role_refuse = role.refuse) != null ? _role_refuse : []).filter((stem)=>!named.has(stem));
1958
+ }
1959
+ /** A site's audience in its own words, when it is adults: "Adults finishing a high school diploma". */ const ADULT_AUDIENCE = /\b(?:adults?|grown[- ]?ups?|seniors?|retirees?|older (?:adults|people)|professionals|employees|workforce|job ?seekers)\b/i;
1960
+ /** Words that say a site is for children too, whatever else it says. */ const YOUNG_AUDIENCE = /\b(?:kids?|child(?:ren)?|teens?|teenagers?|youth|young people|famil(?:y|ies)|boys?|girls?|bab(?:y|ies)|toddlers?|k-12|elementary|middle school|preschool|kindergarten|pupils?)\b/i;
1961
+ /**
1962
+ * Whether a site is for adults alone (AGL-3856), as its audience, its name
1963
+ * or its business type says: "Adults finishing a high school diploma",
1964
+ * "Brightpath Adult Learning Center". Then no photo of a child is placed
1965
+ * anywhere on it, whatever else its words name ("school").
1966
+ */ export function aiStockAdultsOnly(texts) {
1967
+ const said = texts.filter(Boolean).join(' . ');
1968
+ return ADULT_AUDIENCE.test(said) && !YOUNG_AUDIENCE.test(said);
1969
+ }
1669
1970
  /** 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 = [
1670
1971
  {
1671
1972
  words: [
@@ -1739,12 +2040,10 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1739
2040
  * family in autumn — read from every word, never only the first clause.
1740
2041
  * `null` where it names no people.
1741
2042
  */ export function aiStockScene(text) {
1742
- var _ref;
1743
- var _SCENE_SETTINGS_find;
1744
- const named = new Set(text.toLowerCase().split(/[^a-zÀ-ɏ]+/).filter(Boolean));
2043
+ const named = sceneWords(text);
1745
2044
  const scene = PEOPLE_SCENES.find((entry)=>entry.words.some((word)=>named.has(word)));
1746
2045
  if (!scene) return null;
1747
- const setting = (_ref = (_SCENE_SETTINGS_find = SCENE_SETTINGS.find((entry)=>entry.words.some((word)=>named.has(word)))) == null ? void 0 : _SCENE_SETTINGS_find.said) != null ? _ref : '';
2046
+ const setting = aiStockSetting(text);
1748
2047
  const query = scene.query(named);
1749
2048
  return {
1750
2049
  queries: [
@@ -1766,6 +2065,69 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1766
2065
  ]
1767
2066
  };
1768
2067
  }
2068
+ /** A text's lowercase words, letters only. */ const sceneWords = (text)=>new Set(text.toLowerCase().split(/[^a-zÀ-ɏ]+/).filter(Boolean));
2069
+ /** Where a text's words set a picture ({@link SCENE_SETTINGS}): "mountains" of "Couples planning mountain weddings"; empty for none. */ export function aiStockSetting(text) {
2070
+ var _ref;
2071
+ var _SCENE_SETTINGS_find;
2072
+ const named = sceneWords(text);
2073
+ return (_ref = (_SCENE_SETTINGS_find = SCENE_SETTINGS.find((entry)=>entry.words.some((word)=>named.has(word)))) == null ? void 0 : _SCENE_SETTINGS_find.said) != null ? _ref : '';
2074
+ }
2075
+ /**
2076
+ * The moment a portfolio's record shows (AGL-3856): read from the record's
2077
+ * own name first, then from what it is filed under, then as a portrait, and
2078
+ * set where its name, its category or else the site's own words say. A
2079
+ * photographer's "Getting Ready" filed under "Weddings and elopements" is a
2080
+ * wedding couple in the mountains; "Milestone Portraits" filed under "Family
2081
+ * portraits" a family. Its searches end on the people alone ("couple"), so a
2082
+ * moment no photo shows in its setting still shows its people. `null` where
2083
+ * neither names people at a moment nor a portrait.
2084
+ */ export function aiStockRecordScene(name, category = '', siteSetting = '') {
2085
+ var _texts_find, _PEOPLE_SCENES_find, _texts_map_find;
2086
+ const texts = [
2087
+ name,
2088
+ category
2089
+ ].filter((text)=>text.trim());
2090
+ const source = (_texts_find = texts.find((text)=>PEOPLE_SCENES.some((entry)=>entry.words.some((word)=>sceneWords(text).has(word))))) != null ? _texts_find : texts.find((text)=>PORTRAIT_SCENE.words.some((word)=>sceneWords(text).has(word)));
2091
+ if (!source) return null;
2092
+ const named = sceneWords(source);
2093
+ const scene = (_PEOPLE_SCENES_find = PEOPLE_SCENES.find((entry)=>entry.words.some((word)=>named.has(word)))) != null ? _PEOPLE_SCENES_find : PORTRAIT_SCENE;
2094
+ const setting = (_texts_map_find = texts.map(aiStockSetting).find(Boolean)) != null ? _texts_map_find : siteSetting;
2095
+ const query = scene.query(named);
2096
+ const all = sceneWords(texts.join(' '));
2097
+ return {
2098
+ queries: [
2099
+ ...new Set([
2100
+ setting ? `${query} ${setting}` : '',
2101
+ query,
2102
+ scene.subject
2103
+ ].filter(Boolean))
2104
+ ],
2105
+ subject: [
2106
+ scene.subject,
2107
+ setting
2108
+ ].filter(Boolean).join(' '),
2109
+ object: scene.subject,
2110
+ // A record's cover leads with its people: a wedding's table decor is not "Overlook Ceremonies".
2111
+ lead: scene.lead.filter((phrase)=>!RECORD_SCENE_UNPEOPLED.has(phrase)).map((phrase)=>stemList(phrase).join(' ')),
2112
+ people: scene.people.map(aiStockStem),
2113
+ shun: scene.shun.filter((word)=>!all.has(word)).map(aiStockStem),
2114
+ named: [
2115
+ ...new Set(stemList(texts.join(' ')))
2116
+ ]
2117
+ };
2118
+ }
2119
+ /**
2120
+ * A scene's lead words a record's cover never leads with alone
2121
+ * ({@link aiStockRecordScene}): the moment with no one in it (a wedding's table
2122
+ * decor for "Overlook Ceremonies"), or children with no family (a ski class
2123
+ * for "Milestone Portraits").
2124
+ */ const RECORD_SCENE_UNPEOPLED = words('wedding elopement child children kid kids baby toddler');
2125
+ /**
2126
+ * What a portfolio's record is never OF, among its first tags, unless its own
2127
+ * words name it (AGL-3856): an animal, a toy or a figurine. The beta.265
2128
+ * Laurel Ridge "Gallery collections" covers were a toy frog with a camera, a
2129
+ * toy duck and a cat's portrait.
2130
+ */ const RECORD_ODD_LEADS = words('animal animals cat cats kitten dog dogs puppy pet pets bird birds frog frogs duck ducks horse horses cow rabbit bunny ' + 'toy toys figurine figurines lego doll dolls teddy puppet miniature');
1769
2131
  /** An object's kin phrases as stems, or none. */ const kinStems = (entry)=>entry ? [
1770
2132
  ...new Set(entry.kin.map((phrase)=>stemList(phrase).join(' ')))
1771
2133
  ] : [];
@@ -1987,8 +2349,14 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1987
2349
  }
1988
2350
  return searches;
1989
2351
  }
1990
- /** The searches a slot tries, in order, most specific first, each distinct. */ export function aiStockSearchesFor(slot, terms, subject, part) {
1991
- var _terms_core, _terms_making, _ref;
2352
+ /**
2353
+ * The searches a slot tries, in order, most specific first, each distinct.
2354
+ * A portrait of one of the business's people ({@link AiLayoutPictureSlot.people})
2355
+ * asks first for the role its words or its list's name (`sectionName`) say,
2356
+ * in the site's world: "teacher classroom" for a "Math instructor" (AGL-3856).
2357
+ */ export function aiStockSearchesFor(slot, terms, subject, part, sectionName = '') {
2358
+ var _terms_core, _terms_making, _ref, _ref1, _terms_setting, _ref2, _terms_maker, _ref3;
2359
+ var _aiStockPeopleRole, _slot_record, _slot_record1;
1992
2360
  const orientation = aiStockOrientation(slot.aspect);
1993
2361
  const size = slot.role === 'hero' ? {
1994
2362
  minWidth: 1600
@@ -1998,6 +2366,11 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
1998
2366
  minWidth: 900
1999
2367
  };
2000
2368
  const domain = new Set(terms.domain);
2369
+ // A portfolio's record of a craft whose work is people at a moment shows the
2370
+ // moment, never the maker: the beta.265 Laurel Ridge "Gallery collections"
2371
+ // searched "photographer mountain elopements" and placed a press
2372
+ // photographer, a toy frog with a camera and a cat (AGL-3856).
2373
+ const work = aiStockShowsTheWork(slot, terms);
2001
2374
  const object = aiStockObjectWord(subject);
2002
2375
  const subjectStems = stemList(subject);
2003
2376
  const kin = object ? objectKin(object, [
@@ -2017,23 +2390,28 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2017
2390
  // A collection tile is product-like: held to the shop's category and its
2018
2391
  // own object among the first tags, and searched by its object's kin, never
2019
2392
  // the category's world alone (AGL-3660).
2020
- const collection = part === 'collection' && Boolean(terms.craft);
2021
- const broad = kin && collection ? [] : terms.broad.map((query)=>({
2393
+ const collection = part === 'collection' && Boolean(terms.craft) && !work;
2394
+ const broad = kin && collection || work ? [] : terms.broad.map((query)=>({
2022
2395
  query,
2023
2396
  broad: true,
2024
2397
  category: collection
2025
2398
  }));
2026
2399
  // The maker at work, held to the craft: never "hands" (AGL-3660).
2027
- const making = ((_terms_making = terms.making) != null ? _terms_making : []).map((query)=>({
2400
+ const making = (work ? [] : (_terms_making = terms.making) != null ? _terms_making : []).map((query)=>({
2028
2401
  query,
2029
2402
  broad: true,
2030
2403
  category: true
2031
2404
  }));
2405
+ // A portrait in its role, in the site's world, where the libraries know a word for it.
2406
+ const roleWord = slot.people ? (_ref = (_aiStockPeopleRole = aiStockPeopleRole(slot.alt, sectionName)) == null ? void 0 : _aiStockPeopleRole.query) != null ? _ref : '' : '';
2407
+ const role = roleWord ? stemList(terms.business).includes(roleWord) ? terms.business : `${roleWord} ${terms.business}`.trim() : '';
2032
2408
  // A scene's people are read from every word of the picture's, never its first clause alone.
2033
- const scene = terms.scenes && slot.role !== 'hero' && part !== 'collection' ? aiStockScene(slot.alt) : null;
2409
+ 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;
2410
+ // The work's own subject is never qualified by the craft: "getting ready", never "photographer getting ready".
2411
+ const own = work ? (phrase)=>phrase : qualify;
2034
2412
  const thing = subject ? [
2035
2413
  {
2036
- query: qualify(subject),
2414
+ query: own(subject),
2037
2415
  held: true
2038
2416
  },
2039
2417
  ...kin ? kin.queries.map((query)=>({
@@ -2041,11 +2419,21 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2041
2419
  held: true
2042
2420
  })) : [
2043
2421
  {
2044
- query: qualify(object),
2422
+ query: own(object),
2045
2423
  held: true
2046
2424
  }
2047
2425
  ]
2048
2426
  ] : [];
2427
+ // Never the maker, the camera, an animal or a toy, unless the record names it.
2428
+ const recordNamed = work ? new Set(stemList(`${slot.alt} ${(_ref2 = (_slot_record1 = slot.record) == null ? void 0 : _slot_record1.category) != null ? _ref2 : ''}`)) : null;
2429
+ const refuseLead = recordNamed ? [
2430
+ ...new Set([
2431
+ ...(_terms_maker = terms.maker) != null ? _terms_maker : [],
2432
+ ...[
2433
+ ...RECORD_ODD_LEADS
2434
+ ].map(aiStockStem)
2435
+ ])
2436
+ ].filter((stem)=>!recordNamed.has(stem)) : [];
2049
2437
  const queries = slot.role === 'hero' ? [
2050
2438
  {
2051
2439
  query: terms.business,
@@ -2053,6 +2441,13 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2053
2441
  },
2054
2442
  ...broad
2055
2443
  ] : slot.role === 'about' ? [
2444
+ ...role ? [
2445
+ {
2446
+ query: role,
2447
+ broad: true,
2448
+ people: true
2449
+ }
2450
+ ] : [],
2056
2451
  ...part === 'story' ? making : [],
2057
2452
  {
2058
2453
  query: `${terms.business} ${subject}`,
@@ -2076,7 +2471,7 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2076
2471
  // A picture of people at a moment asks for them, and where, before anything
2077
2472
  // else, and takes only a photo leading with them (AGL-3856): never the
2078
2473
  // roses of a "Mountain Wedding Day", nor a bride for a family session.
2079
- for (const query of (_ref = scene == null ? void 0 : scene.queries) != null ? _ref : []){
2474
+ for (const query of (_ref3 = scene == null ? void 0 : scene.queries) != null ? _ref3 : []){
2080
2475
  seen.add(`${query}|false`);
2081
2476
  searches.push(_extends({
2082
2477
  query,
@@ -2091,6 +2486,10 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2091
2486
  allow: scene.named
2092
2487
  }, scene.shun.length ? {
2093
2488
  refuse: scene.shun
2489
+ } : {}, refuseLead.length ? {
2490
+ refuseLead
2491
+ } : {}, work ? {
2492
+ worldless: true
2094
2493
  } : {}));
2095
2494
  }
2096
2495
  for (const raw of queries){
@@ -2121,10 +2520,23 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2121
2520
  category: core
2122
2521
  } : {}, slot.role === 'hero' ? {
2123
2522
  first: true
2523
+ } : {}, refuseLead.length ? {
2524
+ refuseLead
2525
+ } : {}, work ? {
2526
+ worldless: true
2124
2527
  } : {}));
2125
2528
  }
2126
2529
  return searches;
2127
2530
  }
2531
+ /**
2532
+ * Whether a slot is a portfolio's record of a craft whose work is people at a
2533
+ * moment (AGL-3856): a photographer's "Mountain Elopements" shows an
2534
+ * elopement, never the photographer. Searched by the moment its words and its
2535
+ * category name, held to no world of the craft, and never led by the craft's
2536
+ * own words, an animal or a toy unless the record names them.
2537
+ */ export function aiStockShowsTheWork(slot, terms) {
2538
+ return Boolean(slot.record) && Boolean(terms.scenes) && slot.role !== 'hero' && !slot.people && !slot.product;
2539
+ }
2128
2540
  /** The words of a photo's page address at its library, its id dropped: "robin-bird-songbird-garden-winter". */ function pageSlug(pageUrl) {
2129
2541
  try {
2130
2542
  var _ref;
@@ -2197,7 +2609,8 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2197
2609
  * the subject's two-word phrase 3, each of its other words 1, and the
2198
2610
  * site's world 1, plus a quarter when it is among the first tags.
2199
2611
  */ export function aiStockRelevance(photo, judgement) {
2200
- var _judgement_rivals, _judgement_subject, _judgement_named, _judgement_shun, _judgement_refuse, _judgement_needs, _judgement_shunLead, _judgement_leadAny, _judgement_kin, _judgement_requires, _judgement_category, _judgement_object, _judgement_domain;
2612
+ 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;
2613
+ var _judgement_roleScene, _judgement_roleRefuse;
2201
2614
  const hit = hitWords(photo);
2202
2615
  if (hit.rejected) return 0;
2203
2616
  if (aiStockSensitive(photo, judgement.named)) return 0;
@@ -2213,6 +2626,18 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2213
2626
  if (((_judgement_shun = judgement.shun) != null ? _judgement_shun : []).some((word)=>hit.all.has(word) && !owned.has(word))) return 0;
2214
2627
  // A family session is never a bride, however the photographer's brief names weddings.
2215
2628
  if (((_judgement_refuse = judgement.refuse) != null ? _judgement_refuse : []).some((word)=>hit.all.has(word))) return 0;
2629
+ // A teacher's portrait is never OF a child, whatever else it names.
2630
+ if (((_judgement_refuseLead = judgement.refuseLead) != null ? _judgement_refuseLead : []).some((word)=>hit.lead.has(word))) return 0;
2631
+ // A teacher is shown teaching: a classroom, a lecture or students among the
2632
+ // first tags, and never a model, a pose or a painting (AGL-3856).
2633
+ if (((_judgement_roleScene = judgement.roleScene) == null ? void 0 : _judgement_roleScene.length) || ((_judgement_roleRefuse = judgement.roleRefuse) == null ? void 0 : _judgement_roleRefuse.length)) {
2634
+ var _photo_tags, _photo_tags1, _judgement_roleRefuse1;
2635
+ var _judgement_roleScene1;
2636
+ const first = new Set(stemList(((_photo_tags = photo.tags) != null ? _photo_tags : []).slice(0, AI_STOCK_ROLE_TAGS_READ).join(' ')));
2637
+ if (((_judgement_roleScene1 = judgement.roleScene) == null ? void 0 : _judgement_roleScene1.length) && !judgement.roleScene.some((stem)=>first.has(stem))) return 0;
2638
+ const read = new Set(stemList(((_photo_tags1 = photo.tags) != null ? _photo_tags1 : []).slice(0, ALT_TAGS_READ).join(' ')));
2639
+ if (((_judgement_roleRefuse1 = judgement.roleRefuse) != null ? _judgement_roleRefuse1 : []).some((stem)=>read.has(stem))) return 0;
2640
+ }
2216
2641
  // A wedding day shows its people: a close-up of roses tagged "wedding" never fills it.
2217
2642
  const needs = (_judgement_needs = judgement.needs) != null ? _judgement_needs : [];
2218
2643
  if (needs.length && !needs.some((word)=>hit.all.has(word))) return 0;
@@ -2294,6 +2719,10 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2294
2719
  if (phrased) score += 3;
2295
2720
  for (const word of new Set(subject))if (word !== object && hit.all.has(word)) score += 1;
2296
2721
  if (world) score += worldLead ? 1.25 : 1;
2722
+ // A portrait in its role: a teacher at the board over a woman in a classroom.
2723
+ const roles = (_judgement_roles = judgement.roles) != null ? _judgement_roles : [];
2724
+ if (roles.some((word)=>hit.lead.has(word))) score += 4;
2725
+ else if (roles.some((word)=>hit.all.has(word))) score += 2;
2297
2726
  // A hero is most of all what its first tags say: "law office, lawyer,
2298
2727
  // attorney" over "lawyers, personal injury, accident" (AGL-3856): a second
2299
2728
  // of them naming its world says it is of that world, not of something else.
@@ -2328,6 +2757,36 @@ const clip = (query)=>query.replace(/\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUE
2328
2757
  }
2329
2758
  return ranked;
2330
2759
  }
2760
+ /**
2761
+ * Whether two hits are of one shoot (AGL-3856): one contributor's photos a few
2762
+ * uploads apart, as Pixabay numbers them — the beta.265 Granite Peak Barbell
2763
+ * coaches were four of one model, 728223 to 728229 — else one page address.
2764
+ */ export function aiStockSameShoot(a, b) {
2765
+ const near = Number(a.id) && Number(b.id) ? Math.abs(Number(a.id) - Number(b.id)) < 100 : false;
2766
+ if (a.photographer && a.photographer === b.photographer && near) return true;
2767
+ const slug = pageSlug(a.pageUrl);
2768
+ return Boolean(slug) && slug === pageSlug(b.pageUrl);
2769
+ }
2770
+ /**
2771
+ * A search's ranked hits with those of a shoot no slot shows yet first among
2772
+ * the best (AGL-3856): a team's three cards are three people where the
2773
+ * library has them, never one model three times while another fits as well.
2774
+ * Every other order is kept.
2775
+ */ export function aiStockFreshShootsFirst(ranked, shown, judgement) {
2776
+ if (!shown.length || ranked.length < 2) return [
2777
+ ...ranked
2778
+ ];
2779
+ const best = aiStockRelevance(ranked[0], judgement);
2780
+ const top = ranked.filter((photo)=>aiStockRelevance(photo, judgement) === best);
2781
+ const fresh = top.filter((photo)=>!shown.some((other)=>aiStockSameShoot(photo, other)));
2782
+ if (!fresh.length || fresh[0] === ranked[0]) return [
2783
+ ...ranked
2784
+ ];
2785
+ return [
2786
+ ...fresh,
2787
+ ...ranked.filter((photo)=>!fresh.includes(photo))
2788
+ ];
2789
+ }
2331
2790
  /** The pick among a search's hits: the first {@link aiStockRank} would try, or `null`. */ export function aiStockPick(photos, used, seed, judgement = {}) {
2332
2791
  var _aiStockRank_;
2333
2792
  return (_aiStockRank_ = aiStockRank(photos, used, seed, judgement)[0]) != null ? _aiStockRank_ : null;
@@ -2374,33 +2833,74 @@ function jobPhotos(key) {
2374
2833
  /** The longest description of a photo written from the library's own words. */ const AI_STOCK_ALT_MAX = 160;
2375
2834
  /**
2376
2835
  * Words a tag uses for how a photo looks or feels, never a thing it shows
2377
- * (AGL-3856): "extravagant" of "wedding dress, bride, extravagant".
2378
- */ 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');
2836
+ * (AGL-3856): "extravagant" of "wedding dress, bride, extravagant", "moody"
2837
+ * of "forest, human, photographer", a season or a people's name.
2838
+ */ 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).
2839
+ '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".
2840
+ 'silhouette silhouettes cutout perspective bokeh closeup ' + // A holiday names when, never a thing: "tet market" is a market.
2841
+ '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');
2379
2842
  /**
2380
2843
  * Words a tag uses for what a person or a thing is like, said before it
2381
- * rather than as a thing of its own (AGL-3856): "a confident lawyer", never
2382
- * "a lawyer, law and a confident".
2383
- */ const QUALITIES = words('confident professional successful serious smart busy creative proud skilled experienced determined focused ' + 'senior junior expert friendly cheerful smiling');
2384
- /** What a tag names that takes no article: "chalk" of "board, chalk". */ const MASS_NOUNS = words('food coffee tea bread wood water fitness wax soap clay nature money fabric yarn wool furniture jewelry jewellery ' + 'pottery glass paper music art sport hair makeup chalk paint ink dust stone marble concrete steel metal ' + 'skincare equipment decor fashion wildlife scenery weather traffic wine beer chocolate rice sugar flour milk ' + 'cheese honey oil ice fire light sunlight dough sourdough pastry dessert crossfit people foliage greenery ' + 'autumn winter spring summer');
2844
+ * rather than as a thing of its own (AGL-3856): "a confident lawyer", "an
2845
+ * elderly couple", "an analog camera", never "a lawyer, law and a confident".
2846
+ */ const QUALITIES = words('confident professional successful serious smart busy creative proud skilled experienced determined focused ' + 'senior junior expert friendly cheerful smiling elderly analog analogue digital mirrorless');
2847
+ /** What a tag names that takes no article: "chalk" of "board, chalk". */ const MASS_NOUNS = words('food coffee tea bread wood water wax soap clay money fabric yarn wool furniture jewelry jewellery ' + 'pottery glass paper music art hair makeup chalk paint ink dust stone marble concrete steel metal ' + 'skincare equipment decor fashion wildlife weather traffic wine beer chocolate rice sugar flour milk ' + 'cheese honey oil ice fire light sunlight dough sourdough pastry dessert people foliage greenery faculty staff ' + 'clothing bedding frosting icing flooring');
2848
+ /** Nouns plural without an "-s": said without an article, "women in a classroom". */ const PLURAL_NOUNS = words('men women people children feet teeth mice geese sheep fish');
2849
+ /**
2850
+ * What a tag names that no photo shows as a thing of its own (AGL-3856): an
2851
+ * idea ("law" of "lawyer, law", "feedback" of "board, chalk, feedback"), a
2852
+ * doing ("read", "lift", "fitness", "a workout"), a word for the photo itself
2853
+ * ("photo", "spot"). Left out, never said with an article: "A bookwork and a
2854
+ * read", "A kettlebell and fitness", "A board and training" said nothing.
2855
+ */ 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');
2856
+ /** 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');
2857
+ /** 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));
2385
2858
  /**
2386
- * What a tag names that no photo shows as a thing of its own (AGL-3856): "law"
2387
- * of "lawyer, law", "feedback" of "board, chalk, feedback". Left out, never
2388
- * said with an article.
2389
- */ 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');
2859
+ * Makers' names a tag gives, never what a photo shows to someone who cannot
2860
+ * see it (AGL-3856): "A photo-camera and a sony".
2861
+ */ 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 ' + 'petri zenit praktica yashica minolta mamiya rolleiflex voigtlander contax ricoh konica lomo lomography holga zeiss');
2390
2862
  /** Stopwords a description still says, as the people a photo shows: "a family", "a person". */ const SAID_PEOPLE = words('people person family');
2391
- /** 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');
2392
- /** People a photo shows; the generic ones give way to a role ("man" to "businessman"). */ const PEOPLE_NOUNS = words('man men woman women person people boy girl guy lady child children kid kids baby family couple bride groom ' + 'businessman businesswoman businessmen lawyer attorney doctor dentist nurse chef cook baker barista athlete ' + 'trainer coach student students teacher worker entrepreneur model photographer potter artist florist mother ' + 'father parent parents grandmother grandfather');
2393
- const GENERIC_PEOPLE = words('man men woman women person people guy lady adult');
2863
+ /** 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');
2864
+ /** "-ing" words that are things, and take no article: "clothing". */ const UNCOUNTED_ING = words('clothing bedding frosting icing flooring stuffing seasoning filling');
2865
+ /** People a photo shows; the generic ones give way to a role ("man" to "businessman"). */ const PEOPLE_NOUNS = words('man men woman women person people human humans boy boys girl girls guy guys lady ladies child children kid kids ' + 'baby family couple bride groom schoolgirl schoolboy teen teenager student students graduate athlete athletes ' + 'businessman businesswoman businessmen lawyer attorney doctor dentist nurse chef cook baker barista ' + 'trainer coach teacher teachers instructor lecturer professor tutor worker workers entrepreneur model ' + 'photographer potter artist florist mother father parent parents grandmother grandfather grandparents ' + 'faculty staff team participants volunteer volunteers musician dancer leader manager owner founder ' + 'journalist journalists reporter reporters master masters lovers newlywed newlyweds husband wife fiance fiancee spouse');
2866
+ /** People said by no role: a model is whoever posed (AGL-3856), "a woman" of "woman, model, show, teacher". */ const GENERIC_PEOPLE = words('man men woman women person people human humans guy guys lady ladies adult boy boys girl girls model models');
2867
+ /**
2868
+ * A body's parts, never a thing a photo is described by (AGL-3856): "A girl
2869
+ * with a hand" said nothing, and "A body in a gym" is a gym.
2870
+ */ const BODY_PARTS = words('hand hands finger fingers arm arms leg legs foot feet body bodies face faces eye eyes skin head heads lip lips mouth nose ' + 'ear ears neck shoulder shoulders knee knees');
2871
+ /**
2872
+ * Words for a kind of thing, said only where nothing names the thing itself
2873
+ * (AGL-3856): "A domestic cat and an animal" is a cat.
2874
+ */ const KINDS_OF_THING = words('animal animals pet pets creature creatures mammal mammals object objects thing things item items');
2875
+ /** How many of a photo's first tags a person leads its description from, where it names a thing too: "A camera" of "petri, camera, film, photography, photographer" (AGL-3856). */ const ALT_PERSON_TAGS_READ = 4;
2876
+ /** How many of a photo's first tags a role is said from, over a man or a woman before it: "A man" of "bed, book, man, …, student" (AGL-3856). */ const ALT_ROLE_TAGS_READ = 6;
2877
+ /** What a person in a photo wears, said "in": "a bride in a wedding dress". */ const CLOTHING = words('dress gown suit uniform costume coat jacket robe apron');
2394
2878
  /**
2395
2879
  * Places a photo is set in, each with how a phrase says it is there: "a
2396
- * train at a station", "a lawyer in court".
2880
+ * train at a station", "a lawyer in court", "a couple at a wedding".
2397
2881
  */ const PLACES = {
2398
2882
  station: 'at',
2399
2883
  market: 'at',
2400
2884
  desk: 'at',
2401
2885
  table: 'at',
2402
2886
  counter: 'at',
2887
+ school: 'at',
2888
+ college: 'at',
2889
+ university: 'at',
2890
+ wedding: 'at',
2891
+ sunset: 'at',
2892
+ sunrise: 'at',
2893
+ dusk: 'at',
2894
+ dawn: 'at',
2403
2895
  beach: 'on',
2896
+ bed: 'on',
2897
+ sofa: 'on',
2898
+ couch: 'on',
2899
+ bench: 'on',
2900
+ trail: 'on',
2901
+ path: 'on',
2902
+ bridge: 'on',
2903
+ street: 'on',
2404
2904
  court: 'in',
2405
2905
  courtroom: 'in',
2406
2906
  office: 'in',
@@ -2408,6 +2908,8 @@ const GENERIC_PEOPLE = words('man men woman women person people guy lady adult')
2408
2908
  gym: 'in',
2409
2909
  studio: 'in',
2410
2910
  classroom: 'in',
2911
+ class: 'in',
2912
+ hall: 'in',
2411
2913
  room: 'in',
2412
2914
  garden: 'in',
2413
2915
  park: 'in',
@@ -2423,35 +2925,57 @@ const GENERIC_PEOPLE = words('man men woman women person people guy lady adult')
2423
2925
  forest: 'in',
2424
2926
  city: 'in',
2425
2927
  church: 'in',
2426
- field: 'in'
2928
+ field: 'in',
2929
+ meadow: 'in',
2930
+ valley: 'in',
2931
+ mountain: 'in',
2932
+ hill: 'on',
2933
+ cliff: 'on',
2934
+ river: 'by',
2935
+ lake: 'by',
2936
+ waterfall: 'at'
2937
+ };
2938
+ /** Places that are things too, a photo's subject where nothing else is: "a desk in a classroom". */ const OBJECT_PLACES = words('desk table counter bridge bed sofa couch bench');
2939
+ /** Places said without an article: "in court", "at sunset". */ const BARE_PLACES = words('court sunset sunrise dusk dawn');
2940
+ /** Places said in words of their own: a mountain tag is "in the mountains". */ const PLACES_SAID = {
2941
+ mountain: 'the mountains'
2427
2942
  };
2428
- /** Places said without an article: "in court". */ const BARE_PLACES = words('court');
2943
+ /** How many of a photo's first tags its description reads: the things and people it is OF. */ const ALT_TAGS_READ = 8;
2944
+ /** How far a photo's tags are read for where it is set, said last: "in a gym" of a kettlebell's seventh tag. */ const ALT_PLACE_TAGS_READ = 10;
2945
+ /** A tag's words, a word for the photo itself taken off a compound: "photo-camera" is a camera. */ function tagWords(tag) {
2946
+ return tag.toLowerCase().replace(/[^a-zÀ-ɏ\s'-]/g, ' ').replace(/\b(?:photo|picture|image)-(?=[a-z])/g, '').split(/\s+/).filter(Boolean);
2947
+ }
2429
2948
  /**
2430
2949
  * A tag as a phrase of an English description (AGL-3856): "a wedding dress",
2431
2950
  * "an attorney", "books", "chalk", "Columbus"; `null` for a tag that names no
2432
- * thing ("extravagant", "beautiful", "feedback") or is too long to be one.
2951
+ * thing ("extravagant", "feedback", "training", "sony") or is too long to be
2952
+ * one.
2433
2953
  */ function describedTag(tag, proper) {
2434
2954
  const describing = (word)=>FILLER.has(word) || DESCRIBING.has(word) || QUALITIES.has(word) || /(?:ous|ful)$/.test(word) || word.length > 4 && /[^e]ed$/.test(word);
2435
- const all = tag.toLowerCase().replace(/[^a-zÀ-ɏ\s'-]/g, ' ').split(/\s+/).filter(Boolean);
2436
- // "Beautiful flowers" shows flowers; "roasted" shows nothing.
2437
- const list = all.filter((word)=>!describing(word));
2955
+ const all = tagWords(tag);
2956
+ // "Beautiful flowers" shows flowers; "roasted" shows nothing; "photo model"
2957
+ // is a model and "traveling couples" are couples: a word before the last
2958
+ // that is a doing says what they do, never what they are.
2959
+ const list = all.filter((word, index)=>!describing(word) && !(all.length > 1 && /^(?:photo|picture|image)$/.test(word)) && !(index < all.length - 1 && index > 0 && word.endsWith('ing') && !COUNTED_ING.has(word)));
2438
2960
  if (!list.length || all.length > 3) return null;
2439
2961
  // A phrase of no thing ("up close") or a doing ("take pictures", "to lift", "work out") is not what it shows.
2440
2962
  if (list.every((word)=>STOPWORDS.has(word) && !SAID_PEOPLE.has(word))) return null;
2441
- if (/^(?:to|take|taking|make|making|get|do|go|lie)$/.test(list[0])) return null;
2963
+ if (/^(?:to|take|taking|make|making|get|do|go|lie|in|on|at|for|with|by|of)$/.test(list[0])) return null;
2442
2964
  if (/^(?:out|up|off|down|away|on|in)$/.test(list[list.length - 1])) return null;
2965
+ if (list.some((word)=>BRANDS.has(word))) return null;
2443
2966
  if (list.every((word)=>proper.has(word))) return list.map((word)=>`${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
2444
2967
  const last = list[list.length - 1];
2445
- // An idea, never a thing: "law", "feedback", "customer satisfaction".
2446
- if (ABSTRACT_NOUNS.has(last)) return null;
2968
+ // An idea or a doing, never a thing: "law", "feedback", "customer satisfaction", "participation", "weightlifting".
2969
+ if (ABSTRACT_NOUNS.has(last) || ABSTRACT_NOUNS.has(aiStockStem(last)) || saysAnIdea(last)) return null;
2970
+ const doing = last.endsWith('ing') && !COUNTED_ING.has(aiStockStem(last)) && !UNCOUNTED_ING.has(last);
2971
+ if (doing) return null;
2447
2972
  const phrase = list.join(' ');
2448
- const plural = aiStockStem(last) !== last && !/^(?:lens|canvas|atlas|gas|bus)$/.test(last);
2449
- const uncounted = MASS_NOUNS.has(last) || last.endsWith('ing') && !COUNTED_ING.has(last);
2450
- if (plural || uncounted || STOPWORDS.has(list[0]) && !SAID_PEOPLE.has(list[0])) return phrase;
2973
+ const plural = PLURAL_NOUNS.has(last) || aiStockStem(last) !== last && !/^(?:lens|canvas|atlas|gas|bus)$/.test(last);
2974
+ if (plural || MASS_NOUNS.has(last) || STOPWORDS.has(list[0]) && !SAID_PEOPLE.has(list[0])) return phrase;
2451
2975
  return `${articleFor(phrase)} ${phrase}`;
2452
2976
  }
2453
2977
  /** "a" or "an" before a phrase. */ const articleFor = (phrase)=>/^(?:[aeio]|u(?!ni|s[eu]))/.test(phrase) ? 'an' : 'a';
2454
- /** Whether two words name one thing, one inside the other: "law" and "lawyer", "man" and "businessman". */ function nearSynonyms(a, b) {
2978
+ /** Whether two words name one thing, one inside the other: "law" and "lawyer", "cam" and "camera". */ function nearSynonyms(a, b) {
2455
2979
  const [short, long] = a.length <= b.length ? [
2456
2980
  a,
2457
2981
  b
@@ -2463,28 +2987,41 @@ const GENERIC_PEOPLE = words('man men woman women person people guy lady adult')
2463
2987
  }
2464
2988
  /**
2465
2989
  * A photo described from its own tags (AGL-3856): a short English phrase of
2466
- * what its first tags show — one or two things, or a thing and where it is,
2467
- * one person at most — never the library's comma-separated list: "A train at
2468
- * a station", "A confident lawyer in court", "A portrait of a businessman",
2469
- * "A board and chalk". A tag of how it looks is said before the thing it
2470
- * qualifies; an idea ("law", "feedback") is left out; a word inside another
2471
- * ("law" of "lawyer", "man" of "businessman") gives way to it; a mass noun
2472
- * takes no article. `null` where no tag names a thing.
2990
+ * concrete things, never the library's comma-separated list: "A train at a
2991
+ * station", "A confident lawyer in court", "A portrait of a businessman", "A
2992
+ * photographer with a camera on a beach", "A kettlebell in a gym", "An analog
2993
+ * camera". It says one person at most (a role over a man or a woman, an adult
2994
+ * over a girl), what they hold or wear, or one or two things, and where:
2995
+ *
2996
+ * - an idea or a doing ("law", "feedback", "training", "a read",
2997
+ * "participation"), a maker's name ("sony"), a word for the photo itself
2998
+ * ("photo", "spot") and a look ("moody", "analog" alone, a season) are left
2999
+ * out; a quality ("elderly", "analog") is said before the one thing it
3000
+ * qualifies;
3001
+ * - of two words for one thing the first tag's stays ("book" over
3002
+ * "bookwork", "camera" over "photo-camera"), unless the later phrase holds
3003
+ * it whole ("wedding dress" over "wedding");
3004
+ * - a second thing comes only from the first two tags and where no place is
3005
+ * said, what a person holds only from the first three, where it is set
3006
+ * from the first ten;
3007
+ * - no word is said twice: never "an elderly couple and an elderly".
3008
+ *
3009
+ * `null` where no tag names a thing.
2473
3010
  */ export function aiStockTagsAlt(tags, proper = new Set()) {
2474
- var _people_find;
2475
3011
  const parts = [];
2476
3012
  let quality = '';
2477
3013
  let portrait = false;
2478
- for (const tag of tags.slice(0, 6)){
3014
+ for (const [index, tag] of tags.slice(0, ALT_PLACE_TAGS_READ).entries()){
2479
3015
  var _ref;
2480
3016
  var _bare_split_pop;
2481
3017
  const lower = tag.toLowerCase().trim();
2482
- if (lower === 'portrait' || lower === 'portraits') {
3018
+ const reading = index < ALT_TAGS_READ;
3019
+ if (reading && (lower === 'portrait' || lower === 'portraits')) {
2483
3020
  portrait = true;
2484
3021
  continue;
2485
3022
  }
2486
3023
  if (QUALITIES.has(lower)) {
2487
- quality || (quality = lower);
3024
+ if (reading) quality || (quality = lower);
2488
3025
  continue;
2489
3026
  }
2490
3027
  const said = describedTag(tag, proper);
@@ -2493,47 +3030,86 @@ const GENERIC_PEOPLE = words('man men woman women person people guy lady adult')
2493
3030
  const stems = stemList(bare);
2494
3031
  const head = (_ref = (_bare_split_pop = bare.split(/\s+/).pop()) == null ? void 0 : _bare_split_pop.toLowerCase()) != null ? _ref : '';
2495
3032
  if (!head) continue;
2496
- const kind = PEOPLE_NOUNS.has(head) ? 'person' : PLACES[head] ? 'place' : 'thing';
3033
+ const headStem = aiStockStem(head);
3034
+ const kind = PEOPLE_NOUNS.has(head) || PEOPLE_NOUNS.has(headStem) ? 'person' : PLACES[headStem] ? 'place' : 'thing';
3035
+ // Past the first tags only where it is set is read.
3036
+ if (!reading && kind !== 'place') continue;
3037
+ // A hand or a body is never what a photo is described by.
3038
+ if (kind === 'thing' && (BODY_PARTS.has(head) || BODY_PARTS.has(headStem))) continue;
2497
3039
  if (parts.some((part)=>stems.every((stem)=>part.stems.includes(stem)))) continue;
2498
- // A longer phrase, or a word holding another, takes its place: "wedding dress" for "wedding", "lawyer" for "law".
2499
- const within = parts.findIndex((part)=>part.stems.every((stem)=>stems.includes(stem)) || nearSynonyms(part.head, head));
2500
3040
  const part = {
2501
3041
  said,
2502
3042
  stems,
2503
- head,
2504
- kind
3043
+ head: headStem,
3044
+ kind,
3045
+ index
2505
3046
  };
2506
- if (within >= 0) {
2507
- const held = parts[within];
2508
- const fuller = nearSynonyms(held.head, head) ? head.length > held.head.length : stems.length > held.stems.length;
2509
- if (fuller) parts.splice(within, 1, part);
3047
+ // A longer phrase of the same thing takes its place: "elderly couple" for "couple".
3048
+ const holds = parts.findIndex((held)=>held.head === headStem && held.stems.every((stem)=>stems.includes(stem)));
3049
+ if (holds >= 0) {
3050
+ parts.splice(holds, 1, _extends({}, part, {
3051
+ index: parts[holds].index
3052
+ }));
2510
3053
  continue;
2511
3054
  }
3055
+ // Two words for one thing ("book" and "bookwork", "cam" and "camera"): the
3056
+ // first tag's stays; a person's are told apart by their role below.
3057
+ if (kind !== 'person' && parts.some((held)=>held.kind !== 'person' && nearSynonyms(held.head, headStem))) continue;
2512
3058
  parts.push(part);
2513
3059
  }
2514
- // One person: a role over a man or a woman ("businessman" over "man"), else the first.
3060
+ // One person: a role among the first tags over a man, a woman, a girl
3061
+ // ("businessman" over "man"), else the first; a role tagged later is not
3062
+ // what the photo shows ("bed, book, man, …, student" is a man), nor is one
3063
+ // a model posed as ("woman, model, show, teacher" is a woman).
2515
3064
  const people = parts.filter((part)=>part.kind === 'person');
2516
- const person = (_people_find = people.find((part)=>!GENERIC_PEOPLE.has(part.head))) != null ? _people_find : people[0];
2517
- const place = parts.find((part)=>part.kind === 'place');
2518
- const things = parts.filter((part)=>part.kind === 'thing');
2519
- const shown = (person ? [
2520
- person
2521
- ] : []).concat(things);
2522
- if (!shown.length && !place) return null;
2523
- let phrase;
2524
- if (portrait && person) phrase = `a portrait of ${person.said}`;
2525
- else {
2526
- const chosen = shown.sort((a, b)=>parts.indexOf(a) - parts.indexOf(b)).slice(0, place ? 1 : 2).map((part)=>part.said);
2527
- // A quality is said before a single counted thing: "a confident lawyer".
2528
- if (quality && chosen[0] && /^an? \S+$/.test(chosen[0])) chosen[0] = `${articleFor(quality)} ${quality} ${chosen[0].replace(/^an?\s+/, '')}`;
2529
- phrase = chosen.join(' and ');
3065
+ const posed = people.some((part)=>part.head === 'model');
3066
+ let places = parts.filter((part)=>part.kind === 'place');
3067
+ let things = parts.filter((part)=>part.kind === 'thing');
3068
+ // A kind of thing only where nothing names the thing: "a cat", never "a cat and an animal".
3069
+ if (things.some((part)=>!KINDS_OF_THING.has(part.head))) things = things.filter((part)=>!KINDS_OF_THING.has(part.head));
3070
+ // A person tagged after the things a photo is of is not who it shows:
3071
+ // "petri, camera, film, photography, photographer" is a camera, and "men,
3072
+ // silhouettes, camera, nature, photographer" men with a camera.
3073
+ const leads = (part)=>portrait || part.index < ALT_PERSON_TAGS_READ || !things.length;
3074
+ const role = posed ? undefined : people.find((part)=>!GENERIC_PEOPLE.has(part.head) && part.index < ALT_ROLE_TAGS_READ && leads(part));
3075
+ const person = role != null ? role : people.find(leads);
3076
+ // Nothing but places: a desk is the thing in a classroom; "a school in a classroom" says one place.
3077
+ if (!person && !things.length && places.length > 1) {
3078
+ const object = places.find((part)=>OBJECT_PLACES.has(part.head));
3079
+ if (object) {
3080
+ things = [
3081
+ object
3082
+ ];
3083
+ places = places.filter((part)=>part !== object);
3084
+ } else places = places.slice(0, 1);
3085
+ }
3086
+ const place = places[0];
3087
+ if (!person && !things.length && !place) return null;
3088
+ /** Every word said so far, so none is said twice. */ const said = new Set();
3089
+ const fresh = (part)=>{
3090
+ const list = stemList(part.said.replace(/^an?\s+/, ''));
3091
+ if (list.some((stem)=>said.has(stem))) return false;
3092
+ for (const stem of list)said.add(stem);
3093
+ return true;
3094
+ };
3095
+ /** A quality before a single counted word: "a confident lawyer", "an analog camera". */ const qualified = (part)=>quality && /^an? \S+$/.test(part.said) && !said.has(aiStockStem(quality)) ? `${articleFor(quality)} ${quality} ${part.said.replace(/^an?\s+/, '')}` : part.said;
3096
+ let phrase = '';
3097
+ if (person && fresh(person)) {
3098
+ phrase = portrait ? `a portrait of ${person.said}` : qualified(person);
3099
+ const held = things.find((part)=>part.index <= 2);
3100
+ if (!portrait && held && fresh(held)) phrase += ` ${CLOTHING.has(held.head) ? 'in' : 'with'} ${held.said}`;
3101
+ } else if (things[0] && fresh(things[0])) {
3102
+ phrase = qualified(things[0]);
3103
+ const second = things[1];
3104
+ if (second && second.index <= 1 && !place && fresh(second)) phrase += ` and ${second.said}`;
2530
3105
  }
2531
- if (place) {
2532
- var _PLACES_place_head;
3106
+ if (place && fresh(place)) {
3107
+ var _PLACES_place_head, _PLACES_SAID_place_head;
2533
3108
  const preposition = (_PLACES_place_head = PLACES[place.head]) != null ? _PLACES_place_head : 'in';
2534
- const where = BARE_PLACES.has(place.head) && /^an? \S+$/.test(place.said) ? place.head : place.said;
3109
+ 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;
2535
3110
  phrase = phrase ? `${phrase} ${preposition} ${where}` : where;
2536
3111
  }
3112
+ if (!phrase) return null;
2537
3113
  const out = `${phrase.charAt(0).toUpperCase()}${phrase.slice(1)}`;
2538
3114
  return out.length > AI_STOCK_ALT_MAX ? out.slice(0, AI_STOCK_ALT_MAX).replace(/[\s,]+\S*$/, '') : out;
2539
3115
  }
@@ -2662,8 +3238,27 @@ const EXTENSIONS = {
2662
3238
  * its business named in English, else its plan's photo words' subject, else
2663
3239
  * its kind of site), every other site by its business type. One reading for
2664
3240
  * every slot of every source — hero, background, gallery, card, product,
2665
- * post — so no slot searches by words another slot is held against.
3241
+ * post — so no slot searches by words another slot is held against. A site for
3242
+ * adults alone ({@link aiStockAdultsOnly}) is told so, whatever its world.
2666
3243
  */ export function aiStockSourceTerms(input) {
3244
+ var _input_audience;
3245
+ const terms = sourceWorldTerms(input);
3246
+ // Where the site's own words set it: a photographer "in the Blue Ridge" for "mountain weddings" (AGL-3856).
3247
+ const setting = aiStockSetting([
3248
+ input.business,
3249
+ (_input_audience = input.audience) != null ? _input_audience : ''
3250
+ ].join(' . '));
3251
+ return _extends({}, terms, aiStockAdultsOnly([
3252
+ input.audience,
3253
+ input.name,
3254
+ input.business
3255
+ ]) ? {
3256
+ adults: true
3257
+ } : {}, setting ? {
3258
+ setting
3259
+ } : {});
3260
+ }
3261
+ /** What a source's photos are searched by: its world, as {@link aiStockSourceTerms} reads it. */ function sourceWorldTerms(input) {
2667
3262
  var _input_name, _input_audience, _input_name1, _input_audience1, _input_kind;
2668
3263
  const english = aiStockSourceSubject(input);
2669
3264
  const terms = aiStockSiteTerms(english || input.business);
@@ -3271,6 +3866,7 @@ const EXTENSIONS = {
3271
3866
  const avoid = new Set((_input_avoid = input.avoid) != null ? _input_avoid : []);
3272
3867
  /** Placed elsewhere in the job: by another page in this process, or one the caller read. */ const elsewhere = (key)=>avoid.has(key) || (job == null ? void 0 : job.has(key)) === true && job.get(key) !== scope;
3273
3868
  /** Source keys this page has claimed for a slot, or turned down. */ const claimed = new Set();
3869
+ /** The photos this page's slots have claimed, first choices, by slot order. */ const shown = [];
3274
3870
  /** Asset srcs this page has placed. */ const placedSrcs = new Set();
3275
3871
  const answers = new Map();
3276
3872
  let searches = 0;
@@ -3297,6 +3893,8 @@ const EXTENSIONS = {
3297
3893
  delete request.refuse;
3298
3894
  delete request.needs;
3299
3895
  delete request.allow;
3896
+ delete request.refuseLead;
3897
+ delete request.worldless;
3300
3898
  const key = JSON.stringify([
3301
3899
  request.query,
3302
3900
  request.orientation,
@@ -3403,9 +4001,41 @@ const EXTENSIONS = {
3403
4001
  }
3404
4002
  return null;
3405
4003
  };
3406
- /** How a search's hits are judged for a slot. */ const judgementFor = (slot, request, subject)=>{
4004
+ /**
4005
+ * How a search's hits are judged for a slot. On a site for adults alone no
4006
+ * hit names a child; a portrait of one of the business's people names an
4007
+ * adult, never leads with a child, and is preferred in its role (AGL-3856).
4008
+ */ const judgementFor = (slot, request, subject)=>{
4009
+ var _request_refuse, _input_sectionNames_slot_sectionIndex, _request_refuseLead;
4010
+ var _request_needs, _role_scene, _role_refuse;
4011
+ const refuse = [
4012
+ ...(_request_refuse = request.refuse) != null ? _request_refuse : [],
4013
+ ...terms.adults ? CHILD_STEMS : []
4014
+ ];
4015
+ const role = slot.people ? aiStockPeopleRole(slot.alt, (_input_sectionNames_slot_sectionIndex = input.sectionNames[slot.sectionIndex]) != null ? _input_sectionNames_slot_sectionIndex : '') : null;
4016
+ return _extends({}, judgementOf(slot, request, subject), refuse.length ? {
4017
+ refuse: [
4018
+ ...new Set(refuse)
4019
+ ]
4020
+ } : {}, slot.people ? _extends({
4021
+ needs: ((_request_needs = request.needs) == null ? void 0 : _request_needs.length) ? request.needs : (role == null ? void 0 : role.required) ? role.kin : ADULT_PEOPLE_STEMS,
4022
+ refuseLead: [
4023
+ ...new Set([
4024
+ ...CHILD_STEMS,
4025
+ ...(_request_refuseLead = request.refuseLead) != null ? _request_refuseLead : []
4026
+ ])
4027
+ ]
4028
+ }, role ? {
4029
+ roles: role.kin
4030
+ } : {}, (role == null ? void 0 : (_role_scene = role.scene) == null ? void 0 : _role_scene.length) ? {
4031
+ roleScene: role.scene
4032
+ } : {}, (role == null ? void 0 : (_role_refuse = role.refuse) == null ? void 0 : _role_refuse.length) ? {
4033
+ roleRefuse: aiStockRoleRefusals(role, slot.alt)
4034
+ } : {}) : {});
4035
+ };
4036
+ /** A search's own judgement, as its words set it. */ const judgementOf = (slot, request, subject)=>{
3407
4037
  var _request_subject, _request_shun;
3408
- var _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs;
4038
+ var _request_allow, _request_shun1, _request_leadAny, _request_refuse, _request_needs, _request_refuseLead;
3409
4039
  return _extends({
3410
4040
  subject: (_request_subject = request.subject) != null ? _request_subject : request.broad ? '' : subject,
3411
4041
  domain: terms.domain,
@@ -3448,6 +4078,11 @@ const EXTENSIONS = {
3448
4078
  refuse: request.refuse
3449
4079
  } : {}, ((_request_needs = request.needs) == null ? void 0 : _request_needs.length) ? {
3450
4080
  needs: request.needs
4081
+ } : {}, ((_request_refuseLead = request.refuseLead) == null ? void 0 : _request_refuseLead.length) ? {
4082
+ refuseLead: request.refuseLead
4083
+ } : {}, request.worldless ? {
4084
+ domain: [],
4085
+ rivals: []
3451
4086
  } : {});
3452
4087
  };
3453
4088
  /**
@@ -3455,7 +4090,8 @@ const EXTENSIONS = {
3455
4090
  * filled from (AGL-3660): its broad searches, any hit naming the craft
3456
4091
  * itself. Never a desk, a laptop or a living room for a soap shop.
3457
4092
  */ const craftSearches = (slot)=>{
3458
- if (!terms.core.length) return [];
4093
+ // A portfolio's record of the work is never filled by the craft's own photos (AGL-3856).
4094
+ if (!terms.core.length || aiStockShowsTheWork(slot, terms)) return [];
3459
4095
  const orientation = aiStockOrientation(slot.aspect);
3460
4096
  const size = slot.role === 'hero' ? {
3461
4097
  minWidth: 1600
@@ -3493,7 +4129,7 @@ const EXTENSIONS = {
3493
4129
  ...slot.product ? aiStockProductSearches(slot, terms.craft, terms.domain, {
3494
4130
  broad: terms.broad,
3495
4131
  core: terms.core
3496
- }) : aiStockSearchesFor(slot, terms, subject, aiStockSlotPart(slot, sectionName)),
4132
+ }) : aiStockSearchesFor(slot, terms, subject, aiStockSlotPart(slot, sectionName), sectionName),
3497
4133
  ...craftSearches(slot)
3498
4134
  ];
3499
4135
  /** The slot's accepted hits for a search; `null` once the page's searches are spent. */ const accepted = async (request, repeat)=>{
@@ -3507,7 +4143,10 @@ const EXTENSIONS = {
3507
4143
  if (elsewhere(key)) blocked.add(key);
3508
4144
  }
3509
4145
  }
3510
- return aiStockRank(found, blocked, `${input.seed}:${index}:${request.query}`, judgementFor(slot, request, subject));
4146
+ const judgement = judgementFor(slot, request, subject);
4147
+ const ranked = aiStockRank(found, blocked, `${input.seed}:${index}:${request.query}`, judgement);
4148
+ // A team's portraits are of different people where the hits allow (AGL-3856).
4149
+ return slot.people && !repeat ? aiStockFreshShootsFirst(ranked, shown, judgement) : ranked;
3511
4150
  };
3512
4151
  /** The first of these searches with a hit to take: a new one, or one the job already shows. */ const choose = async (tried, repeat)=>{
3513
4152
  for (const request of tried){
@@ -3515,7 +4154,10 @@ const EXTENSIONS = {
3515
4154
  // Spent searches answer nothing; those already asked still answer from memory.
3516
4155
  const ranked = await accepted(request, repeat);
3517
4156
  if (!(ranked == null ? void 0 : ranked.length)) continue;
3518
- if (!repeat) claimed.add(stockPhotoSourceKey(ranked[0]));
4157
+ if (!repeat) {
4158
+ claimed.add(stockPhotoSourceKey(ranked[0]));
4159
+ shown.push(ranked[0]);
4160
+ }
3519
4161
  return _extends({
3520
4162
  candidates: ranked.slice(0, AI_LAYOUT_STOCK_TRIES_PER_SLOT),
3521
4163
  query: request.query