"""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;
background: linear-gradient(90deg, #ffd27a, var(--dm-red) 60%, #ff5060);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
.dm-hero p { margin: 12px 0 0; color: var(--dm-muted); 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(255,255,255,0.08); background: var(--dm-panel); }
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; }
"""
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