Files
hope-voice-api/web/style.css
T
snowspeeder 360e3276b0
Rebuild & Deploy / rebuild (push) Successful in 3m18s
feat: switch LLM to OpenWebUI with live model selection
- Point the agent at OpenWebUI's OpenAI-compatible API (LLM_BASE_URL,
  LLM_API_KEY, LLM_MODEL) with GEMMA_* env fallbacks
- Add list_available_models() fetching {base_url}/models, filtering
  non-chat ids (embedding/reranker)
- Rename GemmaLLM -> HopeLLM with update_options(model=...) for runtime
  model switching (mutates _opts.model) and instance-based compaction
- Data channel: agent publishes {type: models} after connect and on
  get_models; set_model switches live and acks model_changed
- Web UI: Brain section in the settings sheet with a model picker that
  persists the choice in localStorage
- Update tests, CI workflow env, and docs for the new endpoint
2026-08-29 12:09:03 -04:00

1409 lines
29 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:root {
--canvas: #08111f;
--canvas-deep: #050b14;
--surface: #0d1929;
--surface-raised: #132238;
--surface-soft: #172941;
--line: rgba(183, 204, 235, 0.13);
--line-strong: rgba(183, 204, 235, 0.22);
--ink: #f4f7fb;
--ink-soft: #c5d1e2;
--ink-muted: #8395ad;
--iris: #8ba7ff;
--iris-strong: #6f8ff7;
--iris-deep: #3c5fc9;
--warm: #ffb38e;
--mint: #76d9b2;
--danger: #ff806f;
--user-bubble: #1b3150;
--agent-bubble: #14243a;
--focus: #b5c5ff;
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
--z-panel: 40;
--z-backdrop: 50;
--z-sheet: 60;
color-scheme: dark;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
min-height: 100%;
background: var(--canvas-deep);
}
body {
min-height: 100vh;
min-height: 100dvh;
padding: calc(1.25rem + env(safe-area-inset-top))
calc(1.25rem + env(safe-area-inset-right))
calc(1.25rem + env(safe-area-inset-bottom))
calc(1.25rem + env(safe-area-inset-left));
overflow-x: hidden;
color: var(--ink);
background:
radial-gradient(circle at 16% 8%, rgba(86, 115, 207, 0.16), transparent 34rem),
radial-gradient(circle at 88% 92%, rgba(255, 179, 142, 0.07), transparent 30rem),
var(--canvas);
font-family: "Avenir Next", "Segoe UI Variable", "Segoe UI", system-ui, -apple-system, sans-serif;
-webkit-font-smoothing: antialiased;
}
button,
input,
select {
font: inherit;
}
button {
color: inherit;
}
[hidden] {
display: none !important;
}
.skip-link {
position: fixed;
top: 0.75rem;
left: 0.75rem;
z-index: 100;
padding: 0.7rem 1rem;
border-radius: 8px;
color: var(--canvas);
background: var(--ink);
font-size: 0.85rem;
font-weight: 700;
transform: translateY(-160%);
transition: transform 180ms var(--ease-out);
}
.skip-link:focus {
transform: translateY(0);
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.app-shell {
width: min(1180px, 100%);
margin: 0 auto;
}
/* ── Header ─────────────────────────────────────────────────────────────── */
.topbar {
min-height: 62px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1.25rem;
margin-bottom: 1.25rem;
padding: 0.25rem 0.15rem;
}
.brand,
.topbar-actions,
.identity-chip,
.tasks-trigger {
display: flex;
align-items: center;
}
.brand {
gap: 0.8rem;
}
.brand-glyph {
position: relative;
width: 38px;
height: 38px;
flex: 0 0 38px;
border: 1px solid rgba(139, 167, 255, 0.55);
border-radius: 50%;
}
.brand-glyph::before,
.brand-glyph::after,
.brand-glyph i {
content: "";
position: absolute;
border-radius: 50%;
}
.brand-glyph::before {
inset: 7px;
border: 1px solid rgba(139, 167, 255, 0.35);
}
.brand-glyph::after {
width: 8px;
height: 8px;
right: 4px;
top: 5px;
background: var(--warm);
box-shadow: 0 0 12px rgba(255, 179, 142, 0.52);
}
.brand-glyph i {
width: 6px;
height: 6px;
inset: 0;
margin: auto;
background: var(--iris);
}
.brand h1 {
color: var(--ink);
font-size: 1.18rem;
font-weight: 680;
letter-spacing: -0.02em;
}
.brand p {
margin-top: 0.1rem;
color: var(--ink-muted);
font-size: 0.74rem;
}
.topbar-actions {
justify-content: flex-end;
gap: 0.65rem;
}
.mode-switch {
height: 38px;
display: flex;
align-items: center;
gap: 2px;
padding: 3px;
border: 1px solid var(--line);
border-radius: 10px;
background: rgba(5, 11, 20, 0.55);
}
.mode-option {
height: 30px;
display: inline-flex;
align-items: center;
gap: 0.38rem;
padding: 0 0.65rem;
border: 0;
border-radius: 7px;
cursor: pointer;
color: var(--ink-muted);
background: transparent;
font-size: 0.72rem;
font-weight: 650;
transition: color 160ms ease, background 160ms ease, box-shadow 160ms ease;
}
.mode-option:hover {
color: var(--ink-soft);
}
.mode-option.active {
color: var(--ink);
background: var(--surface-soft);
box-shadow: 0 1px 5px rgba(2, 7, 14, 0.3);
}
.mode-option:focus-visible,
.chat-composer textarea:focus-visible,
.send-chat:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
.identity-chip {
min-height: 38px;
gap: 0.55rem;
padding: 0 0.85rem;
border: 1px solid var(--line);
border-radius: 999px;
color: var(--ink-soft);
background: rgba(13, 25, 41, 0.72);
font-size: 0.78rem;
}
.identity-chip strong {
color: var(--ink);
font-weight: 650;
}
.presence-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--mint);
box-shadow: 0 0 0 4px rgba(118, 217, 178, 0.1);
}
/* ── Main workspace ─────────────────────────────────────────────────────── */
.workspace {
display: grid;
grid-template-columns: minmax(390px, 0.92fr) minmax(420px, 1.08fr);
height: min(700px, calc(100dvh - 108px));
min-height: 610px;
border: 1px solid var(--line);
border-radius: 16px;
overflow: hidden;
background: var(--surface);
box-shadow: 0 8px 8px rgba(2, 7, 14, 0.18);
}
.voice-console {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
min-width: 0;
padding: clamp(2rem, 4vw, 3.5rem) clamp(1.5rem, 4vw, 3.25rem) 1.6rem;
overflow: hidden;
background:
radial-gradient(circle at 50% 38%, rgba(90, 121, 215, 0.14), transparent 18rem),
var(--surface);
}
.voice-console::after {
content: "";
position: absolute;
left: 10%;
right: 10%;
bottom: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(139, 167, 255, 0.34), transparent);
}
.console-intro {
position: relative;
z-index: 2;
width: 100%;
text-align: center;
}
.console-intro h2 {
color: var(--ink);
font-size: 2rem;
font-weight: 620;
letter-spacing: -0.035em;
text-wrap: balance;
}
.console-intro p {
max-width: 38ch;
margin: 0.55rem auto 0;
color: var(--ink-muted);
font-size: 0.91rem;
line-height: 1.55;
text-wrap: pretty;
}
/* ── Voice presence ─────────────────────────────────────────────────────── */
.orb-stage {
position: relative;
z-index: 1;
flex: 1;
width: 100%;
min-height: 310px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 0.75rem;
}
.orb {
--pulse-scale: 1;
--pulse-opacity: 0.28;
position: relative;
width: 260px;
height: 260px;
display: grid;
place-items: center;
isolation: isolate;
}
.orb-core {
position: relative;
z-index: 3;
width: 116px;
height: 116px;
overflow: hidden;
border-radius: 50%;
background:
radial-gradient(circle at 32% 24%, rgba(244, 247, 251, 0.96) 0 3%, transparent 20%),
radial-gradient(circle at 38% 30%, #b9c9ff, #6f8ff7 42%, #3d57ab 72%, #23305b 100%);
box-shadow:
0 0 0 1px rgba(213, 222, 255, 0.24),
0 12px 28px rgba(2, 8, 20, 0.34),
0 0 52px rgba(111, 143, 247, 0.32),
inset -12px -14px 26px rgba(10, 20, 50, 0.34);
transform: scale(var(--pulse-scale));
transition: filter 220ms ease, box-shadow 220ms ease, transform 90ms linear;
}
.orb-shimmer {
position: absolute;
width: 90px;
height: 34px;
left: 9px;
top: 18px;
border-radius: 50%;
border-top: 1px solid rgba(255, 255, 255, 0.52);
transform: rotate(-22deg);
opacity: 0.7;
}
.orb-ring,
.orb-orbit {
position: absolute;
border-radius: 50%;
pointer-events: none;
}
.orb-ring {
z-index: 2;
width: 164px;
height: 164px;
border: 1px solid rgba(139, 167, 255, 0.2);
opacity: var(--pulse-opacity);
transform: scale(var(--pulse-scale));
transition: opacity 180ms ease, transform 90ms linear;
}
.orb-orbit {
z-index: 1;
border: 1px solid rgba(139, 167, 255, 0.15);
}
.orbit-one {
width: 214px;
height: 140px;
transform: rotate(-18deg);
}
.orbit-one::after {
content: "";
position: absolute;
width: 7px;
height: 7px;
top: 12px;
right: 37px;
border-radius: 50%;
background: var(--warm);
box-shadow: 0 0 13px rgba(255, 179, 142, 0.65);
}
.orbit-two {
width: 230px;
height: 230px;
border-color: rgba(139, 167, 255, 0.08);
}
.orb[data-state="idle"] .orb-core {
filter: saturate(0.72) brightness(0.78);
box-shadow:
0 0 0 1px rgba(213, 222, 255, 0.13),
0 12px 28px rgba(2, 8, 20, 0.3),
0 0 28px rgba(111, 143, 247, 0.16);
}
.orb[data-state="connecting"] .orbit-one,
.orb[data-state="thinking"] .orbit-one {
animation: orbit-turn 3.5s linear infinite;
}
.orb[data-state="connecting"] .orb-core {
animation: presence-breathe 1.7s ease-in-out infinite;
}
.orb[data-state="listening"] .orb-core {
box-shadow:
0 0 0 1px rgba(213, 222, 255, 0.4),
0 12px 28px rgba(2, 8, 20, 0.34),
0 0 68px rgba(111, 143, 247, 0.56),
inset -12px -14px 26px rgba(10, 20, 50, 0.28);
}
.orb[data-state="listening"] .orb-ring {
border-color: rgba(139, 167, 255, 0.5);
}
.orb[data-state="thinking"] .orb-core {
background:
radial-gradient(circle at 32% 24%, rgba(255, 255, 255, 0.9) 0 3%, transparent 20%),
radial-gradient(circle at 38% 30%, #f3c9b5, #a783ca 42%, #5d68bd 72%, #29365f 100%);
box-shadow: 0 0 62px rgba(167, 131, 202, 0.48);
}
.orb[data-state="speaking"] .orb-core {
box-shadow:
0 0 0 1px rgba(213, 222, 255, 0.45),
0 12px 28px rgba(2, 8, 20, 0.34),
0 0 78px rgba(111, 143, 247, 0.64),
0 0 105px rgba(255, 179, 142, 0.11);
}
.orb[data-state="speaking"] .orbit-one::after {
background: #ffd0b8;
}
@keyframes presence-breathe {
0%, 100% { transform: scale(calc(var(--pulse-scale) * 0.98)); }
50% { transform: scale(calc(var(--pulse-scale) * 1.045)); }
}
@keyframes orbit-turn {
to { transform: rotate(342deg); }
}
.orb-labels {
min-height: 46px;
margin-top: 0.2rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.35rem;
}
.state-label {
color: var(--ink-soft);
font-size: 0.83rem;
font-weight: 600;
}
.thinking-chip,
.barge-hint {
color: var(--ink-muted);
font-size: 0.73rem;
}
.thinking-chip::before {
content: "";
display: inline-block;
width: 5px;
height: 5px;
margin: 0 0.42rem 0.1rem 0;
border-radius: 50%;
background: var(--warm);
vertical-align: middle;
animation: think-pulse 1.2s ease-in-out infinite;
}
@keyframes think-pulse {
50% { opacity: 0.28; transform: scale(0.75); }
}
/* ── Buttons and controls ───────────────────────────────────────────────── */
.controls {
position: relative;
z-index: 2;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 0.6rem;
width: 100%;
}
.btn {
min-height: 42px;
border: 0;
border-radius: 9px;
padding: 0.68rem 1rem;
cursor: pointer;
font-size: 0.84rem;
font-weight: 650;
line-height: 1;
transition: color 180ms ease, background 180ms ease, border-color 180ms ease, transform 180ms var(--ease-out), opacity 180ms ease;
}
.btn:hover:not(:disabled) {
transform: translateY(-1px);
}
.btn:active:not(:disabled) {
transform: translateY(0);
}
.btn:focus-visible,
.voice-card:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 3px;
}
.btn:disabled {
opacity: 0.38;
cursor: not-allowed;
}
.btn-primary {
position: relative;
min-width: 180px;
color: #07101d;
background: var(--ink);
}
.btn-primary:hover:not(:disabled) {
background: #dfe7f4;
}
.button-spark {
display: inline-block;
width: 7px;
height: 7px;
margin-right: 0.5rem;
border-radius: 50%;
background: var(--iris-deep);
box-shadow: 0 0 0 4px rgba(60, 95, 201, 0.12);
}
.btn-primary:disabled,
.btn-danger:disabled {
display: none;
}
.btn-danger {
color: #fff4f1;
background: #8f403a;
}
.btn-danger:hover:not(:disabled) {
background: #a84b43;
}
.btn-secondary {
color: var(--ink-soft);
background: var(--surface-raised);
border: 1px solid var(--line);
}
.btn-secondary:hover:not(:disabled) {
color: var(--ink);
border-color: var(--line-strong);
background: var(--surface-soft);
}
.btn-icon {
width: 42px;
min-width: 42px;
height: 42px;
padding: 0;
display: inline-grid;
place-items: center;
color: var(--ink-muted);
background: var(--surface-raised);
border: 1px solid var(--line);
}
.btn-icon:hover:not(:disabled) {
color: var(--ink);
border-color: var(--line-strong);
background: var(--surface-soft);
}
.btn-icon.muted {
color: #ffd1ca;
border-color: rgba(255, 128, 111, 0.5);
background: rgba(143, 64, 58, 0.4);
}
.btn-ghost {
min-height: 34px;
padding: 0.45rem 0.65rem;
color: var(--ink-muted);
background: transparent;
}
.btn-ghost:hover:not(:disabled) {
color: var(--ink);
background: var(--surface-raised);
}
.tasks-trigger {
position: relative;
min-height: 38px;
gap: 0.45rem;
padding: 0 0.8rem;
font-size: 0.78rem;
}
.tasks-badge {
min-width: 17px;
height: 17px;
display: inline-grid;
place-items: center;
padding: 0 4px;
border-radius: 999px;
color: #07101d;
background: var(--warm);
font-size: 0.64rem;
font-weight: 750;
}
.connection-strip {
width: min(340px, 100%);
flex-shrink: 0;
margin-top: 1rem;
}
.status {
min-height: 20px;
color: var(--ink-muted);
font-size: 0.72rem;
text-align: center;
}
.mic-meter {
height: 3px;
margin-top: 0.55rem;
overflow: hidden;
border-radius: 999px;
background: rgba(139, 167, 255, 0.1);
}
.mic-meter-fill {
display: block;
width: 0;
height: 100%;
border-radius: inherit;
background: var(--iris);
transition: width 80ms linear;
}
.privacy-note {
display: flex;
flex-shrink: 0;
align-items: center;
gap: 0.4rem;
margin-top: 1.15rem;
color: var(--ink-muted);
font-size: 0.7rem;
}
.privacy-note svg {
color: var(--mint);
}
/* ── Conversation ───────────────────────────────────────────────────────── */
.transcript {
min-width: 0;
display: flex;
flex-direction: column;
padding: clamp(1.4rem, 3vw, 2.1rem);
border-left: 1px solid var(--line);
background: #0a1524;
}
.transcript-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding-bottom: 1.1rem;
border-bottom: 1px solid var(--line);
}
.transcript-header h2 {
color: var(--ink);
font-size: 1rem;
font-weight: 650;
letter-spacing: -0.01em;
}
.transcript-header p {
margin-top: 0.22rem;
color: var(--ink-muted);
font-size: 0.7rem;
}
#messages {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 1.25rem 0.2rem 1rem 0;
scroll-behavior: smooth;
}
#messages:empty {
display: grid;
place-items: center;
}
#messages:empty::before {
content: "Your conversation will appear here.\A Start when you’re ready—there’s nothing to configure.";
max-width: 30ch;
color: var(--ink-muted);
font-size: 0.84rem;
line-height: 1.65;
text-align: center;
white-space: pre-wrap;
}
.message-row {
position: relative;
display: flex;
align-items: flex-end;
gap: 0.55rem;
margin-bottom: 0.85rem;
animation: message-in 180ms var(--ease-out);
}
.message-row.user {
justify-content: flex-end;
}
.message-row.system {
justify-content: center;
}
.message-row.system .bubble {
padding: 0.35rem 0.65rem;
color: var(--ink-muted);
background: transparent;
font-size: 0.7rem;
}
@keyframes message-in {
from { opacity: 0; transform: translateY(5px); }
to { opacity: 1; transform: translateY(0); }
}
.avatar-dot {
position: relative;
width: 27px;
height: 27px;
flex: 0 0 27px;
margin-bottom: 2px;
border: 1px solid rgba(139, 167, 255, 0.38);
border-radius: 50%;
}
.avatar-dot::after {
content: "";
position: absolute;
inset: 8px;
border-radius: 50%;
background: var(--iris);
}
.bubble {
max-width: min(82%, 58ch);
padding: 0.72rem 0.9rem;
border-radius: 12px;
color: var(--ink-soft);
font-size: 0.9rem;
line-height: 1.55;
overflow-wrap: anywhere;
}
.message-row.user .bubble {
color: #e8efff;
background: var(--user-bubble);
border-bottom-right-radius: 3px;
}
.message-row.agent .bubble {
background: var(--agent-bubble);
border-bottom-left-radius: 3px;
}
.message-row.partial,
.bubble.partial {
opacity: 0.58;
}
.message-row::after {
content: attr(data-ts);
position: absolute;
bottom: -0.9rem;
color: var(--ink-muted);
font-size: 0.62rem;
opacity: 0;
pointer-events: none;
transition: opacity 150ms ease;
}
.message-row:hover::after {
opacity: 0.75;
}
.message-row.user::after { right: 0; }
.message-row.agent::after { left: 2rem; }
.audio-viz {
height: 13px;
margin-left: 0.5rem;
display: inline-flex;
align-items: flex-end;
gap: 2px;
vertical-align: middle;
}
.audio-viz i {
width: 2px;
height: 15%;
border-radius: 2px;
background: var(--iris);
opacity: 0.78;
transition: height 70ms linear;
}
/* ── Text composer ──────────────────────────────────────────────────────── */
.chat-composer {
position: relative;
display: none;
align-items: flex-end;
gap: 0.55rem;
padding: 0.75rem 0 1.25rem;
border-top: 1px solid var(--line);
}
body[data-mode="text"] .chat-composer {
display: flex;
}
.chat-composer textarea {
width: 100%;
min-height: 48px;
max-height: 144px;
resize: none;
padding: 0.8rem 3.2rem 0.8rem 0.9rem;
border: 1px solid var(--line-strong);
border-radius: 12px;
color: var(--ink);
background: var(--surface-raised);
font: inherit;
font-size: 0.9rem;
line-height: 1.4;
caret-color: var(--iris);
transition: border-color 160ms ease, background 160ms ease;
}
.chat-composer textarea::placeholder {
color: var(--ink-muted);
}
.chat-composer textarea:focus {
border-color: rgba(139, 167, 255, 0.56);
background: var(--surface-soft);
}
.send-chat {
position: absolute;
right: 0.55rem;
bottom: 2.05rem;
width: 34px;
height: 34px;
display: grid;
place-items: center;
border: 0;
border-radius: 9px;
cursor: pointer;
color: #08111f;
background: var(--iris);
transition: opacity 150ms ease, transform 150ms var(--ease-out), background 150ms ease;
}
.send-chat:hover:not(:disabled) {
background: #a9bbff;
transform: translateY(-1px);
}
.send-chat:disabled {
cursor: default;
opacity: 0.25;
}
.composer-hint {
position: absolute;
left: 0.2rem;
bottom: 0.25rem;
color: var(--ink-muted);
font-size: 0.62rem;
}
/* Text mode makes the transcript the primary surface while preserving Hope's
live presence and the same underlying conversation. */
body[data-mode="text"] .workspace {
grid-template-columns: minmax(240px, 0.62fr) minmax(480px, 1.38fr);
}
body[data-mode="text"] .voice-console {
padding: 2rem 1.5rem 1.4rem;
}
body[data-mode="text"] .console-intro h2 {
font-size: 1.55rem;
}
body[data-mode="text"] .orb-stage {
min-height: 220px;
}
body[data-mode="text"] .orb {
width: 180px;
height: 180px;
transform: scale(0.72);
}
body[data-mode="text"] .controls {
max-width: 220px;
}
body[data-mode="text"] #muteBtn {
display: none;
}
body[data-mode="text"] .mic-meter {
visibility: hidden;
}
/* ── Voice settings ─────────────────────────────────────────────────────── */
.sheet-backdrop {
position: fixed;
inset: 0;
z-index: var(--z-backdrop);
background: rgba(2, 7, 14, 0.68);
opacity: 0;
transition: opacity 200ms ease;
}
.sheet-backdrop:not([hidden]) {
opacity: 1;
}
.settings-sheet {
position: fixed;
left: 50%;
bottom: 0;
z-index: var(--z-sheet);
width: min(660px, 100%);
max-height: min(76vh, 680px);
padding: 0.75rem 1.25rem calc(1.25rem + env(safe-area-inset-bottom));
display: flex;
flex-direction: column;
border: 1px solid var(--line-strong);
border-bottom: 0;
border-radius: 16px 16px 0 0;
background: #101e31;
transform: translate(-50%, 102%);
transition: transform 240ms var(--ease-out);
}
.settings-sheet.open {
transform: translate(-50%, 0);
}
.sheet-handle {
width: 36px;
height: 3px;
margin: 0 auto 1rem;
border-radius: 999px;
background: var(--line-strong);
}
.sheet-header,
.tasks-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.sheet-header {
padding-bottom: 1rem;
}
.sheet-header h2,
.tasks-header h2 {
color: var(--ink);
font-size: 1rem;
font-weight: 650;
}
.sheet-header p,
.tasks-header p {
margin-top: 0.25rem;
color: var(--ink-muted);
font-size: 0.72rem;
}
.sheet-section-title {
margin: 0.4rem 0 0.55rem;
padding: 0 0.2rem;
color: var(--ink-muted);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.sheet-note {
margin: 0.5rem 0 0;
color: var(--ink-muted);
font-size: 0.72rem;
}
.voice-list {
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.4rem;
padding: 0.15rem 0.2rem 0 0;
}
/* Two pickers (voice + brain) share the settings sheet height. */
.settings-sheet .voice-list {
flex: 1 1 0;
}
.voice-card {
min-height: 54px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.8rem;
padding: 0.65rem 0.75rem 0.65rem 1rem;
border: 1px solid transparent;
border-radius: 10px;
cursor: pointer;
color: var(--ink-soft);
background: rgba(23, 41, 65, 0.55);
transition: color 160ms ease, background 160ms ease, border-color 160ms ease;
}
.voice-card:hover {
color: var(--ink);
background: var(--surface-soft);
}
.voice-card.active {
color: var(--ink);
border-color: rgba(139, 167, 255, 0.42);
background: rgba(60, 95, 201, 0.18);
}
.voice-name {
font-size: 0.86rem;
font-weight: 600;
}
.voice-card.active .voice-name::after {
content: "Selected";
display: inline-block;
margin-left: 0.55rem;
color: var(--iris);
font-size: 0.66rem;
font-weight: 550;
}
.voice-preview {
min-height: 32px;
color: var(--ink-muted);
}
.voice-preview::before {
content: "Listen";
}
/* ── Tasks ──────────────────────────────────────────────────────────────── */
.tasks-panel {
position: fixed;
top: 0;
right: 0;
z-index: var(--z-panel);
width: min(380px, 92vw);
height: 100dvh;
display: flex;
flex-direction: column;
border-left: 1px solid var(--line-strong);
background: #0d1929;
transform: translateX(102%);
transition: transform 240ms var(--ease-out);
}
.tasks-panel.open {
transform: translateX(0);
}
.tasks-header {
padding: calc(1.1rem + env(safe-area-inset-top)) 1.1rem 1rem;
border-bottom: 1px solid var(--line);
}
.tasks-list {
flex: 1;
overflow-y: auto;
padding: 0.9rem;
display: flex;
flex-direction: column;
gap: 0.65rem;
}
.tasks-empty {
min-height: 180px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.35rem;
color: var(--ink-muted);
text-align: center;
font-size: 0.78rem;
}
.tasks-empty strong {
color: var(--ink-soft);
font-size: 0.86rem;
}
.task-card {
padding: 0.85rem;
border-radius: 10px;
color: var(--ink-soft);
background: var(--surface-raised);
font-size: 0.8rem;
}
.task-card-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.45rem;
}
.task-status-dot {
width: 7px;
height: 7px;
flex: 0 0 7px;
border-radius: 50%;
background: var(--ink-muted);
}
.task-status-dot.running {
background: var(--iris);
animation: status-pulse 1.4s ease-in-out infinite;
}
.task-status-dot.completed { background: var(--mint); }
.task-status-dot.failed { background: var(--danger); }
@keyframes status-pulse {
50% { opacity: 0.3; }
}
.task-desc {
flex: 1;
overflow: hidden;
color: var(--ink);
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.task-steps {
max-height: 200px;
margin-top: 0.5rem;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.3rem;
list-style: none;
}
.task-step,
.task-result,
.task-error {
padding: 0.42rem 0.5rem;
border-radius: 6px;
font-size: 0.72rem;
}
.task-step {
color: var(--ink-muted);
background: rgba(5, 11, 20, 0.3);
}
.task-step.tool_call { color: #a9bbff; }
.task-step.tool_result { color: var(--ink-muted); }
.task-step.text { color: var(--ink-soft); }
.task-result { margin-top: 0.5rem; color: #bcebd8; background: rgba(118, 217, 178, 0.1); }
.task-error { margin-top: 0.5rem; color: #ffc2ba; background: rgba(255, 128, 111, 0.1); }
/* ── Responsive ─────────────────────────────────────────────────────────── */
@media (max-height: 760px) and (min-width: 881px) {
.workspace {
min-height: 540px;
}
.voice-console {
padding-top: 1.5rem;
padding-bottom: 1rem;
}
.console-intro h2 {
font-size: 1.65rem;
}
.console-intro p {
margin-top: 0.35rem;
}
.orb-stage {
min-height: 220px;
margin: -1rem 0;
}
.orb {
transform: scale(0.78);
}
.connection-strip,
.privacy-note {
margin-top: 0.65rem;
}
}
@media (max-width: 880px) {
body {
padding: calc(0.85rem + env(safe-area-inset-top))
calc(0.85rem + env(safe-area-inset-right))
calc(0.85rem + env(safe-area-inset-bottom))
calc(0.85rem + env(safe-area-inset-left));
}
.workspace,
body[data-mode="text"] .workspace {
grid-template-columns: 1fr;
height: auto;
min-height: auto;
}
.voice-console {
min-height: 620px;
}
.transcript {
min-height: 430px;
max-height: 62dvh;
border-top: 1px solid var(--line);
border-left: 0;
}
}
@media (max-width: 560px) {
body {
padding-right: env(safe-area-inset-right);
padding-left: env(safe-area-inset-left);
}
.topbar {
padding-right: 0.85rem;
padding-left: 0.85rem;
margin-bottom: 0.65rem;
}
.brand p,
.identity-chip,
.mode-option svg {
display: none;
}
.mode-option {
padding: 0 0.55rem;
}
.workspace {
border-right: 0;
border-left: 0;
border-radius: 0;
}
.voice-console {
min-height: calc(100dvh - 82px);
padding: 2.1rem 1.15rem 1.25rem;
}
body[data-mode="text"] .voice-console {
min-height: 270px;
padding: 1.35rem 1rem 1rem;
}
body[data-mode="text"] .orb-stage {
min-height: 120px;
margin: -0.75rem 0;
}
body[data-mode="text"] .orb {
transform: scale(0.48);
}
.console-intro h2 {
font-size: 1.7rem;
}
.orb-stage {
min-height: 300px;
}
.orb {
width: 235px;
height: 235px;
transform: scale(0.94);
}
.controls {
max-width: 330px;
}
.btn-primary,
.btn-danger {
flex: 1 1 180px;
}
.privacy-note {
margin-top: 1rem;
}
.transcript {
min-height: 480px;
max-height: none;
padding: 1.25rem 1rem;
}
.tasks-panel {
width: 100vw;
}
.bubble {
max-width: 86%;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}