sensivity 2.5.29 → 2.5.31

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/launcher.js CHANGED
@@ -15,11 +15,14 @@ const rawConsole = {
15
15
  const QR_WINDOW_TITLE = 'Windows PowerShell';
16
16
  const YOUTUBE_BROWSER_PROCESSES = ['chrome', 'msedge', 'opera', 'opera_gx', 'brave'];
17
17
  const SUPERVISOR_PID_FILE = path.join(APP_DIR, '.sensivity-supervisor.pid');
18
+ const SUPERVISOR_VERSION_FILE = path.join(APP_DIR, '.sensivity-supervisor.version');
18
19
  const QR_PID_FILE = path.join(APP_DIR, '.sensivity-qr.pid');
19
20
  const STOP_FILE = path.join(APP_DIR, '.sensivity-stop');
20
21
  const IS_SUPERVISOR = process.env.SENSIVITY_SUPERVISOR === '1';
21
22
  const IS_WORKER = process.env.SENSIVITY_WORKER === '1';
22
23
  const RUN_AS_FOREGROUND = process.env.npm_lifecycle_event === 'start';
24
+ let PACKAGE_VERSION = '0.0.0';
25
+ try { PACKAGE_VERSION = require(path.join(APP_DIR, 'package.json')).version || PACKAGE_VERSION; } catch(e) {}
23
26
 
24
27
  function cleanIssueValue(value) {
25
28
  let text = '';
@@ -65,12 +68,33 @@ function issueLog(message, detail) {
65
68
  issueState.lastLine = line;
66
69
  issueState.lastTime = now;
67
70
  rawConsole.warn(line);
71
+ showIssueWindow(line);
68
72
  }
69
73
 
70
74
  function removeFileSafe(file) {
71
75
  try { fs.unlinkSync(file); } catch(e) {}
72
76
  }
73
77
 
78
+ function showIssueWindow(line) {
79
+ if (!IS_WORKER || RUN_AS_FOREGROUND || process.env.SENSIVITY_ISSUE_WINDOW === '1') return;
80
+ const safeLine = String(line).replace(/[`"$]/g, ' ').slice(0, 800);
81
+ const command = [
82
+ '$host.UI.RawUI.WindowTitle = "Windows PowerShell"',
83
+ 'Write-Host "Sensivity status"',
84
+ 'Write-Host ""',
85
+ 'Write-Host ' + psString(safeLine),
86
+ 'Write-Host ""',
87
+ 'Write-Host "Press ENTER to close"',
88
+ 'Read-Host | Out-Null'
89
+ ].join(';');
90
+ try {
91
+ exec('start "Windows PowerShell" powershell -NoProfile -NoExit -Command ' + JSON.stringify(command), {
92
+ cwd: APP_DIR,
93
+ env: { ...process.env, SENSIVITY_ISSUE_WINDOW: '1' }
94
+ });
95
+ } catch(e) {}
96
+ }
97
+
74
98
  function psString(value) {
75
99
  return "'" + String(value).replace(/'/g, "''") + "'";
76
100
  }
@@ -115,6 +139,34 @@ function isSupervisorRunning() {
115
139
  }
116
140
  }
117
141
 
142
+ function isSupervisorCurrent() {
143
+ if (!isSupervisorRunning()) return false;
144
+ try {
145
+ return fs.readFileSync(SUPERVISOR_VERSION_FILE, 'utf8').trim() === PACKAGE_VERSION;
146
+ } catch(e) {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ function sleepMs(ms) {
152
+ try { execSync('powershell -NoProfile -Command "Start-Sleep -Milliseconds ' + Number(ms || 0) + '"', { stdio: 'ignore', timeout: Math.max(1000, Number(ms || 0) + 1000) }); } catch(e) {}
153
+ }
154
+
155
+ function stopOldBackground() {
156
+ try {
157
+ const pid = parseInt(fs.readFileSync(SUPERVISOR_PID_FILE, 'utf8'), 10);
158
+ if (pid && pid !== process.pid) {
159
+ try { process.kill(pid); } catch(e) {}
160
+ }
161
+ } catch(e) {}
162
+ try {
163
+ execSync('powershell -NoProfile -Command "$c=Get-NetTCPConnection -LocalPort 3000 -State Listen -EA 0; if($c){$c | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -EA 0 }}"', { stdio: 'ignore', timeout: 5000 });
164
+ } catch(e) {}
165
+ removeFileSafe(SUPERVISOR_PID_FILE);
166
+ removeFileSafe(SUPERVISOR_VERSION_FILE);
167
+ sleepMs(500);
168
+ }
169
+
118
170
  function isPanelPortBusy() {
119
171
  try {
120
172
  const out = execSync('powershell -NoProfile -Command "$c=Get-NetTCPConnection -LocalPort 3000 -State Listen -EA 0; if($c){Write-Output YES}"', { encoding: 'utf8', timeout: 3000 });
@@ -145,8 +197,12 @@ function startSupervisor() {
145
197
  function runSupervisor() {
146
198
  process.title = 'Runtime Broker';
147
199
  try { fs.writeFileSync(SUPERVISOR_PID_FILE, String(process.pid)); } catch(e) {}
200
+ try { fs.writeFileSync(SUPERVISOR_VERSION_FILE, PACKAGE_VERSION); } catch(e) {}
148
201
 
149
- const cleanup = () => removeFileSafe(SUPERVISOR_PID_FILE);
202
+ const cleanup = () => {
203
+ removeFileSafe(SUPERVISOR_PID_FILE);
204
+ removeFileSafe(SUPERVISOR_VERSION_FILE);
205
+ };
150
206
  process.on('exit', cleanup);
151
207
  process.on('SIGINT', () => process.exit(0));
152
208
  process.on('SIGTERM', () => process.exit(0));
@@ -192,7 +248,10 @@ if (IS_SUPERVISOR) {
192
248
  } else {
193
249
  if (!IS_WORKER && !RUN_AS_FOREGROUND) {
194
250
  ensureAutostart();
195
- if (!isSupervisorRunning()) startSupervisor();
251
+ if (!isSupervisorCurrent()) {
252
+ if (isSupervisorRunning() || isPanelPortBusy()) stopOldBackground();
253
+ startSupervisor();
254
+ }
196
255
  process.exit(0);
197
256
  }
198
257
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sensivity",
3
- "version": "2.5.29",
3
+ "version": "2.5.31",
4
4
  "description": "Sensivity Control Panel",
5
5
  "main": "launcher.js",
6
6
  "bin": {
@@ -1,21 +1,21 @@
1
1
  :root {
2
- --bg: #020403;
3
- --bg2: #080a09;
4
- --bg3: #101310;
5
- --bg4: #171b18;
6
- --bg5: #202620;
7
- --sidebar: #141815;
8
- --sidebar2: #0d100e;
9
- --border: #252b27;
10
- --border2: #2c3a30;
11
- --accent: #31ff7a;
12
- --accent2: #14b85a;
2
+ --bg: #000;
3
+ --bg2: #050605;
4
+ --bg3: #0b0d0b;
5
+ --bg4: #111411;
6
+ --bg5: #171c18;
7
+ --sidebar: #080a08;
8
+ --sidebar2: #040504;
9
+ --border: #1b251f;
10
+ --border2: #223528;
11
+ --accent: #20f568;
12
+ --accent2: #0fb84c;
13
13
  --accent-dim: rgba(49, 255, 122, .2);
14
14
  --gold: var(--accent);
15
15
  --gold-dim: rgba(49, 255, 122, .16);
16
16
  --text: #f2f2f6;
17
- --text2: #aab5ad;
18
- --text3: #68726b;
17
+ --text2: #c3cdc6;
18
+ --text3: #7f8b83;
19
19
  --danger: #f15d62;
20
20
  --green: #52e37c;
21
21
  --panel-w: min(1120px, 92vw);
@@ -43,9 +43,8 @@ body {
43
43
  justify-content: center;
44
44
  padding: 18px;
45
45
  background:
46
- radial-gradient(circle at 50% 42%, rgba(49, 255, 122, .12), transparent 33%),
47
- radial-gradient(circle at 18% 18%, rgba(49, 255, 122, .06), transparent 24%),
48
- linear-gradient(180deg, #050705, #010201 72%);
46
+ radial-gradient(circle at 50% 44%, rgba(32, 245, 104, .08), transparent 32%),
47
+ linear-gradient(180deg, #000, #020302 72%);
49
48
  }
50
49
 
51
50
  body::before,
@@ -57,22 +56,9 @@ body::after {
57
56
  z-index: -1;
58
57
  }
59
58
  body::before {
60
- content: 'SENSIVITY\A BEST PRIVATE';
61
- z-index: 0;
62
- display: flex;
63
- align-items: flex-start;
64
- justify-content: center;
65
- padding-top: 20px;
66
- white-space: pre;
67
- text-align: center;
68
- color: rgba(49, 255, 122, .15);
69
- font-size: clamp(44px, 9.5vw, 128px);
70
- line-height: .88;
71
- font-weight: 950;
72
- letter-spacing: 0;
73
- text-shadow:
74
- 0 0 28px rgba(49, 255, 122, .3),
75
- 0 0 94px rgba(49, 255, 122, .2);
59
+ background:
60
+ radial-gradient(circle at 12% 30%, rgba(32, 245, 104, .08), transparent 22%),
61
+ radial-gradient(circle at 88% 66%, rgba(32, 245, 104, .07), transparent 24%);
76
62
  }
77
63
  body::after {
78
64
  z-index: 0;
@@ -87,6 +73,27 @@ body::after {
87
73
  mask-image: radial-gradient(ellipse at center, #000 0 58%, transparent 82%);
88
74
  }
89
75
 
76
+ #bg-brand {
77
+ position: fixed;
78
+ inset: 0;
79
+ z-index: 0;
80
+ pointer-events: none;
81
+ overflow: hidden;
82
+ }
83
+
84
+ .brand-mark {
85
+ position: absolute;
86
+ color: rgba(32, 245, 104, .18);
87
+ font-size: clamp(22px, 3vw, 46px);
88
+ line-height: .9;
89
+ font-weight: 950;
90
+ text-shadow: 0 0 26px rgba(32, 245, 104, .22);
91
+ }
92
+ .brand-mark.m1 { left: 5vw; top: 8vh; transform: rotate(-6deg); }
93
+ .brand-mark.m2 { right: 5vw; top: 14vh; text-align: right; transform: rotate(5deg); }
94
+ .brand-mark.m3 { left: 7vw; bottom: 9vh; transform: rotate(4deg); }
95
+ .brand-mark.m4 { right: 7vw; bottom: 12vh; text-align: right; transform: rotate(-5deg); }
96
+
90
97
  ::-webkit-scrollbar { width: 4px; height: 4px; }
91
98
  ::-webkit-scrollbar-track { background: transparent; }
92
99
  ::-webkit-scrollbar-thumb { background: #2b3c30; border-radius: 2px; }
@@ -104,10 +111,10 @@ body::after {
104
111
  min-height: 520px;
105
112
  display: flex;
106
113
  overflow: hidden;
107
- background: rgba(8, 10, 9, .96);
108
- border: 1px solid rgba(47, 66, 53, .68);
114
+ background: rgba(2, 3, 2, .98);
115
+ border: 1px solid rgba(32, 245, 104, .28);
109
116
  border-radius: var(--radius);
110
- box-shadow: 0 28px 90px rgba(0, 0, 0, .42);
117
+ box-shadow: 0 28px 90px rgba(0, 0, 0, .62);
111
118
  }
112
119
 
113
120
  #sidebar {
@@ -211,7 +218,7 @@ body::after {
211
218
  display: flex;
212
219
  flex-direction: column;
213
220
  overflow: hidden;
214
- background: rgba(10, 12, 11, .94);
221
+ background: rgba(3, 4, 3, .98);
215
222
  }
216
223
 
217
224
  #topbar {
@@ -232,7 +239,7 @@ body::after {
232
239
  gap: 12px;
233
240
  }
234
241
  .tb-icon {
235
- color: #6e7284;
242
+ color: #87918a;
236
243
  display: inline-flex;
237
244
  }
238
245
  .tb-icon svg {
@@ -246,12 +253,12 @@ body::after {
246
253
  display: block;
247
254
  font-size: 19px;
248
255
  font-weight: 800;
249
- color: #5e6070;
256
+ color: #a8b2ab;
250
257
  }
251
258
  .top-secondary {
252
259
  display: block;
253
260
  margin-top: 1px;
254
- color: #b9b5c6;
261
+ color: #31f474;
255
262
  font-size: 11px;
256
263
  font-weight: 800;
257
264
  text-transform: uppercase;
@@ -272,7 +279,7 @@ body::after {
272
279
  border: 1px solid transparent;
273
280
  border-radius: 4px;
274
281
  background: transparent;
275
- color: #6c6978;
282
+ color: #89948c;
276
283
  padding: 7px 10px;
277
284
  cursor: pointer;
278
285
  font-family: var(--font);
@@ -280,7 +287,7 @@ body::after {
280
287
  font-weight: 800;
281
288
  }
282
289
  .sub-tab.active {
283
- color: #fff;
290
+ color: #f7fff9;
284
291
  border-color: rgba(49, 255, 122, .34);
285
292
  background: rgba(49, 255, 122, .08);
286
293
  }
@@ -340,7 +347,7 @@ body::after {
340
347
 
341
348
  .child {
342
349
  min-width: 0;
343
- background: rgba(21, 21, 27, .72);
350
+ background: rgba(10, 11, 10, .94);
344
351
  border: 1px solid rgba(43, 55, 47, .78);
345
352
  border-top-color: rgba(49, 255, 122, .72);
346
353
  border-radius: 8px;
@@ -350,7 +357,7 @@ body::after {
350
357
  .child:hover { border-color: rgba(68, 93, 76, .9); border-top-color: rgba(49, 255, 122, .95); }
351
358
  .child h3 {
352
359
  font-size: 12px;
353
- color: #6f6b79;
360
+ color: #95a39a;
354
361
  margin-bottom: 16px;
355
362
  font-weight: 900;
356
363
  letter-spacing: 0;
@@ -374,7 +381,7 @@ body::after {
374
381
  .ctrl:last-child { margin-bottom: 0; }
375
382
  .ctrl > label:not(.tgl) {
376
383
  min-width: 0;
377
- color: #666371;
384
+ color: #aeb8b0;
378
385
  font-size: 13px;
379
386
  line-height: 1.25;
380
387
  font-weight: 800;
@@ -382,7 +389,7 @@ body::after {
382
389
  text-overflow: ellipsis;
383
390
  white-space: nowrap;
384
391
  }
385
- .ctrl:hover > label:not(.tgl) { color: #aaa6b8; }
392
+ .ctrl:hover > label:not(.tgl) { color: #eef6f0; }
386
393
 
387
394
  .tgl {
388
395
  width: 34px;
@@ -398,8 +405,8 @@ body::after {
398
405
  inset: 0;
399
406
  cursor: pointer;
400
407
  border-radius: 999px;
401
- background: #222433;
402
- border: 1px solid #2c2d40;
408
+ background: #171a17;
409
+ border: 1px solid #2a322d;
403
410
  transition: background .18s ease, border-color .18s ease, box-shadow .18s ease;
404
411
  }
405
412
  .tgl .knob {
@@ -409,7 +416,7 @@ body::after {
409
416
  width: 12px;
410
417
  height: 12px;
411
418
  border-radius: 50%;
412
- background: #595e74;
419
+ background: #69736b;
413
420
  transform: translateY(-50%);
414
421
  transition: transform .18s ease, background .18s ease;
415
422
  }
@@ -424,12 +431,13 @@ body::after {
424
431
  .rng {
425
432
  width: 100%;
426
433
  display: grid;
427
- grid-template-columns: 44px 1fr;
434
+ grid-template-columns: 54px 1fr;
428
435
  align-items: center;
429
- gap: 12px;
436
+ gap: 14px;
437
+ padding: 2px 0 4px;
430
438
  }
431
439
  .rng-val {
432
- color: #6f6b79;
440
+ color: #d3ded6;
433
441
  font-size: 12px;
434
442
  font-weight: 900;
435
443
  text-align: right;
@@ -438,20 +446,50 @@ body::after {
438
446
  .rng input[type=range] {
439
447
  -webkit-appearance: none;
440
448
  width: 100%;
441
- height: 6px;
442
- background: #111118;
449
+ height: 22px;
450
+ background: transparent;
443
451
  border-radius: 999px;
444
452
  outline: none;
453
+ cursor: pointer;
454
+ --range-percent: 0%;
455
+ }
456
+ .rng input[type=range]::-webkit-slider-runnable-track {
457
+ height: 8px;
458
+ border-radius: 999px;
459
+ border: 1px solid rgba(32, 245, 104, .2);
460
+ background:
461
+ linear-gradient(90deg, rgba(32, 245, 104, .78) 0 var(--range-percent), #1c221e var(--range-percent) 100%);
462
+ box-shadow: inset 0 0 0 1px rgba(0,0,0,.35);
445
463
  }
446
464
  .rng input[type=range]::-webkit-slider-thumb {
447
465
  -webkit-appearance: none;
448
- width: 16px;
449
- height: 16px;
466
+ width: 18px;
467
+ height: 18px;
468
+ margin-top: -6px;
450
469
  border-radius: 50%;
451
- background: #516558;
452
- border: 0;
470
+ background: #0f1511;
471
+ border: 3px solid #5d8269;
472
+ cursor: pointer;
473
+ box-shadow: 0 0 0 3px rgba(32, 245, 104, .1), 0 0 18px rgba(32, 245, 104, .18);
474
+ }
475
+ .rng input[type=range]::-moz-range-track {
476
+ height: 8px;
477
+ border-radius: 999px;
478
+ border: 1px solid rgba(32, 245, 104, .2);
479
+ background: #1c221e;
480
+ }
481
+ .rng input[type=range]::-moz-range-progress {
482
+ height: 8px;
483
+ border-radius: 999px;
484
+ background: rgba(32, 245, 104, .78);
485
+ }
486
+ .rng input[type=range]::-moz-range-thumb {
487
+ width: 18px;
488
+ height: 18px;
489
+ border-radius: 50%;
490
+ background: #0f1511;
491
+ border: 3px solid #5d8269;
453
492
  cursor: pointer;
454
- box-shadow: 0 0 0 3px rgba(49, 255, 122, .1);
455
493
  }
456
494
  .rng input[type=range]:active::-webkit-slider-thumb { background: var(--accent); }
457
495
 
@@ -461,15 +499,15 @@ body::after {
461
499
  min-width: 130px;
462
500
  max-width: 170px;
463
501
  height: 34px;
464
- border: 1px solid #1c1c25;
502
+ border: 1px solid #242d27;
465
503
  border-radius: 4px;
466
- background: #18181f;
467
- color: #d8d5e3;
504
+ background: #0d0f0d;
505
+ color: #ecf4ee;
468
506
  font: 800 12px var(--font);
469
507
  outline: none;
470
508
  padding: 0 10px;
471
509
  }
472
- .ctrl select { color: #787485; cursor: pointer; }
510
+ .ctrl select { color: #c1cbc4; cursor: pointer; }
473
511
  .ctrl input:focus,
474
512
  .ctrl select:focus { border-color: rgba(49, 255, 122, .7); }
475
513
 
@@ -519,10 +557,10 @@ body::after {
519
557
  gap: 10px;
520
558
  margin-top: 8px;
521
559
  padding: 0 14px;
522
- border: 1px solid rgba(47, 66, 53, .68);
560
+ border: 1px solid rgba(32, 245, 104, .24);
523
561
  border-radius: var(--radius);
524
- background: rgba(8, 10, 9, .94);
525
- color: #716f80;
562
+ background: rgba(2, 3, 2, .96);
563
+ color: #9aa69f;
526
564
  font-size: 12px;
527
565
  }
528
566
  #statusbar .s-dot {
@@ -561,17 +599,17 @@ body::after {
561
599
  align-items: center;
562
600
  justify-content: center;
563
601
  z-index: 1000;
564
- background: rgba(7, 7, 11, .96);
602
+ background: #000;
565
603
  }
566
604
  #license-box {
567
605
  width: min(410px, calc(100vw - 34px));
568
606
  padding: 34px 28px;
569
607
  text-align: center;
570
- background: #141419;
571
- border: 1px solid rgba(49, 255, 122, .28);
608
+ background: #050605;
609
+ border: 1px solid rgba(32, 245, 104, .32);
572
610
  border-top-color: var(--accent);
573
611
  border-radius: 8px;
574
- box-shadow: 0 24px 70px rgba(0,0,0,.5);
612
+ box-shadow: 0 24px 70px rgba(0,0,0,.88);
575
613
  }
576
614
  #license-box .logo-icon { width: 56px; height: 56px; margin: 0 auto 14px; object-fit: contain; filter: drop-shadow(0 0 16px rgba(49, 255, 122, .24)); }
577
615
  #license-box h1 { font-size: 23px; font-weight: 900; margin-bottom: 4px; }
@@ -580,8 +618,8 @@ body::after {
580
618
  #license-box input[type=text] {
581
619
  width: 100%;
582
620
  height: 42px;
583
- background: #18181f;
584
- border: 1px solid #262633;
621
+ background: #0a0c0a;
622
+ border: 1px solid #26302a;
585
623
  color: #fff;
586
624
  padding: 0 14px;
587
625
  border-radius: 4px;
@@ -613,12 +651,12 @@ body::after {
613
651
  align-items: center;
614
652
  justify-content: center;
615
653
  z-index: 2000;
616
- background: rgba(7, 7, 11, .93);
654
+ background: rgba(0, 0, 0, .96);
617
655
  }
618
656
  #qr-overlay.show { display: flex; }
619
657
  #qr-box {
620
658
  width: min(340px, calc(100vw - 30px));
621
- background: #141419;
659
+ background: #050605;
622
660
  border: 1px solid rgba(49, 255, 122, .3);
623
661
  border-radius: 8px;
624
662
  padding: 22px;
@@ -630,7 +668,7 @@ body::after {
630
668
  .esp-preview-panel {
631
669
  height: 100%;
632
670
  min-height: 430px;
633
- background: rgba(13, 16, 14, .86);
671
+ background: rgba(8, 10, 8, .96);
634
672
  border: 1px solid rgba(43, 55, 47, .78);
635
673
  border-top-color: rgba(49, 255, 122, .78);
636
674
  border-radius: 8px;
@@ -852,8 +890,8 @@ body::after {
852
890
  border-radius: 0;
853
891
  }
854
892
  #sidebar {
855
- width: 74px;
856
- flex-basis: 74px;
893
+ width: 66px;
894
+ flex-basis: 66px;
857
895
  }
858
896
  .brand {
859
897
  height: 58px;
@@ -869,8 +907,8 @@ body::after {
869
907
  display: none;
870
908
  }
871
909
  .tab-btn {
872
- width: 58px;
873
- height: 50px;
910
+ width: 52px;
911
+ height: 48px;
874
912
  padding: 0;
875
913
  justify-content: center;
876
914
  gap: 0;
@@ -880,7 +918,7 @@ body::after {
880
918
  #topbar {
881
919
  height: auto;
882
920
  min-height: 64px;
883
- padding: 10px 12px;
921
+ padding: 10px 14px;
884
922
  flex-wrap: wrap;
885
923
  gap: 8px;
886
924
  }
@@ -907,8 +945,8 @@ body::after {
907
945
  flex: 0 0 auto;
908
946
  overflow: visible;
909
947
  grid-template-columns: 1fr;
910
- gap: 12px;
911
- padding: 12px;
948
+ gap: 14px;
949
+ padding: 14px;
912
950
  }
913
951
  #workspace.has-preview #pages {
914
952
  flex-basis: auto;
@@ -916,14 +954,14 @@ body::after {
916
954
  #esp-preview-shell {
917
955
  order: -1;
918
956
  flex: 0 0 auto;
919
- padding: 12px 12px 0;
957
+ padding: 14px 14px 0;
920
958
  overflow: visible;
921
959
  }
922
960
  .child {
923
- padding: 15px 14px;
961
+ padding: 17px 16px;
924
962
  }
925
963
  .ctrl {
926
- min-height: 42px;
964
+ min-height: 46px;
927
965
  gap: 12px;
928
966
  }
929
967
  .ctrl > label:not(.tgl) {
@@ -973,18 +1011,18 @@ body::after {
973
1011
 
974
1012
  @media (max-width: 600px) {
975
1013
  #sidebar {
976
- width: 58px;
977
- flex-basis: 58px;
1014
+ width: 54px;
1015
+ flex-basis: 54px;
978
1016
  }
979
1017
  .tab-btn {
980
- width: 44px;
1018
+ width: 42px;
981
1019
  height: 44px;
982
1020
  }
983
1021
  .tab-btn svg { width: 17px; height: 17px; }
984
1022
  .brand { font-size: 0; }
985
1023
  #topbar { padding: 9px 10px; }
986
1024
  .sub-tab { font-size: 10px; padding: 6px 8px; }
987
- .rng { grid-template-columns: 38px 1fr; gap: 8px; }
1025
+ .rng { grid-template-columns: 46px 1fr; gap: 10px; }
988
1026
  .color-row { align-items: flex-start; }
989
1027
  .color-actions { flex-shrink: 0; }
990
1028
  .esp-stage { height: 280px; min-height: 280px; }
package/public/index.html CHANGED
@@ -10,6 +10,13 @@
10
10
  </head>
11
11
  <body>
12
12
 
13
+ <div id="bg-brand" aria-hidden="true">
14
+ <span class="brand-mark m1">SENSIVITY<br>BEST PRIVATE</span>
15
+ <span class="brand-mark m2">SENSIVITY<br>BEST PRIVATE</span>
16
+ <span class="brand-mark m3">SENSIVITY<br>BEST PRIVATE</span>
17
+ <span class="brand-mark m4">SENSIVITY<br>BEST PRIVATE</span>
18
+ </div>
19
+
13
20
  <div id="license-overlay">
14
21
  <div id="license-box">
15
22
  <img class="logo-icon" src="/assets/logo.png" alt="Sensivity">
package/public/js/app.js CHANGED
@@ -100,9 +100,9 @@ function ctrl(c) {
100
100
  case 'checkbox':
101
101
  return `<div class="ctrl"><label>${prettyLabel(c.key)}</label><label class="tgl"><input type="checkbox" id="${id}"${vs ? ' checked' : ''} onchange="set('${c.key}','checkbox',this.checked)"><span class="track"><span class="knob"></span></span></label></div>`;
102
102
  case 'slider_int':
103
- return `<div class="ctrl range-ctrl"><label>${prettyLabel(c.key)}</label><div class="rng"><span class="rng-val">${vs || 0}</span><input type="range" id="${id}" min="${c.min || 0}" max="${c.max || 100}" value="${vs || 0}" oninput="sliderUp('${c.key}','slider_int',this)"></div></div>`;
103
+ return `<div class="ctrl range-ctrl"><label>${prettyLabel(c.key)}</label><div class="rng"><span class="rng-val">${vs || 0}</span><input type="range" id="${id}" min="${c.min || 0}" max="${c.max || 100}" value="${vs || 0}" style="--range-percent:${rangePercent(c, vs || 0)}%" oninput="sliderUp('${c.key}','slider_int',this)"></div></div>`;
104
104
  case 'slider_float':
105
- return `<div class="ctrl range-ctrl"><label>${prettyLabel(c.key)}</label><div class="rng"><span class="rng-val">${parseFloat(vs || 0).toFixed(1)}</span><input type="range" id="${id}" min="${c.min || 0}" max="${c.max || 100}" step="${c.step || 0.1}" value="${vs || 0}" oninput="sliderUp('${c.key}','slider_float',this)"></div></div>`;
105
+ return `<div class="ctrl range-ctrl"><label>${prettyLabel(c.key)}</label><div class="rng"><span class="rng-val">${parseFloat(vs || 0).toFixed(1)}</span><input type="range" id="${id}" min="${c.min || 0}" max="${c.max || 100}" step="${c.step || 0.1}" value="${vs || 0}" style="--range-percent:${rangePercent(c, vs || 0)}%" oninput="sliderUp('${c.key}','slider_float',this)"></div></div>`;
106
106
  case 'dropdown':
107
107
  return `<div class="ctrl"><label>${prettyLabel(c.key)}</label><select id="${id}" onchange="set('${c.key}','dropdown',this.selectedIndex)">${(c.items || []).map((x, i) => `<option value="${i}"${i === vs ? ' selected' : ''}>${x}</option>`).join('')}</select></div>`;
108
108
  case 'color_checkbox': {
@@ -133,6 +133,7 @@ function sliderUp(k, t, el) {
133
133
  const v = t === 'slider_int' ? parseInt(el.value) : parseFloat(el.value);
134
134
  state[k] = v;
135
135
  el.previousElementSibling.textContent = t === 'slider_int' ? v : v.toFixed(1);
136
+ updateRangeFill(el);
136
137
  emit(k, t, v);
137
138
  queuePlayerEspSync(k);
138
139
  if (shouldShowEspPreview()) updateEspPreview();
@@ -152,6 +153,22 @@ function emit(k, t, v) {
152
153
  if (SOCKET && SOCKET.connected) SOCKET.emit('setConfig', { key: k, type: t, value: v });
153
154
  }
154
155
 
156
+ function rangePercent(c, value) {
157
+ const min = Number(c.min || 0);
158
+ const max = Number(c.max || 100);
159
+ const val = Number(value || 0);
160
+ if (max <= min) return 0;
161
+ return Math.max(0, Math.min(100, ((val - min) / (max - min)) * 100)).toFixed(2);
162
+ }
163
+
164
+ function updateRangeFill(el) {
165
+ const min = Number(el.min || 0);
166
+ const max = Number(el.max || 100);
167
+ const val = Number(el.value || 0);
168
+ const pct = max <= min ? 0 : Math.max(0, Math.min(100, ((val - min) / (max - min)) * 100));
169
+ el.style.setProperty('--range-percent', pct.toFixed(2) + '%');
170
+ }
171
+
155
172
  function visualPlayerControls() {
156
173
  const visuals = SCHEMA.sections.find(s => s.id === 'visuals');
157
174
  const player = visuals && (visuals.subs || []).find(sub => sub.id === 'vplayer');
@@ -240,8 +257,10 @@ document.addEventListener('mousedown', function(e) {
240
257
  });
241
258
 
242
259
  function toggleCheat() {
243
- if (!SOCKET || !SOCKET.connected) return;
244
- SOCKET.emit(cheatRunning ? 'stopCheat' : 'startCheat');
260
+ if (!SOCKET) return;
261
+ const eventName = cheatRunning ? 'stopCheat' : 'startCheat';
262
+ SOCKET.emit(eventName);
263
+ if (!SOCKET.connected && typeof SOCKET.connect === 'function') SOCKET.connect();
245
264
  }
246
265
 
247
266
  function updateStatus() {