/* ============================================================ FISHBOWL — aquatic CRT lab readout monospace, phosphor, glassy, scientific Ported from ui/raw/styles.css + ui/raw/show.css for injection into a gr.Blocks app via `css=`. Gradio wraps everything in `.gradio-container`, so the theater markup is wrapped in a `.fishbowl` root and the component rules are scoped under it to survive Gradio's own cascade. The CSS custom properties live on `:root` so they remain available everywhere (incl. shadow-free gr.HTML islands), and the CRT overlay layers are fixed full-bleed so they sit behind / above the app shell regardless of scope. ============================================================ */ :root { /* aquatic darks */ --bg-0: #05121a; /* deepest tank */ --bg-1: #07181f; --bg-2: #0a222b; /* panels sit on a near-opaque dark field so text contrast is stable no matter what the CRT radial-gradient is doing behind the glass (legibility > glassiness). */ --panel: rgba(8, 26, 33, 0.84); --panel-solid: #0c2730; --glass: rgba(120, 222, 214, 0.06); /* phosphor inks — one hue family, calibrated up the lightness axis so every tier clears a readable contrast on the tank (the old ramp bottomed out invisibly at ~1.9:1; this ramp keeps the teal vibe and stays legible). */ --ink: #d2f7f0; /* primary copy ~13:1 */ --ink-mid: #9fdcd2; /* secondary copy ~9:1 */ --ink-dim: #7cb6ad; /* labels / eyebrows ~6:1 */ --ink-faint: #62a39a; /* faint tags / notes ~4.5:1 */ /* lines + grid — a touch stronger so panels read as crisp instruments */ --line: rgba(120, 222, 214, 0.22); --line-soft: rgba(120, 222, 214, 0.12); --line-hot: rgba(120, 222, 214, 0.46); /* accents — shared chroma/lightness, varied hue */ --cyan: #4fe6d2; --teal: #2bc4b4; --blue: #5bb6ff; --violet: #9b8cff; /* signal colors (alarms / states) */ --amber: #ffcf6b; /* tension */ --coral: #ff8f7d; /* lie / danger */ --lime: #9be86b; /* truth / win */ --glow: 0 0 12px rgba(79, 230, 210, 0.35); --glow-hot: 0 0 18px rgba(79, 230, 210, 0.6); --font-display: "Martian Mono", ui-monospace, monospace; --font-body: "IBM Plex Mono", ui-monospace, monospace; --r: 4px; --r-lg: 8px; } /* ============================================================ Gradio container integration — paint the app phosphor-dark, kill the default footer, and let the CRT layers show through. ============================================================ */ .gradio-container { background: var(--bg-0) !important; color: var(--ink) !important; font-family: var(--font-body) !important; max-width: 100% !important; } .gradio-container ::selection { background: rgba(79, 230, 210, 0.28); color: #fff; } /* hide the default Gradio footer ("Use via API", settings, etc.) */ footer { display: none !important; } .gradio-container footer { display: none !important; } /* ------------------------------------------------------------ Gradio-chrome readability — the Lab is built from native Gradio widgets (Radio / Dataframe / Dropdown / Slider / Number). The theme tokens colour them, but Gradio dims disabled fields with opacity, which sinks the phosphor text below readable. Lift the value text to full ink and stop disabled controls from fading the copy (we keep the control visibly "locked" via its border, not by making its text unreadable). ------------------------------------------------------------ */ .gradio-container label, .gradio-container .gr-box label, .gradio-container span[data-testid="block-info"], .gradio-container .gr-check-radio label { color: var(--ink-mid) !important; } .gradio-container input, .gradio-container textarea, .gradio-container .gr-text-input, .gradio-container .wrap .single-select, .gradio-container table td, .gradio-container table th, .gradio-container .cell-wrap span { color: var(--ink) !important; } .gradio-container input::placeholder, .gradio-container textarea::placeholder { color: var(--ink-faint) !important; opacity: 1; } /* Dataframe (the Cast table, §03) ships white header cells + light row fills from Gradio's table palette. Repaint the whole grid dark phosphor. */ .gradio-container table th, .gradio-container table .header-cell, .gradio-container table thead, .gradio-container .table-wrap thead th { background: var(--bg-2) !important; color: var(--ink) !important; border-color: var(--line) !important; } .gradio-container table td, .gradio-container table tbody tr, .gradio-container .table-wrap td { background: rgba(5, 18, 26, 0.45) !important; color: var(--ink) !important; border-color: var(--line-soft) !important; } .gradio-container table tbody tr:nth-child(even) td { background: rgba(10, 34, 43, 0.5) !important; } .gradio-container table tbody tr:hover td { background: rgba(79, 230, 210, 0.07) !important; } .gradio-container .table-wrap, .gradio-container [data-testid="dataframe"] { background: transparent !important; } /* the modern dataframe paints rows as virtual divs (row-odd/row-even), not */ .gradio-container .virtual-row { background: rgba(5, 18, 26, 0.45) !important; } .gradio-container .virtual-row.row-odd { background: rgba(10, 34, 43, 0.5) !important; } /* secondary buttons ship a light fill ("Surprise me", "Drop it") — darken to a phosphor ghost button so they sit in the tank (primary stays cyan). */ .gradio-container button.secondary { background: var(--glass) !important; color: var(--ink) !important; border: 1px solid var(--line) !important; } .gradio-container button.secondary:hover { border-color: var(--line-hot) !important; color: #fff !important; } /* disabled fields: keep the text legible, signal "locked" via opacity on the chrome rather than washing out the value text itself. */ .gradio-container input:disabled, .gradio-container textarea:disabled, .gradio-container [disabled] input, .gradio-container [disabled] textarea { -webkit-text-fill-color: var(--ink); color: var(--ink) !important; opacity: 1 !important; } .gradio-container .gr-box.disabled, .gradio-container fieldset:disabled { opacity: 0.96 !important; } /* Gradio's Group/Form/Styler wrappers default to a light slate-700 fill, which the Scenario group (§01) and others show through as a washed band behind the phosphor text. Repaint the containers transparent so the tank shows through. */ .gradio-container .gr-group { background: rgba(7, 24, 31, 0.55) !important; border-color: var(--line) !important; } .gradio-container .styler, .gradio-container div.form { background: transparent !important; border-color: var(--line) !important; } /* Checkbox/radio OPTION chips ship a light slate fill (#e2e8f0 selected, #334155 border) from Gradio's default palette — bright-on-bright in the tank. Target by structure (label wrapping a radio/checkbox) rather than version- hashed classes or per-option testids, so every group is covered: the scenario radios (§01), the Tools checkboxes (§05), the Show layout/speed radios, and the single "Read their minds" checkbox. */ .gradio-container label:has(> input[type="radio"]), .gradio-container label:has(> input[type="checkbox"]) { background: rgba(5, 18, 26, 0.55) !important; border: 1px solid var(--line) !important; color: var(--ink-mid) !important; } .gradio-container label:has(> input[type="radio"]):hover, .gradio-container label:has(> input[type="checkbox"]):hover { border-color: var(--line-hot) !important; color: var(--ink) !important; } .gradio-container label:has(> input[type="radio"]:checked), .gradio-container label:has(> input[type="checkbox"]:checked) { background: rgba(79, 230, 210, 0.14) !important; border-color: var(--cyan) !important; color: var(--cyan) !important; } .gradio-container label:has(> input[type="radio"]) span, .gradio-container label:has(> input[type="checkbox"]) span { color: inherit !important; } /* the little box/dot itself: dark well, cyan when ticked, visible rim */ .gradio-container input[type="checkbox"], .gradio-container input[type="radio"] { background-color: rgba(3, 12, 18, 0.9) !important; border: 1px solid var(--ink-dim) !important; accent-color: var(--cyan); } .gradio-container input[type="checkbox"]:checked, .gradio-container input[type="radio"]:checked { background-color: var(--cyan) !important; border-color: var(--cyan) !important; } /* phosphor scrollbars (global — harmless outside the app) */ ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(79, 230, 210, 0.18); border-radius: 10px; border: 2px solid transparent; background-clip: padding-box; } ::-webkit-scrollbar-thumb:hover { background: rgba(79, 230, 210, 0.35); background-clip: padding-box; } /* ============================================================ CRT atmosphere — scanlines, vignette, flicker applied as fixed overlays so they never interfere w/ layout. The app shell (Unit 9) emits these overlay divs as raw HTML:
and wraps the live theater in
so the subtle phosphor flicker animation plays over the whole stage. These are intentionally NOT scoped under .fishbowl: they are position:fixed full-bleed layers that frame the entire viewport. ============================================================ */ .crt-bg { position: fixed; inset: 0; z-index: 0; pointer-events: none; background: radial-gradient(120% 90% at 50% -10%, rgba(43, 196, 180, 0.10), transparent 60%), radial-gradient(100% 80% at 50% 120%, rgba(91, 182, 255, 0.07), transparent 55%), var(--bg-0); } .crt-grid { position: fixed; inset: 0; z-index: 0; pointer-events: none; background-image: linear-gradient(var(--line-soft) 1px, transparent 1px), linear-gradient(90deg, var(--line-soft) 1px, transparent 1px); background-size: 44px 44px; mask-image: radial-gradient(120% 100% at 50% 40%, #000 35%, transparent 92%); opacity: 0.6; } .crt-scan { position: fixed; inset: 0; z-index: 60; pointer-events: none; background: repeating-linear-gradient( to bottom, rgba(0, 0, 0, 0) 0px, rgba(0, 0, 0, 0) 2px, rgba(0, 0, 0, 0.12) 3px, rgba(0, 0, 0, 0.12) 3px ); mix-blend-mode: multiply; opacity: 0.32; } .crt-vignette { position: fixed; inset: 0; z-index: 61; pointer-events: none; box-shadow: inset 0 0 180px rgba(0,0,0,0.55), inset 0 0 50px rgba(0,0,0,0.3); } @media (prefers-reduced-motion: no-preference) { .crt-flicker { animation: flicker 7s infinite steps(60); } } @keyframes flicker { 0%, 97%, 100% { opacity: 1; } 97.5% { opacity: 0.92; } 98% { opacity: 0.98; } 98.5% { opacity: 0.9; } } /* ============================================================ .fishbowl — scoped theater root. Everything the renderers emit lives inside this wrapper so the rules win over Gradio defaults. ============================================================ */ .fishbowl { position: relative; z-index: 1; font-family: var(--font-body); font-size: 14px; line-height: 1.5; color: var(--ink); -webkit-font-smoothing: antialiased; } .fishbowl * { box-sizing: border-box; } /* ============================================================ typography helpers ============================================================ */ .fishbowl .mono { font-family: var(--font-body); } .fishbowl .disp { font-family: var(--font-display); } .fishbowl .eyebrow { font-family: var(--font-display); font-size: 10px; letter-spacing: 0.26em; text-transform: uppercase; color: var(--ink-dim); font-weight: 600; } .fishbowl .glow { text-shadow: var(--glow); } .fishbowl .tnum { font-variant-numeric: tabular-nums; } .fishbowl h1, .fishbowl h2, .fishbowl h3, .fishbowl h4 { margin: 0; font-family: var(--font-display); font-weight: 700; } /* ============================================================ primitives ============================================================ */ .fishbowl .panel { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg); backdrop-filter: blur(8px); } .fishbowl .chip { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-display); font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; padding: 3px 8px; border-radius: 999px; border: 1px solid var(--line); color: var(--ink-mid); background: var(--glass); white-space: nowrap; } .fishbowl .chip .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; } .fishbowl .btn { font-family: var(--font-display); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 600; color: var(--ink); background: var(--glass); border: 1px solid var(--line); border-radius: var(--r); padding: 9px 16px; cursor: pointer; transition: all 0.16s ease; display: inline-flex; align-items: center; gap: 8px; } .fishbowl .btn:hover { border-color: var(--line-hot); color: #fff; box-shadow: var(--glow); } .fishbowl .btn:active { transform: translateY(1px); } .fishbowl .btn.primary { background: linear-gradient(180deg, rgba(79,230,210,0.22), rgba(43,196,180,0.12)); border-color: var(--cyan); color: var(--cyan); text-shadow: var(--glow); } .fishbowl .btn.primary:hover { background: linear-gradient(180deg, rgba(79,230,210,0.34), rgba(43,196,180,0.2)); box-shadow: var(--glow-hot); } .fishbowl .btn.ghost { background: transparent; } .fishbowl .btn:disabled { opacity: 0.4; cursor: not-allowed; } .fishbowl .icon-btn { width: 38px; height: 38px; display: grid; place-items: center; border: 1px solid var(--line); background: var(--glass); border-radius: var(--r); color: var(--ink-mid); cursor: pointer; transition: all 0.16s ease; } .fishbowl .icon-btn:hover { border-color: var(--line-hot); color: var(--cyan); box-shadow: var(--glow); } /* form controls — styled to look like crisp lab instruments */ .fishbowl .field { display: flex; flex-direction: column; gap: 6px; } .fishbowl .field > label { font-family: var(--font-display); font-size: 9.5px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--ink-dim); } .fishbowl input[type="text"], .fishbowl textarea, .fishbowl select { font-family: var(--font-body); font-size: 13px; color: var(--ink); background: rgba(5, 18, 26, 0.6); border: 1px solid var(--line); border-radius: var(--r); padding: 9px 11px; outline: none; transition: all 0.16s ease; width: 100%; } .fishbowl input:focus, .fishbowl textarea:focus, .fishbowl select:focus { border-color: var(--cyan); box-shadow: var(--glow); } .fishbowl textarea { resize: vertical; line-height: 1.55; } .fishbowl select { appearance: none; cursor: pointer; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%2379c3bb' stroke-width='1.4' fill='none'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 11px center; padding-right: 28px; } /* range slider */ .fishbowl input[type="range"] { -webkit-appearance: none; appearance: none; width: 100%; height: 3px; background: var(--line); border-radius: 3px; outline: none; cursor: pointer; } .fishbowl input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; border-radius: 50%; background: var(--cyan); box-shadow: var(--glow); border: 2px solid var(--bg-1); cursor: pointer; } .fishbowl input[type="range"]::-moz-range-thumb { width: 14px; height: 14px; border-radius: 50%; background: var(--cyan); box-shadow: var(--glow); border: 2px solid var(--bg-1); cursor: pointer; } /* toggle switch */ .fishbowl .switch { position: relative; width: 46px; height: 24px; flex: none; cursor: pointer; } .fishbowl .switch input { display: none; } .fishbowl .switch .track { position: absolute; inset: 0; border-radius: 999px; background: rgba(5,18,26,0.8); border: 1px solid var(--line); transition: all 0.2s ease; } .fishbowl .switch .knob { position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: var(--ink-dim); transition: all 0.2s ease; } .fishbowl .switch input:checked + .track { border-color: var(--cyan); background: rgba(79,230,210,0.16); box-shadow: var(--glow); } .fishbowl .switch input:checked + .track + .knob { left: 25px; background: var(--cyan); box-shadow: var(--glow); } .fishbowl .divider { height: 1px; background: var(--line-soft); border: 0; margin: 0; } /* scrollable region */ .fishbowl .scroll { overflow-y: auto; overflow-x: hidden; } /* animation utility keyframes used across components */ @keyframes spin { to { transform: rotate(360deg); } } @keyframes blink { 50% { opacity: 0; } } .fishbowl .caret::after { content: "▌"; color: var(--cyan); animation: blink 1s steps(1) infinite; } /* ============================================================ avatar life ============================================================ */ @media (prefers-reduced-motion: no-preference) { .fishbowl .av-ring { animation: avRing 2.4s ease-out infinite; transform-origin: 50% 50%; } .fishbowl .av-blink { animation: avBlink 3.2s infinite; } .fishbowl .av-sweat { animation: avSweat 2.6s ease-in infinite; transform-origin: 74px 24px; } .fishbowl .av-gasp { animation: avGasp 0.8s ease-in-out infinite; transform-origin: 50px 65px; } .fishbowl .av-thinking { animation: avBob 2.8s ease-in-out infinite; } .fishbowl .av-panic { animation: avShake 0.32s linear infinite; } .fishbowl .av-smug, .fishbowl .av-gossip { animation: avTilt 4s ease-in-out infinite; } .fishbowl .av-truth, .fishbowl .av-calm { animation: avFloat 5s ease-in-out infinite; } } @keyframes avRing { 0% { transform: scale(0.85); opacity: 0.6; } 100% { transform: scale(1.25); opacity: 0; } } @keyframes avBlink { 0%, 94%, 100% { transform: scaleY(1); } 97% { transform: scaleY(0.1); } } @keyframes avSweat { 0%, 55% { transform: translateY(0); opacity: 0; } 65% { opacity: 1; } 100% { transform: translateY(14px); opacity: 0; } } @keyframes avGasp { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.25); } } @keyframes avBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } } @keyframes avShake { 0%,100% { transform: translateX(0) rotate(0); } 25% { transform: translateX(-1.5px) rotate(-1.5deg); } 75% { transform: translateX(1.5px) rotate(1.5deg); } } @keyframes avTilt { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(2deg); } } @keyframes avFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } } /* gradio mapping annotation */ .fishbowl .gr-tag { font-family: var(--font-body); font-size: 9px; letter-spacing: 0.04em; color: var(--blue); background: rgba(91, 182, 255, 0.1); border: 1px solid rgba(91, 182, 255, 0.3); border-radius: 3px; padding: 1px 5px; white-space: nowrap; } /* ============================================================ app shell — gradio-style tab header ============================================================ */ .fishbowl .app { display: flex; flex-direction: column; min-height: 100vh; position: relative; z-index: 1; } .fishbowl .topbar { display: flex; align-items: center; gap: 18px; padding: 12px 22px; border-bottom: 1px solid var(--line); background: rgba(6, 20, 27, 0.7); backdrop-filter: blur(10px); flex: none; } .fishbowl .brand { display: flex; align-items: baseline; gap: 10px; } .fishbowl .brand .logo { font-family: var(--font-display); font-weight: 800; font-size: 19px; letter-spacing: 0.12em; color: var(--cyan); text-shadow: var(--glow-hot); } .fishbowl .brand .sub { font-size: 10px; letter-spacing: 0.06em; color: var(--ink-dim); } .fishbowl .tabs { display: flex; gap: 4px; background: rgba(5,18,26,0.6); border: 1px solid var(--line); border-radius: var(--r); padding: 3px; } .fishbowl .tab { font-family: var(--font-display); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; font-weight: 600; color: var(--ink-dim); padding: 7px 16px; border-radius: 3px; cursor: pointer; transition: all 0.16s ease; display: flex; align-items: center; gap: 8px; border: 1px solid transparent; } .fishbowl .tab:hover { color: var(--ink-mid); } .fishbowl .tab.active { color: var(--cyan); background: var(--glass); border-color: var(--line); text-shadow: var(--glow); } .fishbowl .tab-badge { font-size: 8.5px; padding: 1px 6px; border-radius: 999px; letter-spacing: 0.1em; background: rgba(155,232,107,0.14); color: var(--lime); border: 1px solid rgba(155,232,107,0.3); } .fishbowl .app-body { flex: 1; min-height: 0; position: relative; } /* live status dot */ .fishbowl .live-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--lime); box-shadow: 0 0 8px var(--lime); } @media (prefers-reduced-motion: no-preference) { .fishbowl .live-dot { animation: livePulse 1.6s ease-in-out infinite; } } @keyframes livePulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } } /* ============================================================ THE LAB ============================================================ */ .fishbowl .lab { height: 100%; position: relative; } .fishbowl .lab-inner { max-width: 1080px; margin: 0 auto; padding: 26px 26px 120px; display: flex; flex-direction: column; gap: 16px; } .fishbowl .lab-hero { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; padding: 6px 2px 10px; } .fishbowl .lab-hero-t { font-size: 26px; font-weight: 700; letter-spacing: -0.01em; margin: 8px 0 8px; color: var(--ink); text-shadow: var(--glow); max-width: 640px; line-height: 1.2; } .fishbowl .lab-hero-p { font-size: 13px; color: var(--ink-mid); max-width: 600px; margin: 0; line-height: 1.6; } .fishbowl .lab-hero-p b { color: var(--cyan); } .fishbowl .btn.surprise { border-color: var(--violet); color: var(--violet); flex: none; } .fishbowl .btn.surprise:hover { box-shadow: 0 0 16px rgba(155,140,255,0.4); } .fishbowl .lab-sec { padding: 0; overflow: hidden; } .fishbowl .lab-sec-h { display: flex; justify-content: space-between; align-items: center; padding: 14px 18px; border-bottom: 1px solid var(--line-soft); } .fishbowl .lab-sec-n { font-size: 12px; font-weight: 700; color: var(--cyan); letter-spacing: 0.1em; opacity: 0.8; } .fishbowl .lab-sec-t { font-size: 14px; letter-spacing: 0.06em; color: var(--ink); } .fishbowl .lab-sec-body { padding: 18px; } .fishbowl .lab-hint { font-size: 10.5px; color: var(--ink-faint); font-style: italic; max-width: 360px; text-align: right; } .fishbowl .lab-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } /* scenario cards */ .fishbowl .scn-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; } .fishbowl .scn-card { display: flex; flex-direction: column; gap: 7px; align-items: flex-start; padding: 16px 14px; border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--glass); cursor: pointer; transition: all 0.18s ease; text-align: left; position: relative; min-height: 150px; } .fishbowl .scn-card:hover { border-color: var(--line-hot); box-shadow: var(--glow); transform: translateY(-2px); } .fishbowl .scn-card.on { border-color: var(--cyan); background: rgba(79,230,210,0.08); box-shadow: var(--glow); } .fishbowl .scn-card.on::after { content: "ACTIVE"; position: absolute; top: 10px; right: 10px; font-family: var(--font-display); font-size: 7.5px; letter-spacing: 0.18em; color: var(--cyan); } .fishbowl .scn-card.custom { opacity: 0.45; cursor: default; border-style: dashed; } .fishbowl .scn-card.custom:hover { transform: none; box-shadow: none; border-color: var(--line); } .fishbowl .scn-glyph { font-size: 28px; color: var(--cyan); text-shadow: var(--glow); line-height: 1; } .fishbowl .scn-title { font-size: 13px; font-weight: 700; letter-spacing: 0.04em; color: var(--ink); } .fishbowl .scn-code { font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-dim); } .fishbowl .scn-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: auto; } .fishbowl .scn-tag { font-size: 8.5px; letter-spacing: 0.06em; color: var(--ink-mid); border: 1px solid var(--line-soft); border-radius: 999px; padding: 2px 7px; } /* cast table */ .fishbowl .cast-headcount { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } .fishbowl .cast-table { display: flex; flex-direction: column; gap: 2px; } .fishbowl .cast-row { display: grid; grid-template-columns: 48px 1.1fr 1.3fr 1.8fr 1.2fr 1fr; align-items: center; gap: 12px; padding: 8px 10px; border-radius: var(--r); } .fishbowl .cast-row:not(.cast-head):hover { background: var(--glass); } .fishbowl .cast-head { font-family: var(--font-display); font-size: 8.5px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--ink-faint); padding-bottom: 6px; border-bottom: 1px solid var(--line-soft); border-radius: 0; } .fishbowl .cast-av { display: grid; place-items: center; } .fishbowl .cast-name { font-family: var(--font-display); font-weight: 600; font-size: 13px; letter-spacing: 0.05em; padding: 6px 8px; } .fishbowl .cast-arch { font-size: 12px; color: var(--ink-mid); } .fishbowl .cast-model { display: flex; align-items: center; gap: 8px; } .fishbowl .cast-model select { font-size: 11.5px; padding: 6px 8px; } .fishbowl .tier-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; box-shadow: 0 0 6px currentColor; } .fishbowl .cast-temp { display: flex; align-items: center; gap: 9px; } .fishbowl .cast-temp .tnum { font-size: 12px; color: var(--cyan); min-width: 24px; } .fishbowl .cast-role { justify-self: start; font-size: 8px; } .fishbowl .range-ends { display: flex; justify-content: space-between; font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-faint); margin-top: 5px; } /* tools */ .fishbowl .tool-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; } .fishbowl .tool-chip { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; padding: 11px 12px; border: 1px solid var(--line); border-radius: var(--r); background: var(--glass); cursor: pointer; transition: all 0.16s ease; text-align: left; position: relative; } .fishbowl .tool-chip:hover { border-color: var(--line-hot); } .fishbowl .tool-chip.on { border-color: var(--cyan); background: rgba(79,230,210,0.07); } .fishbowl .tool-box { position: absolute; top: 10px; right: 11px; width: 16px; height: 16px; border: 1px solid var(--line); border-radius: 3px; display: grid; place-items: center; font-size: 11px; color: var(--cyan); } .fishbowl .tool-chip.on .tool-box { border-color: var(--cyan); box-shadow: var(--glow); } .fishbowl .tool-name { font-size: 12px; color: var(--ink); } .fishbowl .tool-desc { font-size: 9.5px; color: var(--ink-dim); } /* budget */ .fishbowl .budget-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; } .fishbowl .seed-note { font-size: 11px; color: var(--ink-faint); margin: 16px 0 0; line-height: 1.5; } /* launch bar */ .fishbowl .launch-bar { position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: center; gap: 18px; padding: 14px 26px; background: rgba(6,20,27,0.92); backdrop-filter: blur(12px); border-top: 1px solid var(--line); } .fishbowl .launch-info { display: flex; align-items: center; gap: 14px; flex: 1; } .fishbowl .launch-meta { font-size: 11px; color: var(--ink-dim); letter-spacing: 0.04em; } .fishbowl .launch-voice { flex-direction: row; align-items: center; gap: 10px; } .fishbowl .launch-voice label { white-space: nowrap; } .fishbowl .launch-voice select { min-width: 230px; font-size: 11.5px; } .fishbowl .launch-go { padding: 12px 22px; font-size: 12px; } /* hints toggle + summon flash */ .fishbowl .hints-toggle { display: flex; align-items: center; gap: 9px; cursor: pointer; } .fishbowl .summon-flash { position: fixed; inset: 0; z-index: 200; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background: rgba(5,18,26,0.86); backdrop-filter: blur(10px); animation: sfIn 0.3s ease; } @keyframes sfIn { from { opacity: 0; } to { opacity: 1; } } .fishbowl .sf-glyph { font-size: 90px; color: var(--cyan); text-shadow: var(--glow-hot); animation: sfPulse 0.85s ease-in-out infinite; } @keyframes sfPulse { 0%,100% { transform: scale(1); opacity: 0.8; } 50% { transform: scale(1.12); opacity: 1; } } .fishbowl .sf-text { font-size: 18px; letter-spacing: 0.22em; color: var(--ink); text-shadow: var(--glow); } .fishbowl .sf-sub { font-size: 11px; letter-spacing: 0.08em; color: var(--ink-dim); } .fishbowl .app.surprising .lab-inner { animation: surpriseShimmer 0.6s ease; } @keyframes surpriseShimmer { 0% { filter: brightness(1.4) saturate(1.3); } 100% { filter: none; } } /* ============================================================ THE SHOW ============================================================ */ .fishbowl .show { display: flex; flex-direction: column; height: 100%; position: relative; } /* ---- show bar ---- */ .fishbowl .showbar { display: flex; align-items: center; gap: 16px; padding: 11px 20px; border-bottom: 1px solid var(--line); background: rgba(6,20,27,0.6); backdrop-filter: blur(10px); flex: none; } .fishbowl .show-title { display: flex; align-items: center; gap: 11px; flex: 1; min-width: 0; } .fishbowl .show-title .disp { font-size: 15px; letter-spacing: 0.08em; color: var(--ink); text-shadow: var(--glow); } .fishbowl .show-code { font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-dim); } .fishbowl .show-controls { display: flex; align-items: center; gap: 14px; } /* the star — mind reader toggle */ .fishbowl .mindreader { display: flex; align-items: center; gap: 10px; padding: 6px 12px 6px 14px; border: 1px solid var(--line); border-radius: 999px; cursor: pointer; transition: all 0.2s ease; background: var(--glass); } .fishbowl .mindreader.on { border-color: var(--cyan); box-shadow: var(--glow); background: rgba(79,230,210,0.08); } .fishbowl .mr-icon { color: var(--ink-dim); display: grid; place-items: center; } .fishbowl .mindreader.on .mr-icon { color: var(--cyan); } .fishbowl .mr-label { font-size: 10.5px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-mid); } .fishbowl .mindreader.on .mr-label { color: var(--cyan); } /* segmented control */ .fishbowl .seg { display: flex; gap: 2px; background: rgba(5,18,26,0.7); border: 1px solid var(--line); border-radius: var(--r); padding: 2px; } .fishbowl .seg-b { width: 34px; height: 30px; display: grid; place-items: center; border: 0; background: transparent; color: var(--ink-dim); border-radius: 3px; cursor: pointer; transition: all 0.15s ease; } .fishbowl .seg-b.sb { width: auto; padding: 0 11px; font-family: var(--font-display); font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; } .fishbowl .seg-b:hover { color: var(--ink-mid); } .fishbowl .seg-b.on { background: var(--glass); color: var(--cyan); box-shadow: inset 0 0 0 1px var(--line-hot); } .fishbowl .voice-field select { min-width: 180px; font-size: 11px; padding: 7px 9px; } /* ---- body ---- */ .fishbowl .show-body { flex: 1; min-height: 0; position: relative; } /* ============ CONSTELLATION ============ */ .fishbowl .show-constellation .show-body { display: grid; grid-template-columns: 1fr 384px; } .fishbowl .constellation { position: relative; } .fishbowl .stage { position: relative; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); padding: 22px; gap: 10px; overflow: auto; min-height: 0; place-items: center; } .fishbowl .core { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; pointer-events: none; z-index: 1; } .fishbowl .core-glyph { font-size: 64px; color: var(--cyan); text-shadow: var(--glow-hot); line-height: 1; opacity: 0.85; animation: coreFloat 6s ease-in-out infinite; } /* Gentle amplitude: the stage HTML is replaced every tick, restarting this float mid-cycle, so a large drift would snap the central glyph each time. A small rise keeps the drift alive in static/scrub views without a visible jump during live autoplay. */ @keyframes coreFloat { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-3px); } } .fishbowl .core-title { font-size: 16px; letter-spacing: 0.1em; color: var(--ink); } .fishbowl .core-round { font-size: 9px; color: var(--ink-dim); } /* Audience-only secret peek (Twenty Sprouts) — a dashed "director's view" chip the cast never sees. Dimmed by default so it reads as a backstage aside, brightening the held word so the human can actually read it at a glance. */ .fishbowl .core-secret { margin-top: 4px; font-size: 9px; color: var(--ink-faint); border: 1px dashed var(--ink-faint); border-radius: 999px; padding: 3px 9px; background: color-mix(in srgb, var(--cyan) 6%, transparent); pointer-events: auto; } .fishbowl .core-secret-word { color: var(--cyan); letter-spacing: 0.14em; text-shadow: var(--glow-hot); } .fishbowl .ring { display: contents; } .fishbowl .ring-slot { display: grid; place-items: center; z-index: 2; } .fishbowl .show-constellation .ring-slot:nth-child(1) { grid-area: 1 / 2; } .fishbowl .show-constellation .ring-slot:nth-child(2) { grid-area: 2 / 3; } .fishbowl .show-constellation .ring-slot:nth-child(3) { grid-area: 3 / 2; } .fishbowl .show-constellation .ring-slot:nth-child(4) { grid-area: 2 / 1; } .fishbowl .show-constellation .ring-slot:nth-child(5) { grid-area: 1 / 1; } .fishbowl .show-constellation .ring-slot:nth-child(6) { grid-area: 3 / 3; } /* ---- the mind card ---- */ .fishbowl .mind { perspective: 1200px; width: 300px; } .fishbowl .mind-stage { width: 300px; } .fishbowl .mind-inner { position: relative; transition: transform 0.7s cubic-bezier(0.4,0.1,0.2,1); transform-style: preserve-3d; } .fishbowl .mind.flipped .mind-inner { transform: rotateY(180deg); } .fishbowl .mind-face { border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--panel); backdrop-filter: blur(8px); padding: 13px; backface-visibility: hidden; } .fishbowl .mind-back { position: absolute; inset: 0; transform: rotateY(180deg); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; border-color: var(--lime); background: rgba(155,232,107,0.08); box-shadow: 0 0 24px rgba(155,232,107,0.3); } .fishbowl .reveal-glyph { font-size: 40px; color: var(--lime); text-shadow: 0 0 16px var(--lime); } .fishbowl .reveal-secret { font-size: 14px; color: var(--lime); text-align: center; padding: 0 14px; line-height: 1.4; } .fishbowl .mind.speaking .mind-front { border-color: var(--ac); box-shadow: 0 0 22px color-mix(in oklab, var(--ac) 45%, transparent); } .fishbowl .mind.rattled .mind-front { border-color: var(--coral); animation: cardShake 0.4s ease-in-out infinite; } @keyframes cardShake { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-2px); } 75% { transform: translateX(2px); } } /* head: avatar · identity · the live mood pill (the one datum that changes turn to turn) */ .fishbowl .mind-head { display: grid; grid-template-columns: auto 1fr auto; gap: 11px; align-items: center; padding-bottom: 10px; border-bottom: 1px solid var(--line-soft); } .fishbowl .mind-id { min-width: 0; } .fishbowl .mind-name { font-size: 14.5px; font-weight: 700; letter-spacing: 0.04em; color: var(--ac, var(--ink)); text-shadow: 0 0 9px color-mix(in oklab, var(--ac, var(--ink)) 35%, transparent); line-height: 1.15; display: flex; align-items: center; gap: 7px; } .fishbowl .mic { color: var(--coral); font-size: 9px; animation: livePulse 0.9s ease-in-out infinite; } .fishbowl .mind-arch { margin-top: 3px; font-size: 10.5px; color: var(--ink-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* mood pill — colour-coded by mood, with a glowing status dot; reads at a glance */ .fishbowl .mind-mood-pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px 3px 8px; border-radius: 999px; border: 1px solid color-mix(in oklab, var(--mc) 50%, transparent); background: color-mix(in oklab, var(--mc) 13%, transparent); white-space: nowrap; align-self: start; } .fishbowl .mmp-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--mc); box-shadow: 0 0 7px var(--mc); flex: none; } .fishbowl .mmp-label { font-family: var(--font-display); font-size: 8px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--mc); } /* a panicking mind throbs its pill so the leak is impossible to miss */ @media (prefers-reduced-motion: no-preference) { .fishbowl .mind.rattled .mmp-dot { animation: livePulse 0.7s ease-in-out infinite; } } /* model meta-strip — provenance gets its own labelled row, so a long served id stays legible */ .fishbowl .mind-meta-strip { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 10px; padding: 6px 10px; border: 1px solid var(--line-soft); border-radius: var(--r); background: rgba(3,12,18,0.55); } .fishbowl .mms-model { display: flex; align-items: center; gap: 7px; min-width: 0; } .fishbowl .mms-model .tier-dot { width: 7px; height: 7px; } .fishbowl .mms-name { font-family: var(--font-display); font-size: 10px; letter-spacing: 0.01em; color: var(--ink-mid); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .fishbowl .mms-tier { font-family: var(--font-display); font-size: 8px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-faint); flex: none; } .fishbowl .mms-tier:empty { display: none; } .fishbowl .bubbles { display: flex; flex-direction: column; gap: 8px; padding-top: 11px; } .fishbowl .bubble { border-radius: var(--r); padding: 9px 11px 10px; position: relative; } .fishbowl .bub-tag { font-family: var(--font-display); font-size: 7.5px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-faint); display: block; margin-bottom: 5px; } .fishbowl .bubble p { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink); } .fishbowl .bubble .muted { color: var(--ink-faint); font-style: italic; } .fishbowl .bubble.said { background: rgba(3,12,18,0.78); border: 1px solid var(--line); } .fishbowl .bubble.thought.open { background: color-mix(in oklab, var(--ac) 9%, rgba(5,18,26,0.4)); border: 1px dashed var(--ac); } .fishbowl .bubble.thought.open .bub-tag { color: var(--ac); } .fishbowl .think-text { font-style: italic; color: color-mix(in oklab, var(--ac) 70%, var(--ink)) !important; text-shadow: 0 0 8px color-mix(in oklab, var(--ac) 40%, transparent); } .fishbowl .bubble.thought.sealed { background: rgba(5,18,26,0.35); border: 1px dashed var(--line-soft); } .fishbowl .seal { font-size: 10.5px; color: var(--ink-faint); display: flex; align-items: center; gap: 7px; font-style: italic; } .fishbowl .bubble.thought.leak.open { border-color: var(--coral); background: color-mix(in oklab, var(--coral) 10%, rgba(5,18,26,0.4)); animation: leakPulse 0.7s ease-in-out infinite; } .fishbowl .bubble.thought.leak .bub-tag, .fishbowl .bubble.thought.leak .think-text { color: var(--coral) !important; text-shadow: 0 0 8px rgba(255,143,125,0.5); } @keyframes leakPulse { 0%,100% { box-shadow: 0 0 0 transparent; } 50% { box-shadow: 0 0 16px rgba(255,143,125,0.35); } } /* ---- rail (narrator feed + meters) ---- */ .fishbowl .rail { display: flex; flex-direction: column; border-left: 1px solid var(--line); background: rgba(6,20,27,0.4); min-height: 0; } .fishbowl .rail-h { display: flex; justify-content: space-between; align-items: center; padding: 13px 16px; border-bottom: 1px solid var(--line-soft); flex: none; } .fishbowl .feed { flex: 1; padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; min-height: 0; } /* keep the narrator rail feed compact — minimal height, scroll stays as-is */ .fishbowl .rail .feed { flex: none; max-height: 220px; } .fishbowl .feed.dense { gap: 9px; } /* Only the newest line slides in. The Show re-renders the whole feed HTML every tick (Gradio replaces innerHTML wholesale), so animating every .fe re-played the entrance on the entire transcript each time — reading as a full-screen reload. Scoping the entrance to :last-child keeps the "new message" cue while the rest of the log stays still. */ .fishbowl .fe:last-child { animation: feIn 0.3s ease; } @keyframes feIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } } .fishbowl .fe.narr { border-left: 2px solid var(--violet); padding-left: 12px; } .fishbowl .narr-voice { font-family: var(--font-display); font-size: 8px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--violet); display: block; margin-bottom: 4px; } .fishbowl .fe.narr p { margin: 0; font-size: 13px; line-height: 1.6; color: var(--ink); opacity: 0.92; font-style: italic; } .fishbowl .fe.say { background: rgba(3,12,18,0.6); border-left: 2px solid var(--ac, var(--cyan)); border-radius: 0 var(--r) var(--r) 0; padding: 8px 11px; } .fishbowl .say-line { font-size: 12.5px; line-height: 1.45; } .fishbowl .say-line b { color: var(--ac, var(--cyan)); text-shadow: 0 0 8px color-mix(in oklab, var(--ac, var(--cyan)) 40%, transparent); margin-right: 7px; font-weight: 700; letter-spacing: 0.04em; } .fishbowl .say-line span { color: var(--ink); } .fishbowl .say-think { margin-top: 5px; font-size: 11px; color: var(--ac, var(--cyan)); opacity: 0.85; } .fishbowl .say-think i { color: color-mix(in oklab, var(--ac, var(--cyan)) 75%, var(--ink)); } .fishbowl .fe.poke, .fishbowl .fe.verdict-fe { border: 1px solid var(--amber); border-radius: var(--r); padding: 9px 11px; background: rgba(255,207,107,0.07); } .fishbowl .poke-tag { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-display); font-size: 8.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--amber); border: 1px solid rgba(255,207,107,0.4); border-radius: 999px; padding: 2px 8px; margin-bottom: 6px; } .fishbowl .fe.poke p, .fishbowl .fe.verdict-fe p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--ink); } /* The rafters-critic's color-commentary card: a glass panel wearing the critic's own phosphor (--ac, set inline from its hue), holding a funny line + optional image + audio. */ .fishbowl .fe.commentate { border: 1px solid var(--ac, var(--cyan)); border-radius: var(--r); padding: 10px 12px; background: var(--glass); box-shadow: 0 0 16px color-mix(in oklab, var(--ac, var(--cyan)) 22%, transparent); } .fishbowl .cm-tag { color: var(--ac, var(--cyan)); border-color: color-mix(in oklab, var(--ac, var(--cyan)) 42%, transparent); } .fishbowl .cm-text { margin: 0 0 8px; font-size: 12.5px; line-height: 1.5; color: var(--ink); font-style: italic; } /* The image + audio themselves render in the native "FROM THE RAFTERS" cutaway (see #rafters-box below); the feed card keeps small badges noting a beat carried media. */ .fishbowl .cm-badges { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; } .fishbowl .cm-badge { font-family: var(--font-display); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--ac, var(--cyan)); padding: 3px 8px; border-radius: 999px; border: 1px solid color-mix(in oklab, var(--ac, var(--cyan)) 40%, transparent); background: color-mix(in oklab, var(--ac, var(--cyan)) 10%, transparent); } /* ============================================================ FROM THE RAFTERS — the heckler's native illustrated + spoken cutaway. Native gr.Image / gr.Audio live OUTSIDE the .fishbowl HTML islands, so these rules are global (the stylesheet is injected app-wide via gr.Blocks(css=...)). A vintage broadcast "now showing" box in the critic's hot magenta (hue 320). ============================================================ */ #rafters-box { --cm: #ff5cc8; /* the heckler's hot magenta */ position: relative; margin: 0 0 12px; padding: 11px 11px 9px; border-radius: var(--r-lg, 8px); border: 1px solid color-mix(in oklab, var(--cm) 55%, transparent); background: radial-gradient(130% 90% at 50% -15%, color-mix(in oklab, var(--cm) 20%, transparent), transparent 62%), var(--panel, rgba(8, 26, 33, .9)); box-shadow: 0 0 24px color-mix(in oklab, var(--cm) 26%, transparent), inset 0 0 0 1px color-mix(in oklab, var(--cm) 12%, transparent); overflow: hidden; animation: rafters-in .45s ease both; } @keyframes rafters-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } } #rafters-box::before { /* a sweeping stage-light bar across the top edge */ content: ""; position: absolute; inset: 0 0 auto 0; height: 2px; background: linear-gradient(90deg, transparent, var(--cm), transparent); opacity: .85; } #rafters-box .rafters-head { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; font-family: var(--font-display, ui-monospace); font-size: 11px; letter-spacing: .18em; text-transform: uppercase; color: var(--cm); text-shadow: 0 0 10px color-mix(in oklab, var(--cm) 60%, transparent); } #rafters-box .rafters-quill { font-size: 13px; } #rafters-box .rafters-count { margin-left: auto; font-size: 10px; letter-spacing: .1em; color: color-mix(in oklab, var(--cm) 75%, var(--ink, #d2f7f0)); opacity: .92; } #rafters-box .rafters-head::after { /* blinking "on air" dot */ content: ""; margin-left: 8px; width: 7px; height: 7px; border-radius: 50%; background: var(--cm); box-shadow: 0 0 9px var(--cm); animation: rafters-blink 1.5s steps(1) infinite; } @keyframes rafters-blink { 0%, 62% { opacity: 1; } 63%, 100% { opacity: .25; } } #rafters-box .rafters-img img { width: 100% !important; border-radius: var(--r, 4px) !important; border: 1px solid color-mix(in oklab, var(--cm) 45%, transparent) !important; box-shadow: 0 0 18px color-mix(in oklab, var(--cm) 22%, transparent); animation: rafters-fade .5s ease both; } @keyframes rafters-fade { from { opacity: 0; transform: scale(.985); } to { opacity: 1; transform: none; } } #rafters-box .rafters-cap { margin: 9px 2px 5px; font-family: var(--font-body, ui-monospace); font-style: italic; font-size: 12.5px; line-height: 1.5; color: var(--ink, #d2f7f0); } #rafters-box .rafters-cap::before { content: "\201C"; } #rafters-box .rafters-cap::after { content: "\201D"; } #rafters-box .rafters-audio { margin-top: 4px; } /* ‹ Prev / Next › — walk the critic's run of beats */ #rafters-box .rafters-nav { display: flex; gap: 8px; margin-top: 10px; } #rafters-box .rafters-navbtn { flex: 1; min-width: 0; font-family: var(--font-display, ui-monospace); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--cm); padding: 7px 10px; border-radius: var(--r, 4px); border: 1px solid color-mix(in oklab, var(--cm) 42%, transparent); background: color-mix(in oklab, var(--cm) 9%, transparent); transition: background .15s ease, box-shadow .15s ease, transform .1s ease; } #rafters-box .rafters-navbtn:hover:not([disabled]):not(.disabled) { background: color-mix(in oklab, var(--cm) 20%, transparent); box-shadow: 0 0 13px color-mix(in oklab, var(--cm) 38%, transparent); } #rafters-box .rafters-navbtn:active:not([disabled]):not(.disabled) { transform: translateY(1px); } #rafters-box .rafters-navbtn[disabled], #rafters-box .rafters-navbtn.disabled { opacity: .3; cursor: default; box-shadow: none; } /* ---- meters ---- */ .fishbowl .meters { margin: 0 14px 14px; padding: 13px 15px; flex: none; display: flex; flex-direction: column; gap: 12px; } .fishbowl .stat { font-size: 10px; letter-spacing: 0.08em; color: var(--ink-mid); } .fishbowl .meter-h { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 7px; } .fishbowl .meter-h .tnum { font-size: 11px; font-family: var(--font-display); } .fishbowl .bar { height: 5px; background: rgba(5,18,26,0.8); border-radius: 3px; overflow: hidden; } .fishbowl .bar-fill { height: 100%; border-radius: 3px; transition: width 0.4s ease; box-shadow: 0 0 8px currentColor; } .fishbowl .meter-stats { display: flex; justify-content: space-between; gap: 10px; } /* ============ FEED VIEW ============ */ .fishbowl .feedview { display: grid; grid-template-columns: 220px 1fr 300px; height: 100%; min-height: 0; } .fishbowl .roster { border-right: 1px solid var(--line); padding: 16px 14px; display: flex; flex-direction: column; gap: 10px; background: rgba(6,20,27,0.4); overflow: auto; } .fishbowl .roster-chip { display: flex; align-items: center; gap: 11px; padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--r); transition: all 0.2s ease; } .fishbowl .roster-chip.on { border-color: var(--ac); box-shadow: 0 0 14px color-mix(in oklab, var(--ac) 35%, transparent); background: color-mix(in oklab, var(--ac) 6%, transparent); } .fishbowl .rc-name { font-size: 12.5px; letter-spacing: 0.05em; color: var(--ac, var(--ink)); } .fishbowl .rc-mood { font-family: var(--font-display); font-size: 8px; letter-spacing: 0.1em; text-transform: uppercase; } .fishbowl .feedview-main { min-height: 0; display: flex; } .fishbowl .feedview-main .feed { max-width: 720px; margin: 0 auto; width: 100%; } .fishbowl .feedview-side { border-left: 1px solid var(--line); padding: 16px 0; background: rgba(6,20,27,0.4); } .fishbowl .feedview-side .meters { background: transparent; border: 0; } /* ============ SPLIT VIEW ============ */ .fishbowl .splitview { height: 100%; padding: 20px 26px 26px; max-width: 1180px; margin: 0 auto; width: 100%; } .fishbowl .split-head { display: grid; grid-template-columns: 230px 1fr 1fr; gap: 16px; padding: 0 14px 10px; position: sticky; top: 0; } .fishbowl .split-row { display: grid; grid-template-columns: 230px 1fr 1fr; gap: 16px; align-items: stretch; padding: 14px; border: 1px solid var(--line); border-radius: var(--r-lg); margin-bottom: 12px; background: var(--panel); transition: all 0.2s ease; } .fishbowl .split-row.on { border-color: var(--ac); box-shadow: 0 0 18px color-mix(in oklab, var(--ac) 30%, transparent); } .fishbowl .split-id { display: flex; gap: 12px; align-items: center; } .fishbowl .split-name { font-size: 14px; letter-spacing: 0.06em; font-weight: 700; color: var(--ac, var(--ink)); text-shadow: 0 0 8px color-mix(in oklab, var(--ac, var(--ink)) 32%, transparent); } .fishbowl .split-arch { font-size: 10.5px; color: var(--ink-dim); } .fishbowl .split-model { display: flex; align-items: center; gap: 5px; font-size: 9.5px; color: var(--ink-mid); margin-top: 3px; } .fishbowl .split-said, .fishbowl .split-think { border-radius: var(--r); padding: 12px 14px; display: flex; align-items: center; } .fishbowl .split-said { background: rgba(5,18,26,0.5); border: 1px solid var(--line); } .fishbowl .split-think { background: color-mix(in oklab, var(--ac) 8%, rgba(5,18,26,0.4)); border: 1px dashed var(--ac); } .fishbowl .split-said p, .fishbowl .split-think p { margin: 0; font-size: 13px; line-height: 1.5; } .fishbowl .split-think.leak { border-color: var(--coral); background: rgba(255,143,125,0.08); } .fishbowl .split-think.leak .think-text { color: var(--coral) !important; } /* ---- verdict banner ---- */ .fishbowl .verdict-banner { position: absolute; top: 64px; left: 50%; transform: translateX(-50%); z-index: 40; display: flex; align-items: center; gap: 14px; padding: 11px 22px; background: rgba(10,34,27,0.95); border: 1px solid var(--lime); border-radius: 999px; box-shadow: 0 0 30px rgba(155,232,107,0.4); backdrop-filter: blur(8px); animation: vbDrop 0.5s cubic-bezier(0.2,1.2,0.3,1); } @keyframes vbDrop { from { opacity: 0; transform: translateX(-50%) translateY(-20px); } to { opacity: 1; } } .fishbowl .verdict-banner .disp { color: var(--lime); font-size: 13px; letter-spacing: 0.14em; text-shadow: 0 0 10px var(--lime); } .fishbowl .vb-text { color: var(--ink); font-size: 12.5px; } /* Winner ribbon — the arena's "who won" chip; only present for competitive runs. */ .fishbowl .vb-winner { font-family: var(--font-display); font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--amber); text-shadow: 0 0 12px rgba(255,207,107,0.55); white-space: nowrap; } /* ---- winner celebration ---- The cheerful counterpart to the verdict banner: a one-shot champion overlay that drops in when a run crowns someone. Fixed + pointer-events:none so it floats over the theater without stealing clicks from the transport. Tinted by --win-hue (the winning bot's own hue) so the glow matches the mind on stage. Only rendered for winner/loser scenarios. */ .fishbowl .winner-fx { position: fixed; inset: 0; z-index: 150; pointer-events: none; display: flex; align-items: center; justify-content: center; } /* dismiss machinery — a hidden checkbox toggled by the backdrop + the ✕ (no JS needed) */ .fishbowl .wf-dismiss { position: absolute; width: 0; height: 0; margin: 0; opacity: 0; pointer-events: none; } .fishbowl .wf-backdrop { position: fixed; inset: 0; z-index: 0; pointer-events: auto; cursor: pointer; background: radial-gradient(120% 120% at 50% 42%, transparent 0%, rgba(2, 10, 14, 0.55) 100%); backdrop-filter: blur(2px); animation: wfFade 0.4s ease both; } @keyframes wfFade { from { opacity: 0; } to { opacity: 1; } } /* dismissed → the overlay vanishes and the transport is clickable again */ .fishbowl .wf-dismiss:checked ~ .wf-backdrop, .fishbowl .wf-dismiss:checked ~ .wf-confetti, .fishbowl .wf-dismiss:checked ~ .wf-card { display: none; } /* the ✕ — a hue-tinted phosphor disc that spins on hover */ .fishbowl .wf-x { position: absolute; top: 11px; right: 13px; z-index: 2; width: 26px; height: 26px; display: grid; place-items: center; cursor: pointer; pointer-events: auto; font-family: var(--font-display); font-size: 17px; line-height: 1; color: hsl(var(--win-hue) 70% 80%); border: 1px solid hsl(var(--win-hue) 70% 60% / 0.4); border-radius: 50%; background: hsl(var(--win-hue) 70% 50% / 0.1); transition: color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.2s ease; } .fishbowl .wf-x:hover { color: #fff; border-color: hsl(var(--win-hue) 80% 66% / 0.9); box-shadow: 0 0 14px hsl(var(--win-hue) 85% 60% / 0.5); transform: rotate(90deg); } /* the champion card — phosphor glass, hue-tinted, lands with an overshoot pop */ .fishbowl .wf-card { position: relative; isolation: isolate; z-index: 1; pointer-events: auto; display: flex; flex-direction: column; align-items: center; gap: 9px; padding: 30px 44px 26px; min-width: 280px; max-width: 86vw; text-align: center; border: 1px solid hsl(var(--win-hue) 80% 62% / 0.7); border-radius: 18px; background: radial-gradient(120% 90% at 50% -10%, hsl(var(--win-hue) 70% 22% / 0.55), transparent 60%), rgba(6, 20, 27, 0.94); box-shadow: 0 0 60px hsl(var(--win-hue) 80% 55% / 0.42), inset 0 0 36px hsl(var(--win-hue) 70% 50% / 0.12); backdrop-filter: blur(10px); animation: wfPop 0.62s cubic-bezier(0.18, 1.35, 0.35, 1) both; } @keyframes wfPop { 0% { opacity: 0; transform: translateY(26px) scale(0.86); } 60% { opacity: 1; } 100% { opacity: 1; transform: none; } } /* a slow spotlight cone sweeping behind the trophy */ .fishbowl .wf-rays { position: absolute; inset: -40% -10% auto; height: 200%; z-index: -1; pointer-events: none; background: conic-gradient(from 0deg at 50% 18%, transparent 0 8deg, hsl(var(--win-hue) 85% 60% / 0.14) 9deg 16deg, transparent 17deg 30deg); background-size: 100% 100%; opacity: 0.7; animation: wfSpin 9s linear infinite; transform-origin: 50% 18%; } @keyframes wfSpin { to { transform: rotate(360deg); } } .fishbowl .wf-trophy { font-size: 56px; line-height: 1; filter: drop-shadow(0 0 18px hsl(var(--win-hue) 85% 62% / 0.85)); animation: wfTrophy 2.6s ease-in-out infinite; } @keyframes wfTrophy { 0%, 100% { transform: translateY(0) rotate(-4deg); } 50% { transform: translateY(-6px) rotate(4deg); } } .fishbowl .wf-eyebrow { font-family: var(--font-display); font-size: 10px; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: hsl(var(--win-hue) 75% 72%); text-shadow: 0 0 12px hsl(var(--win-hue) 85% 60% / 0.6); } .fishbowl .wf-name { font-size: 30px; font-weight: 800; letter-spacing: 0.04em; line-height: 1.05; color: #fff; text-shadow: 0 0 22px hsl(var(--win-hue) 90% 65% / 0.9), 0 0 4px hsl(var(--win-hue) 90% 70% / 0.8); animation: wfShimmer 3.4s ease-in-out infinite; } @keyframes wfShimmer { 0%, 100% { text-shadow: 0 0 22px hsl(var(--win-hue) 90% 65% / 0.9), 0 0 4px hsl(var(--win-hue) 90% 70% / 0.8); } 50% { text-shadow: 0 0 34px hsl(var(--win-hue) 95% 68% / 1), 0 0 8px hsl(var(--win-hue) 95% 75% / 0.9); } } .fishbowl .wf-sub { display: flex; align-items: center; justify-content: center; gap: 9px; flex-wrap: wrap; margin-top: -2px; } .fishbowl .wf-arch { font-style: italic; font-size: 12px; color: var(--ink-dim); letter-spacing: 0.02em; } .fishbowl .wf-roster { display: flex; align-items: center; justify-content: center; gap: 7px; flex-wrap: wrap; } .fishbowl .wf-chip { font-family: var(--font-display); font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase; color: hsl(var(--ch, var(--win-hue)) 80% 80%); border: 1px solid hsl(var(--ch, var(--win-hue)) 75% 62% / 0.45); background: hsl(var(--ch, var(--win-hue)) 75% 50% / 0.12); border-radius: 999px; padding: 3px 10px; white-space: nowrap; } .fishbowl .wf-cheer { font-size: 12.5px; color: var(--ink); opacity: 0.92; margin-top: 4px; } .fishbowl .wf-loser { font-family: var(--font-display); font-size: 8.5px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-faint); } /* confetti — a finite phosphor burst; each bit falls once then clears the sky */ .fishbowl .wf-confetti { position: absolute; inset: 0; overflow: hidden; pointer-events: none; } .fishbowl .wf-bit { position: absolute; top: -8vh; width: 8px; height: 13px; border-radius: 2px; opacity: 0; will-change: transform, opacity; box-shadow: 0 0 8px currentColor; animation-name: wfFall; animation-timing-function: cubic-bezier(0.3, 0.1, 0.6, 1); animation-iteration-count: 1; animation-fill-mode: forwards; } .fishbowl .wf-bit.round { width: 9px; height: 9px; border-radius: 50%; } @keyframes wfFall { 0% { opacity: 0; transform: translate(0, 0) rotate(0deg); } 8% { opacity: 1; } 85% { opacity: 1; } 100% { opacity: 0; transform: translate(var(--wf-drift, 0), 118vh) rotate(var(--wf-rot, 360deg)); } } /* respect reduced-motion: keep the card, drop the spin/shimmer/confetti animation */ @media (prefers-reduced-motion: reduce) { .fishbowl .wf-card, .fishbowl .wf-rays, .fishbowl .wf-trophy, .fishbowl .wf-name, .fishbowl .wf-bit, .fishbowl .wf-backdrop { animation: none; } .fishbowl .wf-x:hover { transform: none; } .fishbowl .wf-bit { opacity: 0; } } /* ---- poke strip ---- */ .fishbowl .poke-strip { display: flex; align-items: center; gap: 14px; padding: 10px 20px; border-top: 1px solid var(--line-soft); background: rgba(6,20,27,0.5); flex: none; } .fishbowl .poke-btns { display: flex; gap: 8px; flex: 1; flex-wrap: wrap; } .fishbowl .poke-b { display: inline-flex; align-items: center; gap: 7px; font-family: var(--font-display); font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--amber); border: 1px solid rgba(255,207,107,0.35); background: rgba(255,207,107,0.06); border-radius: var(--r); padding: 7px 12px; cursor: pointer; transition: all 0.16s ease; } .fishbowl .poke-b:hover:not(:disabled) { box-shadow: 0 0 14px rgba(255,207,107,0.35); border-color: var(--amber); } .fishbowl .poke-b:disabled { opacity: 0.3; cursor: default; } .fishbowl .poke-b.next { animation: pokeBeck 1.8s ease-in-out infinite; border-color: var(--amber); } @keyframes pokeBeck { 0%,100% { box-shadow: 0 0 0 transparent; } 50% { box-shadow: 0 0 14px rgba(255,207,107,0.4); } } .fishbowl .export-btn { color: var(--lime); border-color: rgba(155,232,107,0.35); } .fishbowl .export-btn:hover:not(:disabled) { box-shadow: 0 0 14px rgba(155,232,107,0.3); } .fishbowl .mini-spin { width: 12px; height: 12px; border: 2px solid var(--lime); border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; display: inline-block; } /* ---- transport ---- */ .fishbowl .transport { display: flex; align-items: center; gap: 16px; padding: 13px 22px; border-top: 1px solid var(--line); background: rgba(6,20,27,0.85); backdrop-filter: blur(10px); flex: none; } .fishbowl .tp-btns { display: flex; gap: 6px; } .fishbowl .icon-btn.play { border-color: var(--cyan); color: var(--cyan); box-shadow: var(--glow); } /* The curtain-call button: a text pill (not a square icon), amber like the judge's gavel. */ .fishbowl .icon-btn.judge-now { width: auto; min-width: 0; padding: 0 13px; gap: 6px; font-family: var(--font-display); font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase; border-color: rgba(227,193,76,0.5); color: var(--amber); } .fishbowl .icon-btn.judge-now:hover { border-color: var(--amber); box-shadow: 0 0 14px rgba(227,193,76,0.35); color: var(--amber); } .fishbowl .scrub { flex: 1; } .fishbowl .tp-count { font-size: 12px; color: var(--ink-mid); white-space: nowrap; } .fishbowl .speed-seg .seg-b.on { color: var(--cyan); } /* ---- saga toast ---- */ .fishbowl .saga-toast { position: absolute; bottom: 130px; left: 50%; transform: translateX(-50%); z-index: 45; padding: 16px 22px; max-width: 520px; text-align: center; border-color: var(--lime); box-shadow: 0 0 30px rgba(155,232,107,0.25); animation: vbDrop 0.4s ease; } .fishbowl .saga-head { font-size: 15px; color: var(--lime); margin: 8px 0 0; line-height: 1.4; } /* ============================================================ ✦ ENHANCEMENTS — polish layer (additive; overrides by source order) ------------------------------------------------------------ The theater markup is delivered through Gradio gr.HTML islands, each wrapped in a `.fishbowl fb-` scope root by app.py. The base rules above port the prototype faithfully; this layer makes the *Gradio-shaped* stage actually sing — the constellation re-flows fluidly instead of clipping at the narrow pane width, the meters read as instruments, and the cast cards gain depth, entrance motion, and hover life. Inspired by ui/raw, tuned for the live app. ============================================================ */ /* Gradio caps the content column at 1024px; the theater wants more room so the constellation can hold two columns of MindCards and the stage can breathe. */ .gradio-container .main, .gradio-container .main.fillable { max-width: 1240px !important; } /* pane scope roots are plain blocks; let the inner stage own the layout */ .fishbowl.fb-stage, .fishbowl.fb-feed, .fishbowl.fb-meters, .fishbowl.fb-verdict { display: block; } /* ---- topbar: branded bar with a status cluster on the right ---- */ .fishbowl .topbar { justify-content: space-between; gap: 16px; box-shadow: 0 1px 0 var(--line-soft), 0 8px 28px -22px rgba(79,230,210,0.5); } .fishbowl .brand .logo { font-size: 21px; } .fishbowl .topbar-status { display: flex; align-items: center; gap: 14px; flex: none; } .fishbowl .topbar-tag { color: var(--ink-faint); white-space: nowrap; } .fishbowl .chip.live { color: var(--lime); border-color: rgba(155,232,107,0.34); background: rgba(155,232,107,0.08); } .fishbowl .chip.live .live-dot { width: 6px; height: 6px; } @media (max-width: 760px) { .fishbowl .topbar-tag { display: none; } } /* ---- meters: stack each stat so its label & value never collide ---- */ .fishbowl .meters.panel { background: linear-gradient(180deg, rgba(10,34,43,0.6), rgba(6,20,27,0.55)); border: 1px solid var(--line); border-radius: var(--r-lg); } .fishbowl .meter-stats { gap: 18px; padding-top: 2px; } .fishbowl .stat { display: flex; flex-direction: column; gap: 3px; min-width: 0; } .fishbowl .stat .eyebrow { font-size: 8px; letter-spacing: 0.2em; } .fishbowl .stat .disp { font-size: 13px; line-height: 1.1; } .fishbowl .meters .bar { height: 6px; box-shadow: inset 0 0 0 1px var(--line-soft); } /* ============================================================ CONSTELLATION — fluid, never-clipping grid of MindCards. The prototype's rigid 3×3 grid-area placement assumes a wide square stage; the Gradio pane is narrower, so we re-flow the cards into a centered wrap and float the scene "core" as an ambient marquee + radial glow behind them. ============================================================ */ .fishbowl.fb-constellation .constellation { position: relative; height: 100%; } .fishbowl.fb-constellation .stage { display: flex; flex-wrap: wrap; gap: 16px; justify-content: center; align-content: flex-start; grid-template-columns: none; grid-template-rows: none; padding: 16px 18px 26px; min-height: 380px; overflow: visible; position: relative; } /* the energy at the heart of the bowl — a soft radial breath behind the cast */ .fishbowl.fb-constellation .stage::before { content: ""; position: absolute; inset: 0; pointer-events: none; background: radial-gradient(60% 55% at 50% 46%, rgba(79,230,210,0.10), transparent 70%); z-index: 0; } .fishbowl.fb-constellation .ring { display: contents; } .fishbowl.fb-constellation .ring-slot { display: block; grid-area: auto !important; z-index: 2; flex: 1 1 260px; max-width: 330px; min-width: 250px; } .fishbowl.fb-constellation .mind { width: 100%; perspective: 1400px; } .fishbowl.fb-constellation .mind-stage { width: 100%; } /* the scene core, re-cast as a centered marquee above the cast (no overlap) */ .fishbowl.fb-constellation .core { position: static; transform: none; order: -1; flex: 1 1 100%; flex-direction: row; flex-wrap: wrap; align-items: baseline; justify-content: center; gap: 12px; padding: 4px 0 10px; z-index: 1; } .fishbowl.fb-constellation .core-glyph { font-size: 30px; } .fishbowl.fb-constellation .core-title { font-size: 15px; letter-spacing: 0.14em; } .fishbowl.fb-constellation .core-round { color: var(--ink-faint); } /* ---- MindCard depth + life: entrance, hover lift, richer speaking glow ---- */ /* The stage HTML is rebuilt every tick (wholesale innerHTML swap), so a per-card entrance replayed the full staggered cascade on the whole cast each time — the dominant "screen reload" flicker. Instead, only the card that just acted gives a small settle, drawing the eye to the active speaker while the rest of the cast holds perfectly still. */ @keyframes mindSpeakIn { from { opacity: 0.6; transform: translateY(4px); } to { opacity: 1; transform: none; } } @media (prefers-reduced-motion: no-preference) { .fishbowl.fb-constellation .mind.speaking { animation: mindSpeakIn 0.3s cubic-bezier(0.2,0.7,0.2,1) both; } } .fishbowl .mind-front { transition: border-color 0.22s ease, box-shadow 0.22s ease, transform 0.22s ease; box-shadow: 0 10px 30px -20px rgba(0,0,0,0.9); } .fishbowl.fb-constellation .mind:hover .mind-front { transform: translateY(-3px); border-color: var(--line-hot); box-shadow: 0 16px 40px -24px rgba(0,0,0,0.9), var(--glow); } /* a speaking mind pulses its accent ring so the eye follows the live voice */ @media (prefers-reduced-motion: no-preference) { .fishbowl .mind.speaking .mind-front { animation: speakGlow 1.8s ease-in-out infinite; } } @keyframes speakGlow { 0%, 100% { box-shadow: 0 0 18px color-mix(in oklab, var(--ac) 32%, transparent); } 50% { box-shadow: 0 0 30px color-mix(in oklab, var(--ac) 55%, transparent); } } .fishbowl .bubble.said { box-shadow: inset 0 0 0 1px var(--line-soft); } /* ---- SPLIT (Lab Readout): let the omniscient table flow full-width ---- */ .fishbowl.fb-split .stage { display: block; padding: 18px; min-height: 0; overflow: visible; } .fishbowl.fb-split .core { position: static; transform: none; display: block; width: 100%; text-align: left; pointer-events: auto; } .fishbowl.fb-split .splitview { padding: 6px 0 0; max-width: 100%; } /* ---- FEED pane: read like a live transcript, comfortable scroll ---- */ .fishbowl.fb-feed .feed { padding: 4px 4px 8px; max-height: 50vh; } .fishbowl .fe.narr p { font-size: 13px; } /* ---- NARRATOR feed entrance is already animated (feIn); add a hover tint ---- */ .fishbowl .fe.say { transition: border-color 0.18s ease, background 0.18s ease; } .fishbowl .fe.say:hover { border-left-color: color-mix(in oklab, var(--ac) 90%, white); background: rgba(79,230,210,0.05); } /* ============================================================ LAB polish — the composer is native Gradio chrome; lift the panels to crisp lab instruments and make the headline + Summon button carry weight. (Scoped to .gradio-container so it rides on top of the theme, not .fishbowl.) ============================================================ */ .gradio-container .gr-group { border-radius: var(--r-lg) !important; box-shadow: inset 0 1px 0 rgba(120,222,214,0.06), 0 18px 40px -34px rgba(0,0,0,0.9) !important; } /* the Lab section headers ("01 · Scenario & Goal") read as eyebrowed dividers */ .gradio-container .gr-group h1, .gradio-container .gr-group h2, .gradio-container .gr-group h3, .gradio-container .gr-group h4, .gradio-container .gr-group p strong:first-child { color: var(--ink) !important; } /* Summon button — the primary call to action gets the full phosphor glow */ .gradio-container button.primary { letter-spacing: 0.12em !important; text-transform: uppercase !important; box-shadow: 0 0 18px rgba(79,230,210,0.35) !important; transition: box-shadow 0.18s ease, transform 0.12s ease !important; } .gradio-container button.primary:hover { box-shadow: 0 0 28px rgba(79,230,210,0.6) !important; } .gradio-container button.primary:active { transform: translateY(1px) !important; } /* ============================================================ The Lab — per-agent cards (§03) + the Judge card (§04). Each non-judge mind is an editable card: a name/archetype head, a read-only fact strip (role / profile / what it listens for / what it may emit), then the model · tool · persona · schedule controls. The card gets a left phosphor rail + lift so the cast reads as a stack of distinct minds rather than one long form. The Judge card glows amber (the colour of the Wood's Reckoning) so the verdict-maker stands apart. ============================================================ */ .gradio-container .lab-agent-card { position: relative; border: 1px solid var(--line) !important; border-left: 2px solid var(--line-hot) !important; border-radius: var(--r-lg) !important; background: linear-gradient(180deg, rgba(10,34,43,0.55), rgba(7,24,31,0.4)) !important; box-shadow: inset 0 1px 0 rgba(120,222,214,0.07), 0 16px 38px -32px rgba(0,0,0,0.9) !important; margin-bottom: 10px !important; transition: border-color 0.18s ease, box-shadow 0.18s ease; } .gradio-container .lab-agent-card:hover { border-left-color: var(--cyan) !important; box-shadow: inset 0 1px 0 rgba(120,222,214,0.1), 0 0 18px -6px rgba(79,230,210,0.4) !important; } .gradio-container .lab-agent-meta { margin-bottom: 2px; } .lab-agent-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; } .lab-agent-name { font-family: var(--font-display); font-weight: 700; letter-spacing: 0.04em; color: var(--ink); text-shadow: var(--glow); } .lab-agent-arch { font-style: italic; font-size: 12px; color: var(--ink-dim); } .lab-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; } .lab-chip { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border: 1px solid var(--line-soft); border-radius: 999px; background: rgba(5,18,26,0.5); font-size: 10.5px; line-height: 1.5; max-width: 100%; } .lab-chip-k { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.12em; font-size: 8.5px; color: var(--ink-dim); } .lab-chip-v { color: var(--ink-mid); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; } /* The Judge card — amber rail/glow so the verdict-maker reads as set-apart. */ .gradio-container .lab-judge-card { border: 1px solid rgba(255,207,107,0.34) !important; border-left: 2px solid var(--amber) !important; border-radius: var(--r-lg) !important; background: linear-gradient(180deg, rgba(40,30,10,0.34), rgba(7,24,31,0.4)) !important; box-shadow: inset 0 1px 0 rgba(255,207,107,0.08), 0 16px 38px -32px rgba(0,0,0,0.9) !important; } /* ============================================================ The Lab — progressive disclosure (Quick / Director's cut). The form is now a guided console: pick a world, read its live digest, drop a seed, Summon — with every advanced knob folded behind a mode switch so a newcomer is never handed the cockpit. ============================================================ */ /* World digest — the at-a-glance card under the scenario picker. A framed readout (title in phosphor display, goal in calm body, capability badges) so you understand a world before touching a single control. */ .gradio-container .lab-ws-wrap { padding: 0 !important; } .lab-ws { position: relative; border: 1px solid var(--line); border-left: 3px solid var(--cyan); border-radius: var(--r-lg); padding: 13px 16px 14px; margin: 2px 0 4px; background: radial-gradient(120% 140% at 0% 0%, rgba(79,230,210,0.09), transparent 60%), linear-gradient(180deg, rgba(10,34,43,0.6), rgba(7,24,31,0.42)); box-shadow: inset 0 1px 0 rgba(120,222,214,0.08), 0 18px 40px -34px rgba(0,0,0,0.9); } .lab-ws-title { font-family: var(--font-display); font-weight: 700; font-size: 15px; letter-spacing: 0.03em; color: var(--ink); text-shadow: var(--glow); } .lab-ws-goal { margin-top: 5px; font-family: var(--font-body); font-size: 12.5px; line-height: 1.5; color: var(--ink-mid); max-width: 64ch; } .lab-ws-badges { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; } .lab-badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border: 1px solid var(--line); border-radius: 999px; background: rgba(5,18,26,0.55); font-family: var(--font-display); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-mid); } .lab-badge.badge-judge { border-color: rgba(255,207,107,0.45); color: var(--amber); } .lab-badge.badge-tool { border-color: rgba(155,232,107,0.45); color: var(--lime); } .lab-badge.badge-muted { color: var(--ink-faint); opacity: 0.78; } /* Mode switch — a segmented control, not a stack of radio dots. The selected speed glows; the other reads as a quiet sibling tab. */ .gradio-container .lab-mode { border: 0 !important; background: transparent !important; } .gradio-container .lab-mode [role="radiogroup"], .gradio-container .lab-mode .wrap { display: inline-flex !important; gap: 0 !important; padding: 3px !important; border: 1px solid var(--line) !important; border-radius: 999px !important; background: rgba(5,18,26,0.6) !important; } .gradio-container .lab-mode label { margin: 0 !important; padding: 6px 18px !important; border: 0 !important; border-radius: 999px !important; background: transparent !important; color: var(--ink-dim) !important; font-family: var(--font-display) !important; font-size: 11.5px !important; letter-spacing: 0.05em !important; cursor: pointer; transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease; } .gradio-container .lab-mode label input { display: none !important; } .gradio-container .lab-mode label:hover { color: var(--ink) !important; } .gradio-container .lab-mode label:has(input:checked) { background: linear-gradient(180deg, rgba(79,230,210,0.22), rgba(79,230,210,0.1)) !important; color: var(--ink) !important; box-shadow: 0 0 16px -4px rgba(79,230,210,0.5), inset 0 1px 0 rgba(120,222,214,0.15) !important; } /* Director's cut — the advanced column. A left rail + faint inset so the deep controls read as a distinct workspace that opened, not part of the quick flow. */ .gradio-container .lab-advanced { border-left: 2px solid var(--line-soft) !important; padding-left: 12px !important; margin-top: 6px !important; /* Opacity-only fade — NEVER leave a transform on this column. A lingering transform (e.g. translateY held by animation fill-mode) makes it the containing block for descendant gr.Dropdown option popups, detaching the list from its input. Keep it transform-free so the model/judge dropdowns anchor correctly. */ animation: lab-adv-in 0.28s ease; } @keyframes lab-adv-in { from { opacity: 0; } to { opacity: 1; } } /* Per-agent accordion — the collapsed minds. Phosphor rail on the header so the cast reads as a stack of distinct instruments; the open card lifts. */ .gradio-container .lab-agent-card { border: 1px solid var(--line) !important; border-left: 2px solid var(--line-hot) !important; border-radius: var(--r-lg) !important; background: linear-gradient(180deg, rgba(10,34,43,0.4), rgba(7,24,31,0.3)) !important; margin-bottom: 8px !important; overflow: visible !important; /* let the model dropdown's option list spill past the card */ transition: border-color 0.18s ease, box-shadow 0.18s ease; } .gradio-container .lab-agent-card:hover { border-left-color: var(--cyan) !important; } .gradio-container .lab-agent-card > button, .gradio-container .lab-agent-card .label-wrap { font-family: var(--font-display) !important; letter-spacing: 0.03em; color: var(--ink) !important; } /* Seed "edit" toggle — a compact ghost button that sits beside the preset dropdown (nudged down so it lines up with the input, not the label above it). */ .gradio-container .lab-seed-edit { align-self: flex-end !important; min-width: 74px !important; margin-bottom: 1px !important; background: rgba(5,18,26,0.6) !important; border: 1px solid var(--line) !important; color: var(--ink-mid) !important; font-family: var(--font-display) !important; letter-spacing: 0.05em; } .gradio-container .lab-seed-edit:hover { border-color: var(--cyan) !important; color: var(--ink) !important; box-shadow: 0 0 14px -4px rgba(79,230,210,0.5) !important; } /* ============================================================ ✦ JUMP TO LIVE — the feed's "you've scrolled away" affordance ------------------------------------------------------------ Lives on the #feed-html / #stage-html Gradio islands (a sibling of the `.fishbowl` inner wrapper, NOT inside it), so these rules are intentionally UNSCOPED and read the palette from :root. The pill auto-follows the live edge and only surfaces when the viewer has scrolled up to read history — see the feed auto-follow script in assets/head.html. ============================================================ */ .feed-jump { position: absolute; left: 50%; bottom: 14px; transform: translate(-50%, 14px); z-index: 30; display: inline-flex; align-items: center; gap: 8px; padding: 7px 14px 7px 11px; font-family: var(--font-display, "Martian Mono", monospace); font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--bg, #05121a); background: var(--cyan, #4fe6d2); border: 1px solid color-mix(in oklab, var(--cyan, #4fe6d2) 70%, #fff 0%); border-radius: 999px; cursor: pointer; box-shadow: 0 6px 22px -6px rgba(79, 230, 210, 0.7), 0 0 0 1px rgba(5, 18, 26, 0.4) inset; opacity: 0; pointer-events: none; visibility: hidden; transition: opacity 0.22s ease, transform 0.22s cubic-bezier(0.2, 1.2, 0.3, 1); } .feed-jump.show { opacity: 1; visibility: visible; pointer-events: auto; transform: translate(-50%, 0); } .feed-jump:hover { background: color-mix(in oklab, var(--cyan, #4fe6d2) 85%, #fff 15%); box-shadow: 0 8px 26px -5px rgba(79, 230, 210, 0.85); } .feed-jump:active { transform: translate(-50%, 1px); } .feed-jump:focus-visible { outline: 2px solid var(--bg, #05121a); outline-offset: 2px; } .feed-jump .fj-label { font-weight: 700; line-height: 1; } .feed-jump .fj-arrow { font-size: 12px; line-height: 1; transform: translateY(0.5px); } .feed-jump .fj-pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--bg, #05121a); box-shadow: 0 0 0 0 rgba(5, 18, 26, 0.55); animation: fjPulse 1.6s ease-in-out infinite; } @keyframes fjPulse { 0% { box-shadow: 0 0 0 0 rgba(5, 18, 26, 0.55); } 70% { box-shadow: 0 0 0 6px rgba(5, 18, 26, 0); } 100% { box-shadow: 0 0 0 0 rgba(5, 18, 26, 0); } } @media (prefers-reduced-motion: reduce) { .feed-jump { transition: opacity 0.2s ease; } .feed-jump.show { transform: translate(-50%, 0); } .feed-jump .fj-pulse { animation: none; } } /* ── Archive drawer: "my past sessions" (Lab → Load → read-only replay) ────────── The accordion + cards are real Gradio components (not inside the `.fishbowl` scope root), so these rules target their `elem_classes` directly and lean on `!important` to win over Gradio's own cascade, like the seed-edit rules above. */ .gradio-container .archive-drawer { border: 1px solid var(--line-soft) !important; border-radius: var(--r-lg) !important; background: linear-gradient(180deg, rgba(10,34,43,0.55), rgba(5,18,26,0.35)) !important; margin-top: 4px !important; } .gradio-container .archive-drawer > .label-wrap, .gradio-container .archive-drawer button.label-wrap, .gradio-container .archive-drawer span.label-wrap { font-family: var(--font-display) !important; font-size: 11px !important; letter-spacing: 0.16em !important; text-transform: uppercase !important; color: var(--ink-mid) !important; } .gradio-container .archive-refresh { min-width: 0 !important; align-self: flex-end !important; font-family: var(--font-display) !important; font-size: 9.5px !important; letter-spacing: 0.18em !important; text-transform: uppercase !important; color: var(--ink-dim) !important; background: transparent !important; border: 1px solid var(--line-soft) !important; border-radius: var(--r) !important; } .gradio-container .archive-refresh:hover { color: var(--cyan) !important; border-color: var(--line-hot) !important; box-shadow: var(--glow) !important; } /* Each past run is a full-width, left-aligned monospace card with a phosphor spine. The whole row is the click target (it loads the replay). */ .gradio-container .archive-card { display: block !important; width: 100% !important; text-align: left !important; font-family: var(--font-body) !important; font-size: 12px !important; letter-spacing: 0.02em !important; color: var(--ink) !important; background: var(--panel) !important; border: 1px solid var(--line-soft) !important; border-left: 2px solid var(--teal) !important; border-radius: var(--r) !important; padding: 10px 12px !important; margin: 6px 0 0 !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease !important; } .gradio-container .archive-card:hover { border-color: var(--line-hot) !important; border-left-color: var(--cyan) !important; color: var(--cyan) !important; box-shadow: var(--glow) !important; transform: translateX(2px) !important; } .fishbowl .archive-empty { border: 1px dashed var(--line-soft); border-radius: var(--r-lg); padding: 18px 16px; background: rgba(5, 18, 26, 0.35); } .fishbowl .archive-empty .ae-body { font-family: var(--font-body); font-size: 12px; color: var(--ink-dim); margin-top: 6px; line-height: 1.5; max-width: 46ch; } /* ============================================================ ✦ LIVE-SHOW UX — auto-start cue, single-control transport, loud end-card ------------------------------------------------------------ Three small surfaces, big clarity wins: 1. a broadcast "lower third" naming who we're waiting on — model calls take seconds, and silence reads as a freeze; 2. the lone Play/Pause hero button reads as the one thing to press; 3. the end-of-show card is impossible to miss (it used to inherit as ~12px text with no card of its own). ============================================================ */ /* ---- 1 · "who's thinking" badge — pinned to the TOP of the stage so it's in view the instant a model starts working, no scrolling required (it used to sit at the bottom of a tall stage and slip below the fold) ---- */ .fishbowl.fb-stage { position: relative; } .fishbowl .thinking-strip { position: sticky; top: 10px; z-index: 40; pointer-events: none; width: fit-content; max-width: calc(100% - 24px); margin: 2px auto 12px; display: flex; align-items: center; gap: 11px; padding: 9px 17px 9px 14px; font-family: var(--font-display); font-size: 11.5px; letter-spacing: 0.06em; color: var(--cyan); background: rgba(6, 20, 27, 0.94); border: 1px solid var(--line-hot, rgba(79, 230, 210, 0.5)); border-radius: 999px; box-shadow: 0 10px 34px -12px rgba(0, 0, 0, 0.85), 0 0 22px rgba(79, 230, 210, 0.3); backdrop-filter: blur(8px); animation: tsIn 0.35s ease both; } @keyframes tsIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } } .fishbowl .thinking-strip .ts-orb { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--cyan); box-shadow: 0 0 10px var(--cyan), inset 0 0 4px #fff; animation: tsPulse 1.1s ease-in-out infinite; } @keyframes tsPulse { 0%, 100% { opacity: 0.45; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.15); } } .fishbowl .thinking-strip .ts-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-shadow: 0 0 10px rgba(79, 230, 210, 0.4); } .fishbowl .thinking-strip .ts-dots { display: inline-flex; gap: 3px; flex: none; } .fishbowl .thinking-strip .ts-dots i { width: 4px; height: 4px; border-radius: 50%; background: var(--cyan); opacity: 0.3; animation: tsDot 1.1s ease-in-out infinite; } .fishbowl .thinking-strip .ts-dots i:nth-child(2) { animation-delay: 0.18s; } .fishbowl .thinking-strip .ts-dots i:nth-child(3) { animation-delay: 0.36s; } @keyframes tsDot { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } } /* ---- 2 · single Play/Pause hero button — the obvious thing to press, up in the showbar (native gr.Button → reach it via the container, not the .fishbowl scope) ---- */ .gradio-container .play-hero, .gradio-container .play-hero button { min-width: 132px !important; padding: 10px 22px !important; font-family: var(--font-display) !important; font-size: 13px !important; font-weight: 800 !important; letter-spacing: 0.16em !important; text-transform: uppercase !important; color: #04221c !important; background: linear-gradient(180deg, var(--cyan), #2bc4b4) !important; border: 1px solid var(--cyan) !important; border-radius: 999px !important; box-shadow: 0 0 20px rgba(79, 230, 210, 0.5) !important; animation: playBreath 2.4s ease-in-out infinite; } @keyframes playBreath { 0%, 100% { box-shadow: 0 0 16px rgba(79, 230, 210, 0.38); } 50% { box-shadow: 0 0 28px rgba(79, 230, 210, 0.72); } } .gradio-container .play-hero:hover, .gradio-container .play-hero button:hover { box-shadow: 0 0 32px rgba(79, 230, 210, 0.85) !important; filter: brightness(1.1) !important; } @media (prefers-reduced-motion: reduce) { .gradio-container .play-hero, .gradio-container .play-hero button { animation: none; } } /* ---- 3 · the end-of-show card (verdict ruling + STOPPED both reuse .verdict.banner) ---- */ .fishbowl .verdict.banner { position: relative; display: flex; flex-direction: column; align-items: center; gap: 10px; margin: 14px auto; max-width: 560px; padding: 22px 30px 24px; text-align: center; border: 1px solid var(--lime); border-radius: 16px; background: radial-gradient(120% 90% at 50% -10%, rgba(155, 232, 107, 0.18), transparent 62%), rgba(6, 20, 27, 0.96); box-shadow: 0 0 50px rgba(155, 232, 107, 0.32), inset 0 0 30px rgba(155, 232, 107, 0.08); backdrop-filter: blur(10px); animation: endCard 0.55s cubic-bezier(0.18, 1.3, 0.35, 1) both; } @keyframes endCard { from { opacity: 0; transform: translateY(-14px) scale(0.96); } to { opacity: 1; transform: none; } } .fishbowl .verdict.banner .eyebrow { font-size: 12px; letter-spacing: 0.34em; color: var(--lime); text-shadow: 0 0 14px rgba(155, 232, 107, 0.6); } .fishbowl .verdict.banner .vb-text { font-size: 15.5px; line-height: 1.5; color: var(--ink); max-width: 48ch; } .fishbowl .verdict.banner .vb-winner { font-size: 18px; letter-spacing: 0.12em; } /* the STOPPED / ended-without-verdict variant: coral, not celebratory lime */ .fishbowl .verdict.banner.stopped { border-color: var(--coral); background: radial-gradient(120% 90% at 50% -10%, rgba(255, 143, 125, 0.16), transparent 62%), rgba(6, 20, 27, 0.96); box-shadow: 0 0 50px rgba(255, 143, 125, 0.28), inset 0 0 30px rgba(255, 143, 125, 0.08); } .fishbowl .verdict.banner.stopped .eyebrow { color: var(--coral); text-shadow: 0 0 14px rgba(255, 143, 125, 0.6); } @media (prefers-reduced-motion: reduce) { .fishbowl .thinking-strip, .fishbowl .thinking-strip .ts-orb, .fishbowl .thinking-strip .ts-dots i, .fishbowl .verdict.banner { animation: none; } }