"""Celebrity Deathmatch — Gradio frontend (HuggingFace Space).
Upload two fighters -> FIGHT! runs Stage 1 (fight script) + Stage 2 (keyframe reel).
The opt-in "Animate" button runs Stage 3 (chained, captioned fight video).
Runs fully on CPU with DEATHMATCH_MOCK=1 (canned fight, placeholder frames).
"""
from __future__ import annotations
import json
import os
import tempfile
import gradio as gr
from model_runtime import (
API_URL, MOCK, BackendError, animate, generate_fightcard,
generate_keyframes, health,
)
ARENAS = [
"Classic Deathmatch ring", "Rooftop at night", "Abandoned shopping mall",
"Volcano lair", "Suburban backyard", "Neon Tokyo street",
]
STYLES = ["Claymation", "Stop-motion", "Cartoon", "Comic book"]
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Anton&family=DM+Sans:wght@400;600;800&family=JetBrains+Mono:wght@600&display=swap');
:root {
--dm-bg: #0b0608; --dm-panel: rgba(24,12,16,0.86); --dm-red: #e0303c;
--dm-gold: #f2b030; --dm-text: #f4ede0; --dm-muted: #b09088;
}
body, .gradio-container {
background:
radial-gradient(circle at 15% 8%, rgba(224,48,60,0.18), transparent 32%),
radial-gradient(circle at 85% 4%, rgba(242,176,48,0.12), transparent 34%),
linear-gradient(160deg, #0b0608 0%, #140a0d 60%, #1c0e12 100%) !important;
color: var(--dm-text) !important; font-family: 'DM Sans', sans-serif !important;
}
.gradio-container { max-width: 1180px !important; margin: 0 auto !important; padding: 22px 20px 44px !important; }
.dm-disclaimer {
margin: 0 0 16px; padding: 9px 14px; border: 1px dashed rgba(242,176,48,0.45);
border-radius: 12px; background: rgba(242,176,48,0.07); color: var(--dm-gold);
font-family: 'JetBrains Mono', monospace; font-size: 11.5px; letter-spacing: 0.04em; text-align: center;
}
.dm-hero {
position: relative; overflow: hidden; padding: 30px 32px; border-radius: 26px;
border: 1px solid rgba(224,48,60,0.3);
background: linear-gradient(120deg, rgba(28,12,16,0.95), rgba(12,6,8,0.9)),
radial-gradient(circle at 88% 20%, rgba(224,48,60,0.28), transparent 40%);
box-shadow: 0 22px 80px rgba(0,0,0,0.5);
}
.dm-hero h1 {
margin: 0; font-family: 'Anton', sans-serif; font-size: clamp(46px, 8vw, 92px);
line-height: 0.9; letter-spacing: 0.01em; text-transform: uppercase;
color: #ffc233;
text-shadow: 0 0 22px rgba(255, 150, 30, 0.6), 0 0 6px rgba(255, 200, 60, 0.5),
0 2px 4px rgba(0, 0, 0, 0.55);
}
.dm-hero p { margin: 12px 0 0; color: #ffd98a; font-size: 16px; max-width: 640px; }
.dm-status {
margin: 14px 0 4px; padding: 11px 14px; border-radius: 14px; font-size: 12px;
font-family: 'JetBrains Mono', monospace; border: 1px solid rgba(255,255,255,0.1);
background: rgba(10,6,8,0.7); color: var(--dm-muted);
}
.dm-status.ok { border-color: rgba(60,200,110,0.4); }
.dm-status.mock { border-color: rgba(242,176,48,0.45); color: var(--dm-gold); }
.dm-status.fail { border-color: rgba(224,48,60,0.5); color: #ff8a8a; }
.tape { display: grid; grid-template-columns: 1fr auto 1fr; gap: 14px; align-items: stretch; margin-top: 6px; }
.tape-card {
padding: 18px; border-radius: 20px; border: 1px solid rgba(255,255,255,0.09);
background: var(--dm-panel); box-shadow: 0 14px 44px rgba(0,0,0,0.34);
}
.tape-card.b { border-color: rgba(64,160,200,0.28); }
.tape-card .nm { font-family: 'Anton', sans-serif; font-size: 30px; text-transform: uppercase; color: var(--dm-text); }
.tape-card .tag { color: var(--dm-muted); font-size: 13px; margin: 6px 0 14px; }
.tape-card .mv { color: var(--dm-gold); font-size: 13px; font-family: 'JetBrains Mono', monospace; margin-bottom: 14px; }
.statrow { display:flex; align-items:center; gap:10px; margin:7px 0; font-size:11px; font-family:'JetBrains Mono',monospace; color:var(--dm-muted); }
.statrow .lab { width: 96px; text-transform: uppercase; }
.bar { flex:1; height:9px; border-radius:99px; background: rgba(255,255,255,0.08); overflow:hidden; }
.bar > span { display:block; height:100%; background: linear-gradient(90deg, var(--dm-gold), var(--dm-red)); }
.vs { display:flex; align-items:center; font-family:'Anton',sans-serif; font-size:40px; color:var(--dm-red); }
.winner-banner {
margin-top: 4px; padding: 22px; border-radius: 22px; text-align: center;
border: 1px solid rgba(242,176,48,0.5);
background: radial-gradient(circle at 50% 0%, rgba(242,176,48,0.22), transparent 60%), var(--dm-panel);
}
.winner-banner .w { font-family:'Anton',sans-serif; font-size: 56px; text-transform: uppercase;
background: linear-gradient(90deg,#ffd27a,var(--dm-red)); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
.winner-banner .r { color: var(--dm-muted); font-size: 14px; margin-top: 6px; }
button.primary, .dm-primary button {
border: 0 !important; border-radius: 999px !important;
background: linear-gradient(135deg, #ffd27a, var(--dm-red) 55%, #a01820) !important;
color: #1a0608 !important; font-family:'Anton',sans-serif !important; letter-spacing:0.06em !important;
font-size: 18px !important; text-transform: uppercase !important;
box-shadow: 0 14px 40px rgba(224,48,60,0.32) !important;
}
.dm-commentary { padding: 16px 18px; border-radius: 18px; border: 1px solid rgba(242,176,48,0.28);
background: var(--dm-panel) !important; text-shadow: 0 1px 2px rgba(0,0,0,0.6); }
/* Force fire-yellow commentary text so it stays legible regardless of the
visitor's light/dark browser theme (Gradio markdown otherwise inherits a
theme color that can vanish on a light background). */
.dm-commentary, .dm-commentary * { color: #ffc233 !important; }
.animate-status {
margin: 14px 0 0; padding: 16px 18px; border-radius: 16px; font-size: 14px; line-height: 1.55;
}
.animate-status.loading {
border: 1px solid rgba(242,176,48,0.45); background: rgba(242,176,48,0.08); color: #ffd98a;
animation: dm-pulse 1.6s ease-in-out infinite;
}
.animate-status.fail {
border: 1px solid rgba(224,48,60,0.5); background: rgba(224,48,60,0.12); color: #ffb0b0;
}
@keyframes dm-pulse {
0%, 100% { opacity: 0.72; }
50% { opacity: 1; }
}
label, .block label { color: var(--dm-muted) !important; font-family:'JetBrains Mono',monospace !important;
font-size: 11px !important; letter-spacing: 0.07em !important; text-transform: uppercase !important; }
/* ───────── de-Gradio: hide framework chrome, theme the native controls so the
Space stops looking like a Gradio template (Off-Brand) ───────── */
footer, .gradio-container footer { display: none !important; }
.gradio-container { border: none !important; box-shadow: none !important; }
/* Let the page background show through default component panels */
.block, .form, .gr-group, .gr-box { background: transparent !important; border: none !important; box-shadow: none !important; }
/* Tabs → claymation chips, not default Gradio tabs */
.tab-nav, .tabs > .tab-nav { border-bottom: none !important; gap: 8px !important; margin-bottom: 14px !important; }
.tab-nav button {
background: rgba(10,6,8,0.6) !important; color: var(--dm-muted) !important;
border: 1px solid rgba(255,255,255,0.08) !important; border-radius: 12px !important;
font-family: 'Anton', sans-serif !important; letter-spacing: 0.05em !important;
text-transform: uppercase !important; padding: 8px 16px !important;
}
.tab-nav button.selected {
color: #1a0608 !important; border-color: transparent !important;
background: linear-gradient(135deg, var(--dm-gold), var(--dm-red)) !important;
}
/* Native inputs → dark and on-theme */
input[type=text], textarea, select, .gr-input, .wrap input, .multiselect input {
background: rgba(10,6,8,0.66) !important; color: var(--dm-text) !important;
border: 1px solid rgba(255,255,255,0.12) !important; border-radius: 12px !important;
}
input::placeholder, textarea::placeholder { color: rgba(176,144,136,0.6) !important; }
/* Upload dropzones → dashed gold, matching the disclaimer band */
.image-container, [data-testid="image"], .gr-image, .upload-container {
border: 1px dashed rgba(242,176,48,0.4) !important; border-radius: 16px !important;
background: rgba(10,6,8,0.4) !important;
}
/* Secondary / download buttons → de-orange */
button.secondary, .secondary > button {
background: rgba(255,255,255,0.06) !important; color: var(--dm-text) !important;
border: 1px solid rgba(255,255,255,0.14) !important; border-radius: 999px !important;
font-family: 'JetBrains Mono', monospace !important; text-transform: uppercase !important;
letter-spacing: 0.06em !important; font-size: 12px !important;
}
/* Kill the orange Gradio accent + focus ring */
.gradio-container { --color-accent: var(--dm-gold) !important; --color-accent-soft: rgba(242,176,48,0.2) !important; }
:focus-visible { outline: 1px solid var(--dm-gold) !important; box-shadow: none !important; }
/* Fighter uploads: identical boxes regardless of the photo's own aspect ratio */
.dm-fighter, .dm-fighter .image-container, .dm-fighter [data-testid="image"] {
height: 400px !important; border-radius: 16px !important; overflow: hidden !important;
}
.dm-fighter img { height: 100% !important; width: 100% !important; object-fit: cover !important; }
"""
def _hero() -> str:
return (" Upload two fighters. Our AI ring director books the brawl, "
"renders the claymation reel, and crowns a winner. Then hit "
"Animate for the full fight.Celebrity
"
"
Deathmatch