agharsallah
feat(rafters): implement navigable gallery for illustrated and spoken beats with improved audio handling
5e2e876 | /* ============================================================ | |
| 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) ; | |
| color: var(--ink) ; | |
| font-family: var(--font-body) ; | |
| max-width: 100% ; | |
| } | |
| .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 ; } | |
| .gradio-container footer { display: none ; } | |
| /* ------------------------------------------------------------ | |
| 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) ; | |
| } | |
| .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) ; | |
| } | |
| .gradio-container input::placeholder, | |
| .gradio-container textarea::placeholder { | |
| color: var(--ink-faint) ; | |
| 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) ; | |
| color: var(--ink) ; | |
| border-color: var(--line) ; | |
| } | |
| .gradio-container table td, | |
| .gradio-container table tbody tr, | |
| .gradio-container .table-wrap td { | |
| background: rgba(5, 18, 26, 0.45) ; | |
| color: var(--ink) ; | |
| border-color: var(--line-soft) ; | |
| } | |
| .gradio-container table tbody tr:nth-child(even) td { | |
| background: rgba(10, 34, 43, 0.5) ; | |
| } | |
| .gradio-container table tbody tr:hover td { background: rgba(79, 230, 210, 0.07) ; } | |
| .gradio-container .table-wrap, .gradio-container [data-testid="dataframe"] { background: transparent ; } | |
| /* the modern dataframe paints rows as virtual divs (row-odd/row-even), not <tr> */ | |
| .gradio-container .virtual-row { background: rgba(5, 18, 26, 0.45) ; } | |
| .gradio-container .virtual-row.row-odd { background: rgba(10, 34, 43, 0.5) ; } | |
| /* 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) ; | |
| color: var(--ink) ; | |
| border: 1px solid var(--line) ; | |
| } | |
| .gradio-container button.secondary:hover { | |
| border-color: var(--line-hot) ; | |
| color: #fff ; | |
| } | |
| /* 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) ; | |
| opacity: 1 ; | |
| } | |
| .gradio-container .gr-box.disabled, | |
| .gradio-container fieldset:disabled { opacity: 0.96 ; } | |
| /* 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) ; | |
| border-color: var(--line) ; | |
| } | |
| .gradio-container .styler, | |
| .gradio-container div.form { | |
| background: transparent ; | |
| border-color: var(--line) ; | |
| } | |
| /* 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) ; | |
| border: 1px solid var(--line) ; | |
| color: var(--ink-mid) ; | |
| } | |
| .gradio-container label:has(> input[type="radio"]):hover, | |
| .gradio-container label:has(> input[type="checkbox"]):hover { | |
| border-color: var(--line-hot) ; | |
| color: var(--ink) ; | |
| } | |
| .gradio-container label:has(> input[type="radio"]:checked), | |
| .gradio-container label:has(> input[type="checkbox"]:checked) { | |
| background: rgba(79, 230, 210, 0.14) ; | |
| border-color: var(--cyan) ; | |
| color: var(--cyan) ; | |
| } | |
| .gradio-container label:has(> input[type="radio"]) span, | |
| .gradio-container label:has(> input[type="checkbox"]) span { color: inherit ; } | |
| /* 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) ; | |
| border: 1px solid var(--ink-dim) ; | |
| accent-color: var(--cyan); | |
| } | |
| .gradio-container input[type="checkbox"]:checked, | |
| .gradio-container input[type="radio"]:checked { | |
| background-color: var(--cyan) ; | |
| border-color: var(--cyan) ; | |
| } | |
| /* 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: | |
| <div class="crt-bg"></div> | |
| <div class="crt-grid"></div> | |
| <div class="crt-scan"></div> | |
| <div class="crt-vignette"></div> | |
| and wraps the live theater in <div class="crt-flicker"> 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)) ; 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) ; 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% ; border-radius: var(--r, 4px) ; | |
| border: 1px solid color-mix(in oklab, var(--cm) 45%, transparent) ; | |
| 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) ; } | |
| /* ---- 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-<role>` 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 ; } | |
| /* 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 ; | |
| 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) ; | |
| box-shadow: inset 0 1px 0 rgba(120,222,214,0.06), 0 18px 40px -34px rgba(0,0,0,0.9) ; | |
| } | |
| /* 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) ; | |
| } | |
| /* Summon button — the primary call to action gets the full phosphor glow */ | |
| .gradio-container button.primary { | |
| letter-spacing: 0.12em ; | |
| text-transform: uppercase ; | |
| box-shadow: 0 0 18px rgba(79,230,210,0.35) ; | |
| transition: box-shadow 0.18s ease, transform 0.12s ease ; | |
| } | |
| .gradio-container button.primary:hover { box-shadow: 0 0 28px rgba(79,230,210,0.6) ; } | |
| .gradio-container button.primary:active { transform: translateY(1px) ; } | |
| /* ============================================================ | |
| 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) ; | |
| border-left: 2px solid var(--line-hot) ; | |
| border-radius: var(--r-lg) ; | |
| background: linear-gradient(180deg, rgba(10,34,43,0.55), rgba(7,24,31,0.4)) ; | |
| box-shadow: inset 0 1px 0 rgba(120,222,214,0.07), 0 16px 38px -32px rgba(0,0,0,0.9) ; | |
| margin-bottom: 10px ; | |
| transition: border-color 0.18s ease, box-shadow 0.18s ease; | |
| } | |
| .gradio-container .lab-agent-card:hover { | |
| border-left-color: var(--cyan) ; | |
| box-shadow: inset 0 1px 0 rgba(120,222,214,0.1), 0 0 18px -6px rgba(79,230,210,0.4) ; | |
| } | |
| .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) ; | |
| border-left: 2px solid var(--amber) ; | |
| border-radius: var(--r-lg) ; | |
| background: linear-gradient(180deg, rgba(40,30,10,0.34), rgba(7,24,31,0.4)) ; | |
| box-shadow: inset 0 1px 0 rgba(255,207,107,0.08), 0 16px 38px -32px rgba(0,0,0,0.9) ; | |
| } | |
| /* ============================================================ | |
| 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 ; } | |
| .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 ; background: transparent ; } | |
| .gradio-container .lab-mode [role="radiogroup"], | |
| .gradio-container .lab-mode .wrap { | |
| display: inline-flex ; | |
| gap: 0 ; | |
| padding: 3px ; | |
| border: 1px solid var(--line) ; | |
| border-radius: 999px ; | |
| background: rgba(5,18,26,0.6) ; | |
| } | |
| .gradio-container .lab-mode label { | |
| margin: 0 ; | |
| padding: 6px 18px ; | |
| border: 0 ; | |
| border-radius: 999px ; | |
| background: transparent ; | |
| color: var(--ink-dim) ; | |
| font-family: var(--font-display) ; | |
| font-size: 11.5px ; | |
| letter-spacing: 0.05em ; | |
| cursor: pointer; | |
| transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease; | |
| } | |
| .gradio-container .lab-mode label input { display: none ; } | |
| .gradio-container .lab-mode label:hover { color: var(--ink) ; } | |
| .gradio-container .lab-mode label:has(input:checked) { | |
| background: linear-gradient(180deg, rgba(79,230,210,0.22), rgba(79,230,210,0.1)) ; | |
| color: var(--ink) ; | |
| box-shadow: 0 0 16px -4px rgba(79,230,210,0.5), inset 0 1px 0 rgba(120,222,214,0.15) ; | |
| } | |
| /* 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) ; | |
| padding-left: 12px ; | |
| margin-top: 6px ; | |
| /* 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) ; | |
| border-left: 2px solid var(--line-hot) ; | |
| border-radius: var(--r-lg) ; | |
| background: linear-gradient(180deg, rgba(10,34,43,0.4), rgba(7,24,31,0.3)) ; | |
| margin-bottom: 8px ; | |
| overflow: visible ; /* 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) ; } | |
| .gradio-container .lab-agent-card > button, | |
| .gradio-container .lab-agent-card .label-wrap { | |
| font-family: var(--font-display) ; | |
| letter-spacing: 0.03em; | |
| color: var(--ink) ; | |
| } | |
| /* 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 ; | |
| min-width: 74px ; | |
| margin-bottom: 1px ; | |
| background: rgba(5,18,26,0.6) ; | |
| border: 1px solid var(--line) ; | |
| color: var(--ink-mid) ; | |
| font-family: var(--font-display) ; | |
| letter-spacing: 0.05em; | |
| } | |
| .gradio-container .lab-seed-edit:hover { | |
| border-color: var(--cyan) ; | |
| color: var(--ink) ; | |
| box-shadow: 0 0 14px -4px rgba(79,230,210,0.5) ; | |
| } | |
| /* ============================================================ | |
| ✦ 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) ; | |
| border-radius: var(--r-lg) ; | |
| background: linear-gradient(180deg, rgba(10,34,43,0.55), rgba(5,18,26,0.35)) ; | |
| margin-top: 4px ; | |
| } | |
| .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) ; | |
| font-size: 11px ; | |
| letter-spacing: 0.16em ; | |
| text-transform: uppercase ; | |
| color: var(--ink-mid) ; | |
| } | |
| .gradio-container .archive-refresh { | |
| min-width: 0 ; | |
| align-self: flex-end ; | |
| font-family: var(--font-display) ; | |
| font-size: 9.5px ; | |
| letter-spacing: 0.18em ; | |
| text-transform: uppercase ; | |
| color: var(--ink-dim) ; | |
| background: transparent ; | |
| border: 1px solid var(--line-soft) ; | |
| border-radius: var(--r) ; | |
| } | |
| .gradio-container .archive-refresh:hover { | |
| color: var(--cyan) ; | |
| border-color: var(--line-hot) ; | |
| box-shadow: var(--glow) ; | |
| } | |
| /* 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 ; | |
| width: 100% ; | |
| text-align: left ; | |
| font-family: var(--font-body) ; | |
| font-size: 12px ; | |
| letter-spacing: 0.02em ; | |
| color: var(--ink) ; | |
| background: var(--panel) ; | |
| border: 1px solid var(--line-soft) ; | |
| border-left: 2px solid var(--teal) ; | |
| border-radius: var(--r) ; | |
| padding: 10px 12px ; | |
| margin: 6px 0 0 ; | |
| white-space: nowrap ; | |
| overflow: hidden ; | |
| text-overflow: ellipsis ; | |
| transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease ; | |
| } | |
| .gradio-container .archive-card:hover { | |
| border-color: var(--line-hot) ; | |
| border-left-color: var(--cyan) ; | |
| color: var(--cyan) ; | |
| box-shadow: var(--glow) ; | |
| transform: translateX(2px) ; | |
| } | |
| .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 ; | |
| padding: 10px 22px ; | |
| font-family: var(--font-display) ; | |
| font-size: 13px ; font-weight: 800 ; | |
| letter-spacing: 0.16em ; text-transform: uppercase ; | |
| color: #04221c ; | |
| background: linear-gradient(180deg, var(--cyan), #2bc4b4) ; | |
| border: 1px solid var(--cyan) ; border-radius: 999px ; | |
| box-shadow: 0 0 20px rgba(79, 230, 210, 0.5) ; | |
| 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) ; | |
| filter: brightness(1.1) ; | |
| } | |
| @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; } | |
| } | |