aether-garden / ui /explore.py
kavyabhand's picture
Deploy Aether Garden application
781b9f7 verified
Raw
History Blame Contribute Delete
4.71 kB
"""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 """
<div class="room-empty">
<p class="room-empty-title">Step into the world</p>
<p class="room-empty-text">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.</p>
</div>
"""
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'<span class="loc-vibe">{v}</span>' for v in loc.get("vibe_tags", [])[:4])
return f"""
<div class="room-view">
<div class="room-hero" style="{bg}">
<div class="room-hero-scrim"></div>
<div class="room-hero-text">
<p class="room-eyebrow">You step inside</p>
<h2 class="room-name">{loc['name']}</h2>
<p class="room-ambience">{ambience}</p>
</div>
</div>
<div class="room-body">
<div class="loc-card-vibes">{vibes}</div>
<p class="room-lore">{loc['full_lore']}</p>
<p class="room-special">✦ {loc.get('special_property', '')}</p>
<p class="room-count">{count} {'soul dwells' if count == 1 else 'souls dwell'} here — click a face to meet them</p>
</div>
</div>
"""
def render_soul(entity_id: str | None, entity_ids: list[str] | None):
from world.entities import get_entity
if not entity_id:
return '<div class="feed-empty">Click a soul above to meet them.</div>'
entity = get_entity(entity_id)
if not entity:
return '<div class="feed-empty">They have wandered off.</div>'
return render_entity_card(entity)
def random_place_id() -> int:
ids = place_ids()
return random.choice(ids) if ids else 0