@mooncompany/uplink-chat 0.32.3 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/bin/uplink.js +1 -1
  2. package/middleware/error-handler.js +1 -1
  3. package/package.json +1 -1
  4. package/public/css/{app.bc7e7484.css → app.09572269.css} +14 -13
  5. package/public/css/markdown.css +28 -0
  6. package/public/css/mobile.540efab9.css +610 -0
  7. package/public/css/mobile.css +9 -13
  8. package/public/css/{split-view.4bc23474.css → split-view.4e286356.css} +145 -9
  9. package/public/css/themes.css +687 -19
  10. package/public/dist/bundle.9b202855.js +1 -0
  11. package/public/img/wordmark.svg +2 -16
  12. package/public/index.html +107 -7
  13. package/public/js/agents-data.js +1 -1
  14. package/public/js/agents-ui.js +1 -1
  15. package/public/js/agents.js +1 -1
  16. package/public/js/app.js +1 -1
  17. package/public/js/appearance-settings.js +1 -1
  18. package/public/js/artifacts.js +1 -1
  19. package/public/js/audio-pcm-processor.js +1 -1
  20. package/public/js/audio-queue.js +1 -1
  21. package/public/js/bootstrap.js +1 -1
  22. package/public/js/chat.js +1 -1
  23. package/public/js/commands.js +1 -1
  24. package/public/js/connection-api.js +1 -1
  25. package/public/js/connection.js +1 -1
  26. package/public/js/context-tracker.js +1 -1
  27. package/public/js/core.js +1 -1
  28. package/public/js/cron-panel.js +1 -1
  29. package/public/js/dashboard.js +1 -1
  30. package/public/js/developer.js +1 -1
  31. package/public/js/encryption.js +1 -1
  32. package/public/js/errors.js +1 -1
  33. package/public/js/event-bus.js +1 -1
  34. package/public/js/fetch-utils.js +1 -1
  35. package/public/js/file-handler.js +1 -1
  36. package/public/js/files.js +1 -1
  37. package/public/js/gateway-chat.js +1 -1
  38. package/public/js/logger.js +1 -1
  39. package/public/js/markdown.js +1 -1
  40. package/public/js/message-actions.js +1 -1
  41. package/public/js/message-renderer.js +1 -1
  42. package/public/js/missed-messages.js +1 -1
  43. package/public/js/mobile-debug.js +1 -1
  44. package/public/js/notifications.js +1 -1
  45. package/public/js/offline-queue.js +1 -1
  46. package/public/js/onboarding.js +1 -1
  47. package/public/js/panels.js +1 -1
  48. package/public/js/premium.js +1 -1
  49. package/public/js/primary-header.js +1 -1
  50. package/public/js/realtime-voice.js +1 -1
  51. package/public/js/satellite-sync.js +1 -1
  52. package/public/js/satellite-ui.js +1 -1
  53. package/public/js/satellites.js +1 -1
  54. package/public/js/settings.js +1 -1
  55. package/public/js/shortcuts.js +1 -1
  56. package/public/js/split-chat.js +1 -1
  57. package/public/js/split-resize.js +1 -1
  58. package/public/js/splitview.js +1 -1
  59. package/public/js/storage.js +1 -1
  60. package/public/js/streaming-handler.js +1 -1
  61. package/public/js/stt-settings.js +1 -1
  62. package/public/js/themes.js +1 -1
  63. package/public/js/timestamps.js +1 -1
  64. package/public/js/tts-settings.js +1 -1
  65. package/public/js/ui.js +1 -1
  66. package/public/js/update-notifier.js +1 -1
  67. package/public/js/utils/constants.js +1 -1
  68. package/public/js/utils/icons.js +1 -1
  69. package/public/js/utils/sanitize.js +1 -1
  70. package/public/js/utils/sse-parser.js +1 -1
  71. package/public/js/vad.js +1 -1
  72. package/public/js/vendor/dompurify.min.js +1 -1
  73. package/public/js/voice-settings-v2.js +1 -1
  74. package/public/js/voice.js +1 -1
  75. package/public/sw.js +1 -1
  76. package/server/channel.js +1 -1
  77. package/server/chat.js +1 -1
  78. package/server/config-store.js +1 -1
  79. package/server/config.js +1 -1
  80. package/server/context.js +1 -1
  81. package/server/gateway-api-proxy.js +1 -1
  82. package/server/gateway-commands.js +1 -1
  83. package/server/gateway-proxy.js +1 -1
  84. package/server/index.js +1 -1
  85. package/server/logger.js +1 -1
  86. package/server/message-store.js +1 -1
  87. package/server/middleware/auth.js +1 -1
  88. package/server/middleware.js +1 -1
  89. package/server/openclaw-discover.js +1 -1
  90. package/server/premium/index.js +1 -1
  91. package/server/premium/license.js +1 -1
  92. package/server/realtime/bridge.js +1 -1
  93. package/server/realtime/index.js +1 -1
  94. package/server/realtime/tts-stream.js +1 -1
  95. package/server/routes/agents.js +1 -1
  96. package/server/routes/artifacts.js +1 -1
  97. package/server/routes/chat.js +1 -1
  98. package/server/routes/config-settings.js +1 -1
  99. package/server/routes/config.js +1 -1
  100. package/server/routes/cron.js +1 -1
  101. package/server/routes/files.js +1 -1
  102. package/server/routes/index.js +1 -1
  103. package/server/routes/media.js +1 -1
  104. package/server/routes/missed-messages.js +1 -1
  105. package/server/routes/premium.js +1 -1
  106. package/server/routes/push.js +1 -1
  107. package/server/routes/satellite.js +1 -1
  108. package/server/routes/status.js +1 -1
  109. package/server/routes/stt.js +1 -1
  110. package/server/routes/voice.js +1 -1
  111. package/server/routes/webhooks.js +1 -1
  112. package/server/routes.js +1 -1
  113. package/server/runtime-config.js +1 -1
  114. package/server/share.js +1 -1
  115. package/server/stt/faster-whisper.js +1 -1
  116. package/server/stt/groq.js +1 -1
  117. package/server/stt/index.js +1 -1
  118. package/server/stt/openai.js +1 -1
  119. package/server/sync.js +1 -1
  120. package/server/tailscale-https.js +1 -1
  121. package/server/tts.js +1 -1
  122. package/server/update-checker.js +1 -1
  123. package/server/utils/filename.js +1 -1
  124. package/server/utils.js +1 -1
  125. package/server/watchdog.js +1 -1
  126. package/server/websocket/broadcast.js +1 -1
  127. package/server/websocket/connections.js +1 -1
  128. package/server/websocket/index.js +1 -1
  129. package/server/websocket/routing.js +1 -1
  130. package/server/websocket/sync.js +1 -1
  131. package/server.js +1 -1
  132. package/utils/detect-tool-usage.js +1 -1
  133. package/utils/errors.js +1 -1
  134. package/utils/html-escape.js +1 -1
  135. package/utils/id-sanitize.js +1 -1
  136. package/utils/response.js +1 -1
  137. package/utils/with-retry.js +1 -1
  138. package/public/dist/bundle.493af136.js +0 -1
@@ -1,4 +1,4 @@
1
- /* ============================================
1
+ /* ============================================
2
2
  THEME SYSTEM
3
3
 
4
4
  How it works:
@@ -9,16 +9,16 @@
9
9
  - JS sets data-theme on <html> and saves to localStorage
10
10
  ============================================ */
11
11
 
12
- /* ── Midnight (Default) ──
12
+ /* ── Midnight (Default) ──
13
13
  Values live in :root in app.css. This block is a reference only. */
14
14
  :root[data-theme="midnight"] {
15
- /* All defaults no overrides needed */
15
+ /* All defaults — no overrides needed */
16
16
  }
17
17
 
18
- /* ── Daylight (Light Mode) ──
19
- Warm light theme soft white, washed red accent */
18
+ /* ── Daylight (Light Mode) ──
19
+ Warm light theme — soft white, washed red accent */
20
20
  :root[data-theme="daylight"] {
21
- --bg: #f8f5f1;
21
+ --bg: #f1efe9;
22
22
  --bg-secondary: rgba(252, 249, 244, 0.95);
23
23
  --bg-input: rgba(0, 0, 0, 0.03);
24
24
  --accent: #c84b4b;
@@ -66,8 +66,8 @@
66
66
  --black-40: rgba(0, 0, 0, 0.1);
67
67
  }
68
68
 
69
- /* ── Ember ──
70
- Deep fire theme dark navy base, crimson surfaces, amber/yellow accents
69
+ /* ── Ember ──
70
+ Deep fire theme — dark navy base, crimson surfaces, amber/yellow accents
71
71
  Palette: #03071e #370617 #6a040f #9d0208 #d00000 #dc2f02 #e85d04 #f48c06 #faa307 #ffba08 */
72
72
  :root[data-theme="ember"] {
73
73
  --bg: #03071e;
@@ -115,8 +115,8 @@
115
115
  --syntax-number: #e85d04;
116
116
  }
117
117
 
118
- /* ── Forest ──
119
- Deep earthy dark theme dark woodland greens, muted sage accents
118
+ /* ── Forest ──
119
+ Deep earthy dark theme — dark woodland greens, muted sage accents
120
120
  Palette: #dad7cd #a3b18a #588157 #3a5a40 #344e41 */
121
121
  :root[data-theme="forest"] {
122
122
  --bg: #131813;
@@ -147,7 +147,7 @@
147
147
  --user-bubble: rgba(58, 90, 64, 0.3);
148
148
  --user-accent: #3a5a40;
149
149
  --user-accent-hover: #344e41;
150
- --assistant-bubble: rgba(46, 43, 35, 0.63);
150
+ --assistant-bubble: rgb(69 52 33 / 65%);
151
151
 
152
152
  --success: #a3b18a;
153
153
  --warning: #d4a746;
@@ -164,8 +164,8 @@
164
164
  --syntax-number: #588157;
165
165
  }
166
166
 
167
- /* ── Noir ──
168
- Monochrome grayscale no color accent */
167
+ /* ── Noir ──
168
+ Monochrome grayscale — no color accent */
169
169
  :root[data-theme="noir"] {
170
170
  --bg: #000;
171
171
  --bg-secondary: rgba(20, 20, 20, 0.95);
@@ -192,10 +192,10 @@
192
192
  --error-30: rgba(239, 68, 68, 0.3);
193
193
  --success-20: rgba(74, 222, 128, 0.2);
194
194
 
195
- --user-bubble: rgba(60, 60, 60, 0.4);
195
+ --user-bubble: rgb(182 182 182 / 69%);
196
196
  --user-accent: #404040;
197
197
  --user-accent-hover: #333333;
198
- --assistant-bubble: #161f25;
198
+ --assistant-bubble: #212324cf;
199
199
 
200
200
  --success: #4ade80;
201
201
  --warning: #fbbf24;
@@ -258,13 +258,13 @@
258
258
  border-color: rgba(0, 0, 0, 0.1);
259
259
  }
260
260
 
261
- /* Header barely-there warm tint */
261
+ /* Header — barely-there warm tint */
262
262
  :root[data-theme="daylight"] .main-header {
263
263
  background: rgba(0, 0, 0, 0.02);
264
264
  border-bottom: 1px solid var(--border);
265
265
  }
266
266
 
267
- /* Code blocks subtle warm gray */
267
+ /* Code blocks — subtle warm gray */
268
268
  :root[data-theme="daylight"] pre,
269
269
  :root[data-theme="daylight"] code {
270
270
  background: rgba(0, 0, 0, 0.035);
@@ -275,7 +275,7 @@
275
275
  }
276
276
 
277
277
  /* ============================================
278
- EMBER OVERRIDES Rising ember sparks
278
+ EMBER OVERRIDES — Rising ember sparks
279
279
  ============================================ */
280
280
  :root[data-theme="ember"] .star {
281
281
  animation: ember-rise var(--duration) linear infinite !important;
@@ -320,7 +320,7 @@
320
320
  ============================================ */
321
321
 
322
322
  /* ============================================
323
- THEME PICKER Visual theme selector
323
+ THEME PICKER — Visual theme selector
324
324
  Replaces the plain <select> dropdown
325
325
  ============================================ */
326
326
 
@@ -385,3 +385,671 @@
385
385
  font-size: 0.6rem;
386
386
  opacity: 0.6;
387
387
  }
388
+
389
+
390
+ /* ============================================
391
+ PHOSPHOR
392
+ CRT terminal aesthetic — near-black base, neon green accent,
393
+ animated film grain overlay replacing the starfield
394
+ ============================================ */
395
+ :root[data-theme="phosphor"] {
396
+ --bg: #0a0a0f;
397
+ --bg-secondary: rgba(10, 18, 10, 0.97);
398
+ --bg-input: rgba(0, 30, 0, 0.6);
399
+ --accent: #39ff14;
400
+ --accent-hover: #57ff3a;
401
+ --accent-secondary: #00c800;
402
+ --text: #d4f0c8;
403
+ --text-muted: #7ab868;
404
+ --text-dim: #4a7a3a;
405
+ --text-inverse: #0a0a0f;
406
+ --border: rgba(57, 255, 20, 0.12);
407
+
408
+ --accent-05: rgba(57, 255, 20, 0.05);
409
+ --accent-10: rgba(57, 255, 20, 0.1);
410
+ --accent-15: rgba(57, 255, 20, 0.15);
411
+ --accent-20: rgba(57, 255, 20, 0.2);
412
+ --accent-30: rgba(57, 255, 20, 0.3);
413
+ --accent-40: rgba(57, 255, 20, 0.4);
414
+ --accent-50: rgba(57, 255, 20, 0.5);
415
+
416
+ --error-10: rgba(255, 68, 68, 0.1);
417
+ --error-20: rgba(255, 68, 68, 0.2);
418
+ --error-30: rgba(255, 68, 68, 0.3);
419
+ --success-20: rgba(57, 255, 20, 0.2);
420
+
421
+ --user-bubble: rgba(0, 60, 0, 0.5);
422
+ --user-accent: #004d00;
423
+ --user-accent-hover: #003300;
424
+ --assistant-bubble: rgba(10, 22, 10, 0.95);
425
+
426
+ --success: #39ff14;
427
+ --warning: #fbbf24;
428
+ --info: #60a5fa;
429
+ --recording: #ff4444;
430
+ --error: #ff4444;
431
+ --neutral: #4a7a3a;
432
+
433
+ --star-color: transparent;
434
+
435
+ --syntax-keyword: #57ff3a;
436
+ --syntax-string: #a8ff80;
437
+ --syntax-comment: #3a5a2a;
438
+ --syntax-number: #39ff14;
439
+ }
440
+
441
+ /* Phosphor: hide stars, add grain overlay */
442
+ :root[data-theme="phosphor"] #stars {
443
+ display: none;
444
+ }
445
+
446
+ :root[data-theme="phosphor"] body::after {
447
+ content: '';
448
+ position: fixed;
449
+ inset: -50%;
450
+ width: 200%;
451
+ height: 200%;
452
+ pointer-events: none;
453
+ z-index: 1;
454
+ opacity: 0.075;
455
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='250' height='250'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='250' height='250' filter='url(%23n)'/%3E%3C/svg%3E");
456
+ background-size: 250px 250px;
457
+ animation: phosphor-grain 0.4s steps(1) infinite;
458
+ }
459
+
460
+ @keyframes phosphor-grain {
461
+ 0% { transform: translate(0, 0); }
462
+ 10% { transform: translate(-3%, -2%); }
463
+ 20% { transform: translate(2%, 4%); }
464
+ 30% { transform: translate(-4%, 1%); }
465
+ 40% { transform: translate(3%, -3%); }
466
+ 50% { transform: translate(-1%, 4%); }
467
+ 60% { transform: translate(4%, 2%); }
468
+ 70% { transform: translate(-2%, -4%); }
469
+ 80% { transform: translate(3%, 3%); }
470
+ 90% { transform: translate(-3%, 1%); }
471
+ 100% { transform: translate(1%, -2%); }
472
+ }
473
+
474
+ /* Phosphor: scanline effect on top of grain */
475
+ :root[data-theme="phosphor"] .app::before {
476
+ content: '';
477
+ position: fixed;
478
+ inset: 0;
479
+ pointer-events: none;
480
+ z-index: 1;
481
+ animation: phosphor-lines 0.15s linear infinite;
482
+ background: repeating-linear-gradient(
483
+ to bottom,
484
+ transparent,
485
+ transparent 3px,
486
+ rgba(0, 0, 0, 0.10) 3px,
487
+ rgba(0, 0, 0, 0.10) 4px
488
+ );
489
+ }
490
+
491
+ /* Phosphor: subtle green glow on panels */
492
+ :root[data-theme="phosphor"] .settings-panel,
493
+ :root[data-theme="phosphor"] .panel {
494
+ box-shadow: 0 0 30px rgba(57, 255, 20, 0.04);
495
+ }
496
+
497
+ /* Respect reduced motion for grain */
498
+ @keyframes phosphor-lines {
499
+ from { background-position: 0 0; }
500
+ to { background-position: 0 4px; }
501
+ }
502
+
503
+ @media (prefers-reduced-motion: reduce) {
504
+ :root[data-theme="phosphor"] body::after {
505
+ animation: none;
506
+ }
507
+ }
508
+
509
+ @media (prefers-reduced-motion: reduce) {
510
+ :root[data-theme="phosphor"] .app::before {
511
+ animation: none;
512
+ }
513
+ }
514
+
515
+
516
+ /* ============================================
517
+ DUSK
518
+ The moment between day and night —
519
+ deep indigo base, rose/coral accent, soft lavender stars
520
+ ============================================ */
521
+ :root[data-theme="dusk"] {
522
+ --bg: #0e0818;
523
+ --bg-secondary: rgba(18, 8, 28, 0.97);
524
+ --bg-input: rgba(30, 10, 35, 0.6);
525
+ --accent: #ff6b6b;
526
+ --accent-hover: #ff8e8e;
527
+ --accent-secondary: #e8435a;
528
+ --text: #fce8e2;
529
+ --text-muted: #c89090;
530
+ --text-dim: #7a5060;
531
+ --text-inverse: #0e0818;
532
+ --border: rgba(255, 107, 107, 0.15);
533
+
534
+ --accent-05: rgba(255, 107, 107, 0.05);
535
+ --accent-10: rgba(255, 107, 107, 0.1);
536
+ --accent-15: rgba(255, 107, 107, 0.15);
537
+ --accent-20: rgba(255, 107, 107, 0.2);
538
+ --accent-30: rgba(255, 107, 107, 0.3);
539
+ --accent-40: rgba(255, 107, 107, 0.4);
540
+ --accent-50: rgba(255, 107, 107, 0.5);
541
+
542
+ --error-10: rgba(239, 68, 68, 0.1);
543
+ --error-20: rgba(239, 68, 68, 0.2);
544
+ --error-30: rgba(239, 68, 68, 0.3);
545
+ --success-20: rgba(74, 222, 128, 0.15);
546
+
547
+ --user-bubble: rgba(255, 107, 107, 0.18);
548
+ --user-accent: #8a2040;
549
+ --user-accent-hover: #6a1030;
550
+ --assistant-bubble: rgb(18 8 30 / 95%);
551
+
552
+ --success: #4ade80;
553
+ --warning: #fbbf24;
554
+ --info: #60a5fa;
555
+ --recording: #ef4444;
556
+ --error: #ef4444;
557
+ --neutral: #7a5060;
558
+
559
+ --star-color: rgba(255, 180, 160, 0.8);
560
+
561
+ --syntax-keyword: #ff6b6b;
562
+ --syntax-string: #ffb3b3;
563
+ --syntax-comment: #5a3050;
564
+ --syntax-number: #e8435a;
565
+ }
566
+
567
+
568
+ /* ============================================
569
+ SLATE
570
+ Brutalist focus mode — cool blue-gray matte,
571
+ no stars, no particles, no distractions
572
+ ============================================ */
573
+ :root[data-theme="slate"] {
574
+ --bg: #0f1318;
575
+ --bg-secondary: rgba(16, 22, 32, 0.97);
576
+ --bg-input: rgba(20, 30, 44, 0.7);
577
+ --accent: #3b82f6;
578
+ --accent-hover: #60a5fa;
579
+ --accent-secondary: #2563eb;
580
+ --text: #e2e8f0;
581
+ --text-muted: #94a3b8;
582
+ --text-dim: #64748b;
583
+ --text-inverse: #0f1318;
584
+ --border: rgba(59, 130, 246, 0.15);
585
+
586
+ --accent-05: rgba(59, 130, 246, 0.05);
587
+ --accent-10: rgba(59, 130, 246, 0.1);
588
+ --accent-15: rgba(59, 130, 246, 0.15);
589
+ --accent-20: rgba(59, 130, 246, 0.2);
590
+ --accent-30: rgba(59, 130, 246, 0.3);
591
+ --accent-40: rgba(59, 130, 246, 0.4);
592
+ --accent-50: rgba(59, 130, 246, 0.5);
593
+
594
+ --error-10: rgba(239, 68, 68, 0.1);
595
+ --error-20: rgba(239, 68, 68, 0.2);
596
+ --error-30: rgba(239, 68, 68, 0.3);
597
+ --success-20: rgba(74, 222, 128, 0.15);
598
+
599
+ --user-bubble: rgba(30, 58, 120, 0.35);
600
+ --user-accent: #1e3a78;
601
+ --user-accent-hover: #152d60;
602
+ --assistant-bubble: rgb(33 43 58 / 95%);
603
+
604
+ --success: #4ade80;
605
+ --warning: #fbbf24;
606
+ --info: #60a5fa;
607
+ --recording: #ef4444;
608
+ --error: #ef4444;
609
+ --neutral: #64748b;
610
+
611
+ --star-color: transparent;
612
+
613
+ --syntax-keyword: #60a5fa;
614
+ --syntax-string: #86efac;
615
+ --syntax-comment: #475569;
616
+ --syntax-number: #93c5fd;
617
+ }
618
+
619
+ /* Slate: hide stars — pure matte surface */
620
+ :root[data-theme="slate"] #stars {
621
+ display: none;
622
+ }
623
+
624
+ /* Slate: kill all transitions — sharp and immediate */
625
+ :root[data-theme="slate"] * {
626
+ transition-duration: 0.08s !important;
627
+ }
628
+
629
+
630
+
631
+ /* ============================================
632
+ PER-THEME ASSISTANT BUBBLE TEXT COLORS
633
+ ============================================ */
634
+
635
+ :root[data-theme="forest"] .assistant-message .message-content {
636
+ color: #e6dfc6;
637
+ }
638
+
639
+ :root[data-theme="dusk"] .assistant-message .message-content {
640
+ color: #e8d9fb;
641
+ }
642
+
643
+ :root[data-theme="slate"] .assistant-message .message-content {
644
+ color: #dde6f3;
645
+ }
646
+
647
+
648
+
649
+
650
+ /* ============================================
651
+ NOIR — Film grain overlay
652
+ Classic black & white film stock texture
653
+ ============================================ */
654
+ :root[data-theme="noir"] body::after {
655
+ content: '';
656
+ position: fixed;
657
+ top: -20%;
658
+ left: -20%;
659
+ width: 140%;
660
+ height: 140%;
661
+ pointer-events: none;
662
+ z-index: 1;
663
+ opacity: 0.24;
664
+ background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n' x='0' y='0'><feTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/></filter><rect width='200' height='200' filter='url(%23n)' opacity='1'/></svg>");
665
+ background-size: 200px 200px;
666
+ animation: noir-grain-live 0.18s steps(1) infinite;
667
+ mix-blend-mode: screen;
668
+ }
669
+
670
+ @keyframes noir-grain-drift {
671
+ 0% { transform: translate(0, 0); }
672
+ 25% { transform: translate(-2%, 1.5%); }
673
+ 50% { transform: translate(1.5%, -2%); }
674
+ 75% { transform: translate(-1%, 2%); }
675
+ 100% { transform: translate(2%, -1%); }
676
+ }
677
+
678
+ @keyframes noir-grain-live {
679
+ 0% { transform: translate(0%, 0%); }
680
+ 10% { transform: translate(-0.8%, -0.5%); }
681
+ 20% { transform: translate(0.6%, 0.9%); }
682
+ 30% { transform: translate(-0.5%, 0.7%); }
683
+ 40% { transform: translate(0.9%, -0.6%); }
684
+ 50% { transform: translate(-0.7%, 0.8%); }
685
+ 60% { transform: translate(0.5%, -0.9%); }
686
+ 70% { transform: translate(-0.9%, 0.5%); }
687
+ 80% { transform: translate(0.7%, 0.6%); }
688
+ 90% { transform: translate(-0.6%, -0.8%); }
689
+ 100% { transform: translate(0.8%, 0.4%); }
690
+ }
691
+
692
+ @keyframes noir-grain-live {
693
+ 0% { transform: translate(0%, 0%); }
694
+ 10% { transform: translate(-0.8%, -0.5%); }
695
+ 20% { transform: translate(0.6%, 0.9%); }
696
+ 30% { transform: translate(-0.5%, 0.7%); }
697
+ 40% { transform: translate(0.9%, -0.6%); }
698
+ 50% { transform: translate(-0.7%, 0.8%); }
699
+ 60% { transform: translate(0.5%, -0.9%); }
700
+ 70% { transform: translate(-0.9%, 0.5%); }
701
+ 80% { transform: translate(0.7%, 0.6%); }
702
+ 90% { transform: translate(-0.6%, -0.8%); }
703
+ 100% { transform: translate(0.8%, 0.4%); }
704
+ }
705
+
706
+ @keyframes noir-grain {
707
+ 0% { transform: translate(0, 0); }
708
+ 10% { transform: translate(-4%, -2%); }
709
+ 20% { transform: translate(3%, 5%); }
710
+ 30% { transform: translate(-5%, 2%); }
711
+ 40% { transform: translate(4%, -4%); }
712
+ 50% { transform: translate(-2%, 5%); }
713
+ 60% { transform: translate(5%, 1%); }
714
+ 70% { transform: translate(-3%, -5%); }
715
+ 80% { transform: translate(4%, 4%); }
716
+ 90% { transform: translate(-4%, 2%); }
717
+ 100% { transform: translate(2%, -3%); }
718
+ }
719
+
720
+ @media (prefers-reduced-motion: reduce) {
721
+ :root[data-theme="noir"] body::after {
722
+ animation: none;
723
+ }
724
+ }
725
+
726
+
727
+
728
+
729
+
730
+
731
+
732
+
733
+ /* ============================================
734
+ SESSION SWITCHER BAR — match theme base bg, no effects
735
+ ============================================ */
736
+ @media (max-width: 1023px) {
737
+ .split-primary-header {
738
+ border-top: 1px solid var(--border) !important;
739
+ }
740
+ }
741
+
742
+ .split-primary-header,
743
+ .split-secondary-header {
744
+ background: transparent !important;
745
+ position: relative;
746
+ z-index: 2;
747
+ }
748
+
749
+
750
+
751
+ /* ============================================
752
+ NOIR — Vintage film effects
753
+ Stars hidden. Grain stays. Adding:
754
+ vignette + vertical scratches + dust specks
755
+ ============================================ */
756
+
757
+ /* Hide stars — grain is the texture */
758
+ :root[data-theme="noir"] #stars {
759
+ display: none;
760
+ }
761
+
762
+ /* Vignette — dark edges, classic film framing */
763
+ :root[data-theme="noir"] body::before {
764
+ content: '';
765
+ position: fixed;
766
+ inset: 0;
767
+ pointer-events: none;
768
+ z-index: 2;
769
+ background: radial-gradient(
770
+ ellipse at 50% 45%,
771
+ transparent 38%,
772
+ rgba(0, 0, 0, 0.45) 75%,
773
+ rgba(0, 0, 0, 0.75) 100%
774
+ );
775
+ }
776
+
777
+ 12% { opacity: 1; }
778
+ 13% { opacity: 0; }
779
+ 27% { opacity: 0; }
780
+ 28% { opacity: 1; background-position: 32% 0, 71% 0, 88% 0; }
781
+ 41% { opacity: 1; }
782
+ 42% { opacity: 0; }
783
+ 55% { opacity: 0; }
784
+ 56% { opacity: 1; background-position: 9% 0, 54% 0, 76% 0; }
785
+ 69% { opacity: 1; }
786
+ 70% { opacity: 0; }
787
+ 83% { opacity: 0; }
788
+ 84% { opacity: 1; background-position: 22% 0, 68% 0, 84% 0; }
789
+ 100% { opacity: 1; }
790
+ }
791
+
792
+ 100% { transform: translateY(-400px); }
793
+ }
794
+
795
+ @media (prefers-reduced-motion: reduce) {
796
+ :root[data-theme="noir"] body::before {
797
+ animation: none;
798
+ }
799
+ }
800
+
801
+
802
+
803
+ /* Noir — full grayscale + contrast, classic B&W film */
804
+ :root[data-theme="noir"] .app {
805
+ filter: grayscale(100%) contrast(1.05);
806
+ }
807
+
808
+
809
+
810
+
811
+
812
+
813
+
814
+
815
+ /* Noir — header sits above grain layer */
816
+ :root[data-theme="noir"] .header {
817
+ position: relative;
818
+ z-index: 2;
819
+ isolation: isolate;
820
+ background: var(--bg);
821
+ }
822
+
823
+ :root[data-theme="noir"] .bottom-dock {
824
+ position: relative;
825
+ z-index: 2;
826
+ background: var(--bg);
827
+ }
828
+
829
+
830
+
831
+
832
+
833
+
834
+
835
+ /* Phosphor — CRT sweep beam traveling down the screen */
836
+ :root[data-theme="phosphor"] .app::after {
837
+ content: '';
838
+ position: fixed;
839
+ left: 0;
840
+ right: 0;
841
+ top: -420px;
842
+ height: 420px;
843
+ pointer-events: none;
844
+ z-index: 3;
845
+ background: linear-gradient(
846
+ to bottom,
847
+ transparent,
848
+ rgba(57, 255, 20, 0.02) 20%,
849
+ rgba(57, 255, 20, 0.04) 50%,
850
+ rgba(57, 255, 20, 0.02) 80%,
851
+ transparent
852
+ );
853
+ animation: phosphor-sweep 6s linear infinite;
854
+ }
855
+
856
+ @keyframes phosphor-sweep {
857
+ from { transform: translateY(0); }
858
+ to { transform: translateY(calc(100vh + 420px)); }
859
+ }
860
+
861
+ @media (prefers-reduced-motion: reduce) {
862
+ :root[data-theme="phosphor"] .app::after {
863
+ animation: none;
864
+ }
865
+ }
866
+
867
+
868
+
869
+
870
+
871
+
872
+ /* Phosphor — RGB chromatic aberration split (real channel separation via SVG filters) */
873
+ :root[data-theme="phosphor"] .app {
874
+ animation:
875
+ phosphor-rgb-a 20s steps(1) infinite,
876
+ phosphor-rgb-b 27s steps(1) infinite;
877
+ }
878
+
879
+ @keyframes phosphor-rgb-a {
880
+ 0%, 94%, 96%, 100% { filter: none; transform: none; }
881
+ 95% { filter: url(#ca-lg); transform: skewX(-2deg) scaleX(1.015); }
882
+ }
883
+ 89% { filter: url(#ca-md); transform: skewX(-2deg) scaleX(1.015); }
884
+ 90% { filter: url(#ca-sm); transform: skewX(1.5deg) scaleX(0.99); }
885
+ 91% { filter: none; transform: none; }
886
+ }
887
+ 89% { filter: url(#ca-md); }
888
+ 90% { filter: url(#ca-sm); }
889
+ 91% { filter: none; }
890
+ }
891
+ 86% { filter: url(#ca-md); }
892
+ 87% { filter: url(#ca-sm); }
893
+ 88% { filter: none; }
894
+ 89% { filter: url(#ca-lg); }
895
+ 90% { filter: url(#ca-sm); }
896
+ 91% { filter: none; }
897
+ }
898
+
899
+ @keyframes phosphor-rgb-b {
900
+ 0%, 87%, 89%, 100% { filter: none; transform: none; }
901
+ 88% { filter: url(#ca-md); transform: skewX(2deg) scaleX(0.988); }
902
+ }
903
+ 83% { filter: url(#ca-lg); transform: skewX(2.5deg) scaleX(0.985); }
904
+ 84% { filter: url(#ca-md); transform: skewX(-1deg) scaleX(1.01); }
905
+ 85% { filter: none; transform: none; }
906
+ }
907
+ 83% { filter: url(#ca-lg); }
908
+ 84% { filter: url(#ca-md); }
909
+ 85% { filter: none; }
910
+ }
911
+ 79% { filter: url(#ca-lg); }
912
+ 80% { filter: url(#ca-sm); }
913
+ 81% { filter: none; }
914
+ 82% { filter: url(#ca-md); }
915
+ 83% { filter: url(#ca-lg); }
916
+ 84% { filter: none; }
917
+ }
918
+
919
+ @keyframes phosphor-distort-a {
920
+ 0%, 88%, 100% { transform: none; }
921
+ 89% { transform: skewX(-2deg) scaleX(1.015); }
922
+ 90% { transform: skewX(1.5deg) scaleX(0.99); }
923
+ 91% { transform: none; }
924
+ }
925
+
926
+ @keyframes phosphor-distort-b {
927
+ 0%, 82%, 100% { transform: none; }
928
+ 83% { transform: skewX(2.5deg) scaleX(0.985); }
929
+ 84% { transform: skewX(-1deg) scaleX(1.01); }
930
+ 85% { transform: none; }
931
+ }
932
+
933
+ @media (prefers-reduced-motion: reduce) {
934
+ :root[data-theme="phosphor"] .app {
935
+ animation: none;
936
+ }
937
+ }
938
+
939
+
940
+
941
+
942
+
943
+
944
+
945
+
946
+
947
+
948
+
949
+
950
+ /* ============================================
951
+ DUSK — City lights coming on, last warmth at the horizon
952
+ ============================================ */
953
+
954
+ /* Multi-zone sky gradient — navy top, plum mid, amber glow at horizon */
955
+ :root[data-theme="dusk"] #stars {
956
+ background: linear-gradient(
957
+ to bottom,
958
+ #08050f 0%,
959
+ #0e0818 15%,
960
+ #180830 35%,
961
+ #2a0830 55%,
962
+ #3a0828 72%,
963
+ #4a1020 88%,
964
+ #3a1218 100%
965
+ );
966
+ }
967
+
968
+ /* Amber horizon glow — warmth rising from below */
969
+ :root[data-theme="dusk"] #stars::after {
970
+ content: '';
971
+ position: absolute;
972
+ bottom: 0;
973
+ left: 0;
974
+ right: 0;
975
+ height: 35%;
976
+ background: linear-gradient(
977
+ to top,
978
+ rgba(200, 40, 80, 0.22) 0%,
979
+ rgba(160, 30, 60, 0.12) 40%,
980
+ transparent 100%
981
+ );
982
+ pointer-events: none;
983
+ }
984
+
985
+ /* City lights — warm white stars clustered toward lower half */
986
+ :root[data-theme="dusk"] .star {
987
+ background: rgba(255, 180, 160, 0.9) !important;
988
+ box-shadow: 0 0 4px 2px rgba(255, 100, 120, 0.25);
989
+ }
990
+
991
+ /* Fade out stars in upper 40% — sky is still dark up top, lights are below */
992
+ :root[data-theme="dusk"] .star {
993
+ opacity: calc(var(--opacity) * (1 - (var(--top, 50) / 100) * 0.6));
994
+ }
995
+
996
+ /* Sparse — only every 4th star visible */
997
+ :root[data-theme="dusk"] #stars .star:not(:nth-child(4n)) {
998
+ display: none;
999
+ }
1000
+
1001
+ /* Subtle warm vignette framing the scene */
1002
+ :root[data-theme="dusk"] body::before {
1003
+ content: '';
1004
+ position: fixed;
1005
+ inset: 0;
1006
+ pointer-events: none;
1007
+ z-index: 1;
1008
+ background: radial-gradient(
1009
+ ellipse at 50% 50%,
1010
+ transparent 40%,
1011
+ rgba(10, 8, 20, 0.4) 80%,
1012
+ rgba(8, 5, 15, 0.65) 100%
1013
+ );
1014
+ }
1015
+
1016
+ /* Assistant bubble text — warm off-white */
1017
+ :root[data-theme="dusk"] .assistant-message .message-content {
1018
+ color: #f2d8e0;
1019
+ }
1020
+
1021
+ @media (prefers-reduced-motion: reduce) {
1022
+ :root[data-theme="dusk"] .star {
1023
+ animation: none !important;
1024
+ opacity: 0.3;
1025
+ }
1026
+ }
1027
+
1028
+
1029
+
1030
+
1031
+
1032
+
1033
+
1034
+ /* Noir — links: bright white + underline since grayscale kills color */
1035
+ :root[data-theme="noir"] .app a {
1036
+ color: #ffffff !important;
1037
+ text-decoration: underline;
1038
+ text-decoration-color: rgba(255, 255, 255, 0.4);
1039
+ text-underline-offset: 3px;
1040
+ }
1041
+
1042
+ :root[data-theme="noir"] .app a:hover {
1043
+ text-decoration-color: rgba(255, 255, 255, 0.9);
1044
+ }
1045
+
1046
+ /* Noir — wordmark: force pure white since parent grayscale kills color */
1047
+ :root[data-theme="noir"] .header-wordmark {
1048
+ filter: brightness(0) invert(1);
1049
+ opacity: 0.9;
1050
+ }
1051
+
1052
+
1053
+
1054
+
1055
+