"""WitGym Gradio demo — dark gym landing, ivory practice screen.""" import html import os import threading from pathlib import Path from dotenv import load_dotenv load_dotenv() if os.getenv("SPACE_ID"): os.environ.setdefault("LLM_BACKEND", "hf_api") import gradio as gr from loguru import logger from witgym.conversation import ConversationManager from witgym.engine import WitGymEngine, get_shared_resources from witgym.debug_render import ( format_transcript_html, thinking_turn_html, StreamingTurnState, apply_stream_event, format_transcript_with_streaming, ) from witgym import config from witgym.avatars import char_avatar_url, char_avatar_svg INDEX_PATH = os.getenv("WITGYM_INDEX_PATH", config.INDEX_PATH) _FAVICON = Path(__file__).parent / "assets" / "favicon.png" # Character data: (name, role-label, card-bg, avatar-url, bio-title, bio-desc) CHARACTERS = [ ("Michael", "comedian", "#5a1a0a", char_avatar_url("Michael"), "Regional Manager", "Needs to be the funniest person in the room — always. Even at funerals."), ("Dwight", "contrarian", "#2d3d1a", char_avatar_url("Dwight"), "Assistant (to the) Regional Manager", "Treats every situation as a threat to be neutralised through superior preparation."), ("Jim", "wit", "#1a2d4a", char_avatar_url("Jim"), "Sales Representative", "Deflects chaos with a raised eyebrow and impeccable comedic timing."), ("Pam", "empath", "#5a1a4a", char_avatar_url("Pam"), "Receptionist → Office Administrator", "Finds the kindest possible way to say the unsayable thing everyone else is thinking."), ("Kevin", "literalist", "#3d1a5a", char_avatar_url("Kevin"), "Accountant", "Cuts to the literal truth everyone else is too sophisticated to say out loud."), ("Andy", "overclaimer", "#7a3010", char_avatar_url("Andy"), "Sales Representative", "Overclaims, overshares, and somehow — through sheer confidence — lands it."), ("Stanley", "cynic", "#0f2d1a", char_avatar_url("Stanley"), "Sales Representative", "Has seen it all. Cares about essentially none of it. Will now return to his crossword."), ("Angela", "moralist", "#2a2a0a", char_avatar_url("Angela"), "Head of Accounting", "Holds the line on decorum, propriety and cats while everything collapses around her."), ("Ryan", "hustler", "#1f0a2d", char_avatar_url("Ryan"), "Temp → VP → Temp → Temp", "Dresses up insecurity as strategy. The hustle is the product."), ("Kelly", "enthusiast", "#6a0a3a", char_avatar_url("Kelly"), "Customer Service Representative", "Turns raw enthusiasm into an overwhelming and surprisingly effective force of nature."), ] STARTERS = [ ("Status", "I just got promoted to manager and I have no idea what I'm doing."), ("Social", "My coworker keeps stealing my lunch from the fridge."), ("Delusion", "I'm pretending to understand cryptocurrency at dinner parties."), ("Anxiety", "I've been ignoring a voicemail so long it feels like a legal risk."), ("Self-aware", "I sent a complaint about my manager to my manager."), ("Coach me", "Help me respond when someone asks about my job and I don't know what to say."), ] from witgym.prompts import DRILL_KEYS as _DRILL_KEYS _drill_text = {v: k for k, v in _DRILL_KEYS.items()} DRILL_ACTIONS = [ ("sharpen it", _drill_text["sharpen"]), ("different angle", _drill_text["angle"]), ("explain the joke", _drill_text["explain"]), ] TRANSCRIPT_MIN_HEIGHT = 440 TRANSCRIPT_MAX_HEIGHT = 580 # ── Mascot SVG ──────────────────────────────────────────────────────────────── _MASCOT = """""" APP_CSS = """ @import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=EB+Garamond:ital,wght@0,400;0,600;1,400&display=swap'); /* ── Global dark base ──────────────────────────────────────────────────── */ body, .gradio-container, .main { background: #141414 !important; } footer { display: none !important; height: 0 !important; padding: 0 !important; margin: 0 !important; overflow: hidden !important; } /* ── Light mode (activated on START TRAINING) ──────────────────────────── */ body.wg-light-mode, body.wg-light-mode .gradio-container, body.wg-light-mode .main { background: #f5f0e8 !important; } body.wg-light-mode footer { display: none !important; } :root { --wg-bg: #141414; --wg-surf: #1e1e1e; --wg-surf2: #252525; --wg-border: #2e2e2e; --wg-yellow: #f5c518; --wg-green: #2d6a4f; --wg-white: #f0f0f0; --wg-muted: #777; --wg-r: 10px; } /* ── Light-mode CSS variable scope — ALL var(--wg-*) inside #wg-practice resolve to light values without needing per-class overrides. This is the structural fix: dark :root vars don't cascade into the light practice screen. We also override Gradio's dark-theme vars (--body-text-color, etc.) so that Gradio's own .prose * { color: var(--body-text-color) } rule renders dark text, not near-white (#f0f0f0) on our ivory background. */ #wg-practice { --wg-bg: #fffff8; --wg-surf: #faf9f6; --wg-surf2: #f0ece4; --wg-border: #e0d8cc; --wg-white: #2a2118; --wg-muted: #9e9288; --wg-yellow: #b45309; /* --wg-green and --wg-r stay the same across modes */ /* Gradio dark-theme CSS vars — override so .prose * inherits dark text */ --body-text-color: #2a2118; --body-text-color-subdued: #6b6258; --block-text-color: #2a2118; --block-label-text-color: #6b6258; --input-text: #2a2118; --color-text-body: #2a2118; } /* ── Landing / Hero ─────────────────────────────────────────────────────── */ #wg-landing { background: transparent !important; border: none !important; } /* Collapse Gradio's default gap between HTML components in landing */ #wg-landing > .svelte-1plpy97, #wg-landing > div { gap: 0 !important; } #wg-landing .gap-4 { gap: 0 !important; } #wg-landing .block { padding: 0 !important; margin: 0 !important; min-height: 0 !important; box-shadow: none !important; border: none !important; } .wg-hero { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; /* Dot grid scoped to hero only — footer gap below START TRAINING stays solid dark */ background: radial-gradient(circle, rgba(245,197,24,0.18) 1px, transparent 2px) center/24px 24px, var(--wg-bg); padding: 0.75rem 1rem 0.75rem; text-align: center; } /* ● REC indicator — bigger, more visible flicker */ .wg-rec { position: absolute; top: 1.5rem; right: 1.75rem; display: flex; align-items: center; gap: 0.5rem; font-family: 'Courier New', monospace; font-size: 0.8rem; font-weight: 700; color: #e53e3e; letter-spacing: 0.22em; z-index: 1; } .wg-rec-dot { width: 12px; height: 12px; border-radius: 50%; background: #e53e3e; box-shadow: 0 0 8px rgba(229,62,62,0.6); animation: wg-pulse 1.4s ease-in-out infinite; } @keyframes wg-pulse { 0%,100% { opacity: 1; box-shadow: 0 0 8px rgba(229,62,62,0.6); } 50% { opacity: 0.1; box-shadow: 0 0 2px rgba(229,62,62,0.1); } } /* Kicker */ .wg-kicker { font-family: 'EB Garamond', Georgia, serif; font-style: italic; font-size: 0.78rem; letter-spacing: 0.22em; color: var(--wg-yellow); text-transform: uppercase; margin-bottom: 0.25rem; position: relative; z-index: 1; } /* Vertical logo: mascot → WIT → GYM stacked, all centered */ .wg-logo-row { display: flex; flex-direction: column; align-items: center; gap: 0; position: relative; z-index: 1; } /* Mascot */ .wg-mascot { position: relative; z-index: 1; margin-bottom: 0.25rem; filter: drop-shadow(0 4px 24px rgba(45,106,79,0.4)); } /* WIT and GYM stacked vertically, each on its own line */ .wg-wordmark { display: flex; flex-direction: column; align-items: center; line-height: 0.9; position: relative; z-index: 1; } /* Huge font — fills ~260px of the 720px viewport so content organically fills height, eliminating the empty footer gap without fighting Svelte's height binding. */ .wg-wordmark-wit, .wg-wordmark-gym { font-family: 'Bebas Neue', Impact, 'Arial Black', sans-serif; font-size: clamp(5rem, 12vw, 8.5rem); letter-spacing: 0.03em; display: block; } /* Hardcoded hex + !important: Gradio 6 SSR on HF Spaces injects theme CSS after APP_CSS, causing same-specificity cascade override of var(--wg-white/yellow). */ .wg-wordmark-wit { color: #f0f0f0 !important; text-shadow: 0 0 40px rgba(0,0,0,0.6); } .wg-wordmark-gym { color: #f5c518 !important; text-shadow: 0 2px 20px rgba(0,0,0,0.8), 0 0 60px rgba(0,0,0,0.5); } .wg-hero-tagline { font-family: 'EB Garamond', Georgia, serif; font-style: italic; font-size: 1rem; color: rgba(240,240,240,0.7); margin-bottom: 1.75rem; position: relative; z-index: 1; } .wg-start-hint { font-size: 0.72rem; color: var(--wg-muted); margin: 0.15rem 0 0 !important; font-style: italic; text-align: center; background: transparent !important; } /* ── Scroll cue: clickable circle button between tagline and CTA ─────────── */ .wg-scroll-cue { display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 0.3rem; padding: 0.5rem 1rem 0.75rem; background: transparent !important; cursor: pointer; transition: transform .22s ease; position: relative; z-index: 1; } .wg-scroll-cue:hover { transform: translateY(5px); } .wg-scroll-circle { width: 46px; height: 46px; border-radius: 50%; border: 1.5px solid rgba(74,222,128,0.35); background: rgba(45,106,79,0.08); display: flex; align-items: center; justify-content: center; transition: border-color .2s, box-shadow .2s; } .wg-scroll-cue:hover .wg-scroll-circle { border-color: rgba(74,222,128,0.85); box-shadow: 0 0 18px rgba(74,222,128,0.28); } .wg-scroll-arrow-svg { animation: wg-arrow-fall 1.9s ease-in-out infinite; filter: drop-shadow(0 0 4px rgba(74,222,128,0.45)); } .wg-scroll-label { font-family: 'EB Garamond', Georgia, serif; font-style: italic; font-size: 0.68rem; color: rgba(74,222,128,0.45); letter-spacing: 0.2em; text-transform: uppercase; transition: color .2s; } .wg-scroll-cue:hover .wg-scroll-label { color: rgba(74,222,128,0.8); } @keyframes wg-arrow-fall { 0%,100% { transform: translateY(0); opacity: 0.55; } 50% { transform: translateY(7px); opacity: 1; } } /* ── Real Gradio START TRAINING button ──────────────────────────────────── */ #wg-start-btn { justify-content: center !important; background: transparent !important; padding: 0 !important; } #wg-start-btn button { font-family: 'Bebas Neue', Impact, sans-serif !important; font-size: 1.1rem !important; letter-spacing: 0.22em !important; background: var(--wg-green) !important; color: #fff !important; border: none !important; border-radius: 50px !important; padding: 0.55rem 3rem !important; transition: background .2s, transform .15s !important; } #wg-start-btn button:hover { background: #235a40 !important; transform: translateY(-2px) !important; } /* ── Coaching panel ─────────────────────────────────────────────────────── */ .wg-coach-panel { width: 100%; padding: 0.2rem 1rem 0.15rem; background: var(--wg-bg); border-top: none; } .wg-coach-divider { display: flex; align-items: center; gap: 1rem; margin-bottom: 0.2rem; } .wg-coach-div-line { flex: 1; height: 1px; background: #3a3a3a; } .wg-coach-div-text { font-family: 'Bebas Neue', Impact, sans-serif; font-size: 0.82rem; letter-spacing: 0.3em; color: var(--wg-yellow); white-space: nowrap; } .wg-char-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.65rem; max-width: 900px; margin: 0 auto; } .wg-char-card { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; background: var(--wg-surf2); border-radius: var(--wg-r); padding: 0.65rem 0.4rem 0.55rem; width: 82px; border: 1px solid var(--wg-border); cursor: pointer; transition: border-color .15s, transform .15s, box-shadow .15s; } .wg-char-card:hover { border-color: var(--wg-yellow); transform: translateY(-4px); box-shadow: 0 8px 24px rgba(245,197,24,0.15); } .wg-char-card img { width: 58px; height: 58px; border-radius: 8px; background: transparent; } .wg-char-name { font-family: 'Bebas Neue', sans-serif; font-size: 0.73rem; letter-spacing: 0.06em; color: var(--wg-white); text-align: center; } .wg-char-role { font-family: 'EB Garamond', serif; font-style: italic; font-size: 0.58rem; color: var(--wg-muted); text-align: center; } /* ── Practice screen header (compact, centered) ─────────────────────────── */ @keyframes wg-header-drop { 0% { transform: translateY(-10px) scaleY(0.82); opacity: 0; } 65% { transform: translateY(2px) scaleY(1.04); opacity: 1; } 100% { transform: translateY(0) scaleY(1); } } .wg-practice-bar { display: flex; align-items: center; justify-content: center; gap: 0.75rem; padding: 0.75rem 1.25rem; border-bottom: 1px solid #d8d0c4; background: #faf9f6; animation: wg-header-drop 0.42s cubic-bezier(.22,.68,0,1.35) both; } .wg-practice-logo { font-family: 'Bebas Neue', Impact, sans-serif; font-size: 1.5rem; letter-spacing: 0.1em; color: #3d3429; line-height: 1; } .wg-practice-logo span { color: var(--wg-green); } .wg-practice-sub { font-family: 'EB Garamond', Georgia, serif; font-style: italic; font-size: 0.8rem; color: #9e9288; } /* ── Practice screen: ivory/light override ──────────────────────────────── */ #wg-practice { background: #fffff8 !important; } /* Target Gradio's internal wrappers that carry the dark theme background */ #wg-practice .block, #wg-practice .gap, #wg-practice .form, #wg-practice .scroll-hide, #wg-practice .styler, #wg-practice > div, #wg-practice .gradio-group { background: #faf9f6 !important; border-color: #e0d8cc !important; } #wg-practice #wg-chat-shell, #wg-practice #wg-chat-shell .block, #wg-practice #wg-chat-shell .gap { background: #faf9f6 !important; border-color: #e0d8cc !important; } #wg-practice .wg-transcript { color: #2a2118 !important; } #wg-practice .wg-user { color: #2d6a4f !important; } #wg-practice .wg-thinking { background: #f5f2eb !important; border-color: rgba(200,190,175,0.5) !important; color: #6b6258 !important; } #wg-practice .wg-coach-reply { background: #f0fdf4 !important; border-color: #4ade80 !important; border-left-color: #2d6a4f !important; } #wg-practice .wg-coach-reply-header { color: #2d6a4f !important; } #wg-practice .wg-coach-reply-body { color: #14532d !important; font-size: 1.55rem !important; font-weight: 600 !important; } #wg-practice .wg-panel-yellow { background: #fffbeb !important; border-color: #fbbf24 !important; color: #78350f !important; } #wg-practice .wg-panel-yellow .wg-panel-title { color: #b45309 !important; } #wg-practice .wg-panel-blue { background: #eff6ff !important; border-color: #60a5fa !important; color: #1e3a5f !important; } #wg-practice .wg-panel-blue .wg-panel-title { color: #2563eb !important; } #wg-practice .wg-panel-green { background: #f0fdf4 !important; border-color: #4ade80 !important; color: #14532d !important; } #wg-practice .wg-panel-green .wg-panel-title { color: #16a34a !important; } #wg-practice .wg-panel-dim { background: #f5f5f4 !important; border-color: #d6d3d1 !important; color: #78716c !important; } #wg-practice .wg-dim { color: #9e9288 !important; } #wg-practice .wg-cyan { color: #0891b2 !important; } #wg-practice .wg-dim-italic { color: #9e9288 !important; font-style: italic; } #wg-practice .wg-trace-block { background: #faf9f6 !important; border-color: #e0d8cc !important; } #wg-practice .wg-trace-title { color: #9e9288 !important; } #wg-practice .wg-trace-json { color: #3d3429 !important; } #wg-practice .wg-debug-toggle-line { background: #e0d8cc !important; } #wg-practice .wg-debug-toggle-label { border-color: #e0d8cc !important; background: #f5f2eb !important; color: #9e9288 !important; } #wg-practice .wg-debug-toggle-label:hover { color: #6b6258 !important; } #wg-practice .wg-rule { border-color: #e0d8cc !important; } #wg-practice .wg-empty { color: #9e9288 !important; } #wg-practice #wg-sidebar { background: #faf9f6 !important; border-color: #e0d8cc !important; } #wg-practice .wg-sidebar-label { color: #9e9288 !important; } #wg-practice .wg-starter-btn button { background: #fff !important; border-color: #e0d8cc !important; color: #3d3429 !important; } #wg-practice .wg-starter-btn button:hover { border-color: var(--wg-green) !important; background: #f0fdf4 !important; } #wg-practice textarea, #wg-practice input, #wg-practice input[type="text"], #wg-practice .gradio-container textarea, #wg-practice .gradio-container input[type="text"] { background: #fff !important; color: #2a2118 !important; border-color: #e0d8cc !important; } #wg-practice textarea::placeholder, #wg-practice input::placeholder { color: #9e9288 !important; } /* Hide native placeholder in chat shell — flicker overlay replaces it */ #wg-chat-shell textarea::placeholder { color: transparent !important; } #wg-practice button.secondary, #wg-practice .gradio-container button.secondary { background: #f5f2eb !important; border-color: #e0d8cc !important; color: #3d3429 !important; } #wg-practice label span, #wg-practice .gradio-container label span { color: #6b6258 !important; } /* Main layout */ #witgym-main { max-width: 1200px; margin: 0 auto; padding: 0.75rem 0.5rem 1rem; } /* Submit button */ #wg-submit-btn button { background: var(--wg-green) !important; font-family: 'Bebas Neue', sans-serif !important; letter-spacing: 0.18em !important; font-size: 1rem !important; transition: background .2s; } #wg-submit-btn button:hover { background: #235a40 !important; } /* Sidebar */ #wg-sidebar { background: var(--wg-surf) !important; border: 1px solid var(--wg-border) !important; border-radius: var(--wg-r) !important; padding: 0.85rem !important; } .wg-sidebar-label { font-family: 'Bebas Neue', sans-serif; font-size: 0.88rem; letter-spacing: 0.15em; color: var(--wg-muted); margin-bottom: 0.4rem; } .wg-starter-btn button { width: 100%; text-align: left; white-space: normal; height: auto !important; min-height: 2.1rem; line-height: 1.3; padding: 0.38rem 0.55rem !important; font-size: 0.82rem !important; font-family: 'EB Garamond', serif !important; border-radius: 7px !important; background: var(--wg-surf2) !important; border: 1px solid var(--wg-border) !important; color: rgba(240,240,240,.82) !important; transition: border-color .15s; } .wg-starter-btn button:hover { border-color: var(--wg-yellow) !important; } /* Chat shell */ #wg-chat-shell { background: var(--wg-surf) !important; border: 1px solid var(--wg-border) !important; border-radius: var(--wg-r) !important; overflow: hidden; } /* Transcript (dark default, overridden in #wg-practice) */ .wg-transcript { font-size: 16px; line-height: 1.65; color: var(--wg-white); } .wg-empty { color: var(--wg-muted); font-style: italic; font-family: 'EB Garamond', serif; padding: 2.5rem 1.5rem; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 0.6rem; } .wg-empty-icon { font-size: 2rem; opacity: 0.4; } .wg-empty-text { max-width: 280px; line-height: 1.55; } .wg-turn { margin-bottom: 1.75rem; } .wg-user { color: #4ade80; font-weight: 700; margin-bottom: 0.65rem; font-size: 17px; } .wg-label { font-weight: 700; margin-right: 0.3rem; } .wg-thinking { display: flex; align-items: center; gap: 0.5rem; color: var(--wg-muted); font-style: italic; font-size: 0.95rem; padding: 0.6rem 0.85rem; margin-top: 0.25rem; background: var(--wg-surf2); border-radius: 8px; border: 1px solid var(--wg-border); } .wg-thinking-icon { flex-shrink: 0; animation: wg-spin .9s linear infinite; } @keyframes wg-spin { to { transform: rotate(360deg); } } @media (prefers-reduced-motion: reduce) { .wg-thinking-icon { animation: none; } } @media (max-width: 640px) { #wg-practice #wg-chat-shell .html-container { min-height: 180px !important; max-height: none !important; } #wg-practice .wg-trace-json { font-size: 0.72rem; line-height: 1.45; } #wg-practice .wg-reply-actions { top: 0.4rem; right: 0.45rem; gap: 0.35rem; } } .wg-coach-reply { margin-top: 0.85rem; padding: 0.9rem 1.1rem; background: #051a0a; border: 1px solid var(--wg-green); border-left: 3px solid var(--wg-yellow); border-radius: var(--wg-r); } .wg-coach-reply-header { font-family: 'Bebas Neue', sans-serif; font-size: 0.8rem; letter-spacing: 0.15em; color: var(--wg-yellow); margin-bottom: 0.4rem; } .wg-coach-reply-body { font-family: 'EB Garamond', Georgia, serif; font-size: 1.55rem; line-height: 1.55; color: #c6f6d5; font-weight: 600; } .wg-coach-reply--compact { margin-top: 0.5rem; } .wg-mode-badge { font-family: 'Bebas Neue', sans-serif; font-size: 0.72rem; letter-spacing: 0.18em; padding: 0.15rem 0.55rem; border-radius: 20px; display: inline-block; margin-bottom: 0.4rem; } .wg-mode-banter { background: #1a3d2b; color: #4ade80; border: 1px solid #2d6a4f; } .wg-mode-wit { background: #3d2a00; color: #fcd34d; border: 1px solid #92400e; } .wg-mode-coach { background: #050e1e; color: #93c5fd; border: 1px solid #1e3558; } /* Debug toggle */ .wg-debug-toggle { display: flex; align-items: center; gap: 0.6rem; margin: 0.8rem 0 0.4rem; cursor: pointer; user-select: none; } .wg-debug-toggle-line { flex: 1; height: 1px; background: var(--wg-border); } .wg-debug-toggle-label { font-family: 'EB Garamond', serif; font-style: italic; font-size: 0.75rem; color: var(--wg-muted); white-space: nowrap; padding: 0.12rem 0.45rem; border: 1px solid var(--wg-border); border-radius: 20px; background: var(--wg-surf2); transition: color .15s; } .wg-debug-toggle-label:hover { color: var(--wg-yellow); } .wg-debug-chevron { font-size: 0.6rem; margin-left: 0.2rem; } .wg-debug-body.wg-collapsed { display: none; } /* Debug panels */ .wg-panel { border-radius: 7px; padding: 0.6rem 0.8rem; margin: 0.35rem 0; border: 1px solid; font-size: 14px; } .wg-panel-title { font-family: 'Bebas Neue', sans-serif; font-size: 0.82rem; letter-spacing: 0.08em; margin-bottom: 0.35rem; } .wg-trace-block { border-radius: 10px; padding: 0.75rem 0.9rem; margin: 0.35rem 0; border: 1px solid var(--wg-border); background: var(--wg-surf2); } .wg-trace-title { font-family: 'Bebas Neue', sans-serif; font-size: 0.82rem; letter-spacing: 0.14em; color: var(--wg-muted); margin-bottom: 0.45rem; } .wg-trace-json { margin: 0; white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.8rem; line-height: 1.55; color: var(--wg-white); } /* Clickable chips */ .wg-chip-clickable { cursor: pointer; transition: filter .15s, transform .1s; } .wg-chip-clickable:hover { filter: brightness(1.25); transform: scale(1.06); } .wg-panel-yellow { background: #1a1000; border-color: #6b3a0a; color: #fcd34d; } .wg-panel-yellow .wg-panel-title { color: #fbbf24; } .wg-panel-blue { background: #050e1e; border-color: #1e3558; color: #93c5fd; } .wg-panel-blue .wg-panel-title { color: #60a5fa; } .wg-panel-green { background: #041409; border-color: #145228; color: #86efac; } .wg-panel-green .wg-panel-title { color: #4ade80; } .wg-panel-dim { background: #111; border-color: var(--wg-border); color: var(--wg-muted); } .wg-clickable { cursor: pointer; transition: opacity .15s, transform .1s; } .wg-clickable:hover { opacity: 0.85; transform: scale(1.01); } /* Scene card: gentle border-pulse + arrow float to signal interactivity */ @keyframes wg-scene-beckon { 0%,100% { box-shadow: 0 0 0 0 rgba(96,165,250,0); border-color: #1e3558; } 50% { box-shadow: 0 0 0 5px rgba(96,165,250,0.2); border-color: #60a5fa; } } @keyframes wg-arrow-float { 0%,100% { transform: translate(0,0); opacity: 0.6; } 50% { transform: translate(2px,-2px); opacity: 1; } } .wg-panel-blue.wg-clickable { animation: wg-scene-beckon 2.8s ease-in-out infinite; } .wg-scene-arrow { display: inline-block; animation: wg-arrow-float 2.8s ease-in-out infinite; } /* Pause animations when user hovers — reduces distraction mid-read */ .wg-panel-blue.wg-clickable:hover { animation: none; } .wg-panel-blue.wg-clickable:hover .wg-scene-arrow { animation: none; opacity: 1; } .wg-meta { border-collapse: collapse; width: 100%; } .wg-meta td { padding: 0.1rem 0.5rem 0.1rem 0; vertical-align: top; } .wg-rule { border-top: 1px solid var(--wg-border); margin: 0.75rem 0; } /* ── Coaching notes toggle attention flicker (plays 4× on new turns) ────── */ @keyframes wg-toggle-beckon { 0%,100% { color: var(--wg-muted); box-shadow: none; } 40%,60% { color: var(--wg-yellow); box-shadow: 0 0 10px rgba(245,197,24,0.35); border-color: rgba(245,197,24,0.5); } } .wg-debug-toggle--new .wg-debug-toggle-label { animation: wg-toggle-beckon 1.1s ease-in-out 0.4s 4 forwards; } #wg-practice .wg-debug-toggle--new .wg-debug-toggle-label { animation: wg-toggle-beckon-light 1.1s ease-in-out 0.4s 4 forwards; } @keyframes wg-toggle-beckon-light { 0%,100% { color: #9e9288; box-shadow: none; } 40%,60% { color: #b45309; box-shadow: 0 0 8px rgba(180,83,9,0.25); border-color: rgba(180,83,9,0.4); } } /* ── Metadata chips (dark-mode defaults) ────────────────────────────────── */ .wg-chip-row { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.45rem 0 0.35rem; } .wg-chip { font-family: 'Bebas Neue', sans-serif; font-size: 0.69rem; letter-spacing: 0.1em; padding: 0.18rem 0.55rem; border-radius: 20px; display: inline-block; line-height: 1.5; } .wg-chip-cyan { background: #0d2d33; color: #67e8f9; border: 1px solid #164e63; } .wg-chip-purple { background: #1e1030; color: #c4b5fd; border: 1px solid #4c1d95; } .wg-chip-orange { background: #2d1a00; color: #fbbf24; border: 1px solid #92400e; } .wg-chip-green { background: #0a2018; color: #4ade80; border: 1px solid #1a3d2b; } .wg-chip-label { font-size: 0.68rem; color: var(--wg-muted); align-self: center; font-family: 'EB Garamond', serif; font-style: italic; } .wg-avoided { font-size: 0.82rem; color: var(--wg-muted); margin: 0.3rem 0 0.45rem; padding: 0.25rem 0; border-top: 1px dashed rgba(255,255,255,0.07); } /* Expandable capsules (dark-mode) */ .wg-capsule { border: 1px solid var(--wg-border); border-radius: 7px; margin-top: 0.38rem; overflow: hidden; } .wg-capsule-head { cursor: pointer; padding: 0.38rem 0.65rem; font-family: 'Bebas Neue', sans-serif; font-size: 0.69rem; letter-spacing: 0.1em; color: #888; user-select: none; display: flex; justify-content: space-between; align-items: center; transition: color .15s, background .15s; } .wg-capsule-head:hover { color: var(--wg-white); background: rgba(255,255,255,0.04); } .wg-capsule-body { padding: 0.5rem 0.65rem; font-size: 0.92rem; color: rgba(240,240,240,0.88); line-height: 1.55; border-top: 1px solid var(--wg-border); background: var(--wg-surf2); } .wg-capsule-body.wg-collapsed { display: none; } .wg-cap-chev { font-size: 0.6rem; transition: transform .18s; display: inline-block; } .wg-capsule--open .wg-cap-chev { transform: rotate(90deg); } /* ── Shared shimmer + attention keyframes ────────────────────────────────── */ /* Shimmer: light highlight sweeps L→R — signals "this surface has depth" */ @keyframes wg-shimmer-slide { 0% { transform: translateX(-160%); } 100% { transform: translateX(160%); } } /* Chevron bobs toward hidden content — pure motion affordance */ @keyframes wg-chev-beckon { 0%,100% { transform: translateX(0); } 40% { transform: translateX(6px); } } /* Border breathes with a warm glow — signals "this boundary is crossable" */ @keyframes wg-capsule-glow { 0%,100% { border-color: var(--wg-border); box-shadow: none; } 50% { border-color: rgba(245,197,24,0.7); box-shadow: 0 0 0 2.5px rgba(245,197,24,0.18); } } /* Chip pop-in: slight scale bounce then settles — signals "I'm interactive" */ @keyframes wg-chip-pop { 0% { transform: scale(0.88); opacity: 0.6; } 60% { transform: scale(1.08); opacity: 1; } 100% { transform: scale(1); opacity: 1; } } /* Chip shimmer: same sweep but stronger amber */ @keyframes wg-chip-shimmer { 0% { transform: translateX(-180%); } 100% { transform: translateX(180%); } } /* ── Capsule attention (border glow + head shimmer + chevron bob) ─────────── */ .wg-capsule--new { animation: wg-capsule-glow 1.2s ease-in-out 0.4s 3 both; } .wg-capsule--new .wg-capsule-head { position: relative; overflow: hidden; } .wg-capsule--new .wg-capsule-head::after { content: ''; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(90deg, transparent 15%, rgba(245,197,24,0.55) 50%, transparent 85%); transform: translateX(-160%); animation: wg-shimmer-slide 1.1s ease-in-out 0.7s 3 both; } .wg-capsule--new .wg-cap-chev { animation: wg-chev-beckon 0.5s ease-in-out 0.3s 6 both; } /* Kill all animations once the user engages */ .wg-capsule--new.wg-capsule--open, .wg-capsule--new.wg-capsule--open .wg-capsule-head::after, .wg-capsule--new.wg-capsule--open .wg-cap-chev { animation: none; } /* Light-mode capsule overrides */ @keyframes wg-capsule-glow-light { 0%,100% { border-color: #e0d8cc; box-shadow: none; } 50% { border-color: rgba(180,83,9,0.6); box-shadow: 0 0 0 2.5px rgba(180,83,9,0.14); } } #wg-practice .wg-capsule--new { animation: wg-capsule-glow-light 1.2s ease-in-out 0.4s 3 both; } #wg-practice .wg-capsule--new .wg-capsule-head::after { background: linear-gradient(90deg, transparent 15%, rgba(180,83,9,0.45) 50%, transparent 85%); } /* ── Chip attention: pop-in scale bounce + shimmer sweep ─────────────────── */ .wg-chip-clickable { position: relative; overflow: hidden; animation: wg-chip-pop 0.45s cubic-bezier(.22,.68,0,1.4) both; } /* Stagger the three chips */ .wg-chip-row .wg-chip-clickable:nth-child(1) { animation-delay: 0.05s; } .wg-chip-row .wg-chip-clickable:nth-child(2) { animation-delay: 0.18s; } .wg-chip-row .wg-chip-clickable:nth-child(3) { animation-delay: 0.31s; } /* Shimmer on each chip after its pop-in */ .wg-chip-clickable::after { content: ''; position: absolute; inset: 0; pointer-events: none; border-radius: inherit; background: linear-gradient(90deg, transparent 10%, rgba(255,255,255,0.55) 50%, transparent 90%); transform: translateX(-180%); animation: wg-chip-shimmer 0.9s ease-in-out 0.55s 2 both; } .wg-chip-row .wg-chip-clickable:nth-child(2)::after { animation-delay: 0.68s; } .wg-chip-row .wg-chip-clickable:nth-child(3)::after { animation-delay: 0.81s; } /* ── Coaching notes toggle shimmer ──────────────────────────────────────── */ .wg-debug-toggle--new .wg-debug-toggle-label { position: relative; overflow: hidden; } .wg-debug-toggle--new .wg-debug-toggle-label::after { content: ''; position: absolute; inset: 0; pointer-events: none; border-radius: inherit; background: linear-gradient(90deg, transparent 15%, rgba(245,197,24,0.5) 50%, transparent 85%); transform: translateX(-160%); animation: wg-shimmer-slide 1.3s ease-in-out 0.2s 3 both; } #wg-practice .wg-debug-toggle--new .wg-debug-toggle-label::after { background: linear-gradient(90deg, transparent 15%, rgba(180,83,9,0.38) 50%, transparent 85%); } /* ── Light-mode overrides for chips + capsules ──────────────────────────── */ #wg-practice .wg-chip-cyan { background: #ecfeff; color: #0e7490; border-color: #a5f3fc; } #wg-practice .wg-chip-purple { background: #f5f3ff; color: #7c3aed; border-color: #ddd6fe; } #wg-practice .wg-chip-orange { background: #fffbeb; color: #b45309; border-color: #fde68a; } #wg-practice .wg-chip-green { background: #f0fdf4; color: #16a34a; border-color: #bbf7d0; } #wg-practice .wg-chip-label { color: #9e9288; } #wg-practice .wg-avoided { color: #9e9288; border-top-color: rgba(0,0,0,0.07); } #wg-practice .wg-capsule { border-color: #e0d8cc; } #wg-practice .wg-capsule-head { color: #9e9288 !important; } #wg-practice .wg-capsule-head:hover { color: #3d3429 !important; background: rgba(0,0,0,0.025) !important; } #wg-practice .wg-capsule-body { background: #faf9f6 !important; color: #3d3429 !important; border-top-color: #e0d8cc !important; } /* ── Light-mode overrides for mode badges ───────────────────────────────── */ #wg-practice .wg-mode-banter { background: #dcfce7 !important; color: #15803d !important; border-color: #86efac !important; } #wg-practice .wg-mode-wit { background: #fef9c3 !important; color: #92400e !important; border-color: #fde047 !important; } #wg-practice .wg-mode-coach { background: #dbeafe !important; color: #1d4ed8 !important; border-color: #93c5fd !important; } .wg-dim { color: var(--wg-muted); } .wg-dim-italic { color: var(--wg-muted); font-style: italic; } .wg-cyan { color: #22d3ee; font-weight: 500; } .wg-bold { font-weight: 600; } /* ── Slide-in animation on new turns ───────────────────────────────────── */ @keyframes wg-slide-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .wg-turn { animation: wg-slide-in 0.35s ease-out both; } /* ── Reveal animation on winning coach reply ────────────────────────────── */ @keyframes wg-reply-reveal { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } } .wg-coach-reply--new .wg-coach-reply-body { animation: wg-reply-reveal 0.6s ease-out 0.15s both; } /* Flash on "another take" swap */ @keyframes wg-alt-flash { 0%,100% { opacity: 1; } 40% { opacity: 0.3; } } .wg-coach-reply--alt .wg-coach-reply-body { animation: wg-alt-flash 0.35s ease-out; } /* ── Twist potential meter ──────────────────────────────────────────────── */ .wg-twist-meter { display: flex; align-items: center; gap: 0.5rem; margin: 0.6rem 0 0.75rem; font-family: 'Bebas Neue', sans-serif; font-size: 0.72rem; letter-spacing: 0.12em; } .wg-twist-label { color: var(--wg-muted); white-space: nowrap; } .wg-twist-bar { flex: 1; height: 4px; background: var(--wg-border); border-radius: 2px; overflow: hidden; } .wg-twist-fill { height: 100%; border-radius: 2px; background: linear-gradient(to right, var(--wg-green), var(--wg-yellow)); transition: width 0.8s ease-out; } .wg-twist-score { color: var(--wg-yellow); min-width: 2.5rem; text-align: right; } /* Light-mode overrides for meter */ #wg-practice .wg-twist-bar { background: #e0d8cc !important; } #wg-practice .wg-twist-score { color: #b45309 !important; } #wg-practice .wg-twist-label { color: #9e9288 !important; } /* ── Drill chips ─────────────────────────────────────────────────────────── */ .wg-drill-chips { display: flex; flex-wrap: wrap; gap: 0.45rem; margin: 0.6rem 0 0.2rem; } .wg-drill-chip { font-family: 'EB Garamond', serif; font-style: italic; font-size: 0.82rem; cursor: pointer; user-select: none; padding: 0.22rem 0.7rem; border-radius: 20px; border: 1px solid var(--wg-border); color: var(--wg-muted); background: var(--wg-surf2); transition: border-color .15s, color .15s, background .15s; } .wg-drill-chip:hover { border-color: var(--wg-yellow); color: var(--wg-yellow); background: rgba(245,197,24,0.06); } #wg-practice .wg-drill-chip { border-color: #e0d8cc !important; color: #9e9288 !important; background: #f5f2eb !important; } #wg-practice .wg-drill-chip:hover { border-color: #b45309 !important; color: #b45309 !important; background: rgba(180,83,9,0.06) !important; } /* ── Persona label + another-take ──────────────────────────────────────── */ .wg-persona-label { font-style: italic; font-family: 'EB Garamond', serif; font-size: 0.78rem; color: var(--wg-yellow); letter-spacing: 0; font-weight: 400; background: transparent !important; border: none !important; padding: 0 !important; cursor: pointer; box-shadow: none !important; display: inline; vertical-align: baseline; } #wg-practice .wg-persona-label { color: #b45309 !important; } .wg-insight-strip { margin-top: 0.85rem; padding: 0.8rem 0.95rem; border-radius: 14px; background: linear-gradient(180deg, rgba(255,250,241,0.98), rgba(245,240,232,0.98)); border: 1px solid rgba(180,83,9,0.16); } .wg-insight-title { font-family: 'Bebas Neue', sans-serif; font-size: 0.9rem; letter-spacing: 0.12em; color: #92400e !important; } .wg-insight-sub { margin-top: 0.2rem; font-size: 0.88rem; color: #6b6258 !important; } .wg-insight-buttons { display: flex; flex-wrap: wrap; gap: 0.45rem; margin-top: 0.65rem; } .wg-insight-btn { -webkit-appearance: none !important; appearance: none !important; background: #fffaf1 !important; border: 1px solid rgba(180,83,9,0.22) !important; color: #9a3412 !important; border-radius: 999px !important; padding: 0.42rem 0.72rem !important; font-family: 'Bebas Neue', sans-serif; font-size: 0.8rem; letter-spacing: 0.09em; cursor: pointer; line-height: 1; box-shadow: none !important; } .wg-insight-btn:hover { background: #ffffff !important; border-color: rgba(180,83,9,0.42) !important; transform: translateY(-1px); } .wg-another-take { float: right; cursor: pointer; font-family: 'EB Garamond', serif; font-size: 0.75rem; font-style: italic; letter-spacing: 0; color: rgba(245,197,24,0.6); transition: color .15s; user-select: none; } .wg-another-take:hover { color: var(--wg-yellow); } #wg-practice .wg-another-take { color: #b4960a !important; } #wg-practice .wg-another-take:hover { color: #78350f !important; } /* ── Step-cycle loading messages ────────────────────────────────────────── */ .wg-step-cycle { position: relative; display: inline-block; height: 1.4em; min-width: 200px; overflow: hidden; vertical-align: middle; } .wg-step-cycle span { position: absolute; left: 0; top: 0; opacity: 0; animation: wg-step-show 6s linear infinite; white-space: nowrap; } .wg-step-cycle span:nth-child(2) { animation-delay: 2s; } .wg-step-cycle span:nth-child(3) { animation-delay: 4s; } @keyframes wg-step-show { 0% { opacity: 0; transform: translateY(4px); } 6% { opacity: 0.8; transform: translateY(0); } 28% { opacity: 0.8; transform: translateY(0); } 34% { opacity: 0; transform: translateY(-4px); } 100% { opacity: 0; } } /* ── Subtle stage spotlight on practice bg ──────────────────────────────── */ #wg-practice::before { content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 0; background: radial-gradient(ellipse 70% 35% at 50% 0%, rgba(45,106,79,0.05) 0%, transparent 70%); } #wg-practice { position: relative; } /* ── Comic-style modal ──────────────────────────────────────────────────── */ /* Same structural fix as #wg-practice: Gradio injects .prose * { color: var(--body-text-color) } which resolves to #f0f0f0 (dark theme) on every child element. The modal is outside #wg-practice so that scope doesn't apply. Override the same Gradio CSS vars here so child text inherits dark-on-ivory correctly — no per-element !important hacks needed. */ #wg-modal { --body-text-color: #2a2118; --body-text-color-subdued: #6b6258; --block-text-color: #2a2118; --block-label-text-color: #6b6258; --input-text: #2a2118; --color-text-body: #2a2118; color: #2a2118; } #wg-modal-overlay { position: fixed; inset: 0; z-index: 9999; background: rgba(0,0,0,0.65); backdrop-filter: blur(5px); display: none; align-items: center; justify-content: center; padding: 1rem; } #wg-modal { background: #fffff8; background-image: radial-gradient(rgba(0,0,0,0.04) 1px, transparent 1px); background-size: 20px 20px; border-radius: 18px; max-width: 680px; width: 100%; padding: 1.75rem 1.75rem 1.5rem; position: relative; box-shadow: 0 24px 64px rgba(0,0,0,0.45), 0 0 0 2px rgba(0,0,0,0.08); font-family: 'EB Garamond', Georgia, serif; max-height: 90vh; overflow-y: auto; } .wg-modal-x { position: absolute; top: 1rem; right: 1rem; width: 32px; height: 32px; border-radius: 50%; background: #1a1a1a; color: #fff; border: none; font-size: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background .15s; } .wg-modal-x:hover { background: #333; } .wg-pop-show { font-family: 'Bebas Neue', sans-serif; font-size: 1rem; letter-spacing: 0.2em; color: #2d6a4f !important; margin-bottom: 1rem; border-bottom: 2px solid #e0d8cc; padding-bottom: 0.5rem; } .wg-pop-row { display: flex; gap: 1.25rem; margin-bottom: 1rem; align-items: flex-start; } .wg-pop-char { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; flex-shrink: 0; } .wg-pop-avatar { width: 110px; height: 110px; border-radius: 12px; background: #f5f0e6; } .wg-pop-name { font-family: 'Bebas Neue', sans-serif; font-size: 1rem; letter-spacing: 0.08em; color: #2a2118 !important; text-align: center; } .wg-pop-title { font-size: 0.72rem; color: #9e9288 !important; text-align: center; font-style: italic; max-width: 110px; line-height: 1.3; } .wg-pop-right { flex: 1; display: flex; flex-direction: column; gap: 0.65rem; } .wg-pop-setup { font-style: italic; color: #6b6258 !important; font-size: 0.95rem; line-height: 1.5; } .wg-pop-bubble { background: #fff; border: 2.5px solid #1a1a1a; border-radius: 14px; padding: 0.85rem 1rem; font-family: 'Bebas Neue', Impact, sans-serif; font-size: 1.15rem; line-height: 1.3; letter-spacing: 0.02em; color: #1a1a1a !important; position: relative; } .wg-pop-bubble::before { content: ''; position: absolute; left: -14px; top: 50%; transform: translateY(-50%); border: 7px solid transparent; border-right-color: #1a1a1a; } .wg-pop-bubble::after { content: ''; position: absolute; left: -10px; top: 50%; transform: translateY(-50%); border: 6px solid transparent; border-right-color: #fff; } .wg-pop-why { background: #eff6ff; border: 1px solid #60a5fa; border-radius: 10px; padding: 0.75rem 1rem; margin-top: 0.25rem; } .wg-pop-why-title { font-family: 'Bebas Neue', sans-serif; font-size: 0.82rem; letter-spacing: 0.12em; color: #2563eb !important; margin-bottom: 0.35rem; } .wg-pop-why-body { font-size: 0.95rem; color: #1e3a5f !important; line-height: 1.55; } /* Bio modal (character card click — no scene context) */ .wg-pop-bio { font-size: 1.05rem; color: #3d3429 !important; line-height: 1.6; font-style: italic; background: #f5f0e6; border-left: 3px solid #2d6a4f; padding: 0.75rem 1rem; border-radius: 0 10px 10px 0; } .wg-pop-minihead { font-family: 'Bebas Neue', sans-serif; font-size: 0.8rem; letter-spacing: 0.12em; color: #2563eb !important; margin: 0.9rem 0 0.35rem; } /* ── Arcade Character Selector ─────────────────────────────────────────────── */ .wg-arcade { display: flex; align-items: center; justify-content: center; gap: 0.5rem; padding: 0.05rem 0 0; max-width: 700px; margin: 0 auto; } .wg-arcade-stage { display: flex; align-items: center; justify-content: center; gap: 0.75rem; flex: 1; } .wg-arcade-arrow { background: rgba(10,20,14,0.72) !important; border: 1.5px solid #4ade80 !important; color: #4ade80 !important; font-size: 2rem !important; border-radius: 50% !important; width: 48px !important; height: 48px !important; display: flex !important; align-items: center !important; justify-content: center !important; cursor: pointer !important; transition: background .15s, box-shadow .15s, transform .1s !important; flex-shrink: 0 !important; line-height: 1 !important; padding: 0 !important; box-shadow: 0 0 12px rgba(74,222,128,0.35), inset 0 0 8px rgba(74,222,128,0.08) !important; backdrop-filter: blur(8px) !important; -webkit-backdrop-filter: blur(8px) !important; outline: none !important; } .wg-arcade-arrow:hover { background: rgba(74,222,128,0.15) !important; border-color: #86efac !important; box-shadow: 0 0 22px rgba(74,222,128,0.6), inset 0 0 12px rgba(74,222,128,0.15) !important; transform: scale(1.12) !important; color: #86efac !important; } .wg-arcade-center { background: var(--wg-surf2); border: 2px solid var(--wg-yellow); border-radius: 14px; padding: 0.35rem 0.8rem 0.35rem; display: flex; flex-direction: column; align-items: center; gap: 0.15rem; cursor: pointer; min-width: 150px; max-width: 175px; box-shadow: 0 0 28px rgba(245,197,24,0.22); transition: box-shadow .2s, border-color .2s; animation: wg-arcade-glow 2.4s ease-in-out infinite; } .wg-arcade-center:hover { box-shadow: 0 0 45px rgba(245,197,24,0.45); animation: none; } @keyframes wg-arcade-glow { 0%,100% { box-shadow: 0 0 18px rgba(245,197,24,0.18); border-color: var(--wg-yellow); } 50% { box-shadow: 0 0 36px rgba(245,197,24,0.4); border-color: #fde68a; } } .wg-arcade-avatar-wrap { position: relative; } .wg-arcade-img { width: 76px; height: 76px; border-radius: 10px; background: rgba(255,255,255,0.05); object-fit: cover; transition: opacity .18s; } .wg-arcade-name { font-family: 'Bebas Neue', sans-serif; font-size: 1.15rem; letter-spacing: 0.12em; color: var(--wg-yellow) !important; text-align: center; margin-top: 0.1rem; } .wg-arcade-role { font-family: 'EB Garamond', serif; font-style: italic; font-size: 0.7rem; color: var(--wg-muted); text-align: center; letter-spacing: 0.1em; } .wg-arcade-bio { display: none; } .wg-arcade-select-hint { font-family: 'Bebas Neue', sans-serif; font-size: 0.55rem; letter-spacing: 0.22em; color: #4ade80; margin-top: 0.1rem; opacity: 0.7; } /* Peek (adjacent) cards */ .wg-arcade-peek { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; opacity: 0.42; filter: blur(1.5px); transform: scale(0.78); transition: opacity .2s, filter .2s, transform .2s; pointer-events: none; } .wg-arcade-peek img { width: 52px; height: 52px; border-radius: 8px; } .wg-arcade-peek-name { font-family: 'Bebas Neue', sans-serif; font-size: 0.65rem; letter-spacing: 0.1em; color: var(--wg-muted); text-align: center; } /* Dot indicators */ .wg-arcade-dots { display: flex; justify-content: center; gap: 0.5rem; margin: 0.6rem 0 0.25rem; } .wg-arcade-dot { width: 6px; height: 6px; border-radius: 50%; background: rgba(255,255,255,0.2); transition: background .2s, transform .2s; } .wg-arcade-dot--active { background: var(--wg-yellow); transform: scale(1.4); } /* Slide animation */ @keyframes wg-arcade-slide-in-right { 0% { opacity: 0; transform: translateX(40px) scale(0.92); } 100% { opacity: 1; transform: translateX(0) scale(1); } } @keyframes wg-arcade-slide-in-left { 0% { opacity: 0; transform: translateX(-40px) scale(0.92); } 100% { opacity: 1; transform: translateX(0) scale(1); } } .wg-arcade-center.wg-arcade--slide-right { animation: wg-arcade-slide-in-right 0.22s ease-out both; } .wg-arcade-center.wg-arcade--slide-left { animation: wg-arcade-slide-in-left 0.22s ease-out both; } /* ── Practice header coach identity ─────────────────────────────────────── */ .wg-coach-avatar-wrap { display: flex; align-items: center; flex-shrink: 0; } .wg-coach-avatar-img { width: 42px; height: 42px; border-radius: 8px; object-fit: cover; } .wg-coach-id { display: flex; flex-direction: column; justify-content: center; } /* ── Coach reply: avatar + NAME SAYS ────────────────────────────────────── */ .wg-coach-reply { position: relative; } .wg-coach-reply-header--char { display: flex; align-items: center; gap: 0.45rem; } .wg-coach-reply-avatar { width: 28px; height: 28px; border-radius: 6px; flex-shrink: 0; background: rgba(255,255,255,0.08); } #wg-practice .wg-coach-reply-avatar { background: rgba(0,0,0,0.05); } /* ── Reply action buttons ────────────────────────────────────────────────── */ .wg-reply-actions { position: absolute; top: 0.5rem; right: 0.6rem; display: inline-flex; align-items: center; gap: 0.5rem; z-index: 3; } .wg-action-btn { -webkit-appearance: none !important; appearance: none !important; display: inline-flex; align-items: center; justify-content: center; width: 2.15rem; height: 2.15rem; min-width: 2.15rem; background: rgba(245,240,232,0.88) !important; border: 1px solid rgba(45,106,79,0.12) !important; border-radius: 0.72rem !important; box-shadow: none !important; cursor: pointer; color: rgba(94, 84, 72, 0.88); opacity: 0.92; transition: opacity .15s, color .15s, transform .15s, border-color .15s, background-color .15s; padding: 0 !important; line-height: 1; font: inherit; outline: none !important; } .wg-action-btn:hover { opacity: 1; color: #9c5c19; transform: translateY(-1px); border-color: rgba(180,83,9,0.22) !important; background: rgba(255, 250, 241, 0.98) !important; } #wg-practice .wg-action-btn { color: rgba(94, 84, 72, 0.88); background: rgba(245,240,232,0.9) !important; } #wg-practice .wg-action-btn:hover { color: #b45309; background: rgba(255,250,241,0.98) !important; } .wg-action-btn:focus, .wg-action-btn:focus-visible { outline: none !important; box-shadow: 0 0 0 2px rgba(45,106,79,0.14) !important; } .wg-action-icon { width: 0.86rem; height: 0.86rem; display: block; flex-shrink: 0; pointer-events: none; } /* ── Dedicated trace launcher + modal post-mortem ───────────────────────── */ .wg-trace-launch-wrap { display: flex; justify-content: flex-start; margin-top: 0.75rem; } .wg-trace-launch { display: inline-flex; align-items: center; gap: 0.55rem; border: 1.5px solid rgba(180,83,9,0.34); background: linear-gradient(180deg, rgba(255,250,241,0.95), rgba(245,240,232,0.98)); color: #b45309; border-radius: 999px; padding: 0.55rem 0.95rem; cursor: pointer; font-family: 'Bebas Neue', sans-serif; font-size: 0.88rem; letter-spacing: 0.12em; box-shadow: 0 8px 24px rgba(180,83,9,0.08); transition: transform .15s, box-shadow .15s, border-color .15s, color .15s; } .wg-trace-launch:hover { transform: translateY(-1px); box-shadow: 0 12px 28px rgba(180,83,9,0.13); border-color: rgba(180,83,9,0.55); color: #92400e; } .wg-trace-launch-icon { width: 1.9rem; height: 1.9rem; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.95), rgba(255,241,220,0.92) 42%, rgba(255,231,189,0.88)); border: 1px solid rgba(180,83,9,0.2); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.65), 0 4px 14px rgba(180,83,9,0.14); } .wg-trace-launch-icon svg { width: 1.08rem; height: 1.08rem; display: block; } .wg-trace-launch-text { display: inline-flex; align-items: center; } .wg-trace-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; padding-bottom: 0.8rem; border-bottom: 2px solid #e0d8cc; } .wg-trace-modal-title { font-family: 'Bebas Neue', sans-serif; font-size: 1.05rem; letter-spacing: 0.18em; color: #2d6a4f !important; } .wg-trace-modal-sub { color: #9e9288 !important; font-size: 0.92rem; font-style: italic; } .wg-trace-summary { display: flex; flex-wrap: wrap; gap: 0.45rem; margin-bottom: 1rem; } .wg-trace-pill { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.28rem 0.62rem; border-radius: 999px; border: 1px solid #e0d8cc; background: #fff; color: #3d3429 !important; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.76rem; } .wg-trace-logbox { background: #171717; color: #f5f0e8; border-radius: 14px; padding: 0.9rem 1rem; margin-bottom: 1rem; box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05); } .wg-trace-logtitle { font-family: 'Bebas Neue', sans-serif; font-size: 0.82rem; letter-spacing: 0.16em; color: #f5c518 !important; margin-bottom: 0.55rem; } .wg-trace-logline { display: grid; grid-template-columns: 118px 64px 1fr; gap: 0.7rem; padding: 0.24rem 0; align-items: start; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.78rem; line-height: 1.45; } .wg-trace-step { color: #93c5fd !important; } .wg-trace-status { color: #86efac !important; text-transform: uppercase; } .wg-trace-detail { color: #f5f0e8 !important; opacity: 0.92; } .wg-trace-jsonbox { background: #faf9f6; border: 1px solid #e0d8cc; border-radius: 14px; overflow: hidden; } .wg-trace-jsonhead { font-family: 'Bebas Neue', sans-serif; font-size: 0.82rem; letter-spacing: 0.15em; color: #9e9288 !important; padding: 0.75rem 0.95rem; border-bottom: 1px solid #e0d8cc; background: rgba(255,255,255,0.7); } .wg-trace-jsonpre { margin: 0; padding: 0.95rem 1rem 1.1rem; max-height: 48vh; overflow: auto; white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.78rem; line-height: 1.55; color: #3d3429 !important; } /* ── Hidden char state textbox ───────────────────────────────────────────── */ #wg-char-hidden { position: absolute; width: 0; height: 0; overflow: hidden; opacity: 0; pointer-events: none; } /* ── Floating roast messages — JS-spawned, multi-directional ─────────────── */ .wg-roast-chip { position: fixed; z-index: 50; pointer-events: none; font-family: 'EB Garamond', Georgia, serif; font-style: italic; font-size: 0.75rem; color: rgba(245,197,24,0.9); background: rgba(20,20,20,0.78); border: 1px solid rgba(245,197,24,0.28); border-radius: 20px; padding: 0.28rem 0.8rem; opacity: 0; transition: none; animation: wg-roast-float-in 4s ease-out forwards; } @keyframes wg-roast-float-in { 0% { opacity: 0; } 15% { opacity: 1; } 75% { opacity: 1; } 100% { opacity: 0; } } /* ── Sidebar bubble head ─────────────────────────────────────────────────── */ @keyframes wg-bubble-bob { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-6px) rotate(0.4deg); } } .wg-bubble-head { display: flex; flex-direction: column; align-items: center; padding: 1.4rem 0.5rem 0.6rem; user-select: none; } .wg-bubble-head-inner { animation: wg-bubble-bob 2.8s ease-in-out infinite; transition: transform 0.22s ease; cursor: pointer; } .wg-bubble-head-inner:hover { animation-play-state: paused; transform: scale(1.1) rotate(5deg) !important; } .wg-bubble-avatar-large { width: 90px; height: 90px; border-radius: 50%; border: 2.5px solid var(--wg-green); box-shadow: 0 0 18px rgba(74,222,128,0.22), 0 3px 10px rgba(0,0,0,0.35); object-fit: cover; display: block; } .wg-bubble-char-name { font-family: 'Bebas Neue', sans-serif; letter-spacing: 0.18em; font-size: 0.68rem; color: var(--wg-muted); margin-top: 0.5rem; text-align: center; } #wg-practice .wg-bubble-avatar-large { border-color: #2d6a4f; box-shadow: 0 0 16px rgba(45,106,79,0.18), 0 2px 8px rgba(0,0,0,0.1); } #wg-practice .wg-bubble-char-name { color: #9e9288; } /* ── Sci-fi flicker input overlay ────────────────────────────────────────── */ .wg-flicker-wrap { position: relative; } .wg-flicker-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; padding: 0.55rem 0.75rem; font-family: 'EB Garamond', serif; font-size: 1rem; color: #2a2118; opacity: 0.5; line-height: 1.5; display: flex; align-items: center; z-index: 2; } .wg-flicker-cursor { display: inline-block; width: 2px; height: 1.1em; background: #2a2118; margin-left: 1px; vertical-align: middle; animation: wg-cursor-blink 0.9s step-end infinite; } @keyframes wg-cursor-blink { 0%,100% { opacity: 1; } 50% { opacity: 0; } } /* Glow border on idle textarea */ #wg-chat-shell textarea:not(:focus) { box-shadow: 0 0 0 1.5px rgba(45,106,79,0.25) !important; transition: box-shadow .4s; } #wg-chat-shell textarea:focus { box-shadow: 0 0 0 2px rgba(45,106,79,0.55) !important; } #wg-practice #wg-chat-shell textarea:not(:focus) { box-shadow: 0 0 0 1.5px rgba(45,106,79,0.18) !important; } #wg-practice #wg-chat-shell textarea:focus { box-shadow: 0 0 0 2px rgba(45,106,79,0.4) !important; } """ # ── Global JS — injected into page
via gr.HTML(head=...) ────────────── # Using head= instead of value= because " # ── App state & engine ──────────────────────────────────────────────────────── _shared = None _warmup_error: str | None = None def _get_shared(): global _shared if _shared is None: from witgym.retriever import load_index load_index(INDEX_PATH) _shared = get_shared_resources(index_path=INDEX_PATH) return _shared def _format_warmup_error(exc: Exception) -> str: from witgym.hub_data import get_startup_status lines = [str(exc)] status = get_startup_status() if status: lines += ["", "Diagnostics:"] + [f"• {e}" for e in status] return "\n".join(lines) def _bg_warmup(): global _warmup_error try: _get_shared() pass # TTS warms up on first use via HF Inference API except Exception as e: logger.exception("Background warmup failed") _warmup_error = _format_warmup_error(e) threading.Thread(target=_bg_warmup, daemon=True).start() def _new_session(): return {"conversation": ConversationManager(), "traces": [], "last_wit_response": None, "selected_char": "AI"} def _on_page_load(): if _warmup_error: return ( '