shell-mirror 1.5.129 → 1.5.131

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.129",
3
+ "version": "1.5.131",
4
4
  "description": "Access your Mac shell from any device securely. Perfect for mobile coding with Claude Code CLI, Gemini CLI, and any shell tool.",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -370,6 +370,151 @@
370
370
  white-space: nowrap;
371
371
  }
372
372
  }
373
+
374
+ /* ========================================
375
+ Floating Buttons - Mobile CLI Shortcuts
376
+ ======================================== */
377
+ .floating-buttons {
378
+ position: fixed;
379
+ bottom: 20px;
380
+ left: 50%;
381
+ transform: translateX(-50%);
382
+ z-index: 1000;
383
+ display: flex;
384
+ align-items: center;
385
+ gap: 0;
386
+ }
387
+
388
+ /* Toggle button (collapse/expand) */
389
+ .fab-toggle {
390
+ width: 40px;
391
+ height: 40px;
392
+ border-radius: 50%;
393
+ background: rgba(20, 21, 25, 0.95);
394
+ backdrop-filter: blur(10px);
395
+ -webkit-backdrop-filter: blur(10px);
396
+ border: 1px solid #2a2b30;
397
+ color: #fff;
398
+ font-size: 18px;
399
+ cursor: pointer;
400
+ flex-shrink: 0;
401
+ margin-right: 4px;
402
+ }
403
+
404
+ .fab-toggle:active {
405
+ background: #22232a;
406
+ }
407
+
408
+ /* Scroll indicators */
409
+ .fab-scroll {
410
+ width: 28px;
411
+ height: 48px;
412
+ background: rgba(20, 21, 25, 0.9);
413
+ border: none;
414
+ color: #8a8f98;
415
+ font-size: 22px;
416
+ font-weight: 300;
417
+ cursor: pointer;
418
+ flex-shrink: 0;
419
+ opacity: 0.8;
420
+ transition: opacity 0.15s;
421
+ display: flex;
422
+ align-items: center;
423
+ justify-content: center;
424
+ }
425
+
426
+ .fab-scroll:active { opacity: 1; background: #22232a; }
427
+ .fab-scroll.hidden { opacity: 0; pointer-events: none; width: 0; }
428
+
429
+ .fab-scroll-left {
430
+ border-radius: 10px 0 0 10px;
431
+ border: 1px solid #2a2b30;
432
+ border-right: none;
433
+ }
434
+ .fab-scroll-right {
435
+ border-radius: 0 10px 10px 0;
436
+ border: 1px solid #2a2b30;
437
+ border-left: none;
438
+ }
439
+
440
+ /* Scrollable button strip */
441
+ .fab-strip {
442
+ display: flex;
443
+ gap: 6px;
444
+ overflow-x: auto;
445
+ scroll-snap-type: x mandatory;
446
+ scroll-behavior: smooth;
447
+ -webkit-overflow-scrolling: touch;
448
+ padding: 6px 10px;
449
+ background: rgba(20, 21, 25, 0.95);
450
+ backdrop-filter: blur(10px);
451
+ -webkit-backdrop-filter: blur(10px);
452
+ border-top: 1px solid #2a2b30;
453
+ border-bottom: 1px solid #2a2b30;
454
+ max-width: calc(100vw - 130px);
455
+ scrollbar-width: none;
456
+ transition: max-width 0.2s, opacity 0.2s, padding 0.2s;
457
+ }
458
+
459
+ .fab-strip::-webkit-scrollbar { display: none; }
460
+
461
+ /* Collapsed state */
462
+ .floating-buttons.collapsed .fab-strip,
463
+ .floating-buttons.collapsed .fab-scroll {
464
+ max-width: 0;
465
+ width: 0;
466
+ padding: 0;
467
+ opacity: 0;
468
+ overflow: hidden;
469
+ border: none;
470
+ }
471
+
472
+ /* Individual buttons */
473
+ .fab-btn {
474
+ flex-shrink: 0;
475
+ min-width: 48px;
476
+ height: 44px;
477
+ padding: 0 12px;
478
+ border-radius: 8px;
479
+ background: #1a1b20;
480
+ color: #fff;
481
+ border: 1px solid #2a2b30;
482
+ font-size: 13px;
483
+ font-weight: 600;
484
+ scroll-snap-align: center;
485
+ white-space: nowrap;
486
+ cursor: pointer;
487
+ transition: background 0.1s;
488
+ }
489
+
490
+ .fab-btn:active { background: #22232a; }
491
+
492
+ /* Highlight most critical button (Mode switcher) */
493
+ .fab-btn.fab-primary {
494
+ background: #5d5fef;
495
+ border-color: #5d5fef;
496
+ }
497
+
498
+ .fab-btn.fab-primary:active {
499
+ background: #4a4cd6;
500
+ }
501
+
502
+ /* Desktop: hide floating buttons */
503
+ @media (min-width: 1025px) {
504
+ .floating-buttons { display: none; }
505
+ }
506
+
507
+ /* Small phones: adjust sizing */
508
+ @media (max-width: 375px) {
509
+ .fab-btn {
510
+ min-width: 44px;
511
+ padding: 0 10px;
512
+ font-size: 12px;
513
+ }
514
+ .fab-strip {
515
+ max-width: calc(100vw - 110px);
516
+ }
517
+ }
373
518
  </style>
374
519
  </head>
375
520
  <body>
@@ -400,7 +545,44 @@
400
545
  </div>
401
546
  <div id="terminal"></div>
402
547
  </div>
403
-
548
+
549
+ <!-- Floating Buttons - Mobile CLI Shortcuts -->
550
+ <div id="floating-buttons" class="floating-buttons">
551
+ <!-- Toggle button (collapse/expand) -->
552
+ <button class="fab-toggle" id="fabToggle" aria-label="Toggle keyboard shortcuts">
553
+ <span class="fab-toggle-icon">⌨</span>
554
+ </button>
555
+
556
+ <!-- Left scroll indicator -->
557
+ <button class="fab-scroll fab-scroll-left hidden" id="fabScrollLeft" aria-label="Scroll left">
558
+ <span>‹</span>
559
+ </button>
560
+
561
+ <!-- Scrollable button strip -->
562
+ <div class="fab-strip" id="fabStrip">
563
+ <!-- Control group (scroll left to reveal) -->
564
+ <button class="fab-btn" data-keys="ctrl+l" title="Clear screen">^L</button>
565
+ <button class="fab-btn" data-keys="ctrl+d" title="EOF/Exit">^D</button>
566
+ <button class="fab-btn" data-keys="ctrl+c" title="Interrupt">^C</button>
567
+
568
+ <!-- Primary group (default visible - AI CLI essentials) -->
569
+ <button class="fab-btn fab-primary" data-keys="shift+tab" title="Mode: Normal → Auto → Plan">⇧Tab</button>
570
+ <button class="fab-btn" data-keys="tab" title="Autocomplete / Thinking">Tab</button>
571
+ <button class="fab-btn" data-keys="up" data-repeat="true" title="History up">↑</button>
572
+ <button class="fab-btn" data-keys="down" data-repeat="true" title="History down">↓</button>
573
+ <button class="fab-btn" data-keys="escape" title="Cancel / Rewind (2x)">Esc</button>
574
+
575
+ <!-- Navigation group (scroll right to reveal) -->
576
+ <button class="fab-btn" data-keys="left" title="Cursor left">←</button>
577
+ <button class="fab-btn" data-keys="right" title="Cursor right">→</button>
578
+ </div>
579
+
580
+ <!-- Right scroll indicator -->
581
+ <button class="fab-scroll fab-scroll-right" id="fabScrollRight" aria-label="Scroll right">
582
+ <span>›</span>
583
+ </button>
584
+ </div>
585
+
404
586
  <!-- Help Modal (Dark Kraken Style) -->
405
587
  <div id="help-modal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.85); align-items: center; justify-content: center; z-index: 20000;">
406
588
  <div style="background: #141519; border-radius: 8px; max-width: 500px; width: 90%; max-height: 80vh; overflow: hidden; border: 1px solid #2a2b30; box-shadow: 0 8px 32px rgba(0,0,0,0.5);">
@@ -1388,19 +1388,19 @@ function saveSessionToLocalStorage(agentId, sessionInfo) {
1388
1388
  console.log('[CLIENT] 🔍 DEBUG: Saving session to localStorage');
1389
1389
  console.log('[CLIENT] 🔍 DEBUG: AgentID:', agentId);
1390
1390
  console.log('[CLIENT] 🔍 DEBUG: SessionInfo:', sessionInfo);
1391
-
1391
+
1392
1392
  const storedSessions = localStorage.getItem('shell-mirror-sessions');
1393
1393
  console.log('[CLIENT] 🔍 DEBUG: Current stored sessions:', storedSessions);
1394
-
1394
+
1395
1395
  let sessionData = storedSessions ? JSON.parse(storedSessions) : {};
1396
-
1396
+
1397
1397
  if (!sessionData[agentId]) {
1398
1398
  sessionData[agentId] = [];
1399
1399
  }
1400
-
1400
+
1401
1401
  // Remove existing session with same ID
1402
1402
  sessionData[agentId] = sessionData[agentId].filter(s => s.id !== sessionInfo.id);
1403
-
1403
+
1404
1404
  // Add updated session info
1405
1405
  const sessionToStore = {
1406
1406
  id: sessionInfo.id,
@@ -1409,9 +1409,9 @@ function saveSessionToLocalStorage(agentId, sessionInfo) {
1409
1409
  createdAt: sessionInfo.createdAt || Date.now(),
1410
1410
  status: 'active'
1411
1411
  };
1412
-
1412
+
1413
1413
  sessionData[agentId].push(sessionToStore);
1414
-
1414
+
1415
1415
  localStorage.setItem('shell-mirror-sessions', JSON.stringify(sessionData));
1416
1416
  console.log('[CLIENT] 💾 Session saved to storage:', sessionToStore);
1417
1417
 
@@ -1431,4 +1431,163 @@ function saveSessionToLocalStorage(agentId, sessionInfo) {
1431
1431
  } catch (error) {
1432
1432
  console.error('[CLIENT] ❌ Error saving session to storage:', error);
1433
1433
  }
1434
- }
1434
+ }
1435
+
1436
+ // ========================================
1437
+ // Floating Buttons - Mobile CLI Shortcuts
1438
+ // ========================================
1439
+
1440
+ const fabKeyMap = {
1441
+ 'shift+tab': '\x1b[Z', // Shift+Tab - MODE SWITCHING (most critical!)
1442
+ 'tab': '\t', // Tab - autocomplete + thinking toggle
1443
+ 'escape': '\x1b', // Esc - cancel (double = rewind)
1444
+ 'up': '\x1b[A', // ↑ - history up
1445
+ 'down': '\x1b[B', // ↓ - history down
1446
+ 'left': '\x1b[D', // ← - cursor left
1447
+ 'right': '\x1b[C', // → - cursor right
1448
+ 'ctrl+c': '\x03', // ^C - interrupt
1449
+ 'ctrl+d': '\x04', // ^D - EOF/exit
1450
+ 'ctrl+l': '\x0c', // ^L - clear screen
1451
+ };
1452
+
1453
+ function initFloatingButtons() {
1454
+ const container = document.getElementById('floating-buttons');
1455
+ const toggle = document.getElementById('fabToggle');
1456
+ const strip = document.getElementById('fabStrip');
1457
+ const scrollLeft = document.getElementById('fabScrollLeft');
1458
+ const scrollRight = document.getElementById('fabScrollRight');
1459
+
1460
+ if (!container || !toggle || !strip) {
1461
+ console.log('[CLIENT] ⌨️ Floating buttons not found in DOM');
1462
+ return;
1463
+ }
1464
+
1465
+ console.log('[CLIENT] ⌨️ Initializing floating buttons for mobile CLI shortcuts');
1466
+
1467
+ // Toggle collapse/expand
1468
+ toggle.addEventListener('click', () => {
1469
+ container.classList.toggle('collapsed');
1470
+ localStorage.setItem('fab-collapsed', container.classList.contains('collapsed'));
1471
+ });
1472
+
1473
+ // Restore saved collapsed state
1474
+ if (localStorage.getItem('fab-collapsed') === 'true') {
1475
+ container.classList.add('collapsed');
1476
+ }
1477
+
1478
+ // Scroll indicator click handlers
1479
+ const scrollAmount = 150; // pixels per click
1480
+
1481
+ if (scrollLeft) {
1482
+ scrollLeft.addEventListener('click', () => {
1483
+ strip.scrollBy({ left: -scrollAmount, behavior: 'smooth' });
1484
+ });
1485
+ }
1486
+
1487
+ if (scrollRight) {
1488
+ scrollRight.addEventListener('click', () => {
1489
+ strip.scrollBy({ left: scrollAmount, behavior: 'smooth' });
1490
+ });
1491
+ }
1492
+
1493
+ // Update scroll indicator visibility based on scroll position
1494
+ function updateScrollIndicators() {
1495
+ if (!scrollLeft || !scrollRight) return;
1496
+
1497
+ const atStart = strip.scrollLeft <= 5;
1498
+ const atEnd = strip.scrollLeft >= strip.scrollWidth - strip.clientWidth - 5;
1499
+
1500
+ scrollLeft.classList.toggle('hidden', atStart);
1501
+ scrollRight.classList.toggle('hidden', atEnd);
1502
+ }
1503
+
1504
+ strip.addEventListener('scroll', updateScrollIndicators);
1505
+
1506
+ // Button click handlers - send key sequences to terminal
1507
+ document.querySelectorAll('.fab-btn').forEach(btn => {
1508
+ btn.addEventListener('click', (e) => {
1509
+ e.preventDefault();
1510
+ const keys = btn.dataset.keys;
1511
+ if (keys && fabKeyMap[keys]) {
1512
+ // Send the key sequence through the terminal input handler
1513
+ sendFabKey(fabKeyMap[keys]);
1514
+ term.focus();
1515
+ }
1516
+ });
1517
+ });
1518
+
1519
+ // Long-press repeat for arrow keys
1520
+ initFabLongPress();
1521
+
1522
+ // Scroll to show primary buttons (⇧Tab visible on left)
1523
+ setTimeout(() => {
1524
+ const modeBtn = strip.querySelector('[data-keys="shift+tab"]');
1525
+ if (modeBtn) {
1526
+ strip.scrollLeft = modeBtn.offsetLeft - 10;
1527
+ }
1528
+ updateScrollIndicators();
1529
+ }, 100);
1530
+
1531
+ console.log('[CLIENT] ⌨️ Floating buttons initialized');
1532
+ }
1533
+
1534
+ // Send key sequence through the appropriate connection
1535
+ function sendFabKey(keySequence) {
1536
+ // Use the same logic as term.onData - send through WebRTC or direct WS
1537
+ if (dataChannel && dataChannel.readyState === 'open') {
1538
+ dataChannel.send(JSON.stringify({ type: 'input', data: keySequence }));
1539
+ } else if (ws && ws.readyState === WebSocket.OPEN && currentSession) {
1540
+ ws.send(JSON.stringify({
1541
+ type: 'input',
1542
+ sessionId: currentSession.id,
1543
+ data: keySequence
1544
+ }));
1545
+ }
1546
+ }
1547
+
1548
+ // Long-press support for arrow buttons (↑/↓)
1549
+ function initFabLongPress() {
1550
+ document.querySelectorAll('.fab-btn[data-repeat="true"]').forEach(btn => {
1551
+ let interval = null;
1552
+ let timeout = null;
1553
+
1554
+ const startRepeat = () => {
1555
+ const keys = btn.dataset.keys;
1556
+ if (!keys || !fabKeyMap[keys]) return;
1557
+
1558
+ // First key immediately
1559
+ sendFabKey(fabKeyMap[keys]);
1560
+ term.focus();
1561
+
1562
+ // Then repeat after delay
1563
+ timeout = setTimeout(() => {
1564
+ interval = setInterval(() => {
1565
+ sendFabKey(fabKeyMap[keys]);
1566
+ }, 100); // Repeat every 100ms
1567
+ }, 300); // Start repeating after 300ms hold
1568
+ };
1569
+
1570
+ const stopRepeat = () => {
1571
+ if (timeout) clearTimeout(timeout);
1572
+ if (interval) clearInterval(interval);
1573
+ timeout = null;
1574
+ interval = null;
1575
+ };
1576
+
1577
+ // Touch events for mobile
1578
+ btn.addEventListener('touchstart', (e) => {
1579
+ e.preventDefault();
1580
+ startRepeat();
1581
+ });
1582
+ btn.addEventListener('touchend', stopRepeat);
1583
+ btn.addEventListener('touchcancel', stopRepeat);
1584
+
1585
+ // Mouse events for testing on desktop
1586
+ btn.addEventListener('mousedown', startRepeat);
1587
+ btn.addEventListener('mouseup', stopRepeat);
1588
+ btn.addEventListener('mouseleave', stopRepeat);
1589
+ });
1590
+ }
1591
+
1592
+ // Initialize floating buttons when DOM is ready
1593
+ document.addEventListener('DOMContentLoaded', initFloatingButtons);
Binary file
Binary file
package/public/index.html CHANGED
@@ -53,6 +53,20 @@
53
53
  </script>
54
54
 
55
55
  <style>
56
+ :root {
57
+ --bg-primary: #0a0b0d;
58
+ --bg-secondary: #141519;
59
+ --bg-tertiary: #1a1b20;
60
+ --bg-hover: #22232a;
61
+ --text-primary: #ffffff;
62
+ --text-secondary: #8a8f98;
63
+ --text-muted: #5a5f6a;
64
+ --accent: #5d5fef;
65
+ --accent-hover: #4a4cd6;
66
+ --success: #00c853;
67
+ --border: #2a2b30;
68
+ }
69
+
56
70
  * {
57
71
  margin: 0;
58
72
  padding: 0;
@@ -62,8 +76,8 @@
62
76
  body {
63
77
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
64
78
  line-height: 1.6;
65
- color: #333;
66
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
79
+ color: var(--text-primary);
80
+ background: var(--bg-primary);
67
81
  min-height: 100vh;
68
82
  }
69
83
 
@@ -75,9 +89,11 @@
75
89
 
76
90
  /* Header */
77
91
  header {
78
- padding: 20px 0;
92
+ padding: 16px 0;
79
93
  position: relative;
80
94
  z-index: 100;
95
+ background: var(--bg-secondary);
96
+ border-bottom: 1px solid var(--border);
81
97
  }
82
98
 
83
99
  nav {
@@ -87,46 +103,44 @@
87
103
  }
88
104
 
89
105
  .logo {
90
- font-size: 24px;
91
- font-weight: bold;
92
- color: white;
106
+ font-size: 1.2rem;
107
+ font-weight: 600;
108
+ color: var(--text-primary);
93
109
  }
94
110
 
95
111
  .cta-button {
96
- background: rgba(255, 255, 255, 0.2);
112
+ background: var(--accent);
97
113
  color: white;
98
- padding: 12px 24px;
114
+ padding: 10px 20px;
99
115
  text-decoration: none;
100
- border-radius: 25px;
116
+ border-radius: 6px;
101
117
  font-weight: 500;
102
- backdrop-filter: blur(10px);
103
- border: 1px solid rgba(255, 255, 255, 0.3);
104
- transition: all 0.3s ease;
118
+ border: none;
119
+ transition: all 0.15s ease;
105
120
  }
106
121
 
107
122
  .cta-button:hover {
108
- background: rgba(255, 255, 255, 0.3);
109
- transform: translateY(-2px);
123
+ background: var(--accent-hover);
110
124
  }
111
125
 
112
126
  /* Hero Section */
113
127
  .hero {
114
128
  text-align: center;
115
129
  padding: 60px 0 80px;
116
- color: white;
130
+ color: var(--text-primary);
117
131
  }
118
132
 
119
133
  .hero h1 {
120
- font-size: clamp(2.5rem, 5vw, 4rem);
121
- font-weight: 700;
122
- margin-bottom: 20px;
134
+ font-size: clamp(2rem, 4vw, 3rem);
135
+ font-weight: 600;
136
+ margin-bottom: 16px;
123
137
  line-height: 1.2;
124
138
  }
125
139
 
126
140
  .hero p {
127
- font-size: clamp(1.1rem, 2vw, 1.3rem);
141
+ font-size: clamp(1rem, 1.5vw, 1.1rem);
128
142
  margin-bottom: 40px;
129
- opacity: 0.9;
143
+ color: var(--text-secondary);
130
144
  max-width: 600px;
131
145
  margin-left: auto;
132
146
  margin-right: auto;
@@ -134,45 +148,43 @@
134
148
 
135
149
  .hero-buttons {
136
150
  display: flex;
137
- gap: 20px;
151
+ gap: 16px;
138
152
  justify-content: center;
139
153
  flex-wrap: wrap;
140
154
  margin-bottom: 60px;
141
155
  }
142
156
 
143
157
  .btn-primary {
144
- background: #4285F4;
158
+ background: var(--accent);
145
159
  color: white;
146
- padding: 16px 32px;
160
+ padding: 14px 28px;
147
161
  text-decoration: none;
148
- border-radius: 30px;
149
- font-weight: 600;
150
- font-size: 1.1rem;
151
- transition: all 0.3s ease;
152
- box-shadow: 0 4px 15px rgba(66, 133, 244, 0.3);
162
+ border-radius: 6px;
163
+ font-weight: 500;
164
+ font-size: 1rem;
165
+ transition: all 0.15s ease;
166
+ border: none;
153
167
  }
154
168
 
155
169
  .btn-primary:hover {
156
- background: #3367d6;
157
- transform: translateY(-2px);
158
- box-shadow: 0 6px 20px rgba(66, 133, 244, 0.4);
170
+ background: var(--accent-hover);
159
171
  }
160
172
 
161
173
  .btn-secondary {
162
174
  background: transparent;
163
- color: white;
164
- padding: 16px 32px;
175
+ color: var(--text-primary);
176
+ padding: 14px 28px;
165
177
  text-decoration: none;
166
- border-radius: 30px;
167
- font-weight: 600;
168
- font-size: 1.1rem;
169
- border: 2px solid white;
170
- transition: all 0.3s ease;
178
+ border-radius: 6px;
179
+ font-weight: 500;
180
+ font-size: 1rem;
181
+ border: 1px solid var(--border);
182
+ transition: all 0.15s ease;
171
183
  }
172
184
 
173
185
  .btn-secondary:hover {
174
- background: white;
175
- color: #667eea;
186
+ background: var(--bg-hover);
187
+ border-color: var(--text-muted);
176
188
  }
177
189
 
178
190
  /* Setup Steps */
@@ -181,42 +193,45 @@
181
193
  max-width: 600px;
182
194
  display: flex;
183
195
  flex-direction: column;
184
- gap: 20px;
196
+ gap: 16px;
185
197
  }
186
198
 
187
199
  .setup-step {
188
200
  display: flex;
189
201
  align-items: center;
190
- gap: 20px;
202
+ gap: 16px;
191
203
  }
192
204
 
193
205
  .step-number {
194
- font-size: 2rem;
195
- font-weight: bold;
196
- color: white;
197
- opacity: 0.8;
198
- min-width: 40px;
199
- text-align: center;
206
+ font-size: 1rem;
207
+ font-weight: 600;
208
+ color: var(--text-muted);
209
+ min-width: 32px;
210
+ height: 32px;
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: center;
214
+ background: var(--bg-tertiary);
215
+ border-radius: 50%;
200
216
  }
201
217
 
202
218
  /* Installation Code */
203
219
  .install-code {
204
- background: rgba(0, 0, 0, 0.3);
205
- padding: 20px;
206
- border-radius: 15px;
207
- backdrop-filter: blur(10px);
208
- border: 1px solid rgba(255, 255, 255, 0.2);
220
+ background: var(--bg-tertiary);
221
+ padding: 16px 20px;
222
+ border-radius: 6px;
223
+ border: 1px solid var(--border);
209
224
  position: relative;
210
225
  flex: 1;
211
- height: 60px;
226
+ height: 56px;
212
227
  display: flex;
213
228
  align-items: center;
214
229
  }
215
230
 
216
231
  .install-code pre {
217
- color: #00ff88;
232
+ color: var(--success);
218
233
  font-family: 'Monaco', 'Menlo', monospace;
219
- font-size: 1.1rem;
234
+ font-size: 0.95rem;
220
235
  margin: 0;
221
236
  padding-right: 40px;
222
237
  flex: 1;
@@ -224,21 +239,23 @@
224
239
 
225
240
  .copy-btn {
226
241
  position: absolute;
227
- right: 15px;
242
+ right: 12px;
228
243
  top: 50%;
229
244
  transform: translateY(-50%);
230
- background: rgba(255, 255, 255, 0.1);
231
- border: 1px solid rgba(255, 255, 255, 0.2);
232
- border-radius: 8px;
233
- padding: 8px;
234
- color: white;
245
+ background: var(--bg-hover);
246
+ border: 1px solid var(--border);
247
+ border-radius: 4px;
248
+ padding: 6px 10px;
249
+ color: var(--text-secondary);
235
250
  cursor: pointer;
236
- transition: all 0.2s ease;
251
+ transition: all 0.15s ease;
252
+ font-size: 0.75rem;
237
253
  }
238
254
 
239
255
  .copy-btn:hover {
240
- background: rgba(255, 255, 255, 0.2);
241
- transform: translateY(-50%) scale(1.05);
256
+ background: var(--accent);
257
+ border-color: var(--accent);
258
+ color: white;
242
259
  }
243
260
 
244
261
  .copy-btn:active {
@@ -253,31 +270,27 @@
253
270
  .btn-google {
254
271
  background: white;
255
272
  color: #3c4043;
256
- padding: 20px 24px;
257
- border: 1px solid #dadce0;
258
- border-radius: 15px;
273
+ padding: 16px 20px;
274
+ border: none;
275
+ border-radius: 6px;
259
276
  font-weight: 500;
260
- font-size: 1rem;
277
+ font-size: 0.95rem;
261
278
  display: flex;
262
279
  align-items: center;
263
280
  justify-content: center;
264
- gap: 12px;
265
- transition: all 0.2s ease;
281
+ gap: 10px;
282
+ transition: all 0.15s ease;
266
283
  cursor: pointer;
267
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
268
284
  flex: 1;
269
- height: 60px;
285
+ height: 56px;
270
286
  }
271
287
 
272
288
  .btn-google:hover {
273
- background: #f8f9fa;
274
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
275
- /* transform: translateY(-1px); */
289
+ background: #f0f0f0;
276
290
  }
277
291
 
278
292
  .btn-google:active {
279
- transform: translateY(0);
280
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
293
+ transform: scale(0.98);
281
294
  }
282
295
 
283
296
  /* Installation Modal */
@@ -299,9 +312,10 @@
299
312
  }
300
313
 
301
314
  .install-modal-content {
302
- background: white;
303
- padding: 30px;
304
- border-radius: 15px;
315
+ background: var(--bg-secondary);
316
+ padding: 24px;
317
+ border-radius: 8px;
318
+ border: 1px solid var(--border);
305
319
  max-width: 500px;
306
320
  margin: 20px;
307
321
  text-align: center;
@@ -309,23 +323,22 @@
309
323
  }
310
324
 
311
325
  .install-modal-content h3 {
312
- color: #333;
313
- margin-bottom: 20px;
326
+ color: var(--text-primary);
327
+ margin-bottom: 16px;
314
328
  }
315
329
 
316
330
  .install-modal-content p {
317
- color: #666;
318
- margin-bottom: 20px;
331
+ color: var(--text-secondary);
332
+ margin-bottom: 16px;
319
333
  }
320
334
 
321
335
  .install-modal-content .install-code {
322
- background: #f5f5f5;
323
- color: #333;
324
- margin: 20px 0;
336
+ background: var(--bg-tertiary);
337
+ margin: 16px 0;
325
338
  }
326
339
 
327
340
  .install-modal-content .install-code pre {
328
- color: #333;
341
+ color: var(--success);
329
342
  }
330
343
 
331
344
  .close-modal {
@@ -336,65 +349,70 @@
336
349
  border: none;
337
350
  font-size: 24px;
338
351
  cursor: pointer;
339
- color: #666;
352
+ color: var(--text-muted);
340
353
  }
341
354
 
342
355
  .close-modal:hover {
343
- color: #000;
356
+ color: var(--text-primary);
344
357
  }
345
358
 
346
359
  /* How It Works */
347
360
  .how-it-works {
348
- background: white;
361
+ background: var(--bg-secondary);
349
362
  padding: 80px 0;
350
363
  }
351
364
 
352
365
  .section-title {
353
366
  text-align: center;
354
- font-size: 2.5rem;
355
- margin-bottom: 60px;
356
- color: #333;
367
+ font-size: 1.75rem;
368
+ font-weight: 600;
369
+ margin-bottom: 48px;
370
+ color: var(--text-primary);
357
371
  }
358
372
 
359
373
  .steps {
360
374
  display: grid;
361
375
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
362
- gap: 40px;
363
- margin-bottom: 60px;
376
+ gap: 32px;
377
+ margin-bottom: 48px;
364
378
  }
365
379
 
366
380
  .step {
367
381
  text-align: center;
368
- padding: 30px 20px;
382
+ padding: 24px 16px;
369
383
  }
370
384
 
371
385
  .step-icon {
372
- width: 80px;
373
- height: 80px;
374
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
386
+ width: 56px;
387
+ height: 56px;
388
+ background: var(--bg-tertiary);
389
+ border: 1px solid var(--border);
375
390
  border-radius: 50%;
376
391
  display: flex;
377
392
  align-items: center;
378
393
  justify-content: center;
379
- margin: 0 auto 20px;
380
- font-size: 2rem;
381
- color: white;
394
+ margin: 0 auto 16px;
395
+ font-size: 1.25rem;
396
+ font-weight: 600;
397
+ color: var(--text-secondary);
382
398
  }
383
399
 
384
400
  .step h3 {
385
- font-size: 1.3rem;
386
- margin-bottom: 15px;
387
- color: #333;
401
+ font-size: 1.1rem;
402
+ font-weight: 600;
403
+ margin-bottom: 12px;
404
+ color: var(--text-primary);
388
405
  }
389
406
 
390
407
  .step p {
391
- color: #666;
408
+ color: var(--text-secondary);
392
409
  line-height: 1.6;
410
+ font-size: 0.95rem;
393
411
  }
394
412
 
395
413
  /* Security Section */
396
414
  .security {
397
- background: #f8f9fa;
415
+ background: var(--bg-tertiary);
398
416
  padding: 80px 0;
399
417
  }
400
418
 
@@ -402,102 +420,118 @@
402
420
  display: flex;
403
421
  align-items: center;
404
422
  justify-content: center;
405
- gap: 30px;
423
+ gap: 24px;
406
424
  flex-wrap: wrap;
407
425
  margin: 40px 0;
408
426
  }
409
427
 
410
428
  .security-step {
411
- background: white;
429
+ background: var(--bg-secondary);
412
430
  padding: 20px;
413
- border-radius: 15px;
414
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
431
+ border-radius: 6px;
432
+ border: 1px solid var(--border);
415
433
  text-align: center;
416
- min-width: 200px;
434
+ min-width: 180px;
417
435
  }
418
436
 
419
437
  .security-step h4 {
420
- color: #4285F4;
421
- margin-bottom: 10px;
438
+ color: var(--accent);
439
+ margin-bottom: 8px;
440
+ font-size: 0.95rem;
441
+ }
442
+
443
+ .security-step p {
444
+ color: var(--text-secondary);
445
+ font-size: 0.85rem;
422
446
  }
423
447
 
424
448
  .arrow {
425
- font-size: 2rem;
426
- color: #4285F4;
449
+ font-size: 1.5rem;
450
+ color: var(--text-muted);
427
451
  }
428
452
 
429
453
  /* Use Cases */
430
454
  .use-cases {
431
455
  padding: 80px 0;
432
- background: white;
456
+ background: var(--bg-secondary);
433
457
  }
434
458
 
435
459
  .use-case-grid {
436
460
  display: grid;
437
461
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
438
- gap: 30px;
462
+ gap: 24px;
439
463
  }
440
464
 
441
465
  .use-case {
442
- background: #f8f9fa;
443
- padding: 30px;
444
- border-radius: 15px;
445
- border-left: 4px solid #4285F4;
466
+ background: var(--bg-tertiary);
467
+ padding: 24px;
468
+ border-radius: 6px;
469
+ border-left: 3px solid var(--accent);
446
470
  }
447
471
 
448
472
  .use-case h3 {
449
- color: #333;
450
- margin-bottom: 15px;
473
+ color: var(--text-primary);
474
+ margin-bottom: 12px;
475
+ font-size: 1rem;
476
+ font-weight: 600;
451
477
  }
452
478
 
453
479
  .use-case p {
454
- color: #666;
480
+ color: var(--text-secondary);
481
+ font-size: 0.95rem;
455
482
  }
456
483
 
457
484
  /* Footer CTA */
458
485
  .footer-cta {
459
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
486
+ background: var(--bg-primary);
487
+ border-top: 1px solid var(--border);
460
488
  padding: 60px 0;
461
489
  text-align: center;
462
- color: white;
490
+ color: var(--text-primary);
463
491
  }
464
492
 
465
493
  .footer-cta h2 {
466
- font-size: 2.5rem;
467
- margin-bottom: 20px;
494
+ font-size: 1.75rem;
495
+ font-weight: 600;
496
+ margin-bottom: 16px;
468
497
  }
469
498
 
470
499
  .footer-cta p {
471
- font-size: 1.2rem;
472
- margin-bottom: 40px;
473
- opacity: 0.9;
500
+ font-size: 1rem;
501
+ margin-bottom: 32px;
502
+ color: var(--text-secondary);
474
503
  }
475
504
 
476
505
  /* Responsive Design */
477
506
  @media (max-width: 768px) {
478
507
  .setup-steps {
479
- max-width: 90%;
480
- gap: 15px;
508
+ max-width: 95%;
509
+ gap: 12px;
481
510
  }
482
511
 
483
512
  .setup-step {
484
- gap: 15px;
513
+ gap: 12px;
485
514
  }
486
515
 
487
516
  .step-number {
488
- font-size: 1.5rem;
489
- min-width: 30px;
517
+ min-width: 28px;
518
+ height: 28px;
519
+ font-size: 0.85rem;
490
520
  }
491
521
 
492
522
  .install-code {
493
- height: 50px;
494
- padding: 15px;
523
+ height: 48px;
524
+ padding: 12px 16px;
525
+ }
526
+
527
+ .install-code pre {
528
+ font-size: 0.85rem;
495
529
  }
496
530
 
497
531
  .btn-google {
498
- height: 50px;
499
- padding: 15px 20px;
500
- font-size: 0.9rem;
532
+ height: 48px;
533
+ padding: 12px 16px;
534
+ font-size: 0.85rem;
501
535
  }
502
536
 
503
537
  .hero-buttons {
@@ -506,7 +540,7 @@
506
540
  }
507
541
 
508
542
  .btn-primary {
509
- width: 250px;
543
+ width: 240px;
510
544
  }
511
545
 
512
546
  .security-flow {
@@ -520,22 +554,24 @@
520
554
  .steps {
521
555
  grid-template-columns: 1fr;
522
556
  }
523
- }
524
557
 
525
- /* Animations */
526
- @keyframes fadeInUp {
527
- from {
528
- opacity: 0;
529
- transform: translateY(30px);
558
+ .section-title {
559
+ font-size: 1.5rem;
530
560
  }
531
- to {
532
- opacity: 1;
533
- transform: translateY(0);
561
+
562
+ .hero h1 {
563
+ font-size: 1.75rem;
534
564
  }
535
565
  }
536
566
 
537
- .hero, .step, .use-case {
538
- animation: fadeInUp 0.6s ease-out;
567
+ /* Subtle animations */
568
+ @keyframes fadeIn {
569
+ from { opacity: 0; }
570
+ to { opacity: 1; }
571
+ }
572
+
573
+ .hero {
574
+ animation: fadeIn 0.4s ease-out;
539
575
  }
540
576
  </style>
541
577
  </head>
@@ -554,8 +590,8 @@
554
590
  <h1>Mirror your Mac terminal<br>to your phone</h1>
555
591
  <p>Continue your Claude Code or Gemini CLI session on your phone or iPad while on the go.</p>
556
592
 
557
- <p style="text-align: center; color: rgba(255, 255, 255, 0.95); font-size: 1.15rem; margin: 40px 0 30px; font-weight: 500;">
558
- Get started in 3 simple steps
593
+ <p style="text-align: center; color: var(--text-muted); font-size: 0.9rem; margin: 40px 0 24px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">
594
+ Quick Setup
559
595
  </p>
560
596
 
561
597
  <div class="setup-steps">
@@ -612,19 +648,19 @@
612
648
 
613
649
  <div class="steps">
614
650
  <div class="step">
615
- <div class="step-icon">🔐</div>
651
+ <div class="step-icon">1</div>
616
652
  <h3>Secure Login</h3>
617
653
  <p>Sign in with your Google account. Your credentials are encrypted and secure.</p>
618
654
  </div>
619
-
655
+
620
656
  <div class="step">
621
- <div class="step-icon">🌐</div>
657
+ <div class="step-icon">2</div>
622
658
  <h3>Cloud Terminal</h3>
623
659
  <p>Access a cloud-based terminal environment with all your favorite command-line tools pre-installed.</p>
624
660
  </div>
625
-
661
+
626
662
  <div class="step">
627
- <div class="step-icon">📱</div>
663
+ <div class="step-icon">3</div>
628
664
  <h3>Any Device</h3>
629
665
  <p>Use from your phone, tablet, or any web browser. Your terminal is always accessible.</p>
630
666
  </div>
@@ -636,7 +672,7 @@
636
672
  <section class="security" id="security">
637
673
  <div class="container">
638
674
  <h2 class="section-title">Secure & Reliable</h2>
639
- <p style="text-align: center; margin-bottom: 40px; color: #666; font-size: 1.1rem;">
675
+ <p style="text-align: center; margin-bottom: 40px; color: var(--text-secondary); font-size: 0.95rem;">
640
676
  Enterprise-grade security with Google OAuth authentication.
641
677
  </p>
642
678
 
@@ -658,10 +694,12 @@
658
694
  </div>
659
695
 
660
696
  <div style="text-align: center; margin-top: 40px;">
661
- <p style="color: #666;">✅ Encrypted connections (HTTPS/WSS)<br>
662
- Google-grade authentication<br>
663
- ✅ Session-based security<br>
664
- No local installation required</p>
697
+ <p style="color: var(--text-muted); font-size: 0.9rem; line-height: 1.8;">
698
+ Encrypted connections (HTTPS/WSS)<br>
699
+ Google-grade authentication<br>
700
+ Session-based security<br>
701
+ No local installation required
702
+ </p>
665
703
  </div>
666
704
  </div>
667
705
  </section>
@@ -673,32 +711,32 @@
673
711
 
674
712
  <div class="use-case-grid">
675
713
  <div class="use-case">
676
- <h3>🤖 Claude Code CLI</h3>
714
+ <h3>Claude Code CLI</h3>
677
715
  <p>Use Claude Code CLI from your phone. Run AI-powered coding commands using your own Claude API account.</p>
678
716
  </div>
679
-
717
+
680
718
  <div class="use-case">
681
- <h3>✨ Gemini CLI</h3>
719
+ <h3>Gemini CLI</h3>
682
720
  <p>Use Gemini CLI from your phone. Access Google's AI tools with your own Google account and API access.</p>
683
721
  </div>
684
-
722
+
685
723
  <div class="use-case">
686
- <h3>🔧 Standard Tools</h3>
724
+ <h3>Standard Tools</h3>
687
725
  <p>Git, npm, pip, Docker, SSH - all your usual command-line tools work normally from your phone.</p>
688
726
  </div>
689
-
727
+
690
728
  <div class="use-case">
691
- <h3>🔧 Quick Fixes</h3>
729
+ <h3>Quick Fixes</h3>
692
730
  <p>Fix bugs, check logs, or deploy code from your phone. Full access to your development environment.</p>
693
731
  </div>
694
-
732
+
695
733
  <div class="use-case">
696
- <h3>📱 Mobile Development</h3>
734
+ <h3>Mobile Development</h3>
697
735
  <p>Code during commutes or away from your desk. Follow tutorials and practice programming from your phone.</p>
698
736
  </div>
699
-
737
+
700
738
  <div class="use-case">
701
- <h3>🏠 Personal Projects</h3>
739
+ <h3>Personal Projects</h3>
702
740
  <p>Work on personal projects from anywhere. Your own computer, your own accounts, your own code.</p>
703
741
  </div>
704
742
  </div>
@@ -708,24 +746,20 @@
708
746
  <!-- Footer CTA -->
709
747
  <section class="footer-cta" id="get-started">
710
748
  <div class="container">
711
- <h2>Ready to Start?</h2>
712
- <p>Access your terminal from anywhere in seconds</p>
713
-
714
- <div style="margin-top: 40px;">
715
- <button class="btn-primary" onclick="handleGoogleLogin()" style="margin-right: 20px; padding: 20px 40px; font-size: 1.2rem;">Start Now</button>
749
+ <h2>Get Started</h2>
750
+ <p>Access your terminal from anywhere</p>
751
+
752
+ <div style="margin-top: 32px; display: flex; gap: 16px; justify-content: center; flex-wrap: wrap;">
753
+ <button class="btn-primary" onclick="handleGoogleLogin()" style="padding: 16px 32px;">Start Now</button>
716
754
  <a href="https://github.com/karmalsky/shell-mirror" class="btn-secondary">View on GitHub</a>
717
755
  </div>
718
-
719
- <p style="margin-top: 30px; opacity: 0.8; font-size: 0.9rem;">
720
- Free to use • Secure • No installation required
721
- </p>
722
756
  </div>
723
757
  </section>
724
-
758
+
725
759
  <!-- Version Footer -->
726
- <footer style="background: #ff6b35; color: white; text-align: center; padding: 20px 0; font-size: 0.8rem;">
760
+ <footer style="background: var(--bg-secondary); color: var(--text-muted); text-align: center; padding: 16px 0; font-size: 0.75rem; border-top: 1px solid var(--border);">
727
761
  <div class="container">
728
- <p id="version-info">Shell Mirror • Loading version...</p>
762
+ <p id="version-info">Shell Mirror</p>
729
763
  </div>
730
764
  </footer>
731
765
  </main>
@@ -797,17 +831,10 @@
797
831
  const response = await fetch('/build-info.json');
798
832
  const buildInfo = await response.json();
799
833
  const versionElement = document.getElementById('version-info');
800
- const footerElement = versionElement?.parentElement?.parentElement; // Get the footer element
801
-
834
+
802
835
  if (versionElement && buildInfo) {
803
836
  const buildDate = new Date(buildInfo.buildTime).toLocaleDateString();
804
- versionElement.textContent = `Shell Mirror v${buildInfo.version} • Built ${buildDate}`;
805
-
806
- // Apply random footer color from build info
807
- if (footerElement && buildInfo.footerColor) {
808
- footerElement.style.background = buildInfo.footerColor;
809
- console.log(`🎨 Applied footer color: ${buildInfo.footerColor}`);
810
- }
837
+ versionElement.textContent = `Shell Mirror v${buildInfo.version}`;
811
838
  }
812
839
  } catch (error) {
813
840
  console.log('Could not load build info:', error);
@@ -881,12 +908,12 @@
881
908
  // Update the Google login button in step 3 for authenticated users
882
909
  const googleButton = document.querySelector('.btn-google');
883
910
  if (googleButton && authStatus.isAuthenticated) {
884
- googleButton.innerHTML = '📊 Enter Dashboard';
911
+ googleButton.innerHTML = 'Enter Dashboard';
885
912
  googleButton.onclick = (e) => {
886
913
  e.preventDefault();
887
914
  openDashboard();
888
915
  };
889
- googleButton.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
916
+ googleButton.style.background = 'var(--accent)';
890
917
  googleButton.style.color = 'white';
891
918
  googleButton.style.border = 'none';
892
919
  }
@@ -902,26 +929,23 @@
902
929
  });
903
930
  });
904
931
 
905
- // Add intersection observer for animations
932
+ // Subtle fade-in for sections
906
933
  const observerOptions = {
907
934
  threshold: 0.1,
908
- rootMargin: '0px 0px -50px 0px'
935
+ rootMargin: '0px 0px -20px 0px'
909
936
  };
910
937
 
911
938
  const observer = new IntersectionObserver((entries) => {
912
939
  entries.forEach(entry => {
913
940
  if (entry.isIntersecting) {
914
941
  entry.target.style.opacity = '1';
915
- entry.target.style.transform = 'translateY(0)';
916
942
  }
917
943
  });
918
944
  }, observerOptions);
919
945
 
920
- // Observe elements for animation
921
946
  document.querySelectorAll('.step, .use-case').forEach(el => {
922
947
  el.style.opacity = '0';
923
- el.style.transform = 'translateY(30px)';
924
- el.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
948
+ el.style.transition = 'opacity 0.3s ease';
925
949
  observer.observe(el);
926
950
  });
927
951
  </script>