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
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<link rel="icon" type="image/png" href="/images/favicon.png">
|
|
7
|
+
<title>>shell-mirror — Contact</title>
|
|
8
|
+
<meta name="description" content="Get in touch with the >shell-mirror team. Questions, feedback, bug reports — we'd love to hear from you.">
|
|
9
|
+
|
|
10
|
+
<meta property="og:type" content="website">
|
|
11
|
+
<meta property="og:title" content=">shell-mirror — Contact">
|
|
12
|
+
<meta property="og:description" content="Get in touch with the >shell-mirror team.">
|
|
13
|
+
<meta property="og:url" content="https://shellmirror.app/contact">
|
|
14
|
+
|
|
15
|
+
<meta property="twitter:card" content="summary">
|
|
16
|
+
<meta property="twitter:title" content=">shell-mirror — Contact">
|
|
17
|
+
<meta property="twitter:description" content="Get in touch with the >shell-mirror team.">
|
|
18
|
+
|
|
19
|
+
<!-- Fonts -->
|
|
20
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
21
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
22
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;800&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
23
|
+
|
|
24
|
+
<!-- Google Analytics 4 -->
|
|
25
|
+
<script>
|
|
26
|
+
window.dataLayer = window.dataLayer || [];
|
|
27
|
+
function gtag(){dataLayer.push(arguments);}
|
|
28
|
+
gtag('js', new Date());
|
|
29
|
+
gtag('config', 'G-LG7ZGLB8FK');
|
|
30
|
+
(function() {
|
|
31
|
+
var script = document.createElement('script');
|
|
32
|
+
script.async = true;
|
|
33
|
+
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-LG7ZGLB8FK';
|
|
34
|
+
script.onload = function() { window.gtagLoaded = true; };
|
|
35
|
+
script.onerror = function() { window.gtagLoaded = false; };
|
|
36
|
+
document.head.appendChild(script);
|
|
37
|
+
})();
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<!-- Microsoft Clarity -->
|
|
41
|
+
<script type="text/javascript">
|
|
42
|
+
(function(c,l,a,r,i,t,y){
|
|
43
|
+
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
|
44
|
+
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
|
45
|
+
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
|
46
|
+
})(window, document, "clarity", "script", "sy1w2d7il7");
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<style>
|
|
50
|
+
:root {
|
|
51
|
+
--bg-primary: #121315;
|
|
52
|
+
--bg-secondary: #1b1c1e;
|
|
53
|
+
--bg-tertiary: #1f2022;
|
|
54
|
+
--bg-hover: #292a2c;
|
|
55
|
+
--text-primary: #e3e2e5;
|
|
56
|
+
--text-secondary: #8a8f98;
|
|
57
|
+
--text-muted: #5a5f6a;
|
|
58
|
+
--accent: #7c4dff;
|
|
59
|
+
--accent-light: #cdbdff;
|
|
60
|
+
--accent-hover: #6833ea;
|
|
61
|
+
--success: #00c853;
|
|
62
|
+
--border: rgba(73, 68, 85, 0.15);
|
|
63
|
+
--border-visible: rgba(73, 68, 85, 0.3);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
67
|
+
|
|
68
|
+
body {
|
|
69
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
70
|
+
line-height: 1.6;
|
|
71
|
+
color: var(--text-primary);
|
|
72
|
+
background: var(--bg-primary);
|
|
73
|
+
min-height: 100vh;
|
|
74
|
+
-webkit-font-smoothing: antialiased;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 0 32px; }
|
|
78
|
+
|
|
79
|
+
/* Header */
|
|
80
|
+
header {
|
|
81
|
+
position: fixed;
|
|
82
|
+
top: 0;
|
|
83
|
+
width: 100%;
|
|
84
|
+
z-index: 100;
|
|
85
|
+
background: rgba(18, 19, 21, 0.6);
|
|
86
|
+
backdrop-filter: blur(20px);
|
|
87
|
+
-webkit-backdrop-filter: blur(20px);
|
|
88
|
+
box-shadow: 0 8px 32px rgba(124, 77, 255, 0.04);
|
|
89
|
+
}
|
|
90
|
+
header .container {
|
|
91
|
+
display: flex;
|
|
92
|
+
justify-content: space-between;
|
|
93
|
+
align-items: center;
|
|
94
|
+
padding-top: 16px;
|
|
95
|
+
padding-bottom: 16px;
|
|
96
|
+
}
|
|
97
|
+
.logo { font-size: 1.3rem; font-weight: 800; color: #f0eff2; letter-spacing: -0.03em; text-decoration: none; }
|
|
98
|
+
.nav-links { display: flex; gap: 32px; align-items: center; }
|
|
99
|
+
.nav-links a { color: var(--text-muted); text-decoration: none; font-size: 0.9rem; font-weight: 500; transition: color 0.2s ease; }
|
|
100
|
+
.nav-links a:hover { color: var(--text-primary); }
|
|
101
|
+
.header-right { display: flex; align-items: center; gap: 16px; }
|
|
102
|
+
.cta-button {
|
|
103
|
+
background: var(--accent); color: white; padding: 10px 22px;
|
|
104
|
+
text-decoration: none; border-radius: 8px; font-weight: 600;
|
|
105
|
+
border: none; cursor: pointer; transition: all 0.2s ease; font-size: 0.9rem;
|
|
106
|
+
box-shadow: 0 4px 16px rgba(124, 77, 255, 0.2);
|
|
107
|
+
}
|
|
108
|
+
.cta-button:hover { background: var(--accent-hover); box-shadow: 0 4px 20px rgba(124, 77, 255, 0.35); }
|
|
109
|
+
|
|
110
|
+
/* Page header */
|
|
111
|
+
.page-header {
|
|
112
|
+
text-align: center;
|
|
113
|
+
padding: 140px 0 40px;
|
|
114
|
+
}
|
|
115
|
+
.page-header h1 {
|
|
116
|
+
font-size: clamp(1.75rem, 4vw, 2.5rem);
|
|
117
|
+
font-weight: 800;
|
|
118
|
+
margin-bottom: 20px;
|
|
119
|
+
letter-spacing: -0.02em;
|
|
120
|
+
}
|
|
121
|
+
.page-header p {
|
|
122
|
+
color: var(--text-secondary);
|
|
123
|
+
font-size: 1.05rem;
|
|
124
|
+
max-width: 600px;
|
|
125
|
+
margin: 0 auto;
|
|
126
|
+
line-height: 1.7;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Contact form card */
|
|
130
|
+
.contact-section {
|
|
131
|
+
max-width: 560px;
|
|
132
|
+
margin: 0 auto;
|
|
133
|
+
padding: 0 32px 100px;
|
|
134
|
+
}
|
|
135
|
+
.contact-card {
|
|
136
|
+
padding: 40px;
|
|
137
|
+
background: rgba(27, 28, 30, 0.8);
|
|
138
|
+
backdrop-filter: blur(20px);
|
|
139
|
+
-webkit-backdrop-filter: blur(20px);
|
|
140
|
+
border: 1px solid var(--border-visible);
|
|
141
|
+
border-radius: 16px;
|
|
142
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Form fields */
|
|
146
|
+
.form-group { margin-bottom: 20px; }
|
|
147
|
+
.form-group label {
|
|
148
|
+
display: block;
|
|
149
|
+
font-size: 0.85rem;
|
|
150
|
+
font-weight: 500;
|
|
151
|
+
color: var(--text-secondary);
|
|
152
|
+
margin-bottom: 8px;
|
|
153
|
+
}
|
|
154
|
+
.form-group input,
|
|
155
|
+
.form-group textarea {
|
|
156
|
+
width: 100%;
|
|
157
|
+
padding: 12px 16px;
|
|
158
|
+
background: var(--bg-primary);
|
|
159
|
+
border: 1px solid var(--border-visible);
|
|
160
|
+
border-radius: 8px;
|
|
161
|
+
color: var(--text-primary);
|
|
162
|
+
font-family: 'Inter', sans-serif;
|
|
163
|
+
font-size: 0.95rem;
|
|
164
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
165
|
+
outline: none;
|
|
166
|
+
}
|
|
167
|
+
.form-group input:focus,
|
|
168
|
+
.form-group textarea:focus {
|
|
169
|
+
border-color: var(--accent);
|
|
170
|
+
box-shadow: 0 0 0 3px rgba(124, 77, 255, 0.15);
|
|
171
|
+
}
|
|
172
|
+
.form-group input::placeholder,
|
|
173
|
+
.form-group textarea::placeholder {
|
|
174
|
+
color: var(--text-muted);
|
|
175
|
+
}
|
|
176
|
+
.form-group textarea {
|
|
177
|
+
min-height: 140px;
|
|
178
|
+
resize: vertical;
|
|
179
|
+
}
|
|
180
|
+
.form-group input.error,
|
|
181
|
+
.form-group textarea.error {
|
|
182
|
+
border-color: #ff4444;
|
|
183
|
+
}
|
|
184
|
+
.form-group .error-text {
|
|
185
|
+
color: #ff6b6b;
|
|
186
|
+
font-size: 0.8rem;
|
|
187
|
+
margin-top: 6px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* Honeypot */
|
|
191
|
+
.form-hp { position: absolute; left: -9999px; }
|
|
192
|
+
|
|
193
|
+
/* Submit button */
|
|
194
|
+
.submit-btn {
|
|
195
|
+
width: 100%;
|
|
196
|
+
background: var(--accent);
|
|
197
|
+
color: white;
|
|
198
|
+
padding: 14px 24px;
|
|
199
|
+
border: none;
|
|
200
|
+
border-radius: 8px;
|
|
201
|
+
font-weight: 600;
|
|
202
|
+
font-size: 1rem;
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
transition: all 0.2s ease;
|
|
205
|
+
box-shadow: 0 4px 16px rgba(124, 77, 255, 0.2);
|
|
206
|
+
font-family: 'Inter', sans-serif;
|
|
207
|
+
margin-top: 4px;
|
|
208
|
+
}
|
|
209
|
+
.submit-btn:hover {
|
|
210
|
+
background: var(--accent-hover);
|
|
211
|
+
box-shadow: 0 4px 20px rgba(124, 77, 255, 0.35);
|
|
212
|
+
}
|
|
213
|
+
.submit-btn:disabled {
|
|
214
|
+
opacity: 0.6;
|
|
215
|
+
cursor: not-allowed;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Status messages */
|
|
219
|
+
.form-status {
|
|
220
|
+
border-radius: 8px;
|
|
221
|
+
padding: 16px;
|
|
222
|
+
margin-top: 20px;
|
|
223
|
+
font-size: 0.95rem;
|
|
224
|
+
text-align: center;
|
|
225
|
+
display: none;
|
|
226
|
+
}
|
|
227
|
+
.form-status.success {
|
|
228
|
+
display: block;
|
|
229
|
+
background: rgba(0, 200, 83, 0.1);
|
|
230
|
+
border: 1px solid rgba(0, 200, 83, 0.3);
|
|
231
|
+
color: var(--success);
|
|
232
|
+
}
|
|
233
|
+
.form-status.error {
|
|
234
|
+
display: block;
|
|
235
|
+
background: rgba(255, 68, 68, 0.1);
|
|
236
|
+
border: 1px solid rgba(255, 68, 68, 0.3);
|
|
237
|
+
color: #ff6b6b;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* Footer */
|
|
241
|
+
footer {
|
|
242
|
+
background: rgba(13, 14, 16, 0.8);
|
|
243
|
+
border-top: 1px solid var(--border);
|
|
244
|
+
padding: 40px 0;
|
|
245
|
+
}
|
|
246
|
+
.footer-inner { display: flex; justify-content: space-between; align-items: center; }
|
|
247
|
+
.footer-brand {
|
|
248
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
249
|
+
font-weight: 700; font-size: 0.85rem; color: var(--text-secondary);
|
|
250
|
+
letter-spacing: 0.15em; text-transform: uppercase;
|
|
251
|
+
}
|
|
252
|
+
.footer-links { display: flex; gap: 28px; }
|
|
253
|
+
.footer-links a {
|
|
254
|
+
color: var(--text-muted); text-decoration: none;
|
|
255
|
+
font-family: 'Space Grotesk', sans-serif; font-size: 0.8rem;
|
|
256
|
+
letter-spacing: 0.1em; text-transform: uppercase; transition: color 0.2s ease;
|
|
257
|
+
}
|
|
258
|
+
.footer-links a:hover { color: var(--accent-light); }
|
|
259
|
+
|
|
260
|
+
@media (max-width: 768px) {
|
|
261
|
+
.container { padding: 0 20px; }
|
|
262
|
+
.page-header { padding: 120px 0 30px; }
|
|
263
|
+
.page-header h1 { font-size: 1.5rem; }
|
|
264
|
+
.header-right .nav-links { display: none; }
|
|
265
|
+
.footer-inner { flex-direction: column; gap: 16px; text-align: center; }
|
|
266
|
+
.contact-section { padding: 0 20px 80px; }
|
|
267
|
+
.contact-card { padding: 28px 20px; }
|
|
268
|
+
}
|
|
269
|
+
</style>
|
|
270
|
+
</head>
|
|
271
|
+
<body>
|
|
272
|
+
<header>
|
|
273
|
+
<div class="container">
|
|
274
|
+
<a href="/" class="logo">>shell-mirror</a>
|
|
275
|
+
<div class="header-right">
|
|
276
|
+
<div class="nav-links">
|
|
277
|
+
<a href="/privacy">Privacy</a>
|
|
278
|
+
<a href="/contact">Contact</a>
|
|
279
|
+
</div>
|
|
280
|
+
<div id="header-cta">
|
|
281
|
+
<button class="cta-button" onclick="handleGoogleLogin()">Get Started</button>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</header>
|
|
286
|
+
|
|
287
|
+
<main>
|
|
288
|
+
<section class="page-header">
|
|
289
|
+
<div class="container">
|
|
290
|
+
<h1>Get in touch</h1>
|
|
291
|
+
<p>Have a question, found a bug, or just want to say hello? We'd love to hear from you.</p>
|
|
292
|
+
</div>
|
|
293
|
+
</section>
|
|
294
|
+
|
|
295
|
+
<div class="contact-section">
|
|
296
|
+
<div class="contact-card">
|
|
297
|
+
<form id="contact-form" onsubmit="handleContactSubmit(event)" novalidate>
|
|
298
|
+
<div class="form-group">
|
|
299
|
+
<label for="name">Name</label>
|
|
300
|
+
<input type="text" id="name" name="name" placeholder="Your name" required>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<div class="form-group">
|
|
304
|
+
<label for="email">Email</label>
|
|
305
|
+
<input type="email" id="email" name="email" placeholder="you@example.com" required>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<div class="form-group">
|
|
309
|
+
<label for="subject">Subject</label>
|
|
310
|
+
<input type="text" id="subject" name="subject" placeholder="What's this about?" required>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
<div class="form-group">
|
|
314
|
+
<label for="message">Message</label>
|
|
315
|
+
<textarea id="message" name="message" placeholder="Tell us more..." required></textarea>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<!-- Honeypot -->
|
|
319
|
+
<div class="form-hp" aria-hidden="true">
|
|
320
|
+
<input type="text" name="website" tabindex="-1" autocomplete="off">
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<button type="submit" class="submit-btn">Send message</button>
|
|
324
|
+
<div id="form-status" class="form-status"></div>
|
|
325
|
+
</form>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<footer>
|
|
330
|
+
<div class="container footer-inner">
|
|
331
|
+
<div class="footer-brand" id="version-info">>shell-mirror</div>
|
|
332
|
+
<div class="footer-links">
|
|
333
|
+
<a href="/how-it-works">How it works</a>
|
|
334
|
+
<a href="/privacy">Privacy</a>
|
|
335
|
+
<a href="/contact">Contact</a>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
</footer>
|
|
339
|
+
</main>
|
|
340
|
+
|
|
341
|
+
<script>
|
|
342
|
+
// ========== Contact form ==========
|
|
343
|
+
async function handleContactSubmit(e) {
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
|
|
346
|
+
const form = e.target;
|
|
347
|
+
const submitBtn = form.querySelector('.submit-btn');
|
|
348
|
+
const statusEl = document.getElementById('form-status');
|
|
349
|
+
|
|
350
|
+
statusEl.className = 'form-status';
|
|
351
|
+
statusEl.textContent = '';
|
|
352
|
+
clearErrors();
|
|
353
|
+
|
|
354
|
+
const data = {
|
|
355
|
+
name: form.name.value.trim(),
|
|
356
|
+
email: form.email.value.trim(),
|
|
357
|
+
subject: form.subject.value.trim(),
|
|
358
|
+
message: form.message.value.trim(),
|
|
359
|
+
website: form.website.value
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
if (data.name.length < 2) { showFieldError('name', 'Please enter your name (at least 2 characters)'); return; }
|
|
363
|
+
if (!data.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) { showFieldError('email', 'Please enter a valid email address'); return; }
|
|
364
|
+
if (data.subject.length < 3) { showFieldError('subject', 'Please enter a subject (at least 3 characters)'); return; }
|
|
365
|
+
if (data.message.length < 10) { showFieldError('message', 'Please enter a message (at least 10 characters)'); return; }
|
|
366
|
+
|
|
367
|
+
submitBtn.disabled = true;
|
|
368
|
+
submitBtn.textContent = 'Sending...';
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
const response = await fetch('/php-backend/api/contact.php', {
|
|
372
|
+
method: 'POST',
|
|
373
|
+
headers: { 'Content-Type': 'application/json' },
|
|
374
|
+
body: JSON.stringify(data)
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const result = await response.json();
|
|
378
|
+
|
|
379
|
+
if (result.success) {
|
|
380
|
+
sendGAEvent('contact_form_submit', { subject: data.subject, success: 'true' });
|
|
381
|
+
statusEl.className = 'form-status success';
|
|
382
|
+
statusEl.textContent = result.message;
|
|
383
|
+
form.reset();
|
|
384
|
+
} else {
|
|
385
|
+
sendGAEvent('contact_form_submit', { subject: data.subject, success: 'false' });
|
|
386
|
+
statusEl.className = 'form-status error';
|
|
387
|
+
statusEl.textContent = result.message || 'Something went wrong. Please try again.';
|
|
388
|
+
}
|
|
389
|
+
} catch (err) {
|
|
390
|
+
sendGAEvent('contact_form_error', { error_type: 'network' });
|
|
391
|
+
statusEl.className = 'form-status error';
|
|
392
|
+
statusEl.textContent = 'Could not reach the server. Please check your connection and try again.';
|
|
393
|
+
} finally {
|
|
394
|
+
submitBtn.disabled = false;
|
|
395
|
+
submitBtn.textContent = 'Send message';
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function showFieldError(fieldId, message) {
|
|
400
|
+
const field = document.getElementById(fieldId);
|
|
401
|
+
field.classList.add('error');
|
|
402
|
+
const errorEl = document.createElement('div');
|
|
403
|
+
errorEl.className = 'error-text';
|
|
404
|
+
errorEl.textContent = message;
|
|
405
|
+
field.parentNode.appendChild(errorEl);
|
|
406
|
+
field.focus();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function clearErrors() {
|
|
410
|
+
document.querySelectorAll('.error').forEach(el => el.classList.remove('error'));
|
|
411
|
+
document.querySelectorAll('.error-text').forEach(el => el.remove());
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Clear field errors on input
|
|
415
|
+
document.addEventListener('input', function(e) {
|
|
416
|
+
if (e.target.closest('.form-group')) {
|
|
417
|
+
e.target.classList.remove('error');
|
|
418
|
+
const errorText = e.target.parentNode.querySelector('.error-text');
|
|
419
|
+
if (errorText) errorText.remove();
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// ========== Shared page boilerplate ==========
|
|
424
|
+
async function checkAuthStatus() {
|
|
425
|
+
try {
|
|
426
|
+
const response = await fetch('/php-backend/api/auth-status.php');
|
|
427
|
+
const data = await response.json();
|
|
428
|
+
if (data.success && data.data && data.data.authenticated)
|
|
429
|
+
return { isAuthenticated: true, user: data.data.user };
|
|
430
|
+
} catch (error) {}
|
|
431
|
+
return { isAuthenticated: false, user: null };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function sendGAEvent(n, p) { if (typeof gtag === 'function') gtag('event', n, p); }
|
|
435
|
+
|
|
436
|
+
async function handleGoogleLogin() {
|
|
437
|
+
sendGAEvent('cta_click', { cta_label: 'sign_in', cta_location: 'contact_page' });
|
|
438
|
+
window.location.href = '/php-backend/api/auth-login.php?return=' + encodeURIComponent('/app/dashboard');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function loadVersionInfo() {
|
|
442
|
+
try {
|
|
443
|
+
const response = await fetch('/build-info.json');
|
|
444
|
+
const buildInfo = await response.json();
|
|
445
|
+
const el = document.getElementById('version-info');
|
|
446
|
+
if (el && buildInfo) el.textContent = `>shell-mirror v${buildInfo.version}`.toUpperCase();
|
|
447
|
+
} catch (e) {}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Scroll depth
|
|
451
|
+
const scrollFired = {};
|
|
452
|
+
window.addEventListener('scroll', () => {
|
|
453
|
+
const pct = Math.round((window.scrollY + window.innerHeight) / document.body.scrollHeight * 100);
|
|
454
|
+
[25, 50, 75, 100].forEach(t => {
|
|
455
|
+
if (pct >= t && !scrollFired[t]) { scrollFired[t] = true; sendGAEvent('scroll_depth', { scroll_percent: t, page_title: document.title }); }
|
|
456
|
+
});
|
|
457
|
+
}, { passive: true });
|
|
458
|
+
|
|
459
|
+
// Time on page
|
|
460
|
+
const pageLoadTime = Date.now();
|
|
461
|
+
window.addEventListener('beforeunload', () => {
|
|
462
|
+
sendGAEvent('time_on_page', {
|
|
463
|
+
seconds_on_page: Math.round((Date.now() - pageLoadTime) / 1000),
|
|
464
|
+
page_title: document.title,
|
|
465
|
+
max_scroll_percent: Math.max(...Object.keys(scrollFired).map(Number), 0)
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
async function updateHeaderAndCTA() {
|
|
470
|
+
const authStatus = await checkAuthStatus();
|
|
471
|
+
const headerCta = document.getElementById('header-cta');
|
|
472
|
+
if (authStatus.isAuthenticated) {
|
|
473
|
+
headerCta.innerHTML = `
|
|
474
|
+
<span style="color: var(--text-secondary); font-size: 0.85rem;">Welcome, ${authStatus.user.name || authStatus.user.email}</span>
|
|
475
|
+
<a href="/app/dashboard.html" class="cta-button">Dashboard</a>
|
|
476
|
+
`;
|
|
477
|
+
headerCta.style.display = 'flex';
|
|
478
|
+
headerCta.style.alignItems = 'center';
|
|
479
|
+
headerCta.style.gap = '15px';
|
|
480
|
+
} else {
|
|
481
|
+
headerCta.innerHTML = '<button class="cta-button" onclick="handleGoogleLogin()">Sign In</button>';
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
486
|
+
sendGAEvent('page_view', { page_title: document.title, page_location: window.location.href });
|
|
487
|
+
await updateHeaderAndCTA();
|
|
488
|
+
loadVersionInfo();
|
|
489
|
+
});
|
|
490
|
+
</script>
|
|
491
|
+
</body>
|
|
492
|
+
</html>
|