shell-mirror 1.5.131 → 1.5.138
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 +1 -1
- package/public/.htaccess +7 -0
- package/public/app/dashboard.html +4 -3
- package/public/app/dashboard.js +4 -0
- package/public/app/terminal.html +14 -13
- package/public/app/terminal.js +39 -14
- package/public/contact.html +492 -0
- package/public/how-it-works.html +442 -0
- package/public/images/favicon.png +0 -0
- package/public/images/hero_mockup.png +0 -0
- package/public/images/private_by_design.png +0 -0
- package/public/images/real_terminal_view.png +0 -0
- package/public/images/same_session.png +0 -0
- package/public/images/shellmirror_clean_hero.png +0 -0
- package/public/images/shellmirror_macbook_hero.png +0 -0
- package/public/images/terminal-svgrepo-com.svg +7 -0
- package/public/index.html +909 -570
- package/public/privacy.html +362 -0
- package/server.js +9 -0
package/package.json
CHANGED
package/public/.htaccess
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Clean URL rewrites for marketing pages
|
|
2
|
+
RewriteEngine On
|
|
3
|
+
RewriteCond %{REQUEST_FILENAME} !-f
|
|
4
|
+
RewriteCond %{REQUEST_FILENAME} !-d
|
|
5
|
+
RewriteRule ^how-it-works$ /how-it-works.html [L]
|
|
6
|
+
RewriteRule ^privacy$ /privacy.html [L]
|
|
7
|
+
RewriteRule ^contact$ /contact.html [L]
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<
|
|
6
|
+
<link rel="icon" type="image/png" href="/images/favicon.png">
|
|
7
|
+
<title>>shell-mirror Dashboard</title>
|
|
7
8
|
|
|
8
9
|
<!-- Google Analytics 4 -->
|
|
9
10
|
<script>
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
<div class="header-content">
|
|
60
61
|
<div class="logo">
|
|
61
62
|
<a href="/" style="text-decoration: none; color: inherit;">
|
|
62
|
-
<h1
|
|
63
|
+
<h1>>shell-mirror</h1>
|
|
63
64
|
</a>
|
|
64
65
|
</div>
|
|
65
66
|
<div class="header-right">
|
|
@@ -98,7 +99,7 @@
|
|
|
98
99
|
|
|
99
100
|
<!-- Version Footer -->
|
|
100
101
|
<footer id="version-footer" style="background: var(--bg-secondary, #141519); color: var(--text-secondary, #8a8f98); text-align: center; padding: 8px 0; font-size: 0.75rem; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; border-top: 1px solid var(--border, #2a2b30);">
|
|
101
|
-
<p id="dashboard-version-info" style="margin: 0;"
|
|
102
|
+
<p id="dashboard-version-info" style="margin: 0;">>shell-mirror • Loading...</p>
|
|
102
103
|
</footer>
|
|
103
104
|
|
|
104
105
|
<script src="dashboard.js"></script>
|
package/public/app/dashboard.js
CHANGED
|
@@ -56,6 +56,10 @@ class ShellMirrorDashboard {
|
|
|
56
56
|
if (authStatus.isAuthenticated) {
|
|
57
57
|
this.isAuthenticated = true;
|
|
58
58
|
this.user = authStatus.user;
|
|
59
|
+
// Track successful sign-in arrival (key event for funnel)
|
|
60
|
+
if (typeof sendGAEvent === 'function') {
|
|
61
|
+
sendGAEvent('sign_in_complete', { method: 'google' });
|
|
62
|
+
}
|
|
59
63
|
await this.loadDashboardData();
|
|
60
64
|
this.renderAuthenticatedDashboard();
|
|
61
65
|
this.updateLastRefreshTime();
|
package/public/app/terminal.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<link rel="icon" type="image/png" href="/images/favicon.png">
|
|
6
7
|
<title>Terminal Mirror</title>
|
|
7
8
|
|
|
8
9
|
<!-- Google Analytics 4 -->
|
|
@@ -549,36 +550,36 @@
|
|
|
549
550
|
<!-- Floating Buttons - Mobile CLI Shortcuts -->
|
|
550
551
|
<div id="floating-buttons" class="floating-buttons">
|
|
551
552
|
<!-- Toggle button (collapse/expand) -->
|
|
552
|
-
<button class="fab-toggle" id="fabToggle" aria-label="Toggle keyboard shortcuts">
|
|
553
|
+
<button class="fab-toggle" id="fabToggle" aria-label="Toggle keyboard shortcuts" tabindex="-1">
|
|
553
554
|
<span class="fab-toggle-icon">⌨</span>
|
|
554
555
|
</button>
|
|
555
556
|
|
|
556
557
|
<!-- Left scroll indicator -->
|
|
557
|
-
<button class="fab-scroll fab-scroll-left hidden" id="fabScrollLeft" aria-label="Scroll left">
|
|
558
|
+
<button class="fab-scroll fab-scroll-left hidden" id="fabScrollLeft" aria-label="Scroll left" tabindex="-1">
|
|
558
559
|
<span>‹</span>
|
|
559
560
|
</button>
|
|
560
561
|
|
|
561
562
|
<!-- Scrollable button strip -->
|
|
562
563
|
<div class="fab-strip" id="fabStrip">
|
|
563
564
|
<!-- 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>
|
|
565
|
+
<button class="fab-btn" data-keys="ctrl+l" title="Clear screen" tabindex="-1">^L</button>
|
|
566
|
+
<button class="fab-btn" data-keys="ctrl+d" title="EOF/Exit" tabindex="-1">^D</button>
|
|
567
|
+
<button class="fab-btn" data-keys="ctrl+c" title="Interrupt" tabindex="-1">^C</button>
|
|
567
568
|
|
|
568
569
|
<!-- 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>
|
|
570
|
+
<button class="fab-btn fab-primary" data-keys="shift+tab" title="Mode: Normal → Auto → Plan" tabindex="-1">⇧Tab</button>
|
|
571
|
+
<button class="fab-btn" data-keys="tab" title="Autocomplete / Thinking" tabindex="-1">Tab</button>
|
|
572
|
+
<button class="fab-btn" data-keys="up" data-repeat="true" title="History up" tabindex="-1">↑</button>
|
|
573
|
+
<button class="fab-btn" data-keys="down" data-repeat="true" title="History down" tabindex="-1">↓</button>
|
|
574
|
+
<button class="fab-btn" data-keys="escape" title="Cancel / Rewind (2x)" tabindex="-1">Esc</button>
|
|
574
575
|
|
|
575
576
|
<!-- 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>
|
|
577
|
+
<button class="fab-btn" data-keys="left" title="Cursor left" tabindex="-1">←</button>
|
|
578
|
+
<button class="fab-btn" data-keys="right" title="Cursor right" tabindex="-1">→</button>
|
|
578
579
|
</div>
|
|
579
580
|
|
|
580
581
|
<!-- Right scroll indicator -->
|
|
581
|
-
<button class="fab-scroll fab-scroll-right" id="fabScrollRight" aria-label="Scroll right">
|
|
582
|
+
<button class="fab-scroll fab-scroll-right" id="fabScrollRight" aria-label="Scroll right" tabindex="-1">
|
|
582
583
|
<span>›</span>
|
|
583
584
|
</button>
|
|
584
585
|
</div>
|
package/public/app/terminal.js
CHANGED
|
@@ -1464,30 +1464,42 @@ function initFloatingButtons() {
|
|
|
1464
1464
|
|
|
1465
1465
|
console.log('[CLIENT] ⌨️ Initializing floating buttons for mobile CLI shortcuts');
|
|
1466
1466
|
|
|
1467
|
-
// Toggle collapse/expand
|
|
1468
|
-
|
|
1467
|
+
// Toggle collapse/expand - preserve focus
|
|
1468
|
+
const handleToggle = (e) => {
|
|
1469
|
+
e.preventDefault();
|
|
1469
1470
|
container.classList.toggle('collapsed');
|
|
1470
1471
|
localStorage.setItem('fab-collapsed', container.classList.contains('collapsed'));
|
|
1471
|
-
|
|
1472
|
+
setTimeout(() => term.focus(), 0);
|
|
1473
|
+
};
|
|
1474
|
+
toggle.addEventListener('touchstart', handleToggle, { passive: false });
|
|
1475
|
+
toggle.addEventListener('click', handleToggle);
|
|
1472
1476
|
|
|
1473
1477
|
// Restore saved collapsed state
|
|
1474
1478
|
if (localStorage.getItem('fab-collapsed') === 'true') {
|
|
1475
1479
|
container.classList.add('collapsed');
|
|
1476
1480
|
}
|
|
1477
1481
|
|
|
1478
|
-
// Scroll indicator click handlers
|
|
1482
|
+
// Scroll indicator click handlers - preserve focus
|
|
1479
1483
|
const scrollAmount = 150; // pixels per click
|
|
1480
1484
|
|
|
1481
1485
|
if (scrollLeft) {
|
|
1482
|
-
|
|
1486
|
+
const handleScrollLeft = (e) => {
|
|
1487
|
+
e.preventDefault();
|
|
1483
1488
|
strip.scrollBy({ left: -scrollAmount, behavior: 'smooth' });
|
|
1484
|
-
|
|
1489
|
+
setTimeout(() => term.focus(), 0);
|
|
1490
|
+
};
|
|
1491
|
+
scrollLeft.addEventListener('touchstart', handleScrollLeft, { passive: false });
|
|
1492
|
+
scrollLeft.addEventListener('click', handleScrollLeft);
|
|
1485
1493
|
}
|
|
1486
1494
|
|
|
1487
1495
|
if (scrollRight) {
|
|
1488
|
-
|
|
1496
|
+
const handleScrollRight = (e) => {
|
|
1497
|
+
e.preventDefault();
|
|
1489
1498
|
strip.scrollBy({ left: scrollAmount, behavior: 'smooth' });
|
|
1490
|
-
|
|
1499
|
+
setTimeout(() => term.focus(), 0);
|
|
1500
|
+
};
|
|
1501
|
+
scrollRight.addEventListener('touchstart', handleScrollRight, { passive: false });
|
|
1502
|
+
scrollRight.addEventListener('click', handleScrollRight);
|
|
1491
1503
|
}
|
|
1492
1504
|
|
|
1493
1505
|
// Update scroll indicator visibility based on scroll position
|
|
@@ -1504,12 +1516,24 @@ function initFloatingButtons() {
|
|
|
1504
1516
|
strip.addEventListener('scroll', updateScrollIndicators);
|
|
1505
1517
|
|
|
1506
1518
|
// Button click handlers - send key sequences to terminal
|
|
1507
|
-
|
|
1519
|
+
// Use touchstart with preventDefault to prevent focus loss on mobile
|
|
1520
|
+
document.querySelectorAll('.fab-btn:not([data-repeat])').forEach(btn => {
|
|
1521
|
+
// Touch handler (mobile) - prevent focus loss
|
|
1522
|
+
btn.addEventListener('touchstart', (e) => {
|
|
1523
|
+
e.preventDefault(); // Prevents focus shift and keyboard dismiss
|
|
1524
|
+
const keys = btn.dataset.keys;
|
|
1525
|
+
if (keys && fabKeyMap[keys]) {
|
|
1526
|
+
sendFabKey(fabKeyMap[keys]);
|
|
1527
|
+
}
|
|
1528
|
+
// Re-focus terminal after a tiny delay to ensure keyboard stays up
|
|
1529
|
+
setTimeout(() => term.focus(), 0);
|
|
1530
|
+
}, { passive: false });
|
|
1531
|
+
|
|
1532
|
+
// Click handler (desktop fallback)
|
|
1508
1533
|
btn.addEventListener('click', (e) => {
|
|
1509
1534
|
e.preventDefault();
|
|
1510
1535
|
const keys = btn.dataset.keys;
|
|
1511
1536
|
if (keys && fabKeyMap[keys]) {
|
|
1512
|
-
// Send the key sequence through the terminal input handler
|
|
1513
1537
|
sendFabKey(fabKeyMap[keys]);
|
|
1514
1538
|
term.focus();
|
|
1515
1539
|
}
|
|
@@ -1557,7 +1581,6 @@ function initFabLongPress() {
|
|
|
1557
1581
|
|
|
1558
1582
|
// First key immediately
|
|
1559
1583
|
sendFabKey(fabKeyMap[keys]);
|
|
1560
|
-
term.focus();
|
|
1561
1584
|
|
|
1562
1585
|
// Then repeat after delay
|
|
1563
1586
|
timeout = setTimeout(() => {
|
|
@@ -1572,13 +1595,15 @@ function initFabLongPress() {
|
|
|
1572
1595
|
if (interval) clearInterval(interval);
|
|
1573
1596
|
timeout = null;
|
|
1574
1597
|
interval = null;
|
|
1598
|
+
// Re-focus terminal when touch ends
|
|
1599
|
+
setTimeout(() => term.focus(), 0);
|
|
1575
1600
|
};
|
|
1576
1601
|
|
|
1577
|
-
// Touch events for mobile
|
|
1602
|
+
// Touch events for mobile - prevent focus loss
|
|
1578
1603
|
btn.addEventListener('touchstart', (e) => {
|
|
1579
|
-
e.preventDefault();
|
|
1604
|
+
e.preventDefault(); // Prevents focus shift and keyboard dismiss
|
|
1580
1605
|
startRepeat();
|
|
1581
|
-
});
|
|
1606
|
+
}, { passive: false });
|
|
1582
1607
|
btn.addEventListener('touchend', stopRepeat);
|
|
1583
1608
|
btn.addEventListener('touchcancel', stopRepeat);
|
|
1584
1609
|
|