1 *, *::before, *::after {
2 box-sizing: border-box;
3 margin: 0;
4 padding: 0;
5 }
6
7 :root {
8 --bg: #fafafa;
9 --surface: #ffffff;
10 --border: #e8e8e8;
11 --text-primary: #1a1a1a;
12 --text-secondary: #8a8a8a;
13 --accent: #2d2d2d;
14 --accent-hover: #1a1a1a;
15 --user-bubble: #1a1a1a;
16 --user-bubble-text: #ffffff;
17 --ai-bubble: #f0f0f0;
18 --ai-bubble-text: #1a1a1a;
19 --input-bg: #f5f5f5;
20 --radius: 18px;
21 --radius-sm: 8px;
22
23 font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
24 font-size: 15px;
25 line-height: 1.5;
26 -webkit-font-smoothing: antialiased;
27
28 /* Adapt native form controls and the WebView backdrop to the system theme. */
29 color-scheme: light dark;
30 }
31
32 @media (prefers-color-scheme: dark) {
33 :root {
34 --bg: #111111;
35 --surface: #1c1c1c;
36 --border: #2a2a2a;
37 --text-primary: #f0f0f0;
38 --text-secondary: #666666;
39 --accent: #e0e0e0;
40 --accent-hover: #ffffff;
41 --user-bubble: #e8e8e8;
42 --user-bubble-text: #111111;
43 --ai-bubble: #242424;
44 --ai-bubble-text: #f0f0f0;
45 --input-bg: #1c1c1c;
46 }
47 }
48
49 html, body {
50 height: 100%;
51 width: 100%;
52 /* Paint the theme colour across the whole WebView, including the regions
53 behind the status bar and home indicator, so the safe areas blend with the
54 app instead of showing the native (dark) backdrop. */
55 background: var(--bg);
56 overscroll-behavior: none;
57 -webkit-tap-highlight-color: transparent;
58 touch-action: manipulation;
59 }
60
61 #root {
62 height: 100%;
63 }
64
65 .app {
66 display: flex;
67 flex-direction: column;
68 height: 100dvh;
69 background: var(--bg);
70 color: var(--text-primary);
71 user-select: none;
72 }
73
74 /* Header */
75 .header {
76 display: flex;
77 align-items: center;
78 justify-content: space-between;
79 /* Push the title clear of the status bar; keep the original 20px on devices
80 with no top inset. Honour left/right insets in landscape / on iPad. */
81 padding: calc(env(safe-area-inset-top) + 20px)
82 max(24px, env(safe-area-inset-right)) 16px max(24px, env(safe-area-inset-left));
83 border-bottom: 1px solid var(--border);
84 background: var(--bg);
85 flex-shrink: 0;
86 }
87
88 .header-title {
89 display: flex;
90 flex-direction: column;
91 }
92
93 .header-title h1 {
94 font-size: 17px;
95 font-weight: 600;
96 letter-spacing: -0.2px;
97 color: var(--text-primary);
98 }
99
100 .header-subtitle {
101 font-size: 12px;
102 color: var(--text-secondary);
103 letter-spacing: 0.2px;
104 }
105
106 .status-dot {
107 width: 8px;
108 height: 8px;
109 border-radius: 50%;
110 background: #4caf50;
111 flex-shrink: 0;
112 }
113
114 .status-dot.thinking {
115 background: #ff9800;
116 animation: pulse 1.2s ease-in-out infinite;
117 }
118
119 @keyframes pulse {
120 0%, 100% { opacity: 1; }
121 50% { opacity: 0.4; }
122 }
123
124 /* Messages area */
125 .messages {
126 flex: 1;
127 overflow-y: scroll;
128 -webkit-overflow-scrolling: touch;
129 overscroll-behavior-y: contain;
130 padding: 24px 0;
131 display: flex;
132 flex-direction: column;
133 gap: 2px;
134 scrollbar-width: none;
135 }
136
137 .messages::-webkit-scrollbar {
138 display: none;
139 }
140
141 /* Empty state */
142 .empty-state {
143 flex: 1;
144 display: flex;
145 flex-direction: column;
146 align-items: center;
147 justify-content: center;
148 gap: 8px;
149 padding: 40px;
150 color: var(--text-secondary);
151 text-align: center;
152 }
153
154 .empty-icon {
155 font-size: 32px;
156 margin-bottom: 4px;
157 opacity: 0.4;
158 }
159
160 .empty-state h2 {
161 font-size: 16px;
162 font-weight: 500;
163 color: var(--text-secondary);
164 }
165
166 .empty-state p {
167 font-size: 13px;
168 max-width: 220px;
169 line-height: 1.6;
170 }
171
172 /* Message row */
173 .message-row {
174 display: flex;
175 padding: 4px 24px;
176 }
177
178 .message-row.user {
179 justify-content: flex-end;
180 }
181
182 .message-row.assistant {
183 justify-content: flex-start;
184 }
185
186 .bubble {
187 max-width: 72%;
188 padding: 10px 14px;
189 border-radius: var(--radius);
190 font-size: 14px;
191 line-height: 1.55;
192 word-break: break-word;
193 user-select: text;
194 -webkit-user-select: text;
195 }
196
197 .message-row.user .bubble {
198 background: var(--user-bubble);
199 color: var(--user-bubble-text);
200 border-bottom-right-radius: var(--radius-sm);
201 }
202
203 .message-row.assistant .bubble {
204 background: var(--ai-bubble);
205 color: var(--ai-bubble-text);
206 border-bottom-left-radius: var(--radius-sm);
207 }
208
209 /* Typing indicator */
210 .typing-indicator {
211 display: flex;
212 gap: 4px;
213 align-items: center;
214 padding: 12px 14px;
215 }
216
217 .typing-dot {
218 width: 6px;
219 height: 6px;
220 border-radius: 50%;
221 background: var(--text-secondary);
222 animation: bounce 1.2s ease-in-out infinite;
223 }
224
225 .typing-dot:nth-child(2) { animation-delay: 0.2s; }
226 .typing-dot:nth-child(3) { animation-delay: 0.4s; }
227
228 @keyframes bounce {
229 0%, 60%, 100% { transform: translateY(0); }
230 30% { transform: translateY(-4px); }
231 }
232
233 /* Input area */
234 .input-area {
235 padding: 16px max(24px, env(safe-area-inset-right))
236 max(20px, env(safe-area-inset-bottom)) max(24px, env(safe-area-inset-left));
237 border-top: 1px solid var(--border);
238 background: var(--bg);
239 flex-shrink: 0;
240 }
241
242 .input-form {
243 display: flex;
244 align-items: flex-end;
245 gap: 10px;
246 background: var(--input-bg);
247 border: 1px solid var(--border);
248 border-radius: 14px;
249 padding: 10px 10px 10px 16px;
250 transition: border-color 0.15s;
251 }
252
253 .input-form:focus-within {
254 border-color: var(--text-secondary);
255 }
256
257 .input-field {
258 flex: 1;
259 background: transparent;
260 border: none;
261 outline: none;
262 color: var(--text-primary);
263 font-size: 16px; /* prevents iOS auto-zoom on focus */
264 font-family: inherit;
265 line-height: 1.5;
266 resize: none;
267 max-height: 120px;
268 min-height: 22px;
269 padding: 0;
270 user-select: text;
271 -webkit-user-select: text;
272 }
273
274 .input-field::placeholder {
275 color: var(--text-secondary);
276 }
277
278 .send-button {
279 width: 32px;
280 height: 32px;
281 border-radius: 8px;
282 border: none;
283 background: var(--accent);
284 color: var(--bg);
285 cursor: pointer;
286 display: flex;
287 align-items: center;
288 justify-content: center;
289 flex-shrink: 0;
290 transition: background 0.15s, transform 0.1s;
291 }
292
293 .send-button:active:not(:disabled) {
294 background: var(--accent-hover);
295 transform: scale(0.94);
296 }
297
298 .send-button:disabled {
299 opacity: 0.3;
300 cursor: default;
301 }
302
303 .send-button svg {
304 width: 16px;
305 height: 16px;
306 }
307
308 .input-hint {
309 font-size: 11px;
310 color: var(--text-secondary);
311 text-align: center;
312 margin-top: 10px;
313 letter-spacing: 0.1px;
314 }
315
316 /* Header actions (status dot + settings) */
317 .header-actions {
318 display: flex;
319 align-items: center;
320 gap: 14px;
321 }
322
323 .status-dot.error {
324 background: #e5484d;
325 animation: none;
326 }
327
328 .icon-button {
329 width: 34px;
330 height: 34px;
331 border: none;
332 background: transparent;
333 color: var(--text-secondary);
334 display: flex;
335 align-items: center;
336 justify-content: center;
337 border-radius: var(--radius-sm);
338 cursor: pointer;
339 -webkit-tap-highlight-color: transparent;
340 transition: color 0.15s, background 0.15s, transform 0.1s;
341 }
342
343 .icon-button:active {
344 background: var(--ai-bubble);
345 color: var(--text-primary);
346 transform: scale(0.92);
347 }
348
349 .icon-button svg {
350 width: 20px;
351 height: 20px;
352 }
353
354 /* ── Settings page (slide-up) ───────────────────────────────────────────── */
355 .settings-overlay {
356 position: fixed;
357 inset: 0;
358 z-index: 50;
359 display: flex;
360 flex-direction: column;
361 background: var(--bg);
362 color: var(--text-primary);
363 transform: translateY(100%);
364 visibility: hidden;
365 transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1), visibility 0.28s;
366 }
367
368 .settings-overlay.open {
369 transform: translateY(0);
370 visibility: visible;
371 }
372
373 .settings-header {
374 display: flex;
375 align-items: center;
376 justify-content: space-between;
377 padding: calc(env(safe-area-inset-top) + 18px) 20px 14px;
378 border-bottom: 1px solid var(--border);
379 flex-shrink: 0;
380 }
381
382 .settings-header h1 {
383 font-size: 22px;
384 font-weight: 700;
385 letter-spacing: -0.4px;
386 }
387
388 .settings-done {
389 border: none;
390 background: transparent;
391 color: var(--text-primary);
392 font-family: inherit;
393 font-size: 16px;
394 font-weight: 600;
395 padding: 6px 2px;
396 cursor: pointer;
397 }
398
399 .settings-done:active {
400 opacity: 0.5;
401 }
402
403 .settings-content {
404 flex: 1;
405 overflow-y: auto;
406 -webkit-overflow-scrolling: touch;
407 padding: 8px 16px max(24px, env(safe-area-inset-bottom));
408 }
409
410 .settings-section {
411 margin-top: 22px;
412 }
413
414 .section-label {
415 font-size: 12px;
416 font-weight: 600;
417 letter-spacing: 0.6px;
418 text-transform: uppercase;
419 color: var(--text-secondary);
420 padding: 0 8px 8px;
421 }
422
423 .settings-card {
424 background: var(--surface);
425 border: 1px solid var(--border);
426 border-radius: 14px;
427 overflow: hidden;
428 }
429
430 .settings-row {
431 display: flex;
432 align-items: center;
433 justify-content: space-between;
434 gap: 12px;
435 padding: 14px 16px;
436 min-height: 52px;
437 }
438
439 .settings-card.about .settings-row + .settings-row {
440 border-top: 1px solid var(--border);
441 }
442
443 .row-label {
444 font-size: 15px;
445 font-weight: 500;
446 color: var(--text-primary);
447 }
448
449 .row-value {
450 font-size: 15px;
451 color: var(--text-secondary);
452 }
453
454 .select-wrap {
455 position: relative;
456 display: flex;
457 align-items: center;
458 }
459
460 .model-select {
461 appearance: none;
462 -webkit-appearance: none;
463 border: none;
464 background: transparent;
465 color: var(--text-secondary);
466 font-family: inherit;
467 font-size: 15px;
468 text-align: right;
469 padding-right: 20px;
470 max-width: 190px;
471 white-space: nowrap;
472 overflow: hidden;
473 text-overflow: ellipsis;
474 cursor: pointer;
475 }
476
477 .model-select:disabled {
478 opacity: 0.5;
479 }
480
481 .select-wrap .chevron {
482 position: absolute;
483 right: 2px;
484 width: 11px;
485 height: 8px;
486 color: var(--text-secondary);
487 pointer-events: none;
488 }
489
490 .model-detail {
491 border-top: 1px solid var(--border);
492 padding: 14px 16px;
493 }
494
495 .model-org {
496 font-size: 12px;
497 font-weight: 600;
498 letter-spacing: 0.2px;
499 color: var(--text-secondary);
500 }
501
502 .model-desc {
503 font-size: 13px;
504 line-height: 1.5;
505 color: var(--text-primary);
506 margin-top: 4px;
507 }
508
509 .model-status {
510 display: flex;
511 align-items: center;
512 gap: 7px;
513 margin-top: 12px;
514 font-size: 13px;
515 font-weight: 500;
516 color: var(--text-secondary);
517 }
518
519 .model-status-dot {
520 width: 7px;
521 height: 7px;
522 border-radius: 50%;
523 background: var(--text-secondary);
524 flex-shrink: 0;
525 }
526
527 .model-status.ready,
528 .model-status.ready .model-status-dot {
529 color: #34c759;
530 }
531 .model-status.ready .model-status-dot {
532 background: #34c759;
533 }
534
535 .model-status.loading,
536 .model-status.loading .model-status-dot {
537 color: #ff9800;
538 }
539 .model-status.loading .model-status-dot {
540 background: #ff9800;
541 animation: pulse 1.2s ease-in-out infinite;
542 }
543
544 .model-status.error,
545 .model-status.error .model-status-dot {
546 color: #e5484d;
547 }
548 .model-status.error .model-status-dot {
549 background: #e5484d;
550 }
551
552 .settings-note {
553 font-size: 12px;
554 line-height: 1.5;
555 color: var(--text-secondary);
556 padding: 10px 8px 0;
557 }