@nodaro/prompts 1.7.3 → 1.8.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 (72) hide show
  1. package/dist/index.cjs +5422 -4269
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +1198 -61
  4. package/dist/index.d.ts +1198 -61
  5. package/dist/index.js +5336 -4271
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/__tests__/b7-person-pack-e2e.test.ts +37 -0
  9. package/src/__tests__/catalog-funnel-ratchet.test.ts +97 -0
  10. package/src/__tests__/catalog-packs.test.ts +130 -0
  11. package/src/__tests__/catalog-sidecar-coverage.test.ts +27 -0
  12. package/src/__tests__/catalog-terms.test.ts +162 -0
  13. package/src/__tests__/character-default-role.test.ts +3 -2
  14. package/src/__tests__/content-free-contract.test.ts +45 -0
  15. package/src/__tests__/dod-replace-pack-acceptance.test.ts +41 -0
  16. package/src/__tests__/fixtures/parameter-hint-golden.json +2959 -0
  17. package/src/__tests__/fixtures/person-sector-pack.ts +27 -0
  18. package/src/__tests__/i18n-entry-completeness.test.ts +5 -2
  19. package/src/__tests__/parameter-hint-mode.test.ts +385 -0
  20. package/src/__tests__/parameter-prompt-hint-pack-fallback.test.ts +25 -0
  21. package/src/__tests__/person-packs.test.ts +166 -0
  22. package/src/__tests__/project-all-catalogs.test.ts +26 -0
  23. package/src/__tests__/prompt-builder.test.ts +53 -0
  24. package/src/__tests__/registered-catalogs-funnel.test.ts +30 -0
  25. package/src/__tests__/registered-catalogs-guard.test.ts +11 -0
  26. package/src/__tests__/term.test.ts +104 -0
  27. package/src/__tests__/transitions.test.ts +8 -5
  28. package/src/__tests__/upstream-immutability.test.ts +23 -0
  29. package/src/action-fx.ts +67 -17
  30. package/src/aesthetic.ts +54 -6
  31. package/src/atmosphere.ts +64 -23
  32. package/src/backdrop.ts +44 -30
  33. package/src/camera-format.ts +33 -11
  34. package/src/camera-motions.ts +89 -0
  35. package/src/catalog-packs.ts +125 -0
  36. package/src/catalog-sidecar-coverage.ts +36 -0
  37. package/src/character-fx.ts +112 -39
  38. package/src/color-look.ts +44 -27
  39. package/src/composition-effects.ts +21 -7
  40. package/src/era.ts +24 -0
  41. package/src/exposure-settings.ts +76 -18
  42. package/src/framing.ts +100 -0
  43. package/src/held-prop.ts +125 -63
  44. package/src/identity-lock.ts +12 -5
  45. package/src/image-reference-doctrine.ts +55 -0
  46. package/src/index.ts +5 -0
  47. package/src/instrumentation.ts +148 -57
  48. package/src/lens.ts +31 -15
  49. package/src/lighting.ts +120 -59
  50. package/src/loop-subject.ts +27 -1
  51. package/src/materials.ts +123 -69
  52. package/src/mood.ts +123 -51
  53. package/src/music-genre.ts +171 -67
  54. package/src/music-mood.ts +85 -17
  55. package/src/parameter-prompt-hint.ts +168 -56
  56. package/src/person-packs.ts +182 -0
  57. package/src/person.ts +506 -408
  58. package/src/photo-genre.ts +37 -22
  59. package/src/photographer.ts +138 -1
  60. package/src/picker-catalogs.ts +86 -40
  61. package/src/pose.ts +105 -40
  62. package/src/post-process-effects.ts +51 -8
  63. package/src/prompt-builder.ts +16 -1
  64. package/src/render-quality.ts +25 -7
  65. package/src/setting.ts +30 -14
  66. package/src/style.ts +33 -15
  67. package/src/styling.ts +155 -94
  68. package/src/temporal.ts +80 -18
  69. package/src/term.ts +155 -0
  70. package/src/transitions.ts +73 -26
  71. package/src/voice-character.ts +190 -90
  72. package/src/voice-delivery.ts +83 -12
package/src/styling.ts CHANGED
@@ -46,6 +46,8 @@
46
46
  * fields for specifics the catalog can't express.
47
47
  */
48
48
 
49
+ import { resolveTerm, type PickerHintMode } from "./term.js"
50
+
49
51
  export type StylingDimension =
50
52
  | "makeup"
51
53
  | "eyewear"
@@ -71,25 +73,45 @@ export interface Styling {
71
73
  readonly dimension: StylingDimension
72
74
  readonly description: string
73
75
  readonly promptHint: string
76
+ /**
77
+ * Compact professional term for hint-compact mode (see `term.ts`).
78
+ *
79
+ * The line this catalog authors on: a label that NAMES a garment, material
80
+ * or accessory ("Beanie", "Trench Coat", "Silk", "Hoop Earrings", "Doc
81
+ * Martens") already IS the term a stylist writes, and gets nothing. A label
82
+ * that is a bare MODIFIER — the whole of `hair-state`, `wardrobe-state`,
83
+ * `nails`, and the adjective half of `makeup` / `jewelry` — means nothing
84
+ * stripped of its category noun, so "Wet" becomes "wet hair" and "Gold"
85
+ * becomes "gold jewelry".
86
+ *
87
+ * The same rule resolves this catalog's internal collisions, where two
88
+ * dimensions share a label and would otherwise derive one indistinguishable
89
+ * fragment: `hair-wet` / `state-wet` (both "Wet"), `jewelry-layered` /
90
+ * `state-layered`, `makeup-goth` / `outfit-goth`. Homonym traps outside the
91
+ * catalog get the same treatment — "Highlights" is a lighting word before it
92
+ * is a hair word, "Cat-Eye" is eyeliner before it is eyewear, "Hood" is a
93
+ * car part, "Bun" is bread.
94
+ */
95
+ readonly term?: string
74
96
  }
75
97
 
76
98
  export const STYLINGS: ReadonlyArray<Styling> = [
77
99
  // -------------------- Makeup --------------------
78
- { id: "makeup-natural", label: "Natural", dimension: "makeup", description: "Subtle, no-makeup makeup", promptHint: "with natural, subtle no-makeup makeup" },
79
- { id: "makeup-glamour", label: "Glamour", dimension: "makeup", description: "Full polished glam look", promptHint: "with full glamour makeup, polished and editorial" },
100
+ { id: "makeup-natural", label: "Natural", dimension: "makeup", description: "Subtle, no-makeup makeup", promptHint: "with natural, subtle no-makeup makeup", term: "natural no-makeup makeup" },
101
+ { id: "makeup-glamour", label: "Glamour", dimension: "makeup", description: "Full polished glam look", promptHint: "with full glamour makeup, polished and editorial", term: "full glamour makeup" },
80
102
  { id: "makeup-smoky", label: "Smoky Eye", dimension: "makeup", description: "Dark dramatic eye makeup", promptHint: "with smoky eye makeup, dark and dramatic" },
81
103
  { id: "makeup-bold-lips", label: "Bold Lips", dimension: "makeup", description: "Strong red or dark lipstick", promptHint: "with bold lips, a strong red or dark lipstick as the focal point" },
82
- { id: "makeup-editorial", label: "Editorial", dimension: "makeup", description: "Avant-garde fashion makeup", promptHint: "with avant-garde editorial makeup, artistic and unconventional" },
83
- { id: "makeup-goth", label: "Goth", dimension: "makeup", description: "Dark goth aesthetic", promptHint: "with goth makeup — pale skin, black eyeliner, dark lips" },
84
- { id: "makeup-dewy", label: "Dewy Glow", dimension: "makeup", description: "Glowing fresh-skin focus", promptHint: "with dewy makeup, glowing fresh skin and minimal coverage" },
104
+ { id: "makeup-editorial", label: "Editorial", dimension: "makeup", description: "Avant-garde fashion makeup", promptHint: "with avant-garde editorial makeup, artistic and unconventional", term: "editorial makeup" },
105
+ { id: "makeup-goth", label: "Goth", dimension: "makeup", description: "Dark goth aesthetic", promptHint: "with goth makeup — pale skin, black eyeliner, dark lips", term: "goth makeup" },
106
+ { id: "makeup-dewy", label: "Dewy Glow", dimension: "makeup", description: "Glowing fresh-skin focus", promptHint: "with dewy makeup, glowing fresh skin and minimal coverage", term: "dewy makeup" },
85
107
 
86
108
  // -------------------- Eyewear --------------------
87
109
  { id: "eyewear-sunglasses", label: "Sunglasses", dimension: "eyewear", description: "Generic dark sunglasses", promptHint: "wearing sunglasses" },
88
- { id: "eyewear-aviators", label: "Aviators", dimension: "eyewear", description: "Classic aviator shades", promptHint: "wearing classic aviator sunglasses with a metal frame" },
89
- { id: "eyewear-cat-eye", label: "Cat-Eye", dimension: "eyewear", description: "Vintage cat-eye frames", promptHint: "wearing cat-eye glasses with vintage upswept frames" },
90
- { id: "eyewear-round", label: "Round / John Lennon", dimension: "eyewear", description: "Round wire-frame glasses", promptHint: "wearing round wire-frame glasses, John Lennon style" },
110
+ { id: "eyewear-aviators", label: "Aviators", dimension: "eyewear", description: "Classic aviator shades", promptHint: "wearing classic aviator sunglasses with a metal frame", term: "aviator sunglasses" },
111
+ { id: "eyewear-cat-eye", label: "Cat-Eye", dimension: "eyewear", description: "Vintage cat-eye frames", promptHint: "wearing cat-eye glasses with vintage upswept frames", term: "cat-eye glasses" },
112
+ { id: "eyewear-round", label: "Round / John Lennon", dimension: "eyewear", description: "Round wire-frame glasses", promptHint: "wearing round wire-frame glasses, John Lennon style", term: "round wire-frame glasses" },
91
113
  { id: "eyewear-fashion", label: "Fashion Glasses", dimension: "eyewear", description: "Bold designer frames", promptHint: "wearing bold fashion glasses with designer frames" },
92
- { id: "eyewear-sport", label: "Sport Goggles", dimension: "eyewear", description: "Wraparound sport eyewear", promptHint: "wearing wraparound sport sunglasses" },
114
+ { id: "eyewear-sport", label: "Sport Goggles", dimension: "eyewear", description: "Wraparound sport eyewear", promptHint: "wearing wraparound sport sunglasses", term: "wraparound sport sunglasses" },
93
115
 
94
116
  // -------------------- Headwear --------------------
95
117
  { id: "headwear-beanie", label: "Beanie", dimension: "headwear", description: "Knit beanie hat", promptHint: "wearing a knit beanie" },
@@ -98,7 +120,7 @@ export const STYLINGS: ReadonlyArray<Styling> = [
98
120
  { id: "headwear-sun-hat", label: "Sun Hat", dimension: "headwear", description: "Wide-brimmed sun hat", promptHint: "wearing a wide-brimmed sun hat" },
99
121
  { id: "headwear-headband", label: "Headband", dimension: "headwear", description: "Hair headband", promptHint: "wearing a headband" },
100
122
  { id: "headwear-bandana", label: "Bandana", dimension: "headwear", description: "Bandana tied around head", promptHint: "wearing a bandana tied around the head" },
101
- { id: "headwear-hood", label: "Hood", dimension: "headwear", description: "Hooded sweatshirt up", promptHint: "with a hood pulled up over the head" },
123
+ { id: "headwear-hood", label: "Hood", dimension: "headwear", description: "Hooded sweatshirt up", promptHint: "with a hood pulled up over the head", term: "hood pulled up" },
102
124
  { id: "headwear-crown", label: "Crown", dimension: "headwear", description: "Royal or decorative crown", promptHint: "wearing a crown atop the head" },
103
125
  { id: "headwear-nemes", label: "Nemes Headdress", dimension: "headwear", description: "Striped pharaonic nemes with uraeus", promptHint: "wearing a striped pharaonic nemes headdress with a golden uraeus cobra at the brow" },
104
126
  { id: "headwear-helmet", label: "Helmet", dimension: "headwear", description: "Protective helmet", promptHint: "wearing a helmet" },
@@ -114,22 +136,22 @@ export const STYLINGS: ReadonlyArray<Styling> = [
114
136
  // Natural texture + length live in Person.hair-base. This is what the
115
137
  // stylist did with it. 45 entries — the picker uses a modal browser.
116
138
  // ---- Short cuts ----
117
- { id: "cut-pixie", label: "Pixie", dimension: "hair-cut", description: "Short, cropped pixie cut", promptHint: "styled in a short pixie cut" },
139
+ { id: "cut-pixie", label: "Pixie", dimension: "hair-cut", description: "Short, cropped pixie cut", promptHint: "styled in a short pixie cut", term: "pixie cut" },
118
140
  { id: "cut-buzz-cut", label: "Buzz Cut", dimension: "hair-cut", description: "Very short buzz cut", promptHint: "buzzed short" },
119
141
  { id: "cut-crew-cut", label: "Crew Cut", dimension: "hair-cut", description: "Short tapered classic crew cut", promptHint: "styled in a short tapered crew cut" },
120
- { id: "cut-shaved", label: "Shaved", dimension: "hair-cut", description: "Fully shaved", promptHint: "shaved bald" },
142
+ { id: "cut-shaved", label: "Shaved", dimension: "hair-cut", description: "Fully shaved", promptHint: "shaved bald", term: "shaved head" },
121
143
  { id: "cut-undercut", label: "Undercut", dimension: "hair-cut", description: "Short sides, longer top", promptHint: "with an undercut — short sides and longer hair on top" },
122
144
  { id: "cut-faux-hawk", label: "Faux Hawk", dimension: "hair-cut", description: "Center strip raised, sides faded", promptHint: "styled in a faux hawk with a raised center strip and faded sides" },
123
145
  { id: "cut-mohawk", label: "Mohawk", dimension: "hair-cut", description: "Shaved sides, tall center strip", promptHint: "styled in a mohawk with shaved sides and a tall center strip" },
124
146
  { id: "cut-pompadour", label: "Pompadour", dimension: "hair-cut", description: "Swept up and back, volume in front", promptHint: "styled in a pompadour with hair swept up and back from the forehead" },
125
- { id: "cut-short", label: "Short", dimension: "hair-cut", description: "Generic short cut", promptHint: "cut short" },
147
+ { id: "cut-short", label: "Short", dimension: "hair-cut", description: "Generic short cut", promptHint: "cut short", term: "short haircut" },
126
148
  { id: "cut-short-curly", label: "Short Curly Cut", dimension: "hair-cut", description: "Short layered curly shape", promptHint: "cut short with curls left out" },
127
149
 
128
150
  // ---- Bob family ----
129
151
  { id: "cut-micro-bob", label: "Micro Bob", dimension: "hair-cut", description: "Very short ear-length bob", promptHint: "styled in a very short ear-length bob cut" },
130
152
  { id: "cut-french-bob", label: "French Bob", dimension: "hair-cut", description: "Jaw-length blunt bob, often with bangs", promptHint: "styled in a jaw-length blunt French bob with soft bangs" },
131
- { id: "cut-bob", label: "Bob", dimension: "hair-cut", description: "Chin-length bob cut", promptHint: "styled in a chin-length bob cut" },
132
- { id: "cut-lob", label: "Lob", dimension: "hair-cut", description: "Long bob, collarbone-length", promptHint: "styled in a long bob falling to the collarbone" },
153
+ { id: "cut-bob", label: "Bob", dimension: "hair-cut", description: "Chin-length bob cut", promptHint: "styled in a chin-length bob cut", term: "bob cut" },
154
+ { id: "cut-lob", label: "Lob", dimension: "hair-cut", description: "Long bob, collarbone-length", promptHint: "styled in a long bob falling to the collarbone", term: "long bob" },
133
155
 
134
156
  // ---- Layered / shag family ----
135
157
  { id: "cut-mullet", label: "Mullet", dimension: "hair-cut", description: "Short on top and sides, long in back", promptHint: "styled in a mullet with short top and long back" },
@@ -139,22 +161,22 @@ export const STYLINGS: ReadonlyArray<Styling> = [
139
161
  { id: "cut-bangs", label: "Blunt Bangs", dimension: "hair-cut", description: "Straight bangs across forehead", promptHint: "with blunt bangs across the forehead" },
140
162
  { id: "cut-curtain-bangs", label: "Curtain Bangs", dimension: "hair-cut", description: "Center-parted face-framing bangs", promptHint: "with center-parted curtain bangs framing the face" },
141
163
  { id: "cut-wispy-bangs", label: "Wispy Bangs", dimension: "hair-cut", description: "Thin, piecey, airy fringe", promptHint: "with thin wispy piecey bangs" },
142
- { id: "cut-side-swept", label: "Side-Swept", dimension: "hair-cut", description: "Falls across face over one eye", promptHint: "side-swept across the face with strands falling over one eye" },
164
+ { id: "cut-side-swept", label: "Side-Swept", dimension: "hair-cut", description: "Falls across face over one eye", promptHint: "side-swept across the face with strands falling over one eye", term: "side-swept hair" },
143
165
 
144
166
  // ---- Pulled back / updos ----
145
- { id: "cut-slicked-back", label: "Slicked Back", dimension: "hair-cut", description: "Pulled straight back, polished", promptHint: "slicked straight back with a polished finish" },
146
- { id: "cut-bardot-tendrils", label: "Bardot Tendrils", dimension: "hair-cut", description: "Pulled back with face-framing strands", promptHint: "pulled back with thin face-framing tendrils falling loose at the temples" },
147
- { id: "cut-ponytail", label: "Ponytail (Low)", dimension: "hair-cut", description: "Low pulled-back ponytail", promptHint: "in a low pulled-back ponytail" },
167
+ { id: "cut-slicked-back", label: "Slicked Back", dimension: "hair-cut", description: "Pulled straight back, polished", promptHint: "slicked straight back with a polished finish", term: "slicked-back hair" },
168
+ { id: "cut-bardot-tendrils", label: "Bardot Tendrils", dimension: "hair-cut", description: "Pulled back with face-framing strands", promptHint: "pulled back with thin face-framing tendrils falling loose at the temples", term: "pulled back with face-framing tendrils" },
169
+ { id: "cut-ponytail", label: "Ponytail (Low)", dimension: "hair-cut", description: "Low pulled-back ponytail", promptHint: "in a low pulled-back ponytail", term: "low ponytail" },
148
170
  { id: "cut-high-ponytail", label: "High Ponytail", dimension: "hair-cut", description: "Ponytail tied high on the crown", promptHint: "in a high ponytail tied at the crown" },
149
171
  { id: "cut-half-up", label: "Half-Up Half-Down", dimension: "hair-cut", description: "Top pulled back, rest flows down", promptHint: "in a half-up half-down style, top pulled back with the rest flowing down" },
150
- { id: "cut-bun", label: "Bun", dimension: "hair-cut", description: "Classic low or mid bun", promptHint: "in a bun" },
172
+ { id: "cut-bun", label: "Bun", dimension: "hair-cut", description: "Classic low or mid bun", promptHint: "in a bun", term: "hair bun" },
151
173
  { id: "cut-top-knot", label: "Top Knot", dimension: "hair-cut", description: "Bun tied high on the crown", promptHint: "in a top knot on the crown" },
152
174
  { id: "cut-space-buns", label: "Space Buns", dimension: "hair-cut", description: "Two symmetric buns either side", promptHint: "in two symmetric space buns" },
153
175
 
154
176
  // ---- Braids ----
155
177
  { id: "cut-braids", label: "Braids", dimension: "hair-cut", description: "Multiple loose woven braids", promptHint: "styled in multiple loose braids" },
156
178
  { id: "cut-single-braid", label: "Single Braid", dimension: "hair-cut", description: "One long braid down the back", promptHint: "in a single long braid down the back" },
157
- { id: "cut-two-braids", label: "Two Braids", dimension: "hair-cut", description: "Pigtail braids either side", promptHint: "in two pigtail braids" },
179
+ { id: "cut-two-braids", label: "Two Braids", dimension: "hair-cut", description: "Pigtail braids either side", promptHint: "in two pigtail braids", term: "pigtail braids" },
158
180
  { id: "cut-french-braid", label: "French Braid", dimension: "hair-cut", description: "Woven flat against the scalp", promptHint: "in a French braid woven flat against the scalp" },
159
181
  { id: "cut-dutch-braid", label: "Dutch Braid", dimension: "hair-cut", description: "Inverted raised French braid", promptHint: "in a raised Dutch braid" },
160
182
  { id: "cut-fishtail-braid", label: "Fishtail Braid", dimension: "hair-cut", description: "Fine two-strand weave", promptHint: "in a fishtail braid" },
@@ -167,36 +189,36 @@ export const STYLINGS: ReadonlyArray<Styling> = [
167
189
  { id: "cut-sisterlocks", label: "Sisterlocks", dimension: "hair-cut", description: "Thin, neatly sectioned micro-locs", promptHint: "in thin neat sisterlocks" },
168
190
 
169
191
  // -------------------- Hair State (motion / condition — distinct from cut shape and treatment color) --------------------
170
- { id: "hair-wet", label: "Wet", dimension: "hair-state", description: "Soaked, water-clinging hair", promptHint: "with hair soaked and wet, strands clinging together and dripping water" },
171
- { id: "hair-damp", label: "Damp", dimension: "hair-state", description: "Just-washed damp hair", promptHint: "with hair freshly damp, slightly weighted and softly clumped" },
172
- { id: "hair-messy", label: "Messy", dimension: "hair-state", description: "Disheveled, unkempt", promptHint: "with hair messy and disheveled, unkempt strands going every direction" },
173
- { id: "hair-tousled", label: "Tousled", dimension: "hair-state", description: "Lightly mussed, lived-in", promptHint: "with hair softly tousled and lived-in, casual undone movement" },
174
- { id: "hair-bedhead", label: "Bedhead", dimension: "hair-state", description: "Just-woken-up flattened side", promptHint: "with bedhead — hair flattened on one side, fluffed on the other, like just woken up" },
175
- { id: "hair-windswept", label: "Windswept", dimension: "hair-state", description: "Wind-blown, hair in motion", promptHint: "with hair caught mid-motion in wind, strands streaming sideways across and away from the face" },
176
- { id: "hair-voluminous", label: "Voluminous", dimension: "hair-state", description: "Big, blown-out volume", promptHint: "with hair full of volume, blown out and bouncy with body and lift at the roots" },
177
- { id: "hair-sleek", label: "Sleek", dimension: "hair-state", description: "Glassy smooth straight", promptHint: "with hair sleek and glass-smooth, every strand pressed flat with a polished sheen" },
178
- { id: "hair-frizzy", label: "Frizzy", dimension: "hair-state", description: "Halo of frizz", promptHint: "with hair frizzy, fine flyaway strands forming a soft halo around the head" },
179
- { id: "hair-strands-on-face", label: "Strands on Face", dimension: "hair-state", description: "Loose strands across face", promptHint: "with loose hair strands falling across the face, partially covering one eye" },
180
- { id: "hair-tucked-behind-ear", label: "Tucked Behind Ear", dimension: "hair-state", description: "Pushed behind one ear", promptHint: "with hair pushed back and tucked behind one ear, exposing the side of the face" },
181
- { id: "hair-flowing", label: "Flowing", dimension: "hair-state", description: "Long flowing motion", promptHint: "with hair flowing freely, strands cascading and rippling with subtle movement" },
192
+ { id: "hair-wet", label: "Wet", dimension: "hair-state", description: "Soaked, water-clinging hair", promptHint: "with hair soaked and wet, strands clinging together and dripping water", term: "wet hair" },
193
+ { id: "hair-damp", label: "Damp", dimension: "hair-state", description: "Just-washed damp hair", promptHint: "with hair freshly damp, slightly weighted and softly clumped", term: "damp hair" },
194
+ { id: "hair-messy", label: "Messy", dimension: "hair-state", description: "Disheveled, unkempt", promptHint: "with hair messy and disheveled, unkempt strands going every direction", term: "messy hair" },
195
+ { id: "hair-tousled", label: "Tousled", dimension: "hair-state", description: "Lightly mussed, lived-in", promptHint: "with hair softly tousled and lived-in, casual undone movement", term: "tousled hair" },
196
+ { id: "hair-bedhead", label: "Bedhead", dimension: "hair-state", description: "Just-woken-up flattened side", promptHint: "with bedhead — hair flattened on one side, fluffed on the other, like just woken up", term: "bedhead hair" },
197
+ { id: "hair-windswept", label: "Windswept", dimension: "hair-state", description: "Wind-blown, hair in motion", promptHint: "with hair caught mid-motion in wind, strands streaming sideways across and away from the face", term: "windswept hair" },
198
+ { id: "hair-voluminous", label: "Voluminous", dimension: "hair-state", description: "Big, blown-out volume", promptHint: "with hair full of volume, blown out and bouncy with body and lift at the roots", term: "voluminous blown-out hair" },
199
+ { id: "hair-sleek", label: "Sleek", dimension: "hair-state", description: "Glassy smooth straight", promptHint: "with hair sleek and glass-smooth, every strand pressed flat with a polished sheen", term: "sleek straight hair" },
200
+ { id: "hair-frizzy", label: "Frizzy", dimension: "hair-state", description: "Halo of frizz", promptHint: "with hair frizzy, fine flyaway strands forming a soft halo around the head", term: "frizzy hair" },
201
+ { id: "hair-strands-on-face", label: "Strands on Face", dimension: "hair-state", description: "Loose strands across face", promptHint: "with loose hair strands falling across the face, partially covering one eye", term: "loose strands across the face" },
202
+ { id: "hair-tucked-behind-ear", label: "Tucked Behind Ear", dimension: "hair-state", description: "Pushed behind one ear", promptHint: "with hair pushed back and tucked behind one ear, exposing the side of the face", term: "hair tucked behind one ear" },
203
+ { id: "hair-flowing", label: "Flowing", dimension: "hair-state", description: "Long flowing motion", promptHint: "with hair flowing freely, strands cascading and rippling with subtle movement", term: "flowing hair" },
182
204
 
183
205
  // -------------------- Hair Treatment (salon coloring techniques) --------------------
184
206
  { id: "treatment-babylights", label: "Babylights", dimension: "hair-treatment", description: "Ultra-fine, delicate highlights", promptHint: "with babylights — ultra-fine, delicate highlights that mimic natural sun-kissed hair" },
185
207
  { id: "treatment-balayage", label: "Balayage", dimension: "hair-treatment", description: "Hand-painted highlights", promptHint: "with balayage — hand-painted highlights with a soft, sun-kissed gradient" },
186
208
  { id: "treatment-ombre", label: "Ombré", dimension: "hair-treatment", description: "Gradient dark roots to light ends", promptHint: "with ombré — a smooth gradient from dark roots to lighter ends" },
187
209
  { id: "treatment-sombre", label: "Sombré", dimension: "hair-treatment", description: "Subtle soft ombré", promptHint: "with sombré — a subtle, soft ombré gradient" },
188
- { id: "treatment-highlights", label: "Highlights", dimension: "hair-treatment", description: "Classic foil highlights", promptHint: "with highlights — lighter streaks throughout the hair" },
189
- { id: "treatment-lowlights", label: "Lowlights", dimension: "hair-treatment", description: "Darker shade streaks", promptHint: "with lowlights — darker streaks woven through for dimension" },
190
- { id: "treatment-rooted", label: "Rooted", dimension: "hair-treatment", description: "Visible root regrowth look", promptHint: "with a rooted look — visible darker roots blending into lighter lengths" },
210
+ { id: "treatment-highlights", label: "Highlights", dimension: "hair-treatment", description: "Classic foil highlights", promptHint: "with highlights — lighter streaks throughout the hair", term: "hair highlights" },
211
+ { id: "treatment-lowlights", label: "Lowlights", dimension: "hair-treatment", description: "Darker shade streaks", promptHint: "with lowlights — darker streaks woven through for dimension", term: "hair lowlights" },
212
+ { id: "treatment-rooted", label: "Rooted", dimension: "hair-treatment", description: "Visible root regrowth look", promptHint: "with a rooted look — visible darker roots blending into lighter lengths", term: "rooted color with dark roots" },
191
213
 
192
214
  // -------------------- Jewelry --------------------
193
- { id: "jewelry-subtle", label: "Subtle", dimension: "jewelry", description: "Minimal, delicate jewelry", promptHint: "wearing subtle, minimal jewelry" },
194
- { id: "jewelry-statement", label: "Statement", dimension: "jewelry", description: "Bold statement piece", promptHint: "wearing a bold statement jewelry piece" },
195
- { id: "jewelry-gold", label: "Gold", dimension: "jewelry", description: "Gold necklace + earrings", promptHint: "wearing gold jewelry — a necklace and earrings" },
196
- { id: "jewelry-silver", label: "Silver", dimension: "jewelry", description: "Silver necklace + earrings", promptHint: "wearing silver jewelry — a necklace and earrings" },
197
- { id: "jewelry-layered", label: "Layered", dimension: "jewelry", description: "Layered necklaces / chains", promptHint: "wearing layered necklaces of varying lengths" },
198
- { id: "jewelry-pearl", label: "Pearl", dimension: "jewelry", description: "Pearl necklace / earrings", promptHint: "wearing a pearl necklace and pearl earrings" },
199
- { id: "jewelry-chunky", label: "Chunky", dimension: "jewelry", description: "Chunky chains and rings", promptHint: "wearing chunky chains and oversized rings" },
215
+ { id: "jewelry-subtle", label: "Subtle", dimension: "jewelry", description: "Minimal, delicate jewelry", promptHint: "wearing subtle, minimal jewelry", term: "delicate minimal jewelry" },
216
+ { id: "jewelry-statement", label: "Statement", dimension: "jewelry", description: "Bold statement piece", promptHint: "wearing a bold statement jewelry piece", term: "statement jewelry" },
217
+ { id: "jewelry-gold", label: "Gold", dimension: "jewelry", description: "Gold necklace + earrings", promptHint: "wearing gold jewelry — a necklace and earrings", term: "gold jewelry" },
218
+ { id: "jewelry-silver", label: "Silver", dimension: "jewelry", description: "Silver necklace + earrings", promptHint: "wearing silver jewelry — a necklace and earrings", term: "silver jewelry" },
219
+ { id: "jewelry-layered", label: "Layered", dimension: "jewelry", description: "Layered necklaces / chains", promptHint: "wearing layered necklaces of varying lengths", term: "layered necklaces" },
220
+ { id: "jewelry-pearl", label: "Pearl", dimension: "jewelry", description: "Pearl necklace / earrings", promptHint: "wearing a pearl necklace and pearl earrings", term: "pearl jewelry" },
221
+ { id: "jewelry-chunky", label: "Chunky", dimension: "jewelry", description: "Chunky chains and rings", promptHint: "wearing chunky chains and oversized rings", term: "chunky chains and rings" },
200
222
  { 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" },
201
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" },
202
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" },
@@ -208,16 +230,16 @@ export const STYLINGS: ReadonlyArray<Styling> = [
208
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" },
209
231
 
210
232
  // -------------------- Nails --------------------
211
- { id: "nails-polished", label: "Polished", dimension: "nails", description: "Clear or nude polished nails", promptHint: "with polished nude nails" },
212
- { id: "nails-red", label: "Red", dimension: "nails", description: "Bright red nail polish", promptHint: "with bright red nail polish" },
213
- { id: "nails-dark", label: "Dark", dimension: "nails", description: "Dark / black nails", promptHint: "with dark black nail polish" },
214
- { id: "nails-long-acrylic", label: "Long Acrylic", dimension: "nails", description: "Long acrylic nails", promptHint: "with long acrylic nails" },
215
- { id: "nails-french", label: "French Tips", dimension: "nails", description: "Classic French manicure", promptHint: "with classic French tip manicure" },
233
+ { id: "nails-polished", label: "Polished", dimension: "nails", description: "Clear or nude polished nails", promptHint: "with polished nude nails", term: "polished nude nails" },
234
+ { id: "nails-red", label: "Red", dimension: "nails", description: "Bright red nail polish", promptHint: "with bright red nail polish", term: "red nail polish" },
235
+ { id: "nails-dark", label: "Dark", dimension: "nails", description: "Dark / black nails", promptHint: "with dark black nail polish", term: "black nail polish" },
236
+ { id: "nails-long-acrylic", label: "Long Acrylic", dimension: "nails", description: "Long acrylic nails", promptHint: "with long acrylic nails", term: "long acrylic nails" },
237
+ { id: "nails-french", label: "French Tips", dimension: "nails", description: "Classic French manicure", promptHint: "with classic French tip manicure", term: "french tip manicure" },
216
238
 
217
239
  // -------------------- Face Paint --------------------
218
240
  { id: "face-paint-subtle", label: "Subtle Body Paint", dimension: "face-paint", description: "Subtle body / face paint accents", promptHint: "with subtle body paint accents on the skin" },
219
241
  { id: "face-paint-dramatic", label: "Dramatic Face Paint", dimension: "face-paint", description: "Bold artistic face paint", promptHint: "with dramatic, artistic face paint covering portions of the face" },
220
- { id: "face-paint-costume", label: "Costume Paint", dimension: "face-paint", description: "Cosplay / theatrical paint", promptHint: "with theatrical costume face paint" },
242
+ { id: "face-paint-costume", label: "Costume Paint", dimension: "face-paint", description: "Cosplay / theatrical paint", promptHint: "with theatrical costume face paint", term: "theatrical costume face paint" },
221
243
  { id: "face-paint-tribal", label: "Tribal Markings", dimension: "face-paint", description: "Tribal-inspired face markings", promptHint: "with tribal-inspired face markings" },
222
244
  { id: "face-paint-flag", label: "Flag Face Paint", dimension: "face-paint", description: "National flag painted on cheeks, sports-fan", promptHint: "with a national flag painted on the cheeks, sports-fan face paint" },
223
245
  { id: "face-paint-warpaint", label: "War Paint", dimension: "face-paint", description: "Warrior war paint streaks", promptHint: "with warrior war paint streaked across the face" },
@@ -233,13 +255,13 @@ export const STYLINGS: ReadonlyArray<Styling> = [
233
255
  { id: "fabric-leather", label: "Leather", dimension: "fabric", description: "Rich supple leather", promptHint: "wearing rich supple leather with a soft satin sheen and natural grain" },
234
256
  { id: "fabric-velvet", label: "Velvet", dimension: "fabric", description: "Plush velvet", promptHint: "wearing plush velvet with a deep soft nap and rich saturated color" },
235
257
  { id: "fabric-satin", label: "Satin", dimension: "fabric", description: "Glossy satin", promptHint: "wearing glossy satin with a lustrous mirror-like surface and fluid folds" },
236
- { id: "fabric-lace", label: "Lace", dimension: "fabric", description: "Delicate patterned lace", promptHint: "wearing delicate patterned lace with intricate floral openwork" },
258
+ { id: "fabric-lace", label: "Lace", dimension: "fabric", description: "Delicate patterned lace", promptHint: "wearing delicate patterned lace with intricate floral openwork", term: "delicate patterned lace" },
237
259
  { id: "fabric-wool", label: "Wool", dimension: "fabric", description: "Warm woven wool", promptHint: "wearing warm woven wool with a soft matte surface and visible fibers" },
238
260
  { id: "fabric-linen", label: "Linen", dimension: "fabric", description: "Natural textured linen", promptHint: "wearing natural linen with a loose open weave, slight slubs and an airy drape" },
239
261
  { id: "fabric-tweed", label: "Tweed", dimension: "fabric", description: "Rustic woven tweed", promptHint: "wearing rustic tweed with a flecked multi-color woven texture and a heritage feel" },
240
262
  { id: "fabric-cashmere", label: "Cashmere", dimension: "fabric", description: "Luxurious soft cashmere", promptHint: "wearing luxurious cashmere with an ultra-soft matte texture" },
241
263
  { id: "fabric-chiffon", label: "Chiffon", dimension: "fabric", description: "Sheer flowing chiffon", promptHint: "wearing sheer chiffon with a lightweight floating drape and soft translucent layers" },
242
- { id: "fabric-fur", label: "Fur", dimension: "fabric", description: "Thick plush fur", promptHint: "wearing thick plush fur with long dense strands and natural variation" },
264
+ { 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" },
243
265
  { id: "fabric-sequins", label: "Sequins", dimension: "fabric", description: "Sparkling sequin fabric", promptHint: "wearing sparkling sequined fabric catching light with countless tiny reflective facets" },
244
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" },
245
267
 
@@ -250,7 +272,7 @@ export const STYLINGS: ReadonlyArray<Styling> = [
250
272
  { 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" },
251
273
  { id: "outfit-wedding-dress", label: "Wedding Dress", dimension: "outfit", description: "White wedding gown", promptHint: "dressed in a white wedding gown with intricate lace detailing and a sweeping train" },
252
274
  { id: "outfit-cocktail-dress", label: "Cocktail Dress", dimension: "outfit", description: "Knee-length cocktail dress", promptHint: "dressed in a sleek knee-length cocktail dress" },
253
- { id: "outfit-sundress", label: "Sundress / Maxi", dimension: "outfit", description: "Flowy halter-neck sundress / patterned maxi", promptHint: "dressed in a flowy halter-neck sundress with a patterned maxi-length skirt" },
275
+ { id: "outfit-sundress", label: "Sundress / Maxi", dimension: "outfit", description: "Flowy halter-neck sundress / patterned maxi", promptHint: "dressed in a flowy halter-neck sundress with a patterned maxi-length skirt", term: "flowy maxi sundress" },
254
276
  { id: "outfit-lab-coat", label: "Lab Coat", dimension: "outfit", description: "White medical lab coat", promptHint: "wearing a crisp white lab coat layered over the rest of the clothing" },
255
277
  { id: "outfit-scrubs", label: "Medical Scrubs", dimension: "outfit", description: "Solid-color medical scrubs", promptHint: "dressed in solid-color medical scrubs — loose-fit top and matching drawstring pants" },
256
278
  { id: "outfit-military", label: "Military Fatigues", dimension: "outfit", description: "Olive-drab military fatigues", promptHint: "dressed in olive-drab military fatigues with utility pockets and a sturdy belt" },
@@ -264,19 +286,19 @@ export const STYLINGS: ReadonlyArray<Styling> = [
264
286
  { 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" },
265
287
  { id: "outfit-bathrobe", label: "Bathrobe", dimension: "outfit", description: "Plush terrycloth robe", promptHint: "wrapped in a plush terrycloth bathrobe tied at the waist" },
266
288
  { id: "outfit-kimono", label: "Kimono", dimension: "outfit", description: "Traditional Japanese kimono", promptHint: "dressed in a traditional Japanese kimono with an obi sash" },
267
- { id: "outfit-ballerina", label: "Ballerina", dimension: "outfit", description: "Classical ballerina costume", promptHint: "dressed in a classical ballerina costume — fitted leotard and tulle tutu" },
268
- { id: "outfit-flapper", label: "1920s Flapper", dimension: "outfit", description: "Beaded flapper dress", promptHint: "dressed in a 1920s flapper dress — beaded fringe and a low-drop hem" },
269
- { id: "outfit-goth", label: "Goth", dimension: "outfit", description: "Full dark goth: black lace, leather, dark makeup, chains", promptHint: "wearing a full goth outfit — black lace and leather layered together, chains and silver hardware, with dark dramatic makeup" },
270
- { id: "outfit-emo", label: "Emo", dimension: "outfit", description: "Early-2000s emo: skinny jeans, band tee, dyed hair, eyeliner", promptHint: "wearing an early-2000s emo outfit — skinny black jeans and a band tee, with dyed side-swept hair and heavy eyeliner" },
289
+ { id: "outfit-ballerina", label: "Ballerina", dimension: "outfit", description: "Classical ballerina costume", promptHint: "dressed in a classical ballerina costume — fitted leotard and tulle tutu", term: "ballerina leotard and tutu" },
290
+ { id: "outfit-flapper", label: "1920s Flapper", dimension: "outfit", description: "Beaded flapper dress", promptHint: "dressed in a 1920s flapper dress — beaded fringe and a low-drop hem", term: "1920s flapper dress" },
291
+ { id: "outfit-goth", label: "Goth", dimension: "outfit", description: "Full dark goth: black lace, leather, dark makeup, chains", promptHint: "wearing a full goth outfit — black lace and leather layered together, chains and silver hardware, with dark dramatic makeup", term: "goth outfit" },
292
+ { id: "outfit-emo", label: "Emo", dimension: "outfit", description: "Early-2000s emo: skinny jeans, band tee, dyed hair, eyeliner", promptHint: "wearing an early-2000s emo outfit — skinny black jeans and a band tee, with dyed side-swept hair and heavy eyeliner", term: "emo outfit" },
271
293
  { id: "outfit-streetwear", label: "Streetwear", dimension: "outfit", description: "Modern streetwear: hoodie, baggy pants, sneakers, bucket hat", promptHint: "wearing a modern streetwear outfit — an oversized hoodie, baggy pants, chunky sneakers, and a bucket hat" },
272
- { id: "outfit-cyberpunk", label: "Cyberpunk", dimension: "outfit", description: "Neon-lit cyberpunk: tech wear, LED accents, mesh layers", promptHint: "wearing a cyberpunk outfit — neon-lit techwear with LED accents, mesh layers, and a futuristic urban silhouette" },
273
- { id: "outfit-cottagecore", label: "Cottagecore", dimension: "outfit", description: "Pastoral cottage aesthetic: prairie dress, apron, basket", promptHint: "wearing a cottagecore outfit — a long pastoral prairie dress with an apron and a woven basket on the arm" },
274
- { id: "outfit-dark-academia", label: "Dark Academia", dimension: "outfit", description: "Oxford-tweed scholar: blazer, vest, oxford shoes, books", promptHint: "wearing a dark academia outfit — a tweed blazer over a knit vest, oxford shoes, with books tucked under the arm" },
294
+ { id: "outfit-cyberpunk", label: "Cyberpunk", dimension: "outfit", description: "Neon-lit cyberpunk: tech wear, LED accents, mesh layers", promptHint: "wearing a cyberpunk outfit — neon-lit techwear with LED accents, mesh layers, and a futuristic urban silhouette", term: "cyberpunk techwear outfit" },
295
+ { id: "outfit-cottagecore", label: "Cottagecore", dimension: "outfit", description: "Pastoral cottage aesthetic: prairie dress, apron, basket", promptHint: "wearing a cottagecore outfit — a long pastoral prairie dress with an apron and a woven basket on the arm", term: "cottagecore prairie dress" },
296
+ { id: "outfit-dark-academia", label: "Dark Academia", dimension: "outfit", description: "Oxford-tweed scholar: blazer, vest, oxford shoes, books", promptHint: "wearing a dark academia outfit — a tweed blazer over a knit vest, oxford shoes, with books tucked under the arm", term: "dark academia outfit" },
275
297
  { id: "outfit-anime-cosplay", label: "Anime Cosplay", dimension: "outfit", description: "Costume modeled on a recognizable anime character archetype", promptHint: "wearing an anime cosplay costume modeled on a recognizable anime character archetype, with stylized details and bold colors" },
276
- { id: "outfit-nurse", label: "Nurse", dimension: "outfit", description: "Medical scrubs, stethoscope, lanyard ID", promptHint: "dressed as a nurse — fitted medical scrubs with a stethoscope around the neck and a lanyard ID badge" },
277
- { id: "outfit-pirate", label: "Pirate", dimension: "outfit", description: "Pirate costume: tricorn hat, sash, frilly shirt, boots", promptHint: "wearing a pirate costume — a tricorn hat, a frilly shirt with a sash at the waist, and tall leather boots" },
278
- { 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" },
279
- { 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" },
298
+ { id: "outfit-nurse", label: "Nurse", dimension: "outfit", description: "Medical scrubs, stethoscope, lanyard ID", promptHint: "dressed as a nurse — fitted medical scrubs with a stethoscope around the neck and a lanyard ID badge", term: "nurse uniform with stethoscope" },
299
+ { id: "outfit-pirate", label: "Pirate", dimension: "outfit", description: "Pirate costume: tricorn hat, sash, frilly shirt, boots", promptHint: "wearing a pirate costume — a tricorn hat, a frilly shirt with a sash at the waist, and tall leather boots", term: "pirate costume" },
300
+ { 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
+ { 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" },
280
302
  { 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" },
281
303
 
282
304
  // -------------------- Top (upper-body garment) --------------------
@@ -287,7 +309,7 @@ export const STYLINGS: ReadonlyArray<Styling> = [
287
309
  { id: "top-sweater", label: "Sweater", dimension: "top", description: "Knit pullover sweater", promptHint: "wearing a soft knit pullover sweater with a relaxed fit" },
288
310
  { id: "top-turtleneck", label: "Turtleneck", dimension: "top", description: "High-neck turtleneck", promptHint: "wearing a fitted turtleneck sweater with a high rolled neckline" },
289
311
  { id: "top-blouse", label: "Blouse", dimension: "top", description: "Flowing dressy blouse", promptHint: "wearing a flowing blouse with a soft drape and a tied neckline" },
290
- { id: "top-button-down", label: "Button-Down", dimension: "top", description: "Tailored button-down shirt", promptHint: "wearing a tailored button-down shirt with a sharp pointed collar" },
312
+ { id: "top-button-down", label: "Button-Down", dimension: "top", description: "Tailored button-down shirt", promptHint: "wearing a tailored button-down shirt with a sharp pointed collar", term: "button-down shirt" },
291
313
  { 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" },
292
314
  { id: "top-tube-top", label: "Tube Top", dimension: "top", description: "Strapless tube top", promptHint: "wearing a strapless tube top fitted across the chest" },
293
315
  { id: "top-camisole", label: "Camisole", dimension: "top", description: "Thin satin cami", promptHint: "wearing a thin satin camisole with delicate spaghetti straps" },
@@ -343,7 +365,7 @@ export const STYLINGS: ReadonlyArray<Styling> = [
343
365
  { id: "legwear-bare", label: "Bare Legs", dimension: "legwear", description: "Bare legs, no hosiery", promptHint: "with bare legs, no hosiery" },
344
366
  { id: "legwear-sheer-tights", label: "Sheer Tights", dimension: "legwear", description: "Sheer nude pantyhose", promptHint: "with sheer nude pantyhose" },
345
367
  { id: "legwear-opaque-tights", label: "Opaque Tights", dimension: "legwear", description: "Solid opaque tights", promptHint: "with opaque black tights covering the legs" },
346
- { id: "legwear-fishnets", label: "Fishnets", dimension: "legwear", description: "Diamond-mesh fishnets", promptHint: "with fishnet stockings, the skin showing through the diamond mesh" },
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" },
347
369
  { id: "legwear-thigh-highs", label: "Thigh-High Stockings", dimension: "legwear", description: "Thigh-high stockings", promptHint: "with thigh-high stockings stopping mid-thigh" },
348
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" },
349
371
  { id: "legwear-knee-highs", label: "Knee-High Socks", dimension: "legwear", description: "Knee-high socks", promptHint: "with knee-high socks pulled up the calf" },
@@ -367,34 +389,34 @@ export const STYLINGS: ReadonlyArray<Styling> = [
367
389
  { id: "footwear-loafers", label: "Loafers", dimension: "footwear", description: "Polished penny loafers", promptHint: "in polished leather penny loafers" },
368
390
  { id: "footwear-oxfords", label: "Oxfords", dimension: "footwear", description: "Laced leather oxfords", promptHint: "in laced leather oxford shoes with a polished finish" },
369
391
  { id: "footwear-sandals", label: "Sandals", dimension: "footwear", description: "Flat strappy sandals", promptHint: "in flat strappy sandals" },
370
- { id: "footwear-mules", label: "Mules", dimension: "footwear", description: "Backless slip-on mules", promptHint: "in slip-on backless mules" },
392
+ { id: "footwear-mules", label: "Mules", dimension: "footwear", description: "Backless slip-on mules", promptHint: "in slip-on backless mules", term: "backless slip-on mules" },
371
393
  { id: "footwear-flip-flops", label: "Flip-Flops", dimension: "footwear", description: "Casual rubber flip-flops", promptHint: "in casual rubber flip-flops" },
372
394
  { id: "footwear-ballet-flats", label: "Ballet Flats", dimension: "footwear", description: "Slip-on ballet flats", promptHint: "in slip-on ballet flats with a soft rounded toe" },
373
395
  { id: "footwear-barefoot", label: "Barefoot", dimension: "footwear", description: "No shoes — barefoot", promptHint: "barefoot, no shoes" },
374
- { id: "footwear-crocs", label: "Crocs", dimension: "footwear", description: "Colorful resin clogs, casual and divisive", promptHint: "in colorful resin Crocs clogs with the signature perforated upper" },
396
+ { id: "footwear-crocs", label: "Crocs", dimension: "footwear", description: "Colorful resin clogs, casual and divisive", promptHint: "in colorful resin Crocs clogs with the signature perforated upper", term: "crocs resin clogs" },
375
397
  { id: "footwear-birkenstocks", label: "Birkenstocks", dimension: "footwear", description: "Cork-footbed sandals", promptHint: "in cork-footbed Birkenstock sandals with twin leather straps" },
376
398
  { id: "footwear-doc-martens", label: "Doc Martens", dimension: "footwear", description: "Chunky leather combat boots", promptHint: "in chunky leather Doc Martens boots with yellow welt stitching and an air-cushioned sole" },
377
- { id: "footwear-converse", label: "Converse", dimension: "footwear", description: "Canvas high-top sneakers, classic Chuck Taylor", promptHint: "in classic Converse Chuck Taylor canvas high-top sneakers with a rubber toe cap" },
378
- { id: "footwear-vans", label: "Vans", dimension: "footwear", description: "Slip-on or low-top skater sneakers", promptHint: "in classic Vans skater sneakers with the signature waffle sole" },
399
+ { id: "footwear-converse", label: "Converse", dimension: "footwear", description: "Canvas high-top sneakers, classic Chuck Taylor", promptHint: "in classic Converse Chuck Taylor canvas high-top sneakers with a rubber toe cap", term: "converse chuck taylor sneakers" },
400
+ { id: "footwear-vans", label: "Vans", dimension: "footwear", description: "Slip-on or low-top skater sneakers", promptHint: "in classic Vans skater sneakers with the signature waffle sole", term: "vans skater sneakers" },
379
401
  { 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" },
380
402
 
381
403
  // -------------------- Wardrobe State (modifier — composes with any garment) --------------------
382
- { id: "state-oversized", label: "Oversized", dimension: "wardrobe-state", description: "Loose, oversized fit", promptHint: "the clothing loose and oversized, draping freely well past the body" },
383
- { 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" },
384
- { id: "state-cropped", label: "Cropped", dimension: "wardrobe-state", description: "Top cropped at midriff", promptHint: "the top cropped above the midriff with the stomach visible" },
385
- { id: "state-sheer", label: "Sheer", dimension: "wardrobe-state", description: "Translucent fabric", promptHint: "the fabric translucent and sheer, hinting at the silhouette underneath" },
386
- { 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" },
387
- { id: "state-ripped", label: "Ripped", dimension: "wardrobe-state", description: "Torn and frayed", promptHint: "the fabric torn and ripped at the seams, with frayed edges" },
388
- { 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" },
389
- { 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" },
390
- { id: "state-tucked-in", label: "Tucked In", dimension: "wardrobe-state", description: "Top tucked into bottom", promptHint: "the top neatly tucked into the bottom" },
391
- { 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" },
392
- { 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" },
393
- { 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" },
404
+ { 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" },
409
+ { 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
+ { 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
+ { 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
+ { 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
+ { 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" },
394
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" },
395
- { id: "state-unbuttoned", label: "Unbuttoned", dimension: "wardrobe-state", description: "Open / unbuttoned", promptHint: "the outerwear hanging open and unbuttoned, falling loose at the sides" },
396
- { id: "state-rolled-sleeves", label: "Rolled Sleeves", dimension: "wardrobe-state", description: "Sleeves rolled up forearm", promptHint: "the sleeves rolled up to the forearm" },
397
- { 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" },
417
+ { 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
+ { 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
+ { 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" },
398
420
  ] as const
399
421
 
400
422
  export const STYLING_DIMENSION_ORDER: ReadonlyArray<StylingDimension> = [
@@ -558,15 +580,33 @@ export function getStylingPromptHint(id: string | undefined | null): string {
558
580
  return getStyling(id)?.promptHint ?? ""
559
581
  }
560
582
 
583
+ /**
584
+ * The COMPACT counterpart of `getStylingPromptHint`: the same lookup, same
585
+ * arity, same empty-string-on-miss, returning the entry's short professional
586
+ * term ("wet hair", "gold jewelry", "low ponytail") instead of its full
587
+ * mechanism clause.
588
+ */
589
+ export function getStylingTerm(id: string | undefined | null): string {
590
+ return resolveTerm(getStyling(id))
591
+ }
592
+
561
593
  export const STYLING_IDS: ReadonlyArray<string> = STYLINGS.map((s) => s.id)
562
594
 
563
- export function buildStylingHints(
595
+ /**
596
+ * The one walk BOTH `buildStylingHints` (verbose) and `buildStylingTerms`
597
+ * (compact) delegate to: free-text pre-field, every dimension in canonical
598
+ * order, free-text post-field. `fragmentFor` is the only difference between
599
+ * the two modes, so they can never disagree about WHICH ids contribute or in
600
+ * what order — only about how each one is phrased.
601
+ */
602
+ function collectStylingFragments(
564
603
  data: Record<string, unknown> & StylingValue,
604
+ fragmentFor: (id: string) => string,
565
605
  ): string[] {
566
- const hints: string[] = []
606
+ const out: string[] = []
567
607
 
568
608
  const pre = typeof data.preText === "string" ? data.preText.trim() : ""
569
- if (pre) hints.push(pre)
609
+ if (pre) out.push(pre)
570
610
 
571
611
  // The Person catalog's `lip-state-bold-red` and this catalog's
572
612
  // `makeup-bold-lips` say the same thing; consumers that fold BOTH pickers
@@ -584,21 +624,42 @@ export function buildStylingHints(
584
624
  const raw = data[field]
585
625
  if (dimension === "makeup" && boldRedAlready && raw === "makeup-bold-lips") continue
586
626
  // jewelry / wardrobe-state / hair-state are multi-pick (string | string[]);
587
- // emit each id's hint independently and let the comma-join compose.
627
+ // emit each id's fragment independently and let the comma-join compose.
588
628
  if (typeof raw === "string" && raw.length > 0) {
589
- const hint = getStylingPromptHint(raw)
590
- if (hint) hints.push(hint)
629
+ const fragment = fragmentFor(raw)
630
+ if (fragment) out.push(fragment)
591
631
  } else if (Array.isArray(raw)) {
592
632
  for (const item of raw) {
593
633
  if (typeof item !== "string" || item.length === 0) continue
594
- const hint = getStylingPromptHint(item)
595
- if (hint) hints.push(hint)
634
+ const fragment = fragmentFor(item)
635
+ if (fragment) out.push(fragment)
596
636
  }
597
637
  }
598
638
  }
599
639
 
600
640
  const post = typeof data.postText === "string" ? data.postText.trim() : ""
601
- if (post) hints.push(post)
641
+ if (post) out.push(post)
642
+
643
+ return out
644
+ }
602
645
 
603
- return hints
646
+ export function buildStylingHints(
647
+ data: Record<string, unknown> & StylingValue,
648
+ mode: PickerHintMode = "full",
649
+ ): string[] {
650
+ if (mode === "compact") return buildStylingTerms(data)
651
+ return collectStylingFragments(data, getStylingPromptHint)
652
+ }
653
+
654
+ /**
655
+ * The COMPACT counterpart of `buildStylingHints`: the same dimension walk in
656
+ * the same canonical order, under the same `makeup-bold-lips` dedupe, emitting
657
+ * each selection's short professional term ("wet hair", "leather jacket",
658
+ * "gold jewelry") instead of its full mechanism clause. Free-text pre/post
659
+ * fields are user prose and pass through unchanged in both modes.
660
+ */
661
+ export function buildStylingTerms(
662
+ data: Record<string, unknown> & StylingValue,
663
+ ): string[] {
664
+ return collectStylingFragments(data, getStylingTerm)
604
665
  }