"""Liquid-flow visual theme for CityQuest-AI. A minimalist pale/beige palette with a slowly drifting "water" gradient, soft pill buttons with a tap ripple, rounded translucent cards, clear sans typography, and hover tooltips injected onto every ``elem_id``'d control. Exports ``CSS`` (stylesheet) and ``JS_INIT`` (tooltip + ripple bootstrap), plus :func:`gradio_theme` for the base Gradio theme object. """ import gradio as gr # ── Tooltip copy keyed by elem_id (applied via JS) ─────────────────────────── TOOLTIPS = { "home-create": "Start a new adventure and become the host", "home-join": "Enter a room code to join a friend's adventure", "create-submit": "Create the room and open the lobby", "create-back": "Back to the start", "join-submit": "Join this room", "join-back": "Back to the start", "lobby-start": "Generate tasks and send every team into play", "lobby-leave": "Leave this room", "td-transcribe": "Transcribe your recording with Cohere ASR", "td-savejournal": "Save this voice/typed note — you can add several per task", "td-addphoto": "Attach this photo with its tagline (add as many as you like)", "td-ask": "Ask the in-game guide about this task or place", "td-complete": "Mark this task done — needs a photo or a journal first", "td-prev": "Previous task", "td-next": "Next task", "td-finish": "Finish and wait for the other teams", "play-finish": "Finish and wait for the other teams", "wait-funny-recap": "Make a light, funny recap of your run so far", "wait-funny-post": "Make a playful poster from your photos", "win-recap": "Generate the winning episode recap", "win-endpost": "Generate an ending poster", "win-grouppost": "Generate a group celebration poster", "win-new": "Start a brand new adventure", } CSS = """ @import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&display=swap'); :root { /* ── High-contrast dark theme (WCAG AAA, white-on-dark) ── Raw palette tokens (bright accents, used directly by some rules) … */ --green: #4CAF50; /* bright green — primary buttons / success */ --green-h: #45A049; /* green hover */ --green-a: #3D8B40; /* green active/pressed */ --blue: #2196F3; /* bright blue — interactive */ --purple: #9C27B0; /* bright purple — CTAs */ --yellow: #FFC107; /* bright amber — warnings / highlights */ --red: #F44336; /* bright red — errors */ /* … and aliases onto the names the rest of the stylesheet + app.py use, so the whole UI re-skins from this one block. */ --sand: #1A1A1A; /* page base — darkest */ --sand-2: #222222; --paper: #2A2A2A; /* card surface */ --paper-2: #3A3A3A; /* inputs / soft surfaces / code */ --line: #4A4A4A; /* borders — visible on dark */ --clay: #FFB74D; /* warm amber accent */ --clay-dk: #FFC107; /* bright amber — used as accent text */ --teal: #4CAF50; /* bright green accent (fills + borders) */ --teal-dk: #66BB6A; /* lighter green — accent text on dark cards */ --ink: #FFFFFF; /* primary text — white, no exceptions */ --muted: #CCCCCC; /* secondary text — light grey */ --good: #4CAF50; --radius: 18px; } /* Force our palette even if Gradio applies its dark class (we also force light via the URL param in JS, this is a belt-and-braces override). */ .dark { --body-text-color: var(--ink); --body-background-fill: var(--sand); } /* ── Flowing water background ── */ body, .gradio-container, .dark .gradio-container { background: var(--sand) !important; color: var(--ink) !important; font-family: 'Inter', system-ui, sans-serif !important; } .gradio-container::before { content: ''; position: fixed; inset: -40%; z-index: -1; background: radial-gradient(40% 55% at 20% 30%, rgba(76,175,80,0.12), transparent 60%), /* green glow */ radial-gradient(45% 50% at 80% 25%, rgba(33,150,243,0.12), transparent 60%), /* blue glow */ radial-gradient(50% 60% at 60% 85%, rgba(156,39,176,0.10), transparent 60%), /* purple glow */ radial-gradient(45% 55% at 15% 80%, rgba(255,193,7,0.07), transparent 60%); /* amber glow */ background-size: 130% 130%; animation: drift 26s ease-in-out infinite alternate; filter: blur(8px); pointer-events: none; } @keyframes drift { 0% { background-position: 30% 35%; } 50% { background-position: 60% 50%; } 100% { background-position: 45% 65%; } } /* Make every full-screen wrapper transparent so the drifting aura gradient (.gradio-container::before) shows through. stays the dark base. */ gradio-app, .gradio-container, .dark .gradio-container, .gradio-container > .main, .gradio-container .wrap, .gradio-container .contain { background: transparent !important; } footer { display: none !important; } /* ── Screen fade/slide transitions ── */ .cq-screen { animation: flow-in .55s cubic-bezier(.22,.61,.36,1); } @keyframes flow-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } } /* ── Cards ── */ .cq-card { background: rgba(42,42,42,0.92) !important; border: 1px solid var(--line) !important; border-radius: var(--radius) !important; padding: 26px !important; backdrop-filter: blur(6px); box-shadow: 0 10px 40px -20px rgba(0,0,0,0.6); } .cq-soft { background: var(--paper-2) !important; border:1px solid var(--line) !important; border-radius: var(--radius) !important; padding: 18px !important; } /* ── Landing page refinements (scoped to home screen only) ── */ /* Remove the grey rectangle/background around the whole home screen */ .cq-home, .cq-home > .block, .cq-home > div { background: transparent !important; border: none !important; box-shadow: none !important; } /* Bigger title "logo" on the landing screen */ .cq-home .cq-title { font-size: clamp(2.6rem, 7vw, 4.2rem) !important; } /* CHANGE 1: Remove borders / box outlines from the home cards so they float cleanly against the background — other screens keep their .cq-card chrome. */ .cq-home-card, .cq-home-card > .block, .cq-home-card > div, .cq-home-card .form, .cq-home .gr-column, .cq-home .gr-column > .block { border: none !important; box-shadow: none !important; outline: none !important; background: transparent !important; backdrop-filter: none !important; } /* CHANGE 3B: Centered, larger description above each button */ .cq-home .landing-description { text-align: center !important; font-size: 1.1rem !important; color: #CCCCCC !important; margin-bottom: 16px !important; line-height: 1.5 !important; } /* CHANGE 3A: Bigger, bolder full-width buttons. CHANGE 4: hover/active animation */ .cq-home .landing-btn { width: 100% !important; padding: 20px 32px !important; font-size: 1.2rem !important; font-weight: 700 !important; border-radius: 16px !important; letter-spacing: 0.5px !important; min-height: 64px !important; transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease !important; cursor: pointer !important; } /* Make both landing buttons the same colour (green) */ .cq-home .landing-btn.secondary { background: var(--green) !important; border: none !important; color: #000000 !important; box-shadow: 0 8px 24px -14px rgba(76,175,80,0.8) !important; } .cq-home .landing-btn.secondary:hover { background: var(--green-h) !important; border: none !important; color: #000000 !important; } .cq-home .landing-btn:hover { transform: scale(1.03) !important; filter: brightness(1.1) !important; box-shadow: 0 8px 24px rgba(76,175,80,0.4) !important; } .cq-home .landing-btn:active { transform: scale(0.98) !important; filter: brightness(0.95) !important; } /* ── Create screen refinements (scoped to .cq-create only) ── */ /* Remove the outer grey rectangle around the whole screen. Each screen's own gr.Group (.cq-screen) renders Gradio's default card chrome; strip it so the inner .cq-card floats against the animated background. Scoped to the group's own wrapper layers so the inner .cq-card keeps its border/fill. Applies to every screen (create, join, lobby, play, wait, win). */ .cq-screen, .cq-screen > .block, .cq-screen > div:not(.cq-card), .cq-screen > .block > div:not(.cq-card) { background: transparent !important; border: none !important; box-shadow: none !important; backdrop-filter: none !important; } /* CHANGE 1: Give the dropdown fields (game type, difficulty, age group, energy) the same grey opaque input box as the textbox fields. Gradio renders a dropdown's display element as .wrap (not a typed ), so it never picked up the input fill — this makes them visually match and read as editable. */ .cq-create .cq-dropdown .wrap { background: var(--paper-2) !important; border: 1px solid var(--line) !important; border-radius: 12px !important; color: var(--ink) !important; } /* CHANGE 4: Center the live game-parameter summary bar */ .cq-create .game-summary, .cq-create .game-summary .cq-soft { text-align: center !important; width: 100% !important; letter-spacing: 0.5px !important; } .cq-create .game-summary .cq-soft { border: none !important; background: transparent !important; } /* CHANGE 5: Bigger buttons with hover/active animation (matches landing page) */ .cq-create .create-btn { transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease !important; cursor: pointer !important; padding: 18px 32px !important; font-size: 1.1rem !important; font-weight: 700 !important; border-radius: 12px !important; min-height: 60px !important; } .cq-create .open-lobby-btn:hover { transform: scale(1.03) !important; box-shadow: 0 8px 24px rgba(76,175,80,0.4) !important; filter: brightness(1.1) !important; } .cq-create .back-btn:hover { transform: scale(1.03) !important; box-shadow: 0 8px 24px rgba(255,255,255,0.1) !important; filter: brightness(1.15) !important; } .cq-create .create-btn:active { transform: scale(0.98) !important; filter: brightness(0.95) !important; } /* ── Win screen: black-card reskin + symmetric columns ─────────── */ /* The Celebrate card overrides the default .cq-card grey fill with pure black and a stronger rounded border, scoped to .cq-win only. */ .cq-win .cq-win-celebrate.cq-card { background: #0A0A0A !important; border: 1px solid rgba(255,255,255,0.08) !important; border-radius: 20px !important; padding: 28px !important; backdrop-filter: none !important; box-shadow: 0 16px 48px -24px rgba(0,0,0,0.8) !important; } /* Strip the default grey block/form fills from inner wrappers inside the Celebrate card so only the black card shows through. Buttons (