moidevx 1.0.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,784 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Who's Watching?</title>
6
+ <style>
7
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
8
+
9
+ * { margin: 0; padding: 0; box-sizing: border-box; }
10
+
11
+ body {
12
+ background: #0a0a0f;
13
+ font-family: 'Inter', sans-serif;
14
+ color: #e5e5e5;
15
+ height: 100vh;
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: center;
19
+ justify-content: center;
20
+ overflow: hidden;
21
+ user-select: none;
22
+ }
23
+
24
+ body::before {
25
+ content: '';
26
+ position: fixed;
27
+ inset: 0;
28
+ background: radial-gradient(ellipse at 20% 50%, rgba(88, 28, 135, 0.15) 0%, transparent 60%),
29
+ radial-gradient(ellipse at 80% 20%, rgba(15, 118, 110, 0.12) 0%, transparent 50%),
30
+ radial-gradient(ellipse at 50% 90%, rgba(190, 24, 93, 0.08) 0%, transparent 50%);
31
+ animation: ambientShift 12s ease-in-out infinite alternate;
32
+ z-index: 0;
33
+ }
34
+
35
+ @keyframes ambientShift {
36
+ 0% { opacity: 0.6; transform: scale(1); }
37
+ 100% { opacity: 1; transform: scale(1.1); }
38
+ }
39
+
40
+ .top-bar {
41
+ position: fixed;
42
+ top: 0; left: 0; right: 0;
43
+ height: 50px;
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: center;
47
+ z-index: 100;
48
+ background: linear-gradient(180deg, rgba(0,0,0,0.6) 0%, transparent 100%);
49
+ }
50
+
51
+ .logo {
52
+ font-size: 20px;
53
+ font-weight: 700;
54
+ letter-spacing: 2px;
55
+ background: linear-gradient(135deg, #a78bfa, #ec4899, #f97316);
56
+ -webkit-background-clip: text;
57
+ -webkit-text-fill-color: transparent;
58
+ text-transform: uppercase;
59
+ }
60
+
61
+ .main {
62
+ position: relative;
63
+ z-index: 10;
64
+ text-align: center;
65
+ animation: fadeInUp 0.6s ease-out;
66
+ }
67
+
68
+ @keyframes fadeInUp {
69
+ from { opacity: 0; transform: translateY(30px); }
70
+ to { opacity: 1; transform: translateY(0); }
71
+ }
72
+
73
+ h1 {
74
+ font-size: 28px;
75
+ font-weight: 600;
76
+ color: #f0f0f0;
77
+ margin-bottom: 8px;
78
+ letter-spacing: 0.5px;
79
+ }
80
+
81
+ .subtitle {
82
+ font-size: 13px;
83
+ color: rgba(255,255,255,0.4);
84
+ margin-bottom: 32px;
85
+ }
86
+
87
+ /* AI Service Tabs */
88
+ .service-tabs {
89
+ display: flex;
90
+ gap: 6px;
91
+ justify-content: center;
92
+ margin-bottom: 28px;
93
+ }
94
+
95
+ .service-tab {
96
+ padding: 8px 20px;
97
+ border-radius: 20px;
98
+ border: 1px solid rgba(255,255,255,0.08);
99
+ background: rgba(255,255,255,0.04);
100
+ color: rgba(255,255,255,0.5);
101
+ font-size: 13px;
102
+ font-weight: 500;
103
+ cursor: pointer;
104
+ transition: all 0.3s ease;
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 6px;
108
+ }
109
+
110
+ .service-tab:hover {
111
+ background: rgba(255,255,255,0.08);
112
+ color: #fff;
113
+ border-color: rgba(255,255,255,0.15);
114
+ transform: translateY(-1px);
115
+ }
116
+
117
+ .service-tab.active {
118
+ border-color: transparent;
119
+ color: #fff;
120
+ font-weight: 600;
121
+ }
122
+
123
+ .service-tab.active[data-service="chatgpt"] {
124
+ background: linear-gradient(135deg, rgba(16,163,127,0.3), rgba(16,163,127,0.1));
125
+ box-shadow: 0 0 20px rgba(16,163,127,0.15);
126
+ }
127
+ .service-tab.active[data-service="claude"] {
128
+ background: linear-gradient(135deg, rgba(204,120,50,0.3), rgba(204,120,50,0.1));
129
+ box-shadow: 0 0 20px rgba(204,120,50,0.15);
130
+ }
131
+ .service-tab.active[data-service="gemini"] {
132
+ background: linear-gradient(135deg, rgba(66,133,244,0.3), rgba(66,133,244,0.1));
133
+ box-shadow: 0 0 20px rgba(66,133,244,0.15);
134
+ }
135
+
136
+ .service-dot {
137
+ width: 8px; height: 8px;
138
+ border-radius: 50%;
139
+ display: inline-block;
140
+ }
141
+ .service-tab[data-service="chatgpt"] .service-dot { background: #10a37f; }
142
+ .service-tab[data-service="claude"] .service-dot { background: #cc7832; }
143
+ .service-tab[data-service="gemini"] .service-dot { background: #4285f4; }
144
+
145
+ /* Profile Grid */
146
+ .profiles {
147
+ display: flex;
148
+ gap: 24px;
149
+ justify-content: center;
150
+ flex-wrap: wrap;
151
+ max-width: 600px;
152
+ }
153
+
154
+ .profile {
155
+ display: flex;
156
+ flex-direction: column;
157
+ align-items: center;
158
+ gap: 10px;
159
+ cursor: pointer;
160
+ transition: all 0.3s ease;
161
+ padding: 12px;
162
+ border-radius: 12px;
163
+ position: relative;
164
+ }
165
+
166
+ .profile:hover { transform: scale(1.08); }
167
+
168
+ .profile:hover .avatar {
169
+ border-color: rgba(255,255,255,0.5);
170
+ box-shadow: 0 0 30px rgba(255,255,255,0.1);
171
+ }
172
+
173
+ .avatar {
174
+ width: 90px;
175
+ height: 90px;
176
+ border-radius: 14px;
177
+ display: flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ font-size: 36px;
181
+ font-weight: 700;
182
+ color: #fff;
183
+ border: 2px solid transparent;
184
+ transition: all 0.3s ease;
185
+ position: relative;
186
+ overflow: hidden;
187
+ }
188
+
189
+ .avatar::after {
190
+ content: '';
191
+ position: absolute;
192
+ inset: 0;
193
+ background: linear-gradient(135deg, rgba(255,255,255,0.15) 0%, transparent 50%);
194
+ border-radius: inherit;
195
+ }
196
+
197
+ .profile-name {
198
+ font-size: 13px;
199
+ color: rgba(255,255,255,0.6);
200
+ font-weight: 500;
201
+ transition: color 0.2s;
202
+ }
203
+
204
+ .profile:hover .profile-name { color: #fff; }
205
+
206
+ /* Token status badges under avatar */
207
+ .token-badges {
208
+ display: flex;
209
+ gap: 4px;
210
+ justify-content: center;
211
+ }
212
+
213
+ .token-badge {
214
+ width: 7px; height: 7px;
215
+ border-radius: 50%;
216
+ opacity: 0.25;
217
+ }
218
+
219
+ .token-badge.has-token { opacity: 1; }
220
+ .token-badge.gpt { background: #10a37f; }
221
+ .token-badge.cld { background: #cc7832; }
222
+ .token-badge.gem { background: #4285f4; }
223
+
224
+ /* Edit button on hover */
225
+ .edit-btn {
226
+ position: absolute;
227
+ top: 4px; right: 4px;
228
+ width: 22px; height: 22px;
229
+ border-radius: 6px;
230
+ background: rgba(0,0,0,0.6);
231
+ border: 1px solid rgba(255,255,255,0.15);
232
+ color: rgba(255,255,255,0.6);
233
+ font-size: 11px;
234
+ cursor: pointer;
235
+ display: none;
236
+ align-items: center;
237
+ justify-content: center;
238
+ z-index: 5;
239
+ }
240
+
241
+ .profile:hover .edit-btn { display: flex; }
242
+ .edit-btn:hover { background: rgba(255,255,255,0.15); color: #fff; }
243
+
244
+ /* No-token warning */
245
+ .no-token-warning {
246
+ font-size: 10px;
247
+ color: rgba(239,68,68,0.7);
248
+ margin-top: -4px;
249
+ }
250
+
251
+ /* Add Profile Button */
252
+ .profile.add-profile .avatar {
253
+ background: rgba(255,255,255,0.05);
254
+ border: 2px dashed rgba(255,255,255,0.15);
255
+ font-size: 28px;
256
+ color: rgba(255,255,255,0.3);
257
+ }
258
+
259
+ .profile.add-profile:hover .avatar {
260
+ border-color: rgba(255,255,255,0.3);
261
+ background: rgba(255,255,255,0.08);
262
+ }
263
+
264
+ /* Loading State */
265
+ .loading-overlay {
266
+ position: fixed;
267
+ inset: 0;
268
+ background: rgba(10,10,15,0.95);
269
+ display: none;
270
+ flex-direction: column;
271
+ align-items: center;
272
+ justify-content: center;
273
+ z-index: 1000;
274
+ }
275
+
276
+ .loading-overlay.active {
277
+ display: flex;
278
+ animation: fadeIn 0.3s ease;
279
+ }
280
+
281
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
282
+
283
+ .loading-spinner {
284
+ width: 48px; height: 48px;
285
+ border: 3px solid rgba(255,255,255,0.1);
286
+ border-top-color: #a78bfa;
287
+ border-radius: 50%;
288
+ animation: spin 0.8s linear infinite;
289
+ margin-bottom: 16px;
290
+ }
291
+
292
+ @keyframes spin { to { transform: rotate(360deg); } }
293
+
294
+ .loading-text {
295
+ font-size: 14px;
296
+ color: rgba(255,255,255,0.6);
297
+ }
298
+
299
+ /* Modal */
300
+ .modal-overlay {
301
+ position: fixed;
302
+ inset: 0;
303
+ background: rgba(0,0,0,0.7);
304
+ backdrop-filter: blur(8px);
305
+ display: none;
306
+ align-items: center;
307
+ justify-content: center;
308
+ z-index: 500;
309
+ }
310
+
311
+ .modal-overlay.active { display: flex; }
312
+
313
+ .modal {
314
+ background: #1a1a24;
315
+ border: 1px solid rgba(255,255,255,0.08);
316
+ border-radius: 16px;
317
+ padding: 28px;
318
+ width: 420px;
319
+ max-height: 85vh;
320
+ overflow-y: auto;
321
+ animation: modalIn 0.3s ease;
322
+ }
323
+
324
+ @keyframes modalIn {
325
+ from { opacity: 0; transform: scale(0.95) translateY(10px); }
326
+ to { opacity: 1; transform: scale(1) translateY(0); }
327
+ }
328
+
329
+ .modal h2 {
330
+ font-size: 18px;
331
+ margin-bottom: 20px;
332
+ font-weight: 600;
333
+ }
334
+
335
+ .modal label {
336
+ display: flex;
337
+ align-items: center;
338
+ gap: 6px;
339
+ font-size: 12px;
340
+ color: rgba(255,255,255,0.5);
341
+ margin-bottom: 6px;
342
+ margin-top: 14px;
343
+ }
344
+
345
+ .modal label .svc-dot {
346
+ width: 6px; height: 6px;
347
+ border-radius: 50%;
348
+ display: inline-block;
349
+ }
350
+
351
+ .modal input, .modal textarea {
352
+ width: 100%;
353
+ padding: 10px 12px;
354
+ border-radius: 8px;
355
+ border: 1px solid rgba(255,255,255,0.1);
356
+ background: rgba(255,255,255,0.05);
357
+ color: #fff;
358
+ font-size: 13px;
359
+ font-family: 'Inter', sans-serif;
360
+ outline: none;
361
+ transition: border-color 0.2s;
362
+ }
363
+
364
+ .modal input:focus, .modal textarea:focus {
365
+ border-color: rgba(167,139,250,0.5);
366
+ }
367
+
368
+ .modal textarea {
369
+ height: 55px;
370
+ resize: vertical;
371
+ font-family: 'Consolas', monospace;
372
+ font-size: 11px;
373
+ }
374
+
375
+ .token-hint {
376
+ font-size: 10px;
377
+ color: rgba(255,255,255,0.25);
378
+ margin-top: 3px;
379
+ }
380
+
381
+ .modal-actions {
382
+ display: flex;
383
+ gap: 10px;
384
+ margin-top: 20px;
385
+ justify-content: space-between;
386
+ }
387
+
388
+ .modal-actions-right {
389
+ display: flex;
390
+ gap: 10px;
391
+ }
392
+
393
+ .btn {
394
+ padding: 8px 20px;
395
+ border-radius: 8px;
396
+ border: none;
397
+ font-size: 13px;
398
+ font-weight: 500;
399
+ cursor: pointer;
400
+ transition: all 0.2s;
401
+ font-family: 'Inter', sans-serif;
402
+ }
403
+
404
+ .btn-cancel {
405
+ background: rgba(255,255,255,0.06);
406
+ color: rgba(255,255,255,0.6);
407
+ }
408
+ .btn-cancel:hover { background: rgba(255,255,255,0.1); color: #fff; }
409
+
410
+ .btn-delete {
411
+ background: rgba(239,68,68,0.15);
412
+ color: rgba(239,68,68,0.8);
413
+ }
414
+ .btn-delete:hover { background: rgba(239,68,68,0.25); color: #ef4444; }
415
+
416
+ .btn-save {
417
+ background: linear-gradient(135deg, #7c3aed, #a78bfa);
418
+ color: #fff;
419
+ }
420
+ .btn-save:hover { transform: translateY(-1px); box-shadow: 0 4px 15px rgba(124,58,237,0.3); }
421
+
422
+ .color-options {
423
+ display: flex;
424
+ gap: 8px;
425
+ margin-top: 6px;
426
+ }
427
+
428
+ .color-swatch {
429
+ width: 28px; height: 28px;
430
+ border-radius: 8px;
431
+ cursor: pointer;
432
+ border: 2px solid transparent;
433
+ transition: all 0.2s;
434
+ }
435
+
436
+ .color-swatch:hover, .color-swatch.selected {
437
+ border-color: #fff;
438
+ transform: scale(1.1);
439
+ }
440
+ </style>
441
+ </head>
442
+ <body>
443
+
444
+ <div class="top-bar">
445
+ <div class="logo">F society</div>
446
+ </div>
447
+
448
+ <div class="main">
449
+ <h1>Who's using?</h1>
450
+ <p class="subtitle">Select your profile &amp; AI service</p>
451
+
452
+ <div class="service-tabs" id="serviceTabs">
453
+ <div class="service-tab active" data-service="chatgpt" onclick="selectService('chatgpt')">
454
+ <span class="service-dot"></span> ChatGPT
455
+ </div>
456
+ <div class="service-tab" data-service="claude" onclick="selectService('claude')">
457
+ <span class="service-dot"></span> Claude
458
+ </div>
459
+ <div class="service-tab" data-service="gemini" onclick="selectService('gemini')">
460
+ <span class="service-dot"></span> Gemini
461
+ </div>
462
+ </div>
463
+
464
+ <div class="profiles" id="profileGrid"></div>
465
+ </div>
466
+
467
+ <div class="loading-overlay" id="loadingOverlay">
468
+ <div class="loading-spinner"></div>
469
+ <div class="loading-text" id="loadingText">Signing in...</div>
470
+ <button class="btn btn-cancel" style="margin-top:20px" onclick="document.getElementById('loadingOverlay').classList.remove('active')">Cancel</button>
471
+ </div>
472
+
473
+ <div class="modal-overlay" id="modalOverlay" onclick="if(event.target===this)closeModal()">
474
+ <div class="modal">
475
+ <h2 id="modalTitle">Add Profile</h2>
476
+ <label>Display Name</label>
477
+ <input type="text" id="inputName" placeholder="e.g. Sagar" maxlength="20">
478
+
479
+ <label>Avatar Color</label>
480
+ <div class="color-options" id="colorOptions"></div>
481
+
482
+ <label><span class="svc-dot" style="background:#10a37f"></span> ChatGPT Session Token</label>
483
+ <textarea id="inputChatgpt" placeholder="Paste __Secure-next-auth.session-token here..."></textarea>
484
+ <div class="token-hint">Get from browser → F12 → Application → Cookies → chatgpt.com</div>
485
+
486
+ <label><span class="svc-dot" style="background:#cc7832"></span> Claude Session Token</label>
487
+ <textarea id="inputClaude" placeholder="Paste sessionKey cookie here..."></textarea>
488
+ <div class="token-hint">Get from browser → F12 → Application → Cookies → claude.ai</div>
489
+
490
+ <label><span class="svc-dot" style="background:#4285f4"></span> Gemini Session Token</label>
491
+ <textarea id="inputGemini" placeholder="Paste __Secure-1PSID cookie here..."></textarea>
492
+ <div class="token-hint">Get from browser → F12 → Application → Cookies → gemini.google.com</div>
493
+
494
+ <div class="modal-actions">
495
+ <div>
496
+ <button class="btn btn-delete" id="btnDelete" onclick="deleteProfile()" style="display:none">Delete</button>
497
+ </div>
498
+ <div class="modal-actions-right">
499
+ <button class="btn btn-cancel" onclick="closeModal()">Cancel</button>
500
+ <button class="btn btn-save" onclick="saveProfile()">Save</button>
501
+ </div>
502
+ </div>
503
+ </div>
504
+ </div>
505
+
506
+ <script>
507
+ const AVATAR_COLORS = [
508
+ 'linear-gradient(135deg, #7c3aed, #a78bfa)',
509
+ 'linear-gradient(135deg, #dc2626, #f87171)',
510
+ 'linear-gradient(135deg, #0891b2, #22d3ee)',
511
+ 'linear-gradient(135deg, #059669, #34d399)',
512
+ 'linear-gradient(135deg, #d97706, #fbbf24)',
513
+ 'linear-gradient(135deg, #db2777, #f472b6)',
514
+ 'linear-gradient(135deg, #4f46e5, #818cf8)',
515
+ 'linear-gradient(135deg, #0f766e, #2dd4bf)'
516
+ ];
517
+
518
+ const SERVICE_URLS = {
519
+ chatgpt: 'https://chatgpt.com/?model=auto',
520
+ claude: 'https://claude.ai/new',
521
+ gemini: 'https://gemini.google.com/app'
522
+ };
523
+
524
+ let selectedService = 'chatgpt';
525
+ let selectedColor = 0;
526
+ let profiles = [];
527
+ let editingIndex = -1; // -1 = add new,// Load profiles from localStorage// Load profiles from localStorage (DISABLED - Hardcoded)
528
+ function loadProfiles() {
529
+ const saved = localStorage.getItem('shadxino_profiles');
530
+ if (saved) {
531
+ try {
532
+ profiles = JSON.parse(saved);
533
+ } catch(e) {
534
+ profiles = [];
535
+ }
536
+ } else {
537
+ profiles = [];
538
+ }
539
+ }
540
+
541
+ function saveProfiles() {
542
+ localStorage.setItem('shadxino_profiles', JSON.stringify(profiles));
543
+ }
544
+
545
+ // Helper to wipe old/corrupted cookies before fresh injection (preserves Cloudflare)
546
+ const clearCookies = async (domains) => {
547
+ const allCookies = await sess.cookies.get({});
548
+ for (const cookie of allCookies) {
549
+ if (domains.some(d => cookie.domain.includes(d))) {
550
+ if (cookie.name === 'cf_clearance' || cookie.name === '__cf_bm') continue; // DO NOT WIPE CLOUDFLARE
551
+ const url = (cookie.secure ? 'https://' : 'http://') + cookie.domain.replace(/^\./, '');
552
+ await sess.cookies.remove(url, cookie.name).catch(()=>{});
553
+ }
554
+ }
555
+ };
556
+
557
+ function seedConfigTokens(dataJson) {
558
+ try {
559
+ const data = JSON.parse(dataJson);
560
+ loadProfiles(); // Load the hardcoded or stored profiles
561
+
562
+ let updated = false;
563
+
564
+ // If the backend sent a profiles array from config.json, overwrite our profiles array with it
565
+ if (data.profiles && Array.isArray(data.profiles) && data.profiles.length > 0) {
566
+ profiles = data.profiles.map((p, i) => ({
567
+ name: p.name || `Profile ${i+1}`,
568
+ colorIdx: p.colorIdx !== undefined ? p.colorIdx : i,
569
+ tokens: {
570
+ chatgpt: p.chatgptSessionToken || '',
571
+ claude: p.claudeSessionToken || '',
572
+ gemini: p.geminiSessionToken || p.geminiCookies || ''
573
+ }
574
+ }));
575
+ updated = true;
576
+ console.log('[PICKER] Profiles populated from config.json.');
577
+ } else if (profiles.length > 0) {
578
+ // Fallback for older config schema (just base tokens)
579
+ if (data.chatgpt && !profiles[0].tokens.chatgpt) {
580
+ profiles[0].tokens.chatgpt = data.chatgpt;
581
+ updated = true;
582
+ }
583
+ if (data.claude && !profiles[0].tokens.claude) {
584
+ profiles[0].tokens.claude = data.claude;
585
+ updated = true;
586
+ }
587
+ if (data.gemini && !profiles[0].tokens.gemini) {
588
+ profiles[0].tokens.gemini = data.gemini;
589
+ updated = true;
590
+ }
591
+ if (updated) {
592
+ console.log('[PICKER] Config tokens seeded into default profile.');
593
+ }
594
+ }
595
+
596
+ if (updated) {
597
+ saveProfiles();
598
+ renderProfiles();
599
+ }
600
+ } catch(e) {
601
+ console.error('[PICKER] Seed error:', e);
602
+ }
603
+ }
604
+
605
+ function renderProfiles() {
606
+ const grid = document.getElementById('profileGrid');
607
+ let html = '';
608
+
609
+ profiles.forEach((p, i) => {
610
+ const initial = p.name.charAt(0).toUpperCase();
611
+ const gradient = AVATAR_COLORS[p.colorIdx % AVATAR_COLORS.length];
612
+ const hasGpt = p.tokens.chatgpt && p.tokens.chatgpt.length > 10;
613
+ const hasCld = p.tokens.claude && p.tokens.claude.length > 10;
614
+ const hasGem = p.tokens.gemini && p.tokens.gemini.length > 10;
615
+ const hasSelectedToken = (selectedService === 'chatgpt' && hasGpt) ||
616
+ (selectedService === 'claude' && hasCld) ||
617
+ (selectedService === 'gemini' && hasGem);
618
+
619
+ html += `
620
+ <div class="profile" onclick="loginAs(${i})">
621
+ <div class="avatar" style="background:${gradient}">${initial}</div>
622
+ <div class="profile-name">${escapeHtml(p.name)}</div>
623
+ <div class="token-badges">
624
+ <div class="token-badge gpt ${hasGpt ? 'has-token' : ''}" title="ChatGPT ${hasGpt ? '✓' : '✗'}"></div>
625
+ <div class="token-badge cld ${hasCld ? 'has-token' : ''}" title="Claude ${hasCld ? '✓' : '✗'}"></div>
626
+ <div class="token-badge gem ${hasGem ? 'has-token' : ''}" title="Gemini ${hasGem ? '✓' : '✗'}"></div>
627
+ </div>
628
+ ${!hasSelectedToken ? '<div class="no-token-warning">No token for selected AI</div>' : ''}
629
+ </div>
630
+ `;
631
+ });
632
+
633
+
634
+
635
+ grid.innerHTML = html;
636
+ }
637
+
638
+ function selectService(svc) {
639
+ selectedService = svc;
640
+ document.querySelectorAll('.service-tab').forEach(tab => {
641
+ tab.classList.toggle('active', tab.dataset.service === svc);
642
+ });
643
+ renderProfiles(); // Re-render to update "no token" warnings
644
+ }
645
+
646
+ function loginAs(idx) {
647
+ const profile = profiles[idx];
648
+ const token = profile.tokens[selectedService] || '';
649
+ const url = SERVICE_URLS[selectedService];
650
+
651
+ const overlay = document.getElementById('loadingOverlay');
652
+ const loadText = document.getElementById('loadingText');
653
+ overlay.classList.add('active');
654
+ loadText.textContent = `Signing in as ${profile.name} → ${selectedService.charAt(0).toUpperCase() + selectedService.slice(1)}...`;
655
+
656
+ // Signal main process using fetch POST (Bypasses URL limits & Deadlocks)
657
+ fetch('https://ipc.shadxino.internal/login_post', {
658
+ method: 'POST',
659
+ body: JSON.stringify({
660
+ action: 'PROFILE_LOGIN',
661
+ service: selectedService,
662
+ url: url,
663
+ token: token,
664
+ profileName: profile.name,
665
+ _t: Date.now()
666
+ })
667
+ }).catch(() => {}); // Fetch will fail because it's intercepted and cancelled, which is expected
668
+
669
+ setTimeout(() => {
670
+ if (document.getElementById('loadingOverlay').classList.contains('active')) {
671
+ loadText.textContent = "Waiting for backend response...";
672
+ }
673
+ }, 1500);
674
+ }
675
+
676
+ // --- Modal ---
677
+
678
+ function openAddModal() {
679
+ editingIndex = -1;
680
+ document.getElementById('modalTitle').textContent = 'Add Profile';
681
+ document.getElementById('inputName').value = '';
682
+ document.getElementById('inputChatgpt').value = '';
683
+ document.getElementById('inputClaude').value = '';
684
+ document.getElementById('inputGemini').value = '';
685
+ document.getElementById('btnDelete').style.display = 'none';
686
+ selectedColor = profiles.length % AVATAR_COLORS.length;
687
+ renderColorPicker();
688
+ document.getElementById('modalOverlay').classList.add('active');
689
+ setTimeout(() => document.getElementById('inputName').focus(), 100);
690
+ }
691
+
692
+ function openEditModal(idx) {
693
+ editingIndex = idx;
694
+ const p = profiles[idx];
695
+ document.getElementById('modalTitle').textContent = 'Edit Profile';
696
+ document.getElementById('inputName').value = p.name;
697
+ document.getElementById('inputChatgpt').value = p.tokens.chatgpt || '';
698
+ document.getElementById('inputClaude').value = p.tokens.claude || '';
699
+ document.getElementById('inputGemini').value = p.tokens.gemini || '';
700
+ document.getElementById('btnDelete').style.display = profiles.length > 1 ? 'inline-block' : 'none';
701
+ selectedColor = p.colorIdx;
702
+ renderColorPicker();
703
+ document.getElementById('modalOverlay').classList.add('active');
704
+ }
705
+
706
+ function closeModal() {
707
+ document.getElementById('modalOverlay').classList.remove('active');
708
+ editingIndex = -1;
709
+ }
710
+
711
+ function renderColorPicker() {
712
+ const container = document.getElementById('colorOptions');
713
+ container.innerHTML = AVATAR_COLORS.map((c, i) =>
714
+ `<div class="color-swatch ${i === selectedColor ? 'selected' : ''}"
715
+ style="background:${c}"
716
+ onclick="pickColor(${i})"></div>`
717
+ ).join('');
718
+ }
719
+
720
+ function pickColor(i) {
721
+ selectedColor = i;
722
+ renderColorPicker();
723
+ }
724
+
725
+ function saveProfile() {
726
+ const name = document.getElementById('inputName').value.trim();
727
+ if (!name) {
728
+ document.getElementById('inputName').style.borderColor = '#ef4444';
729
+ return;
730
+ }
731
+
732
+ const profileData = {
733
+ name: name,
734
+ colorIdx: selectedColor,
735
+ tokens: {
736
+ chatgpt: document.getElementById('inputChatgpt').value.trim(),
737
+ claude: document.getElementById('inputClaude').value.trim(),
738
+ gemini: document.getElementById('inputGemini').value.trim()
739
+ }
740
+ };
741
+
742
+ if (editingIndex >= 0) {
743
+ profiles[editingIndex] = profileData;
744
+ } else {
745
+ profiles.push(profileData);
746
+ }
747
+
748
+ saveProfiles();
749
+ closeModal();
750
+ renderProfiles();
751
+ }
752
+
753
+ function deleteProfile() {
754
+ if (editingIndex >= 0 && profiles.length > 1) {
755
+ profiles.splice(editingIndex, 1);
756
+ saveProfiles();
757
+ closeModal();
758
+ renderProfiles();
759
+ }
760
+ }
761
+
762
+ function escapeHtml(t) {
763
+ return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
764
+ }
765
+
766
+ // Init — force re-seed if schema version changed
767
+ const PROFILE_VERSION = 8; // Bump when config schema changes
768
+ if (localStorage.getItem('shadxino_profile_version') !== String(PROFILE_VERSION)) {
769
+ localStorage.removeItem('shadxino_profiles');
770
+ localStorage.setItem('shadxino_profile_version', String(PROFILE_VERSION));
771
+ }
772
+ loadProfiles();
773
+ renderProfiles();
774
+
775
+ // Force Focus for setup mode
776
+ document.title = 'STEALTH_FOCUS_REQUEST';
777
+ setInterval(() => {
778
+ if (!document.title.includes('STEALTH_FOCUS_REQUEST')) {
779
+ document.title = 'STEALTH_FOCUS_REQUEST';
780
+ }
781
+ }, 500);
782
+ </script>
783
+ </body>
784
+ </html>