termbeam 0.0.5 → 0.0.6
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/README.md +4 -0
- package/package.json +1 -1
- package/public/index.html +194 -68
- package/public/terminal.html +261 -108
package/README.md
CHANGED
|
@@ -164,3 +164,7 @@ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for:
|
|
|
164
164
|
## 📄 License
|
|
165
165
|
|
|
166
166
|
[MIT](LICENSE) — made with ❤️ by [@dorlugasigal](https://github.com/dorlugasigal)
|
|
167
|
+
|
|
168
|
+
## 🙏 Acknowledgments
|
|
169
|
+
|
|
170
|
+
Special thanks to [@tamirdresher](https://github.com/tamirdresher) for the [blog post](https://www.tamirdresher.com/blog/2026/02/26/squad-remote-control) that inspired the solution idea for this project, and for his [cli-tunnel](https://github.com/tamirdresher/cli-tunnel) implementation.
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -8,9 +8,47 @@
|
|
|
8
8
|
/>
|
|
9
9
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
10
10
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
11
|
-
<meta name="theme-color" content="#
|
|
11
|
+
<meta name="theme-color" content="#1e1e1e" />
|
|
12
12
|
<title>TermBeam</title>
|
|
13
13
|
<style>
|
|
14
|
+
:root {
|
|
15
|
+
--bg: #1e1e1e;
|
|
16
|
+
--surface: #252526;
|
|
17
|
+
--border: #3c3c3c;
|
|
18
|
+
--border-subtle: #474747;
|
|
19
|
+
--text: #d4d4d4;
|
|
20
|
+
--text-secondary: #858585;
|
|
21
|
+
--text-dim: #6e6e6e;
|
|
22
|
+
--text-muted: #5a5a5a;
|
|
23
|
+
--accent: #0078d4;
|
|
24
|
+
--accent-hover: #1a8ae8;
|
|
25
|
+
--accent-active: #005a9e;
|
|
26
|
+
--danger: #f14c4c;
|
|
27
|
+
--danger-hover: #d73a3a;
|
|
28
|
+
--success: #89d185;
|
|
29
|
+
--info: #b0b0b0;
|
|
30
|
+
--shadow: rgba(0,0,0,0.15);
|
|
31
|
+
--overlay-bg: rgba(0,0,0,0.7);
|
|
32
|
+
}
|
|
33
|
+
[data-theme="light"] {
|
|
34
|
+
--bg: #ffffff;
|
|
35
|
+
--surface: #f3f3f3;
|
|
36
|
+
--border: #e0e0e0;
|
|
37
|
+
--border-subtle: #d0d0d0;
|
|
38
|
+
--text: #1e1e1e;
|
|
39
|
+
--text-secondary: #616161;
|
|
40
|
+
--text-dim: #767676;
|
|
41
|
+
--text-muted: #a0a0a0;
|
|
42
|
+
--accent: #0078d4;
|
|
43
|
+
--accent-hover: #106ebe;
|
|
44
|
+
--accent-active: #005a9e;
|
|
45
|
+
--danger: #e51400;
|
|
46
|
+
--danger-hover: #c20000;
|
|
47
|
+
--success: #16825d;
|
|
48
|
+
--info: #616161;
|
|
49
|
+
--shadow: rgba(0,0,0,0.06);
|
|
50
|
+
--overlay-bg: rgba(0,0,0,0.4);
|
|
51
|
+
}
|
|
14
52
|
* {
|
|
15
53
|
margin: 0;
|
|
16
54
|
padding: 0;
|
|
@@ -20,39 +58,66 @@
|
|
|
20
58
|
body {
|
|
21
59
|
height: 100%;
|
|
22
60
|
width: 100%;
|
|
23
|
-
background:
|
|
24
|
-
color:
|
|
61
|
+
background: var(--bg);
|
|
62
|
+
color: var(--text);
|
|
25
63
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
64
|
+
transition: background 0.3s, color 0.3s;
|
|
26
65
|
}
|
|
27
66
|
|
|
28
67
|
.header {
|
|
29
68
|
padding: 20px 16px 12px;
|
|
30
69
|
text-align: center;
|
|
31
|
-
border-bottom: 1px solid
|
|
70
|
+
border-bottom: 1px solid var(--border);
|
|
71
|
+
transition: border-color 0.3s;
|
|
72
|
+
position: relative;
|
|
32
73
|
}
|
|
33
74
|
.header h1 {
|
|
34
75
|
font-size: 22px;
|
|
35
76
|
font-weight: 700;
|
|
36
77
|
}
|
|
37
78
|
.header h1 span {
|
|
38
|
-
color:
|
|
79
|
+
color: var(--accent);
|
|
39
80
|
}
|
|
40
81
|
.header p {
|
|
41
82
|
font-size: 13px;
|
|
42
|
-
color:
|
|
83
|
+
color: var(--text-secondary);
|
|
43
84
|
margin-top: 4px;
|
|
44
85
|
}
|
|
86
|
+
.theme-toggle {
|
|
87
|
+
position: absolute;
|
|
88
|
+
top: 16px;
|
|
89
|
+
right: 16px;
|
|
90
|
+
background: none;
|
|
91
|
+
border: 1px solid var(--border);
|
|
92
|
+
color: var(--text-dim);
|
|
93
|
+
width: 32px;
|
|
94
|
+
height: 32px;
|
|
95
|
+
border-radius: 8px;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
font-size: 16px;
|
|
101
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
102
|
+
-webkit-tap-highlight-color: transparent;
|
|
103
|
+
}
|
|
104
|
+
.theme-toggle:hover {
|
|
105
|
+
color: var(--text);
|
|
106
|
+
border-color: var(--border-subtle);
|
|
107
|
+
background: var(--border);
|
|
108
|
+
}
|
|
45
109
|
|
|
46
110
|
.sessions-list {
|
|
47
111
|
padding: 16px;
|
|
112
|
+
padding-bottom: 80px;
|
|
48
113
|
display: flex;
|
|
49
114
|
flex-direction: column;
|
|
50
115
|
gap: 12px;
|
|
51
116
|
}
|
|
52
117
|
|
|
53
118
|
.session-card {
|
|
54
|
-
background:
|
|
55
|
-
border: 1px solid
|
|
119
|
+
background: var(--surface);
|
|
120
|
+
border: 1px solid var(--border);
|
|
56
121
|
border-radius: 12px;
|
|
57
122
|
padding: 16px;
|
|
58
123
|
display: flex;
|
|
@@ -60,14 +125,14 @@
|
|
|
60
125
|
gap: 8px;
|
|
61
126
|
text-decoration: none;
|
|
62
127
|
color: inherit;
|
|
63
|
-
transition: transform 0.2s ease;
|
|
128
|
+
transition: transform 0.2s ease, border-color 0.15s, background 0.3s;
|
|
64
129
|
cursor: pointer;
|
|
65
130
|
-webkit-tap-highlight-color: transparent;
|
|
66
131
|
position: relative;
|
|
67
132
|
z-index: 1;
|
|
68
133
|
}
|
|
69
134
|
.session-card:hover {
|
|
70
|
-
border-color:
|
|
135
|
+
border-color: var(--accent);
|
|
71
136
|
}
|
|
72
137
|
|
|
73
138
|
.swipe-wrap {
|
|
@@ -81,7 +146,7 @@
|
|
|
81
146
|
top: 0;
|
|
82
147
|
bottom: 0;
|
|
83
148
|
width: 80px;
|
|
84
|
-
background:
|
|
149
|
+
background: var(--danger);
|
|
85
150
|
display: flex;
|
|
86
151
|
align-items: center;
|
|
87
152
|
justify-content: center;
|
|
@@ -123,22 +188,23 @@
|
|
|
123
188
|
width: 10px;
|
|
124
189
|
height: 10px;
|
|
125
190
|
border-radius: 50%;
|
|
126
|
-
background:
|
|
191
|
+
background: var(--success);
|
|
127
192
|
flex-shrink: 0;
|
|
128
193
|
}
|
|
129
194
|
.session-card .pid {
|
|
130
195
|
font-size: 12px;
|
|
131
|
-
color:
|
|
132
|
-
background:
|
|
196
|
+
color: var(--text-secondary);
|
|
197
|
+
background: var(--bg);
|
|
133
198
|
padding: 2px 8px;
|
|
134
199
|
border-radius: 4px;
|
|
200
|
+
transition: background 0.3s, color 0.3s;
|
|
135
201
|
}
|
|
136
202
|
.session-card .details {
|
|
137
203
|
display: flex;
|
|
138
204
|
flex-wrap: wrap;
|
|
139
205
|
gap: 6px 16px;
|
|
140
206
|
font-size: 13px;
|
|
141
|
-
color:
|
|
207
|
+
color: var(--info);
|
|
142
208
|
}
|
|
143
209
|
.session-card .details span {
|
|
144
210
|
display: flex;
|
|
@@ -147,43 +213,57 @@
|
|
|
147
213
|
}
|
|
148
214
|
.session-card .connect-btn {
|
|
149
215
|
align-self: flex-end;
|
|
150
|
-
background:
|
|
151
|
-
color:
|
|
216
|
+
background: var(--accent);
|
|
217
|
+
color: #ffffff;
|
|
152
218
|
border: none;
|
|
153
219
|
border-radius: 8px;
|
|
154
220
|
padding: 8px 20px;
|
|
155
221
|
font-size: 14px;
|
|
156
222
|
font-weight: 600;
|
|
157
223
|
cursor: pointer;
|
|
224
|
+
transition: background 0.15s, transform 0.1s;
|
|
225
|
+
}
|
|
226
|
+
.session-card .connect-btn:hover {
|
|
227
|
+
background: var(--accent-hover);
|
|
158
228
|
}
|
|
159
229
|
.session-card .connect-btn:active {
|
|
160
|
-
background:
|
|
230
|
+
background: var(--accent-active);
|
|
231
|
+
transform: scale(0.95);
|
|
161
232
|
}
|
|
162
233
|
|
|
163
234
|
.new-session {
|
|
164
|
-
|
|
235
|
+
position: fixed;
|
|
236
|
+
bottom: 16px;
|
|
237
|
+
left: 16px;
|
|
238
|
+
right: 16px;
|
|
165
239
|
padding: 14px;
|
|
166
|
-
background:
|
|
167
|
-
|
|
240
|
+
background: var(--accent);
|
|
241
|
+
color: #ffffff;
|
|
242
|
+
border: none;
|
|
168
243
|
border-radius: 12px;
|
|
169
|
-
color: #888;
|
|
170
244
|
font-size: 15px;
|
|
171
245
|
font-weight: 600;
|
|
172
246
|
cursor: pointer;
|
|
173
247
|
text-align: center;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
248
|
+
z-index: 50;
|
|
249
|
+
transition: background 0.15s, transform 0.1s, box-shadow 0.15s;
|
|
250
|
+
box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3);
|
|
251
|
+
padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px));
|
|
252
|
+
}
|
|
253
|
+
.new-session:hover {
|
|
254
|
+
background: var(--accent-hover);
|
|
255
|
+
box-shadow: 0 4px 12px rgba(0, 120, 212, 0.4);
|
|
177
256
|
}
|
|
178
257
|
.new-session:active {
|
|
179
|
-
|
|
180
|
-
|
|
258
|
+
background: var(--accent-active);
|
|
259
|
+
transform: scale(0.98);
|
|
260
|
+
box-shadow: 0 1px 4px rgba(0, 120, 212, 0.2);
|
|
181
261
|
}
|
|
182
262
|
|
|
183
263
|
.empty-state {
|
|
184
264
|
text-align: center;
|
|
185
265
|
padding: 60px 20px;
|
|
186
|
-
color:
|
|
266
|
+
color: var(--text-muted);
|
|
187
267
|
font-size: 15px;
|
|
188
268
|
}
|
|
189
269
|
|
|
@@ -195,7 +275,7 @@
|
|
|
195
275
|
left: 0;
|
|
196
276
|
right: 0;
|
|
197
277
|
bottom: 0;
|
|
198
|
-
background:
|
|
278
|
+
background: var(--overlay-bg);
|
|
199
279
|
z-index: 100;
|
|
200
280
|
justify-content: center;
|
|
201
281
|
align-items: center;
|
|
@@ -204,11 +284,12 @@
|
|
|
204
284
|
display: flex;
|
|
205
285
|
}
|
|
206
286
|
.modal {
|
|
207
|
-
background:
|
|
287
|
+
background: var(--surface);
|
|
208
288
|
border-radius: 16px;
|
|
209
289
|
width: 90%;
|
|
210
290
|
max-width: 500px;
|
|
211
291
|
padding: 24px 20px;
|
|
292
|
+
transition: background 0.3s;
|
|
212
293
|
}
|
|
213
294
|
.modal h2 {
|
|
214
295
|
font-size: 18px;
|
|
@@ -217,7 +298,7 @@
|
|
|
217
298
|
.modal label {
|
|
218
299
|
display: block;
|
|
219
300
|
font-size: 13px;
|
|
220
|
-
color:
|
|
301
|
+
color: var(--text-secondary);
|
|
221
302
|
margin-bottom: 4px;
|
|
222
303
|
margin-top: 12px;
|
|
223
304
|
}
|
|
@@ -225,14 +306,15 @@
|
|
|
225
306
|
.modal select {
|
|
226
307
|
width: 100%;
|
|
227
308
|
padding: 10px 12px;
|
|
228
|
-
background:
|
|
229
|
-
border: 1px solid
|
|
309
|
+
background: var(--bg);
|
|
310
|
+
border: 1px solid var(--border);
|
|
230
311
|
border-radius: 8px;
|
|
231
|
-
color:
|
|
312
|
+
color: var(--text);
|
|
232
313
|
font-size: 15px;
|
|
233
314
|
outline: none;
|
|
234
315
|
-webkit-appearance: none;
|
|
235
316
|
appearance: none;
|
|
317
|
+
transition: background 0.3s, border-color 0.15s, color 0.3s;
|
|
236
318
|
}
|
|
237
319
|
.modal select {
|
|
238
320
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
@@ -243,7 +325,7 @@
|
|
|
243
325
|
}
|
|
244
326
|
.modal input:focus,
|
|
245
327
|
.modal select:focus {
|
|
246
|
-
border-color:
|
|
328
|
+
border-color: var(--accent);
|
|
247
329
|
}
|
|
248
330
|
.modal-actions {
|
|
249
331
|
display: flex;
|
|
@@ -258,14 +340,24 @@
|
|
|
258
340
|
font-size: 15px;
|
|
259
341
|
font-weight: 600;
|
|
260
342
|
cursor: pointer;
|
|
343
|
+
transition: background 0.15s, transform 0.1s;
|
|
344
|
+
}
|
|
345
|
+
.modal-actions button:active {
|
|
346
|
+
transform: scale(0.95);
|
|
261
347
|
}
|
|
262
348
|
.btn-cancel {
|
|
263
|
-
background:
|
|
264
|
-
color:
|
|
349
|
+
background: var(--border);
|
|
350
|
+
color: var(--text);
|
|
351
|
+
}
|
|
352
|
+
.btn-cancel:hover {
|
|
353
|
+
background: var(--border-subtle);
|
|
265
354
|
}
|
|
266
355
|
.btn-create {
|
|
267
|
-
background:
|
|
268
|
-
color:
|
|
356
|
+
background: var(--accent);
|
|
357
|
+
color: #ffffff;
|
|
358
|
+
}
|
|
359
|
+
.btn-create:hover {
|
|
360
|
+
background: var(--accent-hover);
|
|
269
361
|
}
|
|
270
362
|
|
|
271
363
|
/* Folder browser */
|
|
@@ -277,9 +369,9 @@
|
|
|
277
369
|
flex: 1;
|
|
278
370
|
}
|
|
279
371
|
.cwd-browse-btn {
|
|
280
|
-
background:
|
|
281
|
-
color:
|
|
282
|
-
border: 1px solid
|
|
372
|
+
background: var(--border);
|
|
373
|
+
color: var(--text);
|
|
374
|
+
border: 1px solid var(--border);
|
|
283
375
|
border-radius: 8px;
|
|
284
376
|
padding: 0 14px;
|
|
285
377
|
font-size: 18px;
|
|
@@ -287,9 +379,14 @@
|
|
|
287
379
|
flex-shrink: 0;
|
|
288
380
|
display: flex;
|
|
289
381
|
align-items: center;
|
|
382
|
+
transition: background 0.15s, border-color 0.15s;
|
|
383
|
+
}
|
|
384
|
+
.cwd-browse-btn:hover {
|
|
385
|
+
border-color: var(--accent);
|
|
290
386
|
}
|
|
291
387
|
.cwd-browse-btn:active {
|
|
292
|
-
background:
|
|
388
|
+
background: var(--accent);
|
|
389
|
+
color: #ffffff;
|
|
293
390
|
}
|
|
294
391
|
|
|
295
392
|
.browser-overlay {
|
|
@@ -299,7 +396,7 @@
|
|
|
299
396
|
left: 0;
|
|
300
397
|
right: 0;
|
|
301
398
|
bottom: 0;
|
|
302
|
-
background:
|
|
399
|
+
background: var(--overlay-bg);
|
|
303
400
|
z-index: 200;
|
|
304
401
|
justify-content: center;
|
|
305
402
|
align-items: flex-end;
|
|
@@ -308,7 +405,7 @@
|
|
|
308
405
|
display: flex;
|
|
309
406
|
}
|
|
310
407
|
.browser-sheet {
|
|
311
|
-
background:
|
|
408
|
+
background: var(--surface);
|
|
312
409
|
border-radius: 16px 16px 0 0;
|
|
313
410
|
width: 100%;
|
|
314
411
|
max-width: 500px;
|
|
@@ -316,10 +413,11 @@
|
|
|
316
413
|
display: flex;
|
|
317
414
|
flex-direction: column;
|
|
318
415
|
overflow: hidden;
|
|
416
|
+
transition: background 0.3s;
|
|
319
417
|
}
|
|
320
418
|
.browser-header {
|
|
321
419
|
padding: 16px 16px 12px;
|
|
322
|
-
border-bottom: 1px solid
|
|
420
|
+
border-bottom: 1px solid var(--border);
|
|
323
421
|
display: flex;
|
|
324
422
|
align-items: center;
|
|
325
423
|
justify-content: space-between;
|
|
@@ -331,14 +429,18 @@
|
|
|
331
429
|
.browser-close {
|
|
332
430
|
background: none;
|
|
333
431
|
border: none;
|
|
334
|
-
color:
|
|
432
|
+
color: var(--text-dim);
|
|
335
433
|
font-size: 24px;
|
|
336
434
|
cursor: pointer;
|
|
337
435
|
padding: 0 4px;
|
|
338
436
|
line-height: 1;
|
|
437
|
+
transition: color 0.15s;
|
|
438
|
+
}
|
|
439
|
+
.browser-close:hover {
|
|
440
|
+
color: var(--text);
|
|
339
441
|
}
|
|
340
442
|
.browser-close:active {
|
|
341
|
-
color:
|
|
443
|
+
color: var(--text);
|
|
342
444
|
}
|
|
343
445
|
|
|
344
446
|
.browser-breadcrumb {
|
|
@@ -349,31 +451,32 @@
|
|
|
349
451
|
font-size: 13px;
|
|
350
452
|
overflow-x: auto;
|
|
351
453
|
white-space: nowrap;
|
|
352
|
-
border-bottom: 1px solid
|
|
454
|
+
border-bottom: 1px solid var(--border);
|
|
353
455
|
flex-shrink: 0;
|
|
354
456
|
-webkit-overflow-scrolling: touch;
|
|
355
457
|
}
|
|
356
458
|
.crumb {
|
|
357
459
|
background: none;
|
|
358
460
|
border: none;
|
|
359
|
-
color:
|
|
461
|
+
color: var(--text-dim);
|
|
360
462
|
font-size: 13px;
|
|
361
463
|
cursor: pointer;
|
|
362
464
|
padding: 4px 6px;
|
|
363
465
|
border-radius: 4px;
|
|
364
466
|
flex-shrink: 0;
|
|
467
|
+
transition: background 0.15s, color 0.15s;
|
|
365
468
|
}
|
|
366
469
|
.crumb:active,
|
|
367
470
|
.crumb:hover {
|
|
368
|
-
background:
|
|
369
|
-
color:
|
|
471
|
+
background: var(--border);
|
|
472
|
+
color: var(--text);
|
|
370
473
|
}
|
|
371
474
|
.crumb.current {
|
|
372
|
-
color:
|
|
475
|
+
color: var(--text);
|
|
373
476
|
font-weight: 600;
|
|
374
477
|
}
|
|
375
478
|
.crumb-sep {
|
|
376
|
-
color:
|
|
479
|
+
color: var(--border-subtle);
|
|
377
480
|
flex-shrink: 0;
|
|
378
481
|
}
|
|
379
482
|
|
|
@@ -386,7 +489,7 @@
|
|
|
386
489
|
.browser-empty {
|
|
387
490
|
text-align: center;
|
|
388
491
|
padding: 40px 20px;
|
|
389
|
-
color:
|
|
492
|
+
color: var(--text-muted);
|
|
390
493
|
font-size: 14px;
|
|
391
494
|
}
|
|
392
495
|
.folder-item {
|
|
@@ -395,15 +498,15 @@
|
|
|
395
498
|
gap: 12px;
|
|
396
499
|
padding: 12px 16px;
|
|
397
500
|
cursor: pointer;
|
|
398
|
-
border-bottom: 1px solid rgba(
|
|
501
|
+
border-bottom: 1px solid rgba(60, 60, 60, 0.5);
|
|
399
502
|
transition: background 0.1s;
|
|
400
503
|
-webkit-tap-highlight-color: transparent;
|
|
401
504
|
}
|
|
402
505
|
.folder-item:active {
|
|
403
|
-
background: rgba(
|
|
506
|
+
background: rgba(0, 120, 212, 0.2);
|
|
404
507
|
}
|
|
405
508
|
.folder-item:hover {
|
|
406
|
-
background: rgba(
|
|
509
|
+
background: rgba(0, 120, 212, 0.1);
|
|
407
510
|
}
|
|
408
511
|
.folder-icon {
|
|
409
512
|
font-size: 22px;
|
|
@@ -413,28 +516,28 @@
|
|
|
413
516
|
}
|
|
414
517
|
.folder-name {
|
|
415
518
|
font-size: 15px;
|
|
416
|
-
color:
|
|
519
|
+
color: var(--text);
|
|
417
520
|
flex: 1;
|
|
418
521
|
overflow: hidden;
|
|
419
522
|
text-overflow: ellipsis;
|
|
420
523
|
white-space: nowrap;
|
|
421
524
|
}
|
|
422
525
|
.folder-arrow {
|
|
423
|
-
color:
|
|
526
|
+
color: var(--border-subtle);
|
|
424
527
|
font-size: 18px;
|
|
425
528
|
flex-shrink: 0;
|
|
426
529
|
}
|
|
427
530
|
|
|
428
531
|
.browser-footer {
|
|
429
532
|
padding: 12px 16px calc(env(safe-area-inset-bottom, 8px) + 8px);
|
|
430
|
-
border-top: 1px solid
|
|
533
|
+
border-top: 1px solid var(--border);
|
|
431
534
|
display: flex;
|
|
432
535
|
flex-direction: column;
|
|
433
536
|
gap: 8px;
|
|
434
537
|
}
|
|
435
538
|
.browser-current-path {
|
|
436
539
|
font-size: 12px;
|
|
437
|
-
color:
|
|
540
|
+
color: var(--text-dim);
|
|
438
541
|
overflow: hidden;
|
|
439
542
|
text-overflow: ellipsis;
|
|
440
543
|
white-space: nowrap;
|
|
@@ -442,23 +545,29 @@
|
|
|
442
545
|
.browser-select-btn {
|
|
443
546
|
width: 100%;
|
|
444
547
|
padding: 12px;
|
|
445
|
-
background:
|
|
446
|
-
color:
|
|
548
|
+
background: var(--accent);
|
|
549
|
+
color: #ffffff;
|
|
447
550
|
border: none;
|
|
448
551
|
border-radius: 10px;
|
|
449
552
|
font-size: 16px;
|
|
450
553
|
font-weight: 600;
|
|
451
554
|
cursor: pointer;
|
|
555
|
+
transition: background 0.15s, transform 0.1s;
|
|
556
|
+
}
|
|
557
|
+
.browser-select-btn:hover {
|
|
558
|
+
background: var(--accent-hover);
|
|
452
559
|
}
|
|
453
560
|
.browser-select-btn:active {
|
|
454
|
-
background:
|
|
561
|
+
background: var(--accent-active);
|
|
562
|
+
transform: scale(0.98);
|
|
455
563
|
}
|
|
456
564
|
</style>
|
|
457
565
|
</head>
|
|
458
566
|
<body>
|
|
459
567
|
<div class="header">
|
|
460
|
-
<h1>📡 Term<span>
|
|
461
|
-
<p>Beam your terminal to any device · <span id="version" style="color:
|
|
568
|
+
<h1>📡 Term<span>Beam</span></h1>
|
|
569
|
+
<p>Beam your terminal to any device · <span id="version" style="color: var(--accent)"></span></p>
|
|
570
|
+
<button class="theme-toggle" id="theme-toggle" title="Toggle theme"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button>
|
|
462
571
|
</div>
|
|
463
572
|
|
|
464
573
|
<div class="sessions-list" id="sessions-list"></div>
|
|
@@ -473,7 +582,7 @@
|
|
|
473
582
|
<select id="sess-shell">
|
|
474
583
|
<option value="">Loading shells…</option>
|
|
475
584
|
</select>
|
|
476
|
-
<label for="sess-cmd">Initial Command <span style="color
|
|
585
|
+
<label for="sess-cmd">Initial Command <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
|
|
477
586
|
<input type="text" id="sess-cmd" placeholder="e.g. copilot, htop, vim" />
|
|
478
587
|
<label for="sess-cwd">Working Directory</label>
|
|
479
588
|
<div class="cwd-picker">
|
|
@@ -535,6 +644,23 @@
|
|
|
535
644
|
</div>
|
|
536
645
|
|
|
537
646
|
<script>
|
|
647
|
+
// Theme
|
|
648
|
+
function getTheme() { return localStorage.getItem('termbeam-theme') || 'dark'; }
|
|
649
|
+
function applyTheme(theme) {
|
|
650
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
651
|
+
document.querySelector('meta[name="theme-color"]').content =
|
|
652
|
+
theme === 'light' ? '#f3f3f3' : '#1e1e1e';
|
|
653
|
+
const btn = document.getElementById('theme-toggle');
|
|
654
|
+
if (btn) btn.innerHTML = theme === 'light'
|
|
655
|
+
? '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>'
|
|
656
|
+
: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
|
|
657
|
+
localStorage.setItem('termbeam-theme', theme);
|
|
658
|
+
}
|
|
659
|
+
applyTheme(getTheme());
|
|
660
|
+
document.getElementById('theme-toggle').addEventListener('click', () => {
|
|
661
|
+
applyTheme(getTheme() === 'light' ? 'dark' : 'light');
|
|
662
|
+
});
|
|
663
|
+
|
|
538
664
|
const listEl = document.getElementById('sessions-list');
|
|
539
665
|
const modal = document.getElementById('modal');
|
|
540
666
|
|
package/public/terminal.html
CHANGED
|
@@ -8,13 +8,53 @@
|
|
|
8
8
|
/>
|
|
9
9
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
10
10
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
11
|
-
<meta name="theme-color" content="#
|
|
11
|
+
<meta name="theme-color" content="#1e1e1e" />
|
|
12
12
|
<title>TermBeam — Terminal</title>
|
|
13
13
|
<link
|
|
14
14
|
rel="stylesheet"
|
|
15
15
|
href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css"
|
|
16
16
|
/>
|
|
17
17
|
<style>
|
|
18
|
+
:root {
|
|
19
|
+
--bg: #1e1e1e;
|
|
20
|
+
--surface: #252526;
|
|
21
|
+
--border: #3c3c3c;
|
|
22
|
+
--border-subtle: #474747;
|
|
23
|
+
--text: #d4d4d4;
|
|
24
|
+
--text-secondary: #858585;
|
|
25
|
+
--text-dim: #6e6e6e;
|
|
26
|
+
--text-muted: #555555;
|
|
27
|
+
--accent: #0078d4;
|
|
28
|
+
--accent-hover: #1a8ae8;
|
|
29
|
+
--accent-active: #005a9e;
|
|
30
|
+
--danger: #f14c4c;
|
|
31
|
+
--danger-hover: #d73a3a;
|
|
32
|
+
--success: #89d185;
|
|
33
|
+
--key-bg: #2d2d2d;
|
|
34
|
+
--key-border: #404040;
|
|
35
|
+
--key-shadow: rgba(0,0,0,0.4);
|
|
36
|
+
--overlay-bg: rgba(0,0,0,0.85);
|
|
37
|
+
}
|
|
38
|
+
[data-theme="light"] {
|
|
39
|
+
--bg: #ffffff;
|
|
40
|
+
--surface: #f3f3f3;
|
|
41
|
+
--border: #e0e0e0;
|
|
42
|
+
--border-subtle: #d0d0d0;
|
|
43
|
+
--text: #1e1e1e;
|
|
44
|
+
--text-secondary: #616161;
|
|
45
|
+
--text-dim: #767676;
|
|
46
|
+
--text-muted: #a0a0a0;
|
|
47
|
+
--accent: #0078d4;
|
|
48
|
+
--accent-hover: #106ebe;
|
|
49
|
+
--accent-active: #005a9e;
|
|
50
|
+
--danger: #e51400;
|
|
51
|
+
--danger-hover: #c20000;
|
|
52
|
+
--success: #16825d;
|
|
53
|
+
--key-bg: #e8e8e8;
|
|
54
|
+
--key-border: #d0d0d0;
|
|
55
|
+
--key-shadow: rgba(0,0,0,0.08);
|
|
56
|
+
--overlay-bg: rgba(0,0,0,0.5);
|
|
57
|
+
}
|
|
18
58
|
@font-face {
|
|
19
59
|
font-family: 'NerdFont';
|
|
20
60
|
src: url('https://cdn.jsdelivr.net/gh/ryanoasis/nerd-fonts@latest/patched-fonts/JetBrainsMono/Ligatures/Regular/JetBrainsMonoNerdFont-Regular.ttf')
|
|
@@ -41,74 +81,117 @@
|
|
|
41
81
|
body {
|
|
42
82
|
height: 100%;
|
|
43
83
|
width: 100%;
|
|
44
|
-
background:
|
|
45
|
-
color:
|
|
84
|
+
background: var(--bg);
|
|
85
|
+
color: var(--text);
|
|
46
86
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
47
87
|
overflow: hidden;
|
|
48
88
|
touch-action: manipulation;
|
|
49
|
-
/* Use dvh to account for mobile browser chrome + keyboard */
|
|
50
89
|
height: 100dvh;
|
|
90
|
+
transition: background 0.3s, color 0.3s;
|
|
51
91
|
}
|
|
52
92
|
|
|
53
93
|
#status-bar {
|
|
54
|
-
height:
|
|
94
|
+
height: 40px;
|
|
55
95
|
display: flex;
|
|
56
96
|
align-items: center;
|
|
57
97
|
justify-content: space-between;
|
|
58
|
-
padding: 0
|
|
59
|
-
background:
|
|
60
|
-
border-bottom: 1px solid
|
|
98
|
+
padding: 0 8px;
|
|
99
|
+
background: var(--surface);
|
|
100
|
+
border-bottom: 1px solid var(--border);
|
|
61
101
|
font-size: 13px;
|
|
102
|
+
transition: background 0.3s, border-color 0.3s;
|
|
62
103
|
}
|
|
63
104
|
#status-bar .left {
|
|
64
105
|
display: flex;
|
|
65
106
|
align-items: center;
|
|
66
|
-
gap:
|
|
107
|
+
gap: 6px;
|
|
67
108
|
}
|
|
68
109
|
#status-bar .right {
|
|
69
110
|
display: flex;
|
|
70
111
|
align-items: center;
|
|
71
|
-
gap:
|
|
112
|
+
gap: 4px;
|
|
72
113
|
}
|
|
73
|
-
|
|
114
|
+
.bar-btn {
|
|
74
115
|
background: none;
|
|
75
116
|
border: none;
|
|
76
|
-
color:
|
|
77
|
-
|
|
117
|
+
color: var(--text-dim);
|
|
118
|
+
width: 30px;
|
|
119
|
+
height: 30px;
|
|
120
|
+
border-radius: 8px;
|
|
78
121
|
cursor: pointer;
|
|
79
|
-
|
|
122
|
+
display: flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
justify-content: center;
|
|
125
|
+
transition: background 0.15s, color 0.15s;
|
|
126
|
+
-webkit-tap-highlight-color: transparent;
|
|
127
|
+
}
|
|
128
|
+
.bar-btn:hover {
|
|
129
|
+
background: var(--border);
|
|
130
|
+
color: var(--text);
|
|
131
|
+
}
|
|
132
|
+
.bar-btn:active {
|
|
133
|
+
background: var(--accent);
|
|
134
|
+
color: #ffffff;
|
|
80
135
|
}
|
|
81
|
-
|
|
82
|
-
|
|
136
|
+
.bar-btn svg {
|
|
137
|
+
display: block;
|
|
138
|
+
}
|
|
139
|
+
.bar-group {
|
|
140
|
+
display: flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
background: var(--bg);
|
|
143
|
+
border-radius: 8px;
|
|
144
|
+
padding: 2px;
|
|
145
|
+
gap: 1px;
|
|
146
|
+
transition: background 0.3s;
|
|
147
|
+
}
|
|
148
|
+
.bar-group .bar-btn {
|
|
149
|
+
width: 26px;
|
|
150
|
+
height: 26px;
|
|
151
|
+
border-radius: 6px;
|
|
152
|
+
font-size: 14px;
|
|
153
|
+
font-weight: 600;
|
|
83
154
|
}
|
|
84
155
|
#stop-btn {
|
|
85
|
-
background:
|
|
156
|
+
background: none;
|
|
86
157
|
border: none;
|
|
87
|
-
color:
|
|
88
|
-
|
|
89
|
-
|
|
158
|
+
color: var(--danger);
|
|
159
|
+
height: 30px;
|
|
160
|
+
border-radius: 8px;
|
|
90
161
|
cursor: pointer;
|
|
91
|
-
padding: 4px 10px;
|
|
92
|
-
border-radius: 6px;
|
|
93
162
|
display: flex;
|
|
94
163
|
align-items: center;
|
|
164
|
+
justify-content: center;
|
|
95
165
|
gap: 4px;
|
|
166
|
+
padding: 0 10px;
|
|
167
|
+
font-size: 11px;
|
|
168
|
+
font-weight: 600;
|
|
169
|
+
margin-left: 2px;
|
|
170
|
+
transition: background 0.15s, color 0.15s, transform 0.1s;
|
|
171
|
+
-webkit-tap-highlight-color: transparent;
|
|
172
|
+
}
|
|
173
|
+
#stop-btn:hover {
|
|
174
|
+
background: var(--danger);
|
|
175
|
+
color: #ffffff;
|
|
96
176
|
}
|
|
97
177
|
#stop-btn:active {
|
|
98
|
-
background:
|
|
178
|
+
background: var(--danger-hover);
|
|
179
|
+
color: #ffffff;
|
|
180
|
+
transform: scale(0.9);
|
|
99
181
|
}
|
|
100
182
|
#status-dot {
|
|
101
183
|
width: 8px;
|
|
102
184
|
height: 8px;
|
|
103
185
|
border-radius: 50%;
|
|
104
|
-
background:
|
|
186
|
+
background: var(--danger);
|
|
105
187
|
display: inline-block;
|
|
188
|
+
transition: background 0.3s;
|
|
106
189
|
}
|
|
107
190
|
#status-dot.connected {
|
|
108
|
-
background:
|
|
191
|
+
background: var(--success);
|
|
109
192
|
}
|
|
110
193
|
#status-text {
|
|
111
|
-
color:
|
|
194
|
+
color: var(--text-secondary);
|
|
112
195
|
}
|
|
113
196
|
#session-name {
|
|
114
197
|
font-weight: 600;
|
|
@@ -116,10 +199,10 @@
|
|
|
116
199
|
|
|
117
200
|
#terminal-container {
|
|
118
201
|
position: absolute;
|
|
119
|
-
top:
|
|
202
|
+
top: 40px;
|
|
120
203
|
left: 0;
|
|
121
204
|
right: 0;
|
|
122
|
-
bottom:
|
|
205
|
+
bottom: 52px;
|
|
123
206
|
padding: 2px;
|
|
124
207
|
overflow: hidden;
|
|
125
208
|
}
|
|
@@ -129,46 +212,67 @@
|
|
|
129
212
|
bottom: 0;
|
|
130
213
|
left: 0;
|
|
131
214
|
right: 0;
|
|
132
|
-
height:
|
|
215
|
+
height: 52px;
|
|
133
216
|
display: flex;
|
|
134
217
|
align-items: center;
|
|
135
|
-
background:
|
|
136
|
-
border-top: 1px solid
|
|
137
|
-
padding: 0
|
|
218
|
+
background: var(--surface);
|
|
219
|
+
border-top: 1px solid var(--border);
|
|
220
|
+
padding: 0 4px;
|
|
138
221
|
padding-bottom: env(safe-area-inset-bottom, 0);
|
|
139
|
-
gap:
|
|
140
|
-
overflow-x: auto;
|
|
222
|
+
gap: 4px;
|
|
141
223
|
z-index: 50;
|
|
224
|
+
transition: background 0.3s, border-color 0.3s;
|
|
142
225
|
}
|
|
143
226
|
.key-btn {
|
|
144
|
-
min-width:
|
|
145
|
-
height:
|
|
146
|
-
background:
|
|
147
|
-
color:
|
|
148
|
-
border: 1px solid
|
|
149
|
-
border-radius:
|
|
150
|
-
font-size:
|
|
227
|
+
min-width: 0;
|
|
228
|
+
height: 40px;
|
|
229
|
+
background: var(--key-bg);
|
|
230
|
+
color: var(--text);
|
|
231
|
+
border: 1px solid var(--key-border);
|
|
232
|
+
border-radius: 8px;
|
|
233
|
+
font-size: 12px;
|
|
151
234
|
font-weight: 600;
|
|
152
235
|
cursor: pointer;
|
|
153
236
|
display: flex;
|
|
237
|
+
flex-direction: column;
|
|
154
238
|
align-items: center;
|
|
155
239
|
justify-content: center;
|
|
156
240
|
-webkit-tap-highlight-color: transparent;
|
|
157
241
|
user-select: none;
|
|
158
242
|
white-space: nowrap;
|
|
159
|
-
padding:
|
|
160
|
-
flex
|
|
243
|
+
padding: 2px 8px;
|
|
244
|
+
flex: 1 1 0;
|
|
245
|
+
gap: 0;
|
|
246
|
+
line-height: 1;
|
|
247
|
+
transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.1s, box-shadow 0.15s;
|
|
248
|
+
box-shadow: 0 1px 3px var(--key-shadow), inset 0 1px 0 rgba(255,255,255,0.05);
|
|
249
|
+
}
|
|
250
|
+
.key-btn .hint {
|
|
251
|
+
font-size: 8px;
|
|
252
|
+
font-weight: 400;
|
|
253
|
+
opacity: 0.5;
|
|
254
|
+
margin-top: 1px;
|
|
255
|
+
letter-spacing: 0.02em;
|
|
256
|
+
}
|
|
257
|
+
.key-btn:hover {
|
|
258
|
+
background: var(--border);
|
|
259
|
+
border-color: var(--accent);
|
|
260
|
+
box-shadow: 0 2px 6px var(--key-shadow);
|
|
161
261
|
}
|
|
162
262
|
.key-btn:active {
|
|
163
|
-
background:
|
|
263
|
+
background: var(--accent);
|
|
264
|
+
color: #ffffff;
|
|
265
|
+
border-color: var(--accent);
|
|
266
|
+
transform: scale(0.93);
|
|
267
|
+
box-shadow: none;
|
|
164
268
|
}
|
|
165
269
|
.key-btn.wide {
|
|
166
|
-
|
|
270
|
+
flex: 1.4 1 0;
|
|
167
271
|
}
|
|
168
272
|
.key-sep {
|
|
169
273
|
width: 1px;
|
|
170
274
|
height: 20px;
|
|
171
|
-
background:
|
|
275
|
+
background: var(--border);
|
|
172
276
|
flex-shrink: 0;
|
|
173
277
|
}
|
|
174
278
|
|
|
@@ -186,7 +290,7 @@
|
|
|
186
290
|
left: 0;
|
|
187
291
|
right: 0;
|
|
188
292
|
bottom: 0;
|
|
189
|
-
background:
|
|
293
|
+
background: var(--overlay-bg);
|
|
190
294
|
z-index: 100;
|
|
191
295
|
flex-direction: column;
|
|
192
296
|
align-items: center;
|
|
@@ -198,6 +302,7 @@
|
|
|
198
302
|
}
|
|
199
303
|
#reconnect-overlay .msg {
|
|
200
304
|
font-size: 17px;
|
|
305
|
+
color: #ffffff;
|
|
201
306
|
}
|
|
202
307
|
.overlay-actions {
|
|
203
308
|
display: flex;
|
|
@@ -210,50 +315,60 @@
|
|
|
210
315
|
font-size: 15px;
|
|
211
316
|
font-weight: 600;
|
|
212
317
|
cursor: pointer;
|
|
318
|
+
transition: background 0.15s, transform 0.1s;
|
|
319
|
+
}
|
|
320
|
+
.overlay-actions button:active {
|
|
321
|
+
transform: scale(0.95);
|
|
213
322
|
}
|
|
214
323
|
#reconnect-btn {
|
|
215
|
-
background:
|
|
216
|
-
color:
|
|
324
|
+
background: var(--accent);
|
|
325
|
+
color: #ffffff;
|
|
326
|
+
}
|
|
327
|
+
#reconnect-btn:hover {
|
|
328
|
+
background: var(--accent-hover);
|
|
217
329
|
}
|
|
218
330
|
#back-to-sessions {
|
|
219
|
-
background:
|
|
220
|
-
color: #
|
|
331
|
+
background: rgba(255,255,255,0.15);
|
|
332
|
+
color: #ffffff;
|
|
221
333
|
}
|
|
334
|
+
#back-to-sessions:hover {
|
|
335
|
+
background: rgba(255,255,255,0.25);
|
|
336
|
+
}
|
|
337
|
+
|
|
222
338
|
</style>
|
|
223
339
|
</head>
|
|
224
340
|
<body>
|
|
225
341
|
<div id="status-bar">
|
|
226
342
|
<div class="left">
|
|
227
|
-
<button id="back-btn" onclick="location.href = '/'"
|
|
343
|
+
<button class="bar-btn" id="back-btn" onclick="location.href = '/'" title="Back to sessions"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg></button>
|
|
228
344
|
<span id="status-dot"></span>
|
|
229
345
|
<span id="session-name">…</span>
|
|
230
346
|
</div>
|
|
231
347
|
<div class="right">
|
|
232
348
|
<span id="status-text">Connecting…</span>
|
|
233
|
-
<span id="version-text" style="font-size: 11px; color:
|
|
234
|
-
<
|
|
349
|
+
<span id="version-text" style="font-size: 11px; color: var(--text-muted)"></span>
|
|
350
|
+
<div class="bar-group">
|
|
351
|
+
<button class="bar-btn" id="zoom-out" title="Decrease font size">−</button>
|
|
352
|
+
<button class="bar-btn" id="zoom-in" title="Increase font size">+</button>
|
|
353
|
+
</div>
|
|
354
|
+
<button class="bar-btn" id="theme-toggle" title="Toggle theme"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button>
|
|
355
|
+
<button id="stop-btn" title="Stop session"><svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" stroke="none"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>Stop</button>
|
|
235
356
|
</div>
|
|
236
357
|
</div>
|
|
237
358
|
|
|
238
359
|
<div id="terminal-container"></div>
|
|
239
360
|
|
|
240
361
|
<div id="key-bar">
|
|
241
|
-
<button class="key-btn" data-key="[A"
|
|
242
|
-
<button class="key-btn" data-key="[B"
|
|
243
|
-
<button class="key-btn" data-key="[D">←</button>
|
|
244
|
-
<button class="key-btn" data-key="[C">→</button>
|
|
245
|
-
<
|
|
246
|
-
<button class="key-btn
|
|
247
|
-
<button class="key-btn wide" data-key="
">Enter</button>
|
|
248
|
-
<button class="key-btn" data-key="">Esc</button>
|
|
362
|
+
<button class="key-btn" data-key="[A" title="Previous command">↑<span class="hint">prev</span></button>
|
|
363
|
+
<button class="key-btn" data-key="[B" title="Next command">↓<span class="hint">next</span></button>
|
|
364
|
+
<button class="key-btn" data-key="[D" title="Left">←</button>
|
|
365
|
+
<button class="key-btn" data-key="[C" title="Right">→</button>
|
|
366
|
+
<button class="key-btn" data-key="[H" title="Home">Home</button>
|
|
367
|
+
<button class="key-btn" data-key="[F" title="End">End</button>
|
|
249
368
|
<div class="key-sep"></div>
|
|
250
|
-
<button class="key-btn" data-key="&#
|
|
251
|
-
<button class="key-btn" data-key="">^D</button>
|
|
252
|
-
<button class="key-btn" data-key="">^Z</button>
|
|
253
|
-
<button class="key-btn" data-key="">^L</button>
|
|
369
|
+
<button class="key-btn wide" data-key="	" title="Autocomplete">Tab</button>
|
|
254
370
|
<div class="key-sep"></div>
|
|
255
|
-
<button class="key-btn"
|
|
256
|
-
<button class="key-btn" id="zoom-in">A+</button>
|
|
371
|
+
<button class="key-btn" data-key="" title="Interrupt process">^C<span class="hint">stop</span></button>
|
|
257
372
|
</div>
|
|
258
373
|
|
|
259
374
|
<div id="reconnect-overlay">
|
|
@@ -277,6 +392,50 @@
|
|
|
277
392
|
const statusText = document.getElementById('status-text');
|
|
278
393
|
const sessionName = document.getElementById('session-name');
|
|
279
394
|
const reconnectOverlay = document.getElementById('reconnect-overlay');
|
|
395
|
+
let sessionExited = false;
|
|
396
|
+
let reconnectTimer = null;
|
|
397
|
+
let reconnectDelay = 3000;
|
|
398
|
+
const MAX_RECONNECT_DELAY = 30000;
|
|
399
|
+
|
|
400
|
+
// Terminal themes
|
|
401
|
+
const darkTermTheme = {
|
|
402
|
+
background: '#1e1e1e', foreground: '#d4d4d4', cursor: '#aeafad', cursorAccent: '#1e1e1e',
|
|
403
|
+
selectionBackground: 'rgba(38, 79, 120, 0.5)',
|
|
404
|
+
black: '#000000', red: '#cd3131', green: '#0dbc79', yellow: '#e5e510',
|
|
405
|
+
blue: '#2472c8', magenta: '#bc3fbc', cyan: '#11a8cd', white: '#e5e5e5',
|
|
406
|
+
brightBlack: '#666666', brightRed: '#f14c4c', brightGreen: '#23d18b',
|
|
407
|
+
brightYellow: '#f5f543', brightBlue: '#3b8eea', brightMagenta: '#d670d6',
|
|
408
|
+
brightCyan: '#29b8db', brightWhite: '#e5e5e5',
|
|
409
|
+
};
|
|
410
|
+
const lightTermTheme = {
|
|
411
|
+
background: '#ffffff', foreground: '#1e1e1e', cursor: '#000000', cursorAccent: '#ffffff',
|
|
412
|
+
selectionBackground: 'rgba(0, 120, 215, 0.3)',
|
|
413
|
+
black: '#000000', red: '#cd3131', green: '#00bc7c', yellow: '#949800',
|
|
414
|
+
blue: '#0451a5', magenta: '#bc05bc', cyan: '#0598bc', white: '#555555',
|
|
415
|
+
brightBlack: '#666666', brightRed: '#cd3131', brightGreen: '#14ce14',
|
|
416
|
+
brightYellow: '#b5ba00', brightBlue: '#0451a5', brightMagenta: '#bc05bc',
|
|
417
|
+
brightCyan: '#0598bc', brightWhite: '#a5a5a5',
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
function getTheme() { return localStorage.getItem('termbeam-theme') || 'dark'; }
|
|
421
|
+
function applyTheme(theme) {
|
|
422
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
423
|
+
document.querySelector('meta[name="theme-color"]').content =
|
|
424
|
+
theme === 'light' ? '#f3f3f3' : '#1e1e1e';
|
|
425
|
+
const btn = document.getElementById('theme-toggle');
|
|
426
|
+
if (btn) btn.innerHTML = theme === 'light'
|
|
427
|
+
? '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>'
|
|
428
|
+
: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
|
|
429
|
+
localStorage.setItem('termbeam-theme', theme);
|
|
430
|
+
if (window._term) {
|
|
431
|
+
window._term.options.theme = theme === 'light' ? lightTermTheme : darkTermTheme;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
applyTheme(getTheme());
|
|
435
|
+
|
|
436
|
+
document.getElementById('theme-toggle').addEventListener('click', () => {
|
|
437
|
+
applyTheme(getTheme() === 'light' ? 'dark' : 'light');
|
|
438
|
+
});
|
|
280
439
|
|
|
281
440
|
// Load Nerd Font, then init terminal
|
|
282
441
|
const nerdFont = new FontFace(
|
|
@@ -307,32 +466,11 @@
|
|
|
307
466
|
fontWeightBold: 'bold',
|
|
308
467
|
letterSpacing: 0,
|
|
309
468
|
lineHeight: 1.1,
|
|
310
|
-
theme:
|
|
311
|
-
background: '#1a1a2e',
|
|
312
|
-
foreground: '#e0e0e0',
|
|
313
|
-
cursor: '#533483',
|
|
314
|
-
cursorAccent: '#1a1a2e',
|
|
315
|
-
selectionBackground: 'rgba(83, 52, 131, 0.4)',
|
|
316
|
-
black: '#1a1a2e',
|
|
317
|
-
red: '#e74c3c',
|
|
318
|
-
green: '#2ecc71',
|
|
319
|
-
yellow: '#f1c40f',
|
|
320
|
-
blue: '#3498db',
|
|
321
|
-
magenta: '#9b59b6',
|
|
322
|
-
cyan: '#1abc9c',
|
|
323
|
-
white: '#ecf0f1',
|
|
324
|
-
brightBlack: '#636e72',
|
|
325
|
-
brightRed: '#ff6b6b',
|
|
326
|
-
brightGreen: '#55efc4',
|
|
327
|
-
brightYellow: '#ffeaa7',
|
|
328
|
-
brightBlue: '#74b9ff',
|
|
329
|
-
brightMagenta: '#a29bfe',
|
|
330
|
-
brightCyan: '#81ecec',
|
|
331
|
-
brightWhite: '#ffffff',
|
|
332
|
-
},
|
|
469
|
+
theme: getTheme() === 'light' ? lightTermTheme : darkTermTheme,
|
|
333
470
|
allowProposedApi: true,
|
|
334
471
|
scrollback: 10000,
|
|
335
472
|
});
|
|
473
|
+
window._term = term;
|
|
336
474
|
|
|
337
475
|
const fitAddon = new window.FitAddon.FitAddon();
|
|
338
476
|
const webLinksAddon = new window.WebLinksAddon.WebLinksAddon();
|
|
@@ -351,8 +489,9 @@
|
|
|
351
489
|
|
|
352
490
|
ws.onopen = () => {
|
|
353
491
|
statusDot.className = 'connected';
|
|
354
|
-
statusText.textContent = '
|
|
492
|
+
statusText.textContent = '';
|
|
355
493
|
reconnectOverlay.classList.remove('visible');
|
|
494
|
+
reconnectDelay = 3000;
|
|
356
495
|
// Attach to session
|
|
357
496
|
ws.send(JSON.stringify({ type: 'attach', sessionId }));
|
|
358
497
|
};
|
|
@@ -369,6 +508,8 @@
|
|
|
369
508
|
ws.send(JSON.stringify({ type: 'resize', cols: dims.cols, rows: dims.rows }));
|
|
370
509
|
}
|
|
371
510
|
} else if (msg.type === 'exit') {
|
|
511
|
+
sessionExited = true;
|
|
512
|
+
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
372
513
|
statusText.textContent = `Exited (code ${msg.code})`;
|
|
373
514
|
statusDot.className = '';
|
|
374
515
|
reconnectOverlay.querySelector('.msg').textContent =
|
|
@@ -388,6 +529,9 @@
|
|
|
388
529
|
statusDot.className = '';
|
|
389
530
|
statusText.textContent = 'Disconnected';
|
|
390
531
|
reconnectOverlay.classList.add('visible');
|
|
532
|
+
if (!sessionExited) {
|
|
533
|
+
scheduleReconnect();
|
|
534
|
+
}
|
|
391
535
|
};
|
|
392
536
|
|
|
393
537
|
ws.onerror = () => {
|
|
@@ -415,7 +559,7 @@
|
|
|
415
559
|
window.addEventListener('resize', doResize);
|
|
416
560
|
screen.orientation?.addEventListener('change', () => setTimeout(doResize, 150));
|
|
417
561
|
|
|
418
|
-
// Key bar
|
|
562
|
+
// Key bar
|
|
419
563
|
// Use mousedown + preventDefault to stop buttons from stealing focus/opening keyboard
|
|
420
564
|
document.getElementById('key-bar').addEventListener('mousedown', (e) => {
|
|
421
565
|
// Only prevent default on buttons, not the scrollable bar itself
|
|
@@ -429,34 +573,43 @@
|
|
|
429
573
|
.addEventListener('touchstart', () => {}, { passive: true });
|
|
430
574
|
document.getElementById('key-bar').addEventListener('click', (e) => {
|
|
431
575
|
const btn = e.target.closest('.key-btn');
|
|
432
|
-
if (!btn
|
|
576
|
+
if (!btn) return;
|
|
433
577
|
if (ws && ws.readyState === 1) {
|
|
434
578
|
ws.send(JSON.stringify({ type: 'input', data: btn.dataset.key }));
|
|
435
579
|
}
|
|
436
580
|
// Don't call term.focus() here — it opens the soft keyboard
|
|
437
581
|
});
|
|
438
582
|
|
|
439
|
-
// Zoom
|
|
440
|
-
const MIN_FONT = 2,
|
|
441
|
-
|
|
442
|
-
let fontSize = parseInt(localStorage.getItem('termbeam-fontsize') || '8', 10);
|
|
443
|
-
|
|
583
|
+
// Zoom (status bar)
|
|
584
|
+
const MIN_FONT = 2, MAX_FONT = 28;
|
|
585
|
+
let fontSize = savedFontSize;
|
|
444
586
|
function applyZoom(size) {
|
|
445
587
|
fontSize = Math.max(MIN_FONT, Math.min(MAX_FONT, size));
|
|
446
588
|
term.options.fontSize = fontSize;
|
|
447
589
|
localStorage.setItem('termbeam-fontsize', fontSize);
|
|
448
590
|
doResize();
|
|
449
591
|
}
|
|
450
|
-
|
|
451
|
-
document.getElementById('zoom-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
592
|
+
document.getElementById('zoom-in').addEventListener('click', () => applyZoom(fontSize + 2));
|
|
593
|
+
document.getElementById('zoom-out').addEventListener('click', () => applyZoom(fontSize - 2));
|
|
594
|
+
|
|
595
|
+
function scheduleReconnect() {
|
|
596
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
597
|
+
const seconds = Math.round(reconnectDelay / 1000);
|
|
598
|
+
reconnectOverlay.querySelector('.msg').textContent =
|
|
599
|
+
`Disconnected — reconnecting in ${seconds}s…`;
|
|
600
|
+
reconnectTimer = setTimeout(() => {
|
|
601
|
+
reconnectTimer = null;
|
|
602
|
+
reconnectOverlay.querySelector('.msg').textContent = 'Reconnecting…';
|
|
603
|
+
reconnectDelay = Math.min(reconnectDelay * 1.5, MAX_RECONNECT_DELAY);
|
|
604
|
+
connect();
|
|
605
|
+
}, reconnectDelay);
|
|
606
|
+
}
|
|
457
607
|
|
|
458
608
|
// Reconnect
|
|
459
609
|
document.getElementById('reconnect-btn').addEventListener('click', () => {
|
|
610
|
+
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
611
|
+
sessionExited = false;
|
|
612
|
+
reconnectDelay = 3000;
|
|
460
613
|
term.clear();
|
|
461
614
|
connect();
|
|
462
615
|
});
|
|
@@ -486,11 +639,11 @@
|
|
|
486
639
|
if (keyboardHeight > 50) {
|
|
487
640
|
// Keyboard is open — move key bar above it
|
|
488
641
|
keyBar.style.bottom = keyboardHeight + 'px';
|
|
489
|
-
container.style.bottom =
|
|
642
|
+
container.style.bottom = 52 + keyboardHeight + 'px';
|
|
490
643
|
} else {
|
|
491
644
|
// Keyboard closed
|
|
492
645
|
keyBar.style.bottom = '0px';
|
|
493
|
-
container.style.bottom = '
|
|
646
|
+
container.style.bottom = '52px';
|
|
494
647
|
}
|
|
495
648
|
// Refit terminal to new available space
|
|
496
649
|
setTimeout(() => doResize(), 50);
|