@nodaro/prompts 1.13.0 → 1.15.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.
- package/dist/index.cjs +719 -255
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +390 -2
- package/dist/index.d.ts +390 -2
- package/dist/index.js +689 -257
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/adult-only-ratchet.test.ts +110 -0
- package/src/__tests__/age-floor.test.ts +347 -0
- package/src/__tests__/catalog-id-guard.test.ts +146 -0
- package/src/__tests__/catalog-overlay.test.ts +163 -0
- package/src/__tests__/dod-replace-pack-acceptance.test.ts +17 -0
- package/src/__tests__/fixtures/parameter-hint-golden.json +2 -2
- package/src/__tests__/minor-age-floor.test.ts +136 -0
- package/src/__tests__/multi-picker-spec.test.ts +21 -1
- package/src/__tests__/person-pack-adult-only.test.ts +45 -0
- package/src/__tests__/person-regional-aesthetic.test.ts +2 -1
- package/src/__tests__/picker-analyzer-registry.test.ts +14 -0
- package/src/__tests__/provider-prompt-doctrine.test.ts +39 -0
- package/src/action-fx.ts +2 -1
- package/src/aesthetic.ts +2 -1
- package/src/age-floor.ts +296 -0
- package/src/age-signal.ts +36 -0
- package/src/atmosphere.ts +2 -1
- package/src/backdrop.ts +2 -1
- package/src/camera-format.ts +2 -1
- package/src/camera-motions.ts +2 -1
- package/src/catalog-id-guard.ts +189 -0
- package/src/catalog-overlay.ts +148 -0
- package/src/catalog-packs.ts +27 -2
- package/src/character-fx.ts +2 -1
- package/src/color-look.ts +2 -1
- package/src/composition-effects.ts +2 -1
- package/src/era.ts +2 -1
- package/src/exposure-settings.ts +2 -1
- package/src/framing.ts +2 -1
- package/src/gemini-omni-inputs.ts +11 -3
- package/src/held-prop.ts +3 -1
- package/src/index.ts +4 -0
- package/src/instrumentation.ts +5 -4
- package/src/lens.ts +2 -1
- package/src/lighting.ts +2 -1
- package/src/loop-subject.ts +3 -1
- package/src/materials.ts +2 -1
- package/src/mood.ts +16 -5
- package/src/music-genre.ts +5 -4
- package/src/music-mood.ts +4 -3
- package/src/parameter-prompt-hint.ts +13 -57
- package/src/person-packs.ts +14 -2
- package/src/person.ts +75 -38
- package/src/photo-genre.ts +14 -3
- package/src/photographer.ts +2 -1
- package/src/picker-analyzer-registry.ts +46 -0
- package/src/picker-catalogs.ts +11 -0
- package/src/pose.ts +17 -6
- package/src/post-process-effects.ts +2 -1
- package/src/prompt-builder.ts +3 -3
- package/src/prompt-wizard-categories.ts +6 -0
- package/src/provider-prompt-doctrine.ts +51 -2
- package/src/render-quality.ts +2 -1
- package/src/setting.ts +3 -1
- package/src/shared-catalog-overlay.ts +83 -0
- package/src/style.ts +17 -1
- package/src/styling.ts +62 -30
- package/src/subject-registry.ts +2 -2
- package/src/temporal.ts +2 -1
- package/src/transitions.ts +2 -1
- package/src/voice-character.ts +6 -5
- package/src/voice-delivery.ts +4 -3
package/src/styling.ts
CHANGED
|
@@ -47,6 +47,15 @@
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import { resolveTerm, type PickerHintMode } from "./term.js"
|
|
50
|
+
// W1-a minor-age floor: `age-signal.ts` is an import-free leaf (no runtime
|
|
51
|
+
// deps at all), so this import is cycle-safe unconditionally — unlike
|
|
52
|
+
// `age-floor.js`, which pulls in the catalog funnel (`person-packs.js` /
|
|
53
|
+
// `picker-catalogs.js`) and would reopen the person → age-floor →
|
|
54
|
+
// picker-catalogs load-time cycle if imported from here.
|
|
55
|
+
import { isMinorAge } from "./age-signal.js"
|
|
56
|
+
// Same discipline: catalog-overlay.ts imports only the pack registry, never
|
|
57
|
+
// the catalog funnel, so this is cycle-safe from every catalog module.
|
|
58
|
+
import { overlayEntry } from "./catalog-overlay.js"
|
|
50
59
|
|
|
51
60
|
export type StylingDimension =
|
|
52
61
|
| "makeup"
|
|
@@ -73,6 +82,16 @@ export interface Styling {
|
|
|
73
82
|
readonly dimension: StylingDimension
|
|
74
83
|
readonly description: string
|
|
75
84
|
readonly promptHint: string
|
|
85
|
+
/**
|
|
86
|
+
* W1-a minor-age floor (spec 2026-09-01 §3.3). `true` marks an entry whose
|
|
87
|
+
* hint describes body exposure, sheer/wet clothing, swimwear/lingerie, a
|
|
88
|
+
* seductive expression or gaze, or a body-placed tattoo. The fragment
|
|
89
|
+
* collectors DROP flagged entries for a minor subject (`isMinorAge`), the
|
|
90
|
+
* picker hides their tiles, the analyzer never emits them, and the backend
|
|
91
|
+
* policy strips their wording from free text. Hand-curated; the
|
|
92
|
+
* `adult-only-ratchet` test only ratchets. Never set on neutral defaults.
|
|
93
|
+
*/
|
|
94
|
+
readonly adultOnly?: true
|
|
76
95
|
/**
|
|
77
96
|
* Compact professional term for hint-compact mode (see `term.ts`).
|
|
78
97
|
*
|
|
@@ -220,14 +239,14 @@ export const STYLINGS: ReadonlyArray<Styling> = [
|
|
|
220
239
|
{ id: "jewelry-pearl", label: "Pearl", dimension: "jewelry", description: "Pearl necklace / earrings", promptHint: "wearing a pearl necklace and pearl earrings", term: "pearl jewelry" },
|
|
221
240
|
{ id: "jewelry-chunky", label: "Chunky", dimension: "jewelry", description: "Chunky chains and rings", promptHint: "wearing chunky chains and oversized rings", term: "chunky chains and rings" },
|
|
222
241
|
{ 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" },
|
|
223
|
-
{ 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" },
|
|
224
|
-
{ 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" },
|
|
225
|
-
{ 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" },
|
|
242
|
+
{ 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 },
|
|
243
|
+
{ 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 },
|
|
244
|
+
{ 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 },
|
|
226
245
|
{ id: "jewelry-stud-earrings", label: "Stud Earrings", dimension: "jewelry", description: "Small simple studs", promptHint: "wearing small simple stud earrings" },
|
|
227
246
|
{ id: "jewelry-hoop-earrings", label: "Hoop Earrings", dimension: "jewelry", description: "Gold or silver hoops", promptHint: "wearing gold or silver hoop earrings" },
|
|
228
247
|
{ 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" },
|
|
229
248
|
{ 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" },
|
|
230
|
-
{ 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" },
|
|
249
|
+
{ 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 },
|
|
231
250
|
|
|
232
251
|
// -------------------- Nails --------------------
|
|
233
252
|
{ id: "nails-polished", label: "Polished", dimension: "nails", description: "Clear or nude polished nails", promptHint: "with polished nude nails", term: "polished nude nails" },
|
|
@@ -263,10 +282,10 @@ export const STYLINGS: ReadonlyArray<Styling> = [
|
|
|
263
282
|
{ id: "fabric-chiffon", label: "Chiffon", dimension: "fabric", description: "Sheer flowing chiffon", promptHint: "wearing sheer chiffon with a lightweight floating drape and soft translucent layers" },
|
|
264
283
|
{ 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" },
|
|
265
284
|
{ id: "fabric-sequins", label: "Sequins", dimension: "fabric", description: "Sparkling sequin fabric", promptHint: "wearing sparkling sequined fabric catching light with countless tiny reflective facets" },
|
|
266
|
-
{ id: "fabric-latex", label: "Latex", dimension: "fabric", description: "Glossy latex", promptHint: "wearing glossy latex with a high-shine liquid look clinging to the body" },
|
|
285
|
+
{ 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 },
|
|
267
286
|
|
|
268
287
|
// -------------------- Outfit (single-pick complete look — overrides individual pieces) --------------------
|
|
269
|
-
{ id: "outfit-school-uniform", label: "School Uniform", dimension: "outfit", description: "Classic school uniform set", promptHint: "dressed in a classic school uniform — collared blouse, pleated skirt and knee-high socks" },
|
|
288
|
+
{ id: "outfit-school-uniform", label: "School Uniform", dimension: "outfit", description: "Classic school uniform set", promptHint: "dressed in a classic school uniform — collared blouse, pleated skirt and knee-high socks" , adultOnly: true },
|
|
270
289
|
{ 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" },
|
|
271
290
|
{ id: "outfit-tuxedo", label: "Tuxedo", dimension: "outfit", description: "Black-tie tuxedo", promptHint: "dressed in a black tuxedo with satin lapels and a bow tie" },
|
|
272
291
|
{ 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" },
|
|
@@ -280,9 +299,9 @@ export const STYLINGS: ReadonlyArray<Styling> = [
|
|
|
280
299
|
{ id: "outfit-athleisure", label: "Athleisure Set", dimension: "outfit", description: "Matching athleisure top and bottom", promptHint: "dressed in a matching athleisure set — a fitted top and leggings in the same tone" },
|
|
281
300
|
{ id: "outfit-tracksuit", label: "Tracksuit", dimension: "outfit", description: "Two-piece zip-front tracksuit", promptHint: "dressed in a two-piece tracksuit — a zip-front jacket and matching joggers" },
|
|
282
301
|
{ id: "outfit-soccer-jersey", label: "Soccer Jersey", dimension: "outfit", description: "National-team football jersey with crest", promptHint: "wearing a national-team soccer jersey — team colors and trim with the team crest on the chest" },
|
|
283
|
-
{ id: "outfit-bikini", label: "Bikini", dimension: "outfit", description: "Two-piece swimsuit", promptHint: "in a two-piece bikini" },
|
|
284
|
-
{ id: "outfit-one-piece-swim", label: "One-Piece Swimsuit", dimension: "outfit", description: "Fitted one-piece swimsuit", promptHint: "in a fitted one-piece swimsuit" },
|
|
285
|
-
{ id: "outfit-lingerie", label: "Lingerie Set", dimension: "outfit", description: "Matching lingerie set", promptHint: "in a matching lingerie set" },
|
|
302
|
+
{ id: "outfit-bikini", label: "Bikini", dimension: "outfit", description: "Two-piece swimsuit", promptHint: "in a two-piece bikini" , adultOnly: true },
|
|
303
|
+
{ 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 },
|
|
304
|
+
{ id: "outfit-lingerie", label: "Lingerie Set", dimension: "outfit", description: "Matching lingerie set", promptHint: "in a matching lingerie set" , adultOnly: true },
|
|
286
305
|
{ id: "outfit-pajamas", label: "Pajamas", dimension: "outfit", description: "Soft cotton pajama set", promptHint: "in a soft cotton pajama set — a button-up top and matching pants" },
|
|
287
306
|
{ id: "outfit-bathrobe", label: "Bathrobe", dimension: "outfit", description: "Plush terrycloth robe", promptHint: "wrapped in a plush terrycloth bathrobe tied at the waist" },
|
|
288
307
|
{ id: "outfit-kimono", label: "Kimono", dimension: "outfit", description: "Traditional Japanese kimono", promptHint: "dressed in a traditional Japanese kimono with an obi sash" },
|
|
@@ -300,6 +319,9 @@ export const STYLINGS: ReadonlyArray<Styling> = [
|
|
|
300
319
|
{ id: "outfit-fairy", label: "Fairy", dimension: "outfit", description: "Fantasy fairy: gauzy wings, flower crown, ethereal dress", promptHint: "wearing a fantasy fairy costume — gauzy translucent wings, a flower crown, and an ethereal flowing dress", term: "fairy costume with wings" },
|
|
301
320
|
{ id: "outfit-mermaid", label: "Mermaid", dimension: "outfit", description: "Fantasy mermaid: scaled tail/skirt, shell top, flowing hair", promptHint: "wearing a fantasy mermaid costume — a scaled tail or fitted scaled skirt, a shell top, and long flowing hair", term: "mermaid costume" },
|
|
302
321
|
{ id: "outfit-pharaoh", label: "Pharaoh Regalia", dimension: "outfit", description: "Ancient Egyptian royalty: usekh collar, pectoral, pleated kilt", promptHint: "wearing ancient Egyptian pharaoh regalia — a broad beaded usekh collar, a jeweled falcon pectoral, and a pleated linen shendyt kilt with golden arm cuffs" },
|
|
322
|
+
{ id: "outfit-workwear-overalls", label: "Workwear Overalls", dimension: "outfit", description: "Denim bib overalls over a plaid flannel shirt", promptHint: "dressed in a farmer's workwear outfit — denim bib overalls over a checked plaid flannel shirt, sturdy and worn-in", term: "denim overalls and plaid shirt" },
|
|
323
|
+
{ id: "outfit-chapan", label: "Chapan Robe", dimension: "outfit", description: "Central Asian long quilted ikat robe", promptHint: "dressed in a traditional Central Asian chapan — a long quilted robe with an ikat-striped weave, tied at the waist with a sash" },
|
|
324
|
+
{ id: "outfit-caftan", label: "Caftan", dimension: "outfit", description: "Long flowing Middle-Eastern / North-African robe", promptHint: "dressed in a long flowing caftan robe, a full-length garment worn across the Middle East and North Africa" },
|
|
303
325
|
|
|
304
326
|
// -------------------- Top (upper-body garment) --------------------
|
|
305
327
|
{ id: "top-tshirt", label: "T-Shirt", dimension: "top", description: "Plain crewneck t-shirt", promptHint: "wearing a fitted plain crewneck t-shirt with short sleeves" },
|
|
@@ -313,14 +335,14 @@ export const STYLINGS: ReadonlyArray<Styling> = [
|
|
|
313
335
|
{ 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" },
|
|
314
336
|
{ id: "top-tube-top", label: "Tube Top", dimension: "top", description: "Strapless tube top", promptHint: "wearing a strapless tube top fitted across the chest" },
|
|
315
337
|
{ id: "top-camisole", label: "Camisole", dimension: "top", description: "Thin satin cami", promptHint: "wearing a thin satin camisole with delicate spaghetti straps" },
|
|
316
|
-
{ id: "top-corset", label: "Corset", dimension: "top", description: "Structured corset top", promptHint: "wearing a structured corset top with boning and visible lacing" },
|
|
317
|
-
{ 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" },
|
|
338
|
+
{ id: "top-corset", label: "Corset", dimension: "top", description: "Structured corset top", promptHint: "wearing a structured corset top with boning and visible lacing" , adultOnly: true },
|
|
339
|
+
{ 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 },
|
|
318
340
|
{ id: "top-sports-bra", label: "Sports Bra", dimension: "top", description: "Athletic sports bra", promptHint: "wearing a structured sports bra with athletic strapping" },
|
|
319
|
-
{ 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" },
|
|
341
|
+
{ 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 },
|
|
320
342
|
{ 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" },
|
|
321
343
|
{ 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" },
|
|
322
|
-
{ 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" },
|
|
323
|
-
{ 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" },
|
|
344
|
+
{ 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 },
|
|
345
|
+
{ 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 },
|
|
324
346
|
{ id: "top-bandeau", label: "Bandeau", dimension: "top", description: "Strapless tube top", promptHint: "wearing a strapless bandeau top wrapped across the chest" },
|
|
325
347
|
|
|
326
348
|
// -------------------- Bottom (lower-body garment) --------------------
|
|
@@ -363,11 +385,11 @@ export const STYLINGS: ReadonlyArray<Styling> = [
|
|
|
363
385
|
|
|
364
386
|
// -------------------- Legwear (between bottom and footwear) --------------------
|
|
365
387
|
{ id: "legwear-bare", label: "Bare Legs", dimension: "legwear", description: "Bare legs, no hosiery", promptHint: "with bare legs, no hosiery" },
|
|
366
|
-
{ id: "legwear-sheer-tights", label: "Sheer Tights", dimension: "legwear", description: "Sheer nude pantyhose", promptHint: "with sheer nude pantyhose" },
|
|
388
|
+
{ id: "legwear-sheer-tights", label: "Sheer Tights", dimension: "legwear", description: "Sheer nude pantyhose", promptHint: "with sheer nude pantyhose" , adultOnly: true },
|
|
367
389
|
{ id: "legwear-opaque-tights", label: "Opaque Tights", dimension: "legwear", description: "Solid opaque tights", promptHint: "with opaque black tights covering the legs" },
|
|
368
|
-
{ 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" },
|
|
369
|
-
{ id: "legwear-thigh-highs", label: "Thigh-High Stockings", dimension: "legwear", description: "Thigh-high stockings", promptHint: "with thigh-high stockings stopping mid-thigh" },
|
|
370
|
-
{ 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" },
|
|
390
|
+
{ 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 },
|
|
391
|
+
{ id: "legwear-thigh-highs", label: "Thigh-High Stockings", dimension: "legwear", description: "Thigh-high stockings", promptHint: "with thigh-high stockings stopping mid-thigh" , adultOnly: true },
|
|
392
|
+
{ 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 },
|
|
371
393
|
{ id: "legwear-knee-highs", label: "Knee-High Socks", dimension: "legwear", description: "Knee-high socks", promptHint: "with knee-high socks pulled up the calf" },
|
|
372
394
|
{ id: "legwear-crew-socks", label: "Crew Socks", dimension: "legwear", description: "Mid-calf crew socks", promptHint: "with simple crew socks visible above the shoes" },
|
|
373
395
|
{ 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" },
|
|
@@ -382,7 +404,7 @@ export const STYLINGS: ReadonlyArray<Styling> = [
|
|
|
382
404
|
{ id: "footwear-platforms", label: "Platform Shoes", dimension: "footwear", description: "Tall platform soles", promptHint: "in platform shoes with thick chunky soles" },
|
|
383
405
|
{ id: "footwear-ankle-boots", label: "Ankle Boots", dimension: "footwear", description: "Fitted ankle boots", promptHint: "in fitted black ankle boots" },
|
|
384
406
|
{ id: "footwear-knee-boots", label: "Knee-High Boots", dimension: "footwear", description: "Tall knee-high boots", promptHint: "in tall knee-high leather boots" },
|
|
385
|
-
{ id: "footwear-thigh-high-boots", label: "Thigh-High Boots", dimension: "footwear", description: "Thigh-high boots", promptHint: "in over-the-knee thigh-high leather boots" },
|
|
407
|
+
{ 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 },
|
|
386
408
|
{ id: "footwear-combat-boots", label: "Combat Boots", dimension: "footwear", description: "Laced combat boots", promptHint: "in laced black combat boots with a chunky lugged sole" },
|
|
387
409
|
{ id: "footwear-chelsea-boots", label: "Chelsea Boots", dimension: "footwear", description: "Slip-on Chelsea boots", promptHint: "in clean Chelsea boots with elastic side panels" },
|
|
388
410
|
{ 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" },
|
|
@@ -402,18 +424,18 @@ export const STYLINGS: ReadonlyArray<Styling> = [
|
|
|
402
424
|
|
|
403
425
|
// -------------------- Wardrobe State (modifier — composes with any garment) --------------------
|
|
404
426
|
{ 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" },
|
|
405
|
-
{ 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" },
|
|
406
|
-
{ 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" },
|
|
407
|
-
{ 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" },
|
|
408
|
-
{ 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" },
|
|
427
|
+
{ 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" , adultOnly: true },
|
|
428
|
+
{ 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 },
|
|
429
|
+
{ 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 },
|
|
430
|
+
{ 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" , adultOnly: true },
|
|
409
431
|
{ 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" },
|
|
410
432
|
{ 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" },
|
|
411
433
|
{ id: "state-vintage", label: "Vintage", dimension: "wardrobe-state", description: "Worn, retro character", promptHint: "the clothing carrying vintage character — softened color, broken-in fit, retro silhouette", term: "vintage clothing" },
|
|
412
434
|
{ 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" },
|
|
413
435
|
{ id: "state-half-tucked", label: "Half-Tucked", dimension: "wardrobe-state", description: "Front-tucked, back loose", promptHint: "the top half-tucked — front neatly tucked in, back hanging loose", term: "top half-tucked" },
|
|
414
|
-
{ 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" },
|
|
415
|
-
{ id: "state-halter-neck", label: "Halter Neck", dimension: "wardrobe-state", description: "Straps tied behind the neck", promptHint: "the top cut as a halter — straps tied behind the neck, shoulders and upper back bare", term: "halter neckline" },
|
|
416
|
-
{ id: "state-plunging-neck", label: "Plunging Neckline", dimension: "wardrobe-state", description: "Deep plunging V neckline", promptHint: "the neckline cut in a deep plunging V" },
|
|
436
|
+
{ 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 },
|
|
437
|
+
{ id: "state-halter-neck", label: "Halter Neck", dimension: "wardrobe-state", description: "Straps tied behind the neck", promptHint: "the top cut as a halter — straps tied behind the neck, shoulders and upper back bare", term: "halter neckline" , adultOnly: true },
|
|
438
|
+
{ 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 },
|
|
417
439
|
{ 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" },
|
|
418
440
|
{ 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" },
|
|
419
441
|
{ 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" },
|
|
@@ -507,6 +529,7 @@ export const STYLING_FIELD_BY_DIMENSION: Record<
|
|
|
507
529
|
* (necklace + earrings + rings), wardrobe-state: 3 (oversized + wet + ripped),
|
|
508
530
|
* hair-state: 2 (wet + windswept). All others single-select (absent → 1). */
|
|
509
531
|
export const MAX_SELECTED_BY_STYLING_DIMENSION: Partial<Record<StylingDimension, number>> = {
|
|
532
|
+
headwear: 2, // a hat layered over a wrap/turban
|
|
510
533
|
jewelry: 3,
|
|
511
534
|
"wardrobe-state": 3,
|
|
512
535
|
"hair-state": 2,
|
|
@@ -520,7 +543,7 @@ export function getStylingDimensionLimit(dimension: StylingDimension): number {
|
|
|
520
543
|
export interface StylingValue {
|
|
521
544
|
makeup?: string
|
|
522
545
|
eyewear?: string
|
|
523
|
-
headwear?: string
|
|
546
|
+
headwear?: string | ReadonlyArray<string>
|
|
524
547
|
/** Hair cut / styling choice — bob, wolf cut, braids, ponytail, etc.
|
|
525
548
|
* Pairs with Person.hair-base (texture + length). */
|
|
526
549
|
hairCut?: string
|
|
@@ -566,7 +589,7 @@ const stylingById = new Map<string, Styling>(STYLINGS.map((s) => [s.id, s]))
|
|
|
566
589
|
|
|
567
590
|
export function getStyling(id: string | undefined | null): Styling | undefined {
|
|
568
591
|
if (!id) return undefined
|
|
569
|
-
return stylingById.get(id)
|
|
592
|
+
return overlayEntry("styling", id, stylingById.get(id))
|
|
570
593
|
}
|
|
571
594
|
|
|
572
595
|
export function getStylingLabel(id: string | undefined | null, fallback?: string): string {
|
|
@@ -619,6 +642,15 @@ function collectStylingFragments(
|
|
|
619
642
|
? lipStateRaw.includes("lip-state-bold-red")
|
|
620
643
|
: lipStateRaw === "lip-state-bold-red"
|
|
621
644
|
|
|
645
|
+
// W1-a (Layer 1): the subject fold passes person + styling as ONE flat bag,
|
|
646
|
+
// so the age is readable here; drop adultOnly garments for a minor. The
|
|
647
|
+
// drop is checked on the ENTRY the collector itself resolves for that id
|
|
648
|
+
// (`getStyling`, the same lookup `fragmentFor` already reads through), not
|
|
649
|
+
// a separately-sourced id set. A separate styling node carries no age and
|
|
650
|
+
// is left alone (Layer 2 — the backend policy — covers that path).
|
|
651
|
+
const floored = isMinorAge(data as { age?: string; customAge?: number; type?: string })
|
|
652
|
+
const emit = floored ? (id: string) => (getStyling(id)?.adultOnly === true ? "" : fragmentFor(id)) : fragmentFor
|
|
653
|
+
|
|
622
654
|
for (const dimension of STYLING_DIMENSION_ORDER) {
|
|
623
655
|
const field = STYLING_FIELD_BY_DIMENSION[dimension]
|
|
624
656
|
const raw = data[field]
|
|
@@ -626,12 +658,12 @@ function collectStylingFragments(
|
|
|
626
658
|
// jewelry / wardrobe-state / hair-state are multi-pick (string | string[]);
|
|
627
659
|
// emit each id's fragment independently and let the comma-join compose.
|
|
628
660
|
if (typeof raw === "string" && raw.length > 0) {
|
|
629
|
-
const fragment =
|
|
661
|
+
const fragment = emit(raw)
|
|
630
662
|
if (fragment) out.push(fragment)
|
|
631
663
|
} else if (Array.isArray(raw)) {
|
|
632
664
|
for (const item of raw) {
|
|
633
665
|
if (typeof item !== "string" || item.length === 0) continue
|
|
634
|
-
const fragment =
|
|
666
|
+
const fragment = emit(item)
|
|
635
667
|
if (fragment) out.push(fragment)
|
|
636
668
|
}
|
|
637
669
|
}
|
package/src/subject-registry.ts
CHANGED
|
@@ -80,7 +80,7 @@ import {
|
|
|
80
80
|
} from "./styling.js"
|
|
81
81
|
import { buildHeldPropHints } from "./held-prop.js"
|
|
82
82
|
import { buildMaterialHints } from "./materials.js"
|
|
83
|
-
import {
|
|
83
|
+
import { curatedAnimalPromptHint, curatedAnimalTerm } from "./shared-catalog-overlay.js"
|
|
84
84
|
|
|
85
85
|
/** Which generation stages fold a dimension (mirrors `DirectionSurface`). */
|
|
86
86
|
export type SubjectSurface = "image" | "video" | "both"
|
|
@@ -200,7 +200,7 @@ export const SUBJECT_FIELDS = [
|
|
|
200
200
|
{ key: "styling", kind: "group", surface: "both", render: viaFragmentBuilder(buildStylingHints) },
|
|
201
201
|
{ key: "heldProp", kind: "ids", surface: "both", maxPicks: 2, render: viaListBuilder(buildHeldPropHints) },
|
|
202
202
|
{ key: "material", kind: "ids", surface: "both", maxPicks: 2, render: viaStringBuilder(buildMaterialHints) },
|
|
203
|
-
{ key: "animal", kind: "ids", surface: "both", maxPicks: 1, render: perId(
|
|
203
|
+
{ key: "animal", kind: "ids", surface: "both", maxPicks: 1, render: perId(curatedAnimalPromptHint, curatedAnimalTerm) },
|
|
204
204
|
] as const satisfies ReadonlyArray<SubjectFieldSpec>
|
|
205
205
|
|
|
206
206
|
export type SubjectFieldRow = (typeof SUBJECT_FIELDS)[number]
|
package/src/temporal.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { resolveTerm, type PickerHintMode } from "./term.js"
|
|
19
|
+
import { overlayEntry } from "./catalog-overlay.js"
|
|
19
20
|
|
|
20
21
|
export type TemporalCategory = "speed" | "freeze" | "direction" | "shutter"
|
|
21
22
|
|
|
@@ -83,7 +84,7 @@ const temporalById = new Map<string, Temporal>(TEMPORALS.map((t) => [t.id, t]))
|
|
|
83
84
|
|
|
84
85
|
export function getTemporal(id: string | undefined | null): Temporal | undefined {
|
|
85
86
|
if (!id) return undefined
|
|
86
|
-
return temporalById.get(id)
|
|
87
|
+
return overlayEntry("temporal", id, temporalById.get(id))
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
export function getTemporalLabel(id: string | undefined | null, fallback?: string): string {
|
package/src/transitions.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { resolveTerm, type PickerHintMode } from "./term.js"
|
|
16
|
+
import { overlayEntry } from "./catalog-overlay.js"
|
|
16
17
|
|
|
17
18
|
export type TransitionCategory =
|
|
18
19
|
| "standard"
|
|
@@ -284,7 +285,7 @@ const transitionById = new Map<string, Transition>(
|
|
|
284
285
|
|
|
285
286
|
export function getTransition(id: string | undefined | null): Transition | undefined {
|
|
286
287
|
if (!id) return undefined
|
|
287
|
-
return transitionById.get(id)
|
|
288
|
+
return overlayEntry("transitions", id, transitionById.get(id))
|
|
288
289
|
}
|
|
289
290
|
|
|
290
291
|
export function getTransitionLabel(id: string | undefined | null, fallback?: string): string {
|
package/src/voice-character.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { pickIds } from "@nodaro/shared"
|
|
11
11
|
import { resolveTerm, type PickerHintMode } from "./term.js"
|
|
12
|
+
import { overlayEntry } from "./catalog-overlay.js"
|
|
12
13
|
|
|
13
14
|
export interface VoiceCharacterEntry {
|
|
14
15
|
readonly id: string
|
|
@@ -159,11 +160,11 @@ const LANGUAGE_BY_ID = new Map(VOICE_LANGUAGES.map((x) => [x.id, x]))
|
|
|
159
160
|
const ACCENT_BY_ID = new Map(VOICE_ACCENTS.map((x) => [x.id, x]))
|
|
160
161
|
const TIMBRE_BY_ID = new Map(VOICE_TIMBRES.map((x) => [x.id, x]))
|
|
161
162
|
|
|
162
|
-
export function getVoiceAge(id: string | undefined) { return id ? AGE_BY_ID.get(id) : undefined }
|
|
163
|
-
export function getVoiceGender(id: string | undefined) { return id ? GENDER_BY_ID.get(id) : undefined }
|
|
164
|
-
export function getVoiceLanguage(id: string | undefined) { return id ? LANGUAGE_BY_ID.get(id) : undefined }
|
|
165
|
-
export function getVoiceAccent(id: string | undefined) { return id ? ACCENT_BY_ID.get(id) : undefined }
|
|
166
|
-
export function getVoiceTimbre(id: string | undefined) { return id ? TIMBRE_BY_ID.get(id) : undefined }
|
|
163
|
+
export function getVoiceAge(id: string | undefined) { return id ? overlayEntry("voice-character", id, AGE_BY_ID.get(id)) : undefined }
|
|
164
|
+
export function getVoiceGender(id: string | undefined) { return id ? overlayEntry("voice-character", id, GENDER_BY_ID.get(id)) : undefined }
|
|
165
|
+
export function getVoiceLanguage(id: string | undefined) { return id ? overlayEntry("voice-character", id, LANGUAGE_BY_ID.get(id)) : undefined }
|
|
166
|
+
export function getVoiceAccent(id: string | undefined) { return id ? overlayEntry("voice-character", id, ACCENT_BY_ID.get(id)) : undefined }
|
|
167
|
+
export function getVoiceTimbre(id: string | undefined) { return id ? overlayEntry("voice-character", id, TIMBRE_BY_ID.get(id)) : undefined }
|
|
167
168
|
|
|
168
169
|
/**
|
|
169
170
|
* The COMPACT counterparts of the five sub-field lookups: the short
|
package/src/voice-delivery.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { resolveTerm, type PickerHintMode } from "./term.js"
|
|
7
|
+
import { overlayEntry } from "./catalog-overlay.js"
|
|
7
8
|
|
|
8
9
|
export interface VoiceDeliveryEntry {
|
|
9
10
|
readonly id: string
|
|
@@ -102,9 +103,9 @@ const PACE_BY_ID = new Map(VOICE_PACES.map((x) => [x.id, x]))
|
|
|
102
103
|
const EMOTION_BY_ID = new Map(VOICE_EMOTIONS.map((x) => [x.id, x]))
|
|
103
104
|
const ARCHETYPE_BY_ID = new Map(VOICE_ARCHETYPES.map((x) => [x.id, x]))
|
|
104
105
|
|
|
105
|
-
export function getVoicePace(id: string | undefined) { return id ? PACE_BY_ID.get(id) : undefined }
|
|
106
|
-
export function getVoiceEmotion(id: string | undefined) { return id ? EMOTION_BY_ID.get(id) : undefined }
|
|
107
|
-
export function getVoiceArchetype(id: string | undefined) { return id ? ARCHETYPE_BY_ID.get(id) : undefined }
|
|
106
|
+
export function getVoicePace(id: string | undefined) { return id ? overlayEntry("voice-delivery", id, PACE_BY_ID.get(id)) : undefined }
|
|
107
|
+
export function getVoiceEmotion(id: string | undefined) { return id ? overlayEntry("voice-delivery", id, EMOTION_BY_ID.get(id)) : undefined }
|
|
108
|
+
export function getVoiceArchetype(id: string | undefined) { return id ? overlayEntry("voice-delivery", id, ARCHETYPE_BY_ID.get(id)) : undefined }
|
|
108
109
|
|
|
109
110
|
/**
|
|
110
111
|
* The COMPACT counterparts of the three lookups above: the short professional
|