Spaces:
Sleeping
Sleeping
| /* Persona panel chrome — parchment palette, self-contained (scoped on .persona-view) | |
| * so it matches the app whether the OS is light or dark. Mirrors the spriteScene.css | |
| * approach. */ | |
| .persona-view { | |
| --p-ink: #141821; --p-muted: #6d6a5f; --p-paper: #f3ebdc; --p-paper-2: #ece2cc; | |
| --p-card: #fbf6ea; --p-transmit: #d8271a; | |
| --p-sans: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| --p-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace; | |
| display: flex; height: 100%; width: 100%; box-sizing: border-box; | |
| color: var(--p-ink); font-family: var(--p-sans); | |
| } | |
| .persona-view * { box-sizing: border-box; } | |
| .persona-controls { | |
| width: 280px; flex-shrink: 0; border-right: 2px solid var(--p-ink); | |
| background: var(--p-paper-2); overflow-y: auto; padding: 16px; | |
| display: flex; flex-direction: column; gap: 8px; | |
| } | |
| .persona-title { | |
| margin: 0 0 6px ; font-family: var(--p-mono) ; font-size: 11px ; | |
| font-weight: 500 ; letter-spacing: .2em; text-transform: uppercase; | |
| color: var(--p-transmit) ; line-height: 1.4 ; | |
| } | |
| .persona-label { | |
| font-family: var(--p-mono); font-size: 10px; letter-spacing: .14em; text-transform: uppercase; | |
| color: var(--p-muted); margin-top: 6px; | |
| } | |
| .persona-input { | |
| font-family: var(--p-sans) ; font-size: 14px ; color: var(--p-ink) ; | |
| background: var(--p-card) ; border: 1.5px solid var(--p-ink) ; | |
| border-radius: 0 ; padding: 7px 9px ; width: 100%; | |
| } | |
| /* ── Class picker — a custom dropdown with an animated idle-pose icon per class. | |
| Mirrors a native <select> (.persona-input chrome) but each row shows the | |
| character's looping idle sprite beside the name. ──────────────────────────── */ | |
| .persona-classdrop { position: relative; } | |
| .persona-classdrop-trigger { | |
| display: flex ; align-items: center; gap: 8px; cursor: pointer; text-align: left; | |
| } | |
| .persona-classdrop-label { flex: 1; min-width: 0; } | |
| .persona-classdrop-chev { color: var(--p-muted); font-size: 11px; transition: transform .12s; } | |
| .persona-classdrop.open .persona-classdrop-chev { transform: rotate(180deg); } | |
| .persona-class-ico { | |
| width: 30px; height: 30px; flex-shrink: 0; display: inline-block; overflow: hidden; | |
| background-repeat: no-repeat; background-position: 0 0; image-rendering: pixelated; | |
| } | |
| .persona-classdrop-menu { | |
| display: none; position: absolute; top: calc(100% + 2px); left: 0; right: 0; z-index: 30; | |
| background: var(--p-card); border: 1.5px solid var(--p-ink); | |
| box-shadow: 3px 3px 0 var(--p-transmit); max-height: 300px; overflow-y: auto; | |
| } | |
| .persona-classdrop.open .persona-classdrop-menu { display: block; } | |
| .persona-classdrop-opt { | |
| display: flex; align-items: center; gap: 8px; width: 100%; cursor: pointer; text-align: left; | |
| font-family: var(--p-sans); font-size: 14px; color: var(--p-ink); | |
| background: transparent; border: 0; border-bottom: 1px solid var(--p-paper-2); padding: 5px 9px; | |
| } | |
| .persona-classdrop-opt:last-child { border-bottom: 0; } | |
| .persona-classdrop-opt:hover { background: var(--p-paper-2); } | |
| .persona-classdrop-opt.sel { background: var(--p-ink); color: var(--p-paper); } | |
| .persona-go { | |
| margin-top: 10px; font-family: var(--p-mono) ; font-size: 12px ; | |
| font-weight: 700 ; letter-spacing: .04em; text-transform: uppercase; | |
| color: var(--p-paper) ; background: var(--p-ink) ; | |
| border: 1.5px solid var(--p-ink) ; border-radius: 0 ; | |
| padding: 9px 12px ; cursor: pointer; box-shadow: 2px 2px 0 var(--p-transmit); | |
| } | |
| .persona-go:hover { background: var(--p-transmit) ; } | |
| .persona-go:disabled { opacity: .55; cursor: default; box-shadow: none; } | |
| .persona-status { | |
| margin-top: 8px; font-family: var(--p-mono); font-size: 10px; letter-spacing: .06em; | |
| color: var(--p-muted); min-height: 14px; line-height: 1.5; | |
| } | |
| .persona-result { | |
| flex: 1; min-width: 0; overflow-y: auto; padding: 28px 32px; | |
| background: var(--p-paper); | |
| } | |
| .persona-name { | |
| font-family: 'Fraunces', Georgia, serif; font-weight: 900; font-size: 34px; | |
| line-height: 1; letter-spacing: -.02em; color: var(--p-ink); | |
| } | |
| .persona-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 16px; } | |
| .persona-tag { | |
| font-family: var(--p-mono); font-size: 10px; letter-spacing: .04em; text-transform: uppercase; | |
| color: var(--p-ink); background: var(--p-card); border: 1.5px solid var(--p-ink); | |
| padding: 3px 8px; | |
| } | |
| .persona-about { | |
| font-size: 17px; line-height: 1.6; max-width: 60ch; color: var(--p-ink); | |
| white-space: pre-wrap; | |
| } | |
| /* ── Section headers — like the sidebar: a short ink line, red heading, with the | |
| action button anchored right after the title (not at the page edge). ───────── */ | |
| .persona-sec { display: flex; align-items: center; gap: 10px; margin-top: 22px; } | |
| .persona-sec-title { | |
| display: flex; align-items: center; gap: 8px; | |
| font-family: var(--p-mono); font-size: 10px; font-weight: 500; letter-spacing: .2em; | |
| text-transform: uppercase; color: var(--p-transmit) ; /* beat Gradio's text var */ | |
| } | |
| .persona-sec-title::before { content: ''; height: 2px; width: 18px; background: var(--p-ink); flex-shrink: 0; } | |
| .persona-voice-desc { | |
| font-family: var(--p-mono); font-size: 12px; line-height: 1.5; color: var(--p-muted); | |
| max-width: 60ch; margin-top: 8px; font-style: italic; | |
| } | |
| /* Read-only when the provider isn't Qwen3-TTS (the design text isn't used then). */ | |
| .persona-voice-desc.readonly { opacity: .6; cursor: default; } | |
| .persona-voice-desc.readonly:hover, .persona-voice-desc.readonly:focus { background: transparent; box-shadow: none; } | |
| /* Per-hero named-voice picker (Kokoro/Kitten/Web Speech). */ | |
| .persona-voice-pick-row { margin-top: 12px; max-width: 320px; } | |
| .persona-voice-pick-row .persona-label { margin-top: 0; } | |
| .persona-voice-pick { margin-top: 4px; } | |
| /* Portrait — an editable appearance prompt + the painted image. */ | |
| .persona-appearance { | |
| font-family: var(--p-mono); font-size: 12px; line-height: 1.5; color: var(--p-muted); | |
| max-width: 60ch; margin-top: 8px; font-style: italic; | |
| } | |
| .persona-portrait-wrap { margin-top: 12px; } | |
| .persona-portrait-wrap:not(.has-img) { display: none; } | |
| .persona-portrait-img { | |
| width: 320px; max-width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; | |
| border: 1.5px solid var(--p-ink); box-shadow: 4px 4px 0 var(--p-transmit); background: var(--p-card); | |
| } | |
| .persona-quote { | |
| margin: 8px 0 0; padding: 4px 0 4px 16px; border-left: 3px solid var(--p-transmit); | |
| font-family: 'Fraunces', Georgia, serif; font-size: 21px; font-style: italic; | |
| line-height: 1.35; color: var(--p-ink); max-width: 54ch; | |
| } | |
| .persona-quote:not(:empty)::before { content: '“'; } | |
| .persona-quote:not(:empty)::after { content: '”'; } | |
| /* Simple icon button anchored after a section heading. */ | |
| .persona-ico { | |
| position: relative; cursor: pointer; flex-shrink: 0; line-height: 1; | |
| font-size: 12px ; color: var(--p-ink) ; background: var(--p-card) ; | |
| border: 1.5px solid var(--p-ink) ; border-radius: 0 ; padding: 3px 9px ; | |
| } | |
| .persona-ico:hover { background: var(--p-paper-2) ; } | |
| .persona-ico.busy { cursor: default; } | |
| /* Working (voice/portrait) → hide the glyph and spin a small ring in its place. */ | |
| .persona-ico.busy { color: transparent ; } | |
| .persona-ico.busy::before { | |
| content: ''; position: absolute; inset: 0; margin: auto; width: 11px; height: 11px; | |
| border: 2px solid var(--p-paper-2); border-top-color: var(--p-transmit); border-radius: 50%; | |
| animation: tac-spin .7s linear infinite; | |
| } | |
| @keyframes tac-spin { to { transform: rotate(360deg); } } | |
| /* Badge = "nothing made yet, or the inputs changed — tap to (re)make it." Pulses. */ | |
| .persona-ico.badged::after { | |
| content: ''; position: absolute; top: -4px; right: -4px; width: 9px; height: 9px; | |
| background: var(--p-transmit); border: 1.5px solid var(--p-card); border-radius: 50%; | |
| animation: tac-badge-pulse 1.3s ease-out infinite; | |
| } | |
| @keyframes tac-badge-pulse { | |
| 0% { box-shadow: 0 0 0 0 rgba(216, 39, 26, .6); } | |
| 70% { box-shadow: 0 0 0 7px rgba(216, 39, 26, 0); } | |
| 100% { box-shadow: 0 0 0 0 rgba(216, 39, 26, 0); } | |
| } | |
| /* Click-to-edit fields (name / about / quote / voice) — auto-saved on blur. */ | |
| .persona-edit { cursor: text; border-radius: 0; outline: none; transition: background .12s; } | |
| .persona-edit:hover { background: color-mix(in srgb, var(--p-card) 60%, transparent); box-shadow: 0 0 0 1px var(--p-paper-2); } | |
| .persona-edit:focus { background: var(--p-card); box-shadow: 0 0 0 1.5px var(--p-transmit); } | |
| .persona-edit:empty::before { content: attr(data-ph); color: var(--p-muted); opacity: .6; font-style: italic; } | |
| /* Empty state — shown until a hero is recruited or picked from the barracks. */ | |
| .persona-empty { | |
| font-family: var(--p-mono); font-size: 12px; letter-spacing: .04em; color: var(--p-muted); | |
| padding: 28px 0; max-width: 50ch; | |
| } | |
| /* ── Barracks roster (saved heroes) ────────────────────────────────────────── */ | |
| .persona-roster-label { margin-top: 18px; } | |
| .persona-roster { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; max-height: 230px; overflow-y: auto; } | |
| .persona-roster-empty { font-family: var(--p-mono); font-size: 10px; color: var(--p-muted); padding: 4px 0; } | |
| .persona-roster-item { display: flex; align-items: stretch; gap: 4px; } | |
| .persona-roster-name { | |
| flex: 1; text-align: left; cursor: pointer; | |
| font-family: var(--p-sans) ; font-size: 13px ; color: var(--p-ink) ; | |
| background: var(--p-card) ; border: 1.5px solid var(--p-ink) ; border-radius: 0 ; | |
| padding: 7px 9px ; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; | |
| } | |
| .persona-roster-name:hover { background: var(--p-paper-2) ; } | |
| /* The selected hero is highlighted in the barracks. */ | |
| .persona-roster-item.active .persona-roster-name { | |
| background: var(--p-ink) ; color: var(--p-paper) ; | |
| border-color: var(--p-ink) ; box-shadow: inset 4px 0 0 var(--p-transmit); font-weight: 700; | |
| } | |
| .persona-roster-x { | |
| cursor: pointer; flex-shrink: 0; font-size: 11px ; | |
| color: var(--p-transmit) ; background: var(--p-card) ; | |
| border: 1.5px solid var(--p-transmit) ; border-radius: 0 ; padding: 0 8px ; | |
| } | |
| .persona-roster-x:hover { background: var(--p-transmit) ; } | |
| /* ── Model picker + cache controls ─────────────────────────────────────────── */ | |
| .model-bar { display: flex; flex-direction: column; gap: 4px; padding-bottom: 10px; margin-bottom: 6px; border-bottom: 1px dashed var(--p-ink); } | |
| .model-select { | |
| font-family: var(--p-sans) ; font-size: 13px ; color: var(--p-ink) ; | |
| background: var(--p-card) ; border: 1.5px solid var(--p-ink) ; | |
| border-radius: 0 ; padding: 6px 8px ; width: 100%; | |
| } | |
| .engine-select { border-color: var(--p-transmit) ; margin-bottom: 2px; } | |
| .model-select option:disabled { color: var(--p-muted); } | |
| .model-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; } | |
| .model-info { font-family: var(--p-mono); font-size: 9px; letter-spacing: .04em; color: var(--p-muted); line-height: 1.4; flex: 1; } | |
| .model-del { | |
| font-family: var(--p-mono) ; font-size: 9px ; letter-spacing: .04em; text-transform: uppercase; | |
| color: var(--p-transmit) ; background: var(--p-card) ; border: 1.5px solid var(--p-transmit) ; | |
| border-radius: 0 ; padding: 3px 6px ; cursor: pointer; flex-shrink: 0; | |
| } | |
| .model-del:hover { background: var(--p-transmit) ; color: var(--p-card) ; } | |
| .model-del:disabled { opacity: .5; cursor: default; } | |
| /* ── Live stats (tok/s) ────────────────────────────────────────────────────── */ | |
| .persona-stats { | |
| font-family: var(--p-mono); font-size: 11px; letter-spacing: .04em; color: var(--p-transmit); | |
| min-height: 14px; margin-top: 6px; | |
| } | |
| /* ── "Thinking" raw stream (see progress as tokens arrive) ──────────────────── */ | |
| .persona-think-wrap { margin-top: 22px; } | |
| .persona-think-wrap > summary { | |
| cursor: pointer; font-family: var(--p-mono); font-size: 10px; letter-spacing: .12em; text-transform: uppercase; | |
| color: var(--p-muted); list-style: none; | |
| } | |
| .persona-think-wrap > summary::-webkit-details-marker { display: none; } | |
| .persona-think-wrap > summary::before { content: '▸ '; } | |
| .persona-think-wrap[open] > summary::before { content: '▾ '; } | |
| .persona-think { | |
| margin: 8px 0 0; max-height: 240px; overflow-y: auto; white-space: pre-wrap; word-break: break-word; | |
| font-family: var(--p-mono); font-size: 11px; line-height: 1.5; color: var(--p-muted); | |
| background: var(--p-paper-2); border: 1px solid var(--p-ink); padding: 8px 10px; | |
| } | |
| .persona-copy { | |
| margin-top: 8px; font-family: var(--p-mono) ; font-size: 10px ; letter-spacing: .04em; text-transform: uppercase; | |
| color: var(--p-transmit) ; background: var(--p-card) ; border: 1.5px solid var(--p-transmit) ; | |
| border-radius: 0 ; padding: 5px 9px ; cursor: pointer; | |
| } | |
| .persona-copy:hover { background: var(--p-transmit) ; color: var(--p-card) ; } | |
| /* ── TTS / voice controls (war-diary read-aloud) ───────────────────────────── */ | |
| .tts-bar { margin-top: 16px; } | |
| .tts-auto-row { | |
| display: flex; align-items: center; gap: 6px; margin-top: 6px; | |
| font-family: var(--p-mono); font-size: 10px; letter-spacing: .04em; text-transform: uppercase; color: var(--p-muted); cursor: pointer; | |
| } | |
| .tts-auto { accent-color: var(--p-transmit); width: 14px; height: 14px; } | |
| /* secondary (outline) button for "Read aloud" */ | |
| .persona-go-alt { | |
| margin-top: 8px; color: var(--p-ink) ; background: var(--p-card) ; | |
| box-shadow: 2px 2px 0 var(--p-ink); | |
| } | |
| .persona-go-alt:hover { background: var(--p-paper-2) ; color: var(--p-ink) ; } | |
| .tts-status { min-height: 14px; } | |
| /* ── "Local AI Model" section injected into Gradio's own Settings page ──────── */ | |
| /* The model bar's styles use --p-* vars (normally scoped to .persona-view); define | |
| them here too so the picker renders correctly inside Gradio's settings modal. */ | |
| .tac-set-section { | |
| --p-ink: #141821; --p-muted: #6d6a5f; --p-paper: #f3ebdc; --p-paper-2: #ece2cc; | |
| --p-card: #fbf6ea; --p-transmit: #d8271a; | |
| --p-sans: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| --p-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace; | |
| } | |
| .tac-set-section * { box-sizing: border-box; } | |
| .tac-set-section .model-bar { border-bottom: 0; padding-bottom: 0; } | |
| /* Gradio's Settings header prints the app URL under the "Settings" title — hide it. */ | |
| .banner-wrap .title .url { display: none ; } | |
| .tac-set-intro { font-size: 14px; line-height: 1.5; opacity: .75; margin: 2px 0 14px; } | |
| /* Recommended settings — quality preset picker (High / Medium / Low / Custom). */ | |
| .tac-quality { display: flex; flex-direction: column; gap: 12px; } | |
| .tac-preset-row { display: flex; gap: 8px; flex-wrap: wrap; } | |
| .tac-preset-btn { | |
| flex: 1 1 120px; display: flex; flex-direction: column; gap: 4px; text-align: left; | |
| background: var(--p-card) ; border: 1.5px solid var(--p-ink) ; border-radius: 0 ; | |
| padding: 14px 16px ; cursor: pointer; color: var(--p-ink) ; transition: background .12s, color .12s; | |
| height: auto ; min-height: 0 ; | |
| } | |
| .tac-preset-btn:hover:not(:disabled) { background: var(--p-paper-2) ; } | |
| .tac-preset-btn.active { background: var(--p-ink) ; color: var(--p-paper) ; box-shadow: 2px 2px 0 var(--p-transmit); } | |
| /* A selected button turns light on hover — keep its text dark so it stays readable. */ | |
| .tac-preset-btn.active:hover:not(:disabled) { color: var(--p-ink) ; } | |
| .tac-preset-btn:disabled { cursor: default; } | |
| .tac-preset-name { font-family: var(--p-mono); font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; } | |
| .tac-preset-sub { font-family: var(--p-mono); font-size: 10px; opacity: .7; } | |
| /* Expandable device readout (debug / benchmark). */ | |
| .tac-device { border: 1px dashed var(--p-ink); padding: 8px 10px; } | |
| .tac-device > summary { | |
| cursor: pointer; font-family: var(--p-mono); font-size: 11px; letter-spacing: .08em; | |
| text-transform: uppercase; color: var(--p-transmit); | |
| } | |
| .tac-device-actions { display: flex; flex-direction: column; gap: 6px; margin: 10px 0 4px; } | |
| .tac-device-rec { align-self: flex-start; margin-top: 0 ; } | |
| .tac-device-rec-note { min-height: 0; } | |
| .tac-device-body { margin-top: 8px; display: flex; flex-direction: column; gap: 4px; } | |
| .tac-device-row { display: flex; gap: 10px; font-family: var(--p-mono); font-size: 11px; line-height: 1.4; } | |
| .tac-device-k { flex: 0 0 130px; color: var(--p-muted); } | |
| .tac-device-v { flex: 1; min-width: 0; color: var(--p-ink); word-break: break-word; } | |
| /* Persona-prompt editor (Settings → Persona Prompt). */ | |
| .persona-prompt-bar { display: flex; flex-direction: column; gap: 10px; width: 100%; } | |
| .persona-prompt-edit { | |
| display: block; width: 100%; max-width: none; box-sizing: border-box; | |
| font-family: var(--p-mono); font-size: 12px; line-height: 1.55; | |
| color: var(--p-ink); background: var(--p-card); border: 1.5px solid var(--p-ink); | |
| border-radius: 0; padding: 10px 12px; resize: vertical; min-height: 200px; | |
| } | |
| .persona-prompt-edit:focus { outline: none; box-shadow: 0 0 0 1.5px var(--p-transmit); } | |
| .persona-prompt-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } | |
| /* Save sits left, Reset is pushed to the far right so the two are clearly separated. */ | |
| .persona-prompt-save { margin-top: 0 ; position: relative; } | |
| .persona-prompt-reset { margin-left: auto; } | |
| /* Pulsing badge on Save = unsaved edits in the prompt. Reuses the play-button badge. */ | |
| .persona-prompt-save.badged::after { | |
| content: ''; position: absolute; top: -5px; right: -5px; width: 10px; height: 10px; | |
| background: var(--p-transmit); border: 1.5px solid var(--p-card); border-radius: 50%; | |
| animation: tac-badge-pulse 1.3s ease-out infinite; | |
| } | |
| /* Collapsible control sections (model / voice). Desktop: no toggle, always shown. */ | |
| .ctl-collapse > summary { display: none; } | |
| @media (max-width: 768px) { | |
| /* The whole view scrolls as one column (instead of two fighting scroll panes in | |
| a fixed-height box), so the story gets real room and the page scrolls. */ | |
| .persona-view { flex-direction: column; height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch; } | |
| /* Mobile: tuck the model/voice bars behind a tap-to-expand summary. */ | |
| .ctl-collapse > summary { | |
| display: block; cursor: pointer; list-style: none; margin-top: 6px; | |
| font-family: var(--p-mono); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; | |
| color: var(--p-ink); background: var(--p-card); border: 1.5px solid var(--p-ink); padding: 10px; | |
| } | |
| .ctl-collapse > summary::-webkit-details-marker { display: none; } | |
| .ctl-collapse > summary::after { content: ' ▾'; float: right; color: var(--p-muted); } | |
| .ctl-collapse[open] > summary::after { content: ' ▴'; } | |
| .persona-label { margin-top: 4px; } | |
| .persona-controls { | |
| width: 100%; border-right: 0; border-bottom: 2px solid var(--p-ink); | |
| overflow: visible; flex-shrink: 0; gap: 6px; | |
| padding: 54px 14px 16px; /* top clears the floating ☰ reopen button */ | |
| } | |
| /* The generated content (persona / war story) gets the majority of the screen. */ | |
| .persona-result { flex: 1 0 auto; min-height: 62vh; padding: 18px 16px; overflow: visible; } | |
| .persona-name { font-size: 26px; } | |
| .persona-about { font-size: 16px; max-width: none; } | |
| .persona-tags { margin: 10px 0 14px; } | |
| .persona-think { max-height: 150px; } | |
| /* 16px inputs avoid iOS focus-zoom; bigger pads = easier taps. */ | |
| .persona-input { font-size: 16px ; padding: 10px ; } | |
| .model-select { font-size: 15px ; padding: 9px 10px ; } | |
| .persona-go, .persona-go-alt { padding: 12px ; } | |
| } | |