termbeam 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/package.json +2 -2
- package/public/index.html +326 -33
- package/public/terminal.html +1493 -113
- package/src/auth.js +96 -12
- package/src/routes.js +6 -2
- package/src/sessions.js +15 -3
- package/src/websocket.js +31 -4
package/README.md
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# TermBeam
|
|
2
4
|
|
|
3
5
|
**Beam your terminal to any device.**
|
|
4
6
|
|
|
5
7
|
[](https://www.npmjs.com/package/termbeam)
|
|
8
|
+
[](https://www.npmjs.com/package/termbeam)
|
|
6
9
|
[](https://github.com/dorlugasigal/TermBeam/actions/workflows/ci.yml)
|
|
7
10
|
[](https://github.com/dorlugasigal/TermBeam/actions/workflows/ci.yml)
|
|
11
|
+
[](https://nodejs.org/)
|
|
8
12
|
[](https://opensource.org/licenses/MIT)
|
|
9
13
|
|
|
14
|
+
</div>
|
|
15
|
+
|
|
10
16
|
TermBeam lets you access your terminal from a phone, tablet, or any browser — no SSH, no port forwarding, no config files. Run one command and scan the QR code.
|
|
11
17
|
|
|
12
18
|
I built this because I kept needing to run quick commands on my dev machine while away from my desk, and SSH on a phone is painful. TermBeam gives you a real terminal with a touch-friendly UI that actually works on small screens.
|
|
13
19
|
|
|
14
|
-
[Full documentation](https://dorlugasigal.github.io/TermBeam/)
|
|
20
|
+
[Full documentation](https://dorlugasigal.github.io/TermBeam/) · [Website](https://termbeam.pages.dev)
|
|
15
21
|
|
|
16
22
|
https://github.com/user-attachments/assets/9dd4f3d7-f017-4314-9b3a-f6a5688e3671
|
|
17
23
|
|
|
@@ -73,6 +79,9 @@ termbeam --no-password # disable password protection
|
|
|
73
79
|
- **Initial command** — optionally launch a session straight into `htop`, `vim`, or any command
|
|
74
80
|
- **Shell detection** — auto-detects your shell on all platforms (PowerShell, cmd, bash, zsh, Git Bash, WSL)
|
|
75
81
|
- **QR code on startup** for instant phone connection
|
|
82
|
+
- **Command completion notifications** — get browser notifications when a command finishes in a background tab; toggle with the bell icon in the toolbar (opt-in, requires browser permission)
|
|
83
|
+
- **Terminal search** — press <kbd>Ctrl+F</kbd> / <kbd>Cmd+F</kbd> to open a search overlay with regex support, powered by xterm.js SearchAddon
|
|
84
|
+
- **Command palette** — press <kbd>Ctrl+K</kbd> / <kbd>Cmd+K</kbd> (or tap the ⚙️ button) to open a slide-out tool panel with categorized actions: Session, Search, View, Share, Notifications, and System
|
|
76
85
|
- **Light/dark theme** with persistent preference
|
|
77
86
|
- **Adjustable font size** via status bar controls, saved across sessions
|
|
78
87
|
- **Port preview** — reverse-proxy a single local web server port and preview it in the browser (HTTP only; no WebSocket/HMR; best with server-rendered apps)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "termbeam",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Beam your terminal to any device — mobile-optimized web terminal with multi-session support",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"author": "Dor Lugasi <dorlugasigal@gmail.com>",
|
|
40
40
|
"license": "MIT",
|
|
41
|
-
"homepage": "https://
|
|
41
|
+
"homepage": "https://termbeam.pages.dev",
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|
|
44
44
|
"url": "https://github.com/dorlugasigal/TermBeam.git"
|
package/public/index.html
CHANGED
|
@@ -55,6 +55,196 @@
|
|
|
55
55
|
--shadow: rgba(0, 0, 0, 0.06);
|
|
56
56
|
--overlay-bg: rgba(0, 0, 0, 0.4);
|
|
57
57
|
}
|
|
58
|
+
[data-theme='monokai'] {
|
|
59
|
+
--bg: #272822;
|
|
60
|
+
--surface: #1e1f1c;
|
|
61
|
+
--border: #49483e;
|
|
62
|
+
--border-subtle: #5c5c4f;
|
|
63
|
+
--text: #f8f8f2;
|
|
64
|
+
--text-secondary: #a59f85;
|
|
65
|
+
--text-dim: #75715e;
|
|
66
|
+
--text-muted: #5a5854;
|
|
67
|
+
--accent: #a6e22e;
|
|
68
|
+
--accent-hover: #b8f53c;
|
|
69
|
+
--accent-active: #8acc16;
|
|
70
|
+
--danger: #f92672;
|
|
71
|
+
--danger-hover: #e0155d;
|
|
72
|
+
--success: #a6e22e;
|
|
73
|
+
--info: #a59f85;
|
|
74
|
+
--shadow: rgba(0, 0, 0, 0.3);
|
|
75
|
+
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
76
|
+
}
|
|
77
|
+
[data-theme='solarized-dark'] {
|
|
78
|
+
--bg: #002b36;
|
|
79
|
+
--surface: #073642;
|
|
80
|
+
--border: #586e75;
|
|
81
|
+
--border-subtle: #657b83;
|
|
82
|
+
--text: #839496;
|
|
83
|
+
--text-secondary: #657b83;
|
|
84
|
+
--text-dim: #586e75;
|
|
85
|
+
--text-muted: #4a5a62;
|
|
86
|
+
--accent: #268bd2;
|
|
87
|
+
--accent-hover: #379ce3;
|
|
88
|
+
--accent-active: #1a7abf;
|
|
89
|
+
--danger: #dc322f;
|
|
90
|
+
--danger-hover: #c8221f;
|
|
91
|
+
--success: #859900;
|
|
92
|
+
--info: #657b83;
|
|
93
|
+
--shadow: rgba(0, 0, 0, 0.25);
|
|
94
|
+
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
95
|
+
}
|
|
96
|
+
[data-theme='solarized-light'] {
|
|
97
|
+
--bg: #fdf6e3;
|
|
98
|
+
--surface: #eee8d5;
|
|
99
|
+
--border: #93a1a1;
|
|
100
|
+
--border-subtle: #839496;
|
|
101
|
+
--text: #657b83;
|
|
102
|
+
--text-secondary: #93a1a1;
|
|
103
|
+
--text-dim: #a0a0a0;
|
|
104
|
+
--text-muted: #b0b0b0;
|
|
105
|
+
--accent: #268bd2;
|
|
106
|
+
--accent-hover: #379ce3;
|
|
107
|
+
--accent-active: #1a7abf;
|
|
108
|
+
--danger: #dc322f;
|
|
109
|
+
--danger-hover: #c8221f;
|
|
110
|
+
--success: #859900;
|
|
111
|
+
--info: #93a1a1;
|
|
112
|
+
--shadow: rgba(0, 0, 0, 0.08);
|
|
113
|
+
--overlay-bg: rgba(0, 0, 0, 0.4);
|
|
114
|
+
}
|
|
115
|
+
[data-theme='nord'] {
|
|
116
|
+
--bg: #2e3440;
|
|
117
|
+
--surface: #3b4252;
|
|
118
|
+
--border: #434c5e;
|
|
119
|
+
--border-subtle: #4c566a;
|
|
120
|
+
--text: #d8dee9;
|
|
121
|
+
--text-secondary: #b0bac9;
|
|
122
|
+
--text-dim: #7b88a1;
|
|
123
|
+
--text-muted: #5c6a85;
|
|
124
|
+
--accent: #88c0d0;
|
|
125
|
+
--accent-hover: #9fd4e4;
|
|
126
|
+
--accent-active: #6aafbf;
|
|
127
|
+
--danger: #bf616a;
|
|
128
|
+
--danger-hover: #a84d57;
|
|
129
|
+
--success: #a3be8c;
|
|
130
|
+
--info: #b0bac9;
|
|
131
|
+
--shadow: rgba(0, 0, 0, 0.2);
|
|
132
|
+
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
133
|
+
}
|
|
134
|
+
[data-theme='dracula'] {
|
|
135
|
+
--bg: #282a36;
|
|
136
|
+
--surface: #343746;
|
|
137
|
+
--border: #44475a;
|
|
138
|
+
--border-subtle: #525568;
|
|
139
|
+
--text: #f8f8f2;
|
|
140
|
+
--text-secondary: #c1c4d2;
|
|
141
|
+
--text-dim: #8e92a4;
|
|
142
|
+
--text-muted: #6272a4;
|
|
143
|
+
--accent: #bd93f9;
|
|
144
|
+
--accent-hover: #d0b0ff;
|
|
145
|
+
--accent-active: #a77de7;
|
|
146
|
+
--danger: #ff5555;
|
|
147
|
+
--danger-hover: #e03d3d;
|
|
148
|
+
--success: #50fa7b;
|
|
149
|
+
--info: #c1c4d2;
|
|
150
|
+
--shadow: rgba(0, 0, 0, 0.25);
|
|
151
|
+
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
152
|
+
}
|
|
153
|
+
[data-theme='github-dark'] {
|
|
154
|
+
--bg: #0d1117;
|
|
155
|
+
--surface: #161b22;
|
|
156
|
+
--border: #30363d;
|
|
157
|
+
--border-subtle: #3d444d;
|
|
158
|
+
--text: #c9d1d9;
|
|
159
|
+
--text-secondary: #8b949e;
|
|
160
|
+
--text-dim: #6e7681;
|
|
161
|
+
--text-muted: #484f58;
|
|
162
|
+
--accent: #58a6ff;
|
|
163
|
+
--accent-hover: #79b8ff;
|
|
164
|
+
--accent-active: #388bfd;
|
|
165
|
+
--danger: #f85149;
|
|
166
|
+
--danger-hover: #da3633;
|
|
167
|
+
--success: #3fb950;
|
|
168
|
+
--info: #8b949e;
|
|
169
|
+
--shadow: rgba(0, 0, 0, 0.3);
|
|
170
|
+
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
171
|
+
}
|
|
172
|
+
[data-theme='one-dark'] {
|
|
173
|
+
--bg: #282c34;
|
|
174
|
+
--surface: #21252b;
|
|
175
|
+
--border: #3e4452;
|
|
176
|
+
--border-subtle: #4b5263;
|
|
177
|
+
--text: #abb2bf;
|
|
178
|
+
--text-secondary: #7f848e;
|
|
179
|
+
--text-dim: #5c6370;
|
|
180
|
+
--text-muted: #4b5263;
|
|
181
|
+
--accent: #61afef;
|
|
182
|
+
--accent-hover: #7dc0ff;
|
|
183
|
+
--accent-active: #4d9ede;
|
|
184
|
+
--danger: #e06c75;
|
|
185
|
+
--danger-hover: #c95c67;
|
|
186
|
+
--success: #98c379;
|
|
187
|
+
--info: #7f848e;
|
|
188
|
+
--shadow: rgba(0, 0, 0, 0.25);
|
|
189
|
+
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
190
|
+
}
|
|
191
|
+
[data-theme='catppuccin'] {
|
|
192
|
+
--bg: #1e1e2e;
|
|
193
|
+
--surface: #313244;
|
|
194
|
+
--border: #45475a;
|
|
195
|
+
--border-subtle: #585b70;
|
|
196
|
+
--text: #cdd6f4;
|
|
197
|
+
--text-secondary: #a6adc8;
|
|
198
|
+
--text-dim: #7f849c;
|
|
199
|
+
--text-muted: #585b70;
|
|
200
|
+
--accent: #89b4fa;
|
|
201
|
+
--accent-hover: #b4d0ff;
|
|
202
|
+
--accent-active: #5c9de3;
|
|
203
|
+
--danger: #f38ba8;
|
|
204
|
+
--danger-hover: #eb7c9d;
|
|
205
|
+
--success: #a6e3a1;
|
|
206
|
+
--info: #a6adc8;
|
|
207
|
+
--shadow: rgba(0, 0, 0, 0.2);
|
|
208
|
+
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
209
|
+
}
|
|
210
|
+
[data-theme='gruvbox'] {
|
|
211
|
+
--bg: #282828;
|
|
212
|
+
--surface: #3c3836;
|
|
213
|
+
--border: #504945;
|
|
214
|
+
--border-subtle: #665c54;
|
|
215
|
+
--text: #ebdbb2;
|
|
216
|
+
--text-secondary: #d5c4a1;
|
|
217
|
+
--text-dim: #a89984;
|
|
218
|
+
--text-muted: #7c6f64;
|
|
219
|
+
--accent: #83a598;
|
|
220
|
+
--accent-hover: #9dbfb4;
|
|
221
|
+
--accent-active: #6a8f8a;
|
|
222
|
+
--danger: #fb4934;
|
|
223
|
+
--danger-hover: #e33826;
|
|
224
|
+
--success: #b8bb26;
|
|
225
|
+
--info: #d5c4a1;
|
|
226
|
+
--shadow: rgba(0, 0, 0, 0.25);
|
|
227
|
+
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
228
|
+
}
|
|
229
|
+
[data-theme='night-owl'] {
|
|
230
|
+
--bg: #011627;
|
|
231
|
+
--surface: #0d2a45;
|
|
232
|
+
--border: #1d3b53;
|
|
233
|
+
--border-subtle: #264863;
|
|
234
|
+
--text: #d6deeb;
|
|
235
|
+
--text-secondary: #8badc1;
|
|
236
|
+
--text-dim: #5f7e97;
|
|
237
|
+
--text-muted: #3f5f7d;
|
|
238
|
+
--accent: #7fdbca;
|
|
239
|
+
--accent-hover: #9ff0e0;
|
|
240
|
+
--accent-active: #62c5b5;
|
|
241
|
+
--danger: #ef5350;
|
|
242
|
+
--danger-hover: #d83130;
|
|
243
|
+
--success: #addb67;
|
|
244
|
+
--info: #8badc1;
|
|
245
|
+
--shadow: rgba(0, 0, 0, 0.3);
|
|
246
|
+
--overlay-bg: rgba(0, 0, 0, 0.75);
|
|
247
|
+
}
|
|
58
248
|
* {
|
|
59
249
|
margin: 0;
|
|
60
250
|
padding: 0;
|
|
@@ -116,10 +306,12 @@
|
|
|
116
306
|
border-color: var(--border-subtle);
|
|
117
307
|
background: var(--border);
|
|
118
308
|
}
|
|
119
|
-
.theme-
|
|
309
|
+
.theme-wrap {
|
|
120
310
|
position: absolute;
|
|
121
311
|
top: 16px;
|
|
122
312
|
right: 16px;
|
|
313
|
+
}
|
|
314
|
+
.theme-toggle {
|
|
123
315
|
background: none;
|
|
124
316
|
border: 1px solid var(--border);
|
|
125
317
|
color: var(--text-dim);
|
|
@@ -142,6 +334,47 @@
|
|
|
142
334
|
border-color: var(--border-subtle);
|
|
143
335
|
background: var(--border);
|
|
144
336
|
}
|
|
337
|
+
.theme-picker {
|
|
338
|
+
display: none;
|
|
339
|
+
position: absolute;
|
|
340
|
+
top: calc(100% + 4px);
|
|
341
|
+
right: 0;
|
|
342
|
+
background: var(--surface);
|
|
343
|
+
border: 1px solid var(--border);
|
|
344
|
+
border-radius: 8px;
|
|
345
|
+
min-width: 160px;
|
|
346
|
+
padding: 4px 0;
|
|
347
|
+
z-index: 200;
|
|
348
|
+
box-shadow: 0 4px 12px var(--shadow);
|
|
349
|
+
}
|
|
350
|
+
.theme-picker.open {
|
|
351
|
+
display: block;
|
|
352
|
+
}
|
|
353
|
+
.theme-option {
|
|
354
|
+
display: flex;
|
|
355
|
+
align-items: center;
|
|
356
|
+
gap: 8px;
|
|
357
|
+
padding: 7px 12px;
|
|
358
|
+
cursor: pointer;
|
|
359
|
+
font-size: 13px;
|
|
360
|
+
color: var(--text);
|
|
361
|
+
transition: background 0.1s;
|
|
362
|
+
white-space: nowrap;
|
|
363
|
+
}
|
|
364
|
+
.theme-option:hover {
|
|
365
|
+
background: var(--border);
|
|
366
|
+
}
|
|
367
|
+
.theme-option.active {
|
|
368
|
+
color: var(--accent);
|
|
369
|
+
}
|
|
370
|
+
.theme-swatch {
|
|
371
|
+
width: 14px;
|
|
372
|
+
height: 14px;
|
|
373
|
+
border-radius: 50%;
|
|
374
|
+
display: inline-block;
|
|
375
|
+
flex-shrink: 0;
|
|
376
|
+
border: 1px solid rgba(128, 128, 128, 0.3);
|
|
377
|
+
}
|
|
145
378
|
|
|
146
379
|
.sessions-list {
|
|
147
380
|
padding: 16px;
|
|
@@ -691,28 +924,66 @@
|
|
|
691
924
|
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
|
|
692
925
|
</svg>
|
|
693
926
|
</button>
|
|
694
|
-
<
|
|
695
|
-
<
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
</
|
|
715
|
-
|
|
927
|
+
<div class="theme-wrap" id="theme-wrap">
|
|
928
|
+
<button class="theme-toggle" id="theme-toggle" title="Switch theme">
|
|
929
|
+
<svg
|
|
930
|
+
width="16"
|
|
931
|
+
height="16"
|
|
932
|
+
viewBox="0 0 24 24"
|
|
933
|
+
fill="none"
|
|
934
|
+
stroke="currentColor"
|
|
935
|
+
stroke-width="2"
|
|
936
|
+
stroke-linecap="round"
|
|
937
|
+
stroke-linejoin="round"
|
|
938
|
+
>
|
|
939
|
+
<circle cx="13.5" cy="6.5" r=".5" fill="currentColor" />
|
|
940
|
+
<circle cx="17.5" cy="10.5" r=".5" fill="currentColor" />
|
|
941
|
+
<circle cx="8.5" cy="7.5" r=".5" fill="currentColor" />
|
|
942
|
+
<circle cx="6.5" cy="12.5" r=".5" fill="currentColor" />
|
|
943
|
+
<path
|
|
944
|
+
d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"
|
|
945
|
+
/>
|
|
946
|
+
</svg>
|
|
947
|
+
</button>
|
|
948
|
+
<div class="theme-picker" id="theme-picker">
|
|
949
|
+
<div class="theme-option" data-theme-option="dark">
|
|
950
|
+
<span class="theme-swatch" style="background: #1e1e1e"></span>Dark
|
|
951
|
+
</div>
|
|
952
|
+
<div class="theme-option" data-theme-option="light">
|
|
953
|
+
<span class="theme-swatch" style="background: #ffffff"></span>Light
|
|
954
|
+
</div>
|
|
955
|
+
<div class="theme-option" data-theme-option="monokai">
|
|
956
|
+
<span class="theme-swatch" style="background: #272822"></span>Monokai
|
|
957
|
+
</div>
|
|
958
|
+
<div class="theme-option" data-theme-option="solarized-dark">
|
|
959
|
+
<span class="theme-swatch" style="background: #002b36"></span>Solarized Dark
|
|
960
|
+
</div>
|
|
961
|
+
<div class="theme-option" data-theme-option="solarized-light">
|
|
962
|
+
<span class="theme-swatch" style="background: #fdf6e3"></span>Solarized Light
|
|
963
|
+
</div>
|
|
964
|
+
<div class="theme-option" data-theme-option="nord">
|
|
965
|
+
<span class="theme-swatch" style="background: #2e3440"></span>Nord
|
|
966
|
+
</div>
|
|
967
|
+
<div class="theme-option" data-theme-option="dracula">
|
|
968
|
+
<span class="theme-swatch" style="background: #282a36"></span>Dracula
|
|
969
|
+
</div>
|
|
970
|
+
<div class="theme-option" data-theme-option="github-dark">
|
|
971
|
+
<span class="theme-swatch" style="background: #0d1117"></span>GitHub Dark
|
|
972
|
+
</div>
|
|
973
|
+
<div class="theme-option" data-theme-option="one-dark">
|
|
974
|
+
<span class="theme-swatch" style="background: #282c34"></span>One Dark
|
|
975
|
+
</div>
|
|
976
|
+
<div class="theme-option" data-theme-option="catppuccin">
|
|
977
|
+
<span class="theme-swatch" style="background: #1e1e2e"></span>Catppuccin
|
|
978
|
+
</div>
|
|
979
|
+
<div class="theme-option" data-theme-option="gruvbox">
|
|
980
|
+
<span class="theme-swatch" style="background: #282828"></span>Gruvbox
|
|
981
|
+
</div>
|
|
982
|
+
<div class="theme-option" data-theme-option="night-owl">
|
|
983
|
+
<span class="theme-swatch" style="background: #011627"></span>Night Owl
|
|
984
|
+
</div>
|
|
985
|
+
</div>
|
|
986
|
+
</div>
|
|
716
987
|
</div>
|
|
717
988
|
|
|
718
989
|
<div class="sessions-list" id="sessions-list"></div>
|
|
@@ -852,24 +1123,46 @@
|
|
|
852
1123
|
|
|
853
1124
|
<script>
|
|
854
1125
|
// Theme
|
|
1126
|
+
const THEMES = [
|
|
1127
|
+
{ id: 'dark', name: 'Dark', bg: '#1e1e1e' },
|
|
1128
|
+
{ id: 'light', name: 'Light', bg: '#f3f3f3' },
|
|
1129
|
+
{ id: 'monokai', name: 'Monokai', bg: '#272822' },
|
|
1130
|
+
{ id: 'solarized-dark', name: 'Solarized Dark', bg: '#002b36' },
|
|
1131
|
+
{ id: 'solarized-light', name: 'Solarized Light', bg: '#fdf6e3' },
|
|
1132
|
+
{ id: 'nord', name: 'Nord', bg: '#2e3440' },
|
|
1133
|
+
{ id: 'dracula', name: 'Dracula', bg: '#282a36' },
|
|
1134
|
+
{ id: 'github-dark', name: 'GitHub Dark', bg: '#0d1117' },
|
|
1135
|
+
{ id: 'one-dark', name: 'One Dark', bg: '#282c34' },
|
|
1136
|
+
{ id: 'catppuccin', name: 'Catppuccin', bg: '#1e1e2e' },
|
|
1137
|
+
{ id: 'gruvbox', name: 'Gruvbox', bg: '#282828' },
|
|
1138
|
+
{ id: 'night-owl', name: 'Night Owl', bg: '#011627' },
|
|
1139
|
+
];
|
|
855
1140
|
function getTheme() {
|
|
856
1141
|
return localStorage.getItem('termbeam-theme') || 'dark';
|
|
857
1142
|
}
|
|
858
1143
|
function applyTheme(theme) {
|
|
859
1144
|
document.documentElement.setAttribute('data-theme', theme);
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
const btn = document.getElementById('theme-toggle');
|
|
863
|
-
if (btn)
|
|
864
|
-
btn.innerHTML =
|
|
865
|
-
theme === 'light'
|
|
866
|
-
? '<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>'
|
|
867
|
-
: '<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>';
|
|
1145
|
+
const t = THEMES.find((x) => x.id === theme) || THEMES[0];
|
|
1146
|
+
document.querySelector('meta[name="theme-color"]').content = t.bg;
|
|
868
1147
|
localStorage.setItem('termbeam-theme', theme);
|
|
1148
|
+
document.querySelectorAll('.theme-option').forEach((el) => {
|
|
1149
|
+
el.classList.toggle('active', el.dataset.themeOption === theme);
|
|
1150
|
+
});
|
|
869
1151
|
}
|
|
870
1152
|
applyTheme(getTheme());
|
|
871
|
-
document.getElementById('theme-toggle').addEventListener('click', () => {
|
|
872
|
-
|
|
1153
|
+
document.getElementById('theme-toggle').addEventListener('click', (e) => {
|
|
1154
|
+
e.stopPropagation();
|
|
1155
|
+
document.getElementById('theme-picker').classList.toggle('open');
|
|
1156
|
+
});
|
|
1157
|
+
document.addEventListener('click', () => {
|
|
1158
|
+
document.getElementById('theme-picker').classList.remove('open');
|
|
1159
|
+
});
|
|
1160
|
+
document.querySelectorAll('.theme-option').forEach((el) => {
|
|
1161
|
+
el.addEventListener('click', (e) => {
|
|
1162
|
+
e.stopPropagation();
|
|
1163
|
+
applyTheme(el.dataset.themeOption);
|
|
1164
|
+
document.getElementById('theme-picker').classList.remove('open');
|
|
1165
|
+
});
|
|
873
1166
|
});
|
|
874
1167
|
|
|
875
1168
|
const listEl = document.getElementById('sessions-list');
|