fb6b44a82e
iOS won't render interactive content in the bottom ~59px (home-indicator zone) and underreports innerHeight by the top inset, so the bar can't be pushed lower. Instead, keep the shell at 100dvh (no clipping) and paint the strip below it the same color as the tab bar (html/body bg + a lighter --bg-line bar + matched theme-color) so it reads as the bar continuing to the edge rather than an empty gap. Cache-bust the stylesheet so the PWA picks it up. Tried the boolinator innerHeight-correction approach (adding the top inset back via --actual-vh); it clipped the icons on-device, so reverted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G796GsLCvJQKVN7hwV2cDx
1273 lines
28 KiB
CSS
1273 lines
28 KiB
CSS
:root {
|
|
--bg-dark: #070707;
|
|
--bg-elev: #0e0e0e;
|
|
--bg-line: #141414;
|
|
--bg-panel: #0e0e0e;
|
|
--border: #2a1d12;
|
|
--border-bright: #4a2f15;
|
|
--accent: #ff7a00;
|
|
--gold: #ffb347;
|
|
--good: #8fd694;
|
|
--bad: #ff5a5a;
|
|
--accent-soft: rgba(255, 122, 0, 0.10);
|
|
--accent-glow: 0 0 6px rgba(255, 122, 0, 0.18);
|
|
--text-main: #e8e8e8;
|
|
--text-fade: #8a8a8a;
|
|
--font-console: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
--font-voice: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
}
|
|
|
|
/* Light mode (secondary — Brian runs dark) */
|
|
body {
|
|
--bg-dark: #f5f3ef;
|
|
--bg-elev: #ffffff;
|
|
--bg-line: #ece8e1;
|
|
--bg-panel: #ffffff;
|
|
--border: #e2dacb;
|
|
--border-bright: #c9a87a;
|
|
--accent: #c75e00;
|
|
--gold: #b8791f;
|
|
--good: #3f9a52;
|
|
--bad: #c0392b;
|
|
--accent-soft: rgba(199, 94, 0, 0.08);
|
|
--accent-glow: none;
|
|
--text-main: #1a1a1a;
|
|
--text-fade: #6a6a6a;
|
|
--text: var(--text-main); /* alias: some rules reference var(--text) */
|
|
}
|
|
|
|
/* Dark mode (primary — RTO warm low-glow) */
|
|
body.dark {
|
|
--bg-dark: #070707;
|
|
--bg-elev: #0e0e0e;
|
|
--bg-line: #141414;
|
|
--bg-panel: #0e0e0e;
|
|
--border: #2a1d12;
|
|
--border-bright: #4a2f15;
|
|
--accent: #ff7a00;
|
|
--gold: #ffb347;
|
|
--good: #8fd694;
|
|
--bad: #ff5a5a;
|
|
--accent-soft: rgba(255, 122, 0, 0.10);
|
|
--accent-glow: 0 0 6px rgba(255, 122, 0, 0.18);
|
|
--text-main: #e8e8e8;
|
|
--text-fade: #8a8a8a;
|
|
}
|
|
|
|
html {
|
|
overscroll-behavior: none;
|
|
/* Stop iOS from inflating font sizes when the device rotates to landscape (and
|
|
leaving them big on rotate back). Every other page sets this; the chat didn't. */
|
|
-webkit-text-size-adjust: 100%;
|
|
text-size-adjust: 100%;
|
|
}
|
|
|
|
html {
|
|
/* Paints the iOS home-indicator strip below the dvh shell; match the tab bar so the
|
|
bar looks like it continues to the physical bottom edge. */
|
|
background: var(--bg-line);
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
background: var(--bg-dark);
|
|
color: var(--text-main);
|
|
font-family: var(--font-console);
|
|
height: 100vh; /* fallback for old browsers */
|
|
height: 100dvh;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overscroll-behavior: none;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
|
|
#chat {
|
|
width: 95%;
|
|
max-width: 900px;
|
|
height: 95vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
box-shadow: none;
|
|
background: var(--bg-dark);
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Header sections */
|
|
#model-select, #session-select, #status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 12px;
|
|
border-bottom: 1px solid var(--border);
|
|
background-color: var(--bg-elev);
|
|
}
|
|
#status {
|
|
justify-content: flex-start;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
/* Mode badge: the always-visible Talk/Cash toggle. Hidden on desktop (the header
|
|
<select> handles it there); shown in the minimal mobile header (see media query). */
|
|
.mode-badge {
|
|
display: none;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-family: var(--font-console);
|
|
font-size: 0.82rem;
|
|
color: var(--text-fade);
|
|
background: var(--bg-line);
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
padding: 4px 11px;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
/* Cash mode: light up the badge (and the chat brand) so the table state is obvious. */
|
|
body.cash-mode .mode-badge {
|
|
color: var(--accent);
|
|
border-color: var(--accent);
|
|
background: var(--accent-soft);
|
|
}
|
|
body.cash-mode .brand { color: var(--accent); }
|
|
|
|
label, select, button {
|
|
font-family: var(--font-console);
|
|
font-size: 0.9rem;
|
|
color: var(--text-main);
|
|
background: var(--bg-line);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
padding: 5px 9px;
|
|
transition: border-color .15s, background-color .15s;
|
|
}
|
|
label { background: transparent; border-color: transparent; padding-left: 0; }
|
|
|
|
button:hover, select:hover {
|
|
border-color: var(--border-bright);
|
|
background: var(--accent-soft);
|
|
cursor: pointer;
|
|
}
|
|
|
|
#thinkingStreamBtn {
|
|
background: var(--bg-line);
|
|
border-color: var(--border-bright);
|
|
color: var(--gold);
|
|
}
|
|
|
|
#thinkingStreamBtn:hover {
|
|
background: var(--accent-soft);
|
|
border-color: var(--gold);
|
|
}
|
|
|
|
/* Chat area */
|
|
#messages {
|
|
flex: 1;
|
|
min-height: 0;
|
|
padding: 16px;
|
|
overflow-y: auto;
|
|
overscroll-behavior: contain;
|
|
-webkit-overflow-scrolling: touch;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
/* No CSS smooth-scroll: during streaming, per-token smooth scrolls pile up and
|
|
iOS Safari leaves ghost paint frames. Smooth is applied explicitly in JS where
|
|
it's a one-shot (load/finalize). */
|
|
}
|
|
|
|
/* Messages */
|
|
.msg {
|
|
max-width: 80%;
|
|
padding: 10px 14px;
|
|
border-radius: 12px;
|
|
line-height: 1.4;
|
|
word-wrap: break-word;
|
|
box-shadow: none;
|
|
}
|
|
.msg.user {
|
|
align-self: flex-end;
|
|
background: var(--accent-soft);
|
|
border: 1px solid var(--border-bright);
|
|
border-bottom-right-radius: 4px;
|
|
}
|
|
.msg.assistant {
|
|
align-self: flex-start;
|
|
background: var(--bg-elev);
|
|
border: 1px solid var(--border);
|
|
border-bottom-left-radius: 4px;
|
|
}
|
|
.msg.system {
|
|
align-self: center;
|
|
font-size: 0.78rem;
|
|
color: var(--text-fade);
|
|
text-align: center;
|
|
padding: 4px 10px;
|
|
}
|
|
|
|
/* Input bar */
|
|
#input {
|
|
display: flex;
|
|
align-items: flex-end; /* arrow stays at the bottom as the textarea grows */
|
|
border-top: 1px solid var(--border);
|
|
background: var(--bg-elev);
|
|
padding: 10px;
|
|
}
|
|
#userInput {
|
|
flex: 1;
|
|
background: var(--bg-line);
|
|
color: var(--text-main);
|
|
border: 1px solid var(--border);
|
|
border-radius: 16px;
|
|
padding: 9px 12px;
|
|
font-family: var(--font-console);
|
|
font-size: 0.95rem;
|
|
line-height: 1.4;
|
|
resize: none; /* grown programmatically, not by the drag handle */
|
|
max-height: 140px;
|
|
overflow-y: auto;
|
|
transition: border-color .15s, box-shadow .15s;
|
|
}
|
|
#userInput::placeholder { color: var(--text-fade); }
|
|
#userInput:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: var(--accent-glow);
|
|
}
|
|
#sendBtn {
|
|
margin-left: 8px;
|
|
flex: none;
|
|
width: 38px;
|
|
height: 38px;
|
|
padding: 0;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.25rem;
|
|
line-height: 1;
|
|
background: var(--accent);
|
|
color: #0a0a0a;
|
|
border-color: var(--accent);
|
|
font-weight: 600;
|
|
}
|
|
#sendBtn:hover { background: var(--gold); border-color: var(--gold); }
|
|
#sendBtn:disabled { opacity: .45; background: var(--bg-line); color: var(--text-fade); border-color: var(--border); }
|
|
|
|
/* Relay status dot */
|
|
#status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-family: var(--font-console);
|
|
font-size: 0.82rem;
|
|
color: var(--text-fade);
|
|
}
|
|
|
|
#status-dot {
|
|
width: 9px;
|
|
height: 9px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
background: var(--text-fade);
|
|
}
|
|
|
|
@keyframes pulseGreen {
|
|
0% { box-shadow: 0 0 5px #8fd694; opacity: 0.9; }
|
|
50% { box-shadow: 0 0 10px #8fd694; opacity: 1; }
|
|
100% { box-shadow: 0 0 5px #8fd694; opacity: 0.9; }
|
|
}
|
|
|
|
.dot.ok {
|
|
background: var(--good);
|
|
animation: pulseGreen 2s infinite ease-in-out;
|
|
}
|
|
|
|
/* Offline state stays solid red */
|
|
.dot.fail {
|
|
background: var(--bad);
|
|
box-shadow: 0 0 8px rgba(255, 90, 90, 0.5);
|
|
}
|
|
|
|
|
|
/* Dropdown (session selector) styling */
|
|
select {
|
|
background-color: var(--bg-line);
|
|
color: var(--text-main);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
padding: 5px 8px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
select option {
|
|
background-color: var(--bg-elev);
|
|
color: var(--text-main);
|
|
}
|
|
|
|
/* Hover/focus for better visibility */
|
|
select:focus,
|
|
select:hover {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
background-color: var(--bg-line);
|
|
}
|
|
|
|
/* Settings Modal */
|
|
.modal {
|
|
display: none !important;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.modal.show {
|
|
display: block !important;
|
|
}
|
|
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
backdrop-filter: blur(4px);
|
|
z-index: 999;
|
|
}
|
|
|
|
.modal-content {
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: var(--bg-elev);
|
|
border: 1px solid var(--border);
|
|
border-radius: 14px;
|
|
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
|
|
min-width: 400px;
|
|
max-width: 600px;
|
|
max-height: 80vh;
|
|
overflow-y: auto;
|
|
z-index: 1001;
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 16px 20px;
|
|
border-bottom: 1px solid var(--border);
|
|
background: var(--bg-line);
|
|
}
|
|
|
|
.modal-header h3 {
|
|
margin: 0;
|
|
font-size: 1.2rem;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.close-btn {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--accent);
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
width: 30px;
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.close-btn:hover {
|
|
background: var(--accent-soft);
|
|
color: var(--accent);
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 20px;
|
|
}
|
|
|
|
.settings-section h4 {
|
|
margin: 0 0 8px 0;
|
|
color: var(--accent);
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.settings-desc {
|
|
margin: 0 0 16px 0;
|
|
color: var(--text-fade);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.radio-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.radio-label {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 12px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
background: var(--bg-line);
|
|
cursor: pointer;
|
|
transition: border-color 0.15s, background-color 0.15s;
|
|
}
|
|
|
|
.radio-label:hover {
|
|
border-color: var(--border-bright);
|
|
background: var(--accent-soft);
|
|
}
|
|
|
|
.radio-label input[type="radio"] {
|
|
margin-right: 8px;
|
|
accent-color: var(--accent);
|
|
}
|
|
|
|
.radio-label span {
|
|
font-weight: 500;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.radio-label small {
|
|
color: var(--text-fade);
|
|
font-size: 0.8rem;
|
|
margin-left: 24px;
|
|
}
|
|
|
|
.radio-label input[type="text"] {
|
|
margin-top: 8px;
|
|
margin-left: 24px;
|
|
padding: 6px;
|
|
background: rgba(0,0,0,0.3);
|
|
border: 1px solid rgba(255,122,0,0.5);
|
|
border-radius: 4px;
|
|
color: var(--text-main);
|
|
font-family: var(--font-console);
|
|
}
|
|
|
|
.radio-label input[type="text"]:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 8px rgba(255,122,0,0.3);
|
|
}
|
|
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
padding: 16px 20px;
|
|
border-top: 1px solid var(--border);
|
|
background: var(--bg-line);
|
|
}
|
|
|
|
.primary-btn {
|
|
background: var(--accent);
|
|
color: #0a0a0a;
|
|
font-weight: 600;
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
.primary-btn:hover {
|
|
background: var(--gold);
|
|
border-color: var(--gold);
|
|
}
|
|
|
|
/* Session List */
|
|
.session-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.session-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
background: var(--bg-line);
|
|
transition: border-color 0.15s, background-color 0.15s;
|
|
}
|
|
|
|
.session-item:hover {
|
|
border-color: var(--border-bright);
|
|
background: var(--accent-soft);
|
|
}
|
|
|
|
.session-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
flex: 1;
|
|
}
|
|
|
|
.session-info strong {
|
|
color: var(--text-main);
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.session-info small {
|
|
color: var(--text-fade);
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.session-delete-btn {
|
|
background: transparent;
|
|
border: 1px solid rgba(255,122,0,0.5);
|
|
color: var(--accent);
|
|
padding: 6px 10px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 1rem;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.session-delete-btn:hover {
|
|
background: rgba(255,0,0,0.2);
|
|
border-color: #ff3333;
|
|
color: #ff3333;
|
|
box-shadow: 0 0 8px rgba(255,0,0,0.3);
|
|
}
|
|
|
|
/* Thinking Stream Panel */
|
|
.thinking-panel {
|
|
border-top: 1px solid var(--border);
|
|
background: var(--bg-dark);
|
|
display: flex;
|
|
flex-direction: column;
|
|
transition: max-height 0.3s ease;
|
|
max-height: 300px;
|
|
}
|
|
|
|
.thinking-panel.collapsed {
|
|
max-height: 40px;
|
|
}
|
|
|
|
.thinking-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 10px 12px;
|
|
background: var(--bg-elev);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.thinking-header:hover {
|
|
background: var(--accent-soft);
|
|
}
|
|
|
|
.thinking-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.thinking-status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: #666;
|
|
display: inline-block;
|
|
}
|
|
|
|
.thinking-status-dot.connected {
|
|
background: #8fd694;
|
|
box-shadow: 0 0 8px #8fd694;
|
|
}
|
|
|
|
.thinking-status-dot.disconnected {
|
|
background: #ff3333;
|
|
}
|
|
|
|
.thinking-clear-btn,
|
|
.thinking-toggle-btn {
|
|
background: var(--bg-line);
|
|
border: 1px solid var(--border);
|
|
color: var(--text-main);
|
|
padding: 4px 8px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.thinking-clear-btn:hover,
|
|
.thinking-toggle-btn:hover {
|
|
background: var(--accent-soft);
|
|
border-color: var(--border-bright);
|
|
}
|
|
|
|
.thinking-toggle-btn {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.thinking-panel.collapsed .thinking-toggle-btn {
|
|
transform: rotate(-90deg);
|
|
}
|
|
|
|
.thinking-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
min-height: 0;
|
|
}
|
|
|
|
.thinking-panel.collapsed .thinking-content {
|
|
display: none;
|
|
}
|
|
|
|
.thinking-empty {
|
|
text-align: center;
|
|
padding: 40px 20px;
|
|
color: var(--text-fade);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.thinking-empty-icon {
|
|
font-size: 2rem;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.thinking-event {
|
|
padding: 8px 12px;
|
|
border-radius: 6px;
|
|
font-size: 0.85rem;
|
|
font-family: 'Courier New', monospace;
|
|
animation: thinkingSlideIn 0.3s ease-out;
|
|
border-left: 3px solid;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
@keyframes thinkingSlideIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.thinking-event-connected {
|
|
background: rgba(0, 255, 122, 0.1);
|
|
border-color: #8fd694;
|
|
color: #8fd694;
|
|
}
|
|
|
|
.thinking-event-thinking {
|
|
background: rgba(255, 179, 71, 0.1);
|
|
border-color: #ffb347;
|
|
color: #c79cff;
|
|
}
|
|
|
|
.thinking-event-tool_call {
|
|
background: rgba(255, 165, 0, 0.1);
|
|
border-color: #ffa500;
|
|
color: #ffb84d;
|
|
}
|
|
|
|
.thinking-event-tool_result {
|
|
background: rgba(0, 191, 255, 0.1);
|
|
border-color: #00bfff;
|
|
color: #7dd3fc;
|
|
}
|
|
|
|
.thinking-event-done {
|
|
background: rgba(168, 85, 247, 0.1);
|
|
border-color: #a855f7;
|
|
color: #e9d5ff;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.thinking-event-error {
|
|
background: rgba(255, 51, 51, 0.1);
|
|
border-color: #ff3333;
|
|
color: #fca5a5;
|
|
}
|
|
|
|
.thinking-event-icon {
|
|
display: inline-block;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.thinking-event-details {
|
|
font-size: 0.75rem;
|
|
color: var(--text-fade);
|
|
margin-top: 4px;
|
|
padding-left: 20px;
|
|
white-space: pre-wrap;
|
|
max-height: 100px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* ========== MOBILE RESPONSIVE STYLES ========== */
|
|
|
|
/* Wordmark + status dot — shown only in the mobile header (media query below) */
|
|
.brand, .brand-dot { display: none; }
|
|
|
|
/* Bottom tab bar — mobile only (shown in the media query) */
|
|
#tabbar { display: none; }
|
|
|
|
/* Hamburger Menu */
|
|
.hamburger-menu {
|
|
display: none;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
cursor: pointer;
|
|
padding: 8px;
|
|
border: 1px solid var(--border-bright);
|
|
border-radius: 8px;
|
|
background: var(--bg-line);
|
|
z-index: 100;
|
|
}
|
|
|
|
.hamburger-menu span {
|
|
width: 20px;
|
|
height: 2px;
|
|
background: var(--accent);
|
|
transition: all 0.3s;
|
|
display: block;
|
|
}
|
|
|
|
.hamburger-menu.active span:nth-child(1) {
|
|
transform: rotate(45deg) translate(5px, 5px);
|
|
}
|
|
|
|
.hamburger-menu.active span:nth-child(2) {
|
|
opacity: 0;
|
|
}
|
|
|
|
.hamburger-menu.active span:nth-child(3) {
|
|
transform: rotate(-45deg) translate(5px, -5px);
|
|
}
|
|
|
|
/* Mobile Menu Container */
|
|
.mobile-menu {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: -100%;
|
|
width: 280px;
|
|
height: 100vh;
|
|
height: 100dvh;
|
|
background: var(--bg-elev);
|
|
border-right: 1px solid var(--border);
|
|
box-shadow: 8px 0 32px rgba(0, 0, 0, 0.5);
|
|
z-index: 999;
|
|
transition: left 0.3s ease;
|
|
overflow-y: auto;
|
|
overscroll-behavior: contain;
|
|
padding: 20px;
|
|
padding-top: calc(20px + env(safe-area-inset-top));
|
|
padding-bottom: calc(20px + env(safe-area-inset-bottom));
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
.mobile-menu.open {
|
|
left: 0;
|
|
}
|
|
|
|
.mobile-menu-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
z-index: 998;
|
|
}
|
|
|
|
.mobile-menu-overlay.show {
|
|
display: block;
|
|
}
|
|
|
|
.mobile-menu-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
padding-bottom: 16px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.mobile-menu-section:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.mobile-menu-section h4 {
|
|
margin: 0;
|
|
color: var(--accent);
|
|
font-size: 0.9rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
.mobile-menu button,
|
|
.mobile-menu select {
|
|
width: 100%;
|
|
padding: 10px;
|
|
font-size: 0.95rem;
|
|
text-align: left;
|
|
}
|
|
|
|
/* Mobile Breakpoints */
|
|
@media screen and (max-width: 768px) {
|
|
body {
|
|
padding: 0;
|
|
background: var(--bg-line); /* matches the tab bar so any strip below #chat is seamless */
|
|
}
|
|
|
|
#chat {
|
|
position: fixed;
|
|
top: 0; left: 0; right: 0;
|
|
width: 100%;
|
|
height: 100vh; /* fallback for old browsers */
|
|
height: 100dvh; /* the *visible* viewport — keep all content (incl. the tab bar)
|
|
inside what iOS actually paints, so nothing is clipped into the
|
|
home-indicator dead zone. The strip below is matched in color. */
|
|
background: var(--bg-dark);
|
|
border-radius: 0;
|
|
border: none;
|
|
}
|
|
/* Only while the keyboard is open do we follow the *visible* viewport: release
|
|
the bottom anchor and size from the top by the measured visible height. */
|
|
body.kb #chat {
|
|
bottom: auto;
|
|
height: var(--app-height, 100dvh);
|
|
transform: translateY(var(--app-offset, 0px));
|
|
}
|
|
|
|
/* Show hamburger, hide desktop header controls */
|
|
.hamburger-menu {
|
|
display: flex;
|
|
}
|
|
|
|
#model-select {
|
|
padding: 12px;
|
|
padding-top: calc(12px + env(safe-area-inset-top));
|
|
padding-left: calc(14px + env(safe-area-inset-left));
|
|
padding-right: calc(14px + env(safe-area-inset-right));
|
|
justify-content: flex-start;
|
|
gap: 12px;
|
|
}
|
|
|
|
/* Mobile header is [≡] Lyra [♠ Cash] [●] — hide everything else. */
|
|
#model-select > *:not(.hamburger-menu):not(.brand):not(.brand-dot):not(.mode-badge) {
|
|
display: none;
|
|
}
|
|
.mode-badge { display: inline-flex; margin-left: 4px; }
|
|
.brand {
|
|
display: block;
|
|
font-family: var(--font-console);
|
|
font-weight: 600;
|
|
font-size: 1.1rem;
|
|
color: var(--accent);
|
|
letter-spacing: 0.5px;
|
|
}
|
|
.brand-dot {
|
|
display: block;
|
|
width: 9px; height: 9px;
|
|
border-radius: 50%;
|
|
background: var(--text-fade);
|
|
margin-left: auto;
|
|
transition: background-color .2s;
|
|
}
|
|
.brand-dot.ok { background: var(--good); box-shadow: 0 0 8px rgba(143, 214, 148, .55); }
|
|
.brand-dot.fail { background: var(--bad); }
|
|
|
|
#session-select { display: none; }
|
|
#status { display: none; } /* relay status now lives as the header dot */
|
|
|
|
/* Show mobile menu */
|
|
.mobile-menu {
|
|
display: flex;
|
|
}
|
|
|
|
/* Messages - more width on mobile */
|
|
.msg {
|
|
max-width: 90%;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
/* Status bar */
|
|
#status {
|
|
padding: 10px 12px;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* Input area - bigger touch targets. The tab bar owns the bottom safe-area
|
|
inset now (the input is no longer the bottom-most element). */
|
|
#input {
|
|
padding: 12px;
|
|
padding-left: calc(12px + env(safe-area-inset-left));
|
|
padding-right: calc(12px + env(safe-area-inset-right));
|
|
}
|
|
|
|
/* Bottom tab bar */
|
|
#tabbar {
|
|
display: flex;
|
|
flex: none; /* never let it be compressed/clipped by the flex column */
|
|
border-top: 1px solid var(--border);
|
|
background: var(--bg-line); /* lighter than the page so it reads as a solid bar */
|
|
/* Shell is 100dvh, so the bar sits at the bottom of the rendered area with the icons
|
|
fully visible. Minimal padding keeps them low; the home-indicator strip just below
|
|
the rendered area is painted the same color (html bg) so the bar looks continuous. */
|
|
padding-bottom: 4px;
|
|
padding-left: env(safe-area-inset-left);
|
|
padding-right: env(safe-area-inset-right);
|
|
}
|
|
#tabbar .tab {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 3px;
|
|
padding: 7px 0 5px;
|
|
background: none;
|
|
border: none;
|
|
border-radius: 0;
|
|
color: var(--text-fade);
|
|
font-family: var(--font-console);
|
|
text-decoration: none;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
#tabbar .tab:hover { background: none; }
|
|
#tabbar .tab:active { background: var(--accent-soft); }
|
|
#tabbar .tab .ti { font-size: 1.3rem; line-height: 1; filter: grayscale(.45); }
|
|
#tabbar .tab .tl { font-size: .64rem; letter-spacing: .3px; }
|
|
#tabbar .tab.active { color: var(--accent); }
|
|
#tabbar .tab.active .ti { filter: none; }
|
|
body.kb #tabbar { display: none; } /* keyboard open ⇒ hide so input pins to keyboard */
|
|
|
|
/* The "More" tab is the menu trigger now — retire the hamburger. */
|
|
.hamburger-menu { display: none !important; }
|
|
|
|
#userInput {
|
|
font-size: 16px; /* Prevents zoom on iOS */
|
|
padding: 11px 14px;
|
|
}
|
|
|
|
#sendBtn {
|
|
width: 44px; /* comfortable touch target */
|
|
height: 44px;
|
|
padding: 0;
|
|
font-size: 1.35rem;
|
|
}
|
|
|
|
/* Modal - full width on mobile */
|
|
.modal-content {
|
|
width: 95%;
|
|
min-width: unset;
|
|
max-width: unset;
|
|
max-height: 90vh;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
.modal-header {
|
|
padding: 12px 16px;
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 16px;
|
|
}
|
|
|
|
.modal-footer {
|
|
padding: 12px 16px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.modal-footer button {
|
|
flex: 1;
|
|
min-width: 120px;
|
|
}
|
|
|
|
/* Radio labels - stack better on mobile */
|
|
.radio-label {
|
|
padding: 10px;
|
|
}
|
|
|
|
.radio-label small {
|
|
margin-left: 20px;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
/* Session list */
|
|
.session-item {
|
|
padding: 10px;
|
|
}
|
|
|
|
.session-info strong {
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.session-info small {
|
|
font-size: 0.7rem;
|
|
}
|
|
|
|
/* Settings button in header */
|
|
#settingsBtn {
|
|
padding: 8px 12px;
|
|
}
|
|
|
|
/* Thinking panel adjustments for mobile */
|
|
.thinking-panel {
|
|
max-height: 250px;
|
|
}
|
|
|
|
.thinking-panel.collapsed {
|
|
max-height: 38px;
|
|
}
|
|
|
|
.thinking-header {
|
|
padding: 8px 10px;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.thinking-event {
|
|
font-size: 0.8rem;
|
|
padding: 6px 10px;
|
|
}
|
|
|
|
.thinking-event-details {
|
|
font-size: 0.7rem;
|
|
max-height: 80px;
|
|
}
|
|
}
|
|
|
|
/* Extra small devices (phones in portrait) */
|
|
@media screen and (max-width: 480px) {
|
|
.mobile-menu {
|
|
width: 240px;
|
|
}
|
|
|
|
.msg {
|
|
max-width: 95%;
|
|
font-size: 0.9rem;
|
|
padding: 8px 12px;
|
|
}
|
|
|
|
#userInput {
|
|
font-size: 16px;
|
|
padding: 10px 13px;
|
|
}
|
|
|
|
#sendBtn {
|
|
width: 42px;
|
|
height: 42px;
|
|
padding: 0;
|
|
font-size: 1.3rem;
|
|
}
|
|
|
|
.modal-header h3 {
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.settings-section h4 {
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.radio-label span {
|
|
font-size: 0.9rem;
|
|
}
|
|
}
|
|
|
|
/* Tablet landscape and desktop */
|
|
@media screen and (min-width: 769px) {
|
|
/* Ensure mobile menu is hidden on desktop */
|
|
.mobile-menu,
|
|
.mobile-menu-overlay {
|
|
display: none !important;
|
|
}
|
|
|
|
.hamburger-menu {
|
|
display: none !important;
|
|
}
|
|
}
|
|
|
|
/* ---- Live Log lines ---- */
|
|
.log-line {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: baseline;
|
|
gap: 8px;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 0.8rem;
|
|
font-family: 'Courier New', monospace;
|
|
border-left: 3px solid var(--text-fade);
|
|
animation: thinkingSlideIn 0.25s ease-out;
|
|
word-break: break-word;
|
|
}
|
|
.log-time { color: var(--text-fade); flex-shrink: 0; }
|
|
.log-level {
|
|
flex-shrink: 0;
|
|
text-transform: uppercase;
|
|
font-size: 0.7rem;
|
|
font-weight: bold;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
.log-msg { color: var(--text); }
|
|
.log-fields { color: var(--text-fade); width: 100%; padding-left: 4px; }
|
|
|
|
.log-info { border-left-color: #00bfff; }
|
|
.log-info .log-level { color: #7dd3fc; }
|
|
.log-debug { border-left-color: #ffb347; }
|
|
.log-debug .log-level { color: #c79cff; }
|
|
.log-error { border-left-color: #ff3333; background: rgba(255,51,51,0.08); }
|
|
.log-error .log-level, .log-error .log-msg { color: #fca5a5; }
|
|
.log-system { border-left-color: #8fd694; }
|
|
.log-system .log-level { color: #8fd694; }
|
|
|
|
.log-detail { width: 100%; margin-top: 4px; }
|
|
.log-detail summary {
|
|
cursor: pointer;
|
|
color: var(--accent);
|
|
font-size: 0.72rem;
|
|
user-select: none;
|
|
}
|
|
.log-detail pre {
|
|
margin: 6px 0 0;
|
|
padding: 8px;
|
|
max-height: 340px;
|
|
overflow: auto;
|
|
background: rgba(0,0,0,0.25);
|
|
border-left: 2px solid var(--accent);
|
|
border-radius: 4px;
|
|
font-size: 0.72rem;
|
|
line-height: 1.4;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
color: var(--text);
|
|
}
|
|
|
|
/* Rendered markdown in Lyra's replies — readable proportional type + structure. */
|
|
.msg.assistant {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
line-height: 1.55;
|
|
max-width: 88%;
|
|
}
|
|
.msg.assistant p { margin: 0 0 10px; }
|
|
.msg.assistant p:last-child { margin-bottom: 0; }
|
|
.msg.assistant h1, .msg.assistant h2, .msg.assistant h3, .msg.assistant h4 {
|
|
margin: 14px 0 6px; line-height: 1.3; color: var(--accent);
|
|
}
|
|
.msg.assistant h1 { font-size: 1.18rem; }
|
|
.msg.assistant h2 { font-size: 1.1rem; }
|
|
.msg.assistant h3 { font-size: 1.02rem; }
|
|
.msg.assistant h4 { font-size: 0.96rem; }
|
|
.msg.assistant ul, .msg.assistant ol { margin: 6px 0 10px; padding-left: 22px; }
|
|
.msg.assistant li { margin: 3px 0; }
|
|
.msg.assistant li > ul, .msg.assistant li > ol { margin: 3px 0; }
|
|
.msg.assistant strong { font-weight: 600; color: var(--text); }
|
|
.msg.assistant em { font-style: italic; }
|
|
.msg.assistant a { color: var(--accent); text-decoration: underline; }
|
|
.msg.assistant code {
|
|
font-family: "IBM Plex Mono", monospace; font-size: 0.88em;
|
|
background: rgba(255,255,255,0.08); padding: 1px 5px; border-radius: 4px;
|
|
}
|
|
.msg.assistant pre {
|
|
background: rgba(0,0,0,0.32); border: 1px solid rgba(255,122,0,0.3);
|
|
border-radius: 6px; padding: 10px 12px; margin: 8px 0; overflow-x: auto;
|
|
}
|
|
.msg.assistant pre code { background: none; padding: 0; font-size: 0.85em; }
|
|
|
|
/* Streaming: a blinking caret while tokens arrive (and a min-size while empty). */
|
|
.msg.assistant.streaming { min-width: 1.4em; min-height: 1.1em; }
|
|
.msg.assistant.streaming::after {
|
|
content: "▋";
|
|
margin-left: 1px;
|
|
color: var(--accent);
|
|
animation: caretBlink 1s steps(1) infinite;
|
|
}
|
|
@keyframes caretBlink { 0%, 50% { opacity: 0.85; } 50.01%, 100% { opacity: 0; } }
|
|
|
|
/* Behind-the-scenes 👍/👎 feedback (fine-tune signal) — subtle until hovered. */
|
|
.rate-bar { display: flex; gap: 6px; margin-top: 7px; opacity: 0.3; transition: opacity .15s; }
|
|
.msg.assistant:hover .rate-bar { opacity: 0.85; }
|
|
.rate-btn {
|
|
background: none; border: none; cursor: pointer; font-size: 0.85rem;
|
|
padding: 2px 5px; border-radius: 5px; line-height: 1; filter: grayscale(0.6);
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
.rate-btn:hover { filter: none; background: var(--accent-soft); }
|
|
.rate-btn.rated { filter: none; background: rgba(255,122,0,0.22); opacity: 1; }
|
|
|
|
/* Per-message copy button (lives in the rate-bar for assistant, its own bar for user). */
|
|
.copy-btn {
|
|
background: none; border: none; cursor: pointer; font-size: 0.85rem;
|
|
padding: 2px 6px; border-radius: 5px; line-height: 1; color: var(--text-fade);
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
.copy-btn:hover { background: var(--accent-soft); color: var(--accent); }
|
|
.copy-btn.copied { color: var(--good); }
|
|
/* User bubbles are right-aligned, so right-align their copy bar too. */
|
|
.msg.user .rate-bar { justify-content: flex-end; opacity: 0.4; }
|
|
.msg.user:hover .rate-bar { opacity: 0.85; }
|
|
/* Touch devices have no hover — keep the tools tappable/visible. */
|
|
@media (hover: none) {
|
|
.rate-bar, .msg.user .rate-bar { opacity: 0.65; }
|
|
}
|
|
|
|
/* Quality floor: honor reduced-motion preference. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*, *::before, *::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|
|
|
|
/* Stack quick-capture (2nd input box on the chat page) — logs without the LLM. */
|
|
#stackQuick {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
padding: 6px 12px;
|
|
border-top: 1px solid var(--border);
|
|
background: var(--bg-panel);
|
|
}
|
|
#stackQuick input {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 8px 10px;
|
|
background: var(--bg-elev);
|
|
color: inherit;
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
}
|
|
#stackQuick button {
|
|
padding: 8px 14px;
|
|
background: var(--accent);
|
|
color: #000;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|