#!/usr/bin/env python3
"""Generate sxcpinup_coloredpencil prompt batches for gradual image creation."""

from __future__ import annotations

import argparse
import json
import random
import re
import shlex
import sys
from pathlib import Path


TRIGGER = "sxcpinup_coloredpencil"
BATCH_SIZE = 200
DEFAULT_TOTAL = 600
DEFAULT_START_INDEX = 41
DEFAULT_RNG_SEED = 20260614
# Kept intentionally minimal on purpose: extra terms can trip image-model
# content guardrails even when used as negatives.
NEGATIVE_PROMPT = (
    "minors, childlike appearance, watermark"
)

# Facial expressions are drawn from a deck seeded independently from the main
# generator RNG so the expression axis can be expanded without disturbing the
# other category draws.
EXPRESSION_SEED = 90210


# Appearance pool is explicitly adult and uses heritage cues in the skin field
# so the image model renders demographic variety reliably rather than defaulting
# ambiguous skin+hair combos. Keep "African" on Black/African-diaspora women
# entries so --no-black can filter them; keep East/Southeast/South/Central Asian labels
# so --ethnicity asian catches regional and mixed-heritage entries.
YOUNG_WOMEN = [
    # White / European
    ("woman", "21-year-old adult", "slim", "fair Scandinavian skin", "long light-blonde waves", "ice blue eyes"),
    ("woman", "23-year-old adult", "slim", "fair rosy skin", "long platinum-blonde waves", "pale blue eyes"),
    ("woman", "21-year-old adult", "petite adult", "fair freckled skin", "strawberry-blonde bob", "green eyes"),
    ("woman", "25-year-old adult", "curvy athletic", "fair skin", "copper pixie cut", "green eyes"),
    ("woman", "23-year-old adult", "hourglass", "fair skin", "fiery red waves", "emerald green eyes"),
    ("woman", "21-year-old adult", "petite adult", "warm ivory skin", "short wavy ginger hair", "blue-green eyes"),
    # Mediterranean / Latina / Middle-Eastern
    ("woman", "22-year-old adult", "curvy", "olive Mediterranean skin", "dark blonde messy bun", "light hazel eyes"),
    ("woman", "24-year-old adult", "athletic", "olive skin", "silver-highlighted brunette hair", "hazel eyes"),
    ("woman", "25-year-old adult", "hourglass", "sun-kissed Latina skin", "long chocolate-brown waves", "hazel-green eyes"),
    ("woman", "22-year-old adult", "curvy", "warm caramel Latina skin", "wavy auburn lob", "amber eyes"),
    ("woman", "21-year-old adult", "toned", "tan skin", "high messy bun with loose strands", "light brown eyes"),
    ("woman", "24-year-old adult", "busty curvy", "warm Middle-Eastern olive skin", "voluminous dark curls", "deep brown eyes"),
    ("woman", "25-year-old adult", "athletic", "bronze skin", "sleek high ponytail", "green-hazel eyes"),
    # East Asian
    ("woman", "21-year-old adult", "slim", "fair East Asian skin", "long straight black hair", "dark almond eyes"),
    ("woman", "24-year-old adult", "petite adult", "light East Asian skin", "sleek black bob", "dark almond eyes"),
    ("woman", "23-year-old adult", "slim athletic", "porcelain East Asian skin", "glossy black ponytail", "dark brown eyes"),
    ("woman", "25-year-old adult", "slim", "warm East Asian skin", "shoulder-length black hair with curtain bangs", "soft brown eyes"),
    # Southeast Asian
    ("woman", "22-year-old adult", "average", "warm Southeast Asian tan skin", "long wavy black hair", "dark brown eyes"),
    ("woman", "23-year-old adult", "curvy", "golden Southeast Asian skin", "black hair in a high bun", "deep brown eyes"),
    # South Asian
    ("woman", "24-year-old adult", "curvy", "deep South Asian brown skin", "long glossy black hair", "dark brown eyes"),
    ("woman", "22-year-old adult", "slim busty", "warm South Asian brown skin", "thick dark waves", "amber eyes"),
    # Black / African
    ("woman", "21-year-old adult", "athletic curvy", "deep brown African skin", "braided black hair", "hazel eyes"),
    ("woman", "24-year-old adult", "plus-size", "rich dark African skin", "long locs with copper tips", "golden brown eyes"),
    ("woman", "23-year-old adult", "average", "warm brown African skin", "natural afro", "dark brown eyes"),
    ("woman", "25-year-old adult", "average", "deep brown African skin", "short copper-curl twists", "dark eyes"),
    # Mixed / ambiguous
    ("woman", "22-year-old adult", "plus-size", "warm brown skin", "short copper curls", "green eyes"),
    # Extended (balanced additions)
    ("woman", "23-year-old adult", "slim", "fair skin", "long chestnut waves", "blue eyes"),
    ("woman", "24-year-old adult", "curvy", "warm caramel Latina skin", "long dark waves", "brown eyes"),
    ("woman", "21-year-old adult", "petite adult", "light East Asian skin", "long black hair with blunt bangs", "dark brown eyes"),
    ("woman", "25-year-old adult", "athletic", "warm Southeast Asian tan skin", "black hair in a ponytail", "dark brown eyes"),
    ("woman", "22-year-old adult", "hourglass", "warm South Asian brown skin", "long glossy black waves", "deep brown eyes"),
    ("woman", "24-year-old adult", "curvy athletic", "deep brown African skin", "voluminous natural curls", "dark brown eyes"),
    ("woman", "23-year-old adult", "slim busty", "light olive skin", "soft black waves", "gray-blue eyes"),
    # Extended round 2 (heritage balance preserved)
    # White / European
    ("woman", "22-year-old adult", "slim", "fair porcelain skin", "long jet-black waves", "icy gray eyes"),
    ("woman", "24-year-old adult", "hourglass", "fair freckled skin", "long honey-blonde waves", "blue eyes"),
    ("woman", "21-year-old adult", "toned", "fair skin", "chestnut high ponytail", "hazel eyes"),
    ("woman", "25-year-old adult", "curvy", "pale skin", "dark red waves", "green eyes"),
    ("woman", "23-year-old adult", "petite adult", "fair rosy skin", "blonde French bob", "blue-gray eyes"),
    # Mediterranean / Latina / Middle-Eastern
    ("woman", "22-year-old adult", "hourglass", "warm olive skin", "long dark-brown waves", "honey-brown eyes"),
    ("woman", "24-year-old adult", "curvy", "golden tan Latina skin", "caramel balayage waves", "light brown eyes"),
    ("woman", "23-year-old adult", "athletic", "bronzed Mediterranean skin", "dark wavy lob", "green-hazel eyes"),
    ("woman", "25-year-old adult", "busty curvy", "warm Middle-Eastern skin", "long black waves", "deep brown eyes"),
    # East Asian
    ("woman", "22-year-old adult", "slim", "fair East Asian skin", "long black hair with soft layers", "dark almond eyes"),
    ("woman", "24-year-old adult", "slim athletic", "light East Asian skin", "sleek black hair with a center part", "dark brown eyes"),
    ("woman", "21-year-old adult", "petite adult", "porcelain East Asian skin", "black hair in twin buns", "dark almond eyes"),
    ("woman", "25-year-old adult", "curvy", "warm East Asian skin", "wavy black lob", "soft brown eyes"),
    # Southeast Asian
    ("woman", "23-year-old adult", "slim busty", "warm Southeast Asian tan skin", "long wavy black hair", "deep brown eyes"),
    ("woman", "22-year-old adult", "curvy", "golden Southeast Asian skin", "black hair in loose waves", "dark brown eyes"),
    # South Asian
    ("woman", "24-year-old adult", "hourglass", "warm South Asian brown skin", "long glossy black waves", "deep brown eyes"),
    ("woman", "21-year-old adult", "slim", "deep South Asian brown skin", "thick black braid", "dark brown eyes"),
    # Black / African
    ("woman", "22-year-old adult", "athletic curvy", "deep brown African skin", "long box braids", "dark brown eyes"),
    ("woman", "24-year-old adult", "hourglass", "warm brown African skin", "natural curls in a high puff", "golden brown eyes"),
    ("woman", "25-year-old adult", "curvy", "rich dark African skin", "sleek straight black hair", "dark eyes"),
    # Mixed / ambiguous
    ("woman", "23-year-old adult", "slim busty", "warm golden-brown skin", "long honey-brown curls", "light hazel eyes"),
    ("woman", "22-year-old adult", "athletic", "warm tan skin", "dark curly hair in a bun", "amber eyes"),
    # Extended round 3: expanded demographic buckets
    # White / European regional variety
    ("woman", "21-year-old adult", "slim", "fair Nordic European skin", "long ash-blonde hair", "blue-gray eyes"),
    ("woman", "22-year-old adult", "curvy", "fair Celtic freckled skin", "long copper curls", "green eyes"),
    ("woman", "23-year-old adult", "athletic", "pale Slavic European skin", "straight dark-blonde hair", "gray-blue eyes"),
    ("woman", "24-year-old adult", "hourglass", "fair Baltic European skin", "honey-blonde waves", "light green eyes"),
    ("woman", "25-year-old adult", "average", "ivory Western European skin", "soft brunette lob", "hazel eyes"),
    ("woman", "21-year-old adult", "petite adult", "porcelain Alpine European skin", "short black bob", "ice blue eyes"),
    ("woman", "22-year-old adult", "toned", "fair Balkan European skin", "dark chestnut ponytail", "amber-brown eyes"),
    ("woman", "23-year-old adult", "slim busty", "rosy Irish skin", "long auburn waves", "emerald eyes"),
    ("woman", "24-year-old adult", "curvy athletic", "fair French skin", "caramel-blonde shag", "blue eyes"),
    ("woman", "25-year-old adult", "plus-size", "pale freckled European skin", "long strawberry-blonde curls", "green-gray eyes"),
    ("woman", "21-year-old adult", "slim", "warm tanned European skin", "sunlit brown waves", "hazel eyes"),
    ("woman", "22-year-old adult", "hourglass", "fair porcelain European skin", "platinum pixie cut", "gray eyes"),
    # Mediterranean / Latina / Middle-Eastern / MENA variety
    ("woman", "23-year-old adult", "curvy", "olive Greek Mediterranean skin", "long espresso waves", "green-hazel eyes"),
    ("woman", "24-year-old adult", "athletic", "warm Italian Mediterranean skin", "dark curly lob", "amber eyes"),
    ("woman", "25-year-old adult", "hourglass", "golden Spanish Mediterranean skin", "chestnut balayage hair", "brown eyes"),
    ("woman", "21-year-old adult", "petite adult", "light Portuguese Mediterranean skin", "short wavy brunette hair", "hazel eyes"),
    ("woman", "22-year-old adult", "curvy", "warm Turkish olive skin", "long black waves", "dark brown eyes"),
    ("woman", "23-year-old adult", "slim busty", "warm Persian olive skin", "glossy dark waves", "honey-brown eyes"),
    ("woman", "24-year-old adult", "average", "warm Levantine olive skin", "dark hair in a loose bun", "deep brown eyes"),
    ("woman", "25-year-old adult", "busty curvy", "golden Maghrebi olive skin", "voluminous dark curls", "amber eyes"),
    ("woman", "21-year-old adult", "toned", "sun-kissed Mexican Latina skin", "long black waves", "dark brown eyes"),
    ("woman", "22-year-old adult", "hourglass", "warm Colombian Latina skin", "caramel-highlighted curls", "light brown eyes"),
    ("woman", "23-year-old adult", "curvy athletic", "golden Brazilian Latina skin", "long honey-brown waves", "hazel-green eyes"),
    ("woman", "24-year-old adult", "plus-size", "warm Puerto Rican Latina skin", "auburn curls", "amber-brown eyes"),
    ("woman", "25-year-old adult", "slim", "bronze Cuban Latina skin", "sleek black bob", "dark eyes"),
    ("woman", "21-year-old adult", "petite adult", "warm Andean Latina skin", "long dark braid", "deep brown eyes"),
    ("woman", "22-year-old adult", "curvy", "golden Dominican Latina skin", "dark curls with caramel tips", "honey-brown eyes"),
    ("woman", "23-year-old adult", "athletic", "warm Chilean Latina skin", "dark ponytail with loose strands", "brown eyes"),
    # East Asian regional variety
    ("woman", "21-year-old adult", "slim", "fair Japanese East Asian skin", "long straight black hair", "dark almond eyes"),
    ("woman", "22-year-old adult", "petite adult", "light Korean East Asian skin", "soft black bob with bangs", "dark brown eyes"),
    ("woman", "23-year-old adult", "slim athletic", "porcelain Han Chinese East Asian skin", "long layered black hair", "dark almond eyes"),
    ("woman", "24-year-old adult", "curvy", "warm Taiwanese East Asian skin", "wavy black lob", "soft brown eyes"),
    ("woman", "25-year-old adult", "average", "fair Mongolian East Asian skin", "thick black braid", "dark brown eyes"),
    ("woman", "21-year-old adult", "toned", "light Tibetan East Asian skin", "dark hair in a high ponytail", "deep brown eyes"),
    ("woman", "22-year-old adult", "hourglass", "fair Manchu East Asian skin", "glossy black waves", "dark almond eyes"),
    ("woman", "23-year-old adult", "slim busty", "warm Japanese East Asian skin", "black wolf-cut hair", "brown eyes"),
    ("woman", "24-year-old adult", "athletic", "light Korean East Asian skin", "sleek center-parted hair", "dark almond eyes"),
    ("woman", "25-year-old adult", "plus-size", "warm East Asian skin", "shoulder-length black curls", "deep brown eyes"),
    ("woman", "21-year-old adult", "petite adult", "porcelain Taiwanese East Asian skin", "short black pixie cut", "soft brown eyes"),
    ("woman", "22-year-old adult", "curvy athletic", "fair East Asian skin", "long black hair with curtain bangs", "dark brown eyes"),
    # Southeast Asian regional variety
    ("woman", "21-year-old adult", "slim", "warm Vietnamese Southeast Asian skin", "long glossy black hair", "dark brown eyes"),
    ("woman", "22-year-old adult", "petite adult", "golden Thai Southeast Asian skin", "black hair in a high bun", "deep brown eyes"),
    ("woman", "23-year-old adult", "curvy", "warm Filipina Southeast Asian skin", "wavy black hair with caramel streaks", "brown eyes"),
    ("woman", "24-year-old adult", "athletic", "deep Indonesian Southeast Asian tan skin", "long black ponytail", "dark eyes"),
    ("woman", "25-year-old adult", "hourglass", "golden Malay Southeast Asian skin", "soft black waves", "dark brown eyes"),
    ("woman", "21-year-old adult", "average", "warm Cambodian Southeast Asian skin", "dark shoulder-length hair", "deep brown eyes"),
    ("woman", "22-year-old adult", "slim busty", "warm Lao Southeast Asian skin", "long black hair with side-swept bangs", "brown eyes"),
    ("woman", "23-year-old adult", "curvy athletic", "golden Burmese Southeast Asian skin", "thick black braid", "dark brown eyes"),
    ("woman", "24-year-old adult", "plus-size", "warm Southeast Asian tan skin", "voluminous black curls", "dark eyes"),
    ("woman", "25-year-old adult", "toned", "golden Singaporean Southeast Asian skin", "sleek black bob", "soft brown eyes"),
    ("woman", "21-year-old adult", "petite adult", "warm Hmong Southeast Asian skin", "straight black hair with blunt bangs", "deep brown eyes"),
    ("woman", "22-year-old adult", "hourglass", "golden Balinese Southeast Asian skin", "long wavy black hair", "dark brown eyes"),
    # South Asian regional variety
    ("woman", "21-year-old adult", "slim", "warm North Indian South Asian brown skin", "long dark waves", "deep brown eyes"),
    ("woman", "22-year-old adult", "curvy", "golden Punjabi South Asian skin", "thick black braid", "dark brown eyes"),
    ("woman", "23-year-old adult", "hourglass", "deep Tamil South Asian brown skin", "long glossy black hair", "dark eyes"),
    ("woman", "24-year-old adult", "athletic", "warm Bengali South Asian skin", "black hair in a low bun", "brown eyes"),
    ("woman", "25-year-old adult", "plus-size", "deep Sri Lankan South Asian brown skin", "wavy black hair", "dark brown eyes"),
    ("woman", "21-year-old adult", "petite adult", "light Nepali South Asian skin", "long dark braid", "amber-brown eyes"),
    ("woman", "22-year-old adult", "slim busty", "warm Pakistani South Asian skin", "soft black waves", "deep brown eyes"),
    ("woman", "23-year-old adult", "curvy athletic", "golden Gujarati South Asian skin", "dark curls over one shoulder", "hazel-brown eyes"),
    ("woman", "24-year-old adult", "average", "warm Bangladeshi South Asian skin", "straight black hair", "dark eyes"),
    ("woman", "25-year-old adult", "toned", "deep Malayali South Asian brown skin", "long black ponytail", "dark brown eyes"),
    ("woman", "21-year-old adult", "curvy", "warm Kashmiri South Asian skin", "dark chestnut waves", "green-brown eyes"),
    ("woman", "22-year-old adult", "hourglass", "deep South Asian brown skin", "thick black curls", "golden brown eyes"),
    # Central Asian / Indigenous / Pacific variety
    ("woman", "21-year-old adult", "slim", "light Kazakh Central Asian skin", "dark blonde waves", "hazel eyes"),
    ("woman", "22-year-old adult", "athletic", "warm Uzbek Central Asian skin", "long dark braid", "amber eyes"),
    ("woman", "23-year-old adult", "curvy", "golden Kyrgyz Central Asian skin", "thick brown ponytail", "brown eyes"),
    ("woman", "24-year-old adult", "average", "warm Uighur Central Asian skin", "long black waves", "green-hazel eyes"),
    ("woman", "25-year-old adult", "hourglass", "warm Armenian skin", "voluminous dark curls", "deep brown eyes"),
    ("woman", "21-year-old adult", "petite adult", "warm Indigenous Mexican skin", "long straight black hair", "dark brown eyes"),
    ("woman", "22-year-old adult", "curvy athletic", "deep Indigenous Andean skin", "thick black braid", "dark eyes"),
    ("woman", "23-year-old adult", "average", "warm Native American skin", "long black hair", "dark brown eyes"),
    ("woman", "24-year-old adult", "curvy", "golden Pacific Islander skin", "long dark waves", "brown eyes"),
    ("woman", "25-year-old adult", "plus-size", "deep Polynesian Pacific Islander skin", "thick black curls", "dark brown eyes"),
    # Black / African diaspora regional variety
    ("woman", "21-year-old adult", "athletic", "deep West African skin", "short natural coils", "dark brown eyes"),
    ("woman", "22-year-old adult", "curvy", "rich Ghanaian African skin", "long box braids", "golden brown eyes"),
    ("woman", "23-year-old adult", "hourglass", "deep Nigerian African skin", "braided updo", "dark eyes"),
    ("woman", "24-year-old adult", "slim", "warm Ethiopian African skin", "soft black curls", "amber eyes"),
    ("woman", "25-year-old adult", "plus-size", "rich Kenyan African skin", "long locs", "deep brown eyes"),
    ("woman", "21-year-old adult", "curvy athletic", "deep Senegalese African skin", "cornrow braids with beads", "brown eyes"),
    ("woman", "22-year-old adult", "average", "warm Somalian African skin", "sleek dark ponytail", "dark brown eyes"),
    ("woman", "23-year-old adult", "busty curvy", "rich African-Caribbean brown skin", "voluminous natural curls", "honey-brown eyes"),
    ("woman", "24-year-old adult", "athletic", "deep African-American brown skin", "short tapered curls", "dark eyes"),
    ("woman", "25-year-old adult", "hourglass", "warm African-British brown skin", "long twists", "golden brown eyes"),
    ("woman", "21-year-old adult", "petite adult", "deep Sudanese African skin", "black curls in a puff", "dark brown eyes"),
    ("woman", "22-year-old adult", "plus-size", "rich African-diaspora brown skin", "copper-highlighted locs", "amber-brown eyes"),
    # Mixed heritage combinations
    ("woman", "21-year-old adult", "slim", "fair East Asian-European mixed skin", "long ash-brown waves", "gray-brown eyes"),
    ("woman", "22-year-old adult", "petite adult", "light Korean European mixed skin", "soft black bob", "blue-gray eyes"),
    ("woman", "23-year-old adult", "curvy", "warm Japanese Latina mixed skin", "dark wavy lob", "hazel eyes"),
    ("woman", "24-year-old adult", "athletic", "golden Chinese Latina mixed skin", "long black ponytail", "light brown eyes"),
    ("woman", "25-year-old adult", "hourglass", "warm East Asian Mediterranean mixed skin", "long espresso waves", "green-brown eyes"),
    ("woman", "21-year-old adult", "slim busty", "light East Asian Middle-Eastern mixed skin", "glossy black waves", "amber eyes"),
    ("woman", "22-year-old adult", "curvy athletic", "warm Vietnamese European mixed skin", "black hair with honey highlights", "hazel eyes"),
    ("woman", "23-year-old adult", "average", "golden Filipina European mixed skin", "soft brown curls", "green eyes"),
    ("woman", "24-year-old adult", "plus-size", "warm Southeast Asian Latina mixed skin", "long dark curls", "brown eyes"),
    ("woman", "25-year-old adult", "toned", "golden Thai Middle-Eastern mixed skin", "sleek black high ponytail", "deep brown eyes"),
    ("woman", "21-year-old adult", "petite adult", "warm South Asian European mixed skin", "dark chestnut waves", "hazel-green eyes"),
    ("woman", "22-year-old adult", "hourglass", "golden South Asian Latina mixed skin", "long black curls", "honey-brown eyes"),
    ("woman", "23-year-old adult", "curvy", "warm South Asian Middle-Eastern mixed skin", "thick dark waves", "amber-brown eyes"),
    ("woman", "24-year-old adult", "athletic", "deep South Asian Southeast Asian mixed skin", "black hair in a braid", "dark brown eyes"),
    ("woman", "25-year-old adult", "slim busty", "warm South Asian East Asian mixed skin", "long glossy black hair", "soft brown eyes"),
    ("woman", "21-year-old adult", "curvy athletic", "golden Central Asian European mixed skin", "dark blonde curls", "green-hazel eyes"),
    ("woman", "22-year-old adult", "average", "warm Central Asian Middle-Eastern mixed skin", "long brown waves", "amber eyes"),
    ("woman", "23-year-old adult", "hourglass", "warm Indigenous Latina European mixed skin", "long dark waves", "hazel eyes"),
    ("woman", "24-year-old adult", "curvy", "deep Indigenous Latina skin", "thick black curls", "dark brown eyes"),
    ("woman", "25-year-old adult", "athletic", "golden Pacific Islander Asian mixed skin", "long black waves", "brown eyes"),
    ("woman", "21-year-old adult", "slim", "warm Pacific Islander European mixed skin", "soft dark-brown waves", "green-brown eyes"),
    ("woman", "22-year-old adult", "curvy", "warm Middle-Eastern Latina mixed skin", "voluminous dark curls", "amber eyes"),
    ("woman", "23-year-old adult", "plus-size", "golden Mediterranean Latina mixed skin", "long caramel curls", "light brown eyes"),
    ("woman", "24-year-old adult", "toned", "warm Turkish European mixed skin", "dark wavy ponytail", "hazel eyes"),
    ("woman", "25-year-old adult", "average", "warm Persian South Asian mixed skin", "glossy black waves", "deep brown eyes"),
    ("woman", "21-year-old adult", "athletic curvy", "deep African-European mixed skin", "brown curls with blonde tips", "hazel eyes"),
    ("woman", "22-year-old adult", "hourglass", "warm African-Latina mixed skin", "long dark curls", "golden brown eyes"),
    ("woman", "23-year-old adult", "slim busty", "deep African-East Asian mixed skin", "sleek black bob", "dark almond eyes"),
    ("woman", "24-year-old adult", "curvy athletic", "warm African-South Asian mixed skin", "long braided black hair", "deep brown eyes"),
    ("woman", "25-year-old adult", "plus-size", "rich African-Pacific Islander mixed skin", "voluminous black curls", "dark brown eyes"),
    ("woman", "21-year-old adult", "petite adult", "warm African-Middle-Eastern mixed skin", "soft dark curls", "amber-brown eyes"),
    ("woman", "22-year-old adult", "curvy", "deep African-Caribbean Latina mixed skin", "long locs with copper tips", "honey-brown eyes"),
    ("woman", "23-year-old adult", "athletic", "warm African-Native American mixed skin", "thick black waves", "dark brown eyes"),
    ("woman", "24-year-old adult", "hourglass", "golden African-Southeast Asian mixed skin", "black curls in a high puff", "brown eyes"),
    ("woman", "25-year-old adult", "average", "warm African-Central Asian mixed skin", "dark brown waves", "hazel-brown eyes"),
]

_YOUNG_EXPANSION_AGES = (
    "21-year-old adult",
    "22-year-old adult",
    "23-year-old adult",
    "24-year-old adult",
    "25-year-old adult",
)

_YOUNG_EXPANSION_BODIES = (
    "slim",
    "petite adult",
    "toned",
    "athletic",
    "average",
    "curvy",
    "curvy athletic",
    "hourglass",
    "slim busty",
    "busty curvy",
    "soft curvy",
    "plus-size",
)

_YOUNG_VARIANTS_PER_PROFILE = 8


def _expand_young_demographics(profiles: list[tuple[str, tuple[str, ...], tuple[str, ...]]]) -> list[tuple[str, str, str, str, str, str]]:
    """Deterministically expand demographic profiles into balanced adult variants.

    Each profile contributes the same number of entries so larger named buckets do
    not accidentally dominate the random sampler.
    """
    seen = set(YOUNG_WOMEN)
    expanded: list[tuple[str, str, str, str, str, str]] = []
    for profile_index, (skin, hair_options, eye_options) in enumerate(profiles):
        for variant_index in range(_YOUNG_VARIANTS_PER_PROFILE):
            entry = (
                "woman",
                _YOUNG_EXPANSION_AGES[(profile_index + variant_index) % len(_YOUNG_EXPANSION_AGES)],
                _YOUNG_EXPANSION_BODIES[(profile_index * 5 + variant_index * 7) % len(_YOUNG_EXPANSION_BODIES)],
                skin,
                hair_options[variant_index % len(hair_options)],
                eye_options[(profile_index + variant_index * 2) % len(eye_options)],
            )
            if entry not in seen:
                seen.add(entry)
                expanded.append(entry)
    return expanded


YOUNG_WOMEN.extend(
    _expand_young_demographics(
        [
            # White / European regional profiles
            ("fair Nordic European skin", ("long ash-blonde waves", "pale blonde bob", "braided flaxen hair", "soft sandy-blonde curls"), ("ice blue eyes", "blue-gray eyes", "light green eyes", "gray eyes")),
            ("fair Swedish European skin", ("long honey-blonde hair", "short blonde pixie cut", "loose dark-blonde waves", "sleek blonde ponytail"), ("blue eyes", "gray-blue eyes", "green eyes", "pale hazel eyes")),
            ("fair Norwegian European skin", ("platinum waves", "dark-blonde braid", "light-brown shag", "long wheat-blonde curls"), ("ice blue eyes", "blue eyes", "gray eyes", "green-gray eyes")),
            ("fair Danish European skin", ("straight ash-brown hair", "soft blonde lob", "messy sandy bun", "long beige-blonde waves"), ("blue-gray eyes", "hazel eyes", "green eyes", "gray eyes")),
            ("pale Celtic European skin", ("long copper curls", "auburn bob", "fiery red waves", "ginger pixie cut"), ("emerald eyes", "green-gray eyes", "blue eyes", "hazel eyes")),
            ("fair Irish European skin", ("strawberry-blonde waves", "dark auburn curls", "red hair in a loose bun", "copper braid"), ("green eyes", "blue-green eyes", "gray eyes", "hazel eyes")),
            ("fair Scottish European skin", ("long chestnut waves", "short copper curls", "dark-red lob", "freckled blonde ponytail"), ("green eyes", "blue eyes", "gray-green eyes", "amber eyes")),
            ("pale Slavic European skin", ("straight dark-blonde hair", "long ash-brown waves", "soft brunette bob", "platinum braid"), ("gray-blue eyes", "green eyes", "blue eyes", "hazel eyes")),
            ("fair Polish European skin", ("honey-brown waves", "dark-blonde lob", "long chestnut curls", "sleek brown ponytail"), ("blue-gray eyes", "hazel eyes", "green eyes", "gray eyes")),
            ("fair Ukrainian European skin", ("long golden-brown waves", "dark blonde braid", "soft auburn hair", "straight brunette hair"), ("green eyes", "blue eyes", "gray eyes", "hazel-brown eyes")),
            ("fair Baltic European skin", ("long honey-blonde waves", "ash-brown bob", "pale blonde braid", "soft chestnut curls"), ("light green eyes", "blue eyes", "gray-blue eyes", "hazel eyes")),
            ("ivory French European skin", ("caramel-blonde shag", "brunette French bob", "long chestnut waves", "soft black pixie cut"), ("blue eyes", "hazel eyes", "green eyes", "gray eyes")),
            ("warm Germanic European skin", ("dark blonde waves", "straight chestnut hair", "honey-blonde ponytail", "auburn lob"), ("blue-gray eyes", "hazel eyes", "green eyes", "brown eyes")),
            ("fair Dutch European skin", ("long flaxen hair", "short blonde bob", "soft brown waves", "messy ash-blonde bun"), ("blue eyes", "green eyes", "gray eyes", "hazel eyes")),
            ("warm Balkan European skin", ("dark chestnut ponytail", "long black waves", "auburn curls", "soft brunette lob"), ("amber-brown eyes", "green eyes", "hazel eyes", "brown eyes")),
            ("fair Alpine European skin", ("short black bob", "long brunette waves", "blonde braid", "soft copper curls"), ("ice blue eyes", "gray eyes", "green eyes", "hazel eyes")),
            # Mediterranean / Latina / Middle-Eastern / MENA profiles
            ("olive Greek Mediterranean skin", ("long espresso waves", "dark curly lob", "black hair in a loose bun", "chestnut balayage hair"), ("green-hazel eyes", "amber eyes", "brown eyes", "dark eyes")),
            ("warm Italian Mediterranean skin", ("dark curly lob", "long chestnut curls", "soft brunette waves", "sleek black ponytail"), ("amber eyes", "hazel eyes", "green eyes", "brown eyes")),
            ("golden Spanish Mediterranean skin", ("chestnut balayage hair", "long dark waves", "soft brown curls", "black bob with side part"), ("brown eyes", "hazel eyes", "green-hazel eyes", "amber eyes")),
            ("light Portuguese Mediterranean skin", ("short wavy brunette hair", "long dark-blonde waves", "caramel curls", "messy chestnut bun"), ("hazel eyes", "brown eyes", "green eyes", "gray-brown eyes")),
            ("warm Turkish olive skin", ("long black waves", "dark hair in a high ponytail", "soft chestnut curls", "sleek brunette bob"), ("dark brown eyes", "amber eyes", "hazel eyes", "green-brown eyes")),
            ("warm Persian olive skin", ("glossy dark waves", "long black curls", "dark-brown hair with caramel tips", "soft black lob"), ("honey-brown eyes", "deep brown eyes", "amber eyes", "green-hazel eyes")),
            ("warm Levantine olive skin", ("dark hair in a loose bun", "long black waves", "chestnut curls", "sleek dark ponytail"), ("deep brown eyes", "hazel eyes", "amber eyes", "green-brown eyes")),
            ("golden Maghrebi olive skin", ("voluminous dark curls", "long black waves", "dark auburn hair", "curly brunette bob"), ("amber eyes", "brown eyes", "hazel eyes", "dark eyes")),
            ("warm Egyptian skin", ("long black waves", "dark curly hair", "sleek black bob", "chestnut ponytail"), ("deep brown eyes", "amber eyes", "hazel-brown eyes", "dark eyes")),
            ("warm Moroccan Amazigh skin", ("dark curls with honey tips", "long black hair", "braided brunette hair", "soft chestnut waves"), ("amber eyes", "hazel eyes", "brown eyes", "green-brown eyes")),
            ("warm Kurdish skin", ("long dark waves", "black hair in a braid", "soft brunette curls", "dark auburn lob"), ("hazel eyes", "green-brown eyes", "amber eyes", "deep brown eyes")),
            ("sun-kissed Mexican Latina skin", ("long black waves", "dark curls", "straight brunette hair", "caramel-highlighted ponytail"), ("dark brown eyes", "hazel eyes", "amber-brown eyes", "brown eyes")),
            ("warm Chicana Latina skin", ("long espresso hair", "soft black waves", "auburn curls", "brown hair with blonde streaks"), ("brown eyes", "hazel eyes", "green-brown eyes", "amber eyes")),
            ("warm Colombian Latina skin", ("caramel-highlighted curls", "long dark waves", "soft brown lob", "black ponytail with loose strands"), ("light brown eyes", "hazel eyes", "green eyes", "deep brown eyes")),
            ("golden Brazilian Latina skin", ("long honey-brown waves", "voluminous dark curls", "caramel balayage hair", "sleek black bob"), ("hazel-green eyes", "brown eyes", "amber eyes", "green eyes")),
            ("warm Puerto Rican Latina skin", ("auburn curls", "dark curls with caramel tips", "long chestnut waves", "black hair in a high bun"), ("amber-brown eyes", "hazel eyes", "brown eyes", "green-brown eyes")),
            ("bronze Cuban Latina skin", ("sleek black bob", "long dark waves", "soft brunette curls", "caramel ponytail"), ("dark eyes", "brown eyes", "hazel eyes", "amber eyes")),
            ("golden Dominican Latina skin", ("dark curls with caramel tips", "long black waves", "voluminous chestnut curls", "sleek ponytail"), ("honey-brown eyes", "dark brown eyes", "hazel eyes", "amber eyes")),
            ("warm Venezuelan Latina skin", ("long chocolate waves", "black curls", "caramel-highlighted lob", "soft auburn hair"), ("brown eyes", "hazel eyes", "amber eyes", "green-brown eyes")),
            ("warm Peruvian Latina skin", ("long dark braid", "straight black hair", "soft brunette waves", "black hair in a bun"), ("deep brown eyes", "dark eyes", "hazel-brown eyes", "amber eyes")),
            ("warm Chilean Latina skin", ("dark ponytail with loose strands", "long brunette waves", "soft black lob", "auburn curls"), ("brown eyes", "hazel eyes", "amber eyes", "green-brown eyes")),
            ("light Argentine Latina skin", ("long dark-blonde waves", "soft brunette curls", "copper lob", "sleek brown ponytail"), ("hazel eyes", "green eyes", "brown eyes", "blue-gray eyes")),
            ("warm Uruguayan Latina skin", ("soft chestnut waves", "dark-blonde bob", "long brunette curls", "black pixie cut"), ("brown eyes", "hazel eyes", "green eyes", "amber eyes")),
            ("golden Ecuadorian Latina skin", ("long black waves", "dark braid", "soft brown curls", "sleek brunette bob"), ("dark brown eyes", "hazel-brown eyes", "amber eyes", "brown eyes")),
            # East Asian profiles
            ("fair Japanese East Asian skin", ("long straight black hair", "black wolf-cut hair", "short black pixie cut", "soft black bob with bangs"), ("dark almond eyes", "soft brown eyes", "dark brown eyes", "gray-brown eyes")),
            ("warm Japanese East Asian skin", ("long layered black hair", "glossy black waves", "sleek black ponytail", "short black bob"), ("brown eyes", "dark almond eyes", "soft brown eyes", "deep brown eyes")),
            ("light Korean East Asian skin", ("soft black bob with bangs", "sleek center-parted hair", "long black waves", "black hair in a low bun"), ("dark brown eyes", "dark almond eyes", "gray-brown eyes", "soft brown eyes")),
            ("fair Korean East Asian skin", ("long black hair with curtain bangs", "short glossy bob", "black ponytail", "soft layered hair"), ("dark almond eyes", "deep brown eyes", "soft brown eyes", "gray eyes")),
            ("porcelain Han Chinese East Asian skin", ("long layered black hair", "straight black hair with blunt bangs", "glossy black ponytail", "soft black lob"), ("dark almond eyes", "dark brown eyes", "soft brown eyes", "deep brown eyes")),
            ("warm Han Chinese East Asian skin", ("long black waves", "sleek black bob", "black braid", "shoulder-length black curls"), ("dark brown eyes", "soft brown eyes", "dark almond eyes", "brown eyes")),
            ("warm Taiwanese East Asian skin", ("wavy black lob", "long black hair with soft layers", "black hair in a high bun", "sleek bob with side part"), ("soft brown eyes", "dark brown eyes", "dark almond eyes", "hazel-brown eyes")),
            ("fair Mongolian East Asian skin", ("thick black braid", "long dark waves", "straight black ponytail", "soft brown-black curls"), ("dark brown eyes", "brown eyes", "gray-brown eyes", "dark almond eyes")),
            ("light Tibetan East Asian skin", ("dark hair in a high ponytail", "long black braid", "soft black waves", "straight dark hair"), ("deep brown eyes", "dark brown eyes", "amber-brown eyes", "soft brown eyes")),
            ("fair Manchu East Asian skin", ("glossy black waves", "black hair in twin buns", "long straight black hair", "short black bob"), ("dark almond eyes", "dark brown eyes", "soft brown eyes", "gray-brown eyes")),
            ("warm Hong Kong Chinese East Asian skin", ("sleek black lob", "long black waves", "black hair with subtle brown highlights", "messy high bun"), ("dark brown eyes", "soft brown eyes", "dark almond eyes", "brown eyes")),
            ("light Okinawan East Asian skin", ("soft black waves", "long straight black hair", "black bob with bangs", "loose black ponytail"), ("dark almond eyes", "brown eyes", "soft brown eyes", "deep brown eyes")),
            # Southeast Asian profiles
            ("warm Vietnamese Southeast Asian skin", ("long glossy black hair", "straight black hair with curtain bangs", "dark hair in a low bun", "soft black waves"), ("dark brown eyes", "deep brown eyes", "brown eyes", "soft brown eyes")),
            ("golden Thai Southeast Asian skin", ("black hair in a high bun", "long wavy black hair", "sleek black bob", "soft dark curls"), ("deep brown eyes", "dark brown eyes", "brown eyes", "amber-brown eyes")),
            ("warm Filipina Southeast Asian skin", ("wavy black hair with caramel streaks", "long dark waves", "soft brown curls", "black ponytail"), ("brown eyes", "dark brown eyes", "hazel-brown eyes", "deep brown eyes")),
            ("deep Indonesian Southeast Asian tan skin", ("long black ponytail", "soft black waves", "dark curls", "straight black hair"), ("dark eyes", "deep brown eyes", "brown eyes", "amber-brown eyes")),
            ("golden Malay Southeast Asian skin", ("soft black waves", "long glossy black hair", "sleek black bob", "dark hair in a loose bun"), ("dark brown eyes", "brown eyes", "soft brown eyes", "deep brown eyes")),
            ("warm Cambodian Southeast Asian skin", ("dark shoulder-length hair", "long black hair", "black braid", "soft dark waves"), ("deep brown eyes", "dark brown eyes", "brown eyes", "amber-brown eyes")),
            ("warm Lao Southeast Asian skin", ("long black hair with side-swept bangs", "straight black hair", "dark ponytail", "soft black curls"), ("brown eyes", "dark brown eyes", "deep brown eyes", "soft brown eyes")),
            ("golden Burmese Southeast Asian skin", ("thick black braid", "long black waves", "sleek ponytail", "soft dark lob"), ("dark brown eyes", "brown eyes", "deep brown eyes", "amber-brown eyes")),
            ("golden Singaporean Southeast Asian skin", ("sleek black bob", "long black waves", "black hair with brown highlights", "messy black bun"), ("soft brown eyes", "dark brown eyes", "brown eyes", "hazel-brown eyes")),
            ("warm Hmong Southeast Asian skin", ("straight black hair with blunt bangs", "long black braid", "dark hair in a bun", "soft black waves"), ("deep brown eyes", "dark brown eyes", "brown eyes", "soft brown eyes")),
            ("golden Balinese Southeast Asian skin", ("long wavy black hair", "black hair with sunlit brown tips", "soft dark curls", "sleek ponytail"), ("dark brown eyes", "brown eyes", "deep brown eyes", "amber-brown eyes")),
            ("warm Javanese Southeast Asian skin", ("long black waves", "dark hair in a low bun", "soft black bob", "straight black ponytail"), ("deep brown eyes", "dark eyes", "brown eyes", "amber-brown eyes")),
            # South Asian profiles
            ("warm North Indian South Asian brown skin", ("long dark waves", "thick black braid", "soft black curls", "dark chestnut hair"), ("deep brown eyes", "dark brown eyes", "hazel-brown eyes", "amber eyes")),
            ("golden Punjabi South Asian skin", ("thick black braid", "long glossy black hair", "dark waves over one shoulder", "soft brown-black curls"), ("dark brown eyes", "deep brown eyes", "amber-brown eyes", "hazel eyes")),
            ("deep Tamil South Asian brown skin", ("long glossy black hair", "black hair in a low bun", "thick black curls", "straight black ponytail"), ("dark eyes", "deep brown eyes", "brown eyes", "golden brown eyes")),
            ("warm Bengali South Asian skin", ("black hair in a low bun", "long black waves", "dark braid", "soft black lob"), ("brown eyes", "deep brown eyes", "dark eyes", "amber-brown eyes")),
            ("deep Sri Lankan South Asian brown skin", ("wavy black hair", "long glossy black waves", "black curls", "dark hair with copper tips"), ("dark brown eyes", "deep brown eyes", "golden brown eyes", "dark eyes")),
            ("light Nepali South Asian skin", ("long dark braid", "soft black waves", "straight dark-brown hair", "dark ponytail"), ("amber-brown eyes", "dark brown eyes", "hazel-brown eyes", "soft brown eyes")),
            ("warm Pakistani South Asian skin", ("soft black waves", "long dark curls", "thick black braid", "dark chestnut lob"), ("deep brown eyes", "dark brown eyes", "amber eyes", "hazel-brown eyes")),
            ("golden Gujarati South Asian skin", ("dark curls over one shoulder", "long black waves", "sleek dark ponytail", "soft brown-black hair"), ("hazel-brown eyes", "deep brown eyes", "amber eyes", "dark brown eyes")),
            ("warm Bangladeshi South Asian skin", ("straight black hair", "long black waves", "dark braid", "soft black curls"), ("dark eyes", "deep brown eyes", "brown eyes", "amber-brown eyes")),
            ("deep Malayali South Asian brown skin", ("long black ponytail", "thick black curls", "glossy black waves", "black hair in a bun"), ("dark brown eyes", "deep brown eyes", "golden brown eyes", "dark eyes")),
            ("warm Kashmiri South Asian skin", ("dark chestnut waves", "long black hair", "soft brown curls", "dark braid"), ("green-brown eyes", "hazel eyes", "amber eyes", "deep brown eyes")),
            ("warm Marathi South Asian skin", ("long dark waves", "black hair with brown highlights", "thick braid", "soft black lob"), ("brown eyes", "deep brown eyes", "hazel-brown eyes", "amber eyes")),
            # Central Asian / West Asian profiles
            ("light Kazakh Central Asian skin", ("dark blonde waves", "long brown braid", "soft black hair", "chestnut ponytail"), ("hazel eyes", "brown eyes", "green-hazel eyes", "gray-brown eyes")),
            ("warm Uzbek Central Asian skin", ("long dark braid", "soft brunette waves", "black hair in a bun", "dark chestnut curls"), ("amber eyes", "brown eyes", "hazel eyes", "green-brown eyes")),
            ("golden Kyrgyz Central Asian skin", ("thick brown ponytail", "long dark waves", "black braid", "soft brown curls"), ("brown eyes", "hazel eyes", "amber-brown eyes", "green eyes")),
            ("warm Uighur Central Asian skin", ("long black waves", "dark chestnut hair", "soft brown curls", "black ponytail"), ("green-hazel eyes", "amber eyes", "brown eyes", "deep brown eyes")),
            ("warm Tajik Central Asian skin", ("long dark-brown waves", "black hair in a braid", "soft chestnut curls", "sleek dark ponytail"), ("hazel eyes", "amber eyes", "brown eyes", "green-brown eyes")),
            ("golden Turkmen Central Asian skin", ("dark braid with loose strands", "long brown waves", "soft black curls", "chestnut bun"), ("amber-brown eyes", "hazel eyes", "brown eyes", "green eyes")),
            ("warm Armenian skin", ("voluminous dark curls", "long black waves", "dark auburn hair", "soft brunette lob"), ("deep brown eyes", "hazel eyes", "amber eyes", "green-brown eyes")),
            ("warm Georgian Caucasus skin", ("dark chestnut waves", "long black curls", "soft brunette hair", "black ponytail"), ("hazel eyes", "green-brown eyes", "amber eyes", "deep brown eyes")),
            # Indigenous / Pacific profiles
            ("warm Indigenous Mexican skin", ("long straight black hair", "thick black braid", "soft dark waves", "black hair in a low bun"), ("dark brown eyes", "deep brown eyes", "brown eyes", "dark eyes")),
            ("deep Indigenous Andean skin", ("thick black braid", "long black hair", "dark hair in twin braids", "soft black waves"), ("dark eyes", "deep brown eyes", "brown eyes", "amber-brown eyes")),
            ("warm Maya Indigenous skin", ("long black waves", "straight black hair", "dark braid", "black hair in a bun"), ("deep brown eyes", "dark brown eyes", "brown eyes", "dark eyes")),
            ("warm Quechua Indigenous skin", ("thick black braid", "long straight black hair", "soft dark waves", "black ponytail"), ("dark brown eyes", "deep brown eyes", "amber-brown eyes", "dark eyes")),
            ("warm Aymara Indigenous skin", ("long black braid", "straight black hair", "dark waves", "black hair in a low bun"), ("dark eyes", "deep brown eyes", "brown eyes", "amber-brown eyes")),
            ("warm Native American skin", ("long black hair", "thick dark braid", "soft black waves", "straight dark hair"), ("dark brown eyes", "deep brown eyes", "brown eyes", "amber-brown eyes")),
            ("warm First Nations Indigenous skin", ("long black waves", "dark braid", "straight black hair", "soft brown-black curls"), ("dark brown eyes", "brown eyes", "amber-brown eyes", "deep brown eyes")),
            ("golden Pacific Islander skin", ("long dark waves", "thick black curls", "soft black hair", "dark hair with sunlit tips"), ("brown eyes", "dark brown eyes", "amber-brown eyes", "deep brown eyes")),
            ("deep Polynesian Pacific Islander skin", ("thick black curls", "long black waves", "voluminous dark hair", "braided black hair"), ("dark brown eyes", "deep brown eyes", "brown eyes", "golden brown eyes")),
            ("golden Samoan Pacific Islander skin", ("long black waves", "dark curls", "sleek black ponytail", "thick braided hair"), ("brown eyes", "dark brown eyes", "deep brown eyes", "amber eyes")),
            ("warm Maori Pacific Islander skin", ("long dark waves", "black curls", "thick black braid", "soft dark ponytail"), ("dark brown eyes", "brown eyes", "deep brown eyes", "amber-brown eyes")),
            ("golden Hawaiian Pacific Islander skin", ("long wavy black hair", "soft dark curls", "black hair with brown tips", "thick black ponytail"), ("brown eyes", "dark brown eyes", "amber-brown eyes", "deep brown eyes")),
            # Black / African diaspora profiles
            ("deep West African skin", ("short natural coils", "long box braids", "braided updo", "voluminous natural curls"), ("dark brown eyes", "golden brown eyes", "dark eyes", "amber-brown eyes")),
            ("rich Ghanaian African skin", ("long box braids", "natural curls in a high puff", "black twists", "sleek straight black hair"), ("golden brown eyes", "dark brown eyes", "amber eyes", "dark eyes")),
            ("deep Nigerian African skin", ("braided updo", "long locs", "short tapered curls", "voluminous black curls"), ("dark eyes", "deep brown eyes", "golden brown eyes", "amber-brown eyes")),
            ("warm Ethiopian African skin", ("soft black curls", "long dark waves", "braided black hair", "curly bob"), ("amber eyes", "dark brown eyes", "golden brown eyes", "deep brown eyes")),
            ("warm Eritrean African skin", ("long dark curls", "soft black waves", "braided ponytail", "short natural curls"), ("amber-brown eyes", "dark brown eyes", "golden brown eyes", "deep brown eyes")),
            ("warm Somalian African skin", ("sleek dark ponytail", "long black waves", "soft curls", "braided black hair"), ("dark brown eyes", "deep brown eyes", "amber eyes", "golden brown eyes")),
            ("rich Kenyan African skin", ("long locs", "short natural coils", "voluminous black curls", "braided crown"), ("deep brown eyes", "dark eyes", "golden brown eyes", "amber-brown eyes")),
            ("deep Senegalese African skin", ("cornrow braids with beads", "long black twists", "natural curls", "sleek braided ponytail"), ("brown eyes", "dark brown eyes", "golden brown eyes", "deep brown eyes")),
            ("deep Sudanese African skin", ("black curls in a puff", "long braided hair", "soft dark waves", "short coils"), ("dark brown eyes", "deep brown eyes", "amber-brown eyes", "golden brown eyes")),
            ("rich South African African skin", ("voluminous natural curls", "long twists", "short tapered curls", "sleek black bob"), ("dark brown eyes", "golden brown eyes", "amber eyes", "deep brown eyes")),
            ("deep African-American brown skin", ("short tapered curls", "long box braids", "natural afro", "sleek straight black hair"), ("dark eyes", "dark brown eyes", "golden brown eyes", "amber-brown eyes")),
            ("rich African-Caribbean brown skin", ("voluminous natural curls", "long locs", "black curls with copper tips", "braided updo"), ("honey-brown eyes", "dark brown eyes", "golden brown eyes", "deep brown eyes")),
            ("deep Haitian African-Caribbean skin", ("long locs", "natural curls", "braided black hair", "short coils"), ("dark brown eyes", "deep brown eyes", "golden brown eyes", "amber eyes")),
            ("warm Jamaican African-Caribbean skin", ("black curls with honey tips", "long braids", "voluminous natural hair", "sleek ponytail"), ("golden brown eyes", "dark brown eyes", "honey-brown eyes", "deep brown eyes")),
            ("deep Afro-Brazilian African skin", ("long dark curls", "braided black hair", "voluminous natural curls", "copper-highlighted locs"), ("dark brown eyes", "golden brown eyes", "amber-brown eyes", "deep brown eyes")),
            ("warm Cape Verdean African skin", ("soft dark curls", "long brown-black waves", "braided hair with caramel tips", "sleek curly bob"), ("hazel-brown eyes", "golden brown eyes", "dark brown eyes", "amber eyes")),
            # Mixed heritage profiles
            ("fair East Asian-European mixed skin", ("long ash-brown waves", "soft black bob", "dark-blonde ponytail", "black hair with honey highlights"), ("gray-brown eyes", "blue-gray eyes", "hazel eyes", "soft brown eyes")),
            ("light Korean East Asian-European mixed skin", ("soft black bob", "long dark-blonde waves", "sleek brown ponytail", "black hair with curtain bangs"), ("blue-gray eyes", "dark almond eyes", "green eyes", "soft brown eyes")),
            ("warm Japanese East Asian-Latina mixed skin", ("dark wavy lob", "long black waves", "brown hair with caramel tips", "soft black curls"), ("hazel eyes", "brown eyes", "soft brown eyes", "amber eyes")),
            ("golden Chinese East Asian-Latina mixed skin", ("long black ponytail", "dark curls", "black hair with caramel streaks", "soft brunette bob"), ("light brown eyes", "dark almond eyes", "hazel-brown eyes", "brown eyes")),
            ("warm East Asian-Mediterranean mixed skin", ("long espresso waves", "glossy black hair", "soft brown curls", "sleek black lob"), ("green-brown eyes", "dark brown eyes", "hazel eyes", "soft brown eyes")),
            ("light East Asian-Middle-Eastern mixed skin", ("glossy black waves", "dark chestnut curls", "sleek black ponytail", "soft black bob"), ("amber eyes", "dark almond eyes", "hazel-brown eyes", "deep brown eyes")),
            ("warm Vietnamese Southeast Asian-European mixed skin", ("black hair with honey highlights", "soft dark-blonde waves", "straight black hair", "brown curls"), ("hazel eyes", "dark brown eyes", "green eyes", "soft brown eyes")),
            ("golden Filipina Southeast Asian-European mixed skin", ("soft brown curls", "long black waves", "caramel-highlighted ponytail", "brunette bob"), ("green eyes", "brown eyes", "hazel eyes", "dark brown eyes")),
            ("warm Southeast Asian-Latina mixed skin", ("long dark curls", "black waves with caramel tips", "soft brown hair", "sleek black ponytail"), ("brown eyes", "hazel eyes", "amber eyes", "deep brown eyes")),
            ("golden Thai Southeast Asian-Middle-Eastern mixed skin", ("sleek black high ponytail", "long dark waves", "soft black curls", "dark chestnut hair"), ("deep brown eyes", "amber eyes", "hazel-brown eyes", "soft brown eyes")),
            ("warm South Asian-European mixed skin", ("dark chestnut waves", "long black hair", "soft brown curls", "dark-blonde lob"), ("hazel-green eyes", "deep brown eyes", "amber eyes", "green eyes")),
            ("golden South Asian-Latina mixed skin", ("long black curls", "dark waves with caramel tips", "soft chestnut hair", "black ponytail"), ("honey-brown eyes", "deep brown eyes", "hazel eyes", "amber eyes")),
            ("warm South Asian-Middle-Eastern mixed skin", ("thick dark waves", "long black hair", "soft brunette curls", "dark braid"), ("amber-brown eyes", "deep brown eyes", "hazel eyes", "dark brown eyes")),
            ("deep South Asian-Southeast Asian mixed skin", ("black hair in a braid", "long glossy black hair", "soft dark curls", "sleek black bob"), ("dark brown eyes", "deep brown eyes", "brown eyes", "amber-brown eyes")),
            ("warm South Asian-East Asian mixed skin", ("long glossy black hair", "soft black bob", "dark waves", "straight black ponytail"), ("soft brown eyes", "deep brown eyes", "dark almond eyes", "brown eyes")),
            ("golden Central Asian-European mixed skin", ("dark blonde curls", "long brown waves", "soft black hair", "chestnut ponytail"), ("green-hazel eyes", "hazel eyes", "gray-brown eyes", "amber eyes")),
            ("warm Central Asian-Middle-Eastern mixed skin", ("long brown waves", "dark braid", "soft black curls", "chestnut lob"), ("amber eyes", "hazel eyes", "deep brown eyes", "green-brown eyes")),
            ("warm Indigenous Latina-European mixed skin", ("long dark waves", "dark-blonde curls", "black braid", "soft chestnut hair"), ("hazel eyes", "brown eyes", "green-brown eyes", "dark brown eyes")),
            ("deep Indigenous Latina skin", ("thick black curls", "long straight black hair", "dark braid", "soft black waves"), ("dark brown eyes", "deep brown eyes", "brown eyes", "amber-brown eyes")),
            ("golden Pacific Islander-Asian mixed skin", ("long black waves", "thick dark curls", "soft black hair", "straight black ponytail"), ("brown eyes", "dark brown eyes", "deep brown eyes", "soft brown eyes")),
            ("warm Pacific Islander-European mixed skin", ("soft dark-brown waves", "long black curls", "dark-blonde beach waves", "black hair with brown tips"), ("green-brown eyes", "brown eyes", "hazel eyes", "deep brown eyes")),
            ("warm Middle-Eastern-Latina mixed skin", ("voluminous dark curls", "long black waves", "caramel-highlighted hair", "soft chestnut bob"), ("amber eyes", "hazel eyes", "brown eyes", "green-brown eyes")),
            ("golden Mediterranean-Latina mixed skin", ("long caramel curls", "dark wavy lob", "soft chestnut waves", "black ponytail"), ("light brown eyes", "hazel eyes", "green-hazel eyes", "amber eyes")),
            ("warm Turkish-European mixed skin", ("dark wavy ponytail", "soft brunette curls", "long chestnut hair", "black bob"), ("hazel eyes", "green eyes", "amber-brown eyes", "deep brown eyes")),
            ("warm Persian-South Asian mixed skin", ("glossy black waves", "thick dark curls", "long black braid", "soft chestnut hair"), ("deep brown eyes", "amber eyes", "hazel-brown eyes", "dark brown eyes")),
            ("deep African-European mixed skin", ("brown curls with blonde tips", "long box braids", "soft chestnut curls", "voluminous natural hair"), ("hazel eyes", "golden brown eyes", "green-brown eyes", "dark brown eyes")),
            ("warm African-Latina mixed skin", ("long dark curls", "black curls with caramel tips", "braided ponytail", "soft chestnut waves"), ("golden brown eyes", "hazel eyes", "dark brown eyes", "amber eyes")),
            ("deep African-East Asian mixed skin", ("sleek black bob", "long black braids", "soft black curls", "glossy black ponytail"), ("dark almond eyes", "deep brown eyes", "soft brown eyes", "dark eyes")),
            ("warm African-South Asian mixed skin", ("long braided black hair", "thick dark curls", "soft black waves", "black hair in a bun"), ("deep brown eyes", "golden brown eyes", "dark brown eyes", "amber-brown eyes")),
            ("rich African-Pacific Islander mixed skin", ("voluminous black curls", "long locs", "thick black waves", "braided black hair"), ("dark brown eyes", "deep brown eyes", "golden brown eyes", "amber eyes")),
            ("warm African-Middle-Eastern mixed skin", ("soft dark curls", "long black waves", "braided updo", "dark chestnut curls"), ("amber-brown eyes", "deep brown eyes", "hazel eyes", "golden brown eyes")),
            ("deep African-Caribbean Latina mixed skin", ("long locs with copper tips", "dark curls", "voluminous natural hair", "braided black ponytail"), ("honey-brown eyes", "dark brown eyes", "golden brown eyes", "amber eyes")),
            ("warm African-Native American mixed skin", ("thick black waves", "long braids", "soft dark curls", "straight black hair"), ("dark brown eyes", "deep brown eyes", "amber-brown eyes", "brown eyes")),
            ("golden African-Southeast Asian mixed skin", ("black curls in a high puff", "long black waves", "sleek black bob", "braided black hair"), ("brown eyes", "deep brown eyes", "soft brown eyes", "dark brown eyes")),
            ("warm African-Central Asian mixed skin", ("dark brown waves", "soft black curls", "long braided hair", "chestnut ponytail"), ("hazel-brown eyes", "golden brown eyes", "amber eyes", "dark brown eyes")),
            ("warm African-Mediterranean mixed skin", ("long dark curls", "soft brunette waves", "braided black hair", "black bob with caramel tips"), ("hazel eyes", "golden brown eyes", "green-brown eyes", "deep brown eyes")),
            ("deep African-Indigenous Latina mixed skin", ("thick black curls", "long dark braids", "voluminous natural hair", "soft black waves"), ("dark brown eyes", "golden brown eyes", "amber-brown eyes", "deep brown eyes")),
            ("warm African-Arab mixed skin", ("soft black curls", "long dark waves", "braided updo", "sleek black ponytail"), ("amber eyes", "deep brown eyes", "hazel-brown eyes", "dark brown eyes")),
            ("golden East Asian-Pacific Islander mixed skin", ("long black waves", "soft dark curls", "straight black hair", "black ponytail"), ("soft brown eyes", "dark almond eyes", "brown eyes", "deep brown eyes")),
            ("warm East Asian-Indigenous Latina mixed skin", ("long black hair", "dark waves with caramel tips", "straight black bob", "soft brunette curls"), ("dark brown eyes", "soft brown eyes", "hazel-brown eyes", "deep brown eyes")),
            ("warm East Asian-South Asian-Middle-Eastern mixed skin", ("glossy black waves", "long dark curls", "soft black bob", "thick black braid"), ("deep brown eyes", "amber eyes", "soft brown eyes", "hazel-brown eyes")),
            ("golden Southeast Asian-South Asian mixed skin", ("long glossy black hair", "dark braid", "soft black waves", "black hair in a high bun"), ("dark brown eyes", "deep brown eyes", "brown eyes", "amber-brown eyes")),
            ("warm Latina-South Asian-European mixed skin", ("dark chestnut waves", "long black curls", "caramel-highlighted hair", "soft brunette bob"), ("hazel eyes", "amber eyes", "brown eyes", "green-brown eyes")),
            ("warm Latina-Middle-Eastern-European mixed skin", ("voluminous dark curls", "long chestnut hair", "soft black waves", "caramel ponytail"), ("amber eyes", "hazel eyes", "brown eyes", "green eyes")),
            ("golden Indigenous-Pacific Islander mixed skin", ("long dark waves", "thick black curls", "dark braid", "soft black hair"), ("dark brown eyes", "brown eyes", "deep brown eyes", "amber eyes")),
            ("warm European-Middle-Eastern mixed skin", ("long chestnut waves", "dark curls", "black ponytail", "soft auburn hair"), ("green-brown eyes", "hazel eyes", "amber eyes", "blue-gray eyes")),
            ("fair European-Latina mixed skin", ("long caramel waves", "dark-blonde curls", "soft brunette bob", "copper ponytail"), ("hazel eyes", "green eyes", "brown eyes", "blue-gray eyes")),
            ("warm European-South Asian-Latina mixed skin", ("long dark waves", "caramel-highlighted curls", "soft chestnut hair", "black braid"), ("hazel-green eyes", "amber eyes", "brown eyes", "deep brown eyes")),
            ("deep African-East Asian-Latina mixed skin", ("long black curls", "sleek black bob", "braided dark hair", "soft waves with caramel tips"), ("dark brown eyes", "soft brown eyes", "golden brown eyes", "hazel eyes")),
            ("warm African-European-East Asian mixed skin", ("soft brown curls", "black bob with bangs", "long braids", "dark-blonde waves"), ("hazel eyes", "soft brown eyes", "green-brown eyes", "dark brown eyes")),
            ("golden African-South Asian-Latina mixed skin", ("long dark curls", "thick black braid", "voluminous black hair", "caramel-highlighted waves"), ("golden brown eyes", "deep brown eyes", "amber eyes", "hazel eyes")),
            ("warm Indigenous-East Asian-European mixed skin", ("long black waves", "soft chestnut hair", "straight black bob", "dark-blonde ponytail"), ("hazel-brown eyes", "soft brown eyes", "green eyes", "dark brown eyes")),
        ]
    )
)

MATURE_WOMEN = [
    # White / European
    ("woman", "late 20s adult", "slim", "warm tanned skin", "red hair in soft waves", "green eyes"),
    ("woman", "late 20s adult", "curvy", "pale skin", "platinum-blonde lob", "gray eyes"),
    ("woman", "40s adult", "slim", "fair skin", "auburn waves", "green eyes"),
    ("woman", "50s adult", "average", "warm beige skin", "brunette bob", "brown eyes"),
    # Mediterranean / Latina
    ("woman", "30s adult", "toned", "olive Mediterranean skin", "dark ponytail", "amber eyes"),
    ("woman", "30s adult", "busty", "sun-kissed Latina skin", "long chestnut waves", "dark brown eyes"),
    ("woman", "late 20s adult", "hourglass", "olive skin", "long dark waves", "amber eyes"),
    # East Asian
    ("woman", "late 20s adult", "slim", "fair East Asian skin", "long straight black hair", "dark almond eyes"),
    ("woman", "40s adult", "hourglass", "warm East Asian skin", "sleek black bob with bangs", "dark almond eyes"),
    # Southeast Asian
    ("woman", "30s adult", "curvy", "warm Southeast Asian tan skin", "black hair in a sleek bob", "dark brown eyes"),
    # South Asian
    ("woman", "30s adult", "plus-size", "deep South Asian brown skin", "long glossy black hair", "dark brown eyes"),
    ("woman", "40s adult", "plus-size", "warm South Asian brown skin", "dark bun with grays", "brown eyes"),
    # Black / African
    ("woman", "40s adult", "curvy", "deep brown African skin", "black shoulder-length curls", "dark brown eyes"),
    ("woman", "50s adult", "curvy", "rich dark African skin", "silver locs", "golden brown eyes"),
    # Extended (balanced additions)
    ("woman", "30s adult", "athletic", "fair skin", "dark pixie cut", "blue eyes"),
    ("woman", "late 20s adult", "curvy", "light East Asian skin", "long straight black hair", "dark almond eyes"),
    ("woman", "40s adult", "average", "warm caramel Latina skin", "shoulder-length brown waves", "hazel eyes"),
    # Extended round 2
    ("woman", "late 20s adult", "athletic", "warm East Asian skin", "long black hair in soft waves", "dark almond eyes"),
    ("woman", "30s adult", "hourglass", "fair skin", "long auburn waves", "green eyes"),
    ("woman", "30s adult", "curvy", "sun-kissed Latina skin", "dark balayage waves", "brown eyes"),
    ("woman", "40s adult", "slim", "light East Asian skin", "sleek black bob", "dark brown eyes"),
    ("woman", "40s adult", "curvy", "olive Mediterranean skin", "shoulder-length dark waves", "hazel eyes"),
    ("woman", "late 20s adult", "busty", "deep brown African skin", "natural curls", "dark brown eyes"),
    ("woman", "30s adult", "athletic", "warm Southeast Asian tan skin", "long black ponytail", "dark brown eyes"),
    ("woman", "50s adult", "average", "fair skin", "silver-blonde bob", "blue eyes"),
    ("woman", "30s adult", "hourglass", "warm South Asian brown skin", "long glossy black waves", "deep brown eyes"),
    ("woman", "40s adult", "curvy", "warm caramel Latina skin", "long dark waves", "amber eyes"),
    ("woman", "late 20s adult", "slim", "pale skin", "platinum pixie cut", "gray eyes"),
    ("woman", "50s adult", "curvy", "warm beige skin", "auburn shoulder-length waves", "green eyes"),
    ("woman", "30s adult", "athletic", "bronze skin", "dark high ponytail", "brown eyes"),
]

_MATURE_EXPANSION_AGES = (
    "late 20s adult",
    "early 30s adult",
    "mid 30s adult",
    "late 30s adult",
    "early 40s adult",
    "mid 40s adult",
    "late 40s adult",
    "early 50s adult",
    "mid 50s adult",
    "late 50s adult",
)

_MATURE_EXPANSION_BODIES = (
    "slim",
    "toned",
    "athletic",
    "average",
    "curvy",
    "soft curvy",
    "curvy athletic",
    "hourglass",
    "slim busty",
    "busty",
    "busty curvy",
    "plus-size",
)


def _mature_hair_variant(hair: str, age: str, index: int) -> str:
    if "50s" in age:
        templates = (
            "{hair} with graceful silver streaks",
            "silver-streaked {hair}",
            "{hair} with polished silver highlights",
            "{hair} with elegant silver strands",
        )
    elif "40s" in age:
        templates = (
            "{hair}",
            "{hair} with subtle silver strands",
            "polished {hair}",
            "{hair} with face-framing layers",
        )
    else:
        templates = (
            "{hair}",
            "polished {hair}",
            "{hair} with face-framing layers",
            "{hair} styled in a sophisticated shape",
        )
    return templates[index % len(templates)].format(hair=hair)


def _expand_mature_demographics_from_young(source_pool: list[tuple[str, str, str, str, str, str]]) -> list[tuple[str, str, str, str, str, str]]:
    """Project the broad young-adult demographic coverage into mature ages.

    Mature rows reuse the same skin/heritage coverage but rotate mature age bands,
    body descriptors, and age-appropriate hair styling.
    """
    seen = set(MATURE_WOMEN)
    expanded: list[tuple[str, str, str, str, str, str]] = []
    for index, (_, _, _, skin, hair, eyes) in enumerate(source_pool):
        age = _MATURE_EXPANSION_AGES[(index * 3) % len(_MATURE_EXPANSION_AGES)]
        body = _MATURE_EXPANSION_BODIES[(index * 5) % len(_MATURE_EXPANSION_BODIES)]
        entry = (
            "woman",
            age,
            body,
            skin,
            _mature_hair_variant(hair, age, index),
            eyes,
        )
        if entry not in seen:
            seen.add(entry)
            expanded.append(entry)
    return expanded


MATURE_WOMEN.extend(_expand_mature_demographics_from_young(YOUNG_WOMEN))

MEN = [
    ("man", "late 20s adult", "slim", "warm tan skin", "curly black hair", "brown eyes"),
    ("man", "late 20s adult", "athletic", "golden skin", "tousled dark-brown hair", "green eyes"),
    ("man", "late 20s adult", "lean athletic", "warm Southeast Asian tan skin", "tousled black hair", "dark brown eyes"),
    ("man", "30s adult", "average", "fair skin", "short dark hair", "blue eyes"),
    ("man", "30s adult", "athletic", "light East Asian skin", "short straight black hair", "dark almond eyes"),
    ("man", "30s adult", "muscular", "olive skin", "dark wavy hair", "hazel eyes"),
    ("man", "30s adult", "stocky", "olive skin", "dark wavy hair", "hazel eyes"),
    ("man", "30s adult", "fat", "medium brown skin", "shaved head", "dark eyes"),
    ("man", "30s adult", "lean athletic", "tanned skin", "slicked-back black hair", "dark brown eyes"),
    ("man", "40s adult", "dad bod", "warm skin tone", "shaved head", "kind brown eyes"),
    ("man", "40s adult", "broad", "deep brown skin", "trim beard", "amber eyes"),
    ("man", "40s adult", "average", "fair skin", "salt-and-pepper hair", "green eyes"),
    ("man", "40s adult", "fat", "warm beige skin", "curly dark hair", "brown eyes"),
    ("man", "40s adult", "muscular", "bronze skin", "short cropped hair with stubble", "steel-gray eyes"),
    ("man", "50s adult", "stocky", "light olive skin", "gray hair", "kind eyes"),
    ("man", "50s adult", "broad", "tanned skin", "gray beard", "blue-gray eyes"),
    ("man", "50s adult", "average", "deep brown skin", "short silver hair", "dark eyes"),
    ("man", "60s adult", "average", "fair skin", "silver hair and moustache", "blue eyes"),
    ("man", "60s adult", "fat", "warm brown skin", "bald head", "soft brown eyes"),
    ("man", "60s adult", "slim", "olive skin", "long gray hair tied back", "green eyes"),
    ("man", "30s adult", "broad", "deep brown African skin", "short afro", "dark brown eyes"),
    ("man", "40s adult", "average", "warm South Asian brown skin", "salt-and-pepper hair", "dark eyes"),
    # Extended round 2
    ("man", "late 20s adult", "athletic", "warm olive skin", "dark tousled hair", "brown eyes"),
    ("man", "30s adult", "muscular", "deep brown African skin", "short fade", "dark brown eyes"),
    ("man", "30s adult", "lean athletic", "light East Asian skin", "tousled black hair", "dark brown eyes"),
    ("man", "40s adult", "broad", "warm tan skin", "salt-and-pepper stubble", "hazel eyes"),
    ("man", "late 20s adult", "slim", "fair skin", "wavy auburn hair", "green eyes"),
    ("man", "30s adult", "muscular", "bronze Latino skin", "dark slicked-back hair", "brown eyes"),
    ("man", "50s adult", "fit", "olive skin", "gray-flecked beard", "blue-gray eyes"),
    ("man", "40s adult", "stocky", "warm South Asian brown skin", "short dark hair", "dark eyes"),
    ("man", "30s adult", "athletic", "tanned skin", "dark hair in a man bun", "hazel eyes"),
    ("man", "late 20s adult", "lean", "deep brown skin", "short twists", "dark brown eyes"),
]

_MEN_EXPANSION_AGES = (
    "late 20s adult",
    "early 30s adult",
    "mid 30s adult",
    "late 30s adult",
    "early 40s adult",
    "mid 40s adult",
    "late 40s adult",
    "early 50s adult",
    "mid 50s adult",
    "late 50s adult",
    "early 60s adult",
    "mid 60s adult",
    "late 60s adult",
    "early 70s adult",
    "mid 70s adult",
    "late 70s adult",
    "early 80s adult",
)

_MEN_EXPANSION_BODIES = (
    "lean",
    "slim",
    "lanky",
    "average",
    "fit",
    "athletic",
    "lean athletic",
    "muscular",
    "broad",
    "stocky",
    "burly",
    "dad bod",
    "heavyset",
    "soft-bodied",
    "fat",
)


def _men_hair_variant(skin: str, age: str, index: int) -> str:
    skin_lower = skin.lower()
    if "african" in skin_lower:
        younger = ("short fade", "close-cropped curls", "short afro", "neat twists", "locs tied back", "shaved head with trimmed beard")
        older = ("silver-flecked short afro", "gray beard with shaved head", "salt-and-pepper locs tied back", "close-cropped gray curls", "bald head with neat gray beard")
    elif any(k in skin_lower for k in ("east asian", "southeast asian", "south asian", "central asian", "japanese", "korean", "chinese", "thai", "filipina", "indian", "pakistani", "kazakh", "uighur")):
        younger = ("short straight black hair", "neat black undercut", "tousled black hair", "black hair tied back", "shaved head with stubble", "dark side-parted hair")
        older = ("salt-and-pepper black hair", "silver-streaked black hair", "short gray-black hair", "balding black hair with stubble", "neatly combed silver hair")
    elif any(k in skin_lower for k in ("european", "fair", "pale", "ivory", "nordic", "celtic", "slavic", "baltic", "irish", "french")):
        younger = ("short dark-blonde hair", "wavy brown hair", "tousled chestnut hair", "close-cropped auburn hair", "shaved head with light stubble", "slicked-back dark hair")
        older = ("silver hair", "salt-and-pepper hair", "gray-flecked beard", "short white hair", "bald head with silver stubble", "silver ponytail")
    else:
        younger = ("short dark hair", "dark wavy hair", "slicked-back black hair", "trim beard", "shaved head with stubble", "dark hair tied back")
        older = ("salt-and-pepper hair", "gray beard", "silver-streaked dark hair", "bald head with trimmed beard", "short silver hair", "long gray hair tied back")
    pool = older if any(decade in age for decade in ("60s", "70s", "80s")) else younger
    return pool[index % len(pool)]


def _expand_men_demographics_from_young(source_pool: list[tuple[str, str, str, str, str, str]]) -> list[tuple[str, str, str, str, str, str]]:
    seen = set(MEN)
    expanded: list[tuple[str, str, str, str, str, str]] = []
    for index, (_, _, _, skin, _, eyes) in enumerate(source_pool):
        entry = (
            "man",
            _MEN_EXPANSION_AGES[(index * 5) % len(_MEN_EXPANSION_AGES)],
            _MEN_EXPANSION_BODIES[(index * 7) % len(_MEN_EXPANSION_BODIES)],
            skin,
            _men_hair_variant(skin, _MEN_EXPANSION_AGES[(index * 5) % len(_MEN_EXPANSION_AGES)], index),
            eyes,
        )
        if entry not in seen:
            seen.add(entry)
            expanded.append(entry)
    return expanded


MEN.extend(_expand_men_demographics_from_young(YOUNG_WOMEN))

# Skewed toward revealing, sexy pin-up styling while staying non-explicit
# (lingerie, swimwear, crop tops, garters, cleavage, etc.).
WOMEN_CLOTHES = [
    "lacy garter belt with sheer stockings and a fitted satin bodice",
    "cropped halter top with deep cleavage and high-cut denim shorts",
    "plunging-neckline bodysuit with a sheer wrap skirt",
    "off-shoulder corset top with a short pleated skirt",
    "string bikini top with a sarong tied low on the hips",
    "sheer babydoll slip over a matching lingerie set",
    "deep-V wrap dress with a thigh-high slit",
    "lace bralette with high-waisted garter shorts",
    "cropped mesh long-sleeve over a bandeau and mini skirt",
    "satin slip dress with thin straps and a low open back",
    "oversized unbuttoned shirt over a lacy lingerie set",
    "leather bustier with a buckled mini skirt",
    "knit crop sweater slipping off one shoulder over high-cut briefs",
    "halterneck monokini with cut-out sides",
    "bodycon mini dress with a plunging neckline",
    "tied gingham crop top with denim cut-off shorts",
    "sheer mesh cover-up over a triangle bikini",
    "corset-laced top with a flowing maxi skirt and a high slit",
    "velvet bralette with matching tap shorts and a garter",
    "fishnet stockings with a wet-look bodysuit",
    "micro cardigan buttoned once over a lace bra and mini skirt",
    "cropped tube top with low-rise leather pants",
    "silk camisole with delicate lace trim and boy shorts",
    "open-back halter dress with a daring neckline",
    "athletic sports bra with cropped leggings and a bare midriff",
    "feathered burlesque corset with opera gloves and stockings",
    "high-cut one-piece swimsuit with a plunging front",
    "draped silk wrap revealing one shoulder and a long leg",
    "tied flannel over a bikini top with denim cut-offs",
    "lace teddy under a sheer robe slipping open",
    "metallic bandeau with high-waisted festival shorts",
    "ribbed crop tank with cleavage and tiny yoga shorts",
    "fitted off-shoulder satin gown with a thigh slit and elegant earrings",
    "sleek backless cocktail dress with a plunging neckline",
    "plunging halter jumpsuit with a backless cut",
    "sheer lace blouse over a satin bralette with a pencil skirt",
    "cut-out evening gown with a high slit and an open back",
    "bodycon bandage dress with cut-out sides",
    "wrap blouse knotted to bare the midriff with tailored shorts",
    "sequined slip dress with a plunging cowl neck",
    "off-shoulder knit dress clinging to the figure",
    "lace-panel cocktail dress with a sheer midriff",
    "satin corset top with wide-leg trousers",
    "halter mini dress with an open lace-up back",
    "deep-V wrap blouse with a leather mini skirt",
    "fitted turtleneck bodysuit with high-cut briefs and tights",
    "mesh-panel athletic set with a bare midriff",
    "draped one-shoulder gown with a thigh slit",
    "cropped blazer worn with little underneath and tailored shorts",
    "silk pajama shirt left mostly unbuttoned with shorts",
    "ribbed knit two-piece with a crop top and a mini skirt",
    "bodycon midi dress with a daring back cut-out",
    "satin slip with a feather-trimmed robe",
    "lace bodysuit under high-waisted wide trousers",
    "halterneck catsuit with a plunging zipper",
    "sheer maxi skirt over bikini bottoms with a crop top",
    "corset-back denim mini with a bandeau",
    "off-shoulder peplum top with a thigh-high slit skirt",
    "metallic mini dress with cut-out waist panels",
    "wrap-front maxi with a slit to the hip and a low neckline",
]


def _extend_unique(target: list[str], additions: list[str]) -> None:
    seen = set(target)
    for item in additions:
        if item not in seen:
            seen.add(item)
            target.append(item)


def _expand_women_full_clothes() -> list[str]:
    additions: list[str] = []

    lingerie_tops = (
        "embroidered lace balconette bra",
        "satin longline bra",
        "strappy lace bralette",
        "velvet demi-cup bra",
        "mesh-panel bustier",
        "silk corset bustier",
        "scalloped lace bodysuit",
        "lace-trim teddy",
        "underwire bralette",
        "sheer lace blouse over a satin bra",
        "ribbon-laced corset top",
        "satin plunge bodysuit",
    )
    lingerie_bottoms = (
        "high-waisted garter shorts",
        "lace tap shorts",
        "sheer stockings and a garter belt",
        "high-cut briefs",
        "satin tap shorts",
        "lace pencil skirt",
        "low-slung silk shorts",
        "thigh-high stockings",
        "ruffled mini skirt",
        "high-waisted satin shorts",
    )
    for top in lingerie_tops:
        for bottom in lingerie_bottoms:
            additions.append(f"{top} with {bottom}")

    swim_tops = (
        "triangle bikini top",
        "bandeau bikini top",
        "underwire bikini top",
        "halter bikini top",
        "crochet bikini top",
        "metallic bikini top",
        "wrap-front bikini top",
        "sporty zip-front bikini top",
        "plunging halterneck swimsuit",
        "high-cut one-piece swimsuit",
        "cut-out monokini",
        "ruched balconette swim top",
    )
    swim_bottoms = (
        "high-cut swim bottoms",
        "tie-side bikini bottoms",
        "a low-tied sarong",
        "a semi-sheer beach skirt",
        "high-waisted swim briefs",
        "a draped resort wrap",
        "tiny board shorts",
        "a sheer mesh cover-up skirt",
        "a knotted linen wrap",
        "a beaded hip-chain detail",
    )
    for top in swim_tops:
        for bottom in swim_bottoms:
            additions.append(f"{top} with {bottom}")

    crop_tops = (
        "ribbed crop tank",
        "deep-V cropped blouse",
        "tie-front gingham crop top",
        "off-shoulder knit crop top",
        "cropped mesh long-sleeve",
        "cropped satin camisole",
        "micro cardigan buttoned once",
        "backless halter crop top",
        "one-shoulder crop top",
        "lace-up corset crop top",
        "cropped blazer with a bralette underneath",
        "plunging wrap blouse knotted at the waist",
    )
    crop_bottoms = (
        "high-cut denim shorts",
        "low-rise leather pants",
        "a pleated mini skirt",
        "a thigh-slit pencil skirt",
        "tailored short shorts",
        "a corset-back denim mini skirt",
        "a satin wrap skirt",
        "tiny yoga shorts",
        "high-waisted festival shorts",
        "a leather mini skirt",
        "a flowing maxi skirt slit high on one leg",
        "wide-leg trousers worn low at the waist",
    )
    for top in crop_tops:
        for bottom in crop_bottoms:
            additions.append(f"{top} with {bottom}")

    dresses = (
        "deep-V wrap dress",
        "sleek backless cocktail dress",
        "bodycon mini dress",
        "satin slip dress",
        "halter mini dress",
        "off-shoulder knit dress",
        "sequined slip dress",
        "draped one-shoulder gown",
        "cut-out evening gown",
        "metallic mini dress",
        "lace-panel cocktail dress",
        "open-back halter dress",
        "plunging cowl-neck slip dress",
        "corset-waist party dress",
        "low-back velvet midi dress",
        "wrap-front maxi dress",
    )
    dress_details = (
        "with a thigh-high slit",
        "with a low open back",
        "with a plunging neckline",
        "with sheer lace side panels",
        "with a lace-up back",
        "with cut-out waist panels",
        "with thin straps and a soft drape",
        "with a high slit and elegant earrings",
    )
    for dress in dresses:
        for detail in dress_details:
            additions.append(f"{dress} {detail}")

    boudoir_bases = (
        "lace teddy",
        "satin slip chemise",
        "silk camisole and tap shorts",
        "lace bodysuit",
        "satin bustier and stockings",
        "feathered burlesque corset",
        "velvet bralette and tap shorts",
        "silk pajama shirt left mostly unbuttoned",
        "lace bra set with high-waisted briefs",
        "plunging satin robe dress",
    )
    boudoir_layers = (
        "under a sheer robe slipping open",
        "under an oversized unbuttoned shirt",
        "with opera gloves and stockings",
        "with a feather-trimmed robe",
        "with a draped silk wrap",
        "with a semi-sheer kimono robe",
        "with lace-top thigh-highs",
        "with a cropped cashmere cardigan falling off one shoulder",
    )
    for base in boudoir_bases:
        for layer in boudoir_layers:
            additions.append(f"{base} {layer}")

    editorial_tops = (
        "tailored waistcoat worn over a lace bra",
        "cropped tuxedo jacket with a satin bralette",
        "open silk blouse over a lace camisole",
        "structured satin corset top",
        "strapless bustier top",
        "sheer organza blouse over a bandeau",
        "leather bustier top",
        "plunging halter jumpsuit",
        "rhinestone bralette under a cropped jacket",
        "corset-laced peplum top",
    )
    editorial_bottoms = (
        "tailored trousers",
        "a high-slit pencil skirt",
        "wide-leg satin trousers",
        "a leather mini skirt",
        "a floor-length slit skirt",
        "high-waisted shorts",
        "a fitted midi skirt",
        "low-slung tuxedo pants",
    )
    for top in editorial_tops:
        for bottom in editorial_bottoms:
            additions.append(f"{top} with {bottom}")

    themed_outfits = (
        "retro sailor-inspired halter bodysuit with high-waisted shorts",
        "pin-up polka-dot crop top with tied shorts and wedge heels",
        "vintage bathing suit with a belted waist and plunging front",
        "cabaret corset with fishnet stockings and satin gloves",
        "resort linen shirt knotted over a bikini with a low sarong",
        "glitter festival bralette with a sheer maxi skirt",
        "rhinestone cowgirl crop top with denim cut-offs",
        "lace-up moto bustier with glossy leggings",
        "tennis-inspired pleated mini skirt with a cropped polo unbuttoned at the collar",
        "ballet-wrap cardigan over a lace bralette with a chiffon skirt",
        "velvet lounge set with a cropped cami and tap shorts",
        "yacht-club bikini under an open linen blazer",
        "spa robe loosely belted over a satin slip",
        "tropical pareo dress tied low over a bikini",
        "noir satin gown with a deep neckline and long gloves",
        "art-deco beaded mini dress with a plunging back",
        "latex-look bodysuit with sheer stockings and ankle boots",
        "soft knit two-piece with a cropped cardigan and mini skirt",
        "silk scarf top with a slit skirt",
        "crystal-trim bralette with a high-waisted pencil skirt",
        "open-back romper with a plunging front",
        "lace-up halter catsuit with a deep zipper front",
        "cropped racing jacket over a bandeau with leather shorts",
        "beach cabana wrap dress over a triangle bikini",
        "glamorous champagne-colored slip with a low cowl back",
        "sheer lace maxi dress over a matching bodysuit",
        "corseted denim romper with a low neckline",
        "off-shoulder satin mini dress with garter-style straps",
        "silk bandeau with palazzo pants slit at both sides",
        "embroidered mesh bodysuit with a satin overskirt",
    )
    additions.extend(themed_outfits)
    return additions


_extend_unique(WOMEN_CLOTHES, _expand_women_full_clothes())

MEN_CLOTHES = [
    "open tropical shirt over a bare chest with board shorts",
    "unbuttoned linen shirt revealing a toned chest with rolled trousers",
    "fitted tank top with low-slung joggers",
    "swim shorts with a towel over one shoulder, shirtless",
    "leather jacket over a bare chest with dark jeans",
    "rolled-sleeve button shirt left open over a fitted vest",
    "snug henley with sleeves pushed up and tailored trousers",
    "sleeveless gym shirt with shorts and a gym towel",
    "fitted chef jacket unbuttoned at the collar with an apron",
    "soft resort shirt open over a tank top with swim shorts",
    "black shirt with rolled sleeves and relaxed trousers",
    "denim jacket over a fitted tee with worn jeans",
    "light blazer over an open-collar shirt with chinos",
    "barber-style suspenders over an open shirt with bare forearms",
    "camp flannel open over a fitted tee with rugged pants",
    "wetsuit peeled to the waist on a beach",
    "tailored waistcoat over a bare chest with dress trousers",
    "open silk shirt with a thin chain over a toned chest",
    "fitted polo with the sleeves pushed up and chinos",
    "henley unbuttoned at the collar with rugged jeans",
    "linen suit with the shirt open at the chest",
    "leather vest over bare arms with dark trousers",
    "rolled-cuff dress shirt half-unbuttoned with suspenders",
    "fitted turtleneck under a tailored blazer",
    "open flannel over a fitted tank with worn jeans",
    "snug crew tee tucked into belted trousers",
    "mechanic's coveralls peeled to the waist over a tank",
    "tuxedo shirt unbuttoned with the bow tie hanging loose",
]

SCENES = [
    ("office", "cozy office desk with warm lamp light and papers"),
    ("balcony", "brick city balcony at golden sunrise"),
    ("garden_rain", "lush garden during gentle summer rain"),
    ("kitchen", "soft-focus kitchen with flowers and bright window light"),
    ("bedroom", "minimal cozy bedroom with muted bedding and warm decor"),
    ("studio", "simple studio backdrop with visible paper texture"),
    ("cafe", "small cafe table by a softly lit street window"),
    ("poolside", "poolside patio with sunset plants and lounge chairs"),
    ("flower_market", "pastel flower market with warm stalls"),
    ("garage", "clean garage doorway with soft industrial lighting"),
    ("vineyard", "vineyard at dusk with a glass of wine"),
    ("bookshop", "cozy bookshop shelves and warm counter light"),
    ("nightclub", "soft nightclub lights and blurred dance floor"),
    ("laundromat", "clean laundromat with folded towels"),
    ("dance_studio", "dance studio with mirror glow and wood floor"),
    ("beach_bar", "beach bar at golden sunset"),
    ("sailboat", "sailboat deck with ocean sunset"),
    ("library", "quiet library aisle with warm lamplight"),
    ("picnic", "summer picnic blanket in a soft park setting"),
    ("train_station", "vintage train station with evening lights"),
    ("hotel_lobby", "hotel lobby with brass lights and polished stone"),
    ("rooftop", "rooftop party with city lights and warm string lights"),
    ("tailor_shop", "tailor shop with fabric swatches and warm mirrors"),
    ("market", "outdoor produce market with pastel stalls"),
    ("artist_studio", "art studio with canvases and soft afternoon light"),
    ("greenhouse", "greenhouse with leaves and diffused glass light"),
    ("record_shop", "record shop aisle with warm neon accents"),
    ("bakery", "bakery counter with pastries and morning light"),
    ("museum", "quiet gallery wall with soft spotlights"),
    ("car_interior", "parked vintage car interior with sunset through windows"),
    ("boudoir", "candlelit boudoir with draped silks and a vanity mirror"),
    ("lingerie_boutique", "upscale lingerie boutique with soft pink lighting"),
    ("rooftop_pool", "rooftop infinity pool at night with city glow"),
    ("neon_bar", "neon-lit cocktail bar with moody shadows"),
    ("penthouse_window", "penthouse window at night overlooking city lights"),
    ("velvet_lounge", "velvet speakeasy lounge with warm amber light"),
    ("beach_sunset", "tropical beach at golden sunset with gentle surf"),
    ("spa", "steamy spa with soft towels and warm tile"),
    ("vintage_motel", "retro motel room with a glowing neon sign outside"),
    ("art_deco_suite", "art deco hotel suite with gold accents and soft lamps"),
    ("jacuzzi", "steamy outdoor jacuzzi at dusk"),
    ("yacht_deck", "luxury yacht deck under the sun"),
    ("tropical_villa", "open tropical villa with ocean breeze"),
    ("loft_window", "sunlit industrial loft with tall windows"),
    ("vanity_mirror", "vintage vanity with a bulb-lit mirror"),
    ("silk_bed", "rumpled silk-sheet bed in soft morning light"),
    ("pool_float", "swimming pool with a flamingo float"),
    ("sauna", "warm wooden sauna with soft steam"),
    ("beach_cabana", "white beach cabana with billowing curtains"),
    ("dressing_room", "theater dressing room with vanity bulbs"),
    ("desert_pool", "desert resort pool at golden hour"),
    ("garden_pool", "private garden pool surrounded by greenery"),
    ("claw_tub_bath", "marble bathroom with a claw-foot tub and soft steam"),
    ("walk_in_closet", "mirrored walk-in closet with warm boutique lighting"),
    ("four_poster", "draped four-poster bed in a sunlit room"),
    ("fireside_rug", "fireside lounge with a soft rug and warm glow"),
    ("sunroom", "glass sunroom full of plants and golden afternoon light"),
    ("attic_skylight", "cozy attic loft under a starlit skylight"),
    ("home_library", "warm home library nook with stacked books"),
    ("photo_studio", "photo studio with seamless paper backdrops and softboxes"),
    ("hot_spring", "steaming open-air hot spring among rocks"),
    ("lavender_field", "rolling lavender field at golden hour"),
    ("cherry_blossom", "cherry-blossom park in soft spring light"),
    ("autumn_forest", "autumn forest path with falling leaves"),
    ("lakeside_dock", "wooden lakeside dock at calm sunrise"),
    ("mountain_cabin", "mountain cabin porch with a valley view"),
    ("snowy_chalet", "snowy chalet window with warm firelight inside"),
    ("waterfall_pool", "secluded jungle waterfall pool"),
    ("desert_dunes", "desert dunes at dusk with long shadows"),
    ("marina", "marina boardwalk lined with white yachts at sunset"),
    ("jazz_club", "smoky jazz club with warm stage light"),
    ("casino_floor", "glamorous casino floor with gold accents"),
    ("ferris_wheel", "ferris wheel cabin over city lights at night"),
    ("retro_diner", "retro diner booth with chrome and neon"),
    ("grand_staircase", "grand marble staircase beneath a chandelier"),
    ("tiki_bar", "tropical tiki bar with torchlight"),
    ("outdoor_shower", "rustic outdoor beach shower in dappled light"),
    ("infinity_spa", "infinity-edge spa pool overlooking the sea"),
    ("moonlit_pool", "moonlit pool with underwater lights glowing"),
    ("rain_window", "rainy night by a window streaked with droplets"),
    ("silk_tent", "draped silk tent with warm lantern light"),
    ("rose_garden", "rose garden in full bloom at golden hour"),
    ("champagne_bar", "marble champagne bar with soft gold light"),
    ("ballet_studio", "ballet studio with barres and morning light"),
    ("clifftop_pool", "cliffside infinity pool overlooking the ocean"),
    ("artist_loft", "paint-splattered artist loft with skylights"),
    ("vintage_boudoir", "vintage boudoir with a velvet chaise and lace curtains"),
    ("hammam", "ornate tiled hammam with warm steam"),
    ("onsen_night", "open-air onsen under a starry night"),
    ("cabana_night", "lantern-lit poolside cabana at night"),
    ("rooftop_garden", "lush rooftop garden with string lights"),
    ("greenhouse_dusk", "glass greenhouse glowing at dusk"),
    ("snow_sauna", "log sauna with a frosted window and snow outside"),
    ("desert_tent", "luxury desert tent with rugs and lanterns"),
    ("yacht_cabin", "cozy yacht cabin with portholes and warm light"),
    ("private_jet", "plush private jet cabin with soft cabin light"),
    ("vineyard_terrace", "vineyard terrace at sunset with a wine glass"),
    ("clifftop_villa", "whitewashed clifftop villa over the sea"),
    ("bamboo_spa", "bamboo spa pavilion with diffused light"),
    ("waterfall_grotto", "hidden grotto behind a waterfall"),
    ("autumn_cabin", "warm cabin interior with autumn light through the windows"),
    ("ski_lodge", "ski lodge lounge by a roaring fire"),
    ("beach_bonfire", "beach bonfire under a dusky sky"),
    ("starlit_rooftop", "rooftop deck under a blanket of stars"),
    ("opera_box", "gilded opera box with velvet seats"),
    ("speakeasy_booth", "leather speakeasy booth with low amber light"),
    ("gold_marble_bath", "opulent marble bath with gold fixtures"),
    ("silk_canopy_bed", "silk-canopied bed with sheer drapes"),
    ("garden_swing", "wicker hanging swing in a flowering garden"),
    ("poolhouse", "modern glass poolhouse with loungers"),
    ("rooftop_jacuzzi", "rooftop jacuzzi with the skyline behind"),
    ("tropical_waterfall", "tropical waterfall with mossy rocks"),
    ("lakeside_sauna", "lakeside sauna deck at sunrise"),
    ("powder_room", "vintage powder room with a tufted stool"),
    ("gallery_after_hours", "after-hours art gallery with track lighting"),
    ("sunset_daybed", "rooftop lounge daybed at sunset"),
    ("forest_spring", "misty forest hot spring among ferns"),
    ("candle_bath", "candlelit soaking tub with rose petals"),
]


def _scene_slug(text: str) -> str:
    return re.sub(r"[^a-z0-9]+", "_", text.lower()).strip("_")[:52]


def _extend_scene_unique(target: list[tuple[str, str]], additions: list[tuple[str, str]]) -> None:
    seen_slugs = {slug for slug, _ in target}
    seen_descriptions = {description for _, description in target}
    for slug, description in additions:
        if description in seen_descriptions:
            continue
        base_slug = slug
        suffix = 2
        while slug in seen_slugs:
            slug = f"{base_slug}_{suffix}"
            suffix += 1
        seen_slugs.add(slug)
        seen_descriptions.add(description)
        target.append((slug, description))


def _expand_scenes() -> list[tuple[str, str]]:
    bases = (
        # Interiors / boudoir / glam rooms
        ("art_deco_elevator_lobby", "art deco elevator lobby with brass doors and mirrored walls"),
        ("boutique_hotel_corridor", "boutique hotel corridor with patterned carpet and wall sconces"),
        ("marble_penthouse_foyer", "marble penthouse foyer with a sculptural staircase"),
        ("velvet_theater_box", "private velvet theater box with gilded trim"),
        ("opera_backstage", "opera backstage dressing area with costume racks"),
        ("old_hollywood_suite", "old Hollywood hotel suite with a velvet chaise"),
        ("designer_dressing_room", "designer dressing room with garment rails and vanity bulbs"),
        ("mirrored_powder_room", "mirrored powder room with polished brass fixtures"),
        ("paris_apartment", "Paris apartment with tall windows and herringbone floors"),
        ("midcentury_living_room", "midcentury living room with low furniture and wood paneling"),
        ("sunken_lounge", "sunken lounge with curved sofas and a conversation pit"),
        ("vinyl_listening_room", "vinyl listening room with speakers and stacked records"),
        ("private_screening_room", "private screening room with leather seats and dim aisle lights"),
        ("fashion_showroom", "fashion showroom with mannequins and fabric bolts"),
        ("jewelry_boutique", "jewelry boutique with glass cases and velvet displays"),
        ("perfume_counter", "perfume counter with crystal bottles and polished mirrors"),
        ("vintage_beauty_salon", "vintage beauty salon with chrome chairs and hair dryers"),
        ("makeup_trailer", "film-set makeup trailer with mirror lights and costume notes"),
        ("portrait_studio", "portrait studio with painted canvas backdrops"),
        ("ceramic_studio", "ceramic studio with clay shelves and a pottery wheel"),
        ("glass_artist_studio", "glass artist studio with colored panes and worktables"),
        ("florist_workroom", "florist workroom with buckets of flowers and ribbon"),
        ("candlelit_conservatory", "glass conservatory filled with plants and candles"),
        ("winter_garden_room", "indoor winter garden with palms and tiled floors"),
        ("collector_library", "collector library with ladders and leather-bound books"),
        ("private_gallery", "private art gallery with sculpture plinths"),
        ("loft_stairwell", "industrial loft stairwell with iron railings"),
        ("warehouse_photo_set", "converted warehouse photo set with canvas drops"),
        ("atelier_balcony", "artist atelier balcony overlooking rooftops"),
        ("tailors_fitting_room", "tailor's fitting room with mirrors and pin cushions"),
        ("grand_hotel_bar", "grand hotel bar with marble counters and amber glass shelves"),
        ("jazz_green_room", "jazz-club green room with velvet curtains and instrument cases"),
        ("speakeasy_hallway", "speakeasy hallway with patterned wallpaper and low sconces"),
        ("casino_private_room", "private casino room with card tables and gold accents"),
        ("champagne_cellar", "champagne cellar with arched brick and stacked bottles"),
        ("wine_library", "wine library with dark wood shelves and tasting glasses"),
        ("rooftop_greenhouse_room", "rooftop greenhouse room with city windows and leafy plants"),
        ("skylit_attic_studio", "skylit attic studio with trunks and soft fabric"),
        ("romantic_train_compartment", "vintage train compartment with polished wood and curtains"),
        ("luxury_rail_dining_car", "luxury rail dining car with linen tables and brass lamps"),
        ("private_jet_lounge", "private jet lounge cabin with cream leather seats"),
        ("yacht_saloon", "yacht saloon with varnished wood and porthole light"),
        # Urban / nightlife / public spaces
        ("neon_arcade", "retro neon arcade with glowing cabinets"),
        ("rainy_taxi_stand", "rainy taxi stand with streetlight reflections"),
        ("rooftop_cinema", "rooftop cinema with deck chairs and skyline lights"),
        ("city_fire_escape", "city fire escape overlooking brick alleys"),
        ("brownstone_stoop", "brownstone stoop with wrought-iron railings"),
        ("subway_tile_platform", "quiet tiled subway platform with vintage signage"),
        ("closed_department_store", "after-hours department store with display windows"),
        ("luxury_mall_atrium", "luxury mall atrium with skylights and polished stone"),
        ("hotel_elevator_bank", "hotel elevator bank with brass doors and patterned carpet"),
        ("parking_garage_rooftop", "parking garage rooftop with city lights"),
        ("rainy_neon_crosswalk", "rainy neon crosswalk with glossy pavement"),
        ("old_movie_marquee", "old movie theater marquee with warm bulbs"),
        ("drive_in_theater", "drive-in theater lot with a glowing screen"),
        ("retro_gas_station", "retro gas station with chrome pumps"),
        ("midnight_diner_counter", "midnight diner counter with chrome stools"),
        ("tiki_lounge_booth", "tiki lounge booth with carved wood and torchlight"),
        ("cocktail_lab", "cocktail lab bar with glassware and citrus bowls"),
        ("recording_booth", "recording booth with microphones and acoustic panels"),
        ("radio_station", "late-night radio station with glowing controls"),
        ("dancehall_balcony", "dancehall balcony overlooking a polished floor"),
        ("gallery_rooftop_party", "gallery rooftop party with art lights and city views"),
        ("fashion_week_backstage", "fashion-week backstage with clothing racks and makeup lights"),
        ("photobooth_corner", "vintage photobooth corner with velvet curtains"),
        ("bookstore_window_nook", "bookstore window nook with stacked novels"),
        ("flower_shop_storefront", "flower shop storefront with buckets of roses"),
        ("bakery_back_room", "bakery back room with flour-dusted counters"),
        ("tailor_shop_window", "tailor shop window with mannequins and fabric rolls"),
        ("hotel_rooftop_bar", "hotel rooftop bar with glass railings and skyline views"),
        # Resort / water / spa / luxury travel
        ("overwater_bungalow_deck", "overwater bungalow deck above turquoise water"),
        ("lagoon_boardwalk", "lagoon boardwalk with resort lights over the water"),
        ("white_sand_cabana", "white sand beach cabana with gauzy curtains"),
        ("cliffside_shower_alcove", "cliffside outdoor shower alcove with stone walls"),
        ("mosaic_spa_pool", "mosaic spa pool with steam and blue tile"),
        ("resort_changing_cabana", "resort changing cabana with striped curtains"),
        ("infinity_pool_steps", "infinity pool steps overlooking the horizon"),
        ("sunset_yacht_bow", "sunset yacht bow with polished railings"),
        ("marina_catwalk", "marina catwalk between white yachts"),
        ("sail_loft", "sail loft with canvas sails and rope coils"),
        ("boathouse_dock", "wooden boathouse dock with quiet water"),
        ("lakeside_daybed", "lakeside daybed beneath linen curtains"),
        ("hot_spring_grotto", "hot-spring grotto with mossy stone"),
        ("onsen_courtyard", "onsen courtyard with wooden screens and steam"),
        ("hammam_antechamber", "hammam antechamber with patterned tiles"),
        ("saltwater_plunge_pool", "saltwater plunge pool in a private courtyard"),
        ("rooftop_spa_terrace", "rooftop spa terrace with loungers and skyline haze"),
        ("desert_resort_daybed", "desert resort daybed under a canvas canopy"),
        ("moroccan_riad_pool", "Moroccan riad pool courtyard with zellige tile"),
        ("balinese_villa_pool", "Balinese villa pool with carved screens and palms"),
        ("santorini_terrace", "whitewashed Santorini terrace above the sea"),
        ("amalfi_balcony", "Amalfi balcony with lemon trees and ocean view"),
        ("capri_boat_deck", "Capri boat deck with striped cushions"),
        ("seaside_cliff_path", "seaside cliff path with wild grass and waves below"),
        ("beach_club_lounger", "beach club lounger row with umbrellas"),
        ("poolside_bar_stools", "poolside bar stools with reflected water light"),
        ("floating_breakfast_pool", "private pool with a floating breakfast tray"),
        ("waterfall_spa_pavilion", "waterfall spa pavilion with bamboo screens"),
        ("jungle_pool_lanai", "jungle pool lanai with tropical leaves"),
        ("cave_pool", "cave pool with shafts of light on blue water"),
        ("thermal_bath_hall", "thermal bath hall with columns and rising steam"),
        # Nature / seasonal / travel
        ("wildflower_hillside", "wildflower hillside with a winding path"),
        ("orchard_ladder", "fruit orchard with a wooden ladder and baskets"),
        ("olive_grove", "olive grove with silver leaves and stone walls"),
        ("sunflower_field", "sunflower field with a dirt path"),
        ("wheat_field", "wheat field with tall golden stalks"),
        ("red_rock_overlook", "red rock desert overlook with layered cliffs"),
        ("desert_oasis", "desert oasis with palms and still water"),
        ("cactus_garden", "cactus garden with terracotta pots"),
        ("rose_covered_archway", "rose-covered archway in a garden path"),
        ("wisteria_pergola", "wisteria pergola with hanging purple blooms"),
        ("bamboo_forest_path", "bamboo forest path with filtered light"),
        ("fern_grotto", "fern grotto with mist and mossy rocks"),
        ("rainforest_suspension_bridge", "rainforest suspension bridge among leaves"),
        ("coastal_cave", "coastal cave opening onto the sea"),
        ("black_sand_beach", "black sand beach with volcanic rocks"),
        ("tidal_pool_rocks", "tidal pool rocks with reflective water"),
        ("lighthouse_walkway", "lighthouse walkway above crashing waves"),
        ("misty_lake_boat", "misty lake with a small wooden boat"),
        ("covered_bridge", "covered bridge with autumn trees"),
        ("maple_forest_path", "maple forest path with red leaves"),
        ("snowy_balcony", "snowy balcony with pine trees and warm window light"),
        ("ice_hotel_suite", "ice hotel suite with carved translucent walls"),
        ("alpine_hot_tub_deck", "alpine hot tub deck with mountain views"),
        ("ski_chalet_balcony", "ski chalet balcony with wool blankets"),
        ("mountain_meadow", "mountain meadow with distant peaks"),
        ("vineyard_lane", "vineyard lane between grape rows"),
        ("lavender_courtyard", "lavender courtyard with stone planters"),
        ("tulip_greenhouse", "tulip greenhouse with rows of color"),
        ("moon_garden", "moon garden with pale flowers and stone paths"),
        ("botanical_palm_house", "botanical palm house with iron-and-glass arches"),
        # Editorial / stylized sets
        ("seamless_red_backdrop", "seamless red studio backdrop with softbox glow"),
        ("pastel_paper_set", "pastel paper studio set with simple geometric blocks"),
        ("checkerboard_photo_set", "checkerboard photo set with glossy floor"),
        ("chrome_curtain_set", "chrome curtain photo set with reflective strands"),
        ("velvet_drape_set", "velvet drape studio set with pooled fabric"),
        ("tropical_print_set", "tropical-print studio set with painted palm leaves"),
        ("noir_shadow_set", "noir shadow studio set with venetian-blind light"),
        ("pop_art_set", "pop-art studio set with oversized dots and color blocks"),
        ("magazine_cover_set", "magazine cover photo set with clean graphic lighting"),
        ("lingerie_editorial_set", "lingerie editorial studio set with soft curtains and floor cushions"),
        ("swimwear_catalog_set", "swimwear catalog set with sand-colored floor and blue backdrop"),
        ("boudoir_editorial_set", "boudoir editorial set with silk sheets and a velvet bench"),
    )
    moods = (
        ("golden_hour", "in golden-hour light with warm shadows"),
        ("lamplight", "with warm lamplight and soft shadows"),
        ("blue_hour", "at blue hour with glowing accent lights"),
        ("morning", "in airy morning light"),
        ("night_glow", "at night with cinematic practical lights"),
        ("overcast", "under soft overcast light"),
        ("neon_reflections", "with subtle neon reflections"),
        ("candlelit", "with candlelit accents"),
    )
    additions: list[tuple[str, str]] = []
    for base_slug, base_description in bases:
        for mood_slug, mood_description in moods:
            separator = ", " if mood_description.startswith("with ") else " "
            additions.append((
                _scene_slug(f"{base_slug}_{mood_slug}"),
                f"{base_description}{separator}{mood_description}",
            ))
    additions.extend(
        [
            ("rainy_paris_balcony", "rainy Paris balcony with wrought-iron rails and glowing apartment windows"),
            ("monsoon_hotel_veranda", "monsoon hotel veranda with wet tile and lush plants"),
            ("desert_motel_pool_night", "desert motel pool at night with a buzzing neon sign"),
            ("carnival_after_hours", "after-hours carnival midway with string lights and quiet rides"),
            ("circus_dressing_tent", "circus dressing tent with striped canvas and costume trunks"),
            ("masquerade_ballroom", "masquerade ballroom with marble columns and scattered masks"),
            ("cruise_ship_promenade", "cruise ship promenade deck with warm rail lights"),
            ("glass_skybridge", "glass skybridge between towers with city lights below"),
            ("botanical_hotel_lobby", "botanical hotel lobby with palms and brass elevators"),
            ("desert_observatory", "desert observatory deck under a deep starry sky"),
            ("moonlit_orchid_house", "moonlit orchid house with humid glass and pale blooms"),
            ("private_pool_cinema", "private pool cinema with floating lights and a projection screen"),
            ("rooftop_tent_lounge", "rooftop tent lounge with rugs and lanterns"),
            ("velvet_train_station_lounge", "velvet train-station lounge with arched windows"),
            ("snow_globe_storefront", "holiday storefront with frosted glass and warm displays"),
            ("valentine_chocolate_shop", "chocolate shop with ribbon boxes and rose petals"),
            ("summer_boardwalk_arcade", "summer boardwalk arcade with ocean air and neon signs"),
            ("autumn_book_cafe", "autumn book cafe with window rain and amber lamps"),
            ("spring_flower_atrium", "spring flower atrium with glass roof and pastel blossoms"),
            ("new_years_rooftop", "New Year's rooftop terrace with city fireworks in the distance"),
            ("studio_water_tank", "studio water tank set with rippling reflections"),
            ("mirror_maze_set", "mirror maze set with soft theatrical lights"),
            ("giant_fan_photo_set", "photo set with a giant fan and wind-swept fabric"),
            ("silk_ribbon_set", "studio set filled with long silk ribbons and soft pastel light"),
            ("paper_moon_set", "vintage paper moon photo set with painted stars"),
            ("oversized_cocktail_glass_set", "playful editorial set with oversized cocktail props"),
            ("retro_pool_float_set", "retro pool-float set with glossy blue floor"),
            ("dreamy_cloud_backdrop", "dreamy cloud backdrop with cottony set pieces"),
            ("neon_heart_wall", "neon heart wall with glossy black floor"),
            ("gold_confetti_set", "gold confetti studio set with warm spotlights"),
        ]
    )
    return additions


_extend_scene_unique(SCENES, _expand_scenes())

# Body language only -- facial expression is a separate axis (see EXPRESSIONS).
POSES = [
    "confident hip-angled pose",
    "relaxed lean with one hand at the waist",
    "playful over-the-shoulder glance",
    "standing tall with one hand holding a prop",
    "seated with crossed ankles",
    "leaning on a counter",
    "walking pose with wind-touched hair",
    "one hand lightly adjusting hair",
    "editorial fashion stance with shoulders angled toward camera",
    "cozy seated pose",
    "arched-back pin-up pose",
    "lying on one side propped on an elbow",
    "kneeling pose with a slight back arch",
    "leaning back against a wall with one knee bent",
    "stretching pose with arms raised overhead",
    "sitting on the floor with knees drawn up",
    "glancing back with hips turned away",
    "perched on the edge of a table with crossed legs",
    "reclining gracefully across a chaise",
    "hands framing the waist in a classic cheesecake pose",
    "twisting at the waist to glance back",
    "seated on the floor leaning back on both hands",
    "kneeling upright with hands resting on thighs",
    "lying on the back with knees raised",
    "leaning forward with hands on the knees",
    "one foot propped up on a stool",
    "arched spine with arms stretched overhead",
    "sitting sideways draped over the back of a chair",
    "standing with arms crossed and a hip pop",
    "lounging with one arm tucked behind the head",
    "peeking playfully around a doorframe",
    "balanced on tiptoes with a gentle twist",
    "hands clasped behind the back with the shoulders open",
    "one hand on the hip, the other lifting the hair",
    "seated with legs crossed and leaning forward slightly",
    "leaning against a doorframe with ankles crossed",
    "looking back with a hand resting on the hip",
    "perched on a stool with one heel hooked on the rung",
    "standing in profile with the back gently arched",
    "sitting on the floor with legs swept to one side",
    "reclining on an elbow with the legs angled",
    "tilting the head while adjusting an earring",
    "leaning forward with forearms on a railing",
    "one knee up on a chair, leaning on the thigh",
    "stretching the arms overhead with a soft side bend",
    "crossing the ankles while leaning on a wall",
    "resting both hands on a tabletop and leaning in",
    "twirling slightly so the skirt flares",
    "sitting sideways with one arm over the chair back",
    "gazing over the shoulder with a relaxed stance",
]

# "evocative" pose mode: described with neutral movement / fashion / yoga /
# dance vocabulary (arch, stretch, recline, lean, glance, lift the hair) that
# reads as alluring through body line and geometry -- no sexual or flaggable
# wording, so it stays safe through the image model's filters. Selected with
# --poses evocative.
EVOCATIVE_POSES = [
    "arching the back gently with both arms reaching overhead",
    "reclining across the bed with one knee raised",
    "kneeling on soft cushions, leaning back on both hands",
    "stretching languidly with both arms above the head",
    "lying on one side with the head propped on a hand",
    "leaning forward over a railing, weight on the forearms",
    "glancing back over one bare shoulder",
    "rising onto tiptoes in a long arched line",
    "draped sideways across an armchair, one leg over the armrest",
    "twisting at the waist with one hip pushed out",
    "seated on the floor with knees drawn up, leaning back on the hands",
    "reclining against a pile of pillows in a relaxed open posture",
    "lying on the stomach with ankles crossed and lifted",
    "reaching up to tie the hair, elbows raised and back arched",
    "leaning against a wall with one knee bent and hip cocked",
    "stretching low to the floor with shoulders down and hips raised",
    "perched on the edge of a table, leaning back on straight arms",
    "curled on one side with a soft inward curve of the body",
    "standing with weight on one hip and shoulders rolled back",
    "kneeling tall and lifting the chin with hands sliding down the thighs",
    "lounging back on the elbows with legs extended and crossed at the ankle",
    "rolling the shoulders back while lifting the hair off the neck",
    "bending at the waist to adjust a shoe strap",
    "stepping out of the water with the back arched and hair swept back",
    "leaning back against the headboard with both arms raised to the pillows",
    "lying back with one arm draped lazily above the head",
    "rolling onto the back with the knees softly bent and relaxed apart",
    "reaching the arms forward along the floor with the hips lifted",
    "seated on a windowsill with one knee drawn up to the chest",
    "leaning into a doorway with one arm stretched up the frame",
    "tilting the head back while running both hands through the hair",
    "stepping forward mid-stride with the hips leading the motion",
    "pausing mid-turn with the body caught in a soft spiral",
    "crouching low and reaching one hand toward the floor",
    "sitting back onto the heels with a long lifted spine",
    "draping back over the arm of a sofa with the head tipped back",
    "lying across the foot of the bed on the stomach with ankles raised",
    "propped on one elbow with the other hand resting on the hip",
    "leaning on a counter with the back gently arched",
    "kneeling on a chair and leaning over the backrest",
    "reaching overhead to a high shelf, the body drawn into a long line",
    "sitting sideways on the floor with one hip dipped and legs folded",
    "tipping the face up into the light with the shoulders drawn open",
    "leaning a shoulder against a column with the weight on one side",
    "wrapping a towel loosely while looking back over the shoulder",
    "settling into a low lunge with the hips lowering and the chest open",
    "lying along a chaise on one side with the top leg sliding forward",
    "sitting on the edge of a tub with one foot dipped in the water",
    "leaning forward onto a vanity to meet the mirror's gaze",
    "perched on a stool with crossed legs and the torso leaned back",
    "curling forward to fasten a sandal with one shoulder dipped",
    "reclining on a towel poolside with one knee bent up",
    "leaning out over a balcony rail into the breeze",
    "sliding down a wall into a relaxed low crouch",
    "stretching across the bed to reach the nightstand",
    "pressing the back to the wall with the hips eased forward",
    "lying back in tall grass with the arms loose overhead",
    "leaning on folded arms across a table with the shoulders forward",
    "swaying mid-dance as the weight shifts onto one hip",
    "rising onto the knees with a long lifted line through the body",
    "tilting back in a chair with both arms folded behind the head",
    "stretching like waking, arms overhead and the back in a deep curve",
    "balancing on the balls of the feet with the ribs lifted high",
    "trailing one hand down the side of the body in a slow stretch",
    "kneeling forward over a cushion with the spine in a soft dip",
    "looking back across the room with the chin over one shoulder",
    "easing onto the side with the top knee drawn up the leg",
    "leaning across a piano with the weight on the forearms",
    "rolling the hips into a slow contrapposto stance",
    "sitting on the floor and leaning back on locked arms, chin up",
    "kneeling upright with the back deeply arched and the chin lifted",
    "kneeling and sinking the hips back toward the heels with a long lower-back curve",
    "on all fours in a long cat-stretch with the shoulders low, glancing back over the shoulder",
    "kneeling tall with the hips eased forward and both hands resting on the thighs",
    "lying on the back drawing both knees up with the feet flat and a hand on the stomach",
    "kneeling and reaching both hands back to gather the hair with the chest lifted",
    "kneeling and leaning back onto straight arms with the hips eased up",
    "arching back over a low ottoman with the arms reaching overhead",
    "lying on the back with one knee raised, gazing up toward the viewer",
    "kneeling back on the heels and tipping the head back with the eyes closed",
    "rising onto the knees on the bed with a long arch through the spine",
    "sitting back on the heels and arching to reach into the hair with the chest open",
    "lying back across pillows lifting the hips into a gentle bridge",
    "kneeling tall and clasping the hands behind the head with the elbows wide",
    "on the knees with the weight sunk into one hip, leaning onto one hand",
    "lying on the stomach and pushing up onto the forearms with an arched back, chin up",
    "kneeling and tipping the torso back with the hands resting high on the thighs",
    "lying on the side and drawing the top knee up toward the chest",
    "crouched low on the toes with the forearms resting on the knees, looking up",
    "kneeling and rolling the shoulders back with the hands gliding down the front of the body",
    "arching deeply backward with the hands sliding down the thighs",
    "lying back with both arms stretched overhead and the spine in a long curve",
    "kneeling and bowing forward with the arms reaching far along the floor",
    "sitting with the knees together and the ankles apart, leaning back on the hands",
    "lifting one leg onto a chair and leaning over the raised knee",
    "reclining with one arm behind the head and the other along the hip",
    "twisting to look back with both hands gathering the hair up high",
    "lying on the side with the top leg drawn forward and the hip lifted",
    "rising from the water with the head tipped back and the back arched",
    "leaning over a vanity with the back arched and the chin lifted to the mirror",
    "sitting on the heels and arching back to rest the hands on the floor behind",
    "kneeling upright and arching to drape the head and arms backward",
    "stretching up onto the toes with the ribs lifted and the hips tilted",
    "lying along the edge of a pool with one knee bent and the toes pointed",
    "curling onto the side and reaching one arm long overhead",
    "leaning back on a sofa with one leg lifted along the cushions",
    "kneeling tall and slowly peeling a long glove down one arm",
    "easing a strap off one shoulder while glancing down",
    "sitting on a counter and leaning back on straight arms with the chin up",
    "arching off the bed with the shoulders down and the hips lifting",
    "reclining across pillows with one knee raised and the other leg long",
    "lying on the stomach with the chin on the hands and the ankles crossed high",
    "rolling onto the back with both knees drawn up and tilted to one side",
    "sitting on the floor and leaning far back with the chest open to the ceiling",
    "kneeling and trailing both hands slowly down the sides of the body",
    "tipping the head back under a stream of water with the back arched",
    "stretching across a chaise on the side with the top leg sliding down",
    "leaning a hip to a wall and arching away with the arms overhead",
    "sitting with the legs folded to one side and twisting to look back",
    "reaching back to unclasp the hair with the chest lifted and the back arched",
    "lying back with one hand resting low on the stomach and the other overhead",
    "balancing on one hip on the floor with the legs stacked and folded",
    "leaning forward over crossed arms with the shoulders drawn toward the viewer",
    "drawing one knee to the chest while seated and resting the chin on it",
    "stretching long on the back with the toes pointed and the arms overhead",
    "sinking into a deep side bend with one arm sweeping overhead",
    "perching on the edge of a tub and leaning back with the spine arched",
    "lying on the side propped on a forearm with the top leg bent up",
    "kneeling and clasping the hands high overhead in a long stretch",
    "twisting at the waist while seated, one hand on the floor behind",
    "rolling the shoulders open while reclining on a heap of cushions",
    "lifting the hair off the neck with both hands and tilting the head back",
    "stepping out of a bath with one foot raised to the rim and the back curved",
    "lying back across a bench with the head tipped and the arms loose",
    "kneeling and arching while smoothing both hands up over the ribs",
    "sitting on the floor leaning on one hand and stretching the other leg out long",
    "curving the back over the arm of a chaise with the legs draped down",
    "reaching overhead to grip a canopy post with the body drawn long",
    "lying on the front and arching up onto the forearms with the chin lifted",
    "kneeling back on the heels and arching to graze the floor behind with the fingertips",
    "lifting one leg to a rail and folding forward along the extended thigh",
    "reclining with the hips rolled to one side and one arm overhead",
    # Prop-interactive poses — phallic-shaped items used in classic suggestive pin-up compositions
    "kneeling upright with a cucumber held to each side of the head like antennae, looking up with wide eyes",
    "lying back with a banana resting lightly across the lips, eyes half-closed",
    "kneeling with a corn cob pressed to each cheek, gazing straight at the viewer",
    "seated with a long zucchini balanced across both wrists, arms extended forward",
    "biting the tip of an asparagus spear with a coy over-the-shoulder glance",
    "reclining on one elbow with a peeled banana held above the face, studying it",
    "pressing a popsicle to the chin and gazing up through the lashes",
    "kneeling and cradling a large eggplant in both hands with a soft amused smile",
    "holding a banana at chin level with both hands, lips slightly parted",
    "sitting cross-legged with a corn cob raised to the lips, looking directly at the viewer",
    "bending forward at the waist with a cucumber held to each side of the head",
    "lying on the front propped on the elbows with a banana balanced on the lower lip",
    "kneeling tall and pressing a long carrot to the lips with both hands",
    "seated at a vanity pressing two cucumbers to the cheeks like earrings, eyes wide",
    "gripping a pool cue at the base while leaning forward over it with the arms long",
    "lying on one side with a banana balanced along the curve of the hip",
    "standing with a baseball bat resting on one shoulder, one hand on the hip, looking back",
    "holding two long carrots hip-width apart at waist height, hip cocked to one side",
    "wrapping both hands around a wine bottle held at lap height, gazing up at the viewer",
    "seated with a hot dog held at lip level in both hands, head tilted",
    "kneeling with both arms raised holding a corn cob in each hand beside the face",
    "lying back on the elbows with a zucchini resting across the lap, one hand on it",
    "pressing the flat of a cucumber against one cheek and looking sidelong at the viewer",
    "holding a churro to the lips with one hand while the other rests on the knee",
    "seated cross-legged with a large eggplant balanced upright between the palms",
    "sliding fingers slowly down a banana held upright, gazing at the viewer",
    "pressing the curved tip of a banana to the lower lip with both hands",
    "cradling a banana in both outstretched palms offered toward the viewer",
    "running a fingertip from base to tip along a banana held in the other hand",
    "seated with a banana balanced upright between pressed-together knees",
    "reclining with a peeled banana held loosely above the face, tracing the curve with one finger",
    "kneeling and presenting a peeled banana in both hands extended toward the viewer",
    "pressing a cucumber lengthwise along the cheek with a sleepy expression",
    "holding a cucumber upright in one fist beside the face, direct gaze",
    "lying on the side with a cucumber pressed lightly to the lips",
    "kneeling with a cucumber pressed vertically between both palms in prayer position",
    "seated with two cucumbers held upright at either side of the head",
    "on all fours with a cucumber balanced along the spine, glancing back",
    "leaning forward with a cucumber resting in the palm extended toward the viewer",
    "biting into a corn cob while seated cross-legged, eyes on the viewer",
    "lying on the stomach propped on the elbows holding a corn cob up beside the face",
    "kneeling tall with a corn cob held upright in each raised fist",
    "licking along the side of a popsicle with the eyes closed",
    "pulling a popsicle slowly from between lightly closed lips",
    "pressing a melting popsicle to the cheek and glancing sidelong at the viewer",
    "pressing an ice lolly flat against the tongue with the eyes on the viewer",
    "letting ice cream drip over the fingers and licking one finger clean",
    "holding an ice cream cone at lip level with the tongue extended toward it",
    "biting the end off a carrot, eyes wide",
    "holding a long carrot horizontally between the teeth, hands on hips",
    "pressed against a wall with a carrot held up beside the head",
    "stroking a finger slowly down the length of a long carrot, head tilted",
    "pressing two carrots to the temples like horns, looking wide-eyed at the viewer",
    "cradling a large eggplant in both arms against the chest",
    "lying on the side with a large eggplant propped against the hip",
    "holding an eggplant at waist height in both hands, head tilted, soft smile",
    "resting a long zucchini on one shoulder like a rifle, one hand on the hip",
    "seated with a zucchini laid across the thighs, hands resting on each end",
    "pressing a zucchini lengthwise to the lips, eyes half-closed",
    "holding a zucchini at hip height in one hand with the hip popped out",
    "pressing an unlit cigar to the lips with two fingers, one brow raised",
    "trailing a cigar along the lower lip with half-closed eyes",
    "holding a long cigarette holder between two fingers beside the face, chin lifted",
    "holding a pool cue vertical beside the body, one hand high and one hand low",
    "leaning over a table with the pool cue extended forward, hips angled back",
    "seated on a surface gripping a pool cue between the knees, leaning forward on it",
    "wrapping the lips loosely around the neck of a wine bottle, eyes gazing up",
    "pressing the base of a wine bottle to the chin, looking forward",
    "tilting a wine bottle above the open mouth with one hand",
    "pressing a microphone to the lips in a singing pose, eyes closed",
    "holding a microphone upright at chin level, head tipped back slightly",
    "seated on a stool leaning toward a microphone at lap height, lips parted",
    "seated with a baseball bat standing upright between the knees, both hands wrapped around it",
    "resting both hands on top of a bat planted between the feet, leaning on it",
    "holding a breadstick between the teeth while looking at the viewer over one shoulder",
    "pressing a hot dog to the lips with a playful expression",
    "holding a long breadstick in each raised fist beside the head, kneeling",
    "lying on the back with a cucumber resting lengthwise across the stomach, hands at the sides",
    "sitting with the back gently arched and a zucchini balanced across the collarbone",
    "leaning forward with a corn cob suspended between two fingers at lip height",
    "seated holding a banana in the crook of the elbow, head resting on it",
    "kneeling with two bananas held up beside the temples, gazing forward",
    "seated with a wine bottle standing upright between closed knees, both hands on the neck",
    "lying on the front with a long carrot balanced across the back of both hands",
    "pressing an eggplant between both palms at chest height, chin down, eyes up",
    "holding a churro with the tip resting on the lower lip, soft smile",
    "biting through the middle of a corn cob with both hands raised, eyes on the viewer",
    "kneeling with a cucumber pressed under the chin, both palms cupping it",
    # Extended prop-interactive set — banana
    "holding a banana with both hands at arm's length and studying it with a raised brow",
    "tucking a banana under the chin with both hands clasped beneath it",
    "balancing a banana on the nose tip with arms wide for balance",
    "pressing a banana lengthwise to the cheek and closing the eyes",
    "tracing the tip of a banana slowly with one index finger while holding it in the other hand",
    "reclining on the back with a banana balanced vertically on the sternum",
    "sitting with a banana balanced upright between pressed knees, hands on thighs",
    "kneeling and offering a banana in both cupped hands with the chin dipped",
    "lying on the side with a banana tucked between the chin and collarbone",
    "standing with a banana in each hand raised shoulder-high like dumbbells, grinning",
    "pressing the flat of a banana to the lips with both hands and closing the eyes",
    "seated with a banana balanced across the thighs end to end, hands on hips",
    "pressing a banana to both lips as if about to eat it, eyes on the viewer",
    "nuzzling the tip of a banana against the nose with both hands, eyes closed",
    "dragging a banana slowly across the lower lip while gazing at the viewer",
    "holding a banana in one fist and tapping it thoughtfully against the chin",
    "seated with a banana held vertically in each fist raised on either side of the head",
    "lying on the stomach propped on the elbows, a banana held horizontally beneath the chin",
    "resting a banana diagonally across the collarbone with one hand steadying each end",
    "kneeling with a banana pressed flat to the lips, eyes closed, lashes down",
    # Extended prop-interactive set — cucumber
    "lying on the back with a cucumber balanced across the closed eyes, hands folded on the stomach",
    "seated with a cucumber balanced upright on one knee, one hand steadying it",
    "holding a cucumber horizontally between the teeth with arms crossed",
    "pressing a cucumber flat to the stomach with both hands, back gently arched",
    "kneeling and tapping a cucumber lightly against the lips, gazing to one side",
    "lying on the side with a cucumber propped against the hip bone, hand resting on it",
    "dangling a cucumber from two fingers at hip height while looking at the viewer",
    "pressing two cucumbers end-to-end across the lips, eyes half-closed",
    "seated with a cucumber held vertically in front of the face, one eye peeking around it",
    "lying on the front with a cucumber pressed lengthwise between the forearms on the floor",
    "kneeling and balancing a cucumber upright on an open palm held overhead",
    "pressing a cucumber under the chin with one finger, head tilted back",
    "holding a cucumber in both hands at waist height and looking down at it",
    "seated cross-legged with a long cucumber resting across both ankles",
    "standing with a cucumber balanced on one shoulder, steadying it with one finger",
    "lying on the back holding a cucumber above the face, rotating it slowly",
    "pressing the flat end of a cucumber to the lower lip, gazing sidelong",
    "kneeling with a cucumber cradled in both arms against the chest like a bouquet",
    "seated at a table resting the chin on one end of a cucumber, eyes up",
    "holding a cucumber with both hands at chest height, squeezing it gently",
    # Extended prop-interactive set — corn cob
    "holding a corn cob in each hand and pressing both to the cheeks simultaneously",
    "seated with a corn cob pressed between the palms at chest height, elbows out",
    "reclining and gnawing the side of a corn cob with a glance at the viewer",
    "raising a corn cob with both hands above the head, tilting back to bite it",
    "kneeling and offering a corn cob forward with both hands, chin down and eyes up",
    "holding a corn cob up like a microphone and leaning toward it with lips parted",
    "biting through the far end of a corn cob with both arms raised holding it high",
    "pressing two corn cobs to the temples like spiral horns, wide eyes",
    "seated with a corn cob balanced on each knee, palms resting on each end",
    "lying on the front with a corn cob propped under the chin for the head to rest on",
    "holding a corn cob at chin level between two fingers like a cigar, one brow raised",
    "kneeling with a corn cob held to each shoulder like epaulettes, chin lifted",
    # Extended prop-interactive set — popsicle / ice cream / ice lolly
    "letting a popsicle drip down the chin and catching the drop on a fingertip",
    "tracing the outline of the lips slowly with a popsicle, eyes on the viewer",
    "licking a dripping ice cream cone from the base to the top",
    "pressing an ice lolly flat to the tongue, making direct eye contact",
    "pressing a popsicle to one cheek and smiling at the viewer",
    "holding an ice cream cone at lip height with the tongue tip extended toward it",
    "pressing a melting popsicle under the lower lip with eyes heavy-lidded",
    "squeezing a popsicle upward out of its wrapper, watching it rise",
    "running the tip of the tongue slowly along the length of a popsicle from base to top",
    "holding a half-eaten popsicle to the lips and looking sideways at the viewer",
    "pressing an ice lolly flat to the lower lip with two fingers, lashes down",
    "tilting an ice cream cone toward the open mouth, a drop ready to fall",
    # Extended prop-interactive set — carrot
    "seated with a carrot balanced across the nose, eyes wide",
    "pressing a carrot flat to the cheek and squinting at the viewer",
    "seated with a long carrot held straight up in one fist at shoulder height, smiling",
    "nibbling along the side of a carrot slowly, eyes locked on the viewer",
    "biting off the leafy end of a carrot with a grin",
    "holding a carrot horizontally below the nose like a moustache, one brow up",
    "pressing a carrot tip to the lips and kissing it lightly",
    "reclining with a long carrot balanced diagonally across the collarbone",
    "kneeling with a carrot pressed gently to each cheek like blush sticks",
    "lying on the stomach holding a carrot upright like a candle in front of the face",
    "seated and holding a peeled carrot at waist height in both hands, head tilted",
    "pressing a carrot flat to the tongue, eyes closed",
    # Extended prop-interactive set — eggplant
    "seated with a large eggplant cradled against one cheek, eyes soft",
    "holding an eggplant at face height with both hands and gazing over the top of it",
    "pressing the rounded end of an eggplant to the chin, head tilted, smiling",
    "balancing an eggplant on end on an open palm held at shoulder height",
    "kneeling and hugging a large eggplant to the chest",
    "seated with an eggplant gripped at either end, resting it across the knees",
    "pressing an eggplant slowly to the lips with closed eyes",
    "lying on the side with an eggplant held vertically in both hands in front of the face",
    "holding an eggplant up to the cheek and closing the eye on that side",
    "seated cross-legged with an eggplant balanced upright on one palm, gazing at it",
    "pressing the stem end of an eggplant lightly to the lower lip",
    "kneeling with a large eggplant resting on each upturned thigh, hands on top",
    # Extended prop-interactive set — zucchini
    "seated with a long zucchini across both knees, one hand on each end",
    "holding a zucchini at chest height like a scepter, chin lifted",
    "pressing a zucchini end-on to the lips with eyes half-closed",
    "lying on the front with a zucchini balanced between both forearms",
    "standing with a zucchini held vertically alongside the face, one eye peeking past it",
    "holding a zucchini horizontally below the nose, one brow raised",
    "seated and tapping a zucchini slowly against the chin",
    "kneeling with a zucchini gripped in both hands at waist height, arms extended",
    "pressing a zucchini flat to the sternum with both palms, back slightly arched",
    "lying on the back with a zucchini balanced standing upright on the stomach",
    "seated on the floor with a long zucchini bridging the gap between both raised knees",
    "holding two zucchinis upright beside the head like pillars, wide-eyed",
    # Extended prop-interactive set — pool cue
    "standing with a pool cue gripped in both fists at hip height, hips angled",
    "seated with the tip of a pool cue resting on the lower lip, eyes thoughtful",
    "kneeling with a pool cue held vertically at the side, both hands low on the shaft",
    "resting both wrists over the top of a pool cue balanced horizontally behind the neck",
    "leaning the chin on the butt end of a pool cue held vertical between the feet",
    "seated with a pool cue gripped at hip height with both hands, leaning forward",
    # Extended prop-interactive set — wine bottle
    "cradling a wine bottle lengthwise against the body, both arms around it",
    "pressing a wine bottle vertically to the stomach with both hands, back slightly arched",
    "seated with a wine bottle between the knees, both hands on the neck, leaning in",
    "trailing the neck of a wine bottle slowly down the collarbone",
    "holding a wine bottle at arm's length and gazing at it",
    "reclining with a wine bottle balanced across the hip, one hand loosely on the neck",
    "pressing the neck of a wine bottle lightly to the lips, eyes heavy",
    "sitting cross-legged with a wine bottle held upright in both palms offered forward",
    # Extended prop-interactive set — microphone
    "building up to sing with a microphone in one fist at lip height, eyes closed",
    "pressing the microphone head to the chin and gazing slowly down its length",
    "wrapping one hand around a microphone at waist height, hip cocked, head turned",
    "holding a microphone with both hands close to the lips, mid-lyric",
    "seated with a microphone pressed to the cheek, eyes soft",
    "trailing a microphone slowly down from the lips to the chin",
    # Extended prop-interactive set — baseball bat
    "resting a bat across both shoulders behind the head with arms draped over it",
    "seated with a bat held vertically in both hands between the knees",
    "holding a bat in one hand with the end resting on the floor, leaning on it",
    "pressing the barrel end of a bat slowly to the lips",
    "lying on the back with a bat balanced across the raised knees, hands folded on it",
    # Extended prop-interactive set — hot dog / breadstick / churro
    "holding a hot dog up beside the face with a mock-serious comparison expression",
    "pressing a hot dog slowly to the lips, one eyebrow arched",
    "seated with a hot dog held at lap height in both hands, leaning toward it",
    "pressing a breadstick to one corner of the mouth",
    "holding a breadstick vertically in front of the face like a wand, one eye closed",
    "nibbling slowly along the length of a breadstick, eyes on the viewer",
    "pressing a churro to both lips as if about to bite it, eyes up",
    "holding a churro at chin level and gazing past it at the viewer",
    "dragging a churro slowly across the lower lip, lashes down",
    # Extended prop-interactive set — cigar
    "pressing an unlit cigar vertically to the closed lips, chin lifted",
    "rolling a cigar slowly between two fingers at chin height, gazing at the viewer",
    "holding a cigar between the teeth at the corner of the mouth, one brow raised",
    "pressing an unlit cigar to one cheek and closing the eye on that side",
    # Extended multi-prop combinations
    "kneeling with a banana in one hand and a cucumber in the other, one held to each cheek",
    "seated with a corn cob in each hand pressed to the temples, staring at the viewer",
    "lying back with a cucumber across the lips and a banana resting on the collarbone",
    "kneeling with a zucchini balanced across the shoulders and a carrot held to the lips",
    "holding a hot dog in one hand and a banana in the other, one at each side of the face",
    # Final top-up to reach 2-in-3
    "seated with a banana pressed end-on to the lips, eyes drifting closed",
    "holding a cucumber in one hand and tapping the tip against the chin thoughtfully",
    "kneeling with a zucchini held out in both hands like an offering, eyes down",
    "pressing a corn cob to the cheek and holding it there, eyes on the viewer",
    "lying back with a long carrot balanced diagonally across the chest, hands at the sides",
    "seated and pressing an eggplant vertically to the sternum with both palms",
    "holding a popsicle at arm's length and studying it before bringing it to the lips",
    "pressing the tip of a churro gently to the tip of the tongue",
    "seated with a wine bottle tilted toward the lips, eyes half-closed",
    "reclining with a banana held loosely in the fingers at hip height",
    "kneeling with a corn cob pressed flat to the lips with both hands, eyes up",
    "pressing a cucumber end-on against the closed lips, lashes lowered",
    "seated with a breadstick balanced across the upper lip like a moustache, one brow raised",
    "standing and tracing a slow circle on the chest with the tip of a carrot",
    "lying on the side with a banana balanced on the upper hip, one hand resting on it",
    "pressing an unlit cigar slowly from one corner of the mouth to the other",
    "seated with a hot dog held vertically between two fingers at eye level, staring past it",
    "kneeling and pressing the length of a banana slowly to one cheek",
    "lying on the back with two cucumbers balanced upright side by side on the stomach",
    "seated with a long zucchini pressed to the collarbone, chin resting on the top end",
    "holding a corn cob upright in each fist at chin height, looking at the viewer over them",
    "dragging the tip of a popsicle slowly along the lower lip, chin tilted up",
    "seated with a carrot pressed lengthwise to the lips, both hands cupping it",
    "pressing the microphone to the lower lip and breathing slowly, eyes soft",
    "kneeling with a baseball bat leaned against one shoulder, arms folded across the top",
    "seated with an eggplant held at hip height and one finger resting on its tip",
    "lying on the stomach with a banana pressed lengthwise to the lips, chin raised",
    "holding a wine bottle vertically in both hands at lap height and looking up at the viewer",
    "pressed against a wall with a cucumber held vertically at chest height in one fist",
    "seated with two breadsticks held parallel at lip height, peering at the viewer between them",
    "kneeling upright with a long carrot balanced horizontally across both open palms at chest height",
    "reclining on one elbow, trailing the tip of a zucchini slowly down the forearm",
    "seated cross-legged pressing a banana gently to each cheek with both hands",
    "holding a pool cue behind the neck across the shoulders, arms draped over it, hips swaying",
    "pressing a churro tip-first gently between the teeth, looking at the viewer",
    "lying on the back with a cucumber balanced upright and held steady by one fingertip on the tip",
    "seated and resting both wrists on top of a large eggplant stood upright in the lap",
    "kneeling with a corn cob pressed to one ear like a telephone, eyes wide",
    "pressing a hot dog lengthwise to the lips with both hands, one eye on the viewer",
    "seated with a long zucchini pressed along the forearm from wrist to elbow, comparing lengths",
    "holding a banana upright between pressed-together palms at chest height in a prayer pose",
    "lying on the stomach with a large eggplant balanced on the lower back, hands folded under the chin",
    "kneeling with a popsicle pressed to the lower lip and ice cream beginning to melt over the fingers",
    "seated with a wine bottle cradled in the crook of the elbow, free hand on the hip",
    "standing with a carrot pressed to the chin like a conductor's baton, one hand raised",
    "pressing a corn cob gently to the mouth, tilting the head as if listening to it",
    "seated with a cucumber balanced across the back of both hands resting in the lap",
    "lying on the side with two bananas held together end-to-end at lips height",
    "kneeling and holding a breadstick horizontally between the lips, hands raised",
    "seated with the end of a pool cue resting on the lower lip, eyes looking up its length",
    "pressing a zucchini to both lips and closing the eyes, chin slightly tilted up",
    "lying back with an eggplant held above the face, slowly lowering it toward the lips",
    "seated with a hot dog balanced on the nose tip, arms outstretched for balance",
    "kneeling with a carrot pressed between both palms, fingers interlaced around it",
    "reclining on the elbows with a banana balanced horizontally across the mouth",
    "pressing a cucumber end-first to the chin, looking steadily at the viewer",
    "seated with a corn cob held at chest height, tracing the rows with one fingertip",
    "lying on the front with a long zucchini pressed under the chin, head raised",
    "kneeling tall with an unlit cigar pressed to the lips, chin up, gaze level",
]

# back-to-viewer / rear-emphasis pin-up angles (glance-over-shoulder cheesecake).
# Kept separate so --backside-bias can weight how often they appear.
BACKSIDE_POSES = [
    "standing with the back to the viewer, weight on one hip, glancing over one shoulder",
    "walking away and looking back over the shoulder",
    "lying on the front across the bed with the ankles crossed and lifted, propped on the elbows",
    "seated on the floor facing away, leaning back on the hands with the head turned",
    "kneeling upright facing away with an arched back, glancing over the shoulder",
    "facing a wall with a forearm resting on it, looking back over the shoulder",
    "standing at a window from behind with the hip cocked, glancing back",
    "running both hands up into the hair, seen from behind",
    "arching the back while facing away with the head turned to the side",
    "stretching tall facing away with the arms overhead and the back lengthened",
    "leaning in to a mirror with the back toward the viewer, glancing at the reflection",
    "leaning over a balcony rail facing out with the weight on the forearms",
    "kneeling on the bed facing away and sinking toward the heels, looking over the shoulder",
    "rising onto tiptoe facing away, reaching upward with an arched back",
    "wrapped loosely in a sheet from behind, glancing over the shoulder",
    "sitting sideways with the back half-turned, looking over the shoulder",
    "wading away into the surf and looking back over the shoulder",
    "leaning a shoulder to the wall facing away, hips angled, head turned back",
    "seated on a stool facing away with a long curve through the spine, glancing back",
    "stretching across a lounge chair on the front with the ankles raised",
    "standing with the back to the viewer and looking over the shoulder while lifting the hair",
    "seated on the floor facing away with the spine arched, head turned back",
    "kneeling tall facing away and reaching both arms overhead, glancing back",
    "leaning forearms on a balcony rail facing out, hips angled, looking back",
    "walking toward a doorway and pausing to glance back over the shoulder",
    "standing rear-on with one hip pushed out, head turned to the viewer",
    "facing a full-length mirror from behind, meeting the reflection's eyes",
    "lying on the front along the bed propped on the forearms, ankles crossed up, looking back",
    "rising onto tiptoe facing away with the arms stretched high",
    "seated sideways on a stool, back half-turned, glancing over the shoulder",
    "leaning a shoulder to a column facing away, head tipped back toward the viewer",
    "wrapping a towel low and looking back over the shoulder",
    "standing at a rain-streaked window from behind, a hand on the glass, glancing back",
    "kneeling on a cushion facing away and arching the back, chin over the shoulder",
    "stepping into the surf facing away and turning the head back",
    "facing away and gathering the hair up off the neck, head dipped",
    "standing with the hands flat on a wall facing away, hips angled, looking back",
    "perched on the edge of a bed facing away with a long curve through the spine",
    "leaning over a vanity facing away, glancing back at the viewer",
    "standing with the back arched and weight on one hip, hands at the hips, looking over the shoulder",
    "facing away on the knees and lengthening the spine tall, glancing back",
    "draped face-down across a chaise with the ankles lifted and crossed",
    "standing at a railing facing out over a view, looking back with a soft smile",
    "turning away mid-step with the hair swinging, glancing back",
    "seated facing away on a windowsill, one knee up, looking back over the shoulder",
]

# Full pool used when no backside skew is requested.
EVOCATIVE_ALL = EVOCATIVE_POSES + BACKSIDE_POSES

# Figure / curve emphasis -- an independent axis (like pose and expression) that
# describes a woman's silhouette with tasteful figure-drawing / fashion
# vocabulary (full bust, cinched/wasp waist, full rounded hips, peach-shaped
# rear) so the image model renders pronounced pin-up curves WITHOUT the crude
# anatomy words that trip its safety filters -- the same trick as the evocative
# poses. Applied to single women only; selected/weighted via --figure. Composes
# with any base body type (slim -> slim-thick, plus-size -> voluptuous, etc.).
FIGURE_CURVY = [
    "a full bust, a narrow waist, and full, rounded hips",
    "a small, cinched waist and full hips",
    "a full bust and a tiny waist",
    "full, rounded hips and a peach-shaped lower silhouette",
    "soft, heavy curves and a defined waist",
    "a full bust and full, curvy hips",
    "an ample bust and a snatched waist",
    "a generous neckline and full, rounded hips",
    "soft, natural fullness up top and a small waist",
    "a full, soft bust and a narrow waist",
    "wide, curvy hips and a slim waist",
    "a rounded, peachy rear and a cinched waist",
    "full hips, a small waist, and a soft, full bust",
    "an opulent, voluptuous figure with a defined waist",
    "a buxom upper figure and full, rounded hips",
    "soft, full curves and a wasp waist",
    "a heart-shaped lower silhouette and a slim waist",
    "a full bust and a generously curved rear",
    "a tiny waist framed by full hips and a full bust",
    "lush, soft curves and a nipped-in waist",
    "a full, rounded rear and a small waist",
    "a soft, heavy bust and curvy hips",
    "shapely, full hips and a defined waistline",
    "a curvy silhouette with a full bust and peachy hips",
    "an ample, soft bust and a narrow waistline",
    "full, sculpted hips and a small waist",
    "voluptuous curves with a cinched middle",
    "a generous bust and a rounded, peach-shaped rear",
    "soft fullness up top and full, rounded hips",
    "a plush, curvy figure with a defined waist",
    "full curves through the bust and hips with a slim waist",
    "a soft, full chest and a snatched waist",
    "a soft, rounded bust and full hips",
    "a curvy waist-to-hip balance with a full bust",
    "full, heavy curves and a slender waist",
    "a deep neckline and a rounded, full rear",
    "soft, generous curves with a trim waist",
    "a full bust and softly rounded hips",
    "a pinched waist between a full bust and full hips",
    "lush hips and a soft, full bust",
    "a curvy lower half with a tiny waist",
    "a full, soft figure with a nipped waist",
    "rounded, full hips and a soft bust",
    "an ample neckline and a wasp waist",
    "soft, full hips and a snatched waistline",
    "a voluptuous bust and a slim, defined waist",
    "full curves up top and a peach-shaped rear",
    "a soft, heavy figure with a cinched waist",
    "a full, rounded silhouette with a small waist",
    "generous, soft curves and a defined middle",
    "a full bust over a narrow ribcage and waist",
    "wide, soft hips and a small, defined waist",
    "a rounded rear and a soft, full neckline",
    "a curvy hourglass-leaning silhouette with a slim waist",
    "plush hips and a soft, ample bust",
    "a soft, full bustline and curvy hips",
    "a defined waist beneath full, soft curves",
    "a peachy, rounded lower silhouette and a slim waist",
    "a soft, curvy figure with a pinched waist and full hips",
    "an ample, rounded bust and a tiny waist",
    "full, soft hips and thighs with a slim waist",
    "a curvy, soft silhouette with a generous neckline",
    "soft, rounded curves with an hourglass-cinched waist",
]

# Lithe / athletic / slender notes -- mixed in with --figure balanced so the
# dataset keeps some non-voluptuous variety.
FIGURE_ATHLETIC = [
    "a lithe, balanced figure with a defined waist",
    "an athletic, toned figure with subtle curves",
    "a slim, elegant silhouette",
    "a toned midsection and gently curved hips",
    "a lean figure with a small waist",
    "a sleek, willowy silhouette",
    "an athletic build with soft curves",
    "a trim waist and toned, shapely legs",
    "a graceful, slender figure",
    "a fit, sculpted figure with a defined waist",
    "a petite, balanced figure with gentle curves",
    "a slim-thick figure with a small waist and soft hips",
    "a toned, hourglass-leaning figure with a slim waist",
    "an athletic, curvy-fit silhouette",
    "a lean, dancer's figure with subtle curves",
    "a sporty, toned figure with a defined waist",
    "a slender frame with gently rounded hips",
    "a lithe figure with a small waist and a soft bust",
    "a fit, lightly hourglass silhouette",
    "a willowy frame with a defined waistline",
]

# Maximum-exaggerated subset (selected with --figure bombshell).
FIGURE_BOMBSHELL = [
    "a dramatic hourglass with a very full bust, a tiny waist, and full, rounded hips",
    "an exaggerated bombshell figure with an ample bust, a wasp waist, and wide, curvy hips",
    "a very full bust and a dramatically cinched waist",
    "lush, voluptuous curves with a tiny waist and a full, peach-shaped rear",
    "an opulent hourglass with a generous bust and full, rounded hips",
    "a heavy, soft bust and a dramatically full, rounded rear",
    "extreme hourglass proportions with a snatched waist and full curves",
    "a very full neckline and wide, curvy hips around a tiny waist",
    "a bombshell silhouette with a full bust, a nipped-in waist, and peachy hips",
    "dramatic full curves through the bust and hips with a wasp waist",
    "a generously full bust and an exaggerated, rounded rear",
    "voluptuous, larger-than-life curves with a tightly cinched waist",
    "an extreme hourglass with a heavy bust, a wasp waist, and very full hips",
    "a dramatically voluptuous figure with a tiny cinched waist",
    "a very full, soft bust and dramatically wide, curvy hips",
    "an exaggerated peach-shaped rear and a snatched waist",
    "lush, oversized curves with a tightly nipped waist",
    "a heavy, full neckline and a dramatically rounded rear",
    "extreme curves through the bust and hips around a tiny waist",
    "a bombshell hourglass with a very full bust and peachy, full hips",
    "dramatically full hips and a heavy bust over a wasp waist",
    "an opulent, larger-than-life bust and a cinched waist",
    "a voluptuous, exaggerated silhouette with a tiny waist and a full rear",
    "a very full, rounded rear and a dramatically pinched waist",
    "a heavy, soft bust and exaggeratedly wide hips with a slim waist",
]

EXPRESSIONS = [
    "warm genuine smile",
    "bright open-mouthed laugh",
    "soft closed-lip smile",
    "playful wink",
    "flirtatious side glance",
    "sultry half-lidded gaze",
    "coy lowered-eyes look",
    "confident raised eyebrow",
    "mischievous smirk",
    "surprised wide-eyed look with parted lips",
    "dreamy faraway gaze",
    "tender affectionate expression",
    "teasing bitten-lip smile",
    "serene calm expression with soft eyes",
    "joyful beaming grin",
    "thoughtful pensive look",
    "sly knowing smile",
    "inviting warm gaze toward the viewer",
    "shy gentle smile with a faint blush",
    "bold direct stare",
    "amused giggle caught mid-laugh",
    "relaxed contented expression",
    "cheeky playful grin",
    "soft pout",
    "delighted smile with raised eyebrows",
    "confident chin-up expression",
    "wistful gaze",
    "starry-eyed enchanted look",
    "narrow-eyed teasing smirk",
    "peaceful closed-eyes smile",
    "curious tilted-head look",
    "radiant happy expression",
    "smoldering intense gaze",
    "sweet dimpled smile",
    "candid mid-conversation expression",
    "nose-scrunching laugh",
    "alluring glossy-lipped smile",
    "daydreaming soft expression",
    "spirited excited grin",
    "composed elegant expression",
    "blowing a playful kiss",
    "biting the corner of the lip",
    "winking with a grin",
    "smize with softly parted lips",
    "raised chin with a confident smirk",
    "soft surprised gasp",
    "content closed-eyes sigh",
    "doe-eyed innocent look",
    "knowing side-eye",
    "warm crinkle-eyed smile",
    "dreamy lip-parted gaze",
    "flirty eyebrow flash",
    "serene meditative gaze",
    "delighted gasp with a hand near the cheek",
    "mischievous stifled giggle",
    "sultry over-the-glasses look",
    "playful tongue-out grin",
    "soft parted-lips look",
    "confident closed-mouth smile with arched brows",
    "languid heavy-lidded gaze",
    "bright surprised smile",
    "tender half-smile with soft eyes",
    "coquettish glance through the lashes",
    "serene faint smile with closed eyes",
    "bold lifted-brow challenge",
    "warm laughing eyes",
    "dreamy upward gaze",
    "sultry parted-lip stare",
    "shy bitten-lip glance away",
    "amused crooked half-smile",
    "playful scrunched-nose smile",
    "soft sigh with lowered lashes",
    "radiant ear-to-ear grin",
    "knowing arched-brow smirk",
    "gentle adoring gaze",
    "mischievous sidelong glance",
    "calm confident gaze into the lens",
    "delighted open-mouthed grin",
    "wistful soft-eyed look",
    "teasing pursed-lip smile",
    "content dreamy half-smile",
    "fierce narrowed-eye look",
    "sweet hopeful gaze",
    "relaxed sleepy smile",
    "flirtatious raised-shoulder glance",
    "quiet enigmatic smile",
]

COMPOSITIONS = [
    "tight upper body portrait",
    "waist-up portrait",
    "mid-thigh portrait",
    "three-quarter portrait",
    "full body portrait",
    "seated portrait",
    "environmental portrait",
    "low-angle full body shot",
    "close-up beauty portrait",
    "over-the-shoulder back view",
    "dynamic dutch-angle portrait",
    "wide environmental pin-up",
    "extreme close-up on the face",
    "full-length mirror reflection shot",
    "high-angle looking-down portrait",
    "profile portrait",
    "cropped torso pin-up frame",
    "knee-up three-quarter portrait",
    "from-below hero angle full body",
    "intimate close crop on the shoulders and face",
    "reclining full-length composition",
    "diagonal across-the-frame pin-up",
    "mirror-and-subject double framing",
    "over-the-hip rear three-quarter view",
    "wide negative-space environmental shot",
    "soft-focus foreground framing the subject",
    "centered symmetrical full body portrait",
    "candid off-center snapshot framing",
    "tight crop from hips to shoulders",
    "low water-level poolside angle",
]

# Keywords that indicate a pose already includes a prop — skip PROPS sampling when matched
_POSE_PROP_KEYWORDS = {
    "banana", "cucumber", "corn cob", "popsicle", "eggplant", "zucchini",
    "carrot", "cigar", "hot dog", "breadstick", "churro", "candy cane",
    "pool cue", "wine bottle", "microphone", "baseball bat", "bratwurst",
    "lollipop", "asparagus", "ice lolly", "ice cream", "drumstick", "straw",
    "cigarette",
}


def _pose_has_prop(pose_text: str) -> bool:
    low = pose_text.lower()
    return any(kw in low for kw in _POSE_PROP_KEYWORDS)


PROPS = [
    "holding a coffee mug",
    "holding a bouquet",
    "holding a wine glass",
    "holding a banana",
    "holding a milkshake",
    "holding a paintbrush",
    "holding a towel",
    "holding a small clutch purse",
    "holding a book",
    "biting into a ripe banana",
    "holding a cucumber",
    "pressing a popsicle to the lips",
    "holding a cold drink",
    "holding a corn cob",
    "holding a camera",
    "holding a vinyl record",
    "holding a zucchini",
    "holding a carrot",
    "draped in a sheer scarf",
    "holding a feather boa",
    "holding a cocktail glass",
    "holding a hand mirror",
    "adjusting a stocking",
    "holding a lace fan",
    "wrapped in a silk sheet",
    "holding a champagne flute",
    "holding a martini glass",
    "twirling a strand of hair",
    "holding a polaroid camera",
    "holding a champagne bottle",
    "draped in string fairy lights",
    "licking an ice lolly",
    "holding a lollipop",
    "resting sunglasses on the head",
    "holding a beach ball",
    "carrying a surfboard",
    "holding a tropical cocktail with a paper umbrella",
    "hugging a fluffy pillow",
    "holding a glass of champagne to the lips",
    "holding a single rose",
    "trailing a feather along the collarbone",
    "holding a silk ribbon",
    "wrapped loosely in a towel",
    "holding a string of pearls",
    "holding a large eggplant",
    "holding a cocktail with a cherry",
    "balancing a sun hat on the hip",
    "holding a teacup and saucer",
    "draped in a fur stole",
    "holding a lit candle",
    "holding a glass of red wine",
    "holding a vintage telephone receiver",
    "holding a cigar",
    "holding a lollipop to the lips",
    "twirling a parasol",
    "nibbling the end of a churro",
    "peeling a banana",
    "holding an ice cream cone",
    "holding a perfume bottle",
    "holding a long unlit cigarette holder",
    "holding a wide-brim sun hat",
    "holding a chilled bottle against the skin",
    "holding a breadstick to the lips",
    "sliding a finger along a banana",
    "holding a glass of iced lemonade",
    "holding an asparagus spear",
    "biting the tip of a carrot",
    "holding a hot dog",
    "licking whipped cream off a finger",
    "holding a candy cane to the lips",
    "dragging a finger along a corn cob",
    "sucking on a straw",
    "holding a bratwurst",
    "biting into a popsicle",
    "pressing a microphone to the lips",
    "holding a baseball bat over one shoulder",
    "gripping a pool cue",
    "holding a wine bottle by the neck",
    "pressing a lipstick to the lips",
    "holding a drumstick between the fingers",
]

COUPLE_TYPES = [
    ("two women", "two women", "close affectionate couple pose"),
    ("two men", "two men", "relaxed romantic couple pose"),
    ("woman and man", "a woman and a man", "playful date-night pose"),
]

COUPLE_AGES = [
    "21-year-old adults",
    "22-year-old adults",
    "23-year-old adults",
    "24-year-old adults",
    "25-year-old adults",
    "early 20s adults",
    "early and mid-20s adults",
    "mid-20s adults",
    "late 20s and 30s adults",
    "30s and 40s",
    "26-year-old adults",
    "late 20s adults",
    "early 30s adults",
    "mid-20s and early 30s adults",
    "27 and 29-year-old adults",
    "early-to-mid 20s adults",
]

COUPLE_OUTFITS = [
    "stylish evening outfits with fitted silhouettes and soft fabric folds",
    "cozy off-shoulder knitwear and an open-collar shirt",
    "date-night cocktail wear with warm satin highlights",
    "casual resort outfits with relaxed adult glamour",
    "tailored party clothes with playful accessories",
    "matching lingerie-inspired loungewear with sheer robes",
    "beachwear with a bikini and an open shirt",
    "a silk slip and an unbuttoned shirt for an intimate evening",
    "coordinated satin robes loosely tied",
    "a backless gown and a tailored open-collar shirt",
    "matching evening wear with plunging necklines",
    "a slip dress and a half-buttoned dress shirt",
    "festival outfits with a bralette and an open shirt",
    "loungewear with a camisole and low-slung joggers",
    "a corset top and a fitted waistcoat",
    "resort chic with a sarong and linen trousers",
]

# ---------------------------------------------------------------------------
# "minimal" clothing mode: local adult generation wardrobe. This is allowed to
# use direct sheer/mesh/see-through wording instead of older external-service
# euphemisms. Selected via --clothing minimal.
# ---------------------------------------------------------------------------
WOMEN_CLOTHES_MINIMAL = [
    # Confirmed pass-throughs — kept exactly as the image model accepted them
    "fishnet stockings with a wet-look bodysuit",
    "wet-look bikini with metallic O-ring details",
    "satin robe falling open over a matching bra and high-leg briefs",
    "crochet bikini with wooden beads",
    "knit crop sweater slipping off one shoulder over high-cut briefs",
    "micro cardigan buttoned once over a lace bra and briefs",
    "tied flannel over a bikini top with denim cut-offs",
    "sheer maxi skirt over bikini bottoms with a crop top",
    "corset-laced top with a flowing maxi skirt and a high slit",
    "satin slip dress with thin straps and a low open back",
    "lace-panel cocktail dress with a sheer midriff",
    "deep-V wrap dress with a thigh-high slit",
    "cropped halter top with deep cleavage and high-cut denim shorts",
    "draped one-shoulder gown with a thigh slit",
    "micro cardigan buttoned once over a lace bra and mini skirt",
    # Local-generation explicit wardrobe entries.
    "transparent mesh mini dress with bare breasts visible through the fabric",
    "see-through lace slip with nipples visible and high-cut briefs",
    "open-front sheer robe exposing bare breasts over a micro thong",
    "black fishnet bodystocking with visible nipples and high-cut hips",
    "wet-look transparent mesh top over bare breasts with tiny shorts",
    "sheer halter bodysuit with exposed nipples and a high-cut thong",
    "micro mesh bra with transparent cups and matching string thong",
    "body tape strips over the nipples with a sheer mesh skirt",
    "transparent lace catsuit showing bare breasts and high-cut underwear",
    "open mesh cover-up worn over a micro bikini with visible nipples",
    "see-through white tank clinging to bare breasts with tiny shorts",
    "sheer lace bodysuit with open hips and visible nipple outline",
    "mesh crop top over bare breasts with a low-slung micro skirt",
    "transparent beach dress over a tiny thong bikini",
    "sheer babydoll open at the front over bare breasts and panties",
    "black mesh wrap dress with the chest fully visible through fabric",
    "clear vinyl mini skirt with a transparent mesh bralette",
    "nipple tape with a sheer open blouse and high-cut briefs",
    "ultra-thin wet white crop top showing nipples with bikini bottoms",
    "see-through mesh long-sleeve over bare breasts with leather briefs",
    # Legacy softcore entries kept for variety.
    "triangle bikini top with adjustable ties and a sarong tied low on the hips",
    "oversized unbuttoned shirt over a lace bra set",
    "lace suspender belt with lace-top thigh-highs and a fitted satin bodice",
    "matching lace bra and briefs under a semi-sheer robe",
    "semi-sheer babydoll slip over a matching lace bra set",
    "lace bralette with high-waisted suspender shorts",
    "semi-sheer mesh cover-up over a triangle bikini",
    "velvet bralette with matching tap shorts and suspender-inspired straps",
    "lace bodysuit under a semi-sheer robe slipping open",
    "satin slip chemise with thin straps",
    "balconette bra with matching suspender straps and stockings",
    "corset bustier with a suspender belt and stockings",
    "mesh-panel bodysuit layered over a bikini",
    "deep-V bodysuit with a mesh-panel overlay",
    "lace-trim bodysuit with a deep-V neckline",
    "scalloped-lace bra and suspender set with lace-top thigh-highs",
    "off-shoulder corset crop with suspender straps and stockings",
    "semi-sheer lace bodystocking",
    "satin bustier with detachable suspender straps",
    "lace bralette under a cropped semi-sheer blouse",
    "silk bodysuit with a deep-V lace front",
    "sheer patterned bodystocking under an open kimono",
    "satin bodysuit with a deep cowl back",
    "semi-sheer lace catsuit over a bra and briefs",
    "halterneck deep-V swimsuit with a keyhole front",
    "high-leg cheeky bodysuit in mesh-panel fabric",
    "lace-trimmed silk robe falling open over a bra and high-cut briefs",
    "tie-side bikini with a long semi-sheer beach skirt",
    "cut-out monokini with a deep-V keyhole and high-cut legs",
    "semi-sheer mesh slip dress over a bikini",
    "lace bodysuit with an open back and high-cut legs",
    "satin bra with suspender straps and lace-top stay-up stockings",
    "halter monokini with a deep-V to the waist",
    "lace bodysuit with snap closures",
    "minimal triangle bikini with adjustable sliding ties",
    "deep-V halter swimsuit cinched at the waist",
    "lace bralette with a semi-sheer wrap mini skirt",
    "semi-sheer babydoll with a deep-V lace neckline",
    "satin slip robe loosely belted over a lace bra set",
    "underbust corset over a semi-sheer bra and briefs",
    "bandeau bikini top with a semi-sheer sarong knotted at the hip",
    "metallic tie-side bikini with a semi-sheer cover-up",
    "deep-V bra and suspender set under a cropped semi-sheer cardigan",
    "mesh-panel bralette with matching cheeky briefs",
    "tie-side monokini with deep side cut-outs",
    "lace bodysuit with a halter neckline and high-cut legs",
    "semi-sheer lace robe over a bandeau and briefs",
    "bodysuit with a deep-V zip front and high-cut legs",
    "satin-and-lace bustier with attached suspender straps",
    "semi-sheer kimono robe open over a matching lace bra set",
    "boudoir corset with suspender straps and stockings",
    "lace bra and high-cut briefs under a draped silk wrap",
    "tie-side bikini with a draped hip-chain detail",
    "unbuttoned denim shirt knotted above a triangle bikini",
    "babydoll dress with semi-sheer panels and a lace hem",
    "sequined bikini with a semi-sheer sarong",
    "tie-front crop top over tie-side bikini bottoms",
    "off-shoulder lace bodysuit with semi-sheer long sleeves",
    # Safe unchanged entries
    "halterneck monokini with cut-out sides",
    "high-cut one-piece swimsuit with a deep-V front",
    "athletic sports bra with a bare midriff and tiny shorts",
    "feathered burlesque corset with opera gloves and stockings",
    "metallic bandeau with high-waisted festival shorts",
    "triangle bikini top with high-cut bottoms",
    "silk camisole with delicate lace trim and boy shorts",
    "off-shoulder bodysuit with high-cut legs",
    "halter bikini with a semi-sheer wrap sarong",
    "ribbed crop tank with a bare midriff and tiny shorts",
    "strappy caged bralette with matching high-waisted briefs",
    "sporty zip-front bikini",
    "wrap-front bikini top with a tied sarong skirt",
    "satin slip nightie with lace cups",
    "cropped mesh long-sleeve over a bandeau and briefs",
    "leather harness bralette with high-cut shorts",
    "sporty crop bralette with cheeky shorts",
    "off-shoulder mesh top over a bikini",
    "neon triangle bikini",
    "cropped tank tied up high with shorts",
    "bandeau top with a low-slung wrap skirt",
    "satin cami and tap-short set with lace edging",
    "cropped tube top with matching low-rise booty shorts",
    "halter-neck bodysuit with a bare back and high legs",
    "tie-side bikini bottoms with a cropped mesh tee",
    "lace high-cut bodysuit under an open silk blouse",
    "halter crop top over high-cut swim bottoms",
    "satin bralette with a wrap-tie front and tap shorts",
    "delicate slip dress with a thigh-high slit and thin straps",
    "cropped knit halter over bikini bottoms",
    "tie-front bikini top with ruched high-cut bottoms",
    "tiny ribbed bralette with matching micro shorts",
    "mesh cover-up dress over a matching bra and briefs",
    "underwire bikini top with a beaded hip chain and bottoms",
    "crochet halter top with matching tie-side bottoms",
    "delicate chemise slipping off both shoulders",
    "ruffled halter bikini with side ties",
    "wet white tank top clinging over a bikini",
    "bandeau bikini with side-tie bottoms",
    "halter monokini with a deep side cut-out",
    "lace mini slip that ends high on the thigh",
    "thin-strap silk slip clinging to the figure",
    "boudoir robe open over a lace bra and tap shorts",
    "lace-up leather bra top with matching briefs",
]


def _expand_women_minimal_clothes() -> list[str]:
    additions: list[str] = []
    minimal_bases = (
        "triangle bikini",
        "bandeau bikini",
        "underwire bikini",
        "halter bikini",
        "crochet bikini",
        "metallic tie-side bikini",
        "lace bodysuit",
        "satin bodysuit",
        "mesh-panel bodysuit",
        "lace teddy",
        "satin slip chemise",
        "transparent mesh mini dress",
        "see-through lace slip",
        "sheer halter bodysuit",
        "fishnet bodystocking",
        "transparent mesh catsuit",
        "open mesh cover-up",
        "body tape outfit",
        "corset bustier",
        "balconette bra set",
        "lace bralette set",
        "halter monokini",
        "high-leg swimsuit",
        "plunging one-piece swimsuit",
        "strappy caged bralette set",
    )
    minimal_details = (
        "with lace-top thigh-highs",
        "with a low-tied semi-sheer sarong",
        "under an open silk robe",
        "under a semi-sheer kimono robe",
        "with a sheer mesh cover-up",
        "with bare breasts visible through the fabric",
        "with nipples visible through transparent mesh",
        "with see-through panels across the chest",
        "with open sides and a micro thong",
        "with nipple tape and a tiny bottom",
        "with detachable suspender straps",
        "with a draped silk wrap",
        "with a cropped mesh tee",
        "with a loose unbuttoned linen shirt",
        "with opera gloves and stockings",
        "with a feather-trimmed robe",
        "with a beaded hip-chain detail",
    )
    for base in minimal_bases:
        for detail in minimal_details:
            additions.append(f"{base} {detail}")

    bralette_tops = (
        "lace bralette",
        "satin bralette",
        "mesh-panel bralette",
        "rhinestone bralette",
        "velvet bralette",
        "crochet halter bralette",
        "strappy underwire bralette",
        "bandeau top",
    )
    minimal_bottoms = (
        "high-cut briefs",
        "lace tap shorts",
        "cheeky shorts",
        "high-waisted briefs",
        "tie-side bikini bottoms",
        "tiny swim shorts",
        "low-rise satin shorts",
        "a semi-sheer wrap mini skirt",
    )
    for top in bralette_tops:
        for bottom in minimal_bottoms:
            additions.append(f"{top} with {bottom}")

    additions.extend(
        [
            "lace bra and high-cut briefs under an oversized shirt falling off one shoulder",
            "deep-V mesh bodysuit with lace-top stockings",
            "satin plunge bodysuit with a low open back",
            "scalloped lace teddy with a sheer robe",
            "cut-out monokini with a low-tied beach wrap",
            "minimal resort bikini with a transparent-effect mesh skirt",
            "semi-sheer lace catsuit layered over a bandeau and briefs",
            "silk slip chemise with a feather-trimmed hem",
            "open satin robe over a balconette bra and matching briefs",
            "lace-up corset bustier with high-cut briefs and stockings",
            "halter swimsuit with high-cut legs and a deep keyhole front",
            "cropped wet-look bustier with matching briefs",
            "crochet bikini with a draped hip scarf",
            "tie-front bikini top with tiny ruched shorts",
            "sheer black cover-up over a metallic bikini",
            "lace bodysuit under an open cropped denim shirt",
            "satin bra set with a garter belt and stockings",
            "mesh wrap dress over a high-leg swimsuit",
            "plunging bodysuit with a sheer maxi cover-up",
            "strappy bikini with a long open beach shirt",
            "soft ribbed bralette with matching high-cut shorts",
            "minimal bandeau swim set with a side-tied sarong",
            "lace chemise with semi-sheer panels and thin straps",
            "deep-V one-piece swimsuit with a loose robe",
            "corset-laced swimsuit with high-cut legs",
            "silk cami and tap shorts under a translucent robe",
            "underwire bikini with lace-up side details",
            "velvet bra set with suspender straps and stockings",
            "semi-sheer babydoll over a high-leg bra set",
            "open kimono over a lace teddy and thigh-highs",
            "transparent mesh dress with bare breasts visible and a micro thong",
            "see-through lace slip clinging to bare breasts and high-cut hips",
            "fishnet bodystocking with visible nipples and a tiny thong",
            "body tape over nipples with a transparent mesh skirt",
            "open sheer robe framing bare breasts and a string thong",
            "wet transparent crop top over bare breasts with low-rise briefs",
            "micro mesh lingerie set with transparent cups and high-cut bottoms",
            "see-through halter dress with exposed nipples and a hip chain",
            "transparent lace catsuit with bare breasts visible through fabric",
            "mesh shrug over bare breasts with a tiny vinyl mini skirt",
        ]
    )
    return additions


_extend_unique(WOMEN_CLOTHES_MINIMAL, _expand_women_minimal_clothes())

MEN_CLOTHES_MINIMAL = [
    "swim shorts with a towel over one shoulder, shirtless",
    "open tropical shirt over a bare chest with board shorts",
    "unbuttoned linen shirt revealing a toned chest with rolled trousers",
    "fitted swim briefs by the pool, shirtless",
    "leather jacket over a bare chest with dark jeans",
    "sleeveless gym shirt pushed up with athletic shorts",
    "wetsuit peeled to the waist on a beach",
    "low-slung joggers with a bare chest and a towel",
    "soft resort shirt open over a bare chest with swim shorts",
    "fitted tank top with low-slung shorts",
    "board shorts with a surfboard, shirtless",
    "loose robe open over swim trunks",
    "swim briefs poolside, shirtless",
    "unbuttoned silk shirt over a bare chest",
    "gym shorts with a sweat towel, shirtless",
    "open bathrobe over swim trunks",
    "linen trousers rolled up, shirtless on a beach",
    "tank top damp after a workout",
    "fitted swim briefs dripping wet after a swim",
    "open robe revealing a bare chest, loosely belted",
    "low-slung towel wrapped at the waist, shirtless",
    "athletic shorts rolled low with a bare torso",
    "unbuttoned denim shirt over a bare chest with swim shorts",
    "snug boxer briefs with an open shirt",
    "wet board shorts on the beach, shirtless",
    "open linen shirt blowing in the breeze over swim trunks",
    "compression shorts after a workout, shirtless",
    "loose drawstring trousers worn low, bare chest",
    "swim shorts with a towel around the neck, shirtless",
    "half-zipped wetsuit pushed off the shoulders",
]

COUPLE_OUTFITS_MINIMAL = [
    "matching beachwear with a bikini and swim shorts",
    "lingerie-inspired loungewear with sheer robes",
    "a see-through mesh dress and a bare-chested partner in low-slung trousers",
    "transparent lingerie with visible nipples and a partner in boxer briefs",
    "body tape and a micro thong beside a shirtless partner",
    "an open sheer robe over bare breasts with a partner in swim briefs",
    "a lace bra set and an open shirt for an intimate evening",
    "poolside swimwear with playful accessories",
    "a silk slip with a bare-chested partner in low-slung trousers",
    "matching satin sleepwear with delicate lace trim",
    "a bikini and board shorts at the beach",
    "matching robes loosely tied",
    "his-and-hers swimwear by the pool",
    "a lace lingerie set and boxer briefs",
    "a bikini and an open beach shirt",
    "a sheer chemise and boxer briefs",
    "matching swimwear at the poolside",
    "a string bikini and swim briefs",
    "a lace teddy and a loosely tied robe",
    "satin lingerie and an open shirt",
    "a triangle bikini and low-slung board shorts",
    "matching sheer robes over lingerie",
    "a bralette set and a bare-chested partner with a towel",
    "a monokini and swim trunks by the water",
]

GROUP_SCENES = [
    ("rooftop", "rooftop party with city lights and warm string lights"),
    ("nightclub", "soft nightclub lights and blurred dance floor"),
    ("hotel_lobby", "hotel lobby with brass lights and polished stone"),
    ("beach_bar", "beach bar at golden sunset"),
    ("gallery", "quiet gallery opening with soft spotlights"),
    ("garden_party", "garden party with lanterns and flowers"),
    ("pool_party", "poolside party with sun loungers and cocktails"),
    ("rooftop_pool", "rooftop infinity pool at night with city glow"),
    ("yacht_party", "luxury yacht party at golden hour"),
    ("beach_volleyball", "sunny beach volleyball court"),
]

GROUP_COMPOSITIONS = ["group portrait", "party scene", "environmental group portrait"]

GROUP_AGES = ["21-25 adult-heavy", "early to late 20s adults", "mid-20s to 30s adults"]

LAYOUTS_FULL = [
    ("polaroid_selfie", "Polaroid selfie layout with two adult friends posing close together"),
    ("real_estate_leaflet", "fictional real estate leaflet with a glamorous adult realtor holding keys"),
    ("dance_guide", "three-pose dance guide sheet with a fully clothed adult dancer"),
    ("cocktail_menu", "fictional cocktail menu cover with a glamorous adult host holding a glass"),
    ("travel_postcard", "vintage travel postcard layout with a stylish adult traveler"),
    ("comic_panel", "single comic panel with an adult couple sharing a playful glance"),
    ("pinup_calendar", "vintage pin-up calendar page with a glamorous adult model"),
    ("lingerie_catalog", "stylish lingerie catalog page with a confident adult model"),
    ("fashion_editorial", "glossy fashion-editorial spread with a glamorous adult model"),
    ("perfume_ad", "elegant perfume advertisement layout with a glamorous adult model"),
    ("vintage_pinup_poster", "retro pin-up poster layout with a glamorous adult model"),
    ("magazine_cover", "stylish magazine cover with a confident adult cover model"),
    ("noir_comic_page", "noir comic page with a glamorous adult femme-fatale character"),
    ("boudoir_album", "boudoir photo album page with an elegant adult model"),
]

LAYOUTS_MINIMAL = [
    ("lingerie_catalog", "stylish lingerie catalog page with a confident adult model"),
    ("pinup_calendar", "vintage pin-up calendar page with a glamorous adult model in lingerie"),
    ("swimwear_lookbook", "swimwear lookbook page with a confident adult model in a bikini"),
    ("boudoir_postcard", "boudoir-style postcard layout with a glamorous adult model in lingerie"),
    ("comic_panel", "single comic panel with an adult pin-up in lingerie sharing a playful glance"),
    ("beach_poster", "retro beach poster layout with a glamorous adult model in a bikini"),
    ("swimwear_cover", "swimwear magazine cover with a confident adult model in a bikini"),
    ("boudoir_series", "boudoir photography series with an adult model in lingerie"),
    ("pinup_card", "pin-up playing card with a glamorous adult model in lingerie"),
    ("bikini_calendar", "bikini calendar page with a glamorous adult model"),
    ("lingerie_lookbook", "lingerie lookbook spread with a confident adult model"),
    ("boudoir_magazine_cover", "boudoir magazine cover with a glamorous adult model in lingerie"),
    ("swimwear_poster", "retro swimwear poster with a confident adult model in a bikini"),
    ("pinup_poster_lingerie", "vintage pin-up poster with a glamorous adult model in lingerie"),
    ("beach_lookbook", "beachwear lookbook page with an adult model in a bikini"),
    ("boudoir_postcard_set", "boudoir postcard set with an elegant adult model in lingerie"),
]


def slugify(text: str) -> str:
    text = text.lower()
    text = re.sub(r"[^a-z0-9]+", "_", text)
    return text.strip("_")[:48]


def choose(rng: random.Random, items: list):
    return items[rng.randrange(len(items))]


def batch_clothing_modes(rng: random.Random, default: str, minimal_ratio: float | None) -> list[str]:
    if minimal_ratio is None:
        return [default] * BATCH_SIZE
    minimal_count = round(BATCH_SIZE * minimal_ratio)
    modes = ["minimal"] * minimal_count + ["full"] * (BATCH_SIZE - minimal_count)
    rng.shuffle(modes)
    return modes


def batch_single_pose_modes(rng: random.Random, default: str, standard_ratio: float | None, count: int) -> list[str]:
    if standard_ratio is None:
        return [default] * count
    standard_count = round(count * standard_ratio)
    modes = ["standard"] * standard_count + ["evocative"] * (count - standard_count)
    rng.shuffle(modes)
    return modes


def batch_category_quotas() -> list[tuple[str, int]]:
    woman_count = round(BATCH_SIZE * 0.72)
    man_count = round(BATCH_SIZE * 0.12)
    couple_count = round(BATCH_SIZE * 0.12)
    group_count = BATCH_SIZE - woman_count - man_count - couple_count
    return [
        ("woman", woman_count),
        ("man", man_count),
        ("couple", couple_count),
        ("group_or_layout", group_count),
    ]


def article(phrase: str) -> str:
    return "an" if phrase[:1].lower() in "aeiou" else "a"


# Heritage keyword sets for the --ethnicity filter (matched against the skin
# field). Include both broad labels and regional names so mixed-heritage entries
# like "Japanese Latina" or "Filipina European" are included in Asian modes even
# when they do not repeat the broad East/Southeast/South Asian label.
ASIAN_KEYWORDS = (
    "east asian",
    "southeast asian",
    "south asian",
    "central asian",
    "japanese",
    "korean",
    "chinese",
    "taiwanese",
    "mongolian",
    "tibetan",
    "manchu",
    "vietnamese",
    "thai",
    "filipina",
    "filipino",
    "indonesian",
    "malay",
    "cambodian",
    "lao",
    "burmese",
    "singaporean",
    "hmong",
    "balinese",
    "indian",
    "punjabi",
    "tamil",
    "bengali",
    "sri lankan",
    "nepali",
    "pakistani",
    "gujarati",
    "bangladeshi",
    "malayali",
    "kashmiri",
    "kazakh",
    "uzbek",
    "kyrgyz",
    "uighur",
    "asian mixed",
)
WHITE_KEYWORDS = (
    "fair",
    "ivory",
    "porcelain",
    "pale",
    "scandinavian",
    "rosy",
    "freckled",
    "beige",
    "tanned",
    "european",
    "nordic",
    "celtic",
    "slavic",
    "baltic",
    "alpine",
    "balkan",
    "irish",
    "french",
    "mediterranean",
)
WESTERN_EUROPEAN_KEYWORDS = ("western european", "french", "germanic", "german", "dutch")
FRENCH_EUROPEAN_KEYWORDS = ("french",)
GERMANIC_EUROPEAN_KEYWORDS = ("germanic", "german")
NORDIC_EUROPEAN_KEYWORDS = ("nordic", "swedish", "norwegian", "danish", "scandinavian")
CELTIC_EUROPEAN_KEYWORDS = ("celtic", "irish", "scottish")
SLAVIC_EUROPEAN_KEYWORDS = ("slavic", "polish", "ukrainian")
BALTIC_EUROPEAN_KEYWORDS = ("baltic",)
ALPINE_EUROPEAN_KEYWORDS = ("alpine",)
BALKAN_EUROPEAN_KEYWORDS = ("balkan",)
GREEK_MEDITERRANEAN_KEYWORDS = ("greek",)
ITALIAN_MEDITERRANEAN_KEYWORDS = ("italian",)
IBERIAN_MEDITERRANEAN_KEYWORDS = ("spanish", "portuguese", "iberian")
EAST_ASIAN_KEYWORDS = (
    "east asian",
    "japanese",
    "korean",
    "chinese",
    "taiwanese",
    "mongolian",
    "tibetan",
    "manchu",
    "okinawan",
)
SOUTHEAST_ASIAN_KEYWORDS = (
    "southeast asian",
    "vietnamese",
    "thai",
    "filipina",
    "filipino",
    "indonesian",
    "malay",
    "cambodian",
    "lao",
    "burmese",
    "singaporean",
    "hmong",
    "balinese",
)
SOUTH_ASIAN_KEYWORDS = (
    "south asian",
    "indian",
    "punjabi",
    "tamil",
    "bengali",
    "sri lankan",
    "nepali",
    "pakistani",
    "gujarati",
    "bangladeshi",
    "malayali",
    "kashmiri",
)
MEDITERRANEAN_MENA_KEYWORDS = (
    "mediterranean",
    "greek",
    "italian",
    "spanish",
    "portuguese",
    "turkish",
    "persian",
    "levantine",
    "maghrebi",
    "egyptian",
    "moroccan",
    "amazigh",
    "kurdish",
    "middle-eastern",
    "middle eastern",
    "mena",
    "olive",
)
LATINA_KEYWORDS = (
    "latina",
    "latino",
    "mexican",
    "chicana",
    "colombian",
    "brazilian",
    "puerto rican",
    "cuban",
    "dominican",
    "venezuelan",
    "peruvian",
    "chilean",
    "argentine",
    "uruguayan",
    "ecuadorian",
)
BLACK_AFRICAN_KEYWORDS = ("african", "african-diaspora", "cape verdean")
INDIGENOUS_KEYWORDS = ("indigenous", "amazigh")
MIXED_KEYWORDS = ("mixed",)
ETHNICITY_KEYWORD_GROUPS = {
    "asian": ASIAN_KEYWORDS,
    "white_asian": WHITE_KEYWORDS + ASIAN_KEYWORDS,
    "european": WHITE_KEYWORDS,
    "western_european": WESTERN_EUROPEAN_KEYWORDS,
    "french_european": FRENCH_EUROPEAN_KEYWORDS,
    "germanic_european": GERMANIC_EUROPEAN_KEYWORDS,
    "nordic_european": NORDIC_EUROPEAN_KEYWORDS,
    "celtic_european": CELTIC_EUROPEAN_KEYWORDS,
    "slavic_european": SLAVIC_EUROPEAN_KEYWORDS,
    "baltic_european": BALTIC_EUROPEAN_KEYWORDS,
    "alpine_european": ALPINE_EUROPEAN_KEYWORDS,
    "balkan_european": BALKAN_EUROPEAN_KEYWORDS,
    "greek_mediterranean": GREEK_MEDITERRANEAN_KEYWORDS,
    "italian_mediterranean": ITALIAN_MEDITERRANEAN_KEYWORDS,
    "iberian_mediterranean": IBERIAN_MEDITERRANEAN_KEYWORDS,
    "mediterranean_mena": MEDITERRANEAN_MENA_KEYWORDS,
    "latina": LATINA_KEYWORDS,
    "east_asian": EAST_ASIAN_KEYWORDS,
    "southeast_asian": SOUTHEAST_ASIAN_KEYWORDS,
    "south_asian": SOUTH_ASIAN_KEYWORDS,
    "black_african": BLACK_AFRICAN_KEYWORDS,
    "indigenous": INDIGENOUS_KEYWORDS,
    "mixed": MIXED_KEYWORDS,
}


def by_ethnicity(pool: list, ethnicity: str) -> list:
    """Filter an appearance pool by heritage keywords found in the skin field.
    'asian' = East/Southeast/South/Central Asian; 'white_asian' = white/European + Asian;
    'any' returns the full pool."""
    ethnicity = str(ethnicity or "any").strip().lower()
    if ethnicity == "any":
        return pool
    tokens = [token.strip() for token in re.split(r"[,+|;/\s]+", ethnicity) if token.strip()]
    kws: list[str] = []
    exclude_kws: list[str] = []
    for token in tokens:
        if token.startswith("exclude_"):
            exclude_key = token.removeprefix("exclude_")
            exclude_kws.extend(ETHNICITY_KEYWORD_GROUPS.get(exclude_key, (exclude_key,)))
            continue
        if token in ("not_mixed", "no_mixed"):
            exclude_kws.extend(MIXED_KEYWORDS)
            continue
        kws.extend(ETHNICITY_KEYWORD_GROUPS.get(token, (token,)))
    filtered = [e for e in pool if any(k in e[3].lower() for k in kws)]
    if exclude_kws:
        filtered = [e for e in filtered if not any(k in e[3].lower() for k in exclude_kws)]
    return filtered or pool


def without_plus(pool: list) -> list:
    """Drop plus-size appearance entries (for the --no-plus-women filter)."""
    filtered = [e for e in pool if "plus" not in e[2].lower()]
    return filtered or pool


def without_black(pool: list) -> list:
    """Drop Black/African appearance entries (for the women-focused --no-black filter). Keyed on
    the 'African' tag in the skin field; ambiguous 'warm/deep brown skin' (mixed)
    entries are intentionally left in since they are not Black-coded."""
    filtered = [e for e in pool if "african" not in e[3].lower()]
    return filtered or pool


def figure_pool(mode: str) -> list:
    """Curve-emphasis vocabulary for single women, selected by --figure mode."""
    if mode == "bombshell":
        return FIGURE_BOMBSHELL
    if mode == "balanced":
        return FIGURE_CURVY + FIGURE_ATHLETIC
    return FIGURE_CURVY  # 'curvy' default: voluptuous-leaning mix


def make_body_phrase(body: str, figure_note: str = "") -> str:
    body = str(body or "").strip()
    figure_note = str(figure_note or "").strip()
    if not body:
        return figure_note
    if not figure_note:
        return f"{body} figure"
    if "figure" in figure_note.lower():
        return f"{body} build and {figure_note}"
    return f"{body} figure with {figure_note}"


def choose_woman(rng: random.Random, ethnicity: str = "any", no_plus: bool = False, no_black: bool = False):
    young = by_ethnicity(YOUNG_WOMEN, ethnicity)
    mature = by_ethnicity(MATURE_WOMEN, ethnicity)
    if no_plus:
        young = without_plus(young)
        mature = without_plus(mature)
    if no_black:
        young = without_black(young)
        mature = without_black(mature)
    if rng.random() < 0.84:
        return choose(rng, young)
    return choose(rng, mature)


class ExpressionDeck:
    """Draws facial expressions without replacement until the deck empties, then
    reshuffles -- guarantees an even spread of expressions across the dataset."""

    def __init__(self, items: list, rng: random.Random) -> None:
        self.items = list(items)
        self.rng = rng
        self.pool: list = []

    def draw(self) -> str:
        if not self.pool:
            self.pool = list(self.items)
            self.rng.shuffle(self.pool)
        return self.pool.pop()

    def draw_two(self) -> tuple[str, str]:
        first = self.draw()
        second = self.draw()
        if second == first:
            second = self.draw()
        return first, second


def row_base(index: int, batch: int, subject: str, age: str, body: str, scene_slug: str, composition: str) -> dict:
    short = slugify(f"{subject}_{age}_{body}_{scene_slug}_{composition}")
    return {
        "id": f"sxcp_{index:04d}",
        "file_name": f"images/sxcp_{index:04d}_{short}.png",
        "batch": f"batch_{batch:03d}",
        "primary_subject": subject,
        "age_band": age,
        "body_type": body,
        "scene": scene_slug,
        "composition": composition,
        "negative_prompt": NEGATIVE_PROMPT,
    }


def make_single(index: int, batch: int, rng: random.Random, gender: str, expr_deck: ExpressionDeck, clothing: str = "full", ethnicity: str = "any", poses: str = "standard", backside_bias: float = 0.0, figure: str = "curvy", no_plus: bool = False, no_black: bool = False) -> dict:
    minimal = clothing == "minimal"
    if gender == "woman":
        subject, age, body, skin, hair, eyes = choose_woman(rng, ethnicity, no_plus, no_black)
        clothes = choose(rng, WOMEN_CLOTHES_MINIMAL if minimal else WOMEN_CLOTHES)
        figure_note = choose(rng, figure_pool(figure))
    else:
        men_pool = by_ethnicity(MEN, ethnicity)
        subject, age, body, skin, hair, eyes = choose(rng, men_pool)
        clothes = choose(rng, MEN_CLOTHES_MINIMAL if minimal else MEN_CLOTHES)
        figure_note = ""
    body_phrase = make_body_phrase(body, figure_note)

    scene_slug, scene = choose(rng, SCENES)
    if poses == "evocative":
        if backside_bias > 0:
            if rng.random() < backside_bias:
                pool = BACKSIDE_POSES
                pose_mode = "evocative_backside"
            else:
                pool = EVOCATIVE_POSES
                pose_mode = "evocative"
        else:
            pool = EVOCATIVE_ALL
            pose_mode = "evocative"
        pose = choose(rng, pool)
        if backside_bias <= 0 and pose in BACKSIDE_POSES:
            pose_mode = "evocative_backside"
    else:
        pose = choose(rng, POSES)
        pose_mode = "standard"
    composition = choose(rng, COMPOSITIONS)
    prop = "" if _pose_has_prop(pose) else ("" if rng.random() < 0.30 else choose(rng, PROPS))
    expression = expr_deck.draw()

    row = row_base(index, batch, subject, age, body, scene_slug, composition)
    row["expression"] = expression
    row["clothing_mode"] = clothing
    row["pose_mode"] = pose_mode
    if figure_note:
        row["figure"] = figure_note
    prop_caption = f", {prop}" if prop else ""
    row["caption"] = (
        f"{TRIGGER}, {subject}, {age}, {body_phrase}, {skin}, {hair}, {eyes}, "
        f"{pose}, {expression}, {clothes}{prop_caption}, {scene}, {composition}, coloured pencil comic illustration, "
        "crisp linework, hatching, soft pastel palette, warm sensual lighting, textured paper"
    )
    prop_prompt = f"Prop/detail: {prop}. " if prop else ""
    row["prompt"] = (
        f"A {subject}: sexy but tasteful adult pin-up "
        f"coloured-pencil comic illustration, {age}, {body_phrase}, {skin}, {hair}, {eyes}. "
        f"Scene: {scene}. Pose: {pose}. Facial expression: {expression}. Clothing: {clothes}, fashion editorial styling. {prop_prompt}"
        f"Composition: vertical {composition}. Use crisp clean comic linework, detailed hatching, "
        "soft blended shading, pastel skin tones, muted blues and pinks, warm sensual lighting, "
        "and tactile textured paper. "
        f"Avoid: {NEGATIVE_PROMPT}."
    )
    return row


def make_couple(index: int, batch: int, rng: random.Random, expr_deck: ExpressionDeck, clothing: str = "full", ethnicity: str = "any", no_plus: bool = False) -> dict:
    primary_subject, subject_phrase, pose = choose(rng, COUPLE_TYPES)
    if ethnicity == "asian":
        subject_phrase = {
            "two women": "two Asian women",
            "two men": "two Asian men",
            "a woman and a man": "an Asian woman and an Asian man",
        }.get(subject_phrase, subject_phrase)
    elif ethnicity == "white_asian":
        subject_phrase = {
            "two women": "two women, both white or East Asian",
            "two men": "two men",
            "a woman and a man": "a white or East Asian woman and a man",
        }.get(subject_phrase, subject_phrase)
    scene_slug, scene = choose(rng, SCENES)
    composition = choose(rng, ["two-person portrait", "waist-up couple portrait", "full body couple pose", "seated couple portrait"])
    ages = choose(rng, COUPLE_AGES)
    body_options = ["slim and average", "curvy and broad", "plus-size and average", "fat and slim", "stocky and curvy", "average and dad bod"]
    if no_plus:
        body_options = [b for b in body_options if "plus" not in b and "fat" not in b]
    body = choose(rng, body_options)
    outfits = choose(rng, COUPLE_OUTFITS_MINIMAL if clothing == "minimal" else COUPLE_OUTFITS)
    expr_a, expr_b = expr_deck.draw_two()

    row = row_base(index, batch, primary_subject, ages, body, scene_slug, composition)
    row["expression"] = f"{expr_a}; {expr_b}"
    row["clothing_mode"] = clothing
    row["caption"] = (
        f"{TRIGGER}, {subject_phrase}, ages {ages}, {body} body types, {pose}, "
        f"one with {article(expr_a)} {expr_a} and the other with {article(expr_b)} {expr_b}, {outfits}, "
        f"{scene}, {composition}, coloured pencil comic illustration, crisp linework, hatching, "
        "soft pastel palette, warm sensual lighting, textured paper"
    )
    row["prompt"] = (
        f"{subject_phrase.capitalize()}: sexy but tasteful adult pin-up "
        f"coloured-pencil comic illustration. Ages: {ages}. Body types: {body}. Scene: {scene}. "
        f"Pose: {pose}, affectionate and flirtatious but non-explicit. "
        f"Facial expressions: one with {article(expr_a)} {expr_a}, the other with {article(expr_b)} {expr_b}. Clothing: {outfits}, resort styling. "
        f"Composition: vertical {composition}. Use crisp comic linework, detailed hatching, "
        "soft pastel colours, warm sensual lighting, and tactile textured paper. "
        f"Avoid: {NEGATIVE_PROMPT}."
    )
    return row


def make_group_or_layout(index: int, batch: int, rng: random.Random, expr_deck: ExpressionDeck, clothing: str = "full", ethnicity: str = "any", no_plus: bool = False) -> dict:
    minimal = clothing == "minimal"
    group_outfits = "minimal beachwear and lingerie-inspired outfits" if minimal else "stylish revealing party outfits"
    if ethnicity == "asian":
        eth, women_note = "Asian ", ""
    elif ethnicity == "white_asian":
        eth, women_note = "", " with the women white or East Asian"
    else:
        eth, women_note = "", ""
    body_types_text = ("slim, average, curvy, athletic, and toned" if no_plus
                       else "slim, average, curvy, broad, fat, and plus-size")
    is_group = rng.random() < 0.65
    if is_group:
        scene_slug, scene = choose(rng, GROUP_SCENES)
        composition = choose(rng, GROUP_COMPOSITIONS)
        subject = f"mixed {eth}adult group"
        age = choose(rng, GROUP_AGES)
        body = "diverse"
        scene_text = scene
        expr_a, expr_b = expr_deck.draw_two()
        row = row_base(index, batch, subject, age, body, str(scene_slug), composition)
        row["expression"] = f"{expr_a}; {expr_b}"
        row["clothing_mode"] = clothing
        row["caption"] = (
            f"{TRIGGER}, mixed {eth}adult group of women and men{women_note}, ages {age}, diverse body types, "
            f"{group_outfits}, a lively mix of expressions from {article(expr_a)} {expr_a} to {article(expr_b)} {expr_b}, "
            f"{scene_text}, {composition}, coloured pencil comic illustration, crisp linework, hatching, "
            "soft pastel palette, warm sensual lighting, textured paper"
        )
        row["prompt"] = (
            f"A mixed {eth}adult group of women and men{women_note}: sexy but tasteful "
            f"adult pin-up coloured-pencil comic illustration, ages {age}, diverse body types including "
            f"{body_types_text}. Scene: {scene_text}. Clothing: {group_outfits}, resort styling. "
            f"Facial expressions: a lively mix including {article(expr_a)} {expr_a} and {article(expr_b)} {expr_b}. "
            f"Composition: vertical {composition}. Use crisp linework, hatching, soft pastel palette, "
            f"warm sensual lighting, and textured paper. Avoid: {NEGATIVE_PROMPT}."
        )
        return row

    layout_slug, layout_desc = choose(rng, LAYOUTS_MINIMAL if minimal else LAYOUTS_FULL)
    if ethnicity == "asian":
        layout_desc = layout_desc.replace("adult", "Asian adult")
    elif ethnicity == "white_asian":
        layout_desc = layout_desc.replace("adult", "white or East Asian adult")
    expression = expr_deck.draw()
    subject = "layout scene"
    age = "adult"
    body = "varied"
    composition = "designed illustration layout"
    row = row_base(index, batch, subject, age, body, layout_slug, composition)
    row["expression"] = expression
    row["clothing_mode"] = clothing
    row["caption"] = (
        f"{TRIGGER}, {layout_desc} with {article(expression)} {expression}, sexy tasteful adult pin-up styling, clean designed layout, "
        "coloured pencil comic illustration, crisp linework, hatching, soft pastel palette, warm sensual lighting, "
        "textured parchment paper"
    )
    row["prompt"] = (
        f"{layout_desc.capitalize()}: sexy but tasteful adult pin-up "
        "coloured-pencil comic illustration, adults only, clean graphic composition, "
        "pastel palette, crisp comic linework, detailed hatching, warm sensual lighting, tactile parchment texture. "
        f"Facial expression: {expression}. "
        f"Avoid: {NEGATIVE_PROMPT}. Use no readable text unless the layout naturally needs small decorative placeholder marks."
    )
    return row


def build_rows(total: int, start_index: int, clothing: str = "full", ethnicity: str = "any", poses: str = "standard", backside_bias: float = 0.0, figure: str = "curvy", no_plus: bool = False, no_black: bool = False, minimal_clothing_ratio: float | None = None, standard_pose_ratio: float | None = None, seed: int = DEFAULT_RNG_SEED, expression_seed: int = EXPRESSION_SEED) -> list[dict]:
    rng = random.Random(seed)
    expr_deck = ExpressionDeck(EXPRESSIONS, random.Random(expression_seed))
    rows: list[dict] = []
    batch_quotas = batch_category_quotas()
    if sum(count for _, count in batch_quotas) != BATCH_SIZE:
        raise ValueError("batch quotas must match batch size")
    single_subject_count = sum(count for category, count in batch_quotas if category in ("woman", "man"))

    batch_count = total // BATCH_SIZE
    index = start_index
    for batch in range(1, batch_count + 1):
        batch_rows: list[dict] = []
        clothing_modes = batch_clothing_modes(rng, clothing, minimal_clothing_ratio)
        single_pose_modes = batch_single_pose_modes(rng, poses, standard_pose_ratio, single_subject_count)
        for category, count in batch_quotas:
            for _ in range(count):
                row_clothing = clothing_modes.pop()
                if category == "woman":
                    row_pose = single_pose_modes.pop()
                    row = make_single(index, batch, rng, "woman", expr_deck, row_clothing, ethnicity, row_pose, backside_bias, figure, no_plus, no_black)
                elif category == "man":
                    row_pose = single_pose_modes.pop()
                    row = make_single(index, batch, rng, "man", expr_deck, row_clothing, ethnicity, row_pose, backside_bias, figure, no_plus, no_black)
                elif category == "couple":
                    row = make_couple(index, batch, rng, expr_deck, row_clothing, ethnicity, no_plus)
                else:
                    row = make_group_or_layout(index, batch, rng, expr_deck, row_clothing, ethnicity, no_plus)
                batch_rows.append(row)
                index += 1
        rng.shuffle(batch_rows)
        rows.extend(batch_rows)
    return rows


def write_jsonl(path: Path, rows: list[dict]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", encoding="utf-8") as handle:
        for row in rows:
            handle.write(json.dumps(row, ensure_ascii=True, sort_keys=True) + "\n")


def write_batch_helpers(batch_dir: Path, rows: list[dict], command_text: str) -> None:
    prompt_blocks = []
    captions_dir = batch_dir / "captions"
    captions_dir.mkdir(parents=True, exist_ok=True)
    # Clear stale sidecars so renamed rows do not leave orphans behind.
    for old in captions_dir.glob("*.txt"):
        old.unlink()

    for row in rows:
        image_name = Path(row["file_name"]).name
        prompt_blocks.append(
            "\n".join(
                [
                    row["id"],
                    f"file: {row['file_name']}",
                    f"age: {row['age_band']}",
                    f"subject: {row['primary_subject']}",
                    f"prompt: {row['prompt']}",
                    f"caption: {row['caption']}",
                ]
            )
        )
        caption_path = captions_dir / f"{Path(image_name).stem}.txt"
        caption_path.write_text(row["caption"] + "\n", encoding="utf-8")

    (batch_dir / "prompts.txt").write_text("\n\n---\n\n".join(prompt_blocks) + "\n", encoding="utf-8")
    (batch_dir / "command.txt").write_text(
        "\n".join(
            [
                f"batch: {batch_dir.name}",
                f"rows: {len(rows)}",
                f"batch_size: {BATCH_SIZE}",
                "command:",
                command_text,
                "",
            ]
        ),
        encoding="utf-8",
    )


def rebuild_all_prompts(out_dir: Path) -> None:
    """Rebuild all_prompts.jsonl from whatever batch metadata is on disk, so the
    aggregate always matches reality even when only some batches were written."""
    lines: list[str] = []
    for batch_dir in sorted(out_dir.glob("batch_*")):
        meta = batch_dir / "metadata.jsonl"
        if meta.exists():
            for line in meta.read_text(encoding="utf-8").splitlines():
                if line.strip():
                    lines.append(line)
    (out_dir / "all_prompts.jsonl").write_text("\n".join(lines) + "\n", encoding="utf-8")


def write_readme(out_dir: Path, total: int, batch_size: int, clothing: str = "full", minimal_clothing_ratio: float | None = None) -> None:
    if minimal_clothing_ratio is not None:
        clothing_note = (
            f"Clothing mode: **mixed**. Each batch uses approximately "
            f"{minimal_clothing_ratio:.0%} minimal clothing rows and "
            f"{1.0 - minimal_clothing_ratio:.0%} full clothing rows."
        )
    elif clothing == "minimal":
        clothing_note = (
            "Clothing mode: **minimal**. Every clothing pool is restricted to the most revealing "
            "adult wardrobe register: lingerie, swimwear, sheer mesh, see-through lace, body tape, "
            "micro pieces, and transparent layers. Use this set for the most revealing outputs."
        )
    else:
        clothing_note = (
            "Clothing mode: **full**. Women's clothing is skewed toward revealing pin-up styling "
            "(lingerie, swimwear, crop tops, garters, cleavage) alongside some glam options."
        )
    text = f"""# Prompt Batches

Generated prompt manifests for the `sxcpinup_coloredpencil` adult-only dataset.

- Total rows: {total}
- Batch size: {batch_size}
- Batch directories: `batch_001` onward
- Each row includes: `id`, `file_name`, `batch`, metadata fields (incl. `expression`, `clothing_mode`, and single-subject `pose_mode`), `prompt`, `caption`, and `negative_prompt`

Use `prompt` to generate each image. Save the output to the row's `file_name` path inside that batch directory, then use `caption` for training metadata or sidecar captions. For quick review, each batch also includes `prompts.txt`.

Safety/style boundary: adult-only erotic pin-up and fashion imagery. Minimal clothing can include direct sheer, mesh, see-through, body-tape, and exposed-nipple wardrobe wording.

{clothing_note}

Variety: appearance, scene, pose, composition, prop, clothing, and facial-expression axes are all sampled independently. Facial expression is decoupled from body pose and drawn from a 40-entry deck for an even spread. Age mix stays young-adult heavy: most single-woman prompts are 21-25 adults, with a smaller late-20s to 50s diversity tail plus men, couples, groups, and layout scenes.
"""
    readme = out_dir / "README.md"
    if readme.exists():
        # Never clobber a manually-edited README (it doubles as a working
        # instructions/state file). Write a sidecar instead.
        (out_dir / "README.generated.md").write_text(text, encoding="utf-8")
        print(f"README.md exists -- left untouched; wrote {out_dir/'README.generated.md'} instead")
        return
    readme.write_text(text, encoding="utf-8")


def parse_write_batches(spec: str, batch_count: int) -> set[int]:
    spec = spec.strip().lower()
    if spec in ("all", "*", ""):
        return set(range(1, batch_count + 1))
    selected: set[int] = set()
    for part in spec.split(","):
        part = part.strip()
        if not part:
            continue
        if "-" in part:
            lo, hi = part.split("-", 1)
            selected.update(range(int(lo), int(hi) + 1))
        else:
            selected.add(int(part))
    return {n for n in selected if 1 <= n <= batch_count}


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--out-dir", default="dataset/prompt_batches")
    parser.add_argument("--total", type=int, default=DEFAULT_TOTAL)
    parser.add_argument("--start-index", type=int, default=DEFAULT_START_INDEX)
    parser.add_argument(
        "--write-batches",
        default="all",
        help="Which batches to write, e.g. 'all' or '2-12' or '2,4,6'. "
        "Batches are always computed for the full range so ids stay consistent.",
    )
    parser.add_argument(
        "--clothing",
        choices=["full", "minimal"],
        default="full",
        help="'full' = the broad revealing+glam wardrobe; "
        "'minimal' = local adult sheer/mesh/see-through lingerie and micro-wear styling.",
    )
    parser.add_argument(
        "--minimal-clothing-ratio",
        type=float,
        default=None,
        help="Optional 0.0-1.0 per-batch ratio of minimal clothing rows. "
        "When set, this mixes minimal and full clothing and overrides --clothing.",
    )
    parser.add_argument(
        "--ethnicity",
        choices=["any", *ETHNICITY_KEYWORD_GROUPS.keys()],
        default="any",
        help="'any' = balanced heritage mix (default). Other values restrict the appearance pool "
        "by heritage keywords, e.g. east_asian, latina, black_african, mixed, asian, or white_asian.",
    )
    parser.add_argument(
        "--poses",
        choices=["standard", "evocative"],
        default="standard",
        help="'standard' = the default body-pose pool; 'evocative' = poses worded "
        "with neutral movement/fashion vocabulary that read alluring (single subjects).",
    )
    parser.add_argument(
        "--standard-pose-ratio",
        type=float,
        default=None,
        help="Optional 0.0-1.0 per-batch ratio of standard poses among single-subject rows. "
        "When set, this mixes standard and evocative poses and overrides --poses for single subjects.",
    )
    parser.add_argument(
        "--backside-bias",
        type=float,
        default=0.0,
        help="0.0-1.0 probability that a single-subject evocative pose is a "
        "rear/back-to-viewer angle (applies to evocative single-subject rows).",
    )
    parser.add_argument(
        "--figure",
        choices=["curvy", "balanced", "bombshell"],
        default="curvy",
        help="Curve emphasis for single women, described with filter-safe "
        "figure-drawing vocabulary (full bust, cinched waist, full hips, "
        "peach-shaped rear): 'curvy' (default, voluptuous-leaning mix), "
        "'balanced' (adds athletic/slim variety), 'bombshell' (maximum exaggerated hourglass).",
    )
    parser.add_argument(
        "--no-plus-women",
        action="store_true",
        help="Exclude plus-size women (and fat/plus couple combos); men are unaffected.",
    )
    parser.add_argument(
        "--no-black",
        action="store_true",
        help="Exclude Black/African-coded women. Men, couples, groups, and layouts "
        "are unaffected by this filter, so "
        "the image model may still render Black people there.",
    )
    args = parser.parse_args()

    if args.total % BATCH_SIZE != 0:
        raise ValueError(f"total must be divisible by {BATCH_SIZE}")
    if args.minimal_clothing_ratio is not None and not 0.0 <= args.minimal_clothing_ratio <= 1.0:
        raise ValueError("--minimal-clothing-ratio must be between 0.0 and 1.0")
    if args.standard_pose_ratio is not None and not 0.0 <= args.standard_pose_ratio <= 1.0:
        raise ValueError("--standard-pose-ratio must be between 0.0 and 1.0")

    out_dir = Path(args.out_dir)
    rows = build_rows(
        args.total,
        args.start_index,
        args.clothing,
        args.ethnicity,
        args.poses,
        args.backside_bias,
        args.figure,
        args.no_plus_women,
        args.no_black,
        args.minimal_clothing_ratio,
        args.standard_pose_ratio,
    )
    batch_count = args.total // BATCH_SIZE
    write_set = parse_write_batches(args.write_batches, batch_count)
    command_text = "python3 " + shlex.join(sys.argv)

    written: list[str] = []
    skipped: list[str] = []
    for offset in range(0, len(rows), BATCH_SIZE):
        batch_rows = rows[offset : offset + BATCH_SIZE]
        batch_name = batch_rows[0]["batch"]
        batch_number = int(batch_name.split("_")[1])
        batch_dir = out_dir / batch_name

        if batch_number not in write_set:
            continue

        images_dir = batch_dir / "images"
        images_dir.mkdir(parents=True, exist_ok=True)
        if any(images_dir.iterdir()):
            skipped.append(f"{batch_name} (images present)")
            continue

        write_jsonl(batch_dir / "metadata.jsonl", batch_rows)
        write_batch_helpers(batch_dir, batch_rows, command_text)
        written.append(batch_name)

    rebuild_all_prompts(out_dir)
    write_readme(out_dir, args.total, BATCH_SIZE, args.clothing, args.minimal_clothing_ratio)
    clothing_summary = (
        f"mixed minimal_ratio={args.minimal_clothing_ratio}"
        if args.minimal_clothing_ratio is not None
        else args.clothing
    )
    pose_summary = (
        f"mixed standard_ratio={args.standard_pose_ratio}"
        if args.standard_pose_ratio is not None
        else args.poses
    )
    print(f"Wrote batches: {', '.join(written) if written else '(none)'} (clothing={clothing_summary}, ethnicity={args.ethnicity}, poses={pose_summary}, figure={args.figure}, no_plus_women={args.no_plus_women}, no_black={args.no_black})")
    if skipped:
        print(f"Skipped (already rendered): {', '.join(skipped)}")
    print(f"Output dir: {out_dir}")


if __name__ == "__main__":
    main()
