"""Gradio app surface for Midnight Static.""" from __future__ import annotations import html import json from pathlib import Path from .fonts import INLINE_FONTS_CSS from .pipeline import run_fixture_pipeline from .schema import Genre from .showcases import default_showcase, load_showcase, showcase_labels STATIONS = { Genre.NOIR: {"call": "KNOX", "frequency": "87.9", "label": "Noir", "left": 8}, Genre.WEIRD: {"call": "WEIRD", "frequency": "91.5", "label": "Weird", "left": 25}, Genre.WESTERN: {"call": "GOLD", "frequency": "94.7", "label": "Western", "left": 42}, Genre.ROMANCE: {"call": "HEART", "frequency": "98.3", "label": "Romance", "left": 58}, Genre.COMEDY: {"call": "LAFF", "frequency": "101.1", "label": "Comedy", "left": 75}, Genre.HINDI_MELODRAMA: { "call": "RAAT", "frequency": "104.7", "label": "Hindi melodrama", "left": 92, }, } GENRE_CHOICES = [(station["label"], genre.value) for genre, station in STATIONS.items()] PREMISE_PLACEHOLDER = "a train conductor hears tomorrow's crash over the radio" ON_AIR_STAGE_TEXT = "87.9 WRITING ... 91.5 CASTING ... 94.7 FOLEY ... 98.3 SCORING ... 101.1 ON AIR" RADIO_CSS = """ /* Fonts are self-hosted (inlined as data: URIs) via INLINE_FONTS_CSS — no runtime network fetch, keeping the off-the-grid claim clean. */ :root { --ms-bg: #0c0907; --ms-wood: #4a2f1d; --ms-wood-light: #6f4527; --ms-wood-dark: #200e06; --ms-brass: #c9a86a; --ms-brass-light: #f3d495; --ms-brass-dark: #8f6f35; --ms-cream: #f3e9d2; --ms-amber: #ffb84d; --ms-amber-hot: #ff7e2e; --ms-red: #b5341f; --ms-ink: #21170f; --ms-font-deco: "Limelight", "Georgia", serif; --ms-font-ident: "Oswald", "Helvetica Neue", sans-serif; --ms-font-body: "EB Garamond", Georgia, "Times New Roman", serif; --ms-font-script: "Special Elite", "Courier New", Courier, monospace; } body, .gradio-container { background: radial-gradient(circle at 50% 8%, rgba(255, 184, 77, 0.18), transparent 34rem), radial-gradient(ellipse at 50% 120%, rgba(201, 168, 106, 0.10), transparent 46rem), radial-gradient(circle at 50% 50%, #15100b 0%, #0c0907 62%, #060403 100%), #0c0907 !important; color: var(--ms-cream); font-family: var(--ms-font-body); } /* Soft vignette to pull focus toward the radio. */ body::before { content: ""; position: fixed; inset: 0; z-index: 1; pointer-events: none; background: radial-gradient(ellipse at 50% 38%, transparent 38%, rgba(0, 0, 0, 0.55) 100%); animation: ms-flicker 6s ease-in-out infinite; } /* Film-grain overlay for analog texture. */ body::after { content: ""; position: fixed; inset: -50%; z-index: 9998; pointer-events: none; opacity: 0.05; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); animation: ms-grain 600ms steps(4) infinite; } .gradio-container > * { position: relative; z-index: 2; } .gradio-container { max-width: 880px !important; margin: 0 auto !important; padding: 24px 16px 40px !important; } /* ---- Tame Gradio's default light chrome into the walnut room ---- */ gradio-app, .gradio-container, .gradio-container .main, .gradio-container .wrap, .gradio-container .contain { background: transparent !important; } .gradio-container { --body-background-fill: transparent; --background-fill-primary: transparent; --background-fill-secondary: transparent; --block-background-fill: rgba(18, 12, 8, 0.60); --block-border-color: rgba(201, 168, 106, 0.22); --block-label-background-fill: transparent; --block-label-text-color: #e9d9b6; --block-title-text-color: #e9d9b6; --block-info-text-color: rgba(243, 233, 210, 0.62); --body-text-color: var(--ms-cream); --body-text-color-subdued: rgba(243, 233, 210, 0.6); --border-color-primary: rgba(201, 168, 106, 0.28); --border-color-accent: rgba(201, 168, 106, 0.45); --color-accent-soft: rgba(255, 184, 77, 0.14); --input-background-fill: #f3e9d2; --input-background-fill-focus: #fffaf0; --input-border-color: rgba(143, 111, 53, 0.55); --input-border-color-focus: var(--ms-amber); --input-text-color: #21170f; --input-placeholder-color: rgba(88, 52, 24, 0.66); --checkbox-background-color: #f3e9d2; --checkbox-background-color-selected: var(--ms-red); --checkbox-border-color: rgba(143, 111, 53, 0.6); --checkbox-border-color-selected: var(--ms-red); --checkbox-label-background-fill: rgba(243, 233, 210, 0.06); --checkbox-label-background-fill-selected: rgba(255, 184, 77, 0.18); --checkbox-label-background-fill-hover: rgba(255, 184, 77, 0.1); --checkbox-label-text-color: var(--ms-cream); --checkbox-label-border-color: rgba(201, 168, 106, 0.3); --neutral-950: #0c0907; } /* Remove leftover white panels behind grouped blocks. */ .gradio-container .gap, .gradio-container .form, .gradio-container .panel { background: transparent !important; } /* Hide the multipage navbar on the styled radio (this CSS only loads here); navigation to the plain feed is offered via the footer link instead. */ .nav-holder { display: none !important; } .ms-stage { display: grid; place-items: center; margin: 0 auto 22px; } .ms-radio { width: min(780px, 100%); min-height: 440px; border-radius: 42px 42px 26px 26px; position: relative; overflow: hidden; padding: 30px; background: linear-gradient(90deg, rgba(255,255,255,0.06), transparent 16%, rgba(0,0,0,0.22) 54%, rgba(255,255,255,0.05)), repeating-linear-gradient(86deg, rgba(255,255,255,0.03) 0 2px, transparent 2px 7px, rgba(0,0,0,0.06) 7px 9px), radial-gradient(ellipse at 28% 18%, rgba(255, 220, 160, 0.16), transparent 50%), linear-gradient(135deg, var(--ms-wood-light), var(--ms-wood) 44%, var(--ms-wood-dark)); box-shadow: inset 0 0 0 3px rgba(255, 225, 160, 0.10), inset 0 2px 1px rgba(255, 235, 190, 0.18), inset 0 -20px 34px rgba(0,0,0,0.40), 0 2px 0 rgba(255,235,190,0.06), 0 40px 90px rgba(0,0,0,0.62); } .ms-radio::before { content: ""; position: absolute; inset: 15px; border: 2px solid rgba(201, 168, 106, 0.46); border-radius: 30px 30px 18px 18px; box-shadow: inset 0 0 22px rgba(0,0,0,0.28), 0 0 1px rgba(255,225,160,0.4); pointer-events: none; } /* Brass cabinet screws. */ .ms-screw { position: absolute; width: 11px; height: 11px; border-radius: 999px; background: radial-gradient(circle at 38% 32%, var(--ms-brass-light), var(--ms-brass) 45%, var(--ms-brass-dark) 90%); box-shadow: inset 0 0 2px rgba(0,0,0,0.5), 0 1px 2px rgba(0,0,0,0.55); z-index: 3; } .ms-screw::after { content: ""; position: absolute; inset: 3px; border-top: 1.4px solid rgba(60, 40, 16, 0.7); transform: rotate(34deg); } .ms-screw.tl { top: 22px; left: 22px; } .ms-screw.tr { top: 22px; right: 22px; } .ms-screw.bl { bottom: 22px; left: 22px; } .ms-screw.br { bottom: 22px; right: 22px; } .ms-brand { display: flex; justify-content: space-between; align-items: center; gap: 16px; margin-bottom: 24px; padding: 0 8px; } .ms-brand-name { font-family: var(--ms-font-deco); color: var(--ms-brass-light); letter-spacing: 0.14em; text-transform: uppercase; font-size: clamp(1.3rem, 3vw, 2.15rem); text-shadow: 0 1px 0 rgba(60, 38, 14, 0.9), 0 0 18px rgba(255, 184, 77, 0.32); } .ms-onair { display: inline-flex; align-items: center; gap: 9px; font-family: var(--ms-font-ident); font-weight: 600; letter-spacing: 0.22em; font-size: 0.72rem; color: #ffcaa0; padding: 5px 12px 5px 11px; border: 1px solid rgba(181, 52, 31, 0.55); border-radius: 4px; background: linear-gradient(180deg, rgba(60, 14, 8, 0.6), rgba(20, 6, 4, 0.5)); box-shadow: inset 0 0 10px rgba(0,0,0,0.5); } .ms-lamp { width: 12px; height: 12px; border-radius: 999px; background: radial-gradient(circle at 35% 30%, #ffe3b0, var(--ms-amber) 55%, var(--ms-amber-hot)); box-shadow: 0 0 12px var(--ms-amber), 0 0 26px rgba(255,184,77,0.55); animation: ms-neon 3.2s ease-in-out infinite; } .ms-radio.is-scanning .ms-lamp { animation: ms-lamp-pulse 700ms ease-in-out infinite alternate; } .ms-face { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(250px, 0.9fr); gap: 28px; align-items: stretch; } .ms-speaker { position: relative; min-height: 252px; border-radius: 18px; border: 2px solid rgba(201, 168, 106, 0.5); overflow: hidden; background: radial-gradient(circle at 50% 46%, rgba(255, 150, 60, 0.14), transparent 58%), repeating-radial-gradient(circle at 50% 46%, rgba(35, 24, 16, 0.0) 0 6px, rgba(0,0,0,0.22) 6px 7px), repeating-linear-gradient(90deg, rgba(35, 24, 16, 0.9) 0 7px, rgba(243, 233, 210, 0.13) 7px 10px), #20140d; box-shadow: inset 0 0 40px rgba(0,0,0,0.72), inset 0 0 0 1px rgba(0,0,0,0.4); } /* Warm cone glow behind the grille; intensifies while broadcasting. */ .ms-speaker::after { content: ""; position: absolute; left: 50%; top: 46%; width: 64%; aspect-ratio: 1; transform: translate(-50%, -50%); border-radius: 999px; background: radial-gradient(circle, rgba(255, 138, 46, 0.28), rgba(255, 138, 46, 0.07) 42%, transparent 68%); filter: blur(2px); animation: ms-cone 5s ease-in-out infinite; } .ms-radio.is-scanning .ms-speaker::after { animation: ms-cone-live 900ms ease-in-out infinite; } .ms-panel { display: grid; gap: 18px; } .ms-dial { min-height: 118px; border-radius: 14px; border: 2px solid rgba(201, 168, 106, 0.85); background: linear-gradient(180deg, rgba(255,255,255,0.45), transparent 26%), repeating-linear-gradient(90deg, rgba(90, 58, 24, 0.30) 0 1px, transparent 1px 7.6%), radial-gradient(ellipse at 50% 40%, #fff3d6, var(--ms-cream) 40%, #d9c187 100%); color: var(--ms-ink); box-shadow: inset 0 0 26px rgba(55, 35, 10, 0.32), inset 0 0 0 1px rgba(255,255,255,0.4), 0 0 26px rgba(255,184,77,0.20); position: relative; overflow: hidden; padding: 16px 18px 14px; } /* Curved glass reflection across the tuning window. */ .ms-dial::before { content: ""; position: absolute; left: -20%; top: -60%; width: 60%; height: 200%; transform: rotate(18deg); background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent); pointer-events: none; } .ms-frequency { display: flex; justify-content: space-between; gap: 8px; font-family: var(--ms-font-ident); font-weight: 500; font-size: 0.76rem; letter-spacing: 0.08em; position: relative; z-index: 1; } .ms-frequency span { color: #3a2410; opacity: 0.92; } .ms-frequency span.is-tuned { color: #8a2114; font-weight: 800; opacity: 1; } .ms-needle { position: absolute; left: var(--ms-needle-left, 25%); top: 10px; bottom: 10px; width: 2px; z-index: 2; background: linear-gradient(180deg, var(--ms-red), #e2502f, var(--ms-red)); box-shadow: 0 0 10px rgba(226, 80, 47, 0.7), 0 0 22px rgba(226, 80, 47, 0.4); transition: left 360ms cubic-bezier(.18, .89, .32, 1.28); } .ms-needle::before { content: ""; position: absolute; top: -4px; left: 50%; transform: translateX(-50%); width: 9px; height: 9px; border-radius: 999px; background: radial-gradient(circle at 38% 32%, #ff8a5a, var(--ms-red)); box-shadow: 0 0 10px rgba(226, 80, 47, 0.8); } .ms-radio.is-scanning .ms-needle { animation: ms-needle-scan 1.35s ease-in-out infinite alternate; } .ms-station { position: absolute; left: 18px; right: 18px; bottom: 13px; z-index: 1; color: #4a2c12; font-family: var(--ms-font-ident); font-weight: 600; font-size: 0.82rem; letter-spacing: 0.2em; text-align: center; } .ms-station strong { color: var(--ms-red); font-weight: 700; text-shadow: 0 0 10px rgba(226, 80, 47, 0.35); } .ms-scanner { position: absolute; left: 18px; right: 18px; bottom: 13px; z-index: 1; color: var(--ms-red); font-family: var(--ms-font-ident); font-weight: 600; letter-spacing: 0.14em; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .ms-radio.is-scanning .ms-frequency span { animation: ms-frequency-glow 1.1s ease-in-out infinite; } .ms-radio.is-scanning .ms-frequency span:nth-child(2) { animation-delay: 140ms; } .ms-radio.is-scanning .ms-frequency span:nth-child(3) { animation-delay: 280ms; } .ms-radio.is-scanning .ms-frequency span:nth-child(4) { animation-delay: 420ms; } .ms-radio.is-scanning .ms-frequency span:nth-child(5) { animation-delay: 560ms; } .ms-radio.is-scanning .ms-frequency span:nth-child(6) { animation-delay: 700ms; } .ms-knobs { display: flex; justify-content: space-between; gap: 18px; } .ms-knob { position: relative; width: 84px; aspect-ratio: 1; border-radius: 999px; background: radial-gradient(circle at 35% 28%, var(--ms-brass-light), var(--ms-brass) 38%, var(--ms-brass-dark) 72%, #6a5024 100%); box-shadow: inset -9px -11px 18px rgba(0,0,0,0.34), inset 6px 7px 12px rgba(255, 235, 190, 0.32), 0 12px 22px rgba(0,0,0,0.32); } .ms-knob::before { content: ""; position: absolute; inset: 18%; border-radius: 999px; background: repeating-conic-gradient(rgba(60,40,16,0.0) 0deg 8deg, rgba(60,40,16,0.32) 8deg 10deg); -webkit-mask: radial-gradient(circle, transparent 56%, #000 57%); mask: radial-gradient(circle, transparent 56%, #000 57%); } .ms-knob::after { content: ""; position: absolute; top: 13%; left: 50%; width: 3px; height: 26%; transform: translateX(-50%); border-radius: 2px; background: rgba(40, 26, 10, 0.8); } .ms-telegram { max-width: 780px; margin: 0 auto !important; border-radius: 10px; border: 1px solid rgba(201, 168, 106, 0.3) !important; background: radial-gradient(ellipse at 50% 0%, rgba(255, 184, 77, 0.06), transparent 60%), linear-gradient(180deg, rgba(40, 28, 19, 0.92), rgba(22, 15, 10, 0.92)) !important; padding: 20px !important; box-shadow: inset 0 0 0 1px rgba(255, 235, 190, 0.04), 0 18px 36px rgba(0,0,0,0.42); } .ms-telegram label, .ms-telegram .wrap label, .ms-telegram span { color: #f3e9d2 !important; } .ms-telegram textarea, .ms-telegram input, .ms-telegram select { background: #f3e9d2 !important; color: #21170f !important; border-color: rgba(201, 168, 106, 0.72) !important; } /* ---- Showcase "rerun" tuner: a clearly-themed dropdown ---- */ .ms-showcase { margin-bottom: 16px !important; } .ms-showcase label > span { font-family: var(--ms-font-ident) !important; letter-spacing: 0.18em !important; text-transform: uppercase !important; font-size: 0.72rem !important; color: var(--ms-brass-light) !important; } /* The collapsed field reads like a brass-framed station selector. */ .ms-showcase .wrap, .ms-showcase .secondary-wrap { background: linear-gradient(180deg, #f7efda, #e7d8b2) !important; border: 1px solid var(--ms-brass) !important; border-radius: 8px !important; box-shadow: inset 0 1px 2px rgba(255,255,255,0.6), inset 0 -3px 8px rgba(90,60,20,0.18) !important; } .ms-showcase input { background: transparent !important; color: #21170f !important; font-family: var(--ms-font-body) !important; font-weight: 600 !important; border: none !important; } /* Brass chevron so it's obviously a control. */ .ms-showcase .icon-wrap, .ms-showcase svg { color: var(--ms-brass-dark) !important; fill: var(--ms-brass-dark) !important; opacity: 1 !important; } /* The open options list: dark walnut so it sits on the cabinet, not a white slab. */ .ms-showcase ul.options, .ms-telegram ul.options { background: linear-gradient(180deg, rgba(34,23,15,0.98), rgba(22,15,10,0.98)) !important; border: 1px solid var(--ms-brass) !important; border-radius: 8px !important; box-shadow: 0 14px 28px rgba(0,0,0,0.5) !important; color: #f3e9d2 !important; z-index: 40 !important; } .ms-showcase ul.options li, .ms-telegram ul.options li { color: #f3e9d2 !important; background: transparent !important; } .ms-showcase ul.options li:hover, .ms-showcase ul.options li.selected, .ms-showcase ul.options li.active, .ms-telegram ul.options li:hover, .ms-telegram ul.options li.active { background: rgba(255, 184, 77, 0.18) !important; color: #fff3d8 !important; } /* Only the ON AIR button is the big brass lozenge. (Scoping to .primary keeps the audio recorder/player's own controls — play, skip, trim, clear — as their compact default icon buttons instead of bloated brass pills.) */ .ms-telegram button.primary { position: relative !important; border-radius: 999px !important; background: radial-gradient(circle at 50% 30%, var(--ms-brass-light), var(--ms-brass) 42%, var(--ms-brass-dark) 88%) !important; color: #2a1709 !important; border: 1px solid #f3d99a !important; font-family: var(--ms-font-ident) !important; font-weight: 700 !important; letter-spacing: 0.28em !important; text-transform: uppercase !important; padding: 12px 22px 12px 38px !important; box-shadow: inset 0 2px 2px rgba(255, 245, 215, 0.6), inset 0 -5px 10px rgba(60, 40, 14, 0.4), 0 8px 18px rgba(0,0,0,0.4) !important; transition: transform 120ms ease, box-shadow 120ms ease, filter 120ms ease !important; } /* Audio recorder + player controls: small, neutral icon buttons that read on the slip — never brass lozenges. */ .ms-callin button:not(.primary), .ms-output button:not(.primary) { border-radius: 7px !important; letter-spacing: normal !important; text-transform: none !important; font-family: var(--ms-font-body) !important; font-weight: 500 !important; padding: 4px 9px !important; background: rgba(243, 233, 210, 0.9) !important; color: #2a1709 !important; border: 1px solid rgba(201, 168, 106, 0.55) !important; box-shadow: none !important; } .ms-callin button:not(.primary):hover, .ms-output button:not(.primary):hover { background: #fffaf0 !important; border-color: var(--ms-brass) !important; } /* Red transmit lamp embedded in the ON AIR button. */ .ms-telegram button.primary::before { content: ""; position: absolute; left: 16px; top: 50%; transform: translateY(-50%); width: 11px; height: 11px; border-radius: 999px; background: radial-gradient(circle at 36% 30%, #ff9b6e, var(--ms-red) 60%, #6e1a0e); box-shadow: 0 0 10px rgba(226, 80, 47, 0.85), inset 0 0 3px rgba(0,0,0,0.5); animation: ms-neon 2.6s ease-in-out infinite; } .ms-telegram button.primary:hover { filter: brightness(1.06); transform: translateY(-1px); box-shadow: inset 0 2px 2px rgba(255, 245, 215, 0.7), inset 0 -5px 10px rgba(60, 40, 14, 0.4), 0 12px 24px rgba(0,0,0,0.46), 0 0 22px rgba(255, 184, 77, 0.3) !important; } .ms-telegram button.primary:active { transform: translateY(1px); box-shadow: inset 0 3px 8px rgba(40, 24, 8, 0.55) !important; } .ms-status { border-radius: 5px; border: 1px solid rgba(255, 184, 77, 0.34); background: repeating-linear-gradient(0deg, rgba(255,255,255,0.02) 0 1px, transparent 1px 3px), linear-gradient(180deg, rgba(18, 12, 8, 0.72), rgba(8, 6, 4, 0.7)); color: #ffd99a; font-family: var(--ms-font-ident); font-weight: 500; font-size: 0.8rem; letter-spacing: 0.22em; margin-top: 12px; min-height: 44px; padding: 13px 15px; text-transform: uppercase; text-shadow: 0 0 12px rgba(255, 184, 77, 0.4); } .ms-status.is-live { border-color: rgba(255, 184, 77, 0.6); box-shadow: inset 0 0 18px rgba(255, 184, 77, 0.12), 0 0 22px rgba(255, 184, 77, 0.22); animation: ms-flicker 4s ease-in-out infinite; } .ms-slip { position: relative; overflow: hidden; border-radius: 5px !important; border: 1px solid rgba(111, 72, 30, 0.48) !important; background: linear-gradient(90deg, rgba(125, 83, 38, 0.08) 0 1px, transparent 1px 100%), repeating-linear-gradient(0deg, transparent 0 29px, rgba(89, 55, 24, 0.16) 29px 30px), #f3e9d2 !important; padding: 14px 16px 16px !important; box-shadow: inset 0 0 24px rgba(72, 40, 16, 0.12), 0 10px 22px rgba(0,0,0,0.18) !important; } .ms-slip::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: radial-gradient(circle at 14% 24%, rgba(83, 51, 23, 0.08), transparent 9rem), linear-gradient(90deg, transparent 0 72%, rgba(95, 58, 25, 0.08)); mix-blend-mode: multiply; } .ms-slip-head { position: relative; z-index: 1; display: grid; grid-template-columns: 1fr auto; gap: 12px; align-items: end; color: #6b4220; font-family: var(--ms-font-script); font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1.5px dashed rgba(111, 72, 30, 0.4); } .ms-slip-head strong { display: block; color: var(--ms-red); font-size: 1rem; letter-spacing: 0.14em; } .ms-slip-head span:last-child { text-align: right; } .ms-slip .form, .ms-slip .wrap, .ms-slip label, .ms-slip textarea { position: relative; z-index: 1; } .ms-slip label > span { color: #583418 !important; letter-spacing: 0.08em; text-transform: uppercase; } .ms-slip textarea { min-height: 86px !important; resize: vertical !important; border: 0 !important; border-radius: 0 !important; background: transparent !important; color: #21170f !important; box-shadow: none !important; font-family: var(--ms-font-script) !important; font-size: 0.98rem !important; line-height: 1.85 !important; } .ms-slip textarea::placeholder { color: rgba(88, 52, 24, 0.66) !important; } .ms-genre label { color: #f3e9d2 !important; } /* ---- Radio option pills (Genre / Render mode / Writer): make the selected station unmistakable — a lit brass pill with dark ink. ---- */ .ms-telegram .wrap label { border: 1px solid rgba(201, 168, 106, 0.38) !important; border-radius: 999px !important; background: rgba(0, 0, 0, 0.28) !important; padding: 6px 14px !important; transition: background 120ms ease, box-shadow 120ms ease, border-color 120ms ease !important; } .ms-telegram .wrap label:hover { border-color: var(--ms-brass) !important; background: rgba(255, 184, 77, 0.12) !important; } .ms-telegram .wrap label.selected, .ms-telegram .wrap label:has(input:checked) { background: radial-gradient(circle at 50% 28%, var(--ms-brass-light), var(--ms-brass) 62%, var(--ms-brass-dark) 100%) !important; border-color: var(--ms-brass-light) !important; box-shadow: inset 0 1px 1px rgba(255, 245, 215, 0.6), 0 0 12px rgba(255, 184, 77, 0.45) !important; } .ms-telegram .wrap label.selected span, .ms-telegram .wrap label:has(input:checked) span { color: #2a1709 !important; font-weight: 700 !important; } /* Recolor the native radio dot so the checked state reads on the brass pill. */ .ms-telegram .wrap label input[type="radio"] { accent-color: var(--ms-red) !important; } .ms-output { max-width: 780px; margin: 18px auto 0 !important; border-radius: 10px; border: 1px solid rgba(201, 168, 106, 0.24) !important; background: linear-gradient(180deg, rgba(20, 14, 9, 0.78), rgba(10, 7, 5, 0.78)) !important; padding: 18px !important; } .ms-script { position: relative; overflow: hidden; border-radius: 5px; border: 1px solid rgba(111, 72, 30, 0.48); background: repeating-linear-gradient(0deg, transparent 0 27px, rgba(89, 55, 24, 0.12) 27px 28px), #f3e9d2; color: #21170f; padding: 22px 24px 24px; font-family: var(--ms-font-script); box-shadow: inset 0 0 24px rgba(72, 40, 16, 0.12), 0 10px 22px rgba(0,0,0,0.22); } /* Red "ON AIR" cue stamp in the script corner. */ .ms-script::after { content: "ON AIR"; position: absolute; top: 18px; right: 20px; transform: rotate(7deg); background: rgba(181, 52, 31, 0.12); color: rgba(181, 52, 31, 0.66); border: 2px solid rgba(181, 52, 31, 0.5); border-radius: 4px; font-family: var(--ms-font-ident); font-weight: 700; letter-spacing: 0.22em; font-size: 0.72rem; padding: 5px 12px; pointer-events: none; } .ms-script-head { border-bottom: 2px solid rgba(89, 55, 24, 0.42); padding-bottom: 12px; margin-bottom: 14px; } .ms-script-title { margin: 0; font-family: var(--ms-font-deco); font-size: clamp(1.25rem, 2.8vw, 1.85rem); letter-spacing: 0.04em; color: #5a1d10; } .ms-script-logline { margin: 8px 0 0; font-family: var(--ms-font-body); font-style: italic; font-size: 1.05rem; color: #4a3320; } .ms-script-meta { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; font-family: var(--ms-font-ident); font-size: 0.68rem; letter-spacing: 0.14em; text-transform: uppercase; } .ms-script-tag { border: 1px solid rgba(111, 72, 30, 0.5); border-radius: 999px; padding: 2px 10px; color: #5a3a1c; } .ms-script-cast { display: grid; gap: 4px; margin-bottom: 16px; font-size: 0.84rem; } .ms-script-cast-row strong { color: #5a1d10; letter-spacing: 0.04em; } .ms-script-cast-row span { color: #6a4a2c; } .ms-scene-slug { margin: 14px 0 8px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: #5a3a1c; } .ms-scene-sfx { color: #8a5a2c; font-style: italic; margin: 4px 0 10px; font-size: 0.82rem; } .ms-line { display: grid; grid-template-columns: minmax(86px, 22%) 1fr; gap: 10px; padding: 3px 0; line-height: 1.5; } .ms-script, .ms-line-text, .ms-script-cast-row { color: #21170f; } .ms-line-who { text-align: right; font-weight: 700; color: #5a1d10; letter-spacing: 0.04em; } .ms-line-delivery { color: #9a6a34; font-style: italic; } .ms-script-empty { color: #6a4a2c; font-style: italic; } .ms-footer { max-width: 780px; margin: 16px auto 0; text-align: center; font-family: var(--ms-font-ident); font-size: 0.74rem; letter-spacing: 0.12em; text-transform: uppercase; color: rgba(243, 233, 210, 0.5); } .ms-footer a { color: var(--ms-brass); text-decoration: none; border-bottom: 1px dotted rgba(201, 168, 106, 0.5); } .ms-footer a:hover { color: var(--ms-amber); } @media (max-width: 760px) { .ms-line { grid-template-columns: 1fr; gap: 1px; padding: 6px 0; } .ms-line-who { text-align: left; } } @media (max-width: 760px) { .gradio-container { padding: 14px 10px 28px !important; } .ms-radio { min-height: 0; padding: 20px; border-radius: 24px 24px 18px 18px; } .ms-brand { align-items: flex-start; flex-direction: column; letter-spacing: 0.12em; } .ms-face { grid-template-columns: 1fr; } .ms-speaker { min-height: 170px; } .ms-scanner, .ms-station { font-size: 0.72rem; letter-spacing: 0.08em; } } @keyframes ms-lamp-pulse { from { opacity: 0.62; box-shadow: 0 0 10px var(--ms-amber), 0 0 18px rgba(255,184,77,0.35); } to { opacity: 1; box-shadow: 0 0 18px var(--ms-amber), 0 0 44px rgba(255,184,77,0.68); } } @keyframes ms-needle-scan { from { left: 8%; } to { left: 92%; } } @keyframes ms-frequency-glow { 0%, 100% { color: #583418; opacity: 0.55; } 50% { color: #9f2f1c; opacity: 1; } } @keyframes ms-flicker { 0%, 100% { opacity: 1; } 48% { opacity: 1; } 50% { opacity: 0.94; } 52% { opacity: 1; } 78% { opacity: 0.97; } } @keyframes ms-grain { 0% { transform: translate(0, 0); } 25% { transform: translate(-6%, 4%); } 50% { transform: translate(4%, -6%); } 75% { transform: translate(-4%, -3%); } 100% { transform: translate(5%, 5%); } } @keyframes ms-neon { 0%, 100% { opacity: 1; box-shadow: 0 0 12px var(--ms-amber), 0 0 26px rgba(255,184,77,0.55); } 43% { opacity: 1; } 45% { opacity: 0.55; box-shadow: 0 0 6px var(--ms-amber), 0 0 12px rgba(255,184,77,0.3); } 47% { opacity: 1; } 92% { opacity: 1; } 94% { opacity: 0.7; } } @keyframes ms-cone { 0%, 100% { opacity: 0.5; transform: translate(-50%, -50%) scale(1); } 50% { opacity: 0.78; transform: translate(-50%, -50%) scale(1.06); } } @keyframes ms-cone-live { 0%, 100% { opacity: 0.7; transform: translate(-50%, -50%) scale(0.96); } 50% { opacity: 1; transform: translate(-50%, -50%) scale(1.12); } } /* ============================================================ LEGIBILITY HARDENING The dial glass and the script paper are LIGHT insets inside the dark walnut room. Gradio paints HTML text with --body-text-color, which we set to cream for the dark chrome — that cream leaked onto these light surfaces and made the frequencies, station line, and script text invisible. Force ink on those surfaces with !important (Gradio's variable rule wins otherwise), then re-assert the few accent colors so they survive. ============================================================ */ .ms-dial, .ms-dial div, .ms-dial span, .ms-script, .ms-script p, .ms-script div, .ms-script span, .ms-script strong { color: #21170f !important; } /* Accent colors re-asserted after the blanket ink rule above. */ .ms-frequency span.is-tuned { color: #8a2114 !important; } .ms-station strong { color: var(--ms-red) !important; } .ms-scanner { color: var(--ms-red) !important; } .ms-script-title, .ms-line-who, .ms-script-cast-row strong { color: #5a1d10 !important; } .ms-script-logline { color: #4a3320 !important; } .ms-scene-sfx { color: #8a5a2c !important; } .ms-line-delivery { color: #9a6a34 !important; } """ def _radio_shell_html( genre: Genre | str = Genre.WEIRD, state: str = "idle", scanner_text: str = ON_AIR_STAGE_TEXT, ) -> str: tuned = _coerce_genre(genre) current = STATIONS[tuned] is_scanning = state == "scanning" frequencies = "".join( f'' f'{station["frequency"]}' for station_genre, station in STATIONS.items() ) station_line = " · ".join( f'{station["call"]}' if station_genre == tuned else station["call"] for station_genre, station in STATIONS.items() ) dial_message = ( f'
No script on air yet.
Tune in for the next broadcast.
' cast_section = f'{logline}