""" The Podium — Hybrid Gradio/HTML UI Python handles all LLM calls; HTML/JS handles all visual interaction. State is kept server-side (_SESSIONS dict); only a JSON-safe session_id travels through Gradio, so no serialisation errors. """ import html import json import os import re import subprocess import sys import threading import time import traceback import uuid from pathlib import Path from typing import Any, Dict, List, Optional, Tuple import gradio as gr from dotenv import load_dotenv load_dotenv() import config import content import engine import portraits from engine import GameState, RoundLog # ── Server-side stores ─────────────────────────────────────────────────────── _SESSIONS: Dict[str, GameState] = {} # session_id → GameState _LAST_LOGS: Dict[str, RoundLog] = {} # session_id → most-recent RoundLog _APP_STATES: Dict[str, dict] = {} # session_id → lightweight state dict _PROCESSING: set = set() # keys currently being processed (dedup) _ACTION_LOCK = threading.Lock() def _new_sid() -> str: return uuid.uuid4().hex def _get_gs(sid: Optional[str]) -> Optional[GameState]: return _SESSIONS.get(sid) if sid else None def _get_log(sid: Optional[str]) -> Optional[RoundLog]: return _LAST_LOGS.get(sid) if sid else None def _save(state: dict, gs: Optional[GameState] = None, log: Optional[RoundLog] = None) -> dict: sid = state.get("session_id") or _new_sid() state = {k: v for k, v in state.items()} state["session_id"] = sid _APP_STATES[sid] = state if gs is not None: _SESSIONS[sid] = gs if log is not None: _LAST_LOGS[sid] = log return state # ── CSS (injected via Gradio css= param — always in document
) ───────── APP_CSS = """ :root { --bg-base: #0a0812; --bg-table: #0f0d22; --bg-card: #12102a; --gold: #c9a84c; --gold-glow: rgba(201,168,76,0.2); --gold-dim: rgba(201,168,76,0.25); --purple: #7a5ba6; --purple-g: rgba(122,91,166,0.3); --red: #8b1a2a; --red-mid: #c16070; --green: #4a8c5c; --txt1: #e8e0cc; --txt2: #c4b89a; --txt3: #3d3550; --noun-c: #5b7fa6; --noun-color: #5b7fa6; --adj-c: #a65b3a; --verb-c: #4a8c5c; --wild-c: #7a5ba6; --wild-color: #7a5ba6; --r: 2px; --chip-r: 2px; --sh: inset 0 2px 5px rgba(0,0,0,0.5), 0 4px 14px rgba(0,0,0,0.6); --shf: 0 8px 32px rgba(0,0,0,0.7); --bg-raised: #1a1730; --bg-overlay: rgba(255,255,255,0.05); --text-primary: var(--txt1); --text-secondary: var(--txt2); --text-muted: var(--txt3); --purple-dim: rgba(201,168,76,0.15); --red-dim: rgba(139,26,42,0.2); } /* ── Base size increase — scales everything proportionally ── */ html { font-size: 17px; } /* Component-level overrides where proportional scaling needs tuning */ .sentence-text { font-size: 1.26rem; line-height: 1.6; } .topic-text { font-size: 1.44rem; line-height: 1.55; } .ability-description { font-size: 1.02rem; line-height: 1.5; } .chip { font-size: 1.14rem; padding: 12px 22px; } .card { padding: 24px; } .lb-label { font-size: 0.86rem; } .lb-value { font-size: 1.2rem; } .verdict-text { font-size: 1.2rem; line-height: 1.65; } .postgame-card { font-size: 1.14rem; } /* Spacing improvements */ .round-screen { gap: 29px; } .setup-screen { gap: 34px; } .verdict-screen { gap: 29px; } .argument-sentences { gap: 14px; } .argument-sentence { padding: 17px 22px; } .debater-grid { gap: 14px; } .ability-hand { gap: 17px; } /* Gradio shell reset */ html, body { height: auto !important; min-height: 0 !important; overflow-x: hidden; } body, .gradio-container { background: var(--bg-base) !important; } .gradio-container { max-width: 100% !important; padding: 0 !important; margin: 0 !important; } /* HF iframe-resizer: shrink Gradio HTML shell to match game content */ #podium-ui, #podium-ui.block, #podium-ui > .html-container, #podium-ui .html-container, #podium-ui .prose { height: auto !important; min-height: 0 !important; max-height: none !important; overflow: visible !important; } .block:has(#podium-action-input), .block:has(#podium-action-btn) { height: 0 !important; min-height: 0 !important; margin: 0 !important; padding: 0 !important; overflow: hidden !important; border: none !important; visibility: hidden !important; } footer { display: none !important; } .progress-bar-wrap, .progress-bar, .eta-bar, .meta { display: none !important; } .queue-position { display: none !important; } /* Suppress Gradio queue grey-out — keep game UI and in-button loading dots visible */ .generating { border: none !important; opacity: 1 !important; filter: none !important; } #podium-ui, #podium-ui.block, #podium-ui.wrap, #podium-ui.generating, #podium-ui .html-container, #podium-ui .html-container.pending, #podium-ui .prose, #podium-ui .prose.pending, #podium-ui .pending, #podium-ui [class*="pending"] { opacity: 1 !important; filter: none !important; pointer-events: auto !important; } .block:has(#podium-ui).pending, .block:has(#podium-ui).generating { opacity: 1 !important; filter: none !important; background: transparent !important; } .gradio-container .loader, .gradio-container .load-wrap, .gradio-container .progress-level, .gradio-container .progress-text, .gradio-container .overlay { display: none !important; background: transparent !important; backdrop-filter: none !important; } .gradio-container .block.pending, .gradio-container .wrap.generating { opacity: 1 !important; background: transparent !important; } .block { padding: 0 !important; margin: 0 !important; background: transparent !important; border: none !important; } [class*="svelte"] { background: transparent !important; border: none !important; box-shadow: none !important; } /* Hide the action channel but keep it functional */ #podium-action-input { position: fixed !important; left: -9999px !important; top: 0 !important; width: 1px !important; height: 1px !important; overflow: hidden !important; opacity: 0 !important; } #podium-action-btn { position: fixed !important; left: -9990px !important; top: 0 !important; width: 40px !important; height: 24px !important; opacity: 0 !important; z-index: 9999 !important; } /* ── Texture overlay ── */ .tex { position: relative; overflow: hidden; } .tex::after { content: ''; position: absolute; inset: 0; z-index: 5; pointer-events: none; background-image: url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20200%20200%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cfilter%20id%3D%22f%22%3E%3CfeTurbulence%20type%3D%22fractalNoise%22%20baseFrequency%3D%220.75%22%20numOctaves%3D%224%22%20stitchTiles%3D%22stitch%22%2F%3E%3C%2Ffilter%3E%3Crect%20width%3D%22200%22%20height%3D%22200%22%20filter%3D%22url%28%23f%29%22%2F%3E%3C%2Fsvg%3E"); background-size: 200px 200px; opacity: 0.04; } /* ── Scrollbar ── */ ::-webkit-scrollbar { width: 5px; background: var(--bg-base); } ::-webkit-scrollbar-thumb { background: #3d3550; border-radius: 2px; } /* ── Game UI ── */ .pw { font-family: 'Inter', sans-serif; color: var(--txt1); background: var(--bg-base); min-height: 0; padding: 0; } .pw * { box-sizing: border-box; } .screen { max-width: 1200px; margin: 0 auto; padding: 38px 24px 96px; } /* ── Card strip decorators ── */ .card-strip-top { border-top: 1px solid rgba(201,168,76,0.5); border-bottom: 1px solid rgba(201,168,76,0.2); height: 17px; flex-shrink: 0; } .card-strip-bot { border-top: 1px solid rgba(201,168,76,0.2); border-bottom: 1px solid rgba(201,168,76,0.5); height: 17px; flex-shrink: 0; } /* ── Cards ── */ .card { background: var(--bg-card); border: 2px solid rgba(201,168,76,0.2); border-radius: var(--r); box-shadow: var(--sh); transition: transform .15s, box-shadow .15s, border-color .15s; cursor: pointer; display: flex; flex-direction: column; position: relative; overflow: hidden; } .card:hover { transform: translateY(-4px); box-shadow: var(--shf); border-color: rgba(201,168,76,0.4); } .card.selected { border-color: var(--gold); box-shadow: 0 0 0 2px rgba(201,168,76,0.18), var(--shf); transform: translateY(-4px); } .card.used { opacity: .35; filter: saturate(0.18); cursor: not-allowed; pointer-events: none; } /* ── Debater cards ── */ .debater-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; margin: 19px 0; } @media (max-width: 960px) { .debater-grid { grid-template-columns: repeat(3, 1fr); } } .debater-grid.round-roster { display: flex; flex-wrap: wrap; gap: 17px; align-items: stretch; } .debater-grid.round-roster .debater-card { min-width: 200px; max-width: 280px; flex: 1 1 220px; } .debater-card { display: flex; flex-direction: column; text-align: center; cursor: pointer; min-width: 0; max-width: none; min-height: 252px; height: 100%; width: 100%; position: relative; } .debater-card.card:hover, .debater-card.card.selected { transform: none; } .debater-card.card:hover { border-color: rgba(201,168,76,0.45); box-shadow: var(--shf); } .debater-card.card.selected { border-color: var(--gold); box-shadow: 0 0 0 2px rgba(201,168,76,0.18), var(--shf); } .debater-card-body { flex: 1; display: flex; flex-direction: column; align-items: stretch; padding: 12px 14px 0; text-align: center; } .debater-emoji { font-size: 2.64rem; line-height: 1; margin-bottom: 6px; align-self: center; } /* Custom portraits — disable via config.USE_CUSTOM_PORTRAITS */ .portrait-slot { display: inline-flex; align-items: center; justify-content: center; position: relative; line-height: 1; } .portrait-slot .portrait-img { width: 100%; height: 100%; object-fit: contain; display: block; } .portrait-slot:not(.portrait-fallback) .portrait-emoji { display: none; } .portrait-slot.portrait-fallback .portrait-emoji { display: block; } .debater-emoji .portrait-slot { width: 2.64rem; height: 2.64rem; font-size: 2.64rem; } .debater-summary .portrait-slot { width: 1.92rem; height: 1.92rem; font-size: 1.92rem; flex-shrink: 0; } .judge-portrait-wrap .portrait-slot { width: 72px; height: 72px; font-size: 3.6rem; } .arg-debater-compact-emoji .portrait-slot { width: 1.4rem; height: 1.4rem; font-size: 1.4rem; } .vr-debater-emoji .portrait-slot { width: 2rem; height: 2rem; font-size: 2rem; } .debater-name { font-family: 'Bebas Neue', sans-serif; font-size: 1.06rem; color: var(--gold); letter-spacing: 0.08em; line-height: 1.15; margin-bottom: 5px; align-self: center; } .debater-line { font-family: 'Inter', sans-serif; font-style: italic; font-size: 0.86rem; color: var(--txt1); line-height: 1.35; flex: 1; margin-bottom: 7px; width: 100%; min-height: 5.4em; } .debater-tag { font-family: 'Bebas Neue', sans-serif; font-size: 0.70rem; letter-spacing: 0.1em; padding: 2px 8px; border-radius: 2px; border: 1px solid var(--txt3); color: var(--txt3); display: inline-block; margin-bottom: 12px; margin-top: auto; align-self: center; } .debater-card[data-tag="HIGH INTENSITY"] .debater-tag, .debater-card[data-tag="HIGH RISK"] .debater-tag { border-color: var(--red-mid); color: var(--red-mid); } .debater-card[data-tag="PRECISION"] .debater-tag, .debater-card[data-tag="UNSHAKEABLE"] .debater-tag, .debater-card[data-tag="RESULTS ONLY"] .debater-tag { border-color: var(--noun-c); color: var(--noun-c); } .debater-card[data-tag="JUDGE FAVOURITE"] .debater-tag, .debater-card[data-tag="MEMORABLE"] .debater-tag { border-color: var(--wild-c); color: var(--wild-c); } .debater-card[data-tag="UNPREDICTABLE"] .debater-tag, .debater-card[data-tag="PLAYS AGAINST"] .debater-tag { border-color: var(--gold); color: var(--gold); } .debater-card[data-tag="BALANCED"] .debater-tag { border-color: var(--green); color: var(--green); } .debater-card.selected .debater-name { color: var(--gold); } .debater-card.used { opacity: 0.35; filter: saturate(0.18); pointer-events: none; } .debater-card.used .debater-tag::before { content: "DEPLOYED · "; } .d-badge { position: absolute; top: 17px; right: 0; background: var(--gold); color: #0a0812; width: 29px; height: 29px; font-family: 'Bebas Neue', sans-serif; font-size: 0.74rem; letter-spacing: 0; display: flex; align-items: center; justify-content: center; border-radius: 0 0 0 4px; z-index: 10; } /* ── Word chips ── */ .chip { display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; width: 134px; height: 79px; background: var(--bg-card); border: 1.5px solid transparent; border-radius: var(--chip-r); cursor: pointer; position: relative; box-shadow: inset 0 2px 5px rgba(0,0,0,0.5); transition: border-color .12s, box-shadow .12s; flex-shrink: 0; overflow: visible; margin: 6px; } .chip.pool-preview { cursor: default; pointer-events: none; } .chip-word { font-family: 'Bebas Neue', sans-serif; color: var(--txt1); font-size: 1.03rem; letter-spacing: 0.05em; line-height: 1; } .chip-div { width: 68%; height: 1px; background: rgba(201,168,76,0.2); } .chip-cat { font-family: 'Bebas Neue', sans-serif; color: #9a8e78; font-size: 0.86rem; letter-spacing: 0.14em; line-height: 1; } .chip.noun { border-color: var(--noun-c); } .chip.adj { border-color: var(--adj-c); } .chip.verb { border-color: var(--verb-c); } .chip.wild, .chip.wildcard { border-color: var(--wild-c); } .chip:hover { border-color: rgba(201,168,76,0.5); filter: brightness(1.15); } .chip.selected { border: 2.5px solid var(--gold); box-shadow: 0 0 0 2px rgba(201,168,76,0.15), inset 0 2px 4px rgba(0,0,0,0.4); } .chip.selected .chip-word { color: var(--gold); } .chip.selected .chip-div { background: rgba(201,168,76,0.35); } .chip.selected .chip-cat { color: #7a6e8a; } .chip.selected::after { content: attr(data-order); position: absolute; top: -7px; right: -7px; background: var(--gold); color: #0a0812; border-radius: 50%; width: 24px; height: 24px; font-family: 'Bebas Neue', sans-serif; font-size: 0.66rem; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 6px rgba(0,0,0,0.5); z-index: 10; } .chip.used-pool { opacity: 0.28; cursor: not-allowed; pointer-events: none; filter: saturate(0.1); } .chip-spent-wrap { position: relative; flex-shrink: 0; display: inline-flex; margin: 6px; width: 134px; height: 79px; } .chip-spent-wrap .chip { position: absolute; inset: 0; margin: 0; } .chip-spent-stamp { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; transform: rotate(-12deg); z-index: 10; pointer-events: none; } .chip-spent-stamp span { font-family: 'Bebas Neue', sans-serif; font-size: 0.70rem; letter-spacing: 0.22em; color: rgba(122,110,138,0.75); border: 1px solid rgba(122,110,138,0.3); padding: 2px 6px; } /* ── Session leaderboard ── */ .leaderboard-panel { margin: 16px 0; padding: 0; overflow: hidden; } .leaderboard-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.06); } .lb-title { font-family: 'Bebas Neue', sans-serif; font-size: 1.08rem; letter-spacing: 0.12em; color: var(--gold); } .lb-toggle { color: var(--text-muted); font-size: 0.7rem; } .leaderboard-body { padding: 12px 20px 20px; display: flex; flex-direction: column; gap: 10px; } .lb-row { display: flex; justify-content: space-between; align-items: flex-start; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.04); } .lb-row.highlight { flex-direction: column; gap: 4px; background: var(--bg-raised); border-radius: 8px; padding: 10px 12px; border: none; } .lb-row-main { display: flex; justify-content: space-between; width: 100%; } .lb-row-detail { font-size: 0.75rem; color: var(--text-muted); font-style: italic; } .lb-label { font-size: 0.65rem; font-weight: 700; letter-spacing: 0.1em; color: var(--text-muted); font-family: 'Bebas Neue', sans-serif; } .lb-value { font-size: 0.95rem; color: var(--text-primary); font-weight: 600; } .lb-value.gold { color: var(--gold); } /* ── Post-game card ── */ .postgame-card { margin: 20px 0; padding: 0; border-color: rgba(201,168,76,0.2); } .postgame-title { font-family: 'Bebas Neue', sans-serif; font-size: 0.82rem; letter-spacing: 0.32em; color: #e0c878; margin-bottom: 19px; padding: 19px 24px 0; } .postgame-stats { display: flex; flex-direction: column; gap: 14px; padding: 0 24px 24px; } .stat-highlight { display: flex; align-items: flex-start; gap: 14px; background: var(--bg-raised); border-radius: var(--r); padding: 14px; } .stat-icon { font-size: 1.44rem; } .stat-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.72rem; letter-spacing: 0.1em; color: var(--txt3); } .stat-value { font-family: 'Inter', sans-serif; font-size: 1.10rem; color: var(--txt1); margin-top: 2px; } .near-miss-banner { margin-top: 19px; padding: 14px 19px; background: rgba(122,91,166,0.08); border: 1px solid rgba(122,91,166,0.2); border-radius: var(--r); font-family: 'Inter', sans-serif; font-size: 1.02rem; color: var(--purple); display: none; } /* ── Buttons ── */ .btn-gold { background: var(--gold); color: #0a0812; border: none; font-family: 'Bebas Neue', sans-serif; font-size: 1.44rem; letter-spacing: 0.32em; padding: 22px 0; border-radius: var(--r); cursor: pointer; width: 100%; transition: all .15s; box-shadow: 0 4px 24px rgba(201,168,76,0.2); } .btn-gold:hover { background: #d9b85c; transform: translateY(-2px); box-shadow: 0 6px 32px rgba(201,168,76,0.3); } .btn-gold:disabled { opacity: .35; cursor: not-allowed; transform: none; } .btn-gold.is-loading { letter-spacing: 0.14em; pointer-events: none; } .loading-dots { display: inline-block; margin-left: 3px; letter-spacing: 0; width: 1.5em; text-align: left; vertical-align: baseline; } .loading-dots span { display: inline-block; animation: dot-pulse 1.2s ease-in-out infinite; opacity: 0.25; } .loading-dots span:nth-child(1) { animation-delay: 0s; } .loading-dots span:nth-child(2) { animation-delay: 0.2s; } .loading-dots span:nth-child(3) { animation-delay: 0.4s; } .opponent-formulating .loading-dots span { color: var(--txt2); } @keyframes dot-pulse { 0%, 60%, 100% { opacity: 0.25; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-3px); } } /* ── Labels / text helpers ── */ .sec-label { font-family: 'Bebas Neue', sans-serif; font-size: 1.2rem; letter-spacing: 0.22em; color: #e0c878; text-shadow: 0 0 18px rgba(201,168,76,0.25); border-bottom: 1px solid rgba(201,168,76,0.22); padding-bottom: 12px; margin-bottom: 22px; } .sec-label.sec-label-red { color: #e88898; border-bottom-color: rgba(193,96,112,0.22); } .subtext { font-family: 'Inter', sans-serif; color: #d4c8a8; font-size: 1.22rem; line-height: 1.55; } .round-banner { font-family: 'Bebas Neue', sans-serif; font-size: 1.68rem; color: var(--gold); letter-spacing: 0.12em; text-shadow: 0 0 40px rgba(201,168,76,0.25); } /* ── Shared step / label utilities ── */ .step-label { font-family: 'Bebas Neue', sans-serif; font-size: 1.2rem; letter-spacing: 0.22em; color: #e0c878; white-space: nowrap; text-shadow: 0 0 18px rgba(201,168,76,0.25); } .step-head { display: flex; align-items: center; gap: 19px; margin-bottom: 24px; } .step-divider { flex: 1; height: 1px; background: rgba(201,168,76,0.1); } .step-counter { font-family: 'Bebas Neue', sans-serif; font-size: 0.98rem; letter-spacing: 0.15em; color: #a89a80; white-space: nowrap; } .muted-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.82rem; letter-spacing: 0.22em; color: #3d3550; text-align: center; } .final-stat-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.82rem; letter-spacing: 0.22em; color: #3d3550; margin-bottom: 10px; } .stat-value-lg { font-family: 'Bebas Neue', sans-serif; font-size: 3rem; } .stat-value-lg .stat-vs { font-size: 1.2rem; color: #3d3550; } .position-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.86rem; letter-spacing: 0.18em; color: #3d3550; margin-bottom: 10px; } .coll-title { font-family: 'Bebas Neue', sans-serif; font-size: 0.94rem; letter-spacing: 0.15em; color: #e8e0cc; } .bd-val { font-family: 'Bebas Neue', sans-serif; font-size: 0.84rem; width: 29px; text-align: right; flex-shrink: 0; } .bd-val.p { color: #7a5ba6; } .bd-val.o { color: #8b1a2a; } .debater-summary { display: flex; align-items: center; gap: 12px; padding: 12px 17px; background: #12102a; border-radius: 2px; margin: 7px 0; } .debater-summary-name { font-family: 'Bebas Neue', sans-serif; font-size: 1.06rem; letter-spacing: 0.08em; color: #c9a84c; } .debater-summary-line { font-family: 'Inter', sans-serif; font-style: italic; color: #e8e0cc; font-size: 0.94rem; } .debater-summary-tag { font-family: 'Bebas Neue', sans-serif; font-size: 0.70rem; letter-spacing: 0.1em; color: #3d3550; margin-top: 5px; } .sb-side-head { font-weight: 700; font-size: 0.98rem; margin-bottom: 12px; } .breakdown-legend { display: flex; gap: 19px; font-family: 'Bebas Neue', sans-serif; font-size: 0.78rem; letter-spacing: 0.12em; margin-bottom: 10px; } .score-vs-divider { font-family: 'Bebas Neue', sans-serif; font-size: 1.32rem; color: #3d3550; flex-shrink: 0; } .note-sm { font-size: 0.94rem; color: var(--txt2); margin-top: 7px; } .abilities-disabled-msg { font-family: 'Inter', sans-serif; color: #3d3550; font-size: 1.02rem; padding: 24px; } /* ── Setup structural helpers ── */ .word-pool-panel { margin-bottom: 43px; } .word-pool-body { padding: 19px 26px 22px; } .setup-step-spaced { margin-top: 53px; } /* ── Landing page ── */ .landing-screen { position: fixed; inset: 0; z-index: 1; display: flex; align-items: center; justify-content: center; overflow-y: auto; overflow-x: hidden; min-height: unset !important; background: radial-gradient(ellipse at 50% -10%, #1c103a 0%, #0f0d22 45%, #0a0812 100%); } .pw.landing-screen { min-height: unset !important; } #podium-ui, #podium-ui > .wrap, #podium-ui > .block { padding: 0 !important; margin: 0 !important; min-height: 0 !important; } .landing-wrap { text-align: center; max-width: 700px; padding: 48px 29px; } .landing-title { font-family: 'Bebas Neue', sans-serif; font-size: 3.36rem; font-weight: 400; color: #c9a84c; letter-spacing: 0.1em; text-shadow: 0 0 40px rgba(201,168,76,0.3); margin-bottom: 12px; } .landing-divider { height: 1px; background: linear-gradient(to right, transparent, rgba(201,168,76,0.4), transparent); margin-bottom: 22px; } .landing-tagline { font-family: 'Inter', sans-serif; color: #c4b89a; font-size: 1.26rem; line-height: 1.6; margin-bottom: 38px; } /* ── Error screen ── */ /* ── Stage 3: debater lock ── */ .debater-grid.setup-roster.locked { opacity: 0; pointer-events: none; transition: opacity 0.4s ease; } /* ── Stage 4: VS reveal panel + turn-order banner ── */ .vs-reveal-panel { display: flex; gap: 2px; margin: 20px 0; border-radius: var(--r); overflow: hidden; } #lineup-vs { margin: 4px 0 8px; } .vr-slot { flex: 1; padding: 20px 14px; text-align: center; background: var(--bg-card); border: 1px solid rgba(201,168,76,0.1); transition: background 0.08s, box-shadow 0.08s; } .vr-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.7rem; letter-spacing: 0.18em; color: var(--text-secondary); margin-bottom: 8px; } .vr-debater-emoji { font-size: 2rem; line-height: 1; margin-bottom: 6px; } .vr-debater-name { font-family: 'Bebas Neue', sans-serif; font-size: 0.95rem; letter-spacing: 0.08em; color: var(--txt1); } .vr-center { flex-shrink: 0; display: flex; align-items: center; padding: 0 14px; background: #0a0812; } .vr-vs-text { font-family: 'Bebas Neue', sans-serif; font-size: 1.6rem; color: var(--gold); letter-spacing: 0.1em; } .vr-slot.lit { background: rgba(201,168,76,0.18); } .vr-slot.winner { background: rgba(201,168,76,0.08); box-shadow: inset 0 0 0 1px var(--gold), 0 0 24px rgba(201,168,76,0.2); } .vr-slot.loser { background: #0a0812; opacity: 0.35; } .turn-order-banner { font-family: 'Bebas Neue', sans-serif; font-size: 1.05rem; letter-spacing: 0.18em; text-align: center; padding: 14px; color: var(--gold); opacity: 0; transition: opacity 0.5s ease; } .turn-order-banner.visible { opacity: 1; } /* ── Stage 5: argument block compact debater header ── */ .arg-debater-header { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.06); } .arg-debater-compact-emoji { font-size: 1.4rem; line-height: 1; } .arg-debater-compact-name { font-family: 'Bebas Neue', sans-serif; font-size: 0.88rem; letter-spacing: 0.1em; color: var(--txt1); } .arg-side-badge { margin-left: auto; font-family: 'Bebas Neue', sans-serif; font-size: 0.62rem; letter-spacing: 0.18em; padding: 2px 8px; border-radius: 2px; } .arg-side-badge.for { background: rgba(74,140,92,0.2); color: #5ca870; } .arg-side-badge.against { background: rgba(139,26,42,0.2); color: #c16070; } .error-wrap { max-width: 960px; margin: 0 auto; background: #12102a; border: 1px solid rgba(139,26,42,0.5); border-radius: 2px; padding: 29px; } .error-title { font-family: 'Bebas Neue', sans-serif; font-size: 0.98rem; letter-spacing: 0.18em; color: #c16070; margin-bottom: 14px; } .error-body { font-family: 'Inter', sans-serif; color: #c4b89a; font-size: 1.02rem; white-space: pre-wrap; overflow-x: auto; } /* ── Top nav branding ── */ .podium-nav { position: sticky; top: 0; z-index: 100; background: rgba(10,8,18,0.97); border-bottom: 1px solid rgba(201,168,76,0.1); display: flex; align-items: center; padding: 0 48px; height: 70px; } .podium-wordmark { font-family: 'Bebas Neue', sans-serif; font-size: 1.5rem; font-weight: 400; color: #c9a84c; letter-spacing: 0.1em; text-shadow: 0 0 24px rgba(201,168,76,0.25); } .flip-ability-lbl { font-family: 'Bebas Neue', sans-serif; font-size: 0.74rem; color: rgba(201,168,76,0.38); letter-spacing: 0.1em; } .flip-uses-tag { font-family: 'Bebas Neue', sans-serif; font-size: 0.70rem; letter-spacing: 0.1em; padding: 2px 8px; border-radius: 2px; display: inline-block; } /* ── Flip cards ── */ .flip-row { display: flex; gap: 24px; justify-content: center; margin: 19px 0; flex-wrap: wrap; align-items: stretch; } .flip-c { perspective: 1000px; width: 232px; height: 336px; position: relative; cursor: pointer; overflow: hidden; border-radius: var(--r); } .flip-inner { position: relative; width: 100%; height: 100%; transition: transform .5s ease-in-out; transform-style: preserve-3d; } .flip-c.flipped .flip-inner { transform: rotateY(180deg); } .flip-face { position: absolute; width: 100%; height: 100%; backface-visibility: hidden; -webkit-backface-visibility: hidden; border-radius: var(--r); display: flex; flex-direction: column; overflow: hidden; } .flip-back { background: var(--bg-card); border: 1px solid rgba(201,168,76,0.25); box-shadow: 0 8px 32px rgba(0,0,0,0.7); } .flip-front { background: var(--bg-card); border: 1px solid rgba(201,168,76,0.25); box-shadow: 0 8px 32px rgba(0,0,0,0.7); transform: rotateY(180deg); } .flip-front-body { flex: 1; display: flex; flex-direction: column; align-items: center; padding: 10px 12px 0; min-height: 0; text-align: center; width: 100%; overflow: hidden; } .ab-emoji { font-size: 1.9rem; line-height: 1; margin-bottom: 5px; flex-shrink: 0; } .ab-name { font-family: 'Bebas Neue', sans-serif; font-size: 1.15rem; color: var(--gold); letter-spacing: 0.1em; margin-bottom: 6px; flex-shrink: 0; } .ab-desc { font-family: 'Inter', sans-serif; font-size: 0.76rem; color: var(--txt1); text-align: center; line-height: 1.38; flex: 1; width: 100%; margin-bottom: 6px; min-height: 0; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; scrollbar-color: rgba(201,168,76,0.28) transparent; } .ab-desc::-webkit-scrollbar { width: 3px; } .ab-desc::-webkit-scrollbar-thumb { background: rgba(201,168,76,0.28); border-radius: 2px; } .flip-uses-wrap { width: 100%; flex-shrink: 0; padding-bottom: 10px; margin-top: auto; } /* ── In-round ability tray ── */ .ability-tray { display: flex; gap: 12px; flex-wrap: wrap; margin: 14px 0 19px; } .ab-mini { width: 120px; min-height: 106px; padding: 10px 7px; background: var(--bg-card); border: 1px solid rgba(201,168,76,0.2); border-radius: var(--r); cursor: pointer; text-align: center; transition: border-color .15s, box-shadow .15s, opacity .15s; display: flex; flex-direction: column; align-items: center; gap: 4px; position: relative; } .ab-mini:hover:not(.locked) { border-color: rgba(201,168,76,0.45); box-shadow: 0 4px 16px rgba(0,0,0,0.4); } .ab-mini.selected { border: 2px solid var(--gold); box-shadow: 0 0 0 2px rgba(201,168,76,0.15); } .ab-mini.armed { border-color: var(--green); box-shadow: 0 0 12px rgba(74,140,92,0.25); } .ab-mini.locked { opacity: 0.42; cursor: pointer; filter: saturate(0.35); } .ab-mini.locked:hover { border-color: rgba(201,168,76,0.28); } .ability-zone.ability-locked .ab-mini.locked { cursor: not-allowed; pointer-events: none; } .ab-mini-check { display: none; position: absolute; top: 5px; right: 5px; width: 18px; height: 18px; border-radius: 50%; background: var(--green); color: #fff; font-size: 11px; font-weight: 700; line-height: 18px; text-align: center; pointer-events: none; z-index: 2; } .ab-mini.armed .ab-mini-check { display: block; } .ab-mini-skip { overflow: hidden; } .ab-mini-skip-line { position: absolute; inset: 0; pointer-events: none; z-index: 1; } .ab-mini-skip-line::before { content: ''; position: absolute; top: 50%; left: -12%; right: -12%; height: 2px; background: rgba(201,168,76,0.38); transform: rotate(-42deg); } .ab-mini-emoji { font-size: 1.68rem; line-height: 1; position: relative; z-index: 2; } .ab-mini-name { font-family: 'Bebas Neue', sans-serif; font-size: 0.74rem; letter-spacing: 0.08em; color: var(--gold); position: relative; z-index: 2; } .ability-panel { background: var(--bg-card); border: 1px solid rgba(201,168,76,0.22); border-radius: var(--r); padding: 19px 22px; margin-bottom: 19px; } .ability-panel.hidden { display: none; } .ability-panel-head { font-family: 'Bebas Neue', sans-serif; font-size: 1.14rem; letter-spacing: 0.12em; color: var(--gold); margin-bottom: 10px; } .ability-panel-desc { font-family: 'Inter', sans-serif; font-size: 1.06rem; color: var(--txt2); line-height: 1.45; margin-bottom: 14px; } .ability-warn { font-family: 'Inter', sans-serif; font-size: 0.98rem; color: var(--red-mid); margin-bottom: 12px; } .ability-panel-error { font-family: 'Inter', sans-serif; font-size: 1.01rem; color: var(--red-mid); margin-bottom: 12px; padding: 10px 12px; background: rgba(139,26,42,0.12); border-radius: var(--r); border: 1px solid rgba(139,26,42,0.25); display: none; } .ability-panel-error.visible { display: block; } .ability-hint { font-family: 'Inter', sans-serif; font-size: 0.98rem; color: var(--green); margin-bottom: 12px; } .ability-input { width: 100%; padding: 12px 14px; background: var(--bg-table); border: 1px solid rgba(201,168,76,0.25); border-radius: var(--r); color: var(--txt1); font-family: 'Inter', sans-serif; font-size: 1.14rem; margin-bottom: 12px; } .bias-btns { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; } .bias-btn { flex: 1; min-width: 96px; padding: 12px 10px; background: var(--bg-table); border: 1px solid rgba(201,168,76,0.25); border-radius: var(--r); font-family: 'Bebas Neue', sans-serif; font-size: 0.86rem; letter-spacing: 0.1em; color: var(--txt1); cursor: pointer; transition: all .15s; } .bias-btn:hover, .bias-btn.selected { background: rgba(201,168,76,0.15); border-color: var(--gold); color: var(--gold); } .call-it-input { background: var(--bg-raised); border: 1px solid rgba(201,168,76,0.3); border-radius: var(--r); padding: 16px; margin-top: 12px; display: flex; flex-direction: column; gap: 10px; } .call-it-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.75rem; letter-spacing: 0.12em; color: var(--gold); } .call-it-textarea { background: var(--bg-card); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: var(--text-primary); font-family: 'Inter', sans-serif; font-size: 0.95rem; line-height: 1.5; padding: 10px 12px; resize: none; width: 100%; box-sizing: border-box; } .call-it-textarea:focus { outline: none; border-color: var(--gold); box-shadow: 0 0 0 1px rgba(201,168,76,0.3); } .call-it-footer { display: flex; justify-content: space-between; align-items: center; } .call-it-counter { font-size: 0.7rem; color: var(--text-muted); font-family: 'Bebas Neue', sans-serif; letter-spacing: 0.08em; } .call-it-counter.at-limit { color: var(--red); } .call-it-warning { font-size: 0.7rem; color: var(--red); font-style: italic; } .call-it-confirm { background: var(--gold); color: var(--bg-base); border: none; border-radius: 8px; font-family: 'Bebas Neue', sans-serif; font-size: 0.9rem; letter-spacing: 0.1em; padding: 10px 20px; cursor: pointer; transition: all 0.15s ease; width: 100%; } .call-it-confirm:disabled { opacity: 0.35; cursor: not-allowed; } .call-it-confirm:not(:disabled):hover { background: #e8b830; transform: translateY(-1px); } .call-it-badge { font-family: 'Bebas Neue', sans-serif; font-size: 0.65rem; letter-spacing: 0.12em; color: var(--gold); text-align: center; margin-bottom: 8px; opacity: 0.7; } .call-it-badge.call-it-revised { color: var(--txt2); opacity: 0.85; margin-bottom: 4px; } .content-flag-banner { background: rgba(224,80,96,0.08); border: 1px solid rgba(224,80,96,0.35); border-radius: 8px; padding: 10px 14px; margin-bottom: 12px; font-family: 'Bebas Neue', sans-serif; font-size: 0.72rem; letter-spacing: 0.08em; color: var(--red-mid); text-align: center; } .forfeit-banner { background: rgba(224,80,96,0.1); border: 1px solid var(--red); border-radius: 8px; padding: 12px 16px; font-family: 'Bebas Neue', sans-serif; font-size: 0.85rem; letter-spacing: 0.08em; color: var(--red); text-align: center; margin-bottom: 16px; } .silence-pick { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; } .silence-word-btn { padding: 8px 14px; background: var(--bg-table); border: 1px solid rgba(139,26,42,0.35); border-radius: var(--r); font-family: 'Bebas Neue', sans-serif; font-size: 0.78rem; letter-spacing: 0.06em; color: var(--red-mid); cursor: pointer; } .silence-word-btn.selected { background: rgba(139,26,42,0.2); border-color: var(--red-mid); } .btn-sm-gold { padding: 10px 18px; background: rgba(201,168,76,0.15); border: 1px solid rgba(201,168,76,0.4); border-radius: var(--r); font-family: 'Bebas Neue', sans-serif; font-size: 0.72rem; letter-spacing: 0.12em; color: var(--gold); cursor: pointer; margin-right: 8px; } .btn-sm-gold:hover { background: rgba(201,168,76,0.28); } .btn-sm-gold.armed { background: rgba(74,140,92,0.18); border-color: var(--green); color: var(--green); } .chip.echo-eligible { opacity: 0.55; } .chip.used-pool.echo-eligible.echo-active { opacity: 1; cursor: pointer; pointer-events: auto; filter: none; border-color: var(--green) !important; } .chip-wildcard-injected { border-color: var(--wild-c) !important; } .btn-appeal { background: rgba(139,26,42,0.15) !important; border: 1px solid rgba(139,26,42,0.45) !important; color: var(--red-mid) !important; margin-bottom: 12px; } .btn-appeal .loading-dots span { color: #c9a84c; } /* ── Judge card ── */ .judge-card { max-width: 360px; margin: 20px auto 28px; text-align: center; padding: 20px 24px; position: relative; background: var(--bg-card); border: 1px solid rgba(201,168,76,0.28); border-radius: var(--r); box-shadow: 0 8px 32px rgba(0,0,0,0.7); } .judge-section-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.65rem; letter-spacing: 0.18em; color: var(--text-secondary); margin-bottom: 12px; } .judge-portrait-wrap { position: relative; display: flex; align-items: center; justify-content: center; width: 72px; height: 72px; margin: 0 auto 10px; } .judge-emoji { font-size: 3.6rem; display: block; text-align: center; position: relative; z-index: 2; } .judge-portrait-wrap .portrait-slot { display: flex; } .judge-name { font-family: 'Cinzel', serif; font-size: 1rem; color: var(--gold); letter-spacing: 0.05em; margin: 8px 0 6px; } .judge-take-inline { font-size: 0.82rem; color: var(--gold); font-style: italic; line-height: 1.4; margin: 6px 0 10px; opacity: 0.85; } .judge-bias { font-size: 0.76rem; color: var(--text-secondary); font-style: italic; line-height: 1.45; } .judge-glow { position: absolute; inset: -28px; background: radial-gradient(circle at center, rgba(201,168,76,0.1) 0%, transparent 65%); pointer-events: none; border-radius: 50%; } /* ── Arguments ── */ .arg-card { background: var(--bg-card); border: 1px solid rgba(201,168,76,0.25); border-radius: var(--r); margin: 12px 0; overflow: hidden; position: relative; } .opponent-argument { background: #0f0d22; border-color: rgba(139,26,42,0.2); } .arg-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.74rem; letter-spacing: 0.22em; color: #a89a80; padding: 12px 26px 0; margin-bottom: 0; } .opponent-argument .arg-label { color: #7a6e8a; } .words-used { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 16px 14px; } .argument-sentences { display: flex; flex-direction: column; } .argument-sentence { display: flex; align-items: baseline; gap: 16px; padding: 12px 22px; } .argument-sentence + .argument-sentence { border-top: 1px solid rgba(201,168,76,0.07); } .opponent-argument .argument-sentence + .argument-sentence { border-color: rgba(139,26,42,0.08); } .sentence-number { font-family: 'Bebas Neue', sans-serif; font-size: 0.90rem; color: var(--txt3); letter-spacing: 0.1em; min-width: 22px; flex-shrink: 0; } .sentence-text { font-family: 'Inter', sans-serif; font-size: 1.15rem; color: var(--txt1); line-height: 1.55; flex: 1; } mark.word-highlight { background: rgba(201,168,76,0.16); color: var(--gold); padding: 1px 5px; border-radius: 2px; } .opponent-argument mark.word-highlight { background: rgba(139,26,42,0.2); color: var(--red-mid); } .opponent-formulating { background: #0f0d22; border: 1px dashed rgba(139,26,42,0.35); border-radius: var(--r); margin: 16px 0; padding: 28px 22px; text-align: center; } .opponent-formulating.fade-in { animation: fadeIn .4s ease forwards, pulse 2s ease-in-out .4s infinite; } .formulating-text { font-family: 'Bebas Neue', sans-serif; font-size: 0.98rem; letter-spacing: 0.22em; color: #c16070; margin-bottom: 10px; } .formulating-sub { font-family: 'Inter', sans-serif; font-style: italic; font-size: 1.02rem; color: var(--txt3); } /* ── Scores ── */ .score-track { background: rgba(255,255,255,.07); border-radius: 4px; height: 10px; overflow: hidden; } .score-fill { height: 100%; border-radius: 4px; transition: width 1s; } .score-fill.p { background: linear-gradient(90deg, #3d2870, var(--purple)); } .score-fill.o { background: linear-gradient(90deg, #5a1020, var(--red)); } /* ── Verdict ── */ .verdict-card { background: var(--bg-card); border: 1px solid rgba(201,168,76,0.28); border-radius: var(--r); overflow: hidden; margin: 16px 0; } .verdict-card-body { padding: 24px 29px; } .verdict-who { font-family: 'Bebas Neue', sans-serif; font-size: 1.06rem; letter-spacing: 0.12em; color: var(--gold); margin-bottom: 12px; } .verdict-text { font-family: 'Inter', sans-serif; font-style: italic; color: var(--txt1); font-size: 1.14rem; line-height: 1.6; } /* ── Winner banners ── */ .w-you { background: rgba(201,168,76,0.07); border: 1px solid rgba(201,168,76,0.28); border-radius: var(--r); padding: 13px 24px; text-align: center; color: var(--gold); font-family: 'Bebas Neue', sans-serif; font-size: 1.06rem; letter-spacing: 0.22em; } .w-opp { background: rgba(139,26,42,0.08); border: 1px solid rgba(139,26,42,0.3); border-radius: var(--r); padding: 13px 24px; text-align: center; color: var(--red-mid); font-family: 'Bebas Neue', sans-serif; font-size: 1.06rem; letter-spacing: 0.22em; } .w-draw { background: rgba(201,168,76,0.05); border: 1px solid rgba(201,168,76,0.2); border-radius: var(--r); padding: 13px 24px; text-align: center; color: rgba(201,168,76,0.7); font-family: 'Bebas Neue', sans-serif; font-size: 1.06rem; letter-spacing: 0.22em; } .w-you.w-banner-lg, .w-opp.w-banner-lg, .w-draw.w-banner-lg { font-size: 2.16rem; margin-bottom: 29px; } /* ── Big score ── */ .big-score { font-family: 'Bebas Neue', sans-serif; font-size: 6.24rem; color: var(--gold); text-align: center; line-height: 0.9; letter-spacing: -0.02em; } .big-score.opp { color: var(--red); } .score-vs { display: flex; gap: 34px; justify-content: center; align-items: center; margin: 29px 0; } .score-side { text-align: center; } .score-lbl { font-family: 'Bebas Neue', sans-serif; font-size: 0.84rem; letter-spacing: 0.22em; color: var(--txt3); margin-bottom: 6px; } /* ── Tally bar ── */ .tally { background: rgba(10,8,18,0.97); border-bottom: 1px solid rgba(201,168,76,0.07); padding: 11px 48px; display: flex; align-items: center; justify-content: center; gap: 24px; position: sticky; top: 0; z-index: 10; font-family: 'Bebas Neue', sans-serif; } .tally-you { font-size: 0.94rem; letter-spacing: 0.2em; color: #c9a84c; } .tally-score { font-size: 1.38rem; } .tally-score.you { color: #c9a84c; } .tally-score.opp { color: #8b1a2a; } .tally-sep { font-size: 0.84rem; color: #3d3550; letter-spacing: 0.1em; } .tally-opp { font-size: 0.94rem; letter-spacing: 0.2em; color: #7a6e8a; } /* ── Breakdown ── */ .bd-row { display: flex; align-items: center; gap: 14px; margin: 10px 0; } .bd-label { width: 168px; font-family: 'Inter', sans-serif; font-size: .94rem; color: var(--txt2); flex-shrink: 0; } .bd-bars { flex: 1; display: flex; flex-direction: column; gap: 4px; } /* ── Collapsible ── */ .coll-hdr { cursor: pointer; user-select: none; display: flex; align-items: center; gap: 8px; justify-content: space-between; } .coll-body { overflow: hidden; max-height: 0; transition: max-height .3s; } .coll-body.open { max-height: 2000px; } /* ── Score math breakdown ── */ .sb-section { margin: 14px 0; padding: 12px 14px; background: rgba(0,0,0,.2); border-radius: var(--r); } .sb-head { font-family: 'Bebas Neue', sans-serif; font-size: .82rem; letter-spacing: .12em; color: var(--txt3); margin-bottom: 10px; } .sb-line { display: flex; align-items: center; gap: 10px; font-family: 'Inter', sans-serif; font-size: 1.02rem; color: var(--txt2); margin: 5px 0; } .sb-word { min-width: 108px; font-weight: 700; color: var(--txt1); } .sb-dots { font-size: .90rem; letter-spacing: 2px; color: var(--gold); } .sb-final { font-family: 'Bebas Neue', sans-serif; font-size: 1.32rem; color: var(--gold); letter-spacing: 0.08em; margin-top: 12px; } /* ── Recap table ── */ .recap { width: 100%; border-collapse: collapse; margin: 19px 0; font-family: 'Inter', sans-serif; font-size: 1.06rem; } .recap th { font-family: 'Bebas Neue', sans-serif; color: #e0c878; text-transform: uppercase; font-size: .78rem; letter-spacing: .18em; padding: 10px 12px; text-align: left; border-bottom: 1px solid rgba(201,168,76,0.22); } .recap td { padding: 10px; border-bottom: 1px solid rgba(255,255,255,.03); color: var(--txt2); } .recap td:first-child { color: var(--txt1); font-weight: 700; } /* ── Motion card ── */ .motion-card { position: relative; text-align: center; margin: 16px 0; padding: 0 16px; } .motion-divider { height: 1px; background: linear-gradient(to right, transparent, rgba(201,168,76,0.45), transparent); margin: 18px 0; } .motion-quote { display: none; } .motion-statement-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.7rem; letter-spacing: 0.18em; color: var(--text-secondary); text-align: center; margin-bottom: 10px; } .motion-text { font-family: 'Inter', sans-serif; font-size: 1.70rem; color: var(--txt1); line-height: 1.4; text-wrap: pretty; } .round-step-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.72rem; letter-spacing: 0.15em; color: var(--gold); text-align: left; margin-bottom: 12px; padding-left: 2px; } .verdict-topic-block { text-align: center; margin: 0 0 20px; } .verdict-topic-text { font-size: 0.9rem; color: var(--text-secondary); font-style: italic; text-align: center; margin-bottom: 6px; } /* ── Side choice ── */ .side-choice { margin: 24px 0 19px; text-align: left; } .side-buttons { display: flex; gap: 19px; margin-top: 14px; } .side-btn { flex: 1; padding: 24px 19px; border-radius: var(--r); border: 2px solid rgba(201,168,76,0.1); background: var(--bg-card); cursor: pointer; transition: all 0.15s ease; display: flex; flex-direction: column; align-items: center; gap: 4px; font-family: 'Bebas Neue', sans-serif; color: var(--txt1); } .for-btn:hover { border-color: var(--green); background: rgba(74,140,92,0.1); } .against-btn:hover { border-color: var(--red-mid); background: rgba(139,26,42,0.1); } .side-btn.selected { transform: translateY(-4px); } .for-btn.selected { border-color: var(--green); box-shadow: 0 0 0 1px var(--green), 0 0 16px rgba(74,140,92,0.3); } .against-btn.selected { border-color: var(--red-mid); box-shadow: 0 0 0 1px var(--red-mid), 0 0 16px rgba(139,26,42,0.3); } .side-btn:disabled { cursor: default; opacity: 1; } .side-btn:disabled:not(.selected) { opacity: 0.35; } .side-btn span { font-size: 1.8rem; font-weight: 700; letter-spacing: 0; } .side-sub { font-family: 'Inter', sans-serif; font-size: 0.90rem; color: #d4c8a8; } /* ── Setup step reveal ── */ .setup-step { transition: opacity .4s; } .setup-step.hidden { display: none; } /* ── Animations ── */ @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.45} } @keyframes fadeIn { from{opacity:0;transform:translateY(12px)} to{opacity:1;transform:translateY(0)} } @keyframes scoreIn { 0%{opacity:0;transform:scale(0.7) translateY(10px)} 60%{opacity:1;transform:scale(1.05) translateY(-2px)} 100%{opacity:1;transform:scale(1) translateY(0)} } .fade-in { animation: fadeIn .4s ease forwards; } """ if config.GOLD_EMOJI_PORTRAITS: APP_CSS += """ /* Gold emoji tint — disable via config.GOLD_EMOJI_PORTRAITS */ .portrait-slot.portrait-fallback .portrait-emoji { filter: sepia(1) saturate(4) hue-rotate(5deg) brightness(0.95); } """ # ── Head extras (fonts + game JS inlined — external file= URLs don't load in Gradio 5.20) ─ def _build_app_head() -> str: js_path = Path(__file__).parent / "podium.js" js_content = js_path.read_text(encoding="utf-8") return f""" """ APP_HEAD = _build_app_head() # ── HTML helpers ───────────────────────────────────────────────────────────── def _sid_tag(sid: Optional[str]) -> str: return f'' def _tally(sid: Optional[str]) -> str: gs = _get_gs(sid) if not gs or not gs.scores: return "" p, o = engine.count_round_wins(gs) return (f'| Round | Topic | Judge | Your Debater | You | Opp |
|---|
{err}