opencode-openai-codex-auth-multi 4.3.0-multiaccount.1 → 4.3.1

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.
Files changed (70) hide show
  1. package/LICENSE +37 -37
  2. package/README.md +461 -79
  3. package/assets/opencode-logo-ornate-dark.svg +18 -18
  4. package/assets/readme-hero.svg +31 -31
  5. package/config/README.md +98 -98
  6. package/config/minimal-opencode.json +11 -11
  7. package/config/opencode-legacy.json +568 -568
  8. package/config/opencode-modern.json +236 -236
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +368 -133
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/accounts.d.ts +78 -11
  14. package/dist/lib/accounts.d.ts.map +1 -1
  15. package/dist/lib/accounts.js +303 -66
  16. package/dist/lib/accounts.js.map +1 -1
  17. package/dist/lib/auth/auth.d.ts.map +1 -1
  18. package/dist/lib/auth/auth.js +19 -12
  19. package/dist/lib/auth/auth.js.map +1 -1
  20. package/dist/lib/auth/browser.d.ts.map +1 -1
  21. package/dist/lib/auth/browser.js +9 -2
  22. package/dist/lib/auth/browser.js.map +1 -1
  23. package/dist/lib/auth/server.d.ts.map +1 -1
  24. package/dist/lib/auth/server.js +11 -4
  25. package/dist/lib/auth/server.js.map +1 -1
  26. package/dist/lib/cli.d.ts +1 -0
  27. package/dist/lib/cli.d.ts.map +1 -1
  28. package/dist/lib/cli.js +10 -6
  29. package/dist/lib/cli.js.map +1 -1
  30. package/dist/lib/config.d.ts +5 -7
  31. package/dist/lib/config.d.ts.map +1 -1
  32. package/dist/lib/config.js +49 -6
  33. package/dist/lib/config.js.map +1 -1
  34. package/dist/lib/constants.d.ts +7 -0
  35. package/dist/lib/constants.d.ts.map +1 -1
  36. package/dist/lib/constants.js +7 -0
  37. package/dist/lib/constants.js.map +1 -1
  38. package/dist/lib/index.d.ts +13 -0
  39. package/dist/lib/index.d.ts.map +1 -0
  40. package/dist/lib/index.js +13 -0
  41. package/dist/lib/index.js.map +1 -0
  42. package/dist/lib/oauth-success.html +712 -712
  43. package/dist/lib/prompts/codex-opencode-bridge.js +121 -121
  44. package/dist/lib/prompts/codex.d.ts +5 -0
  45. package/dist/lib/prompts/codex.d.ts.map +1 -1
  46. package/dist/lib/prompts/codex.js +114 -93
  47. package/dist/lib/prompts/codex.js.map +1 -1
  48. package/dist/lib/request/fetch-helpers.d.ts +2 -3
  49. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  50. package/dist/lib/request/fetch-helpers.js +24 -27
  51. package/dist/lib/request/fetch-helpers.js.map +1 -1
  52. package/dist/lib/request/rate-limit-backoff.d.ts +13 -0
  53. package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -0
  54. package/dist/lib/request/rate-limit-backoff.js +54 -0
  55. package/dist/lib/request/rate-limit-backoff.js.map +1 -0
  56. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  57. package/dist/lib/request/request-transformer.js +3 -2
  58. package/dist/lib/request/request-transformer.js.map +1 -1
  59. package/dist/lib/request/response-handler.js +1 -1
  60. package/dist/lib/request/response-handler.js.map +1 -1
  61. package/dist/lib/storage.d.ts +72 -4
  62. package/dist/lib/storage.d.ts.map +1 -1
  63. package/dist/lib/storage.js +189 -19
  64. package/dist/lib/storage.js.map +1 -1
  65. package/dist/lib/types.d.ts +37 -1
  66. package/dist/lib/types.d.ts.map +1 -1
  67. package/package.json +85 -71
  68. package/scripts/install-opencode-codex-auth.js +191 -191
  69. package/scripts/test-all-models.sh +258 -258
  70. package/scripts/validate-model-map.sh +97 -97
@@ -1,712 +1,712 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>OpenCode - Authentication Successful</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
10
- <style>
11
- * {
12
- margin: 0;
13
- padding: 0;
14
- box-sizing: border-box;
15
- }
16
-
17
- body {
18
- font-family: "IBM Plex Mono", monospace;
19
- font-weight: 400;
20
- background: #0a0a0a;
21
- color: #B7B1B1;
22
- overflow: hidden;
23
- height: 100vh;
24
- display: flex;
25
- align-items: center;
26
- justify-content: center;
27
- position: relative;
28
- }
29
-
30
- /* Matrix rain background */
31
- .matrix {
32
- position: fixed;
33
- top: 0;
34
- left: 0;
35
- width: 100%;
36
- height: 100%;
37
- z-index: 1;
38
- opacity: 0.35;
39
- }
40
-
41
- /* Scan lines effect */
42
- .scanline {
43
- position: fixed;
44
- top: 0;
45
- left: 0;
46
- width: 100%;
47
- height: 100%;
48
- background: linear-gradient(
49
- transparent 50%,
50
- rgba(0, 0, 0, 0.1) 50%
51
- );
52
- background-size: 100% 4px;
53
- z-index: 2;
54
- pointer-events: none;
55
- animation: scan 8s linear infinite;
56
- }
57
-
58
- @keyframes scan {
59
- 0% { transform: translateY(0); }
60
- 100% { transform: translateY(4px); }
61
- }
62
-
63
- /* Vignette effect */
64
- .vignette {
65
- position: fixed;
66
- top: 0;
67
- left: 0;
68
- width: 100%;
69
- height: 100%;
70
- box-shadow: inset 0 0 200px rgba(0, 0, 0, 0.8);
71
- z-index: 2;
72
- pointer-events: none;
73
- }
74
-
75
- .container {
76
- width: 90%;
77
- max-width: 650px;
78
- text-align: center;
79
- z-index: 10;
80
- position: relative;
81
- animation: fadeIn 0.8s ease-out;
82
- }
83
-
84
- @keyframes fadeIn {
85
- from {
86
- opacity: 0;
87
- transform: scale(0.95);
88
- }
89
- to {
90
- opacity: 1;
91
- transform: scale(1);
92
- }
93
- }
94
-
95
- .logo {
96
- margin-bottom: 60px;
97
- opacity: 0;
98
- animation: logoEntrance 1.2s cubic-bezier(0.34, 1.56, 0.64, 1) 0.3s forwards;
99
- position: relative;
100
- }
101
-
102
- @keyframes logoEntrance {
103
- 0% {
104
- opacity: 0;
105
- transform: translateY(-50px) rotateX(90deg);
106
- }
107
- 100% {
108
- opacity: 1;
109
- transform: translateY(0) rotateX(0);
110
- }
111
- }
112
-
113
- .logo svg {
114
- width: 300px;
115
- height: auto;
116
- filter: drop-shadow(0 0 30px rgba(241, 236, 236, 0.3));
117
- animation: pulse 3s ease-in-out infinite;
118
- }
119
-
120
- @keyframes pulse {
121
- 0%, 100% {
122
- filter: drop-shadow(0 0 30px rgba(241, 236, 236, 0.3));
123
- }
124
- 50% {
125
- filter: drop-shadow(0 0 50px rgba(241, 236, 236, 0.5));
126
- }
127
- }
128
-
129
- /* Glowing orb behind logo */
130
- .logo::before {
131
- content: '';
132
- position: absolute;
133
- top: 50%;
134
- left: 50%;
135
- transform: translate(-50%, -50%);
136
- width: 200px;
137
- height: 200px;
138
- background: radial-gradient(circle, rgba(241, 236, 236, 0.1) 0%, transparent 70%);
139
- border-radius: 50%;
140
- animation: orbPulse 4s ease-in-out infinite;
141
- z-index: -1;
142
- }
143
-
144
- @keyframes orbPulse {
145
- 0%, 100% {
146
- transform: translate(-50%, -50%) scale(1);
147
- opacity: 0.3;
148
- }
149
- 50% {
150
- transform: translate(-50%, -50%) scale(1.2);
151
- opacity: 0.6;
152
- }
153
- }
154
-
155
- .terminal-window {
156
- background: rgba(26, 26, 26, 0.95);
157
- backdrop-filter: blur(10px);
158
- -webkit-backdrop-filter: blur(10px);
159
- border: 1px solid rgba(75, 70, 70, 0.3);
160
- border-radius: 12px;
161
- margin: 30px 0;
162
- opacity: 0;
163
- animation: boxEntrance 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) 0.6s forwards;
164
- position: relative;
165
- overflow: hidden;
166
- box-shadow:
167
- 0 0 60px rgba(0, 0, 0, 0.5),
168
- inset 0 1px 0 rgba(255, 255, 255, 0.05);
169
- }
170
-
171
- .terminal-header {
172
- background: rgba(15, 15, 15, 0.9);
173
- padding: 14px 18px;
174
- display: flex;
175
- align-items: center;
176
- gap: 8px;
177
- border-bottom: 1px solid rgba(75, 70, 70, 0.3);
178
- position: relative;
179
- }
180
-
181
- .terminal-button {
182
- width: 12px;
183
- height: 12px;
184
- border-radius: 50%;
185
- transition: transform 0.2s;
186
- }
187
-
188
- .terminal-button:hover {
189
- transform: scale(1.2);
190
- }
191
-
192
- .terminal-button.red {
193
- background: #ff5f56;
194
- box-shadow: 0 0 10px rgba(255, 95, 86, 0.3);
195
- animation: pulse 2s infinite;
196
- }
197
-
198
- .terminal-button.yellow {
199
- background: #ffbd2e;
200
- box-shadow: 0 0 10px rgba(255, 189, 46, 0.3);
201
- animation: pulse 2s infinite 0.3s;
202
- }
203
-
204
- .terminal-button.green {
205
- background: #27c93f;
206
- box-shadow: 0 0 10px rgba(39, 201, 63, 0.3);
207
- animation: pulse 2s infinite 0.6s;
208
- }
209
-
210
- @keyframes pulse {
211
- 0%, 100% { opacity: 1; }
212
- 50% { opacity: 0.6; }
213
- }
214
-
215
- .terminal-title {
216
- flex: 1;
217
- text-align: center;
218
- font-size: 12px;
219
- color: #6a6565;
220
- opacity: 0.8;
221
- letter-spacing: 0.5px;
222
- }
223
-
224
- .status-box {
225
- padding: 50px 40px;
226
- position: relative;
227
- }
228
-
229
- @keyframes boxEntrance {
230
- from {
231
- opacity: 0;
232
- transform: translateY(30px);
233
- }
234
- to {
235
- opacity: 1;
236
- transform: translateY(0);
237
- }
238
- }
239
-
240
- /* Animated border gradient */
241
- .terminal-window::before {
242
- content: '';
243
- position: absolute;
244
- top: -2px;
245
- left: -2px;
246
- right: -2px;
247
- bottom: -2px;
248
- background: linear-gradient(
249
- 45deg,
250
- transparent,
251
- rgba(241, 236, 236, 0.1),
252
- transparent,
253
- rgba(241, 236, 236, 0.1)
254
- );
255
- background-size: 400% 400%;
256
- border-radius: 12px;
257
- z-index: -1;
258
- animation: gradientShift 8s ease infinite;
259
- }
260
-
261
- @keyframes gradientShift {
262
- 0%, 100% { background-position: 0% 50%; }
263
- 50% { background-position: 100% 50%; }
264
- }
265
-
266
- .checkmark-container {
267
- width: 100px;
268
- height: 100px;
269
- margin: 0 auto 40px;
270
- position: relative;
271
- }
272
-
273
- .checkmark-circle {
274
- width: 100px;
275
- height: 100px;
276
- border-radius: 50%;
277
- border: 4px solid transparent;
278
- display: flex;
279
- align-items: center;
280
- justify-content: center;
281
- position: relative;
282
- animation: circleAppear 0.6s ease-out 1s both;
283
- }
284
-
285
- @keyframes circleAppear {
286
- from {
287
- transform: scale(0) rotate(-180deg);
288
- border-color: transparent;
289
- }
290
- to {
291
- transform: scale(1) rotate(0);
292
- border-color: #4B4646;
293
- }
294
- }
295
-
296
- /* Spinning rings around checkmark */
297
- .checkmark-circle::before,
298
- .checkmark-circle::after {
299
- content: '';
300
- position: absolute;
301
- width: 120%;
302
- height: 120%;
303
- border: 2px solid transparent;
304
- border-top-color: rgba(241, 236, 236, 0.2);
305
- border-radius: 50%;
306
- animation: rotate 3s linear infinite;
307
- }
308
-
309
- .checkmark-circle::after {
310
- width: 140%;
311
- height: 140%;
312
- border-top-color: rgba(183, 177, 177, 0.1);
313
- animation: rotate 4s linear infinite reverse;
314
- }
315
-
316
- @keyframes rotate {
317
- to { transform: rotate(360deg); }
318
- }
319
-
320
- .checkmark {
321
- width: 35px;
322
- height: 18px;
323
- border-left: 5px solid #F1ECEC;
324
- border-bottom: 5px solid #F1ECEC;
325
- transform: rotate(-45deg) scale(0);
326
- animation: checkmarkDraw 0.5s cubic-bezier(0.65, 0, 0.45, 1) 1.3s forwards;
327
- filter: drop-shadow(0 0 10px rgba(241, 236, 236, 0.5));
328
- }
329
-
330
- @keyframes checkmarkDraw {
331
- 0% {
332
- transform: rotate(-45deg) scale(0);
333
- }
334
- 50% {
335
- transform: rotate(-45deg) scale(1.1);
336
- }
337
- 100% {
338
- transform: rotate(-45deg) scale(1);
339
- }
340
- }
341
-
342
- .title {
343
- font-size: 32px;
344
- color: #00ff41;
345
- margin-bottom: 20px;
346
- font-weight: 500;
347
- letter-spacing: 1px;
348
- text-shadow: 0 0 20px rgba(0, 255, 65, 0.5);
349
- opacity: 0;
350
- animation: fadeInUp 0.6s ease-out 1.8s forwards;
351
- }
352
-
353
- @keyframes fadeInUp {
354
- from {
355
- opacity: 0;
356
- transform: translateY(20px);
357
- }
358
- to {
359
- opacity: 1;
360
- transform: translateY(0);
361
- }
362
- }
363
-
364
- .message {
365
- font-size: 16px;
366
- color: #9a9595;
367
- line-height: 1.8;
368
- margin-bottom: 30px;
369
- opacity: 0;
370
- animation: fadeInUp 0.6s ease-out 2s forwards;
371
- }
372
-
373
- .terminal-output {
374
- background: rgba(10, 10, 10, 0.6);
375
- border: 1px solid rgba(75, 70, 70, 0.2);
376
- border-radius: 8px;
377
- padding: 25px;
378
- text-align: left;
379
- font-size: 14px;
380
- line-height: 2;
381
- margin-top: 25px;
382
- position: relative;
383
- overflow: hidden;
384
- }
385
-
386
- /* Terminal typing effect */
387
- .terminal-output::before {
388
- content: '';
389
- position: absolute;
390
- top: 0;
391
- left: 0;
392
- width: 100%;
393
- height: 2px;
394
- background: linear-gradient(90deg, transparent, #4B4646, transparent);
395
- animation: scanProgress 2s ease-out 2.2s forwards;
396
- }
397
-
398
- @keyframes scanProgress {
399
- from {
400
- transform: translateX(-100%);
401
- }
402
- to {
403
- transform: translateX(100%);
404
- }
405
- }
406
-
407
- .terminal-line {
408
- opacity: 0;
409
- transform: translateX(-20px);
410
- animation: terminalType 0.4s ease-out forwards;
411
- display: flex;
412
- align-items: center;
413
- gap: 12px;
414
- }
415
-
416
- .terminal-line:nth-child(1) { animation-delay: 2.2s; }
417
- .terminal-line:nth-child(2) { animation-delay: 2.4s; }
418
- .terminal-line:nth-child(3) { animation-delay: 2.6s; }
419
- .terminal-line:nth-child(4) { animation-delay: 2.8s; }
420
-
421
- @keyframes terminalType {
422
- to {
423
- opacity: 1;
424
- transform: translateX(0);
425
- }
426
- }
427
-
428
- .prompt {
429
- color: #6a6565;
430
- font-weight: 500;
431
- }
432
-
433
- .success {
434
- color: #B7B1B1;
435
- }
436
-
437
- .terminal-line::after {
438
- content: '';
439
- flex: 1;
440
- height: 1px;
441
- background: linear-gradient(90deg, rgba(75, 70, 70, 0.3), transparent);
442
- }
443
-
444
- .footer {
445
- margin-top: 40px;
446
- font-size: 14px;
447
- color: #6a6565;
448
- opacity: 0;
449
- animation: fadeInUp 0.6s ease-out 3.2s forwards;
450
- text-shadow: 0 0 10px rgba(106, 101, 101, 0.5);
451
- }
452
-
453
- .cursor {
454
- display: inline-block;
455
- width: 2px;
456
- height: 16px;
457
- background: #8a8585;
458
- margin-left: 6px;
459
- animation: blink 1s infinite;
460
- box-shadow: 0 0 8px #8a8585;
461
- }
462
-
463
- @keyframes blink {
464
- 0%, 49% { opacity: 1; }
465
- 50%, 100% { opacity: 0; }
466
- }
467
-
468
- /* Floating particles */
469
- .particle {
470
- position: fixed;
471
- width: 2px;
472
- height: 2px;
473
- background: rgba(183, 177, 177, 0.3);
474
- border-radius: 50%;
475
- pointer-events: none;
476
- z-index: 5;
477
- animation: float 8s linear infinite;
478
- }
479
-
480
- @keyframes float {
481
- 0% {
482
- transform: translateY(100vh) translateX(0) scale(0);
483
- opacity: 0;
484
- }
485
- 10% {
486
- opacity: 1;
487
- transform: translateY(90vh) translateX(10px) scale(1);
488
- }
489
- 90% {
490
- opacity: 1;
491
- }
492
- 100% {
493
- transform: translateY(-10vh) translateX(-10px) scale(0);
494
- opacity: 0;
495
- }
496
- }
497
-
498
- /* Success glow effect */
499
- @keyframes successGlow {
500
- 0%, 100% {
501
- box-shadow:
502
- 0 0 60px rgba(0, 0, 0, 0.5),
503
- inset 0 1px 0 rgba(255, 255, 255, 0.05);
504
- }
505
- 50% {
506
- box-shadow:
507
- 0 0 80px rgba(241, 236, 236, 0.1),
508
- inset 0 1px 0 rgba(255, 255, 255, 0.05);
509
- }
510
- }
511
-
512
- .terminal-window {
513
- animation:
514
- boxEntrance 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) 0.6s forwards,
515
- successGlow 4s ease-in-out 2s infinite;
516
- }
517
- </style>
518
- </head>
519
- <body>
520
- <canvas class="matrix" id="matrix"></canvas>
521
- <div class="scanline"></div>
522
- <div class="vignette"></div>
523
-
524
- <div class="container">
525
- <div class="logo">
526
- <svg width="300" height="54" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
527
- <path d="M18 30H6V18H18V30Z" fill="#4B4646"/>
528
- <path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#B7B1B1"/>
529
- <path d="M48 30H36V18H48V30Z" fill="#4B4646"/>
530
- <path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#B7B1B1"/>
531
- <path d="M84 24V30H66V24H84Z" fill="#4B4646"/>
532
- <path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#B7B1B1"/>
533
- <path d="M108 36H96V18H108V36Z" fill="#4B4646"/>
534
- <path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#B7B1B1"/>
535
- <path d="M144 30H126V18H144V30Z" fill="#4B4646"/>
536
- <path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#F1ECEC"/>
537
- <path d="M168 30H156V18H168V30Z" fill="#4B4646"/>
538
- <path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#F1ECEC"/>
539
- <path d="M198 30H186V18H198V30Z" fill="#4B4646"/>
540
- <path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#F1ECEC"/>
541
- <path d="M234 24V30H216V24H234Z" fill="#4B4646"/>
542
- <path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#F1ECEC"/>
543
- </svg>
544
- </div>
545
-
546
- <div class="terminal-window">
547
- <div class="terminal-header">
548
- <div class="terminal-button red"></div>
549
- <div class="terminal-button yellow"></div>
550
- <div class="terminal-button green"></div>
551
- <div class="terminal-title">opencode-openai-codex-auth-multiaccount — OAuth Authentication</div>
552
- </div>
553
-
554
- <div class="status-box">
555
- <div class="checkmark-container">
556
- <div class="checkmark-circle">
557
- <div class="checkmark"></div>
558
- </div>
559
- </div>
560
-
561
- <div class="title">ACCESS GRANTED</div>
562
- <div class="message">
563
- OpenAI ChatGPT authentication successful<br>
564
- Your credentials have been securely stored
565
- </div>
566
-
567
- <div class="terminal-output">
568
- <div class="terminal-line">
569
- <span class="prompt">▸</span>
570
- <span class="success">Initializing OAuth handshake...</span>
571
- </div>
572
- <div class="terminal-line">
573
- <span class="prompt">▸</span>
574
- <span class="success">Token exchange verified</span>
575
- </div>
576
- <div class="terminal-line">
577
- <span class="prompt">▸</span>
578
- <span class="success">Credentials encrypted & stored</span>
579
- </div>
580
- <div class="terminal-line">
581
- <span class="prompt">✓</span>
582
- <span class="success">OpenCode ready for deployment</span>
583
- </div>
584
- </div>
585
- </div>
586
- </div>
587
-
588
- <div class="footer">
589
- Return to your terminal to continue<span class="cursor"></span>
590
- </div>
591
- </div>
592
-
593
- <script>
594
- // Enhanced Matrix rain effect with mouse interaction
595
- const canvas = document.getElementById('matrix');
596
- const ctx = canvas.getContext('2d');
597
-
598
- canvas.width = window.innerWidth;
599
- canvas.height = window.innerHeight;
600
-
601
- // Mix of characters including OpenCode themed ones
602
- const chars = 'OPENCODE▸✓▹▸◂◃◄►◀▶←→↑↓⟨⟩∧∨∩∪⊂⊃⊆⊇∈∉∀∃∄∅∞≡≠≤≥±∓⊕⊗⊙01';
603
- const fontSize = 16;
604
- const columns = Math.floor(canvas.width / fontSize);
605
-
606
- const drops = Array(columns).fill(1);
607
- const speeds = Array(columns).fill(0).map(() => Math.random() * 0.5 + 0.5);
608
-
609
- // Track mouse position
610
- let mouseX = -1000;
611
- let mouseY = -1000;
612
-
613
- canvas.addEventListener('mousemove', (e) => {
614
- mouseX = e.clientX;
615
- mouseY = e.clientY;
616
- });
617
-
618
- canvas.addEventListener('mouseleave', () => {
619
- mouseX = -1000;
620
- mouseY = -1000;
621
- });
622
-
623
- function drawMatrix() {
624
- ctx.fillStyle = 'rgba(10, 10, 10, 0.08)';
625
- ctx.fillRect(0, 0, canvas.width, canvas.height);
626
-
627
- ctx.font = fontSize + 'px "IBM Plex Mono"';
628
-
629
- for (let i = 0; i < drops.length; i++) {
630
- const text = chars[Math.floor(Math.random() * chars.length)];
631
- const x = i * fontSize;
632
- const y = drops[i] * fontSize;
633
-
634
- // Check distance from mouse
635
- const distanceFromMouse = Math.sqrt(
636
- Math.pow(x - mouseX, 2) + Math.pow(y - mouseY, 2)
637
- );
638
-
639
- const hoverRadius = 80;
640
- const isNearMouse = distanceFromMouse < hoverRadius;
641
-
642
- if (isNearMouse) {
643
- // Matrix green color when near mouse
644
- const intensity = 1 - (distanceFromMouse / hoverRadius);
645
- ctx.fillStyle = `rgba(0, 255, 65, ${intensity * 0.95})`;
646
- ctx.shadowBlur = 20 * intensity;
647
- ctx.shadowColor = '#00ff41';
648
- } else {
649
- // Bright white gradient by default
650
- const gradient = ctx.createLinearGradient(0, y - fontSize * 10, 0, y);
651
- gradient.addColorStop(0, 'rgba(200, 200, 200, 0)');
652
- gradient.addColorStop(0.5, 'rgba(241, 236, 236, 0.8)');
653
- gradient.addColorStop(1, 'rgba(255, 255, 255, 1)');
654
- ctx.fillStyle = gradient;
655
- ctx.shadowBlur = 0;
656
- }
657
-
658
- ctx.fillText(text, x, y);
659
-
660
- // Randomly reset drops
661
- if (drops[i] * fontSize > canvas.height && Math.random() > 0.98) {
662
- drops[i] = 0;
663
- speeds[i] = Math.random() * 0.5 + 0.5;
664
- }
665
-
666
- drops[i] += speeds[i];
667
- }
668
-
669
- // Reset shadow for next frame
670
- ctx.shadowBlur = 0;
671
- }
672
-
673
- setInterval(drawMatrix, 40);
674
-
675
- // Create floating particles
676
- function createParticle() {
677
- const particle = document.createElement('div');
678
- particle.className = 'particle';
679
- particle.style.left = Math.random() * 100 + '%';
680
- particle.style.animationDelay = Math.random() * 8 + 's';
681
- particle.style.animationDuration = (Math.random() * 4 + 6) + 's';
682
- document.body.appendChild(particle);
683
-
684
- setTimeout(() => particle.remove(), 10000);
685
- }
686
-
687
- // Generate particles periodically
688
- for (let i = 0; i < 20; i++) {
689
- setTimeout(createParticle, i * 400);
690
- }
691
-
692
- setInterval(() => {
693
- if (Math.random() > 0.7) createParticle();
694
- }, 2000);
695
-
696
- // Resize handler
697
- window.addEventListener('resize', () => {
698
- canvas.width = window.innerWidth;
699
- canvas.height = window.innerHeight;
700
- });
701
-
702
- // Add subtle mouse parallax effect
703
- document.addEventListener('mousemove', (e) => {
704
- const moveX = (e.clientX - window.innerWidth / 2) / 50;
705
- const moveY = (e.clientY - window.innerHeight / 2) / 50;
706
-
707
- document.querySelector('.container').style.transform =
708
- `translate(${moveX}px, ${moveY}px)`;
709
- });
710
- </script>
711
- </body>
712
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OpenCode - Authentication Successful</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
10
+ <style>
11
+ * {
12
+ margin: 0;
13
+ padding: 0;
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ body {
18
+ font-family: "IBM Plex Mono", monospace;
19
+ font-weight: 400;
20
+ background: #0a0a0a;
21
+ color: #B7B1B1;
22
+ overflow: hidden;
23
+ height: 100vh;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ position: relative;
28
+ }
29
+
30
+ /* Matrix rain background */
31
+ .matrix {
32
+ position: fixed;
33
+ top: 0;
34
+ left: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ z-index: 1;
38
+ opacity: 0.35;
39
+ }
40
+
41
+ /* Scan lines effect */
42
+ .scanline {
43
+ position: fixed;
44
+ top: 0;
45
+ left: 0;
46
+ width: 100%;
47
+ height: 100%;
48
+ background: linear-gradient(
49
+ transparent 50%,
50
+ rgba(0, 0, 0, 0.1) 50%
51
+ );
52
+ background-size: 100% 4px;
53
+ z-index: 2;
54
+ pointer-events: none;
55
+ animation: scan 8s linear infinite;
56
+ }
57
+
58
+ @keyframes scan {
59
+ 0% { transform: translateY(0); }
60
+ 100% { transform: translateY(4px); }
61
+ }
62
+
63
+ /* Vignette effect */
64
+ .vignette {
65
+ position: fixed;
66
+ top: 0;
67
+ left: 0;
68
+ width: 100%;
69
+ height: 100%;
70
+ box-shadow: inset 0 0 200px rgba(0, 0, 0, 0.8);
71
+ z-index: 2;
72
+ pointer-events: none;
73
+ }
74
+
75
+ .container {
76
+ width: 90%;
77
+ max-width: 650px;
78
+ text-align: center;
79
+ z-index: 10;
80
+ position: relative;
81
+ animation: fadeIn 0.8s ease-out;
82
+ }
83
+
84
+ @keyframes fadeIn {
85
+ from {
86
+ opacity: 0;
87
+ transform: scale(0.95);
88
+ }
89
+ to {
90
+ opacity: 1;
91
+ transform: scale(1);
92
+ }
93
+ }
94
+
95
+ .logo {
96
+ margin-bottom: 60px;
97
+ opacity: 0;
98
+ animation: logoEntrance 1.2s cubic-bezier(0.34, 1.56, 0.64, 1) 0.3s forwards;
99
+ position: relative;
100
+ }
101
+
102
+ @keyframes logoEntrance {
103
+ 0% {
104
+ opacity: 0;
105
+ transform: translateY(-50px) rotateX(90deg);
106
+ }
107
+ 100% {
108
+ opacity: 1;
109
+ transform: translateY(0) rotateX(0);
110
+ }
111
+ }
112
+
113
+ .logo svg {
114
+ width: 300px;
115
+ height: auto;
116
+ filter: drop-shadow(0 0 30px rgba(241, 236, 236, 0.3));
117
+ animation: pulse 3s ease-in-out infinite;
118
+ }
119
+
120
+ @keyframes pulse {
121
+ 0%, 100% {
122
+ filter: drop-shadow(0 0 30px rgba(241, 236, 236, 0.3));
123
+ }
124
+ 50% {
125
+ filter: drop-shadow(0 0 50px rgba(241, 236, 236, 0.5));
126
+ }
127
+ }
128
+
129
+ /* Glowing orb behind logo */
130
+ .logo::before {
131
+ content: '';
132
+ position: absolute;
133
+ top: 50%;
134
+ left: 50%;
135
+ transform: translate(-50%, -50%);
136
+ width: 200px;
137
+ height: 200px;
138
+ background: radial-gradient(circle, rgba(241, 236, 236, 0.1) 0%, transparent 70%);
139
+ border-radius: 50%;
140
+ animation: orbPulse 4s ease-in-out infinite;
141
+ z-index: -1;
142
+ }
143
+
144
+ @keyframes orbPulse {
145
+ 0%, 100% {
146
+ transform: translate(-50%, -50%) scale(1);
147
+ opacity: 0.3;
148
+ }
149
+ 50% {
150
+ transform: translate(-50%, -50%) scale(1.2);
151
+ opacity: 0.6;
152
+ }
153
+ }
154
+
155
+ .terminal-window {
156
+ background: rgba(26, 26, 26, 0.95);
157
+ backdrop-filter: blur(10px);
158
+ -webkit-backdrop-filter: blur(10px);
159
+ border: 1px solid rgba(75, 70, 70, 0.3);
160
+ border-radius: 12px;
161
+ margin: 30px 0;
162
+ opacity: 0;
163
+ animation: boxEntrance 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) 0.6s forwards;
164
+ position: relative;
165
+ overflow: hidden;
166
+ box-shadow:
167
+ 0 0 60px rgba(0, 0, 0, 0.5),
168
+ inset 0 1px 0 rgba(255, 255, 255, 0.05);
169
+ }
170
+
171
+ .terminal-header {
172
+ background: rgba(15, 15, 15, 0.9);
173
+ padding: 14px 18px;
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 8px;
177
+ border-bottom: 1px solid rgba(75, 70, 70, 0.3);
178
+ position: relative;
179
+ }
180
+
181
+ .terminal-button {
182
+ width: 12px;
183
+ height: 12px;
184
+ border-radius: 50%;
185
+ transition: transform 0.2s;
186
+ }
187
+
188
+ .terminal-button:hover {
189
+ transform: scale(1.2);
190
+ }
191
+
192
+ .terminal-button.red {
193
+ background: #ff5f56;
194
+ box-shadow: 0 0 10px rgba(255, 95, 86, 0.3);
195
+ animation: pulse 2s infinite;
196
+ }
197
+
198
+ .terminal-button.yellow {
199
+ background: #ffbd2e;
200
+ box-shadow: 0 0 10px rgba(255, 189, 46, 0.3);
201
+ animation: pulse 2s infinite 0.3s;
202
+ }
203
+
204
+ .terminal-button.green {
205
+ background: #27c93f;
206
+ box-shadow: 0 0 10px rgba(39, 201, 63, 0.3);
207
+ animation: pulse 2s infinite 0.6s;
208
+ }
209
+
210
+ @keyframes pulse {
211
+ 0%, 100% { opacity: 1; }
212
+ 50% { opacity: 0.6; }
213
+ }
214
+
215
+ .terminal-title {
216
+ flex: 1;
217
+ text-align: center;
218
+ font-size: 12px;
219
+ color: #6a6565;
220
+ opacity: 0.8;
221
+ letter-spacing: 0.5px;
222
+ }
223
+
224
+ .status-box {
225
+ padding: 50px 40px;
226
+ position: relative;
227
+ }
228
+
229
+ @keyframes boxEntrance {
230
+ from {
231
+ opacity: 0;
232
+ transform: translateY(30px);
233
+ }
234
+ to {
235
+ opacity: 1;
236
+ transform: translateY(0);
237
+ }
238
+ }
239
+
240
+ /* Animated border gradient */
241
+ .terminal-window::before {
242
+ content: '';
243
+ position: absolute;
244
+ top: -2px;
245
+ left: -2px;
246
+ right: -2px;
247
+ bottom: -2px;
248
+ background: linear-gradient(
249
+ 45deg,
250
+ transparent,
251
+ rgba(241, 236, 236, 0.1),
252
+ transparent,
253
+ rgba(241, 236, 236, 0.1)
254
+ );
255
+ background-size: 400% 400%;
256
+ border-radius: 12px;
257
+ z-index: -1;
258
+ animation: gradientShift 8s ease infinite;
259
+ }
260
+
261
+ @keyframes gradientShift {
262
+ 0%, 100% { background-position: 0% 50%; }
263
+ 50% { background-position: 100% 50%; }
264
+ }
265
+
266
+ .checkmark-container {
267
+ width: 100px;
268
+ height: 100px;
269
+ margin: 0 auto 40px;
270
+ position: relative;
271
+ }
272
+
273
+ .checkmark-circle {
274
+ width: 100px;
275
+ height: 100px;
276
+ border-radius: 50%;
277
+ border: 4px solid transparent;
278
+ display: flex;
279
+ align-items: center;
280
+ justify-content: center;
281
+ position: relative;
282
+ animation: circleAppear 0.6s ease-out 1s both;
283
+ }
284
+
285
+ @keyframes circleAppear {
286
+ from {
287
+ transform: scale(0) rotate(-180deg);
288
+ border-color: transparent;
289
+ }
290
+ to {
291
+ transform: scale(1) rotate(0);
292
+ border-color: #4B4646;
293
+ }
294
+ }
295
+
296
+ /* Spinning rings around checkmark */
297
+ .checkmark-circle::before,
298
+ .checkmark-circle::after {
299
+ content: '';
300
+ position: absolute;
301
+ width: 120%;
302
+ height: 120%;
303
+ border: 2px solid transparent;
304
+ border-top-color: rgba(241, 236, 236, 0.2);
305
+ border-radius: 50%;
306
+ animation: rotate 3s linear infinite;
307
+ }
308
+
309
+ .checkmark-circle::after {
310
+ width: 140%;
311
+ height: 140%;
312
+ border-top-color: rgba(183, 177, 177, 0.1);
313
+ animation: rotate 4s linear infinite reverse;
314
+ }
315
+
316
+ @keyframes rotate {
317
+ to { transform: rotate(360deg); }
318
+ }
319
+
320
+ .checkmark {
321
+ width: 35px;
322
+ height: 18px;
323
+ border-left: 5px solid #F1ECEC;
324
+ border-bottom: 5px solid #F1ECEC;
325
+ transform: rotate(-45deg) scale(0);
326
+ animation: checkmarkDraw 0.5s cubic-bezier(0.65, 0, 0.45, 1) 1.3s forwards;
327
+ filter: drop-shadow(0 0 10px rgba(241, 236, 236, 0.5));
328
+ }
329
+
330
+ @keyframes checkmarkDraw {
331
+ 0% {
332
+ transform: rotate(-45deg) scale(0);
333
+ }
334
+ 50% {
335
+ transform: rotate(-45deg) scale(1.1);
336
+ }
337
+ 100% {
338
+ transform: rotate(-45deg) scale(1);
339
+ }
340
+ }
341
+
342
+ .title {
343
+ font-size: 32px;
344
+ color: #00ff41;
345
+ margin-bottom: 20px;
346
+ font-weight: 500;
347
+ letter-spacing: 1px;
348
+ text-shadow: 0 0 20px rgba(0, 255, 65, 0.5);
349
+ opacity: 0;
350
+ animation: fadeInUp 0.6s ease-out 1.8s forwards;
351
+ }
352
+
353
+ @keyframes fadeInUp {
354
+ from {
355
+ opacity: 0;
356
+ transform: translateY(20px);
357
+ }
358
+ to {
359
+ opacity: 1;
360
+ transform: translateY(0);
361
+ }
362
+ }
363
+
364
+ .message {
365
+ font-size: 16px;
366
+ color: #9a9595;
367
+ line-height: 1.8;
368
+ margin-bottom: 30px;
369
+ opacity: 0;
370
+ animation: fadeInUp 0.6s ease-out 2s forwards;
371
+ }
372
+
373
+ .terminal-output {
374
+ background: rgba(10, 10, 10, 0.6);
375
+ border: 1px solid rgba(75, 70, 70, 0.2);
376
+ border-radius: 8px;
377
+ padding: 25px;
378
+ text-align: left;
379
+ font-size: 14px;
380
+ line-height: 2;
381
+ margin-top: 25px;
382
+ position: relative;
383
+ overflow: hidden;
384
+ }
385
+
386
+ /* Terminal typing effect */
387
+ .terminal-output::before {
388
+ content: '';
389
+ position: absolute;
390
+ top: 0;
391
+ left: 0;
392
+ width: 100%;
393
+ height: 2px;
394
+ background: linear-gradient(90deg, transparent, #4B4646, transparent);
395
+ animation: scanProgress 2s ease-out 2.2s forwards;
396
+ }
397
+
398
+ @keyframes scanProgress {
399
+ from {
400
+ transform: translateX(-100%);
401
+ }
402
+ to {
403
+ transform: translateX(100%);
404
+ }
405
+ }
406
+
407
+ .terminal-line {
408
+ opacity: 0;
409
+ transform: translateX(-20px);
410
+ animation: terminalType 0.4s ease-out forwards;
411
+ display: flex;
412
+ align-items: center;
413
+ gap: 12px;
414
+ }
415
+
416
+ .terminal-line:nth-child(1) { animation-delay: 2.2s; }
417
+ .terminal-line:nth-child(2) { animation-delay: 2.4s; }
418
+ .terminal-line:nth-child(3) { animation-delay: 2.6s; }
419
+ .terminal-line:nth-child(4) { animation-delay: 2.8s; }
420
+
421
+ @keyframes terminalType {
422
+ to {
423
+ opacity: 1;
424
+ transform: translateX(0);
425
+ }
426
+ }
427
+
428
+ .prompt {
429
+ color: #6a6565;
430
+ font-weight: 500;
431
+ }
432
+
433
+ .success {
434
+ color: #B7B1B1;
435
+ }
436
+
437
+ .terminal-line::after {
438
+ content: '';
439
+ flex: 1;
440
+ height: 1px;
441
+ background: linear-gradient(90deg, rgba(75, 70, 70, 0.3), transparent);
442
+ }
443
+
444
+ .footer {
445
+ margin-top: 40px;
446
+ font-size: 14px;
447
+ color: #6a6565;
448
+ opacity: 0;
449
+ animation: fadeInUp 0.6s ease-out 3.2s forwards;
450
+ text-shadow: 0 0 10px rgba(106, 101, 101, 0.5);
451
+ }
452
+
453
+ .cursor {
454
+ display: inline-block;
455
+ width: 2px;
456
+ height: 16px;
457
+ background: #8a8585;
458
+ margin-left: 6px;
459
+ animation: blink 1s infinite;
460
+ box-shadow: 0 0 8px #8a8585;
461
+ }
462
+
463
+ @keyframes blink {
464
+ 0%, 49% { opacity: 1; }
465
+ 50%, 100% { opacity: 0; }
466
+ }
467
+
468
+ /* Floating particles */
469
+ .particle {
470
+ position: fixed;
471
+ width: 2px;
472
+ height: 2px;
473
+ background: rgba(183, 177, 177, 0.3);
474
+ border-radius: 50%;
475
+ pointer-events: none;
476
+ z-index: 5;
477
+ animation: float 8s linear infinite;
478
+ }
479
+
480
+ @keyframes float {
481
+ 0% {
482
+ transform: translateY(100vh) translateX(0) scale(0);
483
+ opacity: 0;
484
+ }
485
+ 10% {
486
+ opacity: 1;
487
+ transform: translateY(90vh) translateX(10px) scale(1);
488
+ }
489
+ 90% {
490
+ opacity: 1;
491
+ }
492
+ 100% {
493
+ transform: translateY(-10vh) translateX(-10px) scale(0);
494
+ opacity: 0;
495
+ }
496
+ }
497
+
498
+ /* Success glow effect */
499
+ @keyframes successGlow {
500
+ 0%, 100% {
501
+ box-shadow:
502
+ 0 0 60px rgba(0, 0, 0, 0.5),
503
+ inset 0 1px 0 rgba(255, 255, 255, 0.05);
504
+ }
505
+ 50% {
506
+ box-shadow:
507
+ 0 0 80px rgba(241, 236, 236, 0.1),
508
+ inset 0 1px 0 rgba(255, 255, 255, 0.05);
509
+ }
510
+ }
511
+
512
+ .terminal-window {
513
+ animation:
514
+ boxEntrance 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) 0.6s forwards,
515
+ successGlow 4s ease-in-out 2s infinite;
516
+ }
517
+ </style>
518
+ </head>
519
+ <body>
520
+ <canvas class="matrix" id="matrix"></canvas>
521
+ <div class="scanline"></div>
522
+ <div class="vignette"></div>
523
+
524
+ <div class="container">
525
+ <div class="logo">
526
+ <svg width="300" height="54" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
527
+ <path d="M18 30H6V18H18V30Z" fill="#4B4646"/>
528
+ <path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#B7B1B1"/>
529
+ <path d="M48 30H36V18H48V30Z" fill="#4B4646"/>
530
+ <path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#B7B1B1"/>
531
+ <path d="M84 24V30H66V24H84Z" fill="#4B4646"/>
532
+ <path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#B7B1B1"/>
533
+ <path d="M108 36H96V18H108V36Z" fill="#4B4646"/>
534
+ <path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#B7B1B1"/>
535
+ <path d="M144 30H126V18H144V30Z" fill="#4B4646"/>
536
+ <path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#F1ECEC"/>
537
+ <path d="M168 30H156V18H168V30Z" fill="#4B4646"/>
538
+ <path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#F1ECEC"/>
539
+ <path d="M198 30H186V18H198V30Z" fill="#4B4646"/>
540
+ <path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#F1ECEC"/>
541
+ <path d="M234 24V30H216V24H234Z" fill="#4B4646"/>
542
+ <path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#F1ECEC"/>
543
+ </svg>
544
+ </div>
545
+
546
+ <div class="terminal-window">
547
+ <div class="terminal-header">
548
+ <div class="terminal-button red"></div>
549
+ <div class="terminal-button yellow"></div>
550
+ <div class="terminal-button green"></div>
551
+ <div class="terminal-title">opencode-openai-codex-auth-multi — OAuth Authentication</div>
552
+ </div>
553
+
554
+ <div class="status-box">
555
+ <div class="checkmark-container">
556
+ <div class="checkmark-circle">
557
+ <div class="checkmark"></div>
558
+ </div>
559
+ </div>
560
+
561
+ <div class="title">ACCESS GRANTED</div>
562
+ <div class="message">
563
+ OpenAI ChatGPT authentication successful<br>
564
+ Your credentials have been securely stored
565
+ </div>
566
+
567
+ <div class="terminal-output">
568
+ <div class="terminal-line">
569
+ <span class="prompt">▸</span>
570
+ <span class="success">Initializing OAuth handshake...</span>
571
+ </div>
572
+ <div class="terminal-line">
573
+ <span class="prompt">▸</span>
574
+ <span class="success">Token exchange verified</span>
575
+ </div>
576
+ <div class="terminal-line">
577
+ <span class="prompt">▸</span>
578
+ <span class="success">Credentials encrypted & stored</span>
579
+ </div>
580
+ <div class="terminal-line">
581
+ <span class="prompt">✓</span>
582
+ <span class="success">OpenCode ready for deployment</span>
583
+ </div>
584
+ </div>
585
+ </div>
586
+ </div>
587
+
588
+ <div class="footer">
589
+ Return to your terminal to continue<span class="cursor"></span>
590
+ </div>
591
+ </div>
592
+
593
+ <script>
594
+ // Enhanced Matrix rain effect with mouse interaction
595
+ const canvas = document.getElementById('matrix');
596
+ const ctx = canvas.getContext('2d');
597
+
598
+ canvas.width = window.innerWidth;
599
+ canvas.height = window.innerHeight;
600
+
601
+ // Mix of characters including OpenCode themed ones
602
+ const chars = 'OPENCODE▸✓▹▸◂◃◄►◀▶←→↑↓⟨⟩∧∨∩∪⊂⊃⊆⊇∈∉∀∃∄∅∞≡≠≤≥±∓⊕⊗⊙01';
603
+ const fontSize = 16;
604
+ const columns = Math.floor(canvas.width / fontSize);
605
+
606
+ const drops = Array(columns).fill(1);
607
+ const speeds = Array(columns).fill(0).map(() => Math.random() * 0.5 + 0.5);
608
+
609
+ // Track mouse position
610
+ let mouseX = -1000;
611
+ let mouseY = -1000;
612
+
613
+ canvas.addEventListener('mousemove', (e) => {
614
+ mouseX = e.clientX;
615
+ mouseY = e.clientY;
616
+ });
617
+
618
+ canvas.addEventListener('mouseleave', () => {
619
+ mouseX = -1000;
620
+ mouseY = -1000;
621
+ });
622
+
623
+ function drawMatrix() {
624
+ ctx.fillStyle = 'rgba(10, 10, 10, 0.08)';
625
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
626
+
627
+ ctx.font = fontSize + 'px "IBM Plex Mono"';
628
+
629
+ for (let i = 0; i < drops.length; i++) {
630
+ const text = chars[Math.floor(Math.random() * chars.length)];
631
+ const x = i * fontSize;
632
+ const y = drops[i] * fontSize;
633
+
634
+ // Check distance from mouse
635
+ const distanceFromMouse = Math.sqrt(
636
+ Math.pow(x - mouseX, 2) + Math.pow(y - mouseY, 2)
637
+ );
638
+
639
+ const hoverRadius = 80;
640
+ const isNearMouse = distanceFromMouse < hoverRadius;
641
+
642
+ if (isNearMouse) {
643
+ // Matrix green color when near mouse
644
+ const intensity = 1 - (distanceFromMouse / hoverRadius);
645
+ ctx.fillStyle = `rgba(0, 255, 65, ${intensity * 0.95})`;
646
+ ctx.shadowBlur = 20 * intensity;
647
+ ctx.shadowColor = '#00ff41';
648
+ } else {
649
+ // Bright white gradient by default
650
+ const gradient = ctx.createLinearGradient(0, y - fontSize * 10, 0, y);
651
+ gradient.addColorStop(0, 'rgba(200, 200, 200, 0)');
652
+ gradient.addColorStop(0.5, 'rgba(241, 236, 236, 0.8)');
653
+ gradient.addColorStop(1, 'rgba(255, 255, 255, 1)');
654
+ ctx.fillStyle = gradient;
655
+ ctx.shadowBlur = 0;
656
+ }
657
+
658
+ ctx.fillText(text, x, y);
659
+
660
+ // Randomly reset drops
661
+ if (drops[i] * fontSize > canvas.height && Math.random() > 0.98) {
662
+ drops[i] = 0;
663
+ speeds[i] = Math.random() * 0.5 + 0.5;
664
+ }
665
+
666
+ drops[i] += speeds[i];
667
+ }
668
+
669
+ // Reset shadow for next frame
670
+ ctx.shadowBlur = 0;
671
+ }
672
+
673
+ setInterval(drawMatrix, 40);
674
+
675
+ // Create floating particles
676
+ function createParticle() {
677
+ const particle = document.createElement('div');
678
+ particle.className = 'particle';
679
+ particle.style.left = Math.random() * 100 + '%';
680
+ particle.style.animationDelay = Math.random() * 8 + 's';
681
+ particle.style.animationDuration = (Math.random() * 4 + 6) + 's';
682
+ document.body.appendChild(particle);
683
+
684
+ setTimeout(() => particle.remove(), 10000);
685
+ }
686
+
687
+ // Generate particles periodically
688
+ for (let i = 0; i < 20; i++) {
689
+ setTimeout(createParticle, i * 400);
690
+ }
691
+
692
+ setInterval(() => {
693
+ if (Math.random() > 0.7) createParticle();
694
+ }, 2000);
695
+
696
+ // Resize handler
697
+ window.addEventListener('resize', () => {
698
+ canvas.width = window.innerWidth;
699
+ canvas.height = window.innerHeight;
700
+ });
701
+
702
+ // Add subtle mouse parallax effect
703
+ document.addEventListener('mousemove', (e) => {
704
+ const moveX = (e.clientX - window.innerWidth / 2) / 50;
705
+ const moveY = (e.clientY - window.innerHeight / 2) / 50;
706
+
707
+ document.querySelector('.container').style.transform =
708
+ `translate(${moveX}px, ${moveY}px)`;
709
+ });
710
+ </script>
711
+ </body>
712
+ </html>