"""Interactive "step inside" exploration — click a place, meet who's there. Built on Gradio's native Gallery component (reliable, image-rich, clickable) plus a couple of HTML panels, so exploration feels spatial instead of dropdown-driven. """ from __future__ import annotations import random from ui import assets from ui.entity_card import render_entity_card from world.entities import get_entities_by_location from world.locations import get_all_locations, get_location_by_id _AMBIENCE = { "library": "Dust hangs in the candlelight. Somewhere a quill is still writing.", "sea": "Cold fog rolls in. A voice you almost recognise calls your name.", "clock-forest": "Everything ticks slightly out of time. The leaves fall upward.", "moon-market": "A hundred unnamed colours of lantern-light. Smoke that smells sweet.", "valley": "The ground rises and falls — slow, enormous, breathing.", "crossroads": "The bonfire shifts colour as you approach. It is deciding about you.", "mirror-bogs": "The water is too still. Your reflection arrives a moment late.", "hollow-mountain": "Cold stone, older light. Echoes of rooms that are elsewhere.", } def place_gallery_items() -> list: """(image_url, caption) tuples for the 8 places, in stable order. We pass the served Gradio file URL (not a filesystem path) so that dynamic gallery updates work across Gradio 5.5+/6 without tripping the stricter local-file cache check. """ items = [] for loc in get_all_locations(): name = assets.LOCATION_IMAGES.get(loc["slug"]) count = loc.get("entity_count", 0) caption = f"{loc['name'].replace('The ', '')} · {count} here" if name: items.append((str(assets.ASSET_DIR / name), caption)) return items def place_ids() -> list[int]: return [loc["id"] for loc in get_all_locations() if assets.LOCATION_IMAGES.get(loc["slug"])] def _soul_tile_image(entity: dict, slug: str | None) -> str | None: """Bespoke cutout portrait for a soul (named art, else type fallback).""" return assets.sprite_path(entity) def soul_gallery(location_id: int) -> tuple[list, list[str]]: """Return (gallery_items, ordered_entity_ids) for everyone in a place.""" loc = get_location_by_id(location_id) slug = loc["slug"] if loc else None entities = get_entities_by_location(location_id, limit=24) items, ids = [], [] for e in entities: img = _soul_tile_image(e, slug) if not img: continue label = e["display_name"] if e["status"] == "legendary": label = "★ " + label items.append((img, label)) ids.append(e["id"]) return items, ids def render_room(location_id: int | None) -> str: if not location_id: return """

Step into the world

Choose a place above. You'll walk inside it, feel its air, and meet the souls who live there. Click any soul to truly meet them.

""" loc = get_location_by_id(location_id) if not loc: return "" img = assets.location_image_url(loc["slug"]) bg = f"background-image: url('{img}');" if img else "" ambience = _AMBIENCE.get(loc["slug"], loc.get("aesthetic_description", "")) count = loc.get("entity_count", 0) vibes = "".join(f'{v}' for v in loc.get("vibe_tags", [])[:4]) return f"""

You step inside

{loc['name']}

{ambience}

{vibes}

{loc['full_lore']}

✦ {loc.get('special_property', '')}

{count} {'soul dwells' if count == 1 else 'souls dwell'} here — click a face to meet them

""" def render_soul(entity_id: str | None, entity_ids: list[str] | None): from world.entities import get_entity if not entity_id: return '
Click a soul above to meet them.
' entity = get_entity(entity_id) if not entity: return '
They have wandered off.
' return render_entity_card(entity) def random_place_id() -> int: ids = place_ids() return random.choice(ids) if ids else 0