@nodaro/prompts 1.14.0 → 1.16.0

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 (69) hide show
  1. package/dist/index.cjs +680 -251
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +406 -1
  4. package/dist/index.d.ts +406 -1
  5. package/dist/index.js +649 -253
  6. package/dist/index.js.map +1 -1
  7. package/package.json +2 -2
  8. package/src/__tests__/adult-only-ratchet.test.ts +110 -0
  9. package/src/__tests__/age-floor.test.ts +347 -0
  10. package/src/__tests__/catalog-id-guard.test.ts +146 -0
  11. package/src/__tests__/catalog-overlay.test.ts +163 -0
  12. package/src/__tests__/doctrine-roster-completeness.test.ts +1 -0
  13. package/src/__tests__/dod-replace-pack-acceptance.test.ts +17 -0
  14. package/src/__tests__/fixtures/parameter-hint-golden.json +315 -37
  15. package/src/__tests__/minor-age-floor.test.ts +136 -0
  16. package/src/__tests__/person-body-axes.test.ts +2 -2
  17. package/src/__tests__/person-exposure-hints.test.ts +59 -4
  18. package/src/__tests__/person-pack-adult-only.test.ts +45 -0
  19. package/src/__tests__/picker-analyzer-registry.test.ts +14 -0
  20. package/src/__tests__/retired-hint-strings.test.ts +68 -0
  21. package/src/__tests__/subject-registry.test.ts +1 -1
  22. package/src/__tests__/w1b-rephrase.test.ts +119 -0
  23. package/src/action-fx.ts +2 -1
  24. package/src/aesthetic.ts +2 -1
  25. package/src/age-floor.ts +359 -0
  26. package/src/age-signal.ts +36 -0
  27. package/src/atmosphere.ts +2 -1
  28. package/src/backdrop.ts +2 -1
  29. package/src/camera-format.ts +2 -1
  30. package/src/camera-motions.ts +2 -1
  31. package/src/catalog-id-guard.ts +189 -0
  32. package/src/catalog-overlay.ts +148 -0
  33. package/src/catalog-packs.ts +27 -2
  34. package/src/character-fx.ts +2 -1
  35. package/src/color-look.ts +2 -1
  36. package/src/composition-effects.ts +2 -1
  37. package/src/era.ts +2 -1
  38. package/src/exposure-settings.ts +2 -1
  39. package/src/framing.ts +2 -1
  40. package/src/held-prop.ts +2 -1
  41. package/src/index.ts +4 -0
  42. package/src/instrumentation.ts +5 -4
  43. package/src/lens.ts +2 -1
  44. package/src/lighting.ts +2 -1
  45. package/src/loop-subject.ts +3 -1
  46. package/src/materials.ts +2 -1
  47. package/src/mood.ts +16 -5
  48. package/src/music-genre.ts +5 -4
  49. package/src/music-mood.ts +4 -3
  50. package/src/parameter-prompt-hint.ts +13 -57
  51. package/src/person-packs.ts +14 -2
  52. package/src/person.ts +76 -42
  53. package/src/photo-genre.ts +14 -3
  54. package/src/photographer.ts +2 -1
  55. package/src/picker-analyzer-registry.ts +9 -0
  56. package/src/picker-catalogs.ts +11 -0
  57. package/src/pose.ts +17 -6
  58. package/src/post-process-effects.ts +2 -1
  59. package/src/prompt-builder.ts +3 -3
  60. package/src/render-quality.ts +2 -1
  61. package/src/setting.ts +2 -1
  62. package/src/shared-catalog-overlay.ts +83 -0
  63. package/src/style.ts +16 -1
  64. package/src/styling.ts +93 -29
  65. package/src/subject-registry.ts +2 -2
  66. package/src/temporal.ts +2 -1
  67. package/src/transitions.ts +2 -1
  68. package/src/voice-character.ts +6 -5
  69. package/src/voice-delivery.ts +4 -3
package/dist/index.cjs CHANGED
@@ -107,6 +107,148 @@ function resolveTerm(entry) {
107
107
  if (entry.promptHint === "") return "";
108
108
  return entry.term ?? deriveTerm(entry.label);
109
109
  }
110
+ function withTerm(o) {
111
+ return { ...o, term: resolveTerm(o) };
112
+ }
113
+ var packs = [];
114
+ var version = 0;
115
+ var listeners = /* @__PURE__ */ new Set();
116
+ function bump() {
117
+ version++;
118
+ for (const l of listeners) l();
119
+ }
120
+ function registerCatalogPack(pack) {
121
+ if (packs.some((p) => p.id === pack.id)) throw new Error(`duplicate catalog pack id "${pack.id}"`);
122
+ packs = [...packs, pack];
123
+ shared.registerCatalogSidecars(pack.catalogId, pack.sidecars);
124
+ bump();
125
+ }
126
+ function getRegisteredCatalogPacks() {
127
+ return packs;
128
+ }
129
+ function resetCatalogPacks() {
130
+ packs = [];
131
+ shared.resetCatalogSidecars();
132
+ bump();
133
+ }
134
+ function catalogPacksVersion() {
135
+ return version;
136
+ }
137
+ function hasCatalogPacksFor(catalogId) {
138
+ return packs.some((p) => p.catalogId === catalogId);
139
+ }
140
+ function subscribeCatalogPacks(listener) {
141
+ listeners.add(listener);
142
+ return () => {
143
+ listeners.delete(listener);
144
+ };
145
+ }
146
+ function cloneCatalog(c) {
147
+ return {
148
+ ...c,
149
+ options: c.options ? c.options.map(withTerm) : void 0,
150
+ dimensions: c.dimensions ? c.dimensions.map((d) => ({ ...d, options: d.options.map(withTerm) })) : void 0
151
+ };
152
+ }
153
+ function applyExtend(c, pack) {
154
+ if (c.kind === "single") {
155
+ return { ...c, options: [...c.options ?? [], ...(pack.options ?? []).map(withTerm)] };
156
+ }
157
+ const byField = new Map((c.dimensions ?? []).map((d) => [d.field, { ...d, options: [...d.options] }]));
158
+ for (const dim of pack.dimensions ?? []) {
159
+ const added = dim.options.map(withTerm);
160
+ const existing = byField.get(dim.field);
161
+ if (existing) existing.options = [...existing.options, ...added];
162
+ else byField.set(dim.field, { ...dim, options: added });
163
+ }
164
+ const dims = [...byField.values()];
165
+ return { ...c, dimensions: dims, fields: dims.map((d) => d.field) };
166
+ }
167
+ function applyDeny(c, denyIds) {
168
+ const deny = new Set(denyIds);
169
+ if (c.kind === "single") return { ...c, options: (c.options ?? []).filter((o) => !deny.has(o.id)) };
170
+ return { ...c, dimensions: (c.dimensions ?? []).map((d) => ({ ...d, options: d.options.filter((o) => !deny.has(o.id)) })) };
171
+ }
172
+ function composePickerCatalogs(base, activePacks) {
173
+ const known = new Set(base.map((c) => c.catalogId));
174
+ for (const pack of activePacks) {
175
+ if (!known.has(pack.catalogId)) throw new Error(`catalog pack "${pack.id}" targets unknown catalog id "${pack.catalogId}"`);
176
+ }
177
+ return base.map((c) => {
178
+ let out = cloneCatalog(c);
179
+ for (const pack of activePacks) {
180
+ if (pack.catalogId !== c.catalogId) continue;
181
+ if (pack.mode === "replace" && pack.catalog) out = cloneCatalog(pack.catalog);
182
+ else if (pack.mode === "extend") out = applyExtend(out, pack);
183
+ else if (pack.mode === "deny") out = applyDeny(out, pack.denyIds ?? []);
184
+ }
185
+ return out;
186
+ });
187
+ }
188
+
189
+ // src/catalog-overlay.ts
190
+ var resolver = null;
191
+ function setComposedCatalogResolver(fn) {
192
+ resolver = fn;
193
+ }
194
+ var memo = null;
195
+ function composedOptionIndex(catalogId) {
196
+ const v = catalogPacksVersion();
197
+ if (!memo || memo.v !== v) memo = { v, byCatalog: /* @__PURE__ */ new Map() };
198
+ const hit = memo.byCatalog.get(catalogId);
199
+ if (hit) return hit;
200
+ if (!resolver) {
201
+ throw new Error(
202
+ `[catalog-overlay] catalog "${catalogId}" is curated by a pack but the composed-catalog resolver is not installed \u2014 refusing to resolve against the stock catalog`
203
+ );
204
+ }
205
+ const cat = resolver(catalogId);
206
+ const index = /* @__PURE__ */ new Map();
207
+ if (cat) {
208
+ for (const o of cat.options ?? []) index.set(o.id, o);
209
+ for (const d of cat.dimensions ?? []) for (const o of d.options) if (!index.has(o.id)) index.set(o.id, o);
210
+ }
211
+ memo.byCatalog.set(catalogId, index);
212
+ return index;
213
+ }
214
+ function overlayEntry(catalogId, id, base) {
215
+ if (!hasCatalogPacksFor(catalogId)) return base;
216
+ const opt = composedOptionIndex(catalogId).get(id);
217
+ if (!opt) return void 0;
218
+ if (!base) return opt;
219
+ return {
220
+ ...base,
221
+ label: opt.label,
222
+ promptHint: opt.promptHint,
223
+ term: opt.term,
224
+ ...opt.description !== void 0 ? { description: opt.description } : {},
225
+ ...opt.category !== void 0 ? { category: opt.category } : {}
226
+ };
227
+ }
228
+ function composedHas(catalogId, id) {
229
+ if (!hasCatalogPacksFor(catalogId)) return true;
230
+ return composedOptionIndex(catalogId).has(id);
231
+ }
232
+ function composedOption(catalogId, id) {
233
+ if (!hasCatalogPacksFor(catalogId)) return void 0;
234
+ return composedOptionIndex(catalogId).get(id);
235
+ }
236
+ function curateEntries(catalogId, base) {
237
+ if (!hasCatalogPacksFor(catalogId)) return base;
238
+ const index = composedOptionIndex(catalogId);
239
+ const out = [];
240
+ for (const e of base) {
241
+ const opt = index.get(e.id);
242
+ if (!opt) continue;
243
+ out.push({
244
+ ...e,
245
+ label: opt.label,
246
+ ...opt.description !== void 0 ? { description: opt.description } : {},
247
+ ..."promptHint" in e && opt.promptHint !== void 0 ? { promptHint: opt.promptHint } : {}
248
+ });
249
+ }
250
+ return out;
251
+ }
110
252
 
111
253
  // src/framing.ts
112
254
  var FRAMINGS = [
@@ -631,7 +773,7 @@ var FRAMING_CATEGORY_LABELS = {
631
773
  var framingById = new Map(FRAMINGS.map((f) => [f.id, f]));
632
774
  function getFraming(id) {
633
775
  if (!id) return void 0;
634
- return framingById.get(id);
776
+ return overlayEntry("framing", id, framingById.get(id));
635
777
  }
636
778
  function getFramingLabel(id, fallback) {
637
779
  const f = getFraming(id);
@@ -802,7 +944,7 @@ var LIGHTING_CATEGORY_LABELS = {
802
944
  var lightingById = new Map(LIGHTINGS.map((l) => [l.id, l]));
803
945
  function getLighting(id) {
804
946
  if (!id) return void 0;
805
- return lightingById.get(id);
947
+ return overlayEntry("lighting", id, lightingById.get(id));
806
948
  }
807
949
  function getLightingLabel(id, fallback) {
808
950
  const l = getLighting(id);
@@ -886,7 +1028,7 @@ var LENSES = [
886
1028
  var lensById = new Map(LENSES.map((l) => [l.id, l]));
887
1029
  function getLens(id) {
888
1030
  if (!id) return void 0;
889
- return lensById.get(id);
1031
+ return overlayEntry("lens", id, lensById.get(id));
890
1032
  }
891
1033
  function getLensLabel(id, fallback) {
892
1034
  const l = getLens(id);
@@ -944,7 +1086,7 @@ var CAMERA_FORMATS = [
944
1086
  var formatById = new Map(CAMERA_FORMATS.map((f) => [f.id, f]));
945
1087
  function getCameraFormat(id) {
946
1088
  if (!id) return void 0;
947
- return formatById.get(id);
1089
+ return overlayEntry("camera-format", id, formatById.get(id));
948
1090
  }
949
1091
  function getCameraFormatLabel(id, fallback) {
950
1092
  const f = getCameraFormat(id);
@@ -1020,7 +1162,7 @@ var COLOR_LOOK_CATEGORY_LABELS = {
1020
1162
  var colorLookById = new Map(COLOR_LOOKS.map((c) => [c.id, c]));
1021
1163
  function getColorLook(id) {
1022
1164
  if (!id) return void 0;
1023
- return colorLookById.get(id);
1165
+ return overlayEntry("color-look", id, colorLookById.get(id));
1024
1166
  }
1025
1167
  function getColorLookLabel(id, fallback) {
1026
1168
  const c = getColorLook(id);
@@ -1082,7 +1224,7 @@ var ATMOSPHERES = [
1082
1224
  var atmosphereById = new Map(ATMOSPHERES.map((a) => [a.id, a]));
1083
1225
  function getAtmosphere(id) {
1084
1226
  if (!id) return void 0;
1085
- return atmosphereById.get(id);
1227
+ return overlayEntry("atmosphere", id, atmosphereById.get(id));
1086
1228
  }
1087
1229
  function getAtmosphereLabel(id, fallback) {
1088
1230
  const a = getAtmosphere(id);
@@ -1671,7 +1813,7 @@ var ACTION_FX_IDS = ACTION_FX.map((fx) => fx.id);
1671
1813
  var actionFxById = new Map(ACTION_FX.map((fx) => [fx.id, fx]));
1672
1814
  function getActionFx(id) {
1673
1815
  if (!id) return void 0;
1674
- return actionFxById.get(id);
1816
+ return overlayEntry("action-fx", id, actionFxById.get(id));
1675
1817
  }
1676
1818
  function getActionFxLabel(id, fallback) {
1677
1819
  const fx = getActionFx(id);
@@ -1772,7 +1914,11 @@ var STYLES = [
1772
1914
  var styleById = new Map(STYLES.map((s) => [s.id, s]));
1773
1915
  function getStyle(id) {
1774
1916
  if (!id) return void 0;
1775
- return styleById.get(id);
1917
+ return overlayEntry("style", id, styleById.get(id));
1918
+ }
1919
+ function isDeniedStyleId(value) {
1920
+ if (!value) return false;
1921
+ return styleById.has(value) && getStyle(value) === void 0;
1776
1922
  }
1777
1923
  function getStyleLabel(id, fallback) {
1778
1924
  const s = getStyle(id);
@@ -1862,7 +2008,7 @@ var SETTINGS = [
1862
2008
  var settingById = new Map(SETTINGS.map((s) => [s.id, s]));
1863
2009
  function getSetting(id) {
1864
2010
  if (!id) return void 0;
1865
- return settingById.get(id);
2011
+ return overlayEntry("setting", id, settingById.get(id));
1866
2012
  }
1867
2013
  function getSettingLabel(id, fallback) {
1868
2014
  const s = getSetting(id);
@@ -2124,7 +2270,8 @@ var LOOP_SUBJECTS = [
2124
2270
  }
2125
2271
  ];
2126
2272
  function getLoopSubject(id) {
2127
- return LOOP_SUBJECTS.find((s) => s.id === id);
2273
+ if (!id) return void 0;
2274
+ return overlayEntry("loop-subject", id, LOOP_SUBJECTS.find((s) => s.id === id));
2128
2275
  }
2129
2276
  function getLoopSubjectLabel(id) {
2130
2277
  return getLoopSubject(id)?.label ?? id;
@@ -2135,70 +2282,6 @@ function getLoopSubjectPromptHint(id) {
2135
2282
  function getLoopSubjectTerm(id) {
2136
2283
  return resolveTerm(getLoopSubject(id));
2137
2284
  }
2138
- function withTerm(o) {
2139
- return { ...o, term: resolveTerm(o) };
2140
- }
2141
- var packs = [];
2142
- var version = 0;
2143
- function registerCatalogPack(pack) {
2144
- if (packs.some((p) => p.id === pack.id)) throw new Error(`duplicate catalog pack id "${pack.id}"`);
2145
- packs = [...packs, pack];
2146
- version++;
2147
- shared.registerCatalogSidecars(pack.catalogId, pack.sidecars);
2148
- }
2149
- function getRegisteredCatalogPacks() {
2150
- return packs;
2151
- }
2152
- function resetCatalogPacks() {
2153
- packs = [];
2154
- version++;
2155
- shared.resetCatalogSidecars();
2156
- }
2157
- function catalogPacksVersion() {
2158
- return version;
2159
- }
2160
- function cloneCatalog(c) {
2161
- return {
2162
- ...c,
2163
- options: c.options ? c.options.map(withTerm) : void 0,
2164
- dimensions: c.dimensions ? c.dimensions.map((d) => ({ ...d, options: d.options.map(withTerm) })) : void 0
2165
- };
2166
- }
2167
- function applyExtend(c, pack) {
2168
- if (c.kind === "single") {
2169
- return { ...c, options: [...c.options ?? [], ...(pack.options ?? []).map(withTerm)] };
2170
- }
2171
- const byField = new Map((c.dimensions ?? []).map((d) => [d.field, { ...d, options: [...d.options] }]));
2172
- for (const dim of pack.dimensions ?? []) {
2173
- const added = dim.options.map(withTerm);
2174
- const existing = byField.get(dim.field);
2175
- if (existing) existing.options = [...existing.options, ...added];
2176
- else byField.set(dim.field, { ...dim, options: added });
2177
- }
2178
- const dims = [...byField.values()];
2179
- return { ...c, dimensions: dims, fields: dims.map((d) => d.field) };
2180
- }
2181
- function applyDeny(c, denyIds) {
2182
- const deny = new Set(denyIds);
2183
- if (c.kind === "single") return { ...c, options: (c.options ?? []).filter((o) => !deny.has(o.id)) };
2184
- return { ...c, dimensions: (c.dimensions ?? []).map((d) => ({ ...d, options: d.options.filter((o) => !deny.has(o.id)) })) };
2185
- }
2186
- function composePickerCatalogs(base, activePacks) {
2187
- const known = new Set(base.map((c) => c.catalogId));
2188
- for (const pack of activePacks) {
2189
- if (!known.has(pack.catalogId)) throw new Error(`catalog pack "${pack.id}" targets unknown catalog id "${pack.catalogId}"`);
2190
- }
2191
- return base.map((c) => {
2192
- let out = cloneCatalog(c);
2193
- for (const pack of activePacks) {
2194
- if (pack.catalogId !== c.catalogId) continue;
2195
- if (pack.mode === "replace" && pack.catalog) out = cloneCatalog(pack.catalog);
2196
- else if (pack.mode === "extend") out = applyExtend(out, pack);
2197
- else if (pack.mode === "deny") out = applyDeny(out, pack.denyIds ?? []);
2198
- }
2199
- return out;
2200
- });
2201
- }
2202
2285
  var personPacks = [];
2203
2286
  var version2 = 0;
2204
2287
  function syncPersonPackFields() {
@@ -2221,7 +2304,8 @@ function registerPersonPack(pack) {
2221
2304
  description: e.description,
2222
2305
  category: e.group,
2223
2306
  promptHint: e.promptHint,
2224
- term: resolveTerm(e)
2307
+ term: resolveTerm(e),
2308
+ adultOnly: e.adultOnly
2225
2309
  };
2226
2310
  const existing = byField.get(dim.field);
2227
2311
  if (existing) existing.options.push(opt);
@@ -2243,7 +2327,7 @@ function resetPersonPacks() {
2243
2327
  syncPersonPackFields();
2244
2328
  }
2245
2329
  function personPacksVersion() {
2246
- return version2;
2330
+ return version2 * 1000003 + catalogPacksVersion();
2247
2331
  }
2248
2332
  function dimensionByField() {
2249
2333
  const out = {};
@@ -2263,7 +2347,8 @@ function personEntriesFromDims(dims) {
2263
2347
  description: o.description ?? "",
2264
2348
  group: o.category,
2265
2349
  promptHint: o.promptHint,
2266
- dimension
2350
+ dimension,
2351
+ adultOnly: o.adultOnly
2267
2352
  });
2268
2353
  }
2269
2354
  }
@@ -2300,6 +2385,36 @@ function getRegisteredPersonDimensionLabels() {
2300
2385
  return extra.length === 0 ? PERSON_DIMENSION_LABELS : { ...PERSON_DIMENSION_LABELS, ...Object.fromEntries(extra) };
2301
2386
  }
2302
2387
 
2388
+ // src/age-signal.ts
2389
+ var ADULT_ONLY_FLAG = "adultOnly";
2390
+ var ADULT_AGE_IDS = /* @__PURE__ */ new Set([
2391
+ "age-early-20s",
2392
+ "age-late-20s",
2393
+ "age-20s",
2394
+ "age-30s",
2395
+ "age-40s",
2396
+ "age-50s",
2397
+ "age-60s",
2398
+ "age-elderly"
2399
+ ]);
2400
+ var MINOR_IMPLYING_TYPE_IDS = /* @__PURE__ */ new Set([
2401
+ "alice-wonderland",
2402
+ "dorothy-oz",
2403
+ "peter-pan",
2404
+ "magical-girl",
2405
+ "prince",
2406
+ "princess"
2407
+ ]);
2408
+ function isMinorAge(value) {
2409
+ if (!value) return false;
2410
+ const { age, customAge, type } = value;
2411
+ if (typeof age === "string" && age.length > 0) {
2412
+ if (age === "age-custom") return !(typeof customAge === "number" && Number.isFinite(customAge) && customAge >= 20);
2413
+ return !ADULT_AGE_IDS.has(age);
2414
+ }
2415
+ return typeof type === "string" && MINOR_IMPLYING_TYPE_IDS.has(type);
2416
+ }
2417
+
2303
2418
  // src/person.ts
2304
2419
  var PEOPLE = [
2305
2420
  // -------------------- Type (primary subject descriptor) --------------------
@@ -2336,7 +2451,7 @@ var PEOPLE = [
2336
2451
  { id: "stylish-influencer", label: "Stylish Influencer", group: "Realistic \u2014 Style", dimension: "type", description: "Polished social-media tastemaker", promptHint: "a stylish social-media influencer \u2014 polished personal-brand styling, trend-forward outfit, expressive natural posing for the camera, the curated put-together look of a content creator" },
2337
2452
  { id: "stunning-model", label: "Stunning Model", group: "Realistic \u2014 Style", dimension: "type", description: "Fashion-model aesthetic", promptHint: "a stunning fashion model with editorial poise, refined features, and high-fashion presence" },
2338
2453
  { id: "supermodel", label: "Supermodel", group: "Realistic \u2014 Style", dimension: "type", description: "Iconic, top-tier runway and cover star", promptHint: "a supermodel \u2014 iconic top-tier presence with magnetic statement features, a striking silhouette and the unmistakable runway-and-cover aura that commands attention" },
2339
- { id: "femme-fatale", label: "Femme Fatale", group: "Realistic \u2014 Style", dimension: "type", description: "Alluring, dangerous noir seductress", promptHint: "a femme fatale \u2014 alluring, mysterious, and dangerous with classic noir seduction" },
2454
+ { id: "femme-fatale", label: "Femme Fatale", group: "Realistic \u2014 Style", dimension: "type", description: "Alluring, dangerous noir seductress", promptHint: "a femme fatale \u2014 alluring, mysterious, and dangerous with classic noir seduction", adultOnly: true },
2340
2455
  { id: "tough-guy", label: "Tough Guy", group: "Realistic \u2014 Style", dimension: "type", description: "Hardened, tough man", promptHint: "a tough, hardened man" },
2341
2456
  // Aesthetic archetypes that traditionally read older — hints kept gender +
2342
2457
  // look-focused so the Age dimension stays in control of the literal age.
@@ -2347,7 +2462,7 @@ var PEOPLE = [
2347
2462
  { id: "twink", label: "Twink", group: "Realistic \u2014 Style", dimension: "type", description: "Slim, smooth, soft gay-male type", promptHint: "a twink \u2014 slim, smooth, soft masculine presence with a delicate, gender-fluid vibe" },
2348
2463
  { id: "soft-butch", label: "Soft Butch", group: "Realistic \u2014 Style", dimension: "type", description: "Gentle masculine-of-center woman", promptHint: "a soft-butch woman \u2014 gentle masculine-of-center styling and energy" },
2349
2464
  { id: "tomboy", label: "Tomboy", group: "Realistic \u2014 Style", dimension: "type", description: "Boyish styling and energy on a feminine body", promptHint: "a tomboy \u2014 boyish styling and energy on a feminine body" },
2350
- { id: "thick", label: "Thick", group: "Realistic \u2014 Style", dimension: "type", description: "Voluptuous body-positive figure with curves", promptHint: "a thick, voluptuous body-positive figure with full curves", term: "thick voluptuous figure" },
2465
+ { id: "thick", label: "Thick", group: "Realistic \u2014 Style", dimension: "type", description: "Voluptuous body-positive figure with curves", promptHint: "a thick, voluptuous body-positive figure with full curves", term: "thick voluptuous figure", adultOnly: true },
2351
2466
  { id: "androgynous", label: "Androgynous", group: "Realistic \u2014 Style", dimension: "type", description: "Deliberately blended masculine + feminine cues", promptHint: "an androgynous figure with deliberately blended masculine and feminine cues", term: "androgynous figure" },
2352
2467
  { id: "goth", label: "Goth", group: "Realistic \u2014 Style", dimension: "type", description: "Goth subculture aesthetic \u2014 dark, romantic, dramatic", promptHint: "a goth figure with a dark romantic dramatic subculture aesthetic" },
2353
2468
  { id: "bear", label: "Bear", group: "Realistic \u2014 Style", dimension: "type", description: "Big, bearded, hairy masculine type", promptHint: "a bear \u2014 a big, bearded, hairy man with a warm masculine presence", term: "big bearded hairy man" },
@@ -2355,7 +2470,7 @@ var PEOPLE = [
2355
2470
  { id: "caveman", label: "Caveman", group: "Primitive / Wild", dimension: "type", description: "Stone Age primitive man", promptHint: "a caveman \u2014 Stone Age primitive in animal furs, with shaggy hair, a heavy brow and a sturdy frame" },
2356
2471
  { id: "cavewoman", label: "Cavewoman", group: "Primitive / Wild", dimension: "type", description: "Stone Age primitive woman", promptHint: "a cavewoman \u2014 Stone Age primitive in animal furs, with shaggy hair and a sturdy frame" },
2357
2472
  { id: "apeman", label: "Apeman", group: "Primitive / Wild", dimension: "type", description: "Half-human half-ape hybrid", promptHint: "an apeman \u2014 half-human half-ape hybrid with simian features, broad shoulders and a heavy stooped build" },
2358
- { id: "feral-wildman", label: "Feral Wild-Human", group: "Primitive / Wild", dimension: "type", description: "Overgrown, primal feral human", promptHint: "a feral wild-human \u2014 overgrown matted hair, primal eyes and a near-naked, bestial bearing" },
2473
+ { id: "feral-wildman", label: "Feral Wild-Human", group: "Primitive / Wild", dimension: "type", description: "Overgrown, primal feral human", promptHint: "a feral wild-human \u2014 overgrown matted hair, primal eyes and a near-naked, bestial bearing", adultOnly: true },
2359
2474
  { id: "neanderthal", label: "Neanderthal", group: "Primitive / Wild", dimension: "type", description: "Pre-modern human species", promptHint: "a neanderthal \u2014 heavy brow, broad nose, stocky pre-modern human build with weather-toughened skin" },
2360
2475
  // ----- Fantasy (humanoid) -----
2361
2476
  { id: "elf-man", label: "Elf (Male)", group: "Fantasy", dimension: "type", description: "Masculine pointed-ear humanoid", promptHint: "a male elf \u2014 slender masculine humanoid with delicately pointed ears, fine sharp features and an ethereal poise", term: "male elf" },
@@ -2369,7 +2484,7 @@ var PEOPLE = [
2369
2484
  { id: "faun", label: "Faun / Satyr", group: "Fantasy", dimension: "type", description: "Goat-legged horned humanoid", promptHint: "a faun \u2014 humanoid with goat legs, small curling horns and a woodland presence", term: "faun" },
2370
2485
  { id: "centaur", label: "Centaur", group: "Fantasy", dimension: "type", description: "Human torso on a horse body", promptHint: "a centaur \u2014 human torso fused to the body of a horse" },
2371
2486
  { id: "vampire-man", label: "Vampire (Male)", group: "Fantasy", dimension: "type", description: "Masculine fanged immortal", promptHint: "a male vampire \u2014 pale, fanged immortal man in formal evening wear with gothic elegance and a quiet predatory poise", term: "male vampire" },
2372
- { id: "vampire-woman", label: "Vampire (Female)", group: "Fantasy", dimension: "type", description: "Feminine fanged immortal", promptHint: "a female vampire \u2014 pale, fanged immortal woman in flowing gothic attire with seductive elegance and a quiet predatory poise", term: "female vampire" },
2487
+ { id: "vampire-woman", label: "Vampire (Female)", group: "Fantasy", dimension: "type", description: "Feminine fanged immortal", promptHint: "a female vampire \u2014 pale, fanged immortal woman in flowing gothic attire with seductive elegance and a quiet predatory poise", term: "female vampire", adultOnly: true },
2373
2488
  { id: "vampire", label: "Vampire", group: "Fantasy", dimension: "type", description: "Pale, fanged immortal", promptHint: "a vampire \u2014 pale, fanged immortal with a pristine gothic elegance and a quiet predatory poise" },
2374
2489
  { id: "werewolf", label: "Werewolf", group: "Fantasy", dimension: "type", description: "Partially transformed lycanthrope", promptHint: "a werewolf \u2014 partially transformed humanoid with wolfish snout, fur, claws and a feral musculature" },
2375
2490
  { id: "mermaid", label: "Mermaid", group: "Fantasy", dimension: "type", description: "Female humanoid with a fish tail", promptHint: "a mermaid \u2014 humanoid woman from the waist up with a long iridescent fish tail below" },
@@ -2601,22 +2716,22 @@ var PEOPLE = [
2601
2716
  { id: "mass-full", label: "Full", dimension: "body-mass", description: "Full-figured", promptHint: "full-figured", term: "full-figured" },
2602
2717
  { id: "mass-heavy", label: "Heavy", dimension: "body-mass", description: "Heavy-set, large build", promptHint: "heavy-set build", term: "heavy-set build" },
2603
2718
  // ----- Bust (applicable subjects; ungated like Facial Hair, neutral = no-op) -----
2604
- { id: "bust-small", label: "Small", dimension: "bust", description: "Small bust", promptHint: "small bust", term: "small bust" },
2719
+ { id: "bust-small", label: "Small", dimension: "bust", description: "Small bust", promptHint: "small bust", term: "small bust", adultOnly: true },
2605
2720
  { id: "bust-average", label: "Average", dimension: "bust", description: "Average bust", promptHint: "" },
2606
- { id: "bust-full", label: "Full", dimension: "bust", description: "Full bust", promptHint: "full bust", term: "full bust" },
2607
- { id: "bust-very-full", label: "Very Full", dimension: "bust", description: "Very full bust", promptHint: "very full bust", term: "very full bust" },
2721
+ { id: "bust-full", label: "Full", dimension: "bust", description: "Full bust", promptHint: "full bust", term: "full bust", adultOnly: true },
2722
+ { id: "bust-very-full", label: "Very Full", dimension: "bust", description: "Very full bust", promptHint: "a fuller bust", term: "fuller bust", adultOnly: true },
2608
2723
  // ----- Waist -----
2609
- { id: "waist-defined", label: "Defined", dimension: "waist", description: "Defined, cinched waist", promptHint: "defined waist", term: "defined waist" },
2724
+ { id: "waist-defined", label: "Defined", dimension: "waist", description: "Defined, cinched waist", promptHint: "a defined waistline", term: "defined waistline", adultOnly: true },
2610
2725
  { id: "waist-average", label: "Average", dimension: "waist", description: "Average waist", promptHint: "" },
2611
- { id: "waist-straight", label: "Straight", dimension: "waist", description: "Straight, undefined waist", promptHint: "straight waistline", term: "straight waistline" },
2726
+ { id: "waist-straight", label: "Straight", dimension: "waist", description: "Straight, undefined waist", promptHint: "straight waistline", term: "straight waistline", adultOnly: true },
2612
2727
  // ----- Hips -----
2613
- { id: "hips-narrow", label: "Narrow", dimension: "hips", description: "Narrow hips", promptHint: "narrow hips", term: "narrow hips" },
2728
+ { id: "hips-narrow", label: "Narrow", dimension: "hips", description: "Narrow hips", promptHint: "narrow hips", term: "narrow hips", adultOnly: true },
2614
2729
  { id: "hips-balanced", label: "Balanced", dimension: "hips", description: "Balanced hips", promptHint: "" },
2615
- { id: "hips-wide", label: "Wide", dimension: "hips", description: "Wide hips", promptHint: "wide hips", term: "wide hips" },
2730
+ { id: "hips-wide", label: "Wide", dimension: "hips", description: "Wide hips", promptHint: "wide hips", term: "wide hips", adultOnly: true },
2616
2731
  // ----- Silhouette (overall body shape; optional, no neutral) -----
2617
- { id: "silhouette-hourglass", label: "Hourglass", dimension: "silhouette", description: "Balanced bust + hips, defined waist", promptHint: "hourglass silhouette", term: "hourglass silhouette" },
2732
+ { id: "silhouette-hourglass", label: "Hourglass", dimension: "silhouette", description: "Balanced bust + hips, defined waist", promptHint: "an hourglass figure", term: "hourglass figure", adultOnly: true },
2618
2733
  { id: "silhouette-rectangular", label: "Rectangular", dimension: "silhouette", description: "Straight up-and-down silhouette", promptHint: "rectangular silhouette", term: "rectangular silhouette" },
2619
- { id: "silhouette-pear", label: "Pear", dimension: "silhouette", description: "Hips wider than shoulders", promptHint: "pear-shaped silhouette", term: "pear-shaped silhouette" },
2734
+ { id: "silhouette-pear", label: "Pear", dimension: "silhouette", description: "Hips wider than shoulders", promptHint: "pear-shaped silhouette", term: "pear-shaped silhouette", adultOnly: true },
2620
2735
  { id: "silhouette-inverted", label: "Inverted Triangle", dimension: "silhouette", description: "Broad shoulders, narrow hips", promptHint: "inverted-triangle silhouette", term: "inverted-triangle silhouette" },
2621
2736
  { id: "silhouette-athletic", label: "Athletic", dimension: "silhouette", description: "Lean, toned, V-taper", promptHint: "athletic silhouette", term: "athletic silhouette" },
2622
2737
  // -------------------- Face Shape --------------------
@@ -2703,17 +2818,17 @@ var PEOPLE = [
2703
2818
  { id: "lips-small", label: "Small", dimension: "lip-shape", description: "Petite mouth", promptHint: "a small petite mouth", term: "small petite mouth" },
2704
2819
  // -------------------- Lip State (what the lips are doing / wearing) --------------------
2705
2820
  { id: "lip-state-chapped", label: "Chapped", dimension: "lip-state", description: "Cracked, dry, weather-worn lips", promptHint: "with chapped, cracked, weather-worn dry lips", term: "chapped dry lips" },
2706
- { id: "lip-state-glossy", label: "Glossy", dimension: "lip-state", description: "High-shine, wet-look lips", promptHint: "with high-shine glossy wet-look lips", term: "glossy wet-look lips" },
2821
+ { id: "lip-state-glossy", label: "Glossy", dimension: "lip-state", description: "High-shine, wet-look lips", promptHint: "with a glossy lip finish", term: "glossy lips", adultOnly: true },
2707
2822
  { id: "lip-state-bare", label: "Bare", dimension: "lip-state", description: "Natural, untreated, no makeup", promptHint: "with bare, natural, untreated lips", term: "bare natural lips" },
2708
2823
  { id: "lip-state-bold-red", label: "Bold Red", dimension: "lip-state", description: "Saturated red lipstick statement", promptHint: "with a bold, saturated red lipstick statement", term: "bold red lipstick" },
2709
- { id: "lip-state-bitten", label: "Bitten", dimension: "lip-state", description: "Slight playful lip-bite, lower lip caught", promptHint: "playfully biting the lower lip", term: "biting the lower lip" },
2710
- { id: "lip-state-parted", label: "Parted", dimension: "lip-state", description: "Lips slightly parted, breath of air", promptHint: "with lips slightly parted, taking a soft breath", term: "lips slightly parted" },
2824
+ { id: "lip-state-bitten", label: "Bitten", dimension: "lip-state", description: "Slight playful lip-bite, lower lip caught", promptHint: "lightly biting the lower lip", term: "biting lower lip", adultOnly: true },
2825
+ { id: "lip-state-parted", label: "Parted", dimension: "lip-state", description: "Lips slightly parted, breath of air", promptHint: "with lips relaxed and slightly open, as if mid-sentence", term: "lips slightly open", adultOnly: true },
2711
2826
  { id: "lip-state-pursed", label: "Pursed", dimension: "lip-state", description: "Lips pressed and pushed forward", promptHint: "with lips pursed, pressed and pushed forward", term: "pursed lips" },
2712
2827
  { id: "lip-state-bold-black", label: "Bold Black", dimension: "lip-state", description: "Saturated black lipstick statement (goth / avant-garde)", promptHint: "with a bold, saturated black lipstick statement, goth / avant-garde", term: "bold black lipstick" },
2713
2828
  { id: "lip-state-burgundy", label: "Burgundy", dimension: "lip-state", description: "Deep wine-red lipstick", promptHint: "with deep wine-red burgundy lipstick", term: "burgundy lipstick" },
2714
2829
  { id: "lip-state-plum", label: "Plum", dimension: "lip-state", description: "Dark purple-toned lipstick", promptHint: "with dark purple-toned plum lipstick", term: "plum lipstick" },
2715
2830
  { id: "lip-state-mauve", label: "Mauve", dimension: "lip-state", description: "Muted mauve / dusty-pink lipstick", promptHint: "with muted mauve, dusty-pink lipstick", term: "mauve lipstick" },
2716
- { id: "lip-state-pouting", label: "Pouting", dimension: "lip-state", description: "Full pout", promptHint: "with a full pouting expression", term: "full pouting expression" },
2831
+ { id: "lip-state-pouting", label: "Pouting", dimension: "lip-state", description: "Full pout", promptHint: "with a full pouting expression", term: "full pouting expression", adultOnly: true },
2717
2832
  // -------------------- Hair Color --------------------
2718
2833
  // Blonde family (light → warm → cool)
2719
2834
  { id: "hair-platinum", label: "Platinum", dimension: "hair-color", description: "Very pale, icy platinum", promptHint: "platinum blonde hair, very pale and icy", term: "platinum blonde hair" },
@@ -2820,9 +2935,9 @@ var PEOPLE = [
2820
2935
  { id: "eyes-black", label: "Black", dimension: "eye-color", description: "Truly black iris (deeper than brown)", promptHint: "jet-black eyes with a truly black iris, deeper than brown", term: "jet-black eyes" },
2821
2936
  // -------------------- Eye State (what the eyes are doing / where they look) --------------------
2822
2937
  { id: "eye-state-closed", label: "Closed", dimension: "eye-state", description: "Eyes fully closed, peaceful", promptHint: "with eyes fully closed in a peaceful expression", term: "eyes fully closed" },
2823
- { id: "eye-state-half-lidded", label: "Half-lidded", dimension: "eye-state", description: "Heavy-lidded sleepy gaze", promptHint: "with heavy half-lidded sleepy eyes", term: "half-lidded sleepy eyes" },
2938
+ { id: "eye-state-half-lidded", label: "Half-lidded", dimension: "eye-state", description: "Heavy-lidded sleepy gaze", promptHint: "with drowsy, partly closed eyes, the lids sitting low over the iris", term: "drowsy, partly closed eyes", adultOnly: true },
2824
2939
  { id: "eye-state-wide-eyed", label: "Wide-eyed", dimension: "eye-state", description: "Eyes wide open, alert / surprised", promptHint: "with eyes wide open, alert and surprised" },
2825
- { id: "eye-state-staring-camera", label: "Staring at Camera", dimension: "eye-state", description: "Direct unbroken eye contact with the lens", promptHint: "staring directly at the camera with unbroken eye contact" },
2940
+ { id: "eye-state-staring-camera", label: "Staring at Camera", dimension: "eye-state", description: "Direct unbroken eye contact with the lens", promptHint: "looking directly into the camera", term: "direct gaze to camera" },
2826
2941
  { id: "eye-state-gazing-away", label: "Gazing Away", dimension: "eye-state", description: "Looking off-camera, contemplative", promptHint: "gazing off-camera with a contemplative expression", term: "gazing off-camera" },
2827
2942
  { id: "eye-state-gazing-up", label: "Gazing Up", dimension: "eye-state", description: "Eyes raised toward something above", promptHint: "with eyes gazing upward toward something above" },
2828
2943
  { id: "eye-state-gazing-down", label: "Gazing Down", dimension: "eye-state", description: "Eyes downcast", promptHint: "with eyes downcast, gazing softly downward" },
@@ -2839,21 +2954,21 @@ var PEOPLE = [
2839
2954
  { id: "texture-smooth", label: "Smooth", dimension: "skin-texture", description: "Flawless, silky smooth skin", promptHint: "with flawless, silky smooth skin", term: "flawless smooth skin" },
2840
2955
  { id: "texture-wrinkled", label: "Wrinkled", dimension: "skin-texture", description: "Aged, deeply lined skin", promptHint: "with deep wrinkles and aged skin texture", term: "deeply wrinkled aged skin" },
2841
2956
  { id: "texture-goosebumps", label: "Goosebumps", dimension: "skin-texture", description: "Raised goosebumps on skin", promptHint: "with goosebumps raised on the skin" },
2842
- { id: "texture-dewy", label: "Dewy", dimension: "skin-texture", description: "Glowing, dewy fresh skin", promptHint: "with dewy, glowing skin and a fresh sheen", term: "dewy glowing skin" },
2843
- { id: "texture-glistening", label: "Glistening", dimension: "skin-texture", description: "Sweat or oil sheen", promptHint: "with glistening skin, sweat or oil catching the light", term: "glistening skin" },
2957
+ { id: "texture-dewy", label: "Dewy", dimension: "skin-texture", description: "Glowing, dewy fresh skin", promptHint: "with dewy, luminous skin", term: "dewy skin" },
2958
+ { id: "texture-glistening", label: "Glistening", dimension: "skin-texture", description: "Sweat or oil sheen", promptHint: "with a light glistening sheen on the skin", term: "glistening sheen", adultOnly: true },
2844
2959
  { id: "texture-weathered", label: "Weathered", dimension: "skin-texture", description: "Sun-aged rough skin", promptHint: "with weathered, sun-worn rough skin", term: "weathered sun-worn skin" },
2845
2960
  { id: "texture-porcelain", label: "Porcelain", dimension: "skin-texture", description: "Flawless near-translucent porcelain", promptHint: "with flawless, near-translucent porcelain skin", term: "porcelain skin" },
2846
2961
  { id: "texture-sun-kissed", label: "Sun-kissed", dimension: "skin-texture", description: "Warm tan with healthy glow", promptHint: "with sun-kissed skin, warmly tanned with a healthy glow", term: "sun-kissed skin" },
2847
2962
  { id: "texture-tanned", label: "Tanned", dimension: "skin-texture", description: "Deeply, evenly tanned skin", promptHint: "with deeply, evenly bronzed tanned skin", term: "deeply tanned skin" },
2848
- { id: "texture-tan-lines", label: "Tan Lines", dimension: "skin-texture", description: "Visible tan lines from swimwear", promptHint: "with visible swimwear tan lines on the skin", term: "visible swimwear tan lines" },
2963
+ { id: "texture-tan-lines", label: "Tan Lines", dimension: "skin-texture", description: "Visible tan lines from swimwear", promptHint: "with visible swimwear tan lines on the skin", term: "visible swimwear tan lines", adultOnly: true },
2849
2964
  { id: "texture-freckled", label: "Freckled", dimension: "skin-texture", description: "Freckles across cheeks and nose", promptHint: "with visible freckles scattered across cheeks and the bridge of the nose", term: "freckled skin" },
2850
2965
  { id: "texture-ruddy", label: "Ruddy", dimension: "skin-texture", description: "Pink-flushed cheeks and nose", promptHint: "with ruddy, warm pink-flushed cheeks and nose", term: "ruddy flushed complexion" },
2851
2966
  { id: "texture-ashen", label: "Ashen / Pale", dimension: "skin-texture", description: "Cool pale, near-bloodless undertone", promptHint: "with ashen, cool pale, near-bloodless skin", term: "ashen pale skin" },
2852
- { id: "texture-oily", label: "Oily / Shiny", dimension: "skin-texture", description: "Slight oil sheen on T-zone", promptHint: "with a slight oily sheen across the T-zone", term: "oily shiny skin" },
2967
+ { id: "texture-oily", label: "Oily / Shiny", dimension: "skin-texture", description: "Slight oil sheen on T-zone", promptHint: "with a slight oily sheen across the T-zone", term: "oily shiny skin", adultOnly: true },
2853
2968
  { id: "texture-matte", label: "Matte", dimension: "skin-texture", description: "Poreless matte finish", promptHint: "with a poreless, matte skin finish", term: "matte skin finish" },
2854
2969
  { id: "texture-blemished", label: "Blemished", dimension: "skin-texture", description: "Visible blemishes, real-skin imperfections", promptHint: "with visible blemishes and natural real-skin imperfections", term: "blemished skin" },
2855
- { id: "texture-baby-soft", label: "Baby-soft", dimension: "skin-texture", description: "Smooth, fine-pored youthful skin", promptHint: "with baby-soft, fine-pored, youthful smooth skin", term: "baby-soft skin" },
2856
- { id: "texture-shower-fresh-wet", label: "Shower-Fresh Wet", dimension: "skin-texture", description: "Just-out-of-shower wet skin with water beads", promptHint: "with just-out-of-the-shower wet skin, water beading on the surface and rolling in slow droplets down the curves of the body", term: "shower-fresh wet skin" },
2970
+ { id: "texture-baby-soft", label: "Baby-soft", dimension: "skin-texture", description: "Smooth, fine-pored youthful skin", promptHint: "with soft, fine-pored skin", term: "soft fine-pored skin" },
2971
+ { id: "texture-shower-fresh-wet", label: "Shower-Fresh Wet", dimension: "skin-texture", description: "Just-out-of-shower wet skin with water beads", promptHint: "with fresh, water-dappled skin as if just out of the shower", term: "water-dappled skin", adultOnly: true },
2857
2972
  { id: "texture-acne-scarred", label: "Acne-scarred", dimension: "skin-texture", description: "Visible acne scarring (distinct from blemished \u2014 healed scar pattern)", promptHint: "with visible acne scarring, healed pitted-skin texture and uneven surface from past breakouts", term: "acne-scarred skin" },
2858
2973
  // -------------------- Distinctive Features --------------------
2859
2974
  { id: "feature-glasses", label: "Glasses", dimension: "distinctive-features", description: "Wears glasses", promptHint: "wearing glasses", term: "wearing glasses" },
@@ -2874,18 +2989,18 @@ var PEOPLE = [
2874
2989
  { id: "feature-sleeve-tattoo", label: "Sleeve Tattoo", dimension: "distinctive-features", description: "Full one-arm tattoo sleeve", promptHint: "with a full tattoo sleeve covering one arm in intricate ink" },
2875
2990
  { id: "feature-face-tattoo", label: "Face Tattoo", dimension: "distinctive-features", description: "Small face tattoo near eye or cheekbone", promptHint: "with a small face tattoo near the eye or cheekbone" },
2876
2991
  { id: "feature-hand-tattoos", label: "Hand Tattoos", dimension: "distinctive-features", description: "Knuckle / finger tattoos", promptHint: "with hand tattoos across knuckles and fingers" },
2877
- { id: "feature-back-tattoo", label: "Back Tattoo", dimension: "distinctive-features", description: "Large piece across the back", promptHint: "with a large back-piece tattoo across the upper back" },
2878
- { id: "feature-chest-tattoo", label: "Chest Tattoo", dimension: "distinctive-features", description: "Tattoo across chest / d\xE9colletage", promptHint: "with a tattoo across the chest and d\xE9colletage" },
2879
- { id: "feature-leg-tattoo", label: "Leg Tattoo", dimension: "distinctive-features", description: "Visible tattoo on leg / calf / thigh", promptHint: "with a visible tattoo on the leg, running across the calf or thigh" },
2992
+ { id: "feature-back-tattoo", label: "Back Tattoo", dimension: "distinctive-features", description: "Large piece across the back", promptHint: "with a large back-piece tattoo across the upper back", adultOnly: true },
2993
+ { id: "feature-chest-tattoo", label: "Chest Tattoo", dimension: "distinctive-features", description: "Tattoo across chest / d\xE9colletage", promptHint: "with a tattoo across the chest and d\xE9colletage", adultOnly: true },
2994
+ { id: "feature-leg-tattoo", label: "Leg Tattoo", dimension: "distinctive-features", description: "Visible tattoo on leg / calf / thigh", promptHint: "with a visible tattoo on the leg, running across the calf or thigh", adultOnly: true },
2880
2995
  { id: "feature-forearm-tattoo", label: "Forearm Tattoo", dimension: "distinctive-features", description: "Tattoo on inner or outer forearm", promptHint: "with a tattoo on the forearm, visible on the inner or outer side" },
2881
2996
  { id: "feature-visible-piercings", label: "Visible Piercings", dimension: "distinctive-features", description: "Multiple visible piercings (septum, nose, lip, multi-ear)", promptHint: "with multiple visible piercings \u2014 septum, nose, lip, and multi-ear", term: "multiple visible piercings" },
2882
2997
  { id: "feature-ear-piercings", label: "Ear Piercings", dimension: "distinctive-features", description: "Multiple stacked ear piercings (cartilage, helix, conch)", promptHint: "with multiple stacked ear piercings \u2014 cartilage, helix, and conch", term: "multiple stacked ear piercings" },
2883
2998
  { id: "feature-lip-piercing", label: "Lip Piercing", dimension: "distinctive-features", description: "Lip ring / labret stud", promptHint: "with a lip piercing, a small ring or labret stud at the lip" },
2884
2999
  { id: "feature-nostril-piercing", label: "Nostril Piercing", dimension: "distinctive-features", description: "Single nostril stud or ring", promptHint: "with a single nostril piercing, a small stud or ring at the nostril" },
2885
- { id: "feature-bare-shoulders", label: "Bare Shoulders", dimension: "distinctive-features", description: "Bare shoulders exposed", promptHint: "with bare shoulders exposed, the line of the collarbone and shoulder muscles uncovered" },
2886
- { id: "feature-collarbone-visible", label: "Collarbone Visible", dimension: "distinctive-features", description: "Prominent collarbone catching light", promptHint: "with a prominent collarbone clearly defined and catching the light", term: "visible collarbone" },
2887
- { id: "feature-midriff-visible", label: "Midriff Visible", dimension: "distinctive-features", description: "Exposed midriff between top and bottom", promptHint: "wearing a cropped style with the midriff visible", term: "visible midriff" },
2888
- { id: "feature-navel-visible", label: "Navel Visible", dimension: "distinctive-features", description: "Visible navel on bare stomach", promptHint: "with the navel visible", term: "visible navel" },
3000
+ { id: "feature-bare-shoulders", label: "Bare Shoulders", dimension: "distinctive-features", description: "Bare shoulders exposed", promptHint: "with the shoulders uncovered", term: "shoulders uncovered", adultOnly: true },
3001
+ { id: "feature-collarbone-visible", label: "Collarbone Visible", dimension: "distinctive-features", description: "Prominent collarbone catching light", promptHint: "with an open neckline that leaves the collarbones uncovered", term: "open neckline, collarbones uncovered", adultOnly: true },
3002
+ { id: "feature-midriff-visible", label: "Midriff Visible", dimension: "distinctive-features", description: "Exposed midriff between top and bottom", promptHint: "with a cropped hemline", term: "cropped hemline", adultOnly: true },
3003
+ { id: "feature-navel-visible", label: "Navel Visible", dimension: "distinctive-features", description: "Visible navel on bare stomach", promptHint: "with the navel visible", term: "visible navel", adultOnly: true },
2889
3004
  { id: "feature-elongated-neck", label: "Elongated Neck", dimension: "distinctive-features", description: "Long swan-like neck", promptHint: "with an elongated, swan-like neck, long and gracefully extended", term: "elongated swan-like neck" },
2890
3005
  { id: "feature-under-eye-circles", label: "Under-Eye Circles", dimension: "distinctive-features", description: "Subtle dark circles under the eyes (distinct from puffy eye-bags)", promptHint: "with subtle dark circles under the eyes", term: "dark under-eye circles" },
2891
3006
  { id: "feature-fangs", label: "Fangs", dimension: "distinctive-features", description: "Visible fangs (vampire / character archetype)", promptHint: "with visible fangs at the canines, character / vampire archetype", term: "visible fangs at the canines" },
@@ -2966,7 +3081,7 @@ var PEOPLE = [
2966
3081
  // ----- Latin America -----
2967
3082
  { id: "carioca-rio", label: "Carioca (Rio)", group: "Latin America", dimension: "regional-aesthetic", description: "Rio de Janeiro beach-and-favela-music Brazilian aesthetic", promptHint: "a Carioca aesthetic \u2014 Rio de Janeiro beach-and-favela-music vibe, sun-warmed Brazilian energy", term: "carioca aesthetic" },
2968
3083
  { id: "paulista", label: "Paulista (S\xE3o Paulo)", group: "Latin America", dimension: "regional-aesthetic", description: "S\xE3o Paulo metropolitan Brazilian creative-class aesthetic", promptHint: "a Paulista aesthetic \u2014 S\xE3o Paulo metropolitan vibe, urban-Brazilian creative-class polish", term: "paulista aesthetic" },
2969
- { id: "buenos-aires-tango", label: "Buenos Aires Tango", group: "Latin America", dimension: "regional-aesthetic", description: "Buenos Aires Recoleta / San Telmo tango aesthetic (feminine)", promptHint: "a Buenos Aires tango aesthetic \u2014 Recoleta / San Telmo vibe, sultry porte\xF1a confidence", term: "buenos aires tango aesthetic" },
3084
+ { id: "buenos-aires-tango", label: "Buenos Aires Tango", group: "Latin America", dimension: "regional-aesthetic", description: "Buenos Aires Recoleta / San Telmo tango aesthetic (feminine)", promptHint: "a Buenos Aires tango aesthetic \u2014 Recoleta / San Telmo vibe, sultry porte\xF1a confidence", term: "buenos aires tango aesthetic", adultOnly: true },
2970
3085
  { id: "porte\xF1o", label: "Porte\xF1o", group: "Latin America", dimension: "regional-aesthetic", description: "Buenos Aires refined milonguero aesthetic (masculine)", promptHint: "a Porte\xF1o aesthetic \u2014 Buenos Aires vibe, refined milonguero presence", term: "porte\xF1o aesthetic" },
2971
3086
  { id: "mexico-city-chic", label: "Mexico City Chic", group: "Latin America", dimension: "regional-aesthetic", description: "CDMX Roma / Polanco modern-Mexican aesthetic", promptHint: "a Mexico City chic aesthetic \u2014 CDMX Roma / Polanco vibe, modern-Mexican polish", term: "mexico city chic aesthetic" },
2972
3087
  { id: "chilango-traditional", label: "Chilango Traditional", group: "Latin America", dimension: "regional-aesthetic", description: "Mexico City warm folk-and-feast aesthetic", promptHint: "a Chilango traditional aesthetic \u2014 Mexico City vibe, warm folk-and-feast mood", term: "chilango traditional aesthetic" },
@@ -3211,7 +3326,7 @@ function emitIndependentFragments(value, fragmentFor) {
3211
3326
  const navel = ids.includes("feature-navel-visible");
3212
3327
  for (const id of ids) {
3213
3328
  if (midriff && navel && (id === "feature-midriff-visible" || id === "feature-navel-visible")) {
3214
- if (id === "feature-midriff-visible") out.push("wearing a cropped style, midriff and navel visible");
3329
+ if (id === "feature-midriff-visible" && fragmentFor(id) !== "") out.push("with a cropped hemline and the navel showing");
3215
3330
  continue;
3216
3331
  }
3217
3332
  const fragment = fragmentFor(id);
@@ -3223,6 +3338,8 @@ function collectPersonFragments(data, fragmentFor) {
3223
3338
  const hints = [];
3224
3339
  const pre = typeof data.preText === "string" ? data.preText.trim() : "";
3225
3340
  if (pre) hints.push(pre);
3341
+ const floored = isMinorAge(data);
3342
+ const emit = floored ? (id) => typeof id === "string" && getPerson(id)?.adultOnly === true ? "" : fragmentFor(id) : fragmentFor;
3226
3343
  const fieldByDimension = getRegisteredPersonFieldByDimension();
3227
3344
  for (const dimension of getRegisteredPersonDimensionOrder()) {
3228
3345
  const field = fieldByDimension[dimension];
@@ -3231,34 +3348,34 @@ function collectPersonFragments(data, fragmentFor) {
3231
3348
  if (dimension === "age") {
3232
3349
  const ageId = typeof raw === "string" ? raw : void 0;
3233
3350
  const customAge = typeof data.customAge === "number" ? data.customAge : void 0;
3234
- const h = buildAgeFragment(ageId, customAge, fragmentFor);
3351
+ const h = buildAgeFragment(ageId, customAge, emit);
3235
3352
  if (h) hints.push(h);
3236
3353
  continue;
3237
3354
  }
3238
3355
  if (dimension === "ethnicity") {
3239
- const h = buildEthnicityFragment(raw, fragmentFor);
3356
+ const h = buildEthnicityFragment(raw, emit);
3240
3357
  if (h) hints.push(h);
3241
3358
  continue;
3242
3359
  }
3243
3360
  if (dimension === "hair-color") {
3244
- const h = buildHairColorFragment(raw, fragmentFor);
3361
+ const h = buildHairColorFragment(raw, emit);
3245
3362
  if (h) hints.push(h);
3246
3363
  continue;
3247
3364
  }
3248
3365
  if (dimension === "eye-color") {
3249
- for (const h of buildEyeColorFragments(raw, fragmentFor)) hints.push(h);
3366
+ for (const h of buildEyeColorFragments(raw, emit)) hints.push(h);
3250
3367
  continue;
3251
3368
  }
3252
3369
  if (dimension === "distinctive-features" || dimension === "lip-state" || dimension === "eye-state" || dimension === "skin-texture" || dimension === "regional-aesthetic") {
3253
- for (const h of emitIndependentFragments(raw, fragmentFor)) hints.push(h);
3370
+ for (const h of emitIndependentFragments(raw, emit)) hints.push(h);
3254
3371
  continue;
3255
3372
  }
3256
3373
  if (typeof raw !== "string" || raw.length === 0) continue;
3257
- const hint = fragmentFor(raw);
3374
+ const hint = emit(raw);
3258
3375
  if (hint) hints.push(hint);
3259
3376
  }
3260
3377
  if (typeof data.lips === "string" && data.lips.length > 0 && !data.lipFullness && !data.lipShape) {
3261
- const legacyLips = fragmentFor(data.lips);
3378
+ const legacyLips = emit(data.lips);
3262
3379
  if (legacyLips) hints.push(legacyLips);
3263
3380
  }
3264
3381
  const post = typeof data.postText === "string" ? data.postText.trim() : "";
@@ -3340,17 +3457,17 @@ var MOODS = [
3340
3457
  { id: "determined", label: "Determined", category: "intense", description: "Resolute, focused will", promptHint: "with a determined, resolute expression and iron focus", term: "determined, resolute expression" },
3341
3458
  { id: "passionate", label: "Passionate", category: "intense", description: "Burning passion", promptHint: "with a passionate, burning expression full of conviction", term: "passionate expression" },
3342
3459
  { id: "brooding", label: "Brooding", category: "intense", description: "Dark, brooding melancholy", promptHint: "with a brooding, dark expression and moody introspection", term: "brooding expression" },
3343
- { id: "seductive", label: "Seductive", category: "intense", description: "Alluring, seductive", promptHint: "with a seductive, alluring expression and lidded gaze", term: "seductive expression" },
3460
+ { id: "seductive", label: "Seductive", category: "intense", description: "Alluring, seductive", promptHint: "with a seductive, alluring expression and lidded gaze", term: "seductive expression", adultOnly: true },
3344
3461
  { id: "defiant", label: "Defiant", category: "intense", description: "Defiant, unyielding", promptHint: "with a defiant, unyielding expression and challenging stance", term: "defiant expression" },
3345
- { id: "sultry", label: "Sultry", category: "intense", description: "Smoldering, heavy-lidded", promptHint: "with a sultry, smoldering expression, heavy-lidded gaze and lips softly parted", term: "sultry expression" },
3346
- { id: "smoldering", label: "Smoldering", category: "intense", description: "Coiled, slow-burning intensity", promptHint: "with a smoldering, coiled expression, slow-burning intensity behind half-closed eyes and a still, predatory calm", term: "smoldering intensity" },
3462
+ { id: "sultry", label: "Sultry", category: "intense", description: "Smoldering, heavy-lidded", promptHint: "with a sultry, smoldering expression, heavy-lidded gaze and lips softly parted", term: "sultry expression", adultOnly: true },
3463
+ { id: "smoldering", label: "Smoldering", category: "intense", description: "Coiled, slow-burning intensity", promptHint: "with a smoldering, coiled expression, slow-burning intensity behind half-closed eyes and a still, predatory calm", term: "smoldering intensity", adultOnly: true },
3347
3464
  { id: "sinister", label: "Sinister", category: "intense", description: "Dark, malicious, threatening", promptHint: "with a sinister expression, a slow crooked smile and eyes glinting with dark malicious intent", term: "sinister expression" },
3348
3465
  { id: "wiccan-mystical", label: "Wiccan / Mystical", category: "intense", description: "Quietly otherworldly, occult", promptHint: "with a quietly mystical, otherworldly expression, eyes distant and knowing as if reading something the camera cannot see", term: "occult, otherworldly mystical expression" },
3349
3466
  { id: "lazy-shy", label: "Lazy Shy", category: "neutral", description: "Drowsy, soft, half-shy", promptHint: "with a soft, drowsy half-shy expression, eyes lowered and barely-there smile, languid and unbothered", term: "drowsy, half-shy expression" },
3350
3467
  { id: "awe", label: "Awe", category: "intense", description: "Wonder, reverent", promptHint: "with an awestruck expression of wonder, mouth slightly agape and wide-eyed", term: "awestruck wonder" },
3351
3468
  { id: "shocked", label: "Shocked", category: "intense", description: "Surprised, mouth open", promptHint: "with a shocked, surprised expression, eyes wide, brows raised and mouth open", term: "shocked expression" },
3352
3469
  // -------------------- Micro-emotions --------------------
3353
- { id: "flirty", label: "Flirty", category: "positive", description: "Playful flirtation, lingering smile, sustained eye contact", promptHint: "with a flirty expression, a lingering playful smile and sustained, knowing eye contact with the camera", term: "flirty expression" },
3470
+ { id: "flirty", label: "Flirty", category: "positive", description: "Playful flirtation, lingering smile, sustained eye contact", promptHint: "with a flirty expression, a lingering playful smile and sustained, knowing eye contact with the camera", term: "flirty expression", adultOnly: true },
3354
3471
  { id: "suspicious", label: "Suspicious", category: "negative", description: "Wary distrust, narrowed eyes, side-eye", promptHint: "with a suspicious, wary expression, eyes narrowed in distrust and a sidelong side-eye glance", term: "suspicious, wary expression" },
3355
3472
  { id: "resigned", label: "Resigned", category: "neutral", description: "Quiet acceptance of an unpleasant situation, sigh", promptHint: "with a resigned expression of quiet acceptance, shoulders softly dropped mid-sigh and a tired half-closed gaze", term: "resigned expression of quiet acceptance" },
3356
3473
  { id: "conflicted", label: "Conflicted", category: "neutral", description: "Visible internal struggle, brow furrowed, gaze unfocused", promptHint: "with a conflicted expression of visible internal struggle, brow furrowed and gaze unfocused, caught between two impulses", term: "conflicted, torn expression" }
@@ -3358,7 +3475,7 @@ var MOODS = [
3358
3475
  var moodById = new Map(MOODS.map((m) => [m.id, m]));
3359
3476
  function getMood(id) {
3360
3477
  if (!id) return void 0;
3361
- return moodById.get(id);
3478
+ return overlayEntry("mood", id, moodById.get(id));
3362
3479
  }
3363
3480
  function getMoodLabel(id, fallback) {
3364
3481
  const m = getMood(id);
@@ -3450,8 +3567,8 @@ var POSES = [
3450
3567
  { id: "cross-legged", label: "Cross-legged", category: "seated", description: "Seated cross-legged on floor", promptHint: "sitting cross-legged on the ground", term: "sitting cross-legged" },
3451
3568
  { id: "kneeling", label: "Kneeling", category: "seated", description: "Kneeling on the ground", promptHint: "kneeling on one or both knees" },
3452
3569
  { id: "crouching", label: "Crouching", category: "seated", description: "Crouched low", promptHint: "crouched low with knees bent" },
3453
- { id: "lounging", label: "Lounging", category: "seated", description: "Reclined, relaxed sitting", promptHint: "lounging in a relaxed, reclined position" },
3454
- { id: "sitting-edge-of-bed", label: "Sitting on Edge of Bed", category: "seated", description: "Perched on the edge of a bed", promptHint: "perched on the edge of a bed, hands resting on the mattress", term: "sitting on the edge of a bed" },
3570
+ { id: "lounging", label: "Lounging", category: "seated", description: "Reclined, relaxed sitting", promptHint: "lounging in a relaxed, reclined position", adultOnly: true },
3571
+ { id: "sitting-edge-of-bed", label: "Sitting on Edge of Bed", category: "seated", description: "Perched on the edge of a bed", promptHint: "perched on the edge of a bed, hands resting on the mattress", term: "sitting on the edge of a bed", adultOnly: true },
3455
3572
  { id: "chair-arm-drape", label: "Legs Draped Over Chair", category: "seated", description: "Legs draped over chair arm", promptHint: "sitting sideways in a chair with legs draped casually over one arm", term: "legs draped over the chair arm" },
3456
3573
  { id: "elbow-propped", label: "Cheek on Propped Elbow", category: "seated", description: "Cheek resting on a propped elbow", promptHint: "seated with cheek resting against a propped-up elbow, contemplative", term: "cheek resting on a propped elbow" },
3457
3574
  { id: "lying-on-stomach-reading", label: "Lying Prone Reading", category: "seated", description: "Lying prone, propped on elbows reading", promptHint: "lying on the stomach, propped up on elbows while reading", term: "lying prone propped on elbows, reading" },
@@ -3474,13 +3591,13 @@ var POSES = [
3474
3591
  { id: "throwing", label: "Throwing", category: "action", description: "Mid-throw motion", promptHint: "caught mid-throw, body coiled and releasing", term: "caught mid-throw" },
3475
3592
  { id: "leaping", label: "Leaping", category: "action", description: "Leaping forward dynamically", promptHint: "leaping forward dynamically with body extended" },
3476
3593
  { id: "dramatic-action", label: "Dramatic Action", category: "action", description: "Exaggerated action pose", promptHint: "in a dramatic, exaggerated action pose full of motion", term: "dramatic exaggerated action pose" },
3477
- { id: "biting-lip", label: "Biting Lip", category: "action", description: "Slight playful lip-bite", promptHint: "biting the lower lip with a subtle playful expression", term: "biting the lower lip" },
3594
+ { id: "biting-lip", label: "Biting Lip", category: "action", description: "Slight playful lip-bite", promptHint: "lightly biting the lower lip, a subtle playful expression", term: "biting lower lip", adultOnly: true },
3478
3595
  { id: "mid-laugh", label: "Mid-Laugh", category: "action", description: "Caught mid-laugh, head back", promptHint: "caught mid-laugh with head tipped back, eyes crinkled" },
3479
3596
  { id: "pointing-at-camera", label: "Pointing at Camera", category: "action", description: "Pointing directly at camera", promptHint: "pointing one finger directly at the camera, arm extended", term: "pointing directly at the camera" },
3480
3597
  { id: "tongue-out", label: "Sticking Tongue Out", category: "action", description: "Playful tongue-out expression", promptHint: "sticking the tongue out playfully" },
3481
3598
  { id: "thinking", label: "Thinking", category: "action", description: "Hand on chin, contemplative", promptHint: "in a thinking pose with hand on chin, gaze contemplative", term: "thinking pose, hand on chin" },
3482
3599
  // -------------------- Resting --------------------
3483
- { id: "lying-down", label: "Lying Down", category: "resting", description: "Lying flat", promptHint: "lying down flat, relaxed" },
3600
+ { id: "lying-down", label: "Lying Down", category: "resting", description: "Lying flat", promptHint: "lying down flat, relaxed", adultOnly: true },
3484
3601
  { id: "sleeping", label: "Sleeping", category: "resting", description: "Eyes closed, sleeping", promptHint: "sleeping peacefully with eyes closed" },
3485
3602
  { id: "hugging", label: "Hugging", category: "resting", description: "Embracing another", promptHint: "hugging or embracing another person" },
3486
3603
  { id: "looking-away", label: "Looking Away", category: "resting", description: "Head turned, looking away", promptHint: "head turned, looking off away from the camera" },
@@ -3502,7 +3619,7 @@ var POSES = [
3502
3619
  { id: "leaning-back", label: "Leaning Back", category: "body-lean", description: "Torso leaning back slightly", promptHint: "with the torso leaning back at a slight backward angle", term: "torso leaning slightly back" },
3503
3620
  { id: "leaning-forward", label: "Leaning Forward", category: "body-lean", description: "Torso leaning toward camera", promptHint: "with the torso leaning forward toward the camera", term: "torso leaning forward" },
3504
3621
  { id: "body-lean-contrapposto", label: "Contrapposto", category: "body-lean", description: "Weight on one leg, hip pushed out", promptHint: "with the weight on one leg and one hip pushed out, classical contrapposto", term: "contrapposto, hip pushed out" },
3505
- { id: "arched-back", label: "Arched Back", category: "body-lean", description: "Back gently arched, chest forward", promptHint: "with the back gently arched, chest forward" },
3622
+ { id: "arched-back", label: "Arched Back", category: "body-lean", description: "Back gently arched, chest forward", promptHint: "with the back gently arched, chest forward", adultOnly: true },
3506
3623
  { id: "shoulder-rolled-forward", label: "Shoulder Rolled Forward", category: "body-lean", description: "One shoulder rolled forward", promptHint: "with one shoulder rolled forward, asymmetric stance", term: "one shoulder rolled forward" },
3507
3624
  // -------------------- Head Tilt --------------------
3508
3625
  { id: "tilted-up", label: "Tilted Up", category: "head-tilt", description: "Head tipped slightly upward", promptHint: "with the head tipped slightly upward", term: "head tipped slightly up" },
@@ -3528,7 +3645,7 @@ var POSES = [
3528
3645
  var poseById = new Map(POSES.map((p) => [p.id, p]));
3529
3646
  function getPose(id) {
3530
3647
  if (!id) return void 0;
3531
- return poseById.get(id);
3648
+ return overlayEntry("pose", id, poseById.get(id));
3532
3649
  }
3533
3650
  function getPoseLabel(id, fallback) {
3534
3651
  const p = getPose(id);
@@ -3727,14 +3844,14 @@ var STYLINGS = [
3727
3844
  { id: "jewelry-pearl", label: "Pearl", dimension: "jewelry", description: "Pearl necklace / earrings", promptHint: "wearing a pearl necklace and pearl earrings", term: "pearl jewelry" },
3728
3845
  { id: "jewelry-chunky", label: "Chunky", dimension: "jewelry", description: "Chunky chains and rings", promptHint: "wearing chunky chains and oversized rings", term: "chunky chains and rings" },
3729
3846
  { id: "jewelry-smart-watch", label: "Smart Watch", dimension: "jewelry", description: "Modern smart watch on wrist", promptHint: "wearing a modern smart watch on the wrist with a sleek digital face and silicone band" },
3730
- { id: "jewelry-choker-leather", label: "Leather Choker", dimension: "jewelry", description: "Black leather choker close to throat", promptHint: "wearing a black leather choker fitted close around the throat" },
3731
- { id: "jewelry-choker-velvet", label: "Velvet Choker", dimension: "jewelry", description: "Velvet ribbon choker, dressy and vintage", promptHint: "wearing a velvet ribbon choker around the throat, a dressy vintage touch with a small pendant at the front" },
3732
- { id: "jewelry-choker-chain", label: "Chain Choker", dimension: "jewelry", description: "Thin metal-chain choker", promptHint: "wearing a thin metal-chain choker tight around the throat" },
3847
+ { id: "jewelry-choker-leather", label: "Leather Choker", dimension: "jewelry", description: "Black leather choker close to throat", promptHint: "wearing a black leather choker fitted close around the throat", adultOnly: true },
3848
+ { id: "jewelry-choker-velvet", label: "Velvet Choker", dimension: "jewelry", description: "Velvet ribbon choker, dressy and vintage", promptHint: "wearing a velvet ribbon choker around the throat, a dressy vintage touch with a small pendant at the front", adultOnly: true },
3849
+ { id: "jewelry-choker-chain", label: "Chain Choker", dimension: "jewelry", description: "Thin metal-chain choker", promptHint: "wearing a thin metal-chain choker tight around the throat", adultOnly: true },
3733
3850
  { id: "jewelry-stud-earrings", label: "Stud Earrings", dimension: "jewelry", description: "Small simple studs", promptHint: "wearing small simple stud earrings" },
3734
3851
  { id: "jewelry-hoop-earrings", label: "Hoop Earrings", dimension: "jewelry", description: "Gold or silver hoops", promptHint: "wearing gold or silver hoop earrings" },
3735
3852
  { id: "jewelry-ear-cuff", label: "Ear Cuff", dimension: "jewelry", description: "Sculptural cuff climbing the cartilage", promptHint: "wearing a sculptural ear cuff climbing the cartilage of one ear" },
3736
3853
  { id: "jewelry-septum-ring", label: "Septum Ring", dimension: "jewelry", description: "Septum nose ring", promptHint: "wearing a septum nose ring through the center of the nose" },
3737
- { id: "jewelry-body-chain", label: "Body Chain", dimension: "jewelry", description: "Thin metallic chain across torso", promptHint: "wearing a thin metallic body chain draped across the torso" },
3854
+ { id: "jewelry-body-chain", label: "Body Chain", dimension: "jewelry", description: "Thin metallic chain across torso", promptHint: "wearing a thin metallic body chain draped across the torso", adultOnly: true },
3738
3855
  // -------------------- Nails --------------------
3739
3856
  { id: "nails-polished", label: "Polished", dimension: "nails", description: "Clear or nude polished nails", promptHint: "with polished nude nails", term: "polished nude nails" },
3740
3857
  { id: "nails-red", label: "Red", dimension: "nails", description: "Bright red nail polish", promptHint: "with bright red nail polish", term: "red nail polish" },
@@ -3767,9 +3884,9 @@ var STYLINGS = [
3767
3884
  { id: "fabric-chiffon", label: "Chiffon", dimension: "fabric", description: "Sheer flowing chiffon", promptHint: "wearing sheer chiffon with a lightweight floating drape and soft translucent layers" },
3768
3885
  { id: "fabric-fur", label: "Fur", dimension: "fabric", description: "Thick plush fur", promptHint: "wearing thick plush fur with long dense strands and natural variation", term: "fur garment" },
3769
3886
  { id: "fabric-sequins", label: "Sequins", dimension: "fabric", description: "Sparkling sequin fabric", promptHint: "wearing sparkling sequined fabric catching light with countless tiny reflective facets" },
3770
- { id: "fabric-latex", label: "Latex", dimension: "fabric", description: "Glossy latex", promptHint: "wearing glossy latex with a high-shine liquid look clinging to the body" },
3887
+ { id: "fabric-latex", label: "Latex", dimension: "fabric", description: "Glossy latex", promptHint: "wearing glossy latex with a high-shine liquid look clinging to the body", adultOnly: true },
3771
3888
  // -------------------- Outfit (single-pick complete look — overrides individual pieces) --------------------
3772
- { id: "outfit-school-uniform", label: "School Uniform", dimension: "outfit", description: "Classic school uniform set", promptHint: "dressed in a classic school uniform \u2014 collared blouse, pleated skirt and knee-high socks" },
3889
+ { id: "outfit-school-uniform", label: "School Uniform", dimension: "outfit", description: "Classic school uniform set", promptHint: "dressed in a classic school uniform \u2014 collared blouse, pleated skirt and knee-high socks", adultOnly: true },
3773
3890
  { id: "outfit-business-suit", label: "Business Suit", dimension: "outfit", description: "Tailored two-piece business suit", promptHint: "dressed in a sharp tailored two-piece business suit with a crisp shirt underneath" },
3774
3891
  { id: "outfit-tuxedo", label: "Tuxedo", dimension: "outfit", description: "Black-tie tuxedo", promptHint: "dressed in a black tuxedo with satin lapels and a bow tie" },
3775
3892
  { id: "outfit-evening-gown", label: "Evening Gown", dimension: "outfit", description: "Floor-length formal gown", promptHint: "dressed in a floor-length evening gown with a fitted bodice and a flowing skirt" },
@@ -3783,9 +3900,9 @@ var STYLINGS = [
3783
3900
  { id: "outfit-athleisure", label: "Athleisure Set", dimension: "outfit", description: "Matching athleisure top and bottom", promptHint: "dressed in a matching athleisure set \u2014 a fitted top and leggings in the same tone" },
3784
3901
  { id: "outfit-tracksuit", label: "Tracksuit", dimension: "outfit", description: "Two-piece zip-front tracksuit", promptHint: "dressed in a two-piece tracksuit \u2014 a zip-front jacket and matching joggers" },
3785
3902
  { id: "outfit-soccer-jersey", label: "Soccer Jersey", dimension: "outfit", description: "National-team football jersey with crest", promptHint: "wearing a national-team soccer jersey \u2014 team colors and trim with the team crest on the chest" },
3786
- { id: "outfit-bikini", label: "Bikini", dimension: "outfit", description: "Two-piece swimsuit", promptHint: "in a two-piece bikini" },
3787
- { id: "outfit-one-piece-swim", label: "One-Piece Swimsuit", dimension: "outfit", description: "Fitted one-piece swimsuit", promptHint: "in a fitted one-piece swimsuit" },
3788
- { id: "outfit-lingerie", label: "Lingerie Set", dimension: "outfit", description: "Matching lingerie set", promptHint: "in a matching lingerie set" },
3903
+ { id: "outfit-bikini", label: "Bikini", dimension: "outfit", description: "Two-piece swimsuit", promptHint: "in a two-piece bikini", adultOnly: true },
3904
+ { id: "outfit-one-piece-swim", label: "One-Piece Swimsuit", dimension: "outfit", description: "Fitted one-piece swimsuit", promptHint: "in a fitted one-piece swimsuit", adultOnly: true },
3905
+ { id: "outfit-lingerie", label: "Lingerie Set", dimension: "outfit", description: "Matching lingerie set", promptHint: "in a matching lingerie set", adultOnly: true },
3789
3906
  { id: "outfit-pajamas", label: "Pajamas", dimension: "outfit", description: "Soft cotton pajama set", promptHint: "in a soft cotton pajama set \u2014 a button-up top and matching pants" },
3790
3907
  { id: "outfit-bathrobe", label: "Bathrobe", dimension: "outfit", description: "Plush terrycloth robe", promptHint: "wrapped in a plush terrycloth bathrobe tied at the waist" },
3791
3908
  { id: "outfit-kimono", label: "Kimono", dimension: "outfit", description: "Traditional Japanese kimono", promptHint: "dressed in a traditional Japanese kimono with an obi sash" },
@@ -3818,14 +3935,14 @@ var STYLINGS = [
3818
3935
  { id: "top-polo", label: "Polo Shirt", dimension: "top", description: "Short-sleeve polo", promptHint: "wearing a fitted polo shirt with a small ribbed collar and short sleeves" },
3819
3936
  { id: "top-tube-top", label: "Tube Top", dimension: "top", description: "Strapless tube top", promptHint: "wearing a strapless tube top fitted across the chest" },
3820
3937
  { id: "top-camisole", label: "Camisole", dimension: "top", description: "Thin satin cami", promptHint: "wearing a thin satin camisole with delicate spaghetti straps" },
3821
- { id: "top-corset", label: "Corset", dimension: "top", description: "Structured corset top", promptHint: "wearing a structured corset top with boning and visible lacing" },
3822
- { id: "top-bra-top", label: "Bra Top", dimension: "top", description: "Bra-style top showing midriff", promptHint: "wearing a fitted bra-style top with the midriff exposed" },
3938
+ { id: "top-corset", label: "Corset", dimension: "top", description: "Structured corset top", promptHint: "wearing a structured corset top with boning and visible lacing", adultOnly: true },
3939
+ { id: "top-bra-top", label: "Bra Top", dimension: "top", description: "Bra-style top showing midriff", promptHint: "wearing a fitted bra-style top with the midriff exposed", adultOnly: true },
3823
3940
  { id: "top-sports-bra", label: "Sports Bra", dimension: "top", description: "Athletic sports bra", promptHint: "wearing a structured sports bra with athletic strapping" },
3824
- { id: "top-bikini-top", label: "Bikini Top", dimension: "top", description: "Triangle bikini top", promptHint: "wearing a triangle bikini top tied behind the neck and back" },
3941
+ { id: "top-bikini-top", label: "Bikini Top", dimension: "top", description: "Triangle bikini top", promptHint: "wearing a triangle bikini top tied behind the neck and back", adultOnly: true },
3825
3942
  { id: "top-bodysuit", label: "Bodysuit", dimension: "top", description: "Fitted one-piece torso garment", promptHint: "wearing a fitted bodysuit, a one-piece torso garment snapped at the inseam" },
3826
3943
  { id: "top-leotard", label: "Leotard", dimension: "top", description: "Dance-style fitted top with sleeves", promptHint: "wearing a dance-style leotard, a fitted one-piece top with sleeves" },
3827
- { id: "top-mesh-top", label: "Mesh Top", dimension: "top", description: "Sheer mesh fabric top", promptHint: "wearing a sheer mesh top, the open-weave fabric letting skin show through" },
3828
- { id: "top-sheer-top", label: "Sheer Top", dimension: "top", description: "Translucent fabric top", promptHint: "wearing a sheer translucent top with a soft drape, hinting at the silhouette underneath" },
3944
+ { id: "top-mesh-top", label: "Mesh Top", dimension: "top", description: "Sheer mesh fabric top", promptHint: "wearing a sheer mesh top, the open-weave fabric letting skin show through", adultOnly: true },
3945
+ { id: "top-sheer-top", label: "Sheer Top", dimension: "top", description: "Translucent fabric top", promptHint: "wearing a sheer translucent top with a soft drape, hinting at the silhouette underneath", adultOnly: true },
3829
3946
  { id: "top-bandeau", label: "Bandeau", dimension: "top", description: "Strapless tube top", promptHint: "wearing a strapless bandeau top wrapped across the chest" },
3830
3947
  // -------------------- Bottom (lower-body garment) --------------------
3831
3948
  { id: "bottom-jeans", label: "Jeans", dimension: "bottom", description: "Classic blue jeans", promptHint: "in classic five-pocket blue jeans with a straight leg cut" },
@@ -3865,11 +3982,11 @@ var STYLINGS = [
3865
3982
  { id: "outerwear-fur-coat", label: "Fur Coat", dimension: "outerwear", description: "Plush fur coat", promptHint: "wearing a plush fur coat with a deep textured pile" },
3866
3983
  // -------------------- Legwear (between bottom and footwear) --------------------
3867
3984
  { id: "legwear-bare", label: "Bare Legs", dimension: "legwear", description: "Bare legs, no hosiery", promptHint: "with bare legs, no hosiery" },
3868
- { id: "legwear-sheer-tights", label: "Sheer Tights", dimension: "legwear", description: "Sheer nude pantyhose", promptHint: "with sheer nude pantyhose" },
3985
+ { id: "legwear-sheer-tights", label: "Sheer Tights", dimension: "legwear", description: "Sheer nude pantyhose", promptHint: "with sheer nude pantyhose", adultOnly: true },
3869
3986
  { id: "legwear-opaque-tights", label: "Opaque Tights", dimension: "legwear", description: "Solid opaque tights", promptHint: "with opaque black tights covering the legs" },
3870
- { id: "legwear-fishnets", label: "Fishnets", dimension: "legwear", description: "Diamond-mesh fishnets", promptHint: "with fishnet stockings, the skin showing through the diamond mesh", term: "fishnet stockings" },
3871
- { id: "legwear-thigh-highs", label: "Thigh-High Stockings", dimension: "legwear", description: "Thigh-high stockings", promptHint: "with thigh-high stockings stopping mid-thigh" },
3872
- { id: "legwear-lace-top-stockings", label: "Lace-Top Stockings", dimension: "legwear", description: "Lace-banded thigh-highs", promptHint: "with lace-topped thigh-high stockings cinched mid-thigh" },
3987
+ { id: "legwear-fishnets", label: "Fishnets", dimension: "legwear", description: "Diamond-mesh fishnets", promptHint: "with fishnet stockings, the skin showing through the diamond mesh", term: "fishnet stockings", adultOnly: true },
3988
+ { id: "legwear-thigh-highs", label: "Thigh-High Stockings", dimension: "legwear", description: "Thigh-high stockings", promptHint: "with thigh-high stockings stopping mid-thigh", adultOnly: true },
3989
+ { id: "legwear-lace-top-stockings", label: "Lace-Top Stockings", dimension: "legwear", description: "Lace-banded thigh-highs", promptHint: "with lace-topped thigh-high stockings cinched mid-thigh", adultOnly: true },
3873
3990
  { id: "legwear-knee-highs", label: "Knee-High Socks", dimension: "legwear", description: "Knee-high socks", promptHint: "with knee-high socks pulled up the calf" },
3874
3991
  { id: "legwear-crew-socks", label: "Crew Socks", dimension: "legwear", description: "Mid-calf crew socks", promptHint: "with simple crew socks visible above the shoes" },
3875
3992
  { id: "legwear-ankle-socks", label: "Ankle Socks", dimension: "legwear", description: "Low-cut ankle socks", promptHint: "with low-cut ankle socks barely showing above the shoes" },
@@ -3883,7 +4000,7 @@ var STYLINGS = [
3883
4000
  { id: "footwear-platforms", label: "Platform Shoes", dimension: "footwear", description: "Tall platform soles", promptHint: "in platform shoes with thick chunky soles" },
3884
4001
  { id: "footwear-ankle-boots", label: "Ankle Boots", dimension: "footwear", description: "Fitted ankle boots", promptHint: "in fitted black ankle boots" },
3885
4002
  { id: "footwear-knee-boots", label: "Knee-High Boots", dimension: "footwear", description: "Tall knee-high boots", promptHint: "in tall knee-high leather boots" },
3886
- { id: "footwear-thigh-high-boots", label: "Thigh-High Boots", dimension: "footwear", description: "Thigh-high boots", promptHint: "in over-the-knee thigh-high leather boots" },
4003
+ { id: "footwear-thigh-high-boots", label: "Thigh-High Boots", dimension: "footwear", description: "Thigh-high boots", promptHint: "in over-the-knee thigh-high leather boots", adultOnly: true },
3887
4004
  { id: "footwear-combat-boots", label: "Combat Boots", dimension: "footwear", description: "Laced combat boots", promptHint: "in laced black combat boots with a chunky lugged sole" },
3888
4005
  { id: "footwear-chelsea-boots", label: "Chelsea Boots", dimension: "footwear", description: "Slip-on Chelsea boots", promptHint: "in clean Chelsea boots with elastic side panels" },
3889
4006
  { id: "footwear-cowboy-boots", label: "Cowboy Boots", dimension: "footwear", description: "Pointed-toe cowboy boots", promptHint: "in pointed-toe cowboy boots with a stacked heel and decorative stitching" },
@@ -3902,18 +4019,18 @@ var STYLINGS = [
3902
4019
  { id: "footwear-espadrilles", label: "Espadrilles", dimension: "footwear", description: "Woven jute-sole canvas summer shoe", promptHint: "in woven jute-sole espadrilles with a canvas upper, a casual summer shoe" },
3903
4020
  // -------------------- Wardrobe State (modifier — composes with any garment) --------------------
3904
4021
  { id: "state-oversized", label: "Oversized", dimension: "wardrobe-state", description: "Loose, oversized fit", promptHint: "the clothing loose and oversized, draping freely well past the body", term: "oversized fit" },
3905
- { id: "state-fitted", label: "Fitted", dimension: "wardrobe-state", description: "Form-fitting silhouette", promptHint: "the clothing fitted and form-conscious, hugging the contours of the body", term: "form-fitting clothing" },
3906
- { id: "state-cropped", label: "Cropped", dimension: "wardrobe-state", description: "Top cropped at midriff", promptHint: "the top cropped above the midriff with the stomach visible", term: "cropped top" },
3907
- { id: "state-sheer", label: "Sheer", dimension: "wardrobe-state", description: "Translucent fabric", promptHint: "the fabric translucent and sheer, hinting at the silhouette underneath", term: "sheer clothing" },
3908
- { id: "state-wet", label: "Wet", dimension: "wardrobe-state", description: "Soaked, water-clinging", promptHint: "the clothing soaked and wet, the fabric clinging to the body and dripping water", term: "wet clothing" },
4022
+ { id: "state-fitted", label: "Fitted", dimension: "wardrobe-state", description: "Form-fitting silhouette", promptHint: "tailored, close-fitting clothing", term: "close-fitting clothing", adultOnly: true },
4023
+ { id: "state-cropped", label: "Cropped", dimension: "wardrobe-state", description: "Top cropped at midriff", promptHint: "the top cropped above the midriff with the stomach visible", term: "cropped top", adultOnly: true },
4024
+ { id: "state-sheer", label: "Sheer", dimension: "wardrobe-state", description: "Translucent fabric", promptHint: "the fabric translucent and sheer, hinting at the silhouette underneath", term: "sheer clothing", adultOnly: true },
4025
+ { id: "state-wet", label: "Wet", dimension: "wardrobe-state", description: "Soaked, water-clinging", promptHint: "the clothing soaked through and dripping", term: "soaked clothing", adultOnly: true },
3909
4026
  { id: "state-ripped", label: "Ripped", dimension: "wardrobe-state", description: "Torn and frayed", promptHint: "the fabric torn and ripped at the seams, with frayed edges", term: "ripped clothing" },
3910
4027
  { id: "state-distressed", label: "Distressed", dimension: "wardrobe-state", description: "Worn, faded, weathered", promptHint: "the clothing distressed and weathered, with faded color and worn-down edges", term: "distressed clothing" },
3911
4028
  { id: "state-vintage", label: "Vintage", dimension: "wardrobe-state", description: "Worn, retro character", promptHint: "the clothing carrying vintage character \u2014 softened color, broken-in fit, retro silhouette", term: "vintage clothing" },
3912
4029
  { id: "state-tucked-in", label: "Tucked In", dimension: "wardrobe-state", description: "Top tucked into bottom", promptHint: "the top neatly tucked into the bottom", term: "top tucked in" },
3913
4030
  { id: "state-half-tucked", label: "Half-Tucked", dimension: "wardrobe-state", description: "Front-tucked, back loose", promptHint: "the top half-tucked \u2014 front neatly tucked in, back hanging loose", term: "top half-tucked" },
3914
- { id: "state-off-shoulder", label: "Off-Shoulder", dimension: "wardrobe-state", description: "Slipped off one shoulder", promptHint: "the top slipped off one shoulder, baring the skin and collarbone", term: "off-the-shoulder top" },
3915
- { id: "state-halter-neck", label: "Halter Neck", dimension: "wardrobe-state", description: "Straps tied behind the neck", promptHint: "the top cut as a halter \u2014 straps tied behind the neck, shoulders and upper back bare", term: "halter neckline" },
3916
- { id: "state-plunging-neck", label: "Plunging Neckline", dimension: "wardrobe-state", description: "Deep plunging V neckline", promptHint: "the neckline cut in a deep plunging V" },
4031
+ { id: "state-off-shoulder", label: "Off-Shoulder", dimension: "wardrobe-state", description: "Slipped off one shoulder", promptHint: "the top slipped off one shoulder, baring the skin and collarbone", term: "off-the-shoulder top", adultOnly: true },
4032
+ { id: "state-halter-neck", label: "Halter Neck", dimension: "wardrobe-state", description: "Straps tied behind the neck", promptHint: "the top cut as a halter \u2014 straps tied behind the neck, shoulders and upper back bare", term: "halter neckline", adultOnly: true },
4033
+ { id: "state-plunging-neck", label: "Plunging Neckline", dimension: "wardrobe-state", description: "Deep plunging V neckline", promptHint: "the neckline cut in a deep plunging V", adultOnly: true },
3917
4034
  { id: "state-unbuttoned", label: "Unbuttoned", dimension: "wardrobe-state", description: "Open / unbuttoned", promptHint: "the outerwear hanging open and unbuttoned, falling loose at the sides", term: "worn open and unbuttoned" },
3918
4035
  { id: "state-rolled-sleeves", label: "Rolled Sleeves", dimension: "wardrobe-state", description: "Sleeves rolled up forearm", promptHint: "the sleeves rolled up to the forearm", term: "rolled-up sleeves" },
3919
4036
  { id: "state-layered", label: "Layered", dimension: "wardrobe-state", description: "Multiple stacked layers", promptHint: "the outfit composed of multiple stacked layers, each piece visible at the edges", term: "layered outfit" }
@@ -3992,7 +4109,7 @@ function getStylingDimensionLimit(dimension) {
3992
4109
  var stylingById = new Map(STYLINGS.map((s) => [s.id, s]));
3993
4110
  function getStyling(id) {
3994
4111
  if (!id) return void 0;
3995
- return stylingById.get(id);
4112
+ return overlayEntry("styling", id, stylingById.get(id));
3996
4113
  }
3997
4114
  function getStylingLabel(id, fallback) {
3998
4115
  const s = getStyling(id);
@@ -4013,17 +4130,26 @@ function collectStylingFragments(data, fragmentFor) {
4013
4130
  if (pre) out.push(pre);
4014
4131
  const lipStateRaw = data.lipState;
4015
4132
  const boldRedAlready = Array.isArray(lipStateRaw) ? lipStateRaw.includes("lip-state-bold-red") : lipStateRaw === "lip-state-bold-red";
4133
+ const floored = isMinorAge(data);
4134
+ const emit = floored ? (id) => getStyling(id)?.adultOnly === true ? "" : fragmentFor(id) : fragmentFor;
4135
+ const featureRaw = data.distinctiveFeature;
4136
+ const midriffPicked = Array.isArray(featureRaw) ? featureRaw.includes("feature-midriff-visible") : featureRaw === "feature-midriff-visible";
4137
+ const midriffAlready = midriffPicked && !floored;
4138
+ const cropTopPicked = data.top === "top-crop-top";
4139
+ const croppedSuppressed = (id) => id === "top-crop-top" && midriffAlready || id === "state-cropped" && (midriffAlready || cropTopPicked);
4016
4140
  for (const dimension of STYLING_DIMENSION_ORDER) {
4017
4141
  const field = STYLING_FIELD_BY_DIMENSION[dimension];
4018
4142
  const raw = data[field];
4019
4143
  if (dimension === "makeup" && boldRedAlready && raw === "makeup-bold-lips") continue;
4020
4144
  if (typeof raw === "string" && raw.length > 0) {
4021
- const fragment = fragmentFor(raw);
4145
+ if (croppedSuppressed(raw)) continue;
4146
+ const fragment = emit(raw);
4022
4147
  if (fragment) out.push(fragment);
4023
4148
  } else if (Array.isArray(raw)) {
4024
4149
  for (const item of raw) {
4025
4150
  if (typeof item !== "string" || item.length === 0) continue;
4026
- const fragment = fragmentFor(item);
4151
+ if (croppedSuppressed(item)) continue;
4152
+ const fragment = emit(item);
4027
4153
  if (fragment) out.push(fragment);
4028
4154
  }
4029
4155
  }
@@ -4080,7 +4206,7 @@ var TEMPORAL_CATEGORY_LABELS = {
4080
4206
  var temporalById = new Map(TEMPORALS.map((t) => [t.id, t]));
4081
4207
  function getTemporal(id) {
4082
4208
  if (!id) return void 0;
4083
- return temporalById.get(id);
4209
+ return overlayEntry("temporal", id, temporalById.get(id));
4084
4210
  }
4085
4211
  function getTemporalLabel(id, fallback) {
4086
4212
  const t = getTemporal(id);
@@ -4673,7 +4799,7 @@ var motionById = new Map(
4673
4799
  );
4674
4800
  function getCameraMotion(id) {
4675
4801
  if (!id) return void 0;
4676
- return motionById.get(id);
4802
+ return overlayEntry("camera-motions", id, motionById.get(id));
4677
4803
  }
4678
4804
  function getCameraMotionLabel(id, fallback) {
4679
4805
  const m = getCameraMotion(id);
@@ -5360,7 +5486,7 @@ var transitionById = new Map(
5360
5486
  );
5361
5487
  function getTransition(id) {
5362
5488
  if (!id) return void 0;
5363
- return transitionById.get(id);
5489
+ return overlayEntry("transitions", id, transitionById.get(id));
5364
5490
  }
5365
5491
  function getTransitionLabel(id, fallback) {
5366
5492
  const t = getTransition(id);
@@ -5887,7 +6013,7 @@ var characterFxById = new Map(
5887
6013
  );
5888
6014
  function getCharacterFx(id) {
5889
6015
  if (!id) return void 0;
5890
- return characterFxById.get(id);
6016
+ return overlayEntry("character-fx", id, characterFxById.get(id));
5891
6017
  }
5892
6018
  function getCharacterFxLabel(id, fallback) {
5893
6019
  const c = getCharacterFx(id);
@@ -6027,7 +6153,7 @@ var MATERIALS = [
6027
6153
  var materialById = new Map(MATERIALS.map((m) => [m.id, m]));
6028
6154
  function getMaterial(id) {
6029
6155
  if (!id) return void 0;
6030
- return materialById.get(id);
6156
+ return overlayEntry("materials", id, materialById.get(id));
6031
6157
  }
6032
6158
  function getMaterialLabel(id, fallback) {
6033
6159
  const m = getMaterial(id);
@@ -6086,6 +6212,35 @@ var MATERIAL_CATEGORY_ORDER = [
6086
6212
  "natural",
6087
6213
  "exotic"
6088
6214
  ];
6215
+ function curatedAnimalPromptHint(id) {
6216
+ if (!id) return "";
6217
+ if (!composedHas("animals", id)) return "";
6218
+ return composedOption("animals", id)?.promptHint ?? shared.getAnimalPromptHint(id);
6219
+ }
6220
+ function curatedAnimalTerm(id) {
6221
+ if (!id) return "";
6222
+ if (!composedHas("animals", id)) return "";
6223
+ return composedOption("animals", id)?.term ?? shared.getAnimalTerm(id);
6224
+ }
6225
+ function objectEntityText(catalogId, id, stock, stockHint, stockTerm) {
6226
+ if (!id || !composedHas(catalogId, id)) return { hint: "", term: "" };
6227
+ const composed = composedOption(catalogId, id);
6228
+ if (composed) return { hint: composed.promptHint, term: composed.term };
6229
+ if (!stock) return { hint: "", term: "" };
6230
+ return { hint: stockHint(stock), term: stockTerm(stock) };
6231
+ }
6232
+ function objectEntityTerm(entry) {
6233
+ return entry.term ?? deriveTerm(entry.label);
6234
+ }
6235
+ function curatedVehicleText(id) {
6236
+ return objectEntityText("vehicles", id, shared.getVehicle(id), (e) => `featuring a ${e.label.toLowerCase()}, ${e.description}`, objectEntityTerm);
6237
+ }
6238
+ function curatedWeaponText(id) {
6239
+ return objectEntityText("weapons", id, shared.getWeapon(id), (e) => `with a ${e.label.toLowerCase()}, ${e.description}`, objectEntityTerm);
6240
+ }
6241
+ function curatedFurnitureText(id) {
6242
+ return objectEntityText("furniture", id, shared.getFurniture(id), (e) => `including a ${e.label.toLowerCase()}, ${e.description}`, objectEntityTerm);
6243
+ }
6089
6244
 
6090
6245
  // src/photo-genre.ts
6091
6246
  var PHOTO_GENRES = [
@@ -6103,7 +6258,7 @@ var PHOTO_GENRES = [
6103
6258
  { id: "brand-jil-sander", label: "Jil Sander Minimalism", category: "editorial", description: "Jil Sander \u2014 minimalist architectural muted", promptHint: "shot in the language of a Jil Sander editorial, minimalist composition, muted ecru and stone palette, architectural wardrobe with clean unbroken lines and the brand's quiet austere mood" },
6104
6259
  { id: "brand-vivienne-tam", label: "Vivienne Tam Style", category: "editorial", description: "Vivienne Tam \u2014 orientalist ornate fashion", promptHint: "shot in the language of a Vivienne Tam editorial, orientalist motifs and dragon embroidery, ornate East-Asian fashion detailing and a saturated jewel-toned palette" },
6105
6260
  { id: "brand-jacquemus", label: "Jacquemus Style", category: "editorial", description: "Jacquemus \u2014 sun-soaked surrealist playful", promptHint: "shot in the language of a Jacquemus campaign, sun-soaked Mediterranean light, surrealist scale play with oversized hats and miniature bags, terracotta and ochre palette and a playful French Riviera mood" },
6106
- { id: "brand-helmut-newton", label: "Helmut Newton Style", category: "editorial", description: "Helmut Newton \u2014 high-contrast B&W provocation", promptHint: "shot in the signature language of Helmut Newton, high-contrast black-and-white, hard direct flash carving every line, statuesque pose, glamorous wardrobe and the cool confrontational eroticism of his 1970s editorial work" },
6261
+ { id: "brand-helmut-newton", label: "Helmut Newton Style", category: "editorial", description: "Helmut Newton \u2014 high-contrast B&W provocation", promptHint: "shot in the signature language of Helmut Newton, high-contrast black-and-white, hard direct flash carving every line, statuesque pose, glamorous wardrobe and the cool confrontational eroticism of his 1970s editorial work", adultOnly: true },
6107
6262
  { id: "brand-harpers-bazaar", label: "Harper's Bazaar Style", category: "editorial", description: "Harper's Bazaar \u2014 high-fashion glossy", promptHint: "shot in the signature language of a Harper's Bazaar editorial, high-fashion glossy production, bold graphic backdrops, dramatic single-source key light and the brand's distinctively confident composition" },
6108
6263
  // -------------------- Documentary / Candid --------------------
6109
6264
  { id: "paparazzi", label: "Paparazzi", category: "documentary", description: "Flash-blown tabloid candid", term: "paparazzi flash candid", promptHint: "shot as a paparazzi tabloid candid, harsh on-camera flash blowing out the highlights, slight motion blur, low candid angle, blurred crowd in the background and grainy cropped tabloid framing" },
@@ -6120,7 +6275,7 @@ var PHOTO_GENRES = [
6120
6275
  { id: "mugshot", label: "Mugshot", category: "studio-formal", description: "Police booking-style portrait", promptHint: "shot as a police booking mugshot, harsh fluorescent overhead lighting, height chart on the wall behind, dead-square frontal framing and a flat institutional color cast" },
6121
6276
  { id: "wedding-portrait", label: "Wedding Portrait", category: "studio-formal", description: "Romantic bridal-style portrait", promptHint: "shot as a wedding portrait, soft golden-hour backlight, romantic shallow depth of field, dreamy pastel color grade and an intimate posed embrace or solo bridal stance" },
6122
6277
  { id: "family-portrait", label: "Family Portrait", category: "studio-formal", description: "Posed family group shot", promptHint: "shot as a posed family portrait, warm even studio lighting, neutral backdrop, coordinated wardrobe and the group arranged in a balanced pyramid composition" },
6123
- { id: "glamour-portrait", label: "Glamour Portrait", category: "studio-formal", description: "Soft-focus glamour portrait", promptHint: "shot as a glamour portrait with soft diffused beauty lighting, a high-key warm tone, gentle skin retouch, voluminous hair and a sultry over-the-shoulder pose" },
6278
+ { id: "glamour-portrait", label: "Glamour Portrait", category: "studio-formal", description: "Soft-focus glamour portrait", promptHint: "shot as a glamour portrait with soft diffused beauty lighting, a high-key warm tone, gentle skin retouch, voluminous hair and a sultry over-the-shoulder pose", adultOnly: true },
6124
6279
  { id: "film-noir", label: "Film Noir", category: "studio-formal", description: "Hard-shadow noir portrait", promptHint: "shot in a film-noir portrait idiom, hard single-source key light casting deep shadows, venetian-blind shadow patterns, monochrome high-contrast palette and a brooding closed-off pose" },
6125
6280
  // -------------------- Selfie sub-types --------------------
6126
6281
  { id: "mirror-selfie", label: "Mirror Selfie", category: "selfie", description: "Phone-in-mirror full-body selfie", promptHint: "shot as a smartphone mirror selfie, the phone visible in one raised hand reflected in the mirror, full or three-quarter body framing, a domestic bedroom or bathroom background and natural ambient light" },
@@ -6149,7 +6304,7 @@ var photoGenreById = new Map(
6149
6304
  );
6150
6305
  function getPhotoGenre(id) {
6151
6306
  if (!id) return void 0;
6152
- return photoGenreById.get(id);
6307
+ return overlayEntry("photo-genre", id, photoGenreById.get(id));
6153
6308
  }
6154
6309
  function getPhotoGenreLabel(id, fallback) {
6155
6310
  const g = getPhotoGenre(id);
@@ -6235,7 +6390,7 @@ var BACKDROPS = [
6235
6390
  var backdropById = new Map(BACKDROPS.map((b) => [b.id, b]));
6236
6391
  function getBackdrop(id) {
6237
6392
  if (!id) return void 0;
6238
- return backdropById.get(id);
6393
+ return overlayEntry("backdrop", id, backdropById.get(id));
6239
6394
  }
6240
6395
  function getBackdropLabel(id, fallback) {
6241
6396
  const b = getBackdrop(id);
@@ -6342,7 +6497,7 @@ var HELD_PROPS = [
6342
6497
  var heldPropById = new Map(HELD_PROPS.map((p) => [p.id, p]));
6343
6498
  function getHeldProp(id) {
6344
6499
  if (!id) return void 0;
6345
- return heldPropById.get(id);
6500
+ return overlayEntry("held-prop", id, heldPropById.get(id));
6346
6501
  }
6347
6502
  function getHeldPropLabel(id, fallback) {
6348
6503
  const p = getHeldProp(id);
@@ -6924,7 +7079,7 @@ var photographerById = new Map(
6924
7079
  );
6925
7080
  function getPhotographer(id) {
6926
7081
  if (!id) return void 0;
6927
- return photographerById.get(id);
7082
+ return overlayEntry("photographer", id, photographerById.get(id));
6928
7083
  }
6929
7084
  function getPhotographerLabel(id, fallback) {
6930
7085
  const p = getPhotographer(id);
@@ -7304,7 +7459,7 @@ var aestheticById = new Map(
7304
7459
  );
7305
7460
  function getAesthetic(id) {
7306
7461
  if (!id) return void 0;
7307
- return aestheticById.get(id);
7462
+ return overlayEntry("aesthetic", id, aestheticById.get(id));
7308
7463
  }
7309
7464
  function getAestheticLabel(id, fallback) {
7310
7465
  const a = getAesthetic(id);
@@ -7571,7 +7726,7 @@ var ERAS = [
7571
7726
  var eraById = new Map(ERAS.map((e) => [e.id, e]));
7572
7727
  function getEra(id) {
7573
7728
  if (!id) return void 0;
7574
- return eraById.get(id);
7729
+ return overlayEntry("era", id, eraById.get(id));
7575
7730
  }
7576
7731
  function getEraLabel(id, fallback) {
7577
7732
  const e = getEra(id);
@@ -7639,7 +7794,7 @@ var exposureById = new Map(
7639
7794
  );
7640
7795
  function getExposure(id) {
7641
7796
  if (!id) return void 0;
7642
- return exposureById.get(id);
7797
+ return overlayEntry("exposure-settings", id, exposureById.get(id));
7643
7798
  }
7644
7799
  function getExposureLabel(id, fallback) {
7645
7800
  const e = getExposure(id);
@@ -7719,7 +7874,7 @@ var renderQualityById = new Map(
7719
7874
  );
7720
7875
  function getRenderQuality(id) {
7721
7876
  if (!id) return void 0;
7722
- return renderQualityById.get(id);
7877
+ return overlayEntry("render-quality", id, renderQualityById.get(id));
7723
7878
  }
7724
7879
  function getRenderQualityLabel(id, fallback) {
7725
7880
  const r = getRenderQuality(id);
@@ -7767,7 +7922,7 @@ var compositionEffectById = new Map(
7767
7922
  );
7768
7923
  function getCompositionEffect(id) {
7769
7924
  if (!id) return void 0;
7770
- return compositionEffectById.get(id);
7925
+ return overlayEntry("composition-effects", id, compositionEffectById.get(id));
7771
7926
  }
7772
7927
  function getCompositionEffectLabel(id, fallback) {
7773
7928
  const c = getCompositionEffect(id);
@@ -7809,7 +7964,7 @@ var postProcessById = new Map(
7809
7964
  );
7810
7965
  function getPostProcessEffect(id) {
7811
7966
  if (!id) return void 0;
7812
- return postProcessById.get(id);
7967
+ return overlayEntry("post-process-effects", id, postProcessById.get(id));
7813
7968
  }
7814
7969
  function getPostProcessEffectLabel(id, fallback) {
7815
7970
  const p = getPostProcessEffect(id);
@@ -8384,20 +8539,20 @@ var GENRE_BY_ID = new Map(MUSIC_GENRES.map((g) => [g.id, g]));
8384
8539
  var ERA_BY_ID = new Map(MUSIC_ERAS.map((e) => [e.id, e]));
8385
8540
  function getMusicGenre(id) {
8386
8541
  if (!id) return void 0;
8387
- return GENRE_BY_ID.get(id);
8542
+ return overlayEntry("music-genre", id, GENRE_BY_ID.get(id));
8388
8543
  }
8389
8544
  function getMusicGenreLabel(id) {
8390
8545
  if (!id) return "";
8391
- return GENRE_BY_ID.get(id)?.label ?? id;
8546
+ return getMusicGenre(id)?.label ?? id;
8392
8547
  }
8393
8548
  function getMusicSubgenre(genreId, subgenreId) {
8394
8549
  if (!genreId || !subgenreId) return void 0;
8395
8550
  const genre = GENRE_BY_ID.get(genreId);
8396
- return genre?.subgenres.find((s) => s.id === subgenreId);
8551
+ return overlayEntry("music-genre", subgenreId, genre?.subgenres.find((s) => s.id === subgenreId));
8397
8552
  }
8398
8553
  function getMusicEra(id) {
8399
8554
  if (!id) return void 0;
8400
- return ERA_BY_ID.get(id);
8555
+ return overlayEntry("music-genre", id, ERA_BY_ID.get(id));
8401
8556
  }
8402
8557
  function getMusicGenreTerm(id) {
8403
8558
  return resolveTerm(getMusicGenre(id ?? void 0));
@@ -8534,13 +8689,13 @@ var ENERGY_BY_ID = new Map(MUSIC_ENERGIES.map((x) => [x.id, x]));
8534
8689
  var EMOTION_BY_ID = new Map(MUSIC_EMOTIONS.map((x) => [x.id, x]));
8535
8690
  var VIBE_BY_ID = new Map(MUSIC_VIBES.map((x) => [x.id, x]));
8536
8691
  function getMusicEnergy(id) {
8537
- return id ? ENERGY_BY_ID.get(id) : void 0;
8692
+ return id ? overlayEntry("music-mood", id, ENERGY_BY_ID.get(id)) : void 0;
8538
8693
  }
8539
8694
  function getMusicEmotion(id) {
8540
- return id ? EMOTION_BY_ID.get(id) : void 0;
8695
+ return id ? overlayEntry("music-mood", id, EMOTION_BY_ID.get(id)) : void 0;
8541
8696
  }
8542
8697
  function getMusicVibe(id) {
8543
- return id ? VIBE_BY_ID.get(id) : void 0;
8698
+ return id ? overlayEntry("music-mood", id, VIBE_BY_ID.get(id)) : void 0;
8544
8699
  }
8545
8700
  function getMusicEnergyTerm(id) {
8546
8701
  return resolveTerm(id ? getMusicEnergy(id) : void 0);
@@ -8797,16 +8952,16 @@ var PRODUCTION_BY_ID = new Map(PRODUCTION_STYLES.map((x) => [x.id, x]));
8797
8952
  var VOCAL_BY_ID = new Map(VOCAL_PRESENCE.map((x) => [x.id, x]));
8798
8953
  var SINGING_STYLE_BY_ID = new Map(SINGING_STYLES.map((x) => [x.id, x]));
8799
8954
  function getInstrument(id) {
8800
- return id ? INSTRUMENT_BY_ID.get(id) : void 0;
8955
+ return id ? overlayEntry("instrumentation", id, INSTRUMENT_BY_ID.get(id)) : void 0;
8801
8956
  }
8802
8957
  function getProductionStyle(id) {
8803
- return id ? PRODUCTION_BY_ID.get(id) : void 0;
8958
+ return id ? overlayEntry("instrumentation", id, PRODUCTION_BY_ID.get(id)) : void 0;
8804
8959
  }
8805
8960
  function getVocalPresence(id) {
8806
- return id ? VOCAL_BY_ID.get(id) : void 0;
8961
+ return id ? overlayEntry("instrumentation", id, VOCAL_BY_ID.get(id)) : void 0;
8807
8962
  }
8808
8963
  function getSingingStyle(id) {
8809
- return id ? SINGING_STYLE_BY_ID.get(id) : void 0;
8964
+ return id ? overlayEntry("instrumentation", id, SINGING_STYLE_BY_ID.get(id)) : void 0;
8810
8965
  }
8811
8966
  function getInstrumentTerm(id) {
8812
8967
  return resolveTerm(id ? getInstrument(id) : void 0);
@@ -9012,19 +9167,19 @@ var LANGUAGE_BY_ID = new Map(VOICE_LANGUAGES.map((x) => [x.id, x]));
9012
9167
  var ACCENT_BY_ID = new Map(VOICE_ACCENTS.map((x) => [x.id, x]));
9013
9168
  var TIMBRE_BY_ID = new Map(VOICE_TIMBRES.map((x) => [x.id, x]));
9014
9169
  function getVoiceAge(id) {
9015
- return id ? AGE_BY_ID.get(id) : void 0;
9170
+ return id ? overlayEntry("voice-character", id, AGE_BY_ID.get(id)) : void 0;
9016
9171
  }
9017
9172
  function getVoiceGender(id) {
9018
- return id ? GENDER_BY_ID.get(id) : void 0;
9173
+ return id ? overlayEntry("voice-character", id, GENDER_BY_ID.get(id)) : void 0;
9019
9174
  }
9020
9175
  function getVoiceLanguage(id) {
9021
- return id ? LANGUAGE_BY_ID.get(id) : void 0;
9176
+ return id ? overlayEntry("voice-character", id, LANGUAGE_BY_ID.get(id)) : void 0;
9022
9177
  }
9023
9178
  function getVoiceAccent(id) {
9024
- return id ? ACCENT_BY_ID.get(id) : void 0;
9179
+ return id ? overlayEntry("voice-character", id, ACCENT_BY_ID.get(id)) : void 0;
9025
9180
  }
9026
9181
  function getVoiceTimbre(id) {
9027
- return id ? TIMBRE_BY_ID.get(id) : void 0;
9182
+ return id ? overlayEntry("voice-character", id, TIMBRE_BY_ID.get(id)) : void 0;
9028
9183
  }
9029
9184
  function getVoiceAgeTerm(id) {
9030
9185
  return resolveTerm(getVoiceAge(id ?? void 0));
@@ -9171,13 +9326,13 @@ var PACE_BY_ID = new Map(VOICE_PACES.map((x) => [x.id, x]));
9171
9326
  var EMOTION_BY_ID2 = new Map(VOICE_EMOTIONS.map((x) => [x.id, x]));
9172
9327
  var ARCHETYPE_BY_ID = new Map(VOICE_ARCHETYPES.map((x) => [x.id, x]));
9173
9328
  function getVoicePace(id) {
9174
- return id ? PACE_BY_ID.get(id) : void 0;
9329
+ return id ? overlayEntry("voice-delivery", id, PACE_BY_ID.get(id)) : void 0;
9175
9330
  }
9176
9331
  function getVoiceEmotion(id) {
9177
- return id ? EMOTION_BY_ID2.get(id) : void 0;
9332
+ return id ? overlayEntry("voice-delivery", id, EMOTION_BY_ID2.get(id)) : void 0;
9178
9333
  }
9179
9334
  function getVoiceArchetype(id) {
9180
- return id ? ARCHETYPE_BY_ID.get(id) : void 0;
9335
+ return id ? overlayEntry("voice-delivery", id, ARCHETYPE_BY_ID.get(id)) : void 0;
9181
9336
  }
9182
9337
  function getVoicePaceTerm(id) {
9183
9338
  return resolveTerm(getVoicePace(id ?? void 0));
@@ -9243,6 +9398,7 @@ function toOptions(arr, categoryField) {
9243
9398
  };
9244
9399
  if (e.description) opt.description = e.description;
9245
9400
  if (categoryField) opt.category = e[categoryField];
9401
+ if (e.adultOnly) opt.adultOnly = true;
9246
9402
  return opt;
9247
9403
  });
9248
9404
  }
@@ -9768,6 +9924,7 @@ function getPickerCatalog(nodeTypeOrCatalogId) {
9768
9924
  const all = getRegisteredPickerCatalogs();
9769
9925
  return all.find((c) => c.nodeType === nodeTypeOrCatalogId) ?? all.find((c) => c.catalogId === nodeTypeOrCatalogId);
9770
9926
  }
9927
+ setComposedCatalogResolver(getPickerCatalog);
9771
9928
  function listPickerCatalogs() {
9772
9929
  return getRegisteredPickerCatalogs();
9773
9930
  }
@@ -9858,9 +10015,6 @@ function readHintMode(data) {
9858
10015
  function byMode(mode, full, compact) {
9859
10016
  return mode === "compact" ? compact : full;
9860
10017
  }
9861
- function objectEntityTerm(entry) {
9862
- return entry.term ?? deriveTerm(entry.label);
9863
- }
9864
10018
  function getParameterPromptHint(node, ctx) {
9865
10019
  return resolveParameterHint(node, ctx);
9866
10020
  }
@@ -9985,36 +10139,23 @@ function resolveBaseHint(type, data, mode = "full") {
9985
10139
  return withCustomText(data, byMode(mode, getLoopSubjectPromptHint, getLoopSubjectTerm)(asStr(data.loopSubject)));
9986
10140
  case "material":
9987
10141
  return withCustomText(data, buildMaterialHints(data.material, mode));
9988
- // Animal is the one Object-entity catalog whose phrasing has a single
9989
- // owner: `@nodaro/shared`'s `getAnimalPromptHint` / `getAnimalTerm`, which
9990
- // the picker-catalog funnel calls too. Both getters already return "" on a
9991
- // miss, so the entry lookup and the `animal ? … : ""` guard are the
9992
- // getters' job now, not this switch's.
10142
+ // The four object-entity catalogs are owned by @nodaro/shared and overlaid
10143
+ // in shared-catalog-overlay.ts — the ONE module every prompt-text read of
10144
+ // them goes through (the subject fold uses it too). Each returns "" on a
10145
+ // miss, so the entry lookup is the module's job, not this switch's.
9993
10146
  case "animal":
9994
- return withCustomText(
9995
- data,
9996
- byMode(mode, shared.getAnimalPromptHint, shared.getAnimalTerm)(asStr(data.animal))
9997
- );
10147
+ return withCustomText(data, byMode(mode, curatedAnimalPromptHint, curatedAnimalTerm)(asStr(data.animal)));
9998
10148
  case "vehicle": {
9999
- const vehicle = shared.getVehicle(asStr(data.vehicle));
10000
- return withCustomText(
10001
- data,
10002
- vehicle ? byMode(mode, `featuring a ${vehicle.label.toLowerCase()}, ${vehicle.description}`, objectEntityTerm(vehicle)) : ""
10003
- );
10149
+ const t = curatedVehicleText(asStr(data.vehicle));
10150
+ return withCustomText(data, byMode(mode, t.hint, t.term));
10004
10151
  }
10005
10152
  case "weapon": {
10006
- const weapon = shared.getWeapon(asStr(data.weapon));
10007
- return withCustomText(
10008
- data,
10009
- weapon ? byMode(mode, `with a ${weapon.label.toLowerCase()}, ${weapon.description}`, objectEntityTerm(weapon)) : ""
10010
- );
10153
+ const t = curatedWeaponText(asStr(data.weapon));
10154
+ return withCustomText(data, byMode(mode, t.hint, t.term));
10011
10155
  }
10012
10156
  case "furniture": {
10013
- const furniture = shared.getFurniture(asStr(data.furniture));
10014
- return withCustomText(
10015
- data,
10016
- furniture ? byMode(mode, `including a ${furniture.label.toLowerCase()}, ${furniture.description}`, objectEntityTerm(furniture)) : ""
10017
- );
10157
+ const t = curatedFurnitureText(asStr(data.furniture));
10158
+ return withCustomText(data, byMode(mode, t.hint, t.term));
10018
10159
  }
10019
10160
  case "photo-genre":
10020
10161
  return withCustomText(data, byMode(mode, getPhotoGenrePromptHint, getPhotoGenreTerm)(asStr(data.photoGenre)));
@@ -11485,7 +11626,7 @@ ${directives}`;
11485
11626
  }
11486
11627
  }
11487
11628
  const styleText2 = style?.trim();
11488
- const styleLine2 = styleText2 ? `Style: ${getStylePromptHint(styleText2) || styleText2}` : "";
11629
+ const styleLine2 = styleText2 && !isDeniedStyleId(styleText2) ? `Style: ${getStylePromptHint(styleText2) || styleText2}` : "";
11489
11630
  const negPrompt2 = negativePrompt?.trim();
11490
11631
  let nativeNegativePrompt2;
11491
11632
  let avoidLine2 = "";
@@ -11567,7 +11708,7 @@ ${directives}`;
11567
11708
  prompt = section.length > 0 ? `${wrapped}${STYLE_SECTION_GAP}${section}` : wrapped;
11568
11709
  }
11569
11710
  const styleText = style?.trim();
11570
- const styleLine = styleText ? `Style: ${getStylePromptHint(styleText) || styleText}` : "";
11711
+ const styleLine = styleText && !isDeniedStyleId(styleText) ? `Style: ${getStylePromptHint(styleText) || styleText}` : "";
11571
11712
  const negPrompt = negativePrompt?.trim();
11572
11713
  let nativeNegativePrompt;
11573
11714
  let avoidLine = "";
@@ -12039,6 +12180,8 @@ function renderLens(l) {
12039
12180
  if (l.aperture) bits.push(`f/${l.aperture}`);
12040
12181
  return bits.length > 0 ? `Lens: ${bits.join(", ")}.` : "";
12041
12182
  }
12183
+
12184
+ // src/subject-registry.ts
12042
12185
  var viaFragmentBuilder = (build) => (subject, mode) => {
12043
12186
  const clause = build(subject, mode).filter((s) => s.length > 0).join(", ");
12044
12187
  return clause.length > 0 ? [clause] : [];
@@ -12061,7 +12204,7 @@ var SUBJECT_FIELDS = [
12061
12204
  { key: "styling", kind: "group", surface: "both", render: viaFragmentBuilder(buildStylingHints) },
12062
12205
  { key: "heldProp", kind: "ids", surface: "both", maxPicks: 2, render: viaListBuilder2(buildHeldPropHints) },
12063
12206
  { key: "material", kind: "ids", surface: "both", maxPicks: 2, render: viaStringBuilder2(buildMaterialHints) },
12064
- { key: "animal", kind: "ids", surface: "both", maxPicks: 1, render: perId2(shared.getAnimalPromptHint, shared.getAnimalTerm) }
12207
+ { key: "animal", kind: "ids", surface: "both", maxPicks: 1, render: perId2(curatedAnimalPromptHint, curatedAnimalTerm) }
12065
12208
  ];
12066
12209
  var SUBJECT_FOLD_KEYS = SUBJECT_FIELDS.map((f) => f.key);
12067
12210
  var IDS_ROWS = SUBJECT_FIELDS.filter((f) => f.kind === "ids");
@@ -13073,6 +13216,114 @@ function resolveVeoI2vInputs(args) {
13073
13216
  droppedEndFrame: Boolean(args.endFrameUrl)
13074
13217
  };
13075
13218
  }
13219
+
13220
+ // src/catalog-id-guard.ts
13221
+ var SIDE_FIELDS = [
13222
+ // pose.ts resolves all four sub-picks through getPosePromptHint
13223
+ ["pose", "handPosition", "pose"],
13224
+ ["pose", "bodyLean", "pose"],
13225
+ ["pose", "headTilt", "pose"],
13226
+ ["pose", "activity", "pose"]
13227
+ ];
13228
+ var DIRECTION_ALIASES = {
13229
+ framingId: "framing",
13230
+ framingAngleId: "framing",
13231
+ lightingId: "lighting",
13232
+ lensId: "lens",
13233
+ cameraFormatId: "camera-format"
13234
+ };
13235
+ var tableMemo = null;
13236
+ function fieldTable() {
13237
+ if (tableMemo) return tableMemo;
13238
+ const byNodeType = /* @__PURE__ */ new Map();
13239
+ const byField = /* @__PURE__ */ new Map();
13240
+ const put = (nodeType, field, catalogId) => {
13241
+ let m = byNodeType.get(nodeType);
13242
+ if (!m) byNodeType.set(nodeType, m = /* @__PURE__ */ new Map());
13243
+ m.set(field, catalogId);
13244
+ if (!byField.has(field)) byField.set(field, catalogId);
13245
+ };
13246
+ for (const c of PICKER_CATALOGS) {
13247
+ if (c.valueField) put(c.nodeType, c.valueField, c.catalogId);
13248
+ for (const d of c.dimensions ?? []) put(c.nodeType, d.field, c.catalogId);
13249
+ for (const f of c.fields ?? []) put(c.nodeType, f, c.catalogId);
13250
+ }
13251
+ for (const [nodeType, field, catalogId] of SIDE_FIELDS) put(nodeType, field, catalogId);
13252
+ for (const [alias, catalogId] of Object.entries(DIRECTION_ALIASES)) if (!byField.has(alias)) byField.set(alias, catalogId);
13253
+ tableMemo = { byNodeType, byField };
13254
+ return tableMemo;
13255
+ }
13256
+ var idsMemo = null;
13257
+ function composedIds(catalogId) {
13258
+ const v = catalogPacksVersion();
13259
+ if (!idsMemo || idsMemo.v !== v) idsMemo = { v, byCatalog: /* @__PURE__ */ new Map() };
13260
+ const hit = idsMemo.byCatalog.get(catalogId);
13261
+ if (hit) return hit;
13262
+ const cat = getPickerCatalog(catalogId);
13263
+ const ids = /* @__PURE__ */ new Set();
13264
+ for (const o of cat?.options ?? []) ids.add(o.id);
13265
+ for (const d of cat?.dimensions ?? []) for (const o of d.options) ids.add(o.id);
13266
+ idsMemo.byCatalog.set(catalogId, ids);
13267
+ return ids;
13268
+ }
13269
+ function values(v) {
13270
+ if (typeof v === "string") return v ? [v] : [];
13271
+ if (Array.isArray(v)) return v.filter((x) => typeof x === "string" && x.length > 0);
13272
+ return [];
13273
+ }
13274
+ function catalogGuardActive() {
13275
+ return getRegisteredCatalogPacks().length > 0;
13276
+ }
13277
+ function checkRecord(nodeType, nodeId, data, fields, out) {
13278
+ for (const [field, catalogId] of fields) {
13279
+ const raw = data[field];
13280
+ if (raw === void 0 || raw === null) continue;
13281
+ const offered = composedIds(catalogId);
13282
+ for (const id of values(raw)) {
13283
+ if (!offered.has(id)) out.push({ nodeId, nodeType, field, id, catalogId });
13284
+ }
13285
+ }
13286
+ }
13287
+ function findForeignCatalogIds(nodes) {
13288
+ if (!catalogGuardActive() || !nodes) return [];
13289
+ const table = fieldTable();
13290
+ const out = [];
13291
+ for (const node of nodes) {
13292
+ const type = typeof node.type === "string" ? node.type : "";
13293
+ const nodeId = typeof node.id === "string" ? node.id : void 0;
13294
+ const data = node.data && typeof node.data === "object" ? node.data : null;
13295
+ if (!type || !data) continue;
13296
+ const own = table.byNodeType.get(type);
13297
+ if (own) checkRecord(type, nodeId, data, own, out);
13298
+ for (const key of ["direction", "subject"]) {
13299
+ const rec = data[key];
13300
+ if (rec && typeof rec === "object" && !Array.isArray(rec)) {
13301
+ checkRecord(type, nodeId, rec, table.byField, out);
13302
+ }
13303
+ }
13304
+ }
13305
+ return out;
13306
+ }
13307
+ function findForeignCatalogIdsInBody(nodeType, body) {
13308
+ if (!catalogGuardActive() || !body) return [];
13309
+ const table = fieldTable();
13310
+ const out = [];
13311
+ for (const key of ["direction", "subject"]) {
13312
+ const rec = body[key];
13313
+ if (rec && typeof rec === "object" && !Array.isArray(rec)) {
13314
+ checkRecord(nodeType, void 0, rec, table.byField, out);
13315
+ }
13316
+ }
13317
+ return out;
13318
+ }
13319
+ function foreignCatalogIdMessage(found) {
13320
+ const parts = [...new Set(found.map((f) => `${f.field}="${f.id}"`))];
13321
+ return `This deployment does not offer the following picker value${parts.length === 1 ? "" : "s"}: ${parts.join(", ")}. Choose from the options shown in the picker.`;
13322
+ }
13323
+ function __resetCatalogIdGuardForTests() {
13324
+ tableMemo = null;
13325
+ idsMemo = null;
13326
+ }
13076
13327
  function packOptionIds(pack) {
13077
13328
  if (pack.options) return pack.options.map((o) => o.id);
13078
13329
  if (pack.dimensions) return pack.dimensions.flatMap((d) => d.options.map((o) => o.id));
@@ -13096,6 +13347,144 @@ function computePackSidecarCoverage(pack) {
13096
13347
  return { total: ids.length * shared.NON_EN_LOCALE_IDS.length, missing, exempted };
13097
13348
  }
13098
13349
 
13350
+ // src/age-floor.ts
13351
+ var ADULT_SWEPT_CATALOG_IDS = ["styling", "mood", "pose", "photo-genre"];
13352
+ var ADULT_SWEPT_CATALOG_ID_SET = new Set(ADULT_SWEPT_CATALOG_IDS);
13353
+ function optionsOf(catalog) {
13354
+ if (catalog.kind === "single") return catalog.options ?? [];
13355
+ return (catalog.dimensions ?? []).flatMap((d) => d.options);
13356
+ }
13357
+ function getAdultOnlyEntries() {
13358
+ const catalogEntries = getRegisteredPickerCatalogs().filter((c) => ADULT_SWEPT_CATALOG_ID_SET.has(c.catalogId)).flatMap(optionsOf);
13359
+ const all = [...getRegisteredPeople(), ...catalogEntries];
13360
+ return all.filter((e) => e.adultOnly === true);
13361
+ }
13362
+ function getAdultOnlyIds() {
13363
+ return new Set(getAdultOnlyEntries().map((e) => e.id));
13364
+ }
13365
+ var RETIRED_ADULT_ONLY_HINT_STRINGS = [
13366
+ // bust-very-full
13367
+ "very full bust",
13368
+ // silhouette-hourglass
13369
+ "hourglass silhouette",
13370
+ // waist-defined
13371
+ "defined waist",
13372
+ // lip-state-glossy
13373
+ "with high-shine glossy wet-look lips",
13374
+ // lip-state-parted
13375
+ "with lips slightly parted, taking a soft breath",
13376
+ // lip-state-bitten
13377
+ "playfully biting the lower lip",
13378
+ // eye-state-half-lidded
13379
+ "with heavy half-lidded sleepy eyes",
13380
+ // texture-glistening
13381
+ "with glistening skin, sweat or oil catching the light",
13382
+ // texture-shower-fresh-wet
13383
+ "with just-out-of-the-shower wet skin, water beading on the surface and rolling in slow droplets down the curves of the body",
13384
+ // feature-bare-shoulders
13385
+ "with bare shoulders exposed, the line of the collarbone and shoulder muscles uncovered",
13386
+ // feature-collarbone-visible
13387
+ "with a prominent collarbone clearly defined and catching the light",
13388
+ // feature-midriff-visible
13389
+ "wearing a cropped style with the midriff visible",
13390
+ // state-fitted — the 2026-07-30 incident clause
13391
+ "the clothing fitted and form-conscious, hugging the contours of the body",
13392
+ // state-wet
13393
+ "the clothing soaked and wet, the fabric clinging to the body and dripping water",
13394
+ // pose `biting-lip` (the lip-state-bitten twin)
13395
+ "biting the lower lip with a subtle playful expression",
13396
+ // the hard-coded midriff+navel fold in emitIndependentFragments — not any
13397
+ // entry's hint, so retiring it ADDS a needle for a clause that really is
13398
+ // emitted verbatim.
13399
+ "wearing a cropped style, midriff and navel visible"
13400
+ ];
13401
+ function getAdultOnlyHintStrings() {
13402
+ const out = new Set(RETIRED_ADULT_ONLY_HINT_STRINGS);
13403
+ for (const e of getAdultOnlyEntries()) {
13404
+ if (e.promptHint) out.add(e.promptHint.toLowerCase());
13405
+ }
13406
+ return [...out].filter((s) => s.length >= 8).sort((a, b) => b.length - a.length);
13407
+ }
13408
+ var cachedMinorHints = null;
13409
+ var cachedMinorHintSourceCount = -1;
13410
+ function getMinorAgeHintStrings() {
13411
+ const people = getRegisteredPeople();
13412
+ if (cachedMinorHints && cachedMinorHintSourceCount === people.length) return cachedMinorHints;
13413
+ const out = /* @__PURE__ */ new Set();
13414
+ for (const e of people) {
13415
+ const minorAge = e.dimension === "age" && !ADULT_AGE_IDS.has(e.id);
13416
+ const minorType = e.dimension === "type" && MINOR_IMPLYING_TYPE_IDS.has(e.id);
13417
+ if (!minorAge && !minorType) continue;
13418
+ if (e.promptHint) out.add(e.promptHint.toLowerCase());
13419
+ }
13420
+ cachedMinorHintSourceCount = people.length;
13421
+ cachedMinorHints = [...out].filter((s) => s.length >= 8).sort((a, b) => b.length - a.length);
13422
+ return cachedMinorHints;
13423
+ }
13424
+ function escapeReSource(s) {
13425
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13426
+ }
13427
+ function needleTokenSource(needle) {
13428
+ return needle.trim().split(/[\s-]+/).filter((t) => t.length > 0).map(escapeReSource).join("[\\s-]+");
13429
+ }
13430
+ function buildNeedleAlternationSource(needles) {
13431
+ const alts = needles.map(needleTokenSource).filter((s) => s.length > 0);
13432
+ if (alts.length === 0) return null;
13433
+ return `(?<![\\w-])(?:${alts.join("|")})(?![\\w-])`;
13434
+ }
13435
+ var cachedMinorNeedleRe = null;
13436
+ var cachedMinorNeedleCount = -1;
13437
+ function minorNeedleRegex() {
13438
+ const needles = getMinorAgeHintStrings();
13439
+ if (cachedMinorNeedleRe && cachedMinorNeedleCount === needles.length) return cachedMinorNeedleRe;
13440
+ cachedMinorNeedleCount = needles.length;
13441
+ const src = buildNeedleAlternationSource(needles);
13442
+ cachedMinorNeedleRe = src === null ? null : new RegExp(src, "i");
13443
+ return cachedMinorNeedleRe;
13444
+ }
13445
+ var NUMERIC_AGE_RE = /(?<![\w-])(\d{1,3})(?:\s*-\s*\d{1,3})?\s*-?\s*(?:years?\s*-?\s*old|yrs?\s*-?\s*old|y(?:\.\s*o\.?|\/o|o)|years?\s+of\s+age)(?![\w-])/gi;
13446
+ var PREFIXED_AGE_RE = /(?<![\w-])aged?\s+(?:of\s+)?(\d{1,3})(?![\w-])/gi;
13447
+ var AGE_NUMBER_RULES = [NUMERIC_AGE_RE, PREFIXED_AGE_RE];
13448
+ var IN_THEIR_TEENS_RE = /(?<![\w-])in[\s-]+their[\s-]+teens(?![\w-])/i;
13449
+ function containsMinorAgeHint(text) {
13450
+ if (typeof text !== "string" || text.trim().length === 0) return false;
13451
+ const re = minorNeedleRegex();
13452
+ if (re && re.test(text)) return true;
13453
+ for (const rule of AGE_NUMBER_RULES) {
13454
+ for (const m of text.matchAll(rule)) {
13455
+ const n = Number(m[1]);
13456
+ if (Number.isFinite(n) && n < 20) return true;
13457
+ }
13458
+ }
13459
+ return IN_THEIR_TEENS_RE.test(text);
13460
+ }
13461
+ var FLOORED_PICKER_KEYS = ["person", ...ADULT_SWEPT_CATALOG_IDS];
13462
+ function stripIds(obj, drop) {
13463
+ const out = {};
13464
+ for (const [k, v] of Object.entries(obj)) {
13465
+ if (typeof v === "string") {
13466
+ if (!drop.has(v)) out[k] = v;
13467
+ } else if (Array.isArray(v)) {
13468
+ const kept = v.filter((x) => !(typeof x === "string" && drop.has(x)));
13469
+ if (kept.length > 0) out[k] = kept;
13470
+ } else {
13471
+ out[k] = v;
13472
+ }
13473
+ }
13474
+ return out;
13475
+ }
13476
+ function applyMinorAgeFloorToPickerValues(values2) {
13477
+ const person = values2.person;
13478
+ if (!person || typeof person !== "object" || !isMinorAge(person)) return values2;
13479
+ const drop = getAdultOnlyIds();
13480
+ const out = { ...values2 };
13481
+ for (const key of FLOORED_PICKER_KEYS) {
13482
+ const v = out[key];
13483
+ if (v && typeof v === "object" && !Array.isArray(v)) out[key] = stripIds(v, drop);
13484
+ }
13485
+ return out;
13486
+ }
13487
+
13099
13488
  // ../../node_modules/zod/v4/classic/external.js
13100
13489
  var external_exports = {};
13101
13490
  __export(external_exports, {
@@ -13777,8 +14166,8 @@ function assert(_) {
13777
14166
  }
13778
14167
  function getEnumValues(entries) {
13779
14168
  const numericValues = Object.values(entries).filter((v) => typeof v === "number");
13780
- const values = Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);
13781
- return values;
14169
+ const values2 = Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);
14170
+ return values2;
13782
14171
  }
13783
14172
  function joinValues(array2, separator = "|") {
13784
14173
  return array2.map((val) => stringifyPrimitive(val)).join(separator);
@@ -16436,10 +16825,10 @@ var $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUnio
16436
16825
  const opts = def.options;
16437
16826
  const map2 = /* @__PURE__ */ new Map();
16438
16827
  for (const o of opts) {
16439
- const values = o._zod.propValues?.[def.discriminator];
16440
- if (!values || values.size === 0)
16828
+ const values2 = o._zod.propValues?.[def.discriminator];
16829
+ if (!values2 || values2.size === 0)
16441
16830
  throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(o)}"`);
16442
- for (const v of values) {
16831
+ for (const v of values2) {
16443
16832
  if (map2.has(v)) {
16444
16833
  throw new Error(`Duplicate discriminator value "${String(v)}"`);
16445
16834
  }
@@ -16697,11 +17086,11 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
16697
17086
  return payload;
16698
17087
  }
16699
17088
  const proms = [];
16700
- const values = def.keyType._zod.values;
16701
- if (values) {
17089
+ const values2 = def.keyType._zod.values;
17090
+ if (values2) {
16702
17091
  payload.value = {};
16703
17092
  const recordKeys = /* @__PURE__ */ new Set();
16704
- for (const key of values) {
17093
+ for (const key of values2) {
16705
17094
  if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
16706
17095
  recordKeys.add(typeof key === "number" ? key.toString() : key);
16707
17096
  const keyResult = def.keyType._zod.run({ value: key, issues: [] }, ctx);
@@ -16905,10 +17294,10 @@ function handleSetResult(result, final) {
16905
17294
  }
16906
17295
  var $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
16907
17296
  $ZodType.init(inst, def);
16908
- const values = getEnumValues(def.entries);
16909
- const valuesSet = new Set(values);
17297
+ const values2 = getEnumValues(def.entries);
17298
+ const valuesSet = new Set(values2);
16910
17299
  inst._zod.values = valuesSet;
16911
- inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex(o) : o.toString()).join("|")})$`);
17300
+ inst._zod.pattern = new RegExp(`^(${values2.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex(o) : o.toString()).join("|")})$`);
16912
17301
  inst._zod.parse = (payload, _ctx) => {
16913
17302
  const input = payload.value;
16914
17303
  if (valuesSet.has(input)) {
@@ -16916,7 +17305,7 @@ var $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
16916
17305
  }
16917
17306
  payload.issues.push({
16918
17307
  code: "invalid_value",
16919
- values,
17308
+ values: values2,
16920
17309
  input,
16921
17310
  inst
16922
17311
  });
@@ -16928,12 +17317,12 @@ var $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
16928
17317
  if (def.values.length === 0) {
16929
17318
  throw new Error("Cannot create literal schema with no valid values");
16930
17319
  }
16931
- const values = new Set(def.values);
16932
- inst._zod.values = values;
17320
+ const values2 = new Set(def.values);
17321
+ inst._zod.values = values2;
16933
17322
  inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
16934
17323
  inst._zod.parse = (payload, _ctx) => {
16935
17324
  const input = payload.value;
16936
- if (values.has(input)) {
17325
+ if (values2.has(input)) {
16937
17326
  return payload;
16938
17327
  }
16939
17328
  payload.issues.push({
@@ -24234,8 +24623,8 @@ function _set(Class2, valueType, params) {
24234
24623
  });
24235
24624
  }
24236
24625
  // @__NO_SIDE_EFFECTS__
24237
- function _enum(Class2, values, params) {
24238
- const entries = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;
24626
+ function _enum(Class2, values2, params) {
24627
+ const entries = Array.isArray(values2) ? Object.fromEntries(values2.map((v) => [v, v])) : values2;
24239
24628
  return new Class2({
24240
24629
  type: "enum",
24241
24630
  entries,
@@ -24979,12 +25368,12 @@ var dateProcessor = (_schema, ctx, _json, _params) => {
24979
25368
  };
24980
25369
  var enumProcessor = (schema, _ctx, json2, _params) => {
24981
25370
  const def = schema._zod.def;
24982
- const values = getEnumValues(def.entries);
24983
- if (values.every((v) => typeof v === "number"))
25371
+ const values2 = getEnumValues(def.entries);
25372
+ if (values2.every((v) => typeof v === "number"))
24984
25373
  json2.type = "number";
24985
- if (values.every((v) => typeof v === "string"))
25374
+ if (values2.every((v) => typeof v === "string"))
24986
25375
  json2.type = "string";
24987
- json2.enum = values;
25376
+ json2.enum = values2;
24988
25377
  };
24989
25378
  var literalProcessor = (schema, ctx, json2, _params) => {
24990
25379
  const def = schema._zod.def;
@@ -26667,9 +27056,9 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
26667
27056
  inst.enum = def.entries;
26668
27057
  inst.options = Object.values(def.entries);
26669
27058
  const keys = new Set(Object.keys(def.entries));
26670
- inst.extract = (values, params) => {
27059
+ inst.extract = (values2, params) => {
26671
27060
  const newEntries = {};
26672
- for (const value of values) {
27061
+ for (const value of values2) {
26673
27062
  if (keys.has(value)) {
26674
27063
  newEntries[value] = def.entries[value];
26675
27064
  } else
@@ -26682,9 +27071,9 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
26682
27071
  entries: newEntries
26683
27072
  });
26684
27073
  };
26685
- inst.exclude = (values, params) => {
27074
+ inst.exclude = (values2, params) => {
26686
27075
  const newEntries = { ...def.entries };
26687
- for (const value of values) {
27076
+ for (const value of values2) {
26688
27077
  if (keys.has(value)) {
26689
27078
  delete newEntries[value];
26690
27079
  } else
@@ -26698,8 +27087,8 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
26698
27087
  });
26699
27088
  };
26700
27089
  });
26701
- function _enum2(values, params) {
26702
- const entries = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;
27090
+ function _enum2(values2, params) {
27091
+ const entries = Array.isArray(values2) ? Object.fromEntries(values2.map((v) => [v, v])) : values2;
26703
27092
  return new ZodEnum({
26704
27093
  type: "enum",
26705
27094
  entries,
@@ -27613,6 +28002,13 @@ var personCleanup = (patch, mode) => {
27613
28002
  } else if ("age" in patch && patch.age !== "age-custom") {
27614
28003
  patch.customAge = void 0;
27615
28004
  }
28005
+ if (isMinorAge(patch)) {
28006
+ const drop = getAdultOnlyIds();
28007
+ for (const [k, v] of Object.entries(patch)) {
28008
+ if (typeof v === "string" && drop.has(v)) patch[k] = void 0;
28009
+ else if (Array.isArray(v)) patch[k] = v.filter((x) => !(typeof x === "string" && drop.has(x)));
28010
+ }
28011
+ }
27616
28012
  };
27617
28013
  var PICKER_ANALYZER_REGISTRY = {
27618
28014
  person: {
@@ -34246,6 +34642,9 @@ exports.ACTION_FX = ACTION_FX;
34246
34642
  exports.ACTION_FX_CATEGORY_LABELS = ACTION_FX_CATEGORY_LABELS;
34247
34643
  exports.ACTION_FX_CATEGORY_ORDER = ACTION_FX_CATEGORY_ORDER;
34248
34644
  exports.ACTION_FX_IDS = ACTION_FX_IDS;
34645
+ exports.ADULT_AGE_IDS = ADULT_AGE_IDS;
34646
+ exports.ADULT_ONLY_FLAG = ADULT_ONLY_FLAG;
34647
+ exports.ADULT_SWEPT_CATALOG_IDS = ADULT_SWEPT_CATALOG_IDS;
34249
34648
  exports.AESTHETICS = AESTHETICS;
34250
34649
  exports.AESTHETIC_CATEGORY_LABELS = AESTHETIC_CATEGORY_LABELS;
34251
34650
  exports.AESTHETIC_CATEGORY_ORDER = AESTHETIC_CATEGORY_ORDER;
@@ -34303,6 +34702,7 @@ exports.FACTORY_PRESETS = FACTORY_PRESETS;
34303
34702
  exports.FACTORY_SNIPPETS = FACTORY_SNIPPETS;
34304
34703
  exports.FILM_STILL_PREFIX = FILM_STILL_PREFIX;
34305
34704
  exports.FILM_STYLE_KEYS = FILM_STYLE_KEYS;
34705
+ exports.FLOORED_PICKER_KEYS = FLOORED_PICKER_KEYS;
34306
34706
  exports.FRAMINGS = FRAMINGS;
34307
34707
  exports.FRAMING_CATEGORY_LABELS = FRAMING_CATEGORY_LABELS;
34308
34708
  exports.FRAMING_CATEGORY_ORDER = FRAMING_CATEGORY_ORDER;
@@ -34339,6 +34739,7 @@ exports.MAX_SELECTED_BY_DIMENSION = MAX_SELECTED_BY_DIMENSION;
34339
34739
  exports.MAX_SELECTED_BY_FRAMING_CATEGORY = MAX_SELECTED_BY_FRAMING_CATEGORY;
34340
34740
  exports.MAX_SELECTED_BY_STYLING_DIMENSION = MAX_SELECTED_BY_STYLING_DIMENSION;
34341
34741
  exports.MAX_SUBJECT_KEYS = MAX_SUBJECT_KEYS;
34742
+ exports.MINOR_IMPLYING_TYPE_IDS = MINOR_IMPLYING_TYPE_IDS;
34342
34743
  exports.MOODS = MOODS;
34343
34744
  exports.MOOD_CATEGORY_LABELS = MOOD_CATEGORY_LABELS;
34344
34745
  exports.MOOD_CATEGORY_ORDER = MOOD_CATEGORY_ORDER;
@@ -34401,6 +34802,7 @@ exports.REFERENCE_RULES_MULTI_PERSON = REFERENCE_RULES_MULTI_PERSON;
34401
34802
  exports.REF_BINDING = REF_BINDING;
34402
34803
  exports.RENDER_QUALITIES = RENDER_QUALITIES;
34403
34804
  exports.RENDER_QUALITY_IDS = RENDER_QUALITY_IDS;
34805
+ exports.RETIRED_ADULT_ONLY_HINT_STRINGS = RETIRED_ADULT_ONLY_HINT_STRINGS;
34404
34806
  exports.SCENE_FRAME_RULE = SCENE_FRAME_RULE;
34405
34807
  exports.SCENE_PROMPT_MAX_LENGTH = SCENE_PROMPT_MAX_LENGTH;
34406
34808
  exports.SETTINGS = SETTINGS;
@@ -34461,8 +34863,10 @@ exports.WARDROBE = WARDROBE;
34461
34863
  exports.WARDROBE_CATEGORY_LABELS = WARDROBE_CATEGORY_LABELS;
34462
34864
  exports.WARDROBE_DIMENSION_ORDER = WARDROBE_DIMENSION_ORDER;
34463
34865
  exports.WARDROBE_FIELD_BY_DIMENSION = WARDROBE_FIELD_BY_DIMENSION;
34866
+ exports.__resetCatalogIdGuardForTests = __resetCatalogIdGuardForTests;
34464
34867
  exports.appendField = appendField;
34465
34868
  exports.appendMusicMeta = appendMusicMeta;
34869
+ exports.applyMinorAgeFloorToPickerValues = applyMinorAgeFloorToPickerValues;
34466
34870
  exports.applyPickerJson = applyPickerJson;
34467
34871
  exports.applyPromptAffixes = applyPromptAffixes;
34468
34872
  exports.applyReferenceOrderToVideo = applyReferenceOrderToVideo;
@@ -34508,6 +34912,7 @@ exports.buildMusicGenreHints = buildMusicGenreHints;
34508
34912
  exports.buildMusicGenreTerms = buildMusicGenreTerms;
34509
34913
  exports.buildMusicMoodHints = buildMusicMoodHints;
34510
34914
  exports.buildMusicMoodTerms = buildMusicMoodTerms;
34915
+ exports.buildNeedleAlternationSource = buildNeedleAlternationSource;
34511
34916
  exports.buildObjectMotionPrompt = buildObjectMotionPrompt;
34512
34917
  exports.buildObjectPrompt = buildObjectPrompt;
34513
34918
  exports.buildPersonHints = buildPersonHints;
@@ -34533,6 +34938,7 @@ exports.buildVoiceCharacterTerms = buildVoiceCharacterTerms;
34533
34938
  exports.buildVoiceDeliveryHints = buildVoiceDeliveryHints;
34534
34939
  exports.buildVoiceDeliveryTerms = buildVoiceDeliveryTerms;
34535
34940
  exports.buildWardrobeHints = buildWardrobeHints;
34941
+ exports.catalogGuardActive = catalogGuardActive;
34536
34942
  exports.catalogPacksVersion = catalogPacksVersion;
34537
34943
  exports.characterLockToRefLock = characterLockToRefLock;
34538
34944
  exports.collectIdentityLockClause = collectIdentityLockClause;
@@ -34545,19 +34951,35 @@ exports.composeSectionedPrompt = composeSectionedPrompt;
34545
34951
  exports.composeSoundHintFromConnections = composeSoundHintFromConnections;
34546
34952
  exports.composeTransitionHintFromConnections = composeTransitionHintFromConnections;
34547
34953
  exports.composeVideoPromptText = composeVideoPromptText;
34954
+ exports.composedHas = composedHas;
34955
+ exports.composedOption = composedOption;
34956
+ exports.composedOptionIndex = composedOptionIndex;
34548
34957
  exports.computeLlmChatFields = computeLlmChatFields;
34549
34958
  exports.computeNodePrompt = computeNodePrompt;
34550
34959
  exports.computePackSidecarCoverage = computePackSidecarCoverage;
34960
+ exports.containsMinorAgeHint = containsMinorAgeHint;
34961
+ exports.curateEntries = curateEntries;
34962
+ exports.curatedAnimalPromptHint = curatedAnimalPromptHint;
34963
+ exports.curatedAnimalTerm = curatedAnimalTerm;
34964
+ exports.curatedFurnitureText = curatedFurnitureText;
34965
+ exports.curatedVehicleText = curatedVehicleText;
34966
+ exports.curatedWeaponText = curatedWeaponText;
34551
34967
  exports.deriveTerm = deriveTerm;
34552
34968
  exports.directionFieldsForSurface = directionFieldsForSurface;
34553
34969
  exports.endsInsideStyleSection = endsInsideStyleSection;
34554
34970
  exports.expandImagePositionRefs = expandImagePositionRefs;
34555
34971
  exports.expandImageRefTokens = expandImageRefTokens;
34556
34972
  exports.filmStillPrefix = filmStillPrefix;
34973
+ exports.findForeignCatalogIds = findForeignCatalogIds;
34974
+ exports.findForeignCatalogIdsInBody = findForeignCatalogIdsInBody;
34975
+ exports.foreignCatalogIdMessage = foreignCatalogIdMessage;
34557
34976
  exports.getActionFx = getActionFx;
34558
34977
  exports.getActionFxLabel = getActionFxLabel;
34559
34978
  exports.getActionFxPromptHint = getActionFxPromptHint;
34560
34979
  exports.getActionFxTerm = getActionFxTerm;
34980
+ exports.getAdultOnlyEntries = getAdultOnlyEntries;
34981
+ exports.getAdultOnlyHintStrings = getAdultOnlyHintStrings;
34982
+ exports.getAdultOnlyIds = getAdultOnlyIds;
34561
34983
  exports.getAesthetic = getAesthetic;
34562
34984
  exports.getAestheticLabel = getAestheticLabel;
34563
34985
  exports.getAestheticPromptHint = getAestheticPromptHint;
@@ -34630,6 +35052,7 @@ exports.getMaterial = getMaterial;
34630
35052
  exports.getMaterialLabel = getMaterialLabel;
34631
35053
  exports.getMaterialPromptHint = getMaterialPromptHint;
34632
35054
  exports.getMaterialTerm = getMaterialTerm;
35055
+ exports.getMinorAgeHintStrings = getMinorAgeHintStrings;
34633
35056
  exports.getMood = getMood;
34634
35057
  exports.getMoodLabel = getMoodLabel;
34635
35058
  exports.getMoodPromptHint = getMoodPromptHint;
@@ -34735,11 +35158,14 @@ exports.getWardrobeEntriesByDimension = getWardrobeEntriesByDimension;
34735
35158
  exports.getWardrobeEntry = getWardrobeEntry;
34736
35159
  exports.getWardrobePromptHint = getWardrobePromptHint;
34737
35160
  exports.groupFactoryPresets = groupFactoryPresets;
35161
+ exports.hasCatalogPacksFor = hasCatalogPacksFor;
34738
35162
  exports.hasUpstreamCharacter = hasUpstreamCharacter;
34739
35163
  exports.identityRefsSentence = identityRefsSentence;
34740
35164
  exports.insertBeforeStyleSection = insertBeforeStyleSection;
34741
35165
  exports.isAnalyzablePicker = isAnalyzablePicker;
35166
+ exports.isDeniedStyleId = isDeniedStyleId;
34742
35167
  exports.isInstrumentalVocal = isInstrumentalVocal;
35168
+ exports.isMinorAge = isMinorAge;
34743
35169
  exports.isSuspiciousDerivedTerm = isSuspiciousDerivedTerm;
34744
35170
  exports.isVantageFraming = isVantageFraming;
34745
35171
  exports.isWizardSupported = isWizardSupported;
@@ -34753,6 +35179,7 @@ exports.nodeHasInlinePrompt = nodeHasInlinePrompt;
34753
35179
  exports.nodeHasPromptField = nodeHasPromptField;
34754
35180
  exports.nodeSupportsPromptAffixes = nodeSupportsPromptAffixes;
34755
35181
  exports.normalizeSubjectFields = normalizeSubjectFields;
35182
+ exports.overlayEntry = overlayEntry;
34756
35183
  exports.partitionStyleClauses = partitionStyleClauses;
34757
35184
  exports.personPacksVersion = personPacksVersion;
34758
35185
  exports.pickerFanoutTargets = pickerFanoutTargets;
@@ -34788,10 +35215,12 @@ exports.resolveTerm = resolveTerm;
34788
35215
  exports.resolveVeoI2vInputs = resolveVeoI2vInputs;
34789
35216
  exports.resolveVideoReferenceCore = resolveVideoReferenceCore;
34790
35217
  exports.sectionedClauseCosts = sectionedClauseCosts;
35218
+ exports.setComposedCatalogResolver = setComposedCatalogResolver;
34791
35219
  exports.splitStyleSection = splitStyleSection;
34792
35220
  exports.styleSectionFromClauses = styleSectionFromClauses;
34793
35221
  exports.styleSlotFor = styleSlotFor;
34794
35222
  exports.subjectFieldsForSurface = subjectFieldsForSurface;
35223
+ exports.subscribeCatalogPacks = subscribeCatalogPacks;
34795
35224
  exports.summarizePickerCatalogs = summarizePickerCatalogs;
34796
35225
  exports.toIdentityLockMode = toIdentityLockMode;
34797
35226
  exports.truncateForField = truncateForField;