"""The Gradio Blocks UI: a scene-based, animated pixel detective game. Single-player: one GameController lives in gr.State per session. The interrogation stage (animated suspect sprite in a room), the notebook, and the scenery are deterministic CSS-animated graphics. Suspect dialogue streams into a visual-novel box; hidden state never reaches the browser. SFX/music play client-side from data URIs. """ from __future__ import annotations import random from concurrent.futures import ThreadPoolExecutor from pathlib import Path import gradio as gr from ..config import get_settings from ..schemas.enums import MotiveCategory from ..suspects.scrub import scrub_spoken from .app_state import ( _TTS_DIR, GameController, _load_audio, _obtain_shared_backend, _obtain_shared_tts, note_interaction, start_case_buffer, ) from .formatters import ( briefing_html, dialogue_html, evidence_html, how_to_play_html, notebook_html, stage_html, verdict_html, ) from .theme import build_css _WEAPON_DECOYS = ("Poison", "Strangulation", "A fall") _FLAVOR_LINES = ( "Opening the precinct", "Dusting for fingerprints", "Rounding up the usual suspects", "Brewing the detective's coffee", "Reviewing the case files", "Polishing the interrogation lamp", "Chasing a lead down the alley", "Cataloguing the evidence", "Tuning the suspects' alibis", ) def _audio_setup_js(sfx: dict[str, str], music: str) -> str: """Load-event JS: create the audio elements, define the audio/animation helpers, cycle the loading-screen flavor text, and try to start the music. Gradio strips