termbeam 1.5.0 → 1.8.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.
@@ -15,6 +15,7 @@
15
15
  />
16
16
  <link rel="manifest" href="/manifest.json" />
17
17
  <link rel="apple-touch-icon" href="/icons/icon-192.png" />
18
+ <link rel="stylesheet" href="/css/themes.css" />
18
19
  <title>TermBeam — Terminal</title>
19
20
  <link
20
21
  rel="stylesheet"
@@ -22,257 +23,23 @@
22
23
  />
23
24
  <style>
24
25
  :root {
25
- --bg: #1e1e1e;
26
- --surface: #252526;
27
- --border: #3c3c3c;
28
- --border-subtle: #474747;
29
- --text: #d4d4d4;
30
- --text-secondary: #858585;
31
- --text-dim: #6e6e6e;
32
- --text-muted: #555555;
33
- --accent: #0078d4;
34
- --accent-hover: #1a8ae8;
35
- --accent-active: #005a9e;
36
- --danger: #f14c4c;
37
- --danger-hover: #d73a3a;
38
- --success: #89d185;
39
26
  --key-bg: #4a4a4c;
40
27
  --key-border: #5a5a5c;
41
28
  --key-shadow: rgba(0, 0, 0, 0.5);
42
29
  --key-special-bg: #333335;
43
30
  --overlay-bg: rgba(0, 0, 0, 0.85);
44
31
  }
45
- [data-theme='light'] {
46
- --bg: #ffffff;
47
- --surface: #f3f3f3;
48
- --border: #e0e0e0;
49
- --border-subtle: #d0d0d0;
50
- --text: #1e1e1e;
51
- --text-secondary: #616161;
52
- --text-dim: #767676;
53
- --text-muted: #a0a0a0;
54
- --accent: #0078d4;
55
- --accent-hover: #106ebe;
56
- --accent-active: #005a9e;
57
- --danger: #e51400;
58
- --danger-hover: #c20000;
59
- --success: #16825d;
60
- --key-bg: #ffffff;
61
- --key-border: #b5b5b5;
62
- --key-shadow: rgba(0, 0, 0, 0.12);
63
- --key-special-bg: #adb5bd;
64
- --overlay-bg: rgba(0, 0, 0, 0.5);
65
- }
66
- [data-theme='monokai'] {
67
- --bg: #272822;
68
- --surface: #1e1f1c;
69
- --border: #49483e;
70
- --border-subtle: #5c5c4f;
71
- --text: #f8f8f2;
72
- --text-secondary: #a59f85;
73
- --text-dim: #75715e;
74
- --text-muted: #5a5854;
75
- --accent: #a6e22e;
76
- --accent-hover: #b8f53c;
77
- --accent-active: #8acc16;
78
- --danger: #f92672;
79
- --danger-hover: #e0155d;
80
- --success: #a6e22e;
81
- --key-bg: #49483e;
82
- --key-border: #5c5c4f;
83
- --key-shadow: rgba(0, 0, 0, 0.4);
84
- --key-special-bg: #3e3d32;
85
- --overlay-bg: rgba(0, 0, 0, 0.75);
86
- }
87
- [data-theme='solarized-dark'] {
88
- --bg: #002b36;
89
- --surface: #073642;
90
- --border: #586e75;
91
- --border-subtle: #657b83;
92
- --text: #839496;
93
- --text-secondary: #657b83;
94
- --text-dim: #586e75;
95
- --text-muted: #4a5a62;
96
- --accent: #268bd2;
97
- --accent-hover: #379ce3;
98
- --accent-active: #1a7abf;
99
- --danger: #dc322f;
100
- --danger-hover: #c8221f;
101
- --success: #859900;
102
- --key-bg: #073642;
103
- --key-border: #586e75;
104
- --key-shadow: rgba(0, 0, 0, 0.3);
105
- --key-special-bg: #002b36;
106
- --overlay-bg: rgba(0, 0, 0, 0.75);
107
- }
108
- [data-theme='solarized-light'] {
109
- --bg: #fdf6e3;
110
- --surface: #eee8d5;
111
- --border: #93a1a1;
112
- --border-subtle: #839496;
113
- --text: #657b83;
114
- --text-secondary: #93a1a1;
115
- --text-dim: #a0a0a0;
116
- --text-muted: #b0b0b0;
117
- --accent: #268bd2;
118
- --accent-hover: #379ce3;
119
- --accent-active: #1a7abf;
120
- --danger: #dc322f;
121
- --danger-hover: #c8221f;
122
- --success: #859900;
123
- --key-bg: #ffffff;
124
- --key-border: #b5b5b5;
125
- --key-shadow: rgba(0, 0, 0, 0.12);
126
- --key-special-bg: #adb5bd;
127
- --overlay-bg: rgba(0, 0, 0, 0.4);
128
- }
129
- [data-theme='nord'] {
130
- --bg: #2e3440;
131
- --surface: #3b4252;
132
- --border: #434c5e;
133
- --border-subtle: #4c566a;
134
- --text: #d8dee9;
135
- --text-secondary: #b0bac9;
136
- --text-dim: #7b88a1;
137
- --text-muted: #5c6a85;
138
- --accent: #88c0d0;
139
- --accent-hover: #9fd4e4;
140
- --accent-active: #6aafbf;
141
- --danger: #bf616a;
142
- --danger-hover: #a84d57;
143
- --success: #a3be8c;
144
- --key-bg: #434c5e;
145
- --key-border: #4c566a;
146
- --key-shadow: rgba(0, 0, 0, 0.3);
147
- --key-special-bg: #3b4252;
148
- --overlay-bg: rgba(0, 0, 0, 0.75);
149
- }
150
- [data-theme='dracula'] {
151
- --bg: #282a36;
152
- --surface: #343746;
153
- --border: #44475a;
154
- --border-subtle: #525568;
155
- --text: #f8f8f2;
156
- --text-secondary: #c1c4d2;
157
- --text-dim: #8e92a4;
158
- --text-muted: #6272a4;
159
- --accent: #bd93f9;
160
- --accent-hover: #d0b0ff;
161
- --accent-active: #a77de7;
162
- --danger: #ff5555;
163
- --danger-hover: #e03d3d;
164
- --success: #50fa7b;
165
- --key-bg: #44475a;
166
- --key-border: #525568;
167
- --key-shadow: rgba(0, 0, 0, 0.4);
168
- --key-special-bg: #343746;
169
- --overlay-bg: rgba(0, 0, 0, 0.75);
170
- }
171
- [data-theme='github-dark'] {
172
- --bg: #0d1117;
173
- --surface: #161b22;
174
- --border: #30363d;
175
- --border-subtle: #3d444d;
176
- --text: #c9d1d9;
177
- --text-secondary: #8b949e;
178
- --text-dim: #6e7681;
179
- --text-muted: #484f58;
180
- --accent: #58a6ff;
181
- --accent-hover: #79b8ff;
182
- --accent-active: #388bfd;
183
- --danger: #f85149;
184
- --danger-hover: #da3633;
185
- --success: #3fb950;
186
- --key-bg: #161b22;
187
- --key-border: #30363d;
188
- --key-shadow: rgba(0, 0, 0, 0.4);
189
- --key-special-bg: #0d1117;
190
- --overlay-bg: rgba(0, 0, 0, 0.75);
191
- }
192
- [data-theme='one-dark'] {
193
- --bg: #282c34;
194
- --surface: #21252b;
195
- --border: #3e4452;
196
- --border-subtle: #4b5263;
197
- --text: #abb2bf;
198
- --text-secondary: #7f848e;
199
- --text-dim: #5c6370;
200
- --text-muted: #4b5263;
201
- --accent: #61afef;
202
- --accent-hover: #7dc0ff;
203
- --accent-active: #4d9ede;
204
- --danger: #e06c75;
205
- --danger-hover: #c95c67;
206
- --success: #98c379;
207
- --key-bg: #3e4452;
208
- --key-border: #4b5263;
209
- --key-shadow: rgba(0, 0, 0, 0.3);
210
- --key-special-bg: #21252b;
211
- --overlay-bg: rgba(0, 0, 0, 0.75);
212
- }
213
- [data-theme='catppuccin'] {
214
- --bg: #1e1e2e;
215
- --surface: #313244;
216
- --border: #45475a;
217
- --border-subtle: #585b70;
218
- --text: #cdd6f4;
219
- --text-secondary: #a6adc8;
220
- --text-dim: #7f849c;
221
- --text-muted: #585b70;
222
- --accent: #89b4fa;
223
- --accent-hover: #b4d0ff;
224
- --accent-active: #5c9de3;
225
- --danger: #f38ba8;
226
- --danger-hover: #eb7c9d;
227
- --success: #a6e3a1;
228
- --key-bg: #45475a;
229
- --key-border: #585b70;
230
- --key-shadow: rgba(0, 0, 0, 0.3);
231
- --key-special-bg: #313244;
232
- --overlay-bg: rgba(0, 0, 0, 0.75);
233
- }
234
- [data-theme='gruvbox'] {
235
- --bg: #282828;
236
- --surface: #3c3836;
237
- --border: #504945;
238
- --border-subtle: #665c54;
239
- --text: #ebdbb2;
240
- --text-secondary: #d5c4a1;
241
- --text-dim: #a89984;
242
- --text-muted: #7c6f64;
243
- --accent: #83a598;
244
- --accent-hover: #9dbfb4;
245
- --accent-active: #6a8f8a;
246
- --danger: #fb4934;
247
- --danger-hover: #e33826;
248
- --success: #b8bb26;
249
- --key-bg: #504945;
250
- --key-border: #665c54;
251
- --key-shadow: rgba(0, 0, 0, 0.4);
252
- --key-special-bg: #3c3836;
253
- --overlay-bg: rgba(0, 0, 0, 0.75);
254
- }
255
- [data-theme='night-owl'] {
256
- --bg: #011627;
257
- --surface: #0d2a45;
258
- --border: #1d3b53;
259
- --border-subtle: #264863;
260
- --text: #d6deeb;
261
- --text-secondary: #8badc1;
262
- --text-dim: #5f7e97;
263
- --text-muted: #3f5f7d;
264
- --accent: #7fdbca;
265
- --accent-hover: #9ff0e0;
266
- --accent-active: #62c5b5;
267
- --danger: #ef5350;
268
- --danger-hover: #d83130;
269
- --success: #addb67;
270
- --key-bg: #1d3b53;
271
- --key-border: #264863;
272
- --key-shadow: rgba(0, 0, 0, 0.4);
273
- --key-special-bg: #0d2a45;
274
- --overlay-bg: rgba(0, 0, 0, 0.75);
275
- }
32
+ [data-theme='light'] { --key-bg: #ffffff; --key-border: #b5b5b5; --key-shadow: rgba(0, 0, 0, 0.12); --key-special-bg: #adb5bd; --overlay-bg: rgba(0, 0, 0, 0.5); }
33
+ [data-theme='monokai'] { --key-bg: #49483e; --key-border: #5c5c4f; --key-shadow: rgba(0, 0, 0, 0.4); --key-special-bg: #3e3d32; }
34
+ [data-theme='solarized-dark'] { --key-bg: #073642; --key-border: #586e75; --key-shadow: rgba(0, 0, 0, 0.3); --key-special-bg: #002b36; }
35
+ [data-theme='solarized-light'] { --key-bg: #ffffff; --key-border: #b5b5b5; --key-shadow: rgba(0, 0, 0, 0.12); --key-special-bg: #adb5bd; }
36
+ [data-theme='nord'] { --key-bg: #434c5e; --key-border: #4c566a; --key-shadow: rgba(0, 0, 0, 0.3); --key-special-bg: #3b4252; }
37
+ [data-theme='dracula'] { --key-bg: #44475a; --key-border: #525568; --key-shadow: rgba(0, 0, 0, 0.4); --key-special-bg: #343746; }
38
+ [data-theme='github-dark'] { --key-bg: #161b22; --key-border: #30363d; --key-shadow: rgba(0, 0, 0, 0.4); --key-special-bg: #0d1117; }
39
+ [data-theme='one-dark'] { --key-bg: #3e4452; --key-border: #4b5263; --key-shadow: rgba(0, 0, 0, 0.3); --key-special-bg: #21252b; }
40
+ [data-theme='catppuccin'] { --key-bg: #45475a; --key-border: #585b70; --key-shadow: rgba(0, 0, 0, 0.3); --key-special-bg: #313244; }
41
+ [data-theme='gruvbox'] { --key-bg: #504945; --key-border: #665c54; --key-shadow: rgba(0, 0, 0, 0.4); --key-special-bg: #3c3836; }
42
+ [data-theme='night-owl'] { --key-bg: #1d3b53; --key-border: #264863; --key-shadow: rgba(0, 0, 0, 0.4); --key-special-bg: #0d2a45; }
276
43
  @font-face {
277
44
  font-family: 'NerdFont';
278
45
  src: url('https://cdn.jsdelivr.net/gh/ryanoasis/nerd-fonts@latest/patched-fonts/JetBrainsMono/Ligatures/Regular/JetBrainsMonoNerdFont-Regular.ttf')
@@ -1730,6 +1497,30 @@
1730
1497
  white-space: normal;
1731
1498
  }
1732
1499
 
1500
+ .side-panel-card-git {
1501
+ display: flex;
1502
+ flex-wrap: wrap;
1503
+ gap: 3px 6px;
1504
+ padding: 0 12px 4px;
1505
+ font-size: 10px;
1506
+ color: var(--text-secondary);
1507
+ align-items: center;
1508
+ }
1509
+ .side-panel-card-git .git-badge {
1510
+ display: inline-flex;
1511
+ align-items: center;
1512
+ gap: 3px;
1513
+ background: var(--surface);
1514
+ padding: 1px 6px;
1515
+ border-radius: 3px;
1516
+ }
1517
+ .side-panel-card-git .git-status-clean {
1518
+ color: var(--success);
1519
+ }
1520
+ .side-panel-card-git .git-status-dirty {
1521
+ color: var(--warning, #fbbf24);
1522
+ }
1523
+
1733
1524
  @media (max-width: 640px) {
1734
1525
  #panel-toggle {
1735
1526
  display: flex;
@@ -2452,6 +2243,11 @@
2452
2243
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js"></script>
2453
2244
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0.11.0/lib/addon-web-links.min.js"></script>
2454
2245
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-search@0.15.0/lib/addon-search.min.js"></script>
2246
+ <script src="/js/shared.js"></script>
2247
+ <script src="/js/themes.js"></script>
2248
+ <script src="/js/terminal-themes.js"></script>
2249
+ <script src="/js/keybar.js"></script>
2250
+ <script src="/js/search.js"></script>
2455
2251
  <script>
2456
2252
  // ===== Constants =====
2457
2253
  const SESSION_COLORS = [
@@ -2513,6 +2309,14 @@
2513
2309
  document.body.removeChild(ta);
2514
2310
  }
2515
2311
 
2312
+ // Hook into shared theme system to update xterm terminal themes
2313
+ window.onThemeApplied = function (theme) {
2314
+ const termTheme = TERM_THEMES[theme] || darkTermTheme;
2315
+ for (const [, ms] of managed) {
2316
+ ms.term.options.theme = termTheme;
2317
+ }
2318
+ };
2319
+
2516
2320
  // ===== DOM Refs =====
2517
2321
  const statusDot = document.getElementById('status-dot');
2518
2322
  const statusText = document.getElementById('status-text');
@@ -2521,433 +2325,6 @@
2521
2325
  const tabListEl = document.getElementById('tab-list');
2522
2326
  const terminalsWrapper = document.getElementById('terminals-wrapper');
2523
2327
 
2524
- // ===== Terminal Themes =====
2525
- const darkTermTheme = {
2526
- background: '#1e1e1e',
2527
- foreground: '#d4d4d4',
2528
- cursor: '#aeafad',
2529
- cursorAccent: '#1e1e1e',
2530
- selectionBackground: 'rgba(38, 79, 120, 0.5)',
2531
- black: '#000000',
2532
- red: '#cd3131',
2533
- green: '#0dbc79',
2534
- yellow: '#e5e510',
2535
- blue: '#2472c8',
2536
- magenta: '#bc3fbc',
2537
- cyan: '#11a8cd',
2538
- white: '#e5e5e5',
2539
- brightBlack: '#666666',
2540
- brightRed: '#f14c4c',
2541
- brightGreen: '#23d18b',
2542
- brightYellow: '#f5f543',
2543
- brightBlue: '#3b8eea',
2544
- brightMagenta: '#d670d6',
2545
- brightCyan: '#29b8db',
2546
- brightWhite: '#e5e5e5',
2547
- };
2548
- const lightTermTheme = {
2549
- background: '#ffffff',
2550
- foreground: '#1e1e1e',
2551
- cursor: '#000000',
2552
- cursorAccent: '#ffffff',
2553
- selectionBackground: 'rgba(0, 120, 215, 0.3)',
2554
- black: '#000000',
2555
- red: '#cd3131',
2556
- green: '#00bc7c',
2557
- yellow: '#949800',
2558
- blue: '#0451a5',
2559
- magenta: '#bc05bc',
2560
- cyan: '#0598bc',
2561
- white: '#555555',
2562
- brightBlack: '#666666',
2563
- brightRed: '#cd3131',
2564
- brightGreen: '#14ce14',
2565
- brightYellow: '#b5ba00',
2566
- brightBlue: '#0451a5',
2567
- brightMagenta: '#bc05bc',
2568
- brightCyan: '#0598bc',
2569
- brightWhite: '#a5a5a5',
2570
- };
2571
- const monokaiTermTheme = {
2572
- background: '#272822',
2573
- foreground: '#f8f8f2',
2574
- cursor: '#f8f8f0',
2575
- cursorAccent: '#272822',
2576
- selectionBackground: 'rgba(166, 226, 46, 0.3)',
2577
- black: '#272822',
2578
- red: '#f92672',
2579
- green: '#a6e22e',
2580
- yellow: '#f4bf75',
2581
- blue: '#66d9e8',
2582
- magenta: '#ae81ff',
2583
- cyan: '#a1efe4',
2584
- white: '#f8f8f2',
2585
- brightBlack: '#75715e',
2586
- brightRed: '#f92672',
2587
- brightGreen: '#a6e22e',
2588
- brightYellow: '#f4bf75',
2589
- brightBlue: '#66d9e8',
2590
- brightMagenta: '#ae81ff',
2591
- brightCyan: '#a1efe4',
2592
- brightWhite: '#f9f8f5',
2593
- };
2594
- const solarizedDarkTermTheme = {
2595
- background: '#002b36',
2596
- foreground: '#839496',
2597
- cursor: '#839496',
2598
- cursorAccent: '#002b36',
2599
- selectionBackground: 'rgba(7, 54, 66, 0.8)',
2600
- black: '#073642',
2601
- red: '#dc322f',
2602
- green: '#859900',
2603
- yellow: '#b58900',
2604
- blue: '#268bd2',
2605
- magenta: '#d33682',
2606
- cyan: '#2aa198',
2607
- white: '#eee8d5',
2608
- brightBlack: '#002b36',
2609
- brightRed: '#cb4b16',
2610
- brightGreen: '#586e75',
2611
- brightYellow: '#657b83',
2612
- brightBlue: '#839496',
2613
- brightMagenta: '#6c71c4',
2614
- brightCyan: '#93a1a1',
2615
- brightWhite: '#fdf6e3',
2616
- };
2617
- const solarizedLightTermTheme = {
2618
- background: '#fdf6e3',
2619
- foreground: '#657b83',
2620
- cursor: '#586e75',
2621
- cursorAccent: '#fdf6e3',
2622
- selectionBackground: 'rgba(238, 232, 213, 0.8)',
2623
- black: '#073642',
2624
- red: '#dc322f',
2625
- green: '#859900',
2626
- yellow: '#b58900',
2627
- blue: '#268bd2',
2628
- magenta: '#d33682',
2629
- cyan: '#2aa198',
2630
- white: '#eee8d5',
2631
- brightBlack: '#002b36',
2632
- brightRed: '#cb4b16',
2633
- brightGreen: '#586e75',
2634
- brightYellow: '#657b83',
2635
- brightBlue: '#839496',
2636
- brightMagenta: '#6c71c4',
2637
- brightCyan: '#93a1a1',
2638
- brightWhite: '#fdf6e3',
2639
- };
2640
- const nordTermTheme = {
2641
- background: '#2e3440',
2642
- foreground: '#d8dee9',
2643
- cursor: '#d8dee9',
2644
- cursorAccent: '#2e3440',
2645
- selectionBackground: 'rgba(67, 76, 94, 0.5)',
2646
- black: '#3b4252',
2647
- red: '#bf616a',
2648
- green: '#a3be8c',
2649
- yellow: '#ebcb8b',
2650
- blue: '#81a1c1',
2651
- magenta: '#b48ead',
2652
- cyan: '#88c0d0',
2653
- white: '#e5e9f0',
2654
- brightBlack: '#4c566a',
2655
- brightRed: '#bf616a',
2656
- brightGreen: '#a3be8c',
2657
- brightYellow: '#ebcb8b',
2658
- brightBlue: '#81a1c1',
2659
- brightMagenta: '#b48ead',
2660
- brightCyan: '#8fbcbb',
2661
- brightWhite: '#eceff4',
2662
- };
2663
- const draculaTermTheme = {
2664
- background: '#282a36',
2665
- foreground: '#f8f8f2',
2666
- cursor: '#f8f8f2',
2667
- cursorAccent: '#282a36',
2668
- selectionBackground: 'rgba(68, 71, 90, 0.7)',
2669
- black: '#21222c',
2670
- red: '#ff5555',
2671
- green: '#50fa7b',
2672
- yellow: '#f1fa8c',
2673
- blue: '#bd93f9',
2674
- magenta: '#ff79c6',
2675
- cyan: '#8be9fd',
2676
- white: '#f8f8f2',
2677
- brightBlack: '#6272a4',
2678
- brightRed: '#ff6e6e',
2679
- brightGreen: '#69ff94',
2680
- brightYellow: '#ffffa5',
2681
- brightBlue: '#d6acff',
2682
- brightMagenta: '#ff92df',
2683
- brightCyan: '#a4ffff',
2684
- brightWhite: '#ffffff',
2685
- };
2686
- const githubDarkTermTheme = {
2687
- background: '#0d1117',
2688
- foreground: '#c9d1d9',
2689
- cursor: '#c9d1d9',
2690
- cursorAccent: '#0d1117',
2691
- selectionBackground: 'rgba(56, 139, 253, 0.3)',
2692
- black: '#484f58',
2693
- red: '#ff7b72',
2694
- green: '#3fb950',
2695
- yellow: '#d29922',
2696
- blue: '#58a6ff',
2697
- magenta: '#bc8cff',
2698
- cyan: '#39c5cf',
2699
- white: '#c9d1d9',
2700
- brightBlack: '#6e7681',
2701
- brightRed: '#ffa198',
2702
- brightGreen: '#56d364',
2703
- brightYellow: '#e3b341',
2704
- brightBlue: '#79c0ff',
2705
- brightMagenta: '#d2a8ff',
2706
- brightCyan: '#56d4dd',
2707
- brightWhite: '#f0f6fc',
2708
- };
2709
- const oneDarkTermTheme = {
2710
- background: '#282c34',
2711
- foreground: '#abb2bf',
2712
- cursor: '#528bff',
2713
- cursorAccent: '#282c34',
2714
- selectionBackground: 'rgba(62, 68, 82, 0.7)',
2715
- black: '#3f4451',
2716
- red: '#e06c75',
2717
- green: '#98c379',
2718
- yellow: '#e5c07b',
2719
- blue: '#61afef',
2720
- magenta: '#c678dd',
2721
- cyan: '#56b6c2',
2722
- white: '#d7dae0',
2723
- brightBlack: '#4f5666',
2724
- brightRed: '#e06c75',
2725
- brightGreen: '#98c379',
2726
- brightYellow: '#e5c07b',
2727
- brightBlue: '#61afef',
2728
- brightMagenta: '#c678dd',
2729
- brightCyan: '#56b6c2',
2730
- brightWhite: '#ffffff',
2731
- };
2732
- const catppuccinTermTheme = {
2733
- background: '#1e1e2e',
2734
- foreground: '#cdd6f4',
2735
- cursor: '#f5e0dc',
2736
- cursorAccent: '#1e1e2e',
2737
- selectionBackground: 'rgba(88, 91, 112, 0.5)',
2738
- black: '#45475a',
2739
- red: '#f38ba8',
2740
- green: '#a6e3a1',
2741
- yellow: '#f9e2af',
2742
- blue: '#89b4fa',
2743
- magenta: '#f5c2e7',
2744
- cyan: '#94e2d5',
2745
- white: '#bac2de',
2746
- brightBlack: '#585b70',
2747
- brightRed: '#f38ba8',
2748
- brightGreen: '#a6e3a1',
2749
- brightYellow: '#f9e2af',
2750
- brightBlue: '#89b4fa',
2751
- brightMagenta: '#f5c2e7',
2752
- brightCyan: '#94e2d5',
2753
- brightWhite: '#a6adc8',
2754
- };
2755
- const gruvboxTermTheme = {
2756
- background: '#282828',
2757
- foreground: '#ebdbb2',
2758
- cursor: '#ebdbb2',
2759
- cursorAccent: '#282828',
2760
- selectionBackground: 'rgba(80, 73, 69, 0.7)',
2761
- black: '#282828',
2762
- red: '#cc241d',
2763
- green: '#98971a',
2764
- yellow: '#d79921',
2765
- blue: '#458588',
2766
- magenta: '#b16286',
2767
- cyan: '#689d6a',
2768
- white: '#a89984',
2769
- brightBlack: '#928374',
2770
- brightRed: '#fb4934',
2771
- brightGreen: '#b8bb26',
2772
- brightYellow: '#fabd2f',
2773
- brightBlue: '#83a598',
2774
- brightMagenta: '#d3869b',
2775
- brightCyan: '#8ec07c',
2776
- brightWhite: '#ebdbb2',
2777
- };
2778
- const nightOwlTermTheme = {
2779
- background: '#011627',
2780
- foreground: '#d6deeb',
2781
- cursor: '#80a4c2',
2782
- cursorAccent: '#011627',
2783
- selectionBackground: 'rgba(29, 59, 83, 0.7)',
2784
- black: '#010e1a',
2785
- red: '#ef5350',
2786
- green: '#22da6e',
2787
- yellow: '#addb67',
2788
- blue: '#82aaff',
2789
- magenta: '#c792ea',
2790
- cyan: '#21c7a8',
2791
- white: '#d6deeb',
2792
- brightBlack: '#575656',
2793
- brightRed: '#ef5350',
2794
- brightGreen: '#22da6e',
2795
- brightYellow: '#ffeb95',
2796
- brightBlue: '#82aaff',
2797
- brightMagenta: '#c792ea',
2798
- brightCyan: '#7fdbca',
2799
- brightWhite: '#ffffff',
2800
- };
2801
- const TERM_THEMES = {
2802
- dark: darkTermTheme,
2803
- light: lightTermTheme,
2804
- monokai: monokaiTermTheme,
2805
- 'solarized-dark': solarizedDarkTermTheme,
2806
- 'solarized-light': solarizedLightTermTheme,
2807
- nord: nordTermTheme,
2808
- dracula: draculaTermTheme,
2809
- 'github-dark': githubDarkTermTheme,
2810
- 'one-dark': oneDarkTermTheme,
2811
- catppuccin: catppuccinTermTheme,
2812
- gruvbox: gruvboxTermTheme,
2813
- 'night-owl': nightOwlTermTheme,
2814
- };
2815
-
2816
- // ===== Theme =====
2817
- const THEMES = [
2818
- { id: 'dark', name: 'Dark', bg: '#1e1e1e' },
2819
- { id: 'light', name: 'Light', bg: '#f3f3f3' },
2820
- { id: 'monokai', name: 'Monokai', bg: '#272822' },
2821
- { id: 'solarized-dark', name: 'Solarized Dark', bg: '#002b36' },
2822
- { id: 'solarized-light', name: 'Solarized Light', bg: '#fdf6e3' },
2823
- { id: 'nord', name: 'Nord', bg: '#2e3440' },
2824
- { id: 'dracula', name: 'Dracula', bg: '#282a36' },
2825
- { id: 'github-dark', name: 'GitHub Dark', bg: '#0d1117' },
2826
- { id: 'one-dark', name: 'One Dark', bg: '#282c34' },
2827
- { id: 'catppuccin', name: 'Catppuccin', bg: '#1e1e2e' },
2828
- { id: 'gruvbox', name: 'Gruvbox', bg: '#282828' },
2829
- { id: 'night-owl', name: 'Night Owl', bg: '#011627' },
2830
- ];
2831
- function getTheme() {
2832
- return localStorage.getItem('termbeam-theme') || 'dark';
2833
- }
2834
- function applyTheme(theme) {
2835
- document.documentElement.setAttribute('data-theme', theme);
2836
- const t = THEMES.find((x) => x.id === theme) || THEMES[0];
2837
- document.querySelector('meta[name="theme-color"]').content = t.bg;
2838
- localStorage.setItem('termbeam-theme', theme);
2839
- document.querySelectorAll('.theme-option').forEach((el) => {
2840
- el.classList.toggle('active', el.dataset.themeOption === theme);
2841
- });
2842
- const termTheme = TERM_THEMES[theme] || darkTermTheme;
2843
- for (const [, ms] of managed) {
2844
- ms.term.options.theme = termTheme;
2845
- }
2846
- }
2847
- applyTheme(getTheme());
2848
- document.getElementById('theme-toggle').addEventListener('click', (e) => {
2849
- e.stopPropagation();
2850
- document.getElementById('theme-picker').classList.toggle('open');
2851
- });
2852
- document.addEventListener('click', () => {
2853
- document.getElementById('theme-picker').classList.remove('open');
2854
- });
2855
- document.querySelectorAll('.theme-option').forEach((el) => {
2856
- el.addEventListener('click', (e) => {
2857
- e.stopPropagation();
2858
- applyTheme(el.dataset.themeOption);
2859
- });
2860
- });
2861
-
2862
- // ===== Terminal Search =====
2863
- const searchBar = document.getElementById('search-bar');
2864
- const searchInput = document.getElementById('search-input');
2865
- const searchCount = document.getElementById('search-count');
2866
- const searchRegexBtn = document.getElementById('search-regex');
2867
- let searchRegex = false;
2868
- let searchResultIndex = 0;
2869
- let searchResultTotal = 0;
2870
-
2871
- function getActiveSearchAddon() {
2872
- if (!activeId) return null;
2873
- const ms = managed.get(activeId);
2874
- return ms ? ms.searchAddon : null;
2875
- }
2876
-
2877
- function updateSearchCount(idx, total) {
2878
- searchResultIndex = idx;
2879
- searchResultTotal = total;
2880
- searchCount.textContent = total > 0 ? idx + 1 + ' of ' + total : 'No results';
2881
- }
2882
-
2883
- function doSearch(direction) {
2884
- const addon = getActiveSearchAddon();
2885
- if (!addon) return;
2886
- const query = searchInput.value;
2887
- if (!query) {
2888
- searchCount.textContent = '';
2889
- return;
2890
- }
2891
- const opts = {
2892
- regex: searchRegex,
2893
- caseSensitive: false,
2894
- incremental: direction === 'next',
2895
- };
2896
- let result;
2897
- if (direction === 'prev') {
2898
- result = addon.findPrevious(query, opts);
2899
- } else {
2900
- result = addon.findNext(query, opts);
2901
- }
2902
- // SearchAddon returns boolean; no match count API in v0.15
2903
- searchCount.textContent = result ? 'Found' : 'No results';
2904
- }
2905
-
2906
- function openSearchBar() {
2907
- searchBar.classList.add('visible');
2908
- searchInput.focus();
2909
- searchInput.select();
2910
- }
2911
-
2912
- function closeSearchBar() {
2913
- searchBar.classList.remove('visible');
2914
- searchCount.textContent = '';
2915
- searchInput.value = '';
2916
- const addon = getActiveSearchAddon();
2917
- if (addon) addon.clearDecorations();
2918
- // Re-focus terminal
2919
- if (activeId) {
2920
- const ms = managed.get(activeId);
2921
- if (ms) ms.term.focus();
2922
- }
2923
- }
2924
-
2925
- searchInput.addEventListener('input', () => doSearch('next'));
2926
- searchInput.addEventListener('keydown', (e) => {
2927
- if (e.key === 'Escape') {
2928
- closeSearchBar();
2929
- e.preventDefault();
2930
- } else if (e.key === 'Enter') {
2931
- e.preventDefault();
2932
- doSearch(e.shiftKey ? 'prev' : 'next');
2933
- }
2934
- });
2935
- document.getElementById('search-next').addEventListener('click', () => doSearch('next'));
2936
- document.getElementById('search-prev').addEventListener('click', () => doSearch('prev'));
2937
- document.getElementById('search-close').addEventListener('click', closeSearchBar);
2938
- document.getElementById('search-regex').addEventListener('click', () => {
2939
- searchRegex = !searchRegex;
2940
- searchRegexBtn.classList.toggle('active', searchRegex);
2941
- if (searchInput.value) doSearch('next');
2942
- });
2943
-
2944
- document.addEventListener('keydown', (e) => {
2945
- if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
2946
- e.preventDefault();
2947
- openSearchBar();
2948
- }
2949
- });
2950
-
2951
2328
  // ===== Font Loading (non-blocking) =====
2952
2329
  const nerdFont = new FontFace(
2953
2330
  'NerdFont',
@@ -2966,11 +2343,6 @@
2966
2343
  init();
2967
2344
 
2968
2345
  // ===== Helpers =====
2969
- function esc(str) {
2970
- const d = document.createElement('div');
2971
- d.textContent = str;
2972
- return d.innerHTML;
2973
- }
2974
2346
  function escAttr(str) {
2975
2347
  return String(str)
2976
2348
  .replace(/&/g, '&amp;')
@@ -3436,6 +2808,7 @@
3436
2808
  shell: data.shell,
3437
2809
  cwd: data.cwd,
3438
2810
  pid: data.pid,
2811
+ git: data.git || null,
3439
2812
  term,
3440
2813
  fitAddon,
3441
2814
  searchAddon,
@@ -3916,6 +3289,26 @@
3916
3289
  '" title="Close session">×</button>' +
3917
3290
  '</div>' +
3918
3291
  (activity ? '<div class="side-panel-card-meta">' + activity + ' ago</div>' : '') +
3292
+ (ms.git
3293
+ ? '<div class="side-panel-card-git">' +
3294
+ '<span class="git-badge"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg> ' +
3295
+ esc(ms.git.branch || 'detached') +
3296
+ '</span>' +
3297
+ (ms.git.provider
3298
+ ? '<span class="git-badge">' + esc(ms.git.provider) + '</span>'
3299
+ : '') +
3300
+ (ms.git.repoName
3301
+ ? '<span class="git-badge">' + esc(ms.git.repoName) + '</span>'
3302
+ : '') +
3303
+ (ms.git.status
3304
+ ? '<span class="git-badge ' +
3305
+ (ms.git.status.clean ? 'git-status-clean' : 'git-status-dirty') +
3306
+ '">' +
3307
+ (ms.git.status.clean ? '✓ clean' : esc(ms.git.status.summary)) +
3308
+ '</span>'
3309
+ : '') +
3310
+ '</div>'
3311
+ : '') +
3919
3312
  previewContent +
3920
3313
  '</div>'
3921
3314
  );
@@ -4103,183 +3496,6 @@
4103
3496
  renderTabs();
4104
3497
  }
4105
3498
 
4106
- // ===== Key Bar =====
4107
- // Modifier state (shared with terminal onData)
4108
- let ctrlActive = false;
4109
- let shiftActive = false;
4110
- function clearModifiers() {
4111
- ctrlActive = false;
4112
- shiftActive = false;
4113
- const ctrlBtn = document.getElementById('ctrl-btn');
4114
- const shiftBtn = document.getElementById('shift-btn');
4115
- if (ctrlBtn) ctrlBtn.classList.remove('active');
4116
- if (shiftBtn) shiftBtn.classList.remove('active');
4117
- }
4118
-
4119
- function setupKeyBar() {
4120
- const keyBar = document.getElementById('key-bar');
4121
- const ctrlBtn = document.getElementById('ctrl-btn');
4122
- const shiftBtn = document.getElementById('shift-btn');
4123
- let repeatTimer = null;
4124
- let repeatInterval = null;
4125
-
4126
- function toggleModifier(which) {
4127
- if (which === 'ctrl') {
4128
- ctrlActive = !ctrlActive;
4129
- ctrlBtn.classList.toggle('active', ctrlActive);
4130
- } else {
4131
- shiftActive = !shiftActive;
4132
- shiftBtn.classList.toggle('active', shiftActive);
4133
- }
4134
- }
4135
-
4136
- function applyModifiers(key) {
4137
- if (!ctrlActive && !shiftActive) return key;
4138
- // Modifier param: Shift=2, Ctrl=5, Ctrl+Shift=6
4139
- const mod = ctrlActive && shiftActive ? 6 : ctrlActive ? 5 : 2;
4140
- // Arrow keys: \x1b[X → \x1b[1;{mod}X
4141
- const csiMatch = key.match(/^\x1b\[([ABCD])$/);
4142
- if (csiMatch) return '\x1b[1;' + mod + csiMatch[1];
4143
- // Home/End: \x1bOH/\x1bOF → \x1b[1;{mod}H/F
4144
- const ssMatch = key.match(/^\x1bO([HF])$/);
4145
- if (ssMatch) return '\x1b[1;' + mod + ssMatch[1];
4146
- // Tab with Shift → reverse tab
4147
- if (key === '\x09' && shiftActive && !ctrlActive) return '\x1b[Z';
4148
- return key;
4149
- }
4150
-
4151
- function flashBtn(btn) {
4152
- btn.classList.add('flash');
4153
- setTimeout(() => btn.classList.remove('flash'), 120);
4154
- }
4155
-
4156
- function sendKey(btn) {
4157
- if (!btn || !btn.dataset.key) return;
4158
- flashBtn(btn);
4159
- let data =
4160
- btn.dataset.key === 'enter'
4161
- ? '\r'
4162
- : btn.dataset.key === 'tab'
4163
- ? '\x09'
4164
- : btn.dataset.key;
4165
- data = applyModifiers(data);
4166
- const ms = managed.get(activeId);
4167
- if (ms && ms.ws && ms.ws.readyState === 1) {
4168
- ms.ws.send(JSON.stringify({ type: 'input', data }));
4169
- }
4170
- clearModifiers();
4171
- }
4172
-
4173
- function stopRepeat() {
4174
- clearTimeout(repeatTimer);
4175
- clearInterval(repeatInterval);
4176
- repeatTimer = null;
4177
- repeatInterval = null;
4178
- }
4179
-
4180
- function startRepeat(btn) {
4181
- stopRepeat();
4182
- sendKey(btn);
4183
- repeatTimer = setTimeout(() => {
4184
- repeatInterval = setInterval(() => sendKey(btn), 80);
4185
- }, 400);
4186
- }
4187
-
4188
- ctrlBtn.addEventListener('click', (e) => {
4189
- e.preventDefault();
4190
- e.stopPropagation();
4191
- toggleModifier('ctrl');
4192
- });
4193
- shiftBtn.addEventListener('click', (e) => {
4194
- e.preventDefault();
4195
- e.stopPropagation();
4196
- toggleModifier('shift');
4197
- });
4198
- ctrlBtn.addEventListener('mousedown', (e) => e.preventDefault());
4199
- shiftBtn.addEventListener('mousedown', (e) => e.preventDefault());
4200
- ctrlBtn.addEventListener(
4201
- 'touchstart',
4202
- (e) => {
4203
- e.preventDefault();
4204
- keyBarTouched = true;
4205
- toggleModifier('ctrl');
4206
- },
4207
- { passive: false },
4208
- );
4209
- shiftBtn.addEventListener(
4210
- 'touchstart',
4211
- (e) => {
4212
- e.preventDefault();
4213
- keyBarTouched = true;
4214
- toggleModifier('shift');
4215
- },
4216
- { passive: false },
4217
- );
4218
-
4219
- let keyBarTouched = false;
4220
- keyBar.addEventListener('mousedown', (e) => {
4221
- if (keyBarTouched) {
4222
- keyBarTouched = false;
4223
- return;
4224
- }
4225
- const btn = e.target.closest('.key-btn');
4226
- if (btn && btn.dataset.key) {
4227
- e.preventDefault();
4228
- startRepeat(btn);
4229
- }
4230
- });
4231
- keyBar.addEventListener('mouseup', stopRepeat);
4232
- keyBar.addEventListener('mouseleave', stopRepeat);
4233
-
4234
- // Touch handling: allow native scroll when swiping, fire key only on tap
4235
- const SWIPE_THRESHOLD = 10;
4236
- let touchStartX = 0;
4237
- let touchBtn = null;
4238
- let touchMoved = false;
4239
-
4240
- keyBar.addEventListener(
4241
- 'touchstart',
4242
- (e) => {
4243
- keyBarTouched = true;
4244
- touchBtn = e.target.closest('.key-btn');
4245
- touchMoved = false;
4246
- touchStartX = e.touches[0].clientX;
4247
- // Don't preventDefault — let the browser handle scroll
4248
- },
4249
- { passive: true },
4250
- );
4251
- keyBar.addEventListener(
4252
- 'touchmove',
4253
- (e) => {
4254
- if (Math.abs(e.touches[0].clientX - touchStartX) > SWIPE_THRESHOLD) {
4255
- touchMoved = true;
4256
- stopRepeat();
4257
- }
4258
- },
4259
- { passive: true },
4260
- );
4261
- keyBar.addEventListener('touchend', (e) => {
4262
- if (!touchMoved && touchBtn && touchBtn.dataset.key) {
4263
- e.preventDefault();
4264
- sendKey(touchBtn);
4265
- }
4266
- stopRepeat();
4267
- touchBtn = null;
4268
- });
4269
- keyBar.addEventListener('touchcancel', () => {
4270
- stopRepeat();
4271
- touchBtn = null;
4272
- });
4273
-
4274
- keyBar.addEventListener('click', (e) => {
4275
- const btn = e.target.closest('.key-btn');
4276
- if (btn) {
4277
- const ms = managed.get(activeId);
4278
- if (ms) ms.term.focus();
4279
- }
4280
- });
4281
- }
4282
-
4283
3499
  // ===== Paste =====
4284
3500
  function setupPaste() {
4285
3501
  const pasteOverlay = document.getElementById('paste-overlay');
@@ -4747,7 +3963,9 @@
4747
3963
  const ms = managed.get(s.id);
4748
3964
  ms.name = s.name;
4749
3965
  ms.color = s.color;
3966
+ ms.cwd = s.cwd;
4750
3967
  ms.lastActivity = s.lastActivity;
3968
+ ms.git = s.git || null;
4751
3969
  }
4752
3970
  }
4753
3971
 
@@ -5030,22 +4248,7 @@
5030
4248
  icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>',
5031
4249
  label: 'Copy link',
5032
4250
  category: 'Share',
5033
- action: () => {
5034
- const url = location.href;
5035
- if (navigator.clipboard && navigator.clipboard.writeText) {
5036
- navigator.clipboard.writeText(url).then(
5037
- () => showToast('Link copied!'),
5038
- () => {
5039
- if (copyToClipboardFallback(url)) showToast('Link copied!');
5040
- else showShareUrlPrompt(url);
5041
- },
5042
- );
5043
- } else if (copyToClipboardFallback(url)) {
5044
- showToast('Link copied!');
5045
- } else {
5046
- showShareUrlPrompt(url);
5047
- }
5048
- },
4251
+ action: shareLink,
5049
4252
  },
5050
4253
  {
5051
4254
  icon: '<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="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>',
@@ -5225,9 +4428,7 @@
5225
4428
  })();
5226
4429
 
5227
4430
  // ===== Service Worker =====
5228
- if ('serviceWorker' in navigator) {
5229
- navigator.serviceWorker.register('/sw.js').catch(() => {});
5230
- }
4431
+ registerServiceWorker();
5231
4432
  </script>
5232
4433
  </body>
5233
4434
  </html>