termbeam 1.2.10 → 1.4.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.
@@ -63,6 +63,216 @@
63
63
  --key-special-bg: #adb5bd;
64
64
  --overlay-bg: rgba(0, 0, 0, 0.5);
65
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
+ }
66
276
  @font-face {
67
277
  font-family: 'NerdFont';
68
278
  src: url('https://cdn.jsdelivr.net/gh/ryanoasis/nerd-fonts@latest/patched-fonts/JetBrainsMono/Ligatures/Regular/JetBrainsMonoNerdFont-Regular.ttf')
@@ -248,6 +458,23 @@
248
458
  .tab-close:hover {
249
459
  color: var(--danger);
250
460
  }
461
+ .tab-unread {
462
+ width: 7px;
463
+ height: 7px;
464
+ border-radius: 50%;
465
+ background: var(--accent);
466
+ flex-shrink: 0;
467
+ animation: tab-pulse 1.5s ease-in-out infinite;
468
+ }
469
+ @keyframes tab-pulse {
470
+ 0%,
471
+ 100% {
472
+ opacity: 1;
473
+ }
474
+ 50% {
475
+ opacity: 0.4;
476
+ }
477
+ }
251
478
  /* ===== Tab Preview ===== */
252
479
  #tab-preview {
253
480
  display: none;
@@ -382,6 +609,52 @@
382
609
  font-size: 14px;
383
610
  font-weight: 600;
384
611
  }
612
+ .theme-wrap {
613
+ position: relative;
614
+ display: flex;
615
+ align-items: center;
616
+ }
617
+ .theme-picker {
618
+ display: none;
619
+ position: absolute;
620
+ top: calc(100% + 4px);
621
+ right: 0;
622
+ background: var(--surface);
623
+ border: 1px solid var(--border);
624
+ border-radius: 8px;
625
+ min-width: 160px;
626
+ padding: 4px 0;
627
+ z-index: 200;
628
+ box-shadow: 0 4px 12px var(--shadow);
629
+ }
630
+ .theme-picker.open {
631
+ display: block;
632
+ }
633
+ .theme-option {
634
+ display: flex;
635
+ align-items: center;
636
+ gap: 8px;
637
+ padding: 7px 12px;
638
+ cursor: pointer;
639
+ font-size: 13px;
640
+ color: var(--text);
641
+ transition: background 0.1s;
642
+ white-space: nowrap;
643
+ }
644
+ .theme-option:hover {
645
+ background: var(--border);
646
+ }
647
+ .theme-option.active {
648
+ color: var(--accent);
649
+ }
650
+ .theme-swatch {
651
+ width: 14px;
652
+ height: 14px;
653
+ border-radius: 50%;
654
+ display: inline-block;
655
+ flex-shrink: 0;
656
+ border: 1px solid rgba(128, 128, 128, 0.3);
657
+ }
385
658
  #stop-btn {
386
659
  background: none;
387
660
  border: none;
@@ -467,6 +740,9 @@
467
740
  [data-theme='light'] #key-bar {
468
741
  background: #d1d3d9;
469
742
  }
743
+ [data-theme='solarized-light'] #key-bar {
744
+ background: #d1d3d9;
745
+ }
470
746
  .key-row {
471
747
  display: flex;
472
748
  align-items: center;
@@ -502,6 +778,10 @@
502
778
  color: #000;
503
779
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
504
780
  }
781
+ [data-theme='solarized-light'] .key-btn {
782
+ color: #000;
783
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
784
+ }
505
785
  .key-btn:active {
506
786
  background: #6e6e72;
507
787
  transform: scale(0.95);
@@ -510,6 +790,9 @@
510
790
  [data-theme='light'] .key-btn:active {
511
791
  background: #c8c8cc;
512
792
  }
793
+ [data-theme='solarized-light'] .key-btn:active {
794
+ background: #c8c8cc;
795
+ }
513
796
  .key-btn.flash {
514
797
  background: #fff !important;
515
798
  color: #000 !important;
@@ -519,6 +802,10 @@
519
802
  background: #333 !important;
520
803
  color: #fff !important;
521
804
  }
805
+ [data-theme='solarized-light'] .key-btn.flash {
806
+ background: #333 !important;
807
+ color: #fff !important;
808
+ }
522
809
  .key-btn.modifier,
523
810
  .key-btn.special {
524
811
  background: var(--key-special-bg);
@@ -530,6 +817,11 @@
530
817
  background: var(--key-special-bg);
531
818
  color: #000;
532
819
  }
820
+ [data-theme='solarized-light'] .key-btn.modifier,
821
+ [data-theme='solarized-light'] .key-btn.special {
822
+ background: var(--key-special-bg);
823
+ color: #000;
824
+ }
533
825
  .key-btn.modifier.active {
534
826
  background: var(--accent);
535
827
  color: #fff;
@@ -563,6 +855,10 @@
563
855
  background: #fee2e2;
564
856
  color: #dc2626;
565
857
  }
858
+ [data-theme='solarized-light'] .key-btn.key-danger {
859
+ background: #fee2e2;
860
+ color: #dc2626;
861
+ }
566
862
  .key-btn.key-danger:active {
567
863
  background: var(--danger);
568
864
  color: #fff;
@@ -1381,7 +1677,7 @@
1381
1677
  #back-btn {
1382
1678
  display: none;
1383
1679
  }
1384
- #theme-toggle {
1680
+ #theme-wrap {
1385
1681
  display: flex;
1386
1682
  }
1387
1683
  #stop-btn {
@@ -1572,28 +1868,66 @@
1572
1868
  <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" />
1573
1869
  </svg>
1574
1870
  </button>
1575
- <button class="bar-btn" id="theme-toggle" title="Toggle theme">
1576
- <svg
1577
- width="16"
1578
- height="16"
1579
- viewBox="0 0 24 24"
1580
- fill="none"
1581
- stroke="currentColor"
1582
- stroke-width="2"
1583
- stroke-linecap="round"
1584
- stroke-linejoin="round"
1585
- >
1586
- <circle cx="12" cy="12" r="5" />
1587
- <line x1="12" y1="1" x2="12" y2="3" />
1588
- <line x1="12" y1="21" x2="12" y2="23" />
1589
- <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
1590
- <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
1591
- <line x1="1" y1="12" x2="3" y2="12" />
1592
- <line x1="21" y1="12" x2="23" y2="12" />
1593
- <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
1594
- <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
1595
- </svg>
1596
- </button>
1871
+ <div class="theme-wrap" id="theme-wrap">
1872
+ <button class="bar-btn" id="theme-toggle" title="Switch theme">
1873
+ <svg
1874
+ width="16"
1875
+ height="16"
1876
+ viewBox="0 0 24 24"
1877
+ fill="none"
1878
+ stroke="currentColor"
1879
+ stroke-width="2"
1880
+ stroke-linecap="round"
1881
+ stroke-linejoin="round"
1882
+ >
1883
+ <circle cx="13.5" cy="6.5" r=".5" fill="currentColor" />
1884
+ <circle cx="17.5" cy="10.5" r=".5" fill="currentColor" />
1885
+ <circle cx="8.5" cy="7.5" r=".5" fill="currentColor" />
1886
+ <circle cx="6.5" cy="12.5" r=".5" fill="currentColor" />
1887
+ <path
1888
+ 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"
1889
+ />
1890
+ </svg>
1891
+ </button>
1892
+ <div class="theme-picker" id="theme-picker">
1893
+ <div class="theme-option" data-theme-option="dark">
1894
+ <span class="theme-swatch" style="background: #1e1e1e"></span>Dark
1895
+ </div>
1896
+ <div class="theme-option" data-theme-option="light">
1897
+ <span class="theme-swatch" style="background: #ffffff"></span>Light
1898
+ </div>
1899
+ <div class="theme-option" data-theme-option="monokai">
1900
+ <span class="theme-swatch" style="background: #272822"></span>Monokai
1901
+ </div>
1902
+ <div class="theme-option" data-theme-option="solarized-dark">
1903
+ <span class="theme-swatch" style="background: #002b36"></span>Solarized Dark
1904
+ </div>
1905
+ <div class="theme-option" data-theme-option="solarized-light">
1906
+ <span class="theme-swatch" style="background: #fdf6e3"></span>Solarized Light
1907
+ </div>
1908
+ <div class="theme-option" data-theme-option="nord">
1909
+ <span class="theme-swatch" style="background: #2e3440"></span>Nord
1910
+ </div>
1911
+ <div class="theme-option" data-theme-option="dracula">
1912
+ <span class="theme-swatch" style="background: #282a36"></span>Dracula
1913
+ </div>
1914
+ <div class="theme-option" data-theme-option="github-dark">
1915
+ <span class="theme-swatch" style="background: #0d1117"></span>GitHub Dark
1916
+ </div>
1917
+ <div class="theme-option" data-theme-option="one-dark">
1918
+ <span class="theme-swatch" style="background: #282c34"></span>One Dark
1919
+ </div>
1920
+ <div class="theme-option" data-theme-option="catppuccin">
1921
+ <span class="theme-swatch" style="background: #1e1e2e"></span>Catppuccin
1922
+ </div>
1923
+ <div class="theme-option" data-theme-option="gruvbox">
1924
+ <span class="theme-swatch" style="background: #282828"></span>Gruvbox
1925
+ </div>
1926
+ <div class="theme-option" data-theme-option="night-owl">
1927
+ <span class="theme-swatch" style="background: #011627"></span>Night Owl
1928
+ </div>
1929
+ </div>
1930
+ </div>
1597
1931
  <button id="stop-btn" title="Stop session">
1598
1932
  <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" stroke="none">
1599
1933
  <rect x="6" y="6" width="12" height="12" rx="2" /></svg
@@ -1970,29 +2304,296 @@
1970
2304
  brightCyan: '#0598bc',
1971
2305
  brightWhite: '#a5a5a5',
1972
2306
  };
2307
+ const monokaiTermTheme = {
2308
+ background: '#272822',
2309
+ foreground: '#f8f8f2',
2310
+ cursor: '#f8f8f0',
2311
+ cursorAccent: '#272822',
2312
+ selectionBackground: 'rgba(166, 226, 46, 0.3)',
2313
+ black: '#272822',
2314
+ red: '#f92672',
2315
+ green: '#a6e22e',
2316
+ yellow: '#f4bf75',
2317
+ blue: '#66d9e8',
2318
+ magenta: '#ae81ff',
2319
+ cyan: '#a1efe4',
2320
+ white: '#f8f8f2',
2321
+ brightBlack: '#75715e',
2322
+ brightRed: '#f92672',
2323
+ brightGreen: '#a6e22e',
2324
+ brightYellow: '#f4bf75',
2325
+ brightBlue: '#66d9e8',
2326
+ brightMagenta: '#ae81ff',
2327
+ brightCyan: '#a1efe4',
2328
+ brightWhite: '#f9f8f5',
2329
+ };
2330
+ const solarizedDarkTermTheme = {
2331
+ background: '#002b36',
2332
+ foreground: '#839496',
2333
+ cursor: '#839496',
2334
+ cursorAccent: '#002b36',
2335
+ selectionBackground: 'rgba(7, 54, 66, 0.8)',
2336
+ black: '#073642',
2337
+ red: '#dc322f',
2338
+ green: '#859900',
2339
+ yellow: '#b58900',
2340
+ blue: '#268bd2',
2341
+ magenta: '#d33682',
2342
+ cyan: '#2aa198',
2343
+ white: '#eee8d5',
2344
+ brightBlack: '#002b36',
2345
+ brightRed: '#cb4b16',
2346
+ brightGreen: '#586e75',
2347
+ brightYellow: '#657b83',
2348
+ brightBlue: '#839496',
2349
+ brightMagenta: '#6c71c4',
2350
+ brightCyan: '#93a1a1',
2351
+ brightWhite: '#fdf6e3',
2352
+ };
2353
+ const solarizedLightTermTheme = {
2354
+ background: '#fdf6e3',
2355
+ foreground: '#657b83',
2356
+ cursor: '#586e75',
2357
+ cursorAccent: '#fdf6e3',
2358
+ selectionBackground: 'rgba(238, 232, 213, 0.8)',
2359
+ black: '#073642',
2360
+ red: '#dc322f',
2361
+ green: '#859900',
2362
+ yellow: '#b58900',
2363
+ blue: '#268bd2',
2364
+ magenta: '#d33682',
2365
+ cyan: '#2aa198',
2366
+ white: '#eee8d5',
2367
+ brightBlack: '#002b36',
2368
+ brightRed: '#cb4b16',
2369
+ brightGreen: '#586e75',
2370
+ brightYellow: '#657b83',
2371
+ brightBlue: '#839496',
2372
+ brightMagenta: '#6c71c4',
2373
+ brightCyan: '#93a1a1',
2374
+ brightWhite: '#fdf6e3',
2375
+ };
2376
+ const nordTermTheme = {
2377
+ background: '#2e3440',
2378
+ foreground: '#d8dee9',
2379
+ cursor: '#d8dee9',
2380
+ cursorAccent: '#2e3440',
2381
+ selectionBackground: 'rgba(67, 76, 94, 0.5)',
2382
+ black: '#3b4252',
2383
+ red: '#bf616a',
2384
+ green: '#a3be8c',
2385
+ yellow: '#ebcb8b',
2386
+ blue: '#81a1c1',
2387
+ magenta: '#b48ead',
2388
+ cyan: '#88c0d0',
2389
+ white: '#e5e9f0',
2390
+ brightBlack: '#4c566a',
2391
+ brightRed: '#bf616a',
2392
+ brightGreen: '#a3be8c',
2393
+ brightYellow: '#ebcb8b',
2394
+ brightBlue: '#81a1c1',
2395
+ brightMagenta: '#b48ead',
2396
+ brightCyan: '#8fbcbb',
2397
+ brightWhite: '#eceff4',
2398
+ };
2399
+ const draculaTermTheme = {
2400
+ background: '#282a36',
2401
+ foreground: '#f8f8f2',
2402
+ cursor: '#f8f8f2',
2403
+ cursorAccent: '#282a36',
2404
+ selectionBackground: 'rgba(68, 71, 90, 0.7)',
2405
+ black: '#21222c',
2406
+ red: '#ff5555',
2407
+ green: '#50fa7b',
2408
+ yellow: '#f1fa8c',
2409
+ blue: '#bd93f9',
2410
+ magenta: '#ff79c6',
2411
+ cyan: '#8be9fd',
2412
+ white: '#f8f8f2',
2413
+ brightBlack: '#6272a4',
2414
+ brightRed: '#ff6e6e',
2415
+ brightGreen: '#69ff94',
2416
+ brightYellow: '#ffffa5',
2417
+ brightBlue: '#d6acff',
2418
+ brightMagenta: '#ff92df',
2419
+ brightCyan: '#a4ffff',
2420
+ brightWhite: '#ffffff',
2421
+ };
2422
+ const githubDarkTermTheme = {
2423
+ background: '#0d1117',
2424
+ foreground: '#c9d1d9',
2425
+ cursor: '#c9d1d9',
2426
+ cursorAccent: '#0d1117',
2427
+ selectionBackground: 'rgba(56, 139, 253, 0.3)',
2428
+ black: '#484f58',
2429
+ red: '#ff7b72',
2430
+ green: '#3fb950',
2431
+ yellow: '#d29922',
2432
+ blue: '#58a6ff',
2433
+ magenta: '#bc8cff',
2434
+ cyan: '#39c5cf',
2435
+ white: '#c9d1d9',
2436
+ brightBlack: '#6e7681',
2437
+ brightRed: '#ffa198',
2438
+ brightGreen: '#56d364',
2439
+ brightYellow: '#e3b341',
2440
+ brightBlue: '#79c0ff',
2441
+ brightMagenta: '#d2a8ff',
2442
+ brightCyan: '#56d4dd',
2443
+ brightWhite: '#f0f6fc',
2444
+ };
2445
+ const oneDarkTermTheme = {
2446
+ background: '#282c34',
2447
+ foreground: '#abb2bf',
2448
+ cursor: '#528bff',
2449
+ cursorAccent: '#282c34',
2450
+ selectionBackground: 'rgba(62, 68, 82, 0.7)',
2451
+ black: '#3f4451',
2452
+ red: '#e06c75',
2453
+ green: '#98c379',
2454
+ yellow: '#e5c07b',
2455
+ blue: '#61afef',
2456
+ magenta: '#c678dd',
2457
+ cyan: '#56b6c2',
2458
+ white: '#d7dae0',
2459
+ brightBlack: '#4f5666',
2460
+ brightRed: '#e06c75',
2461
+ brightGreen: '#98c379',
2462
+ brightYellow: '#e5c07b',
2463
+ brightBlue: '#61afef',
2464
+ brightMagenta: '#c678dd',
2465
+ brightCyan: '#56b6c2',
2466
+ brightWhite: '#ffffff',
2467
+ };
2468
+ const catppuccinTermTheme = {
2469
+ background: '#1e1e2e',
2470
+ foreground: '#cdd6f4',
2471
+ cursor: '#f5e0dc',
2472
+ cursorAccent: '#1e1e2e',
2473
+ selectionBackground: 'rgba(88, 91, 112, 0.5)',
2474
+ black: '#45475a',
2475
+ red: '#f38ba8',
2476
+ green: '#a6e3a1',
2477
+ yellow: '#f9e2af',
2478
+ blue: '#89b4fa',
2479
+ magenta: '#f5c2e7',
2480
+ cyan: '#94e2d5',
2481
+ white: '#bac2de',
2482
+ brightBlack: '#585b70',
2483
+ brightRed: '#f38ba8',
2484
+ brightGreen: '#a6e3a1',
2485
+ brightYellow: '#f9e2af',
2486
+ brightBlue: '#89b4fa',
2487
+ brightMagenta: '#f5c2e7',
2488
+ brightCyan: '#94e2d5',
2489
+ brightWhite: '#a6adc8',
2490
+ };
2491
+ const gruvboxTermTheme = {
2492
+ background: '#282828',
2493
+ foreground: '#ebdbb2',
2494
+ cursor: '#ebdbb2',
2495
+ cursorAccent: '#282828',
2496
+ selectionBackground: 'rgba(80, 73, 69, 0.7)',
2497
+ black: '#282828',
2498
+ red: '#cc241d',
2499
+ green: '#98971a',
2500
+ yellow: '#d79921',
2501
+ blue: '#458588',
2502
+ magenta: '#b16286',
2503
+ cyan: '#689d6a',
2504
+ white: '#a89984',
2505
+ brightBlack: '#928374',
2506
+ brightRed: '#fb4934',
2507
+ brightGreen: '#b8bb26',
2508
+ brightYellow: '#fabd2f',
2509
+ brightBlue: '#83a598',
2510
+ brightMagenta: '#d3869b',
2511
+ brightCyan: '#8ec07c',
2512
+ brightWhite: '#ebdbb2',
2513
+ };
2514
+ const nightOwlTermTheme = {
2515
+ background: '#011627',
2516
+ foreground: '#d6deeb',
2517
+ cursor: '#80a4c2',
2518
+ cursorAccent: '#011627',
2519
+ selectionBackground: 'rgba(29, 59, 83, 0.7)',
2520
+ black: '#010e1a',
2521
+ red: '#ef5350',
2522
+ green: '#22da6e',
2523
+ yellow: '#addb67',
2524
+ blue: '#82aaff',
2525
+ magenta: '#c792ea',
2526
+ cyan: '#21c7a8',
2527
+ white: '#d6deeb',
2528
+ brightBlack: '#575656',
2529
+ brightRed: '#ef5350',
2530
+ brightGreen: '#22da6e',
2531
+ brightYellow: '#ffeb95',
2532
+ brightBlue: '#82aaff',
2533
+ brightMagenta: '#c792ea',
2534
+ brightCyan: '#7fdbca',
2535
+ brightWhite: '#ffffff',
2536
+ };
2537
+ const TERM_THEMES = {
2538
+ dark: darkTermTheme,
2539
+ light: lightTermTheme,
2540
+ monokai: monokaiTermTheme,
2541
+ 'solarized-dark': solarizedDarkTermTheme,
2542
+ 'solarized-light': solarizedLightTermTheme,
2543
+ nord: nordTermTheme,
2544
+ dracula: draculaTermTheme,
2545
+ 'github-dark': githubDarkTermTheme,
2546
+ 'one-dark': oneDarkTermTheme,
2547
+ catppuccin: catppuccinTermTheme,
2548
+ gruvbox: gruvboxTermTheme,
2549
+ 'night-owl': nightOwlTermTheme,
2550
+ };
1973
2551
 
1974
2552
  // ===== Theme =====
2553
+ const THEMES = [
2554
+ { id: 'dark', name: 'Dark', bg: '#1e1e1e' },
2555
+ { id: 'light', name: 'Light', bg: '#f3f3f3' },
2556
+ { id: 'monokai', name: 'Monokai', bg: '#272822' },
2557
+ { id: 'solarized-dark', name: 'Solarized Dark', bg: '#002b36' },
2558
+ { id: 'solarized-light', name: 'Solarized Light', bg: '#fdf6e3' },
2559
+ { id: 'nord', name: 'Nord', bg: '#2e3440' },
2560
+ { id: 'dracula', name: 'Dracula', bg: '#282a36' },
2561
+ { id: 'github-dark', name: 'GitHub Dark', bg: '#0d1117' },
2562
+ { id: 'one-dark', name: 'One Dark', bg: '#282c34' },
2563
+ { id: 'catppuccin', name: 'Catppuccin', bg: '#1e1e2e' },
2564
+ { id: 'gruvbox', name: 'Gruvbox', bg: '#282828' },
2565
+ { id: 'night-owl', name: 'Night Owl', bg: '#011627' },
2566
+ ];
1975
2567
  function getTheme() {
1976
2568
  return localStorage.getItem('termbeam-theme') || 'dark';
1977
2569
  }
1978
2570
  function applyTheme(theme) {
1979
2571
  document.documentElement.setAttribute('data-theme', theme);
1980
- document.querySelector('meta[name="theme-color"]').content =
1981
- theme === 'light' ? '#f3f3f3' : '#1e1e1e';
1982
- const btn = document.getElementById('theme-toggle');
1983
- if (btn)
1984
- btn.innerHTML =
1985
- theme === 'light'
1986
- ? '<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>'
1987
- : '<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>';
2572
+ const t = THEMES.find((x) => x.id === theme) || THEMES[0];
2573
+ document.querySelector('meta[name="theme-color"]').content = t.bg;
1988
2574
  localStorage.setItem('termbeam-theme', theme);
2575
+ document.querySelectorAll('.theme-option').forEach((el) => {
2576
+ el.classList.toggle('active', el.dataset.themeOption === theme);
2577
+ });
2578
+ const termTheme = TERM_THEMES[theme] || darkTermTheme;
1989
2579
  for (const [, ms] of managed) {
1990
- ms.term.options.theme = theme === 'light' ? lightTermTheme : darkTermTheme;
2580
+ ms.term.options.theme = termTheme;
1991
2581
  }
1992
2582
  }
1993
2583
  applyTheme(getTheme());
1994
- document.getElementById('theme-toggle').addEventListener('click', () => {
1995
- applyTheme(getTheme() === 'light' ? 'dark' : 'light');
2584
+ document.getElementById('theme-toggle').addEventListener('click', (e) => {
2585
+ e.stopPropagation();
2586
+ document.getElementById('theme-picker').classList.toggle('open');
2587
+ });
2588
+ document.addEventListener('click', () => {
2589
+ document.getElementById('theme-picker').classList.remove('open');
2590
+ });
2591
+ document.querySelectorAll('.theme-option').forEach((el) => {
2592
+ el.addEventListener('click', (e) => {
2593
+ e.stopPropagation();
2594
+ applyTheme(el.dataset.themeOption);
2595
+ document.getElementById('theme-picker').classList.remove('open');
2596
+ });
1996
2597
  });
1997
2598
 
1998
2599
  // ===== Font Loading (non-blocking) =====
@@ -2049,8 +2650,18 @@
2049
2650
 
2050
2651
  // ===== Zoom =====
2051
2652
  const MIN_FONT = 2,
2052
- MAX_FONT = 28;
2053
- let fontSize = parseInt(localStorage.getItem('termbeam-fontsize') || '8', 10);
2653
+ MAX_FONT = 32;
2654
+ function defaultFontSize() {
2655
+ const w = window.innerWidth;
2656
+ if (w <= 480) return 12;
2657
+ if (w <= 768) return 13;
2658
+ if (w <= 1280) return 14;
2659
+ return 15;
2660
+ }
2661
+ let fontSize = parseInt(
2662
+ localStorage.getItem('termbeam-fontsize') || String(defaultFontSize()),
2663
+ 10,
2664
+ );
2054
2665
 
2055
2666
  function applyZoom(size) {
2056
2667
  fontSize = Math.max(MIN_FONT, Math.min(MAX_FONT, size));
@@ -2126,6 +2737,62 @@
2126
2737
  .getElementById('zoom-out')
2127
2738
  .addEventListener('click', () => applyZoom(fontSize - 2));
2128
2739
 
2740
+ // Pinch-to-zoom
2741
+ (function setupPinchZoom() {
2742
+ const wrapper = document.getElementById('terminals-wrapper');
2743
+ let pinchStartDist = 0;
2744
+ let pinchStartFont = 0;
2745
+ let pinchActive = false;
2746
+ let pinchZoomTimer = null;
2747
+
2748
+ function touchDist(t) {
2749
+ const dx = t[0].clientX - t[1].clientX;
2750
+ const dy = t[0].clientY - t[1].clientY;
2751
+ return Math.sqrt(dx * dx + dy * dy);
2752
+ }
2753
+
2754
+ wrapper.addEventListener(
2755
+ 'touchstart',
2756
+ function (e) {
2757
+ if (e.touches.length === 2) {
2758
+ pinchActive = true;
2759
+ pinchStartDist = touchDist(e.touches);
2760
+ pinchStartFont = fontSize;
2761
+ wrapper.style.touchAction = 'none';
2762
+ }
2763
+ },
2764
+ { passive: true },
2765
+ );
2766
+
2767
+ wrapper.addEventListener(
2768
+ 'touchmove',
2769
+ function (e) {
2770
+ if (!pinchActive || e.touches.length !== 2) return;
2771
+ e.preventDefault();
2772
+ const dist = touchDist(e.touches);
2773
+ const scale = dist / pinchStartDist;
2774
+ const newSize = Math.round(pinchStartFont * scale);
2775
+ if (newSize !== fontSize) {
2776
+ if (pinchZoomTimer) clearTimeout(pinchZoomTimer);
2777
+ pinchZoomTimer = setTimeout(function () {
2778
+ applyZoom(newSize);
2779
+ }, 50);
2780
+ }
2781
+ },
2782
+ { passive: false },
2783
+ );
2784
+
2785
+ wrapper.addEventListener('touchend', function () {
2786
+ pinchActive = false;
2787
+ wrapper.style.touchAction = '';
2788
+ });
2789
+
2790
+ wrapper.addEventListener('touchcancel', function () {
2791
+ pinchActive = false;
2792
+ wrapper.style.touchAction = '';
2793
+ });
2794
+ })();
2795
+
2129
2796
  // Resize
2130
2797
  function doResize() {
2131
2798
  for (const [, ms] of managed) {
@@ -2203,6 +2870,7 @@
2203
2870
  // Scroll to bottom when returning from idle / tab switch
2204
2871
  document.addEventListener('visibilitychange', () => {
2205
2872
  if (!document.hidden && activeId) {
2873
+ clearUnreadIndicator();
2206
2874
  const ms = managed.get(activeId);
2207
2875
  if (ms) {
2208
2876
  ms.term.scrollToBottom();
@@ -2265,7 +2933,7 @@
2265
2933
  fontWeightBold: 'bold',
2266
2934
  letterSpacing: 0,
2267
2935
  lineHeight: 1.1,
2268
- theme: getTheme() === 'light' ? lightTermTheme : darkTermTheme,
2936
+ theme: TERM_THEMES[getTheme()] || darkTermTheme,
2269
2937
  allowProposedApi: true,
2270
2938
  scrollback: 10000,
2271
2939
  scrollOnOutput: false,
@@ -2462,6 +3130,53 @@
2462
3130
  return ms;
2463
3131
  }
2464
3132
 
3133
+ // Tab title activity indicator for unread output
3134
+ const originalTitle = document.title;
3135
+ let hasUnread = false;
3136
+
3137
+ // Shared AudioContext — must be created/resumed on user gesture for mobile
3138
+ let notifAudioCtx = null;
3139
+ function ensureAudioContext() {
3140
+ if (!notifAudioCtx) {
3141
+ notifAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
3142
+ }
3143
+ if (notifAudioCtx.state === 'suspended') notifAudioCtx.resume();
3144
+ return notifAudioCtx;
3145
+ }
3146
+ document.addEventListener('click', ensureAudioContext, { once: true });
3147
+ document.addEventListener('touchstart', ensureAudioContext, { once: true });
3148
+
3149
+ function playNotificationSound() {
3150
+ try {
3151
+ const ctx = ensureAudioContext();
3152
+ const osc = ctx.createOscillator();
3153
+ const gain = ctx.createGain();
3154
+ osc.connect(gain);
3155
+ gain.connect(ctx.destination);
3156
+ osc.frequency.value = 440;
3157
+ osc.type = 'sine';
3158
+ gain.gain.setValueAtTime(0.15, ctx.currentTime);
3159
+ gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.15);
3160
+ osc.start(ctx.currentTime);
3161
+ osc.stop(ctx.currentTime + 0.15);
3162
+ } catch {
3163
+ // Audio not available
3164
+ }
3165
+ }
3166
+
3167
+ function markUnreadOutput() {
3168
+ if (!document.hidden || hasUnread) return;
3169
+ hasUnread = true;
3170
+ playNotificationSound();
3171
+ document.title = '(\u25CF) ' + originalTitle;
3172
+ }
3173
+
3174
+ function clearUnreadIndicator() {
3175
+ if (!hasUnread) return;
3176
+ hasUnread = false;
3177
+ document.title = originalTitle;
3178
+ }
3179
+
2465
3180
  function connectSession(ms) {
2466
3181
  if (ms.reconnectTimer) {
2467
3182
  clearTimeout(ms.reconnectTimer);
@@ -2496,6 +3211,12 @@
2496
3211
  if (msg.type === 'output') {
2497
3212
  ms.coalescedWrite(msg.data);
2498
3213
  ms.lastActivity = Date.now();
3214
+ markUnreadOutput();
3215
+ if (ms.id !== activeId && !ms.hasUnread) {
3216
+ ms.hasUnread = true;
3217
+ playNotificationSound();
3218
+ renderTabs();
3219
+ }
2499
3220
  } else if (msg.type === 'attached') {
2500
3221
  if (ms.container.classList.contains('visible')) {
2501
3222
  sendResize(ms);
@@ -2557,6 +3278,8 @@
2557
3278
  function activateSession(id) {
2558
3279
  if (!managed.has(id)) return;
2559
3280
  activeId = id;
3281
+ const ms = managed.get(id);
3282
+ if (ms) ms.hasUnread = false;
2560
3283
  reconnectOverlay.classList.remove('visible');
2561
3284
 
2562
3285
  // Show/hide panes
@@ -2671,6 +3394,7 @@
2671
3394
  esc(ms.name) +
2672
3395
  '</span>' +
2673
3396
  (activity ? '<span class="tab-activity">' + activity + '</span>' : '') +
3397
+ (ms.hasUnread && !isActive ? '<span class="tab-unread"></span>' : '') +
2674
3398
  '<span class="tab-status" style="background:' +
2675
3399
  safeColor(statusColor) +
2676
3400
  '"></span>' +
@@ -2760,6 +3484,14 @@
2760
3484
  activateSession(tab.dataset.id);
2761
3485
  });
2762
3486
 
3487
+ // Middle-click to close tab
3488
+ tab.addEventListener('auxclick', (e) => {
3489
+ if (e.button === 1) {
3490
+ e.preventDefault();
3491
+ if (confirm('Close this session?')) removeSession(tab.dataset.id);
3492
+ }
3493
+ });
3494
+
2763
3495
  // Desktop: hover preview (non-active tabs only)
2764
3496
  tab.addEventListener('mouseenter', () => {
2765
3497
  if (tab.dataset.id === activeId) return;
@@ -2822,6 +3554,7 @@
2822
3554
  '<span class="side-panel-card-name">' +
2823
3555
  esc(ms.name) +
2824
3556
  '</span>' +
3557
+ (ms.hasUnread && !isActive ? '<span class="tab-unread"></span>' : '') +
2825
3558
  '<span class="side-panel-card-status" style="background:' +
2826
3559
  safeColor(statusColor) +
2827
3560
  '"></span>' +