Mmmma4iio commited on
Commit
869d4ab
·
verified ·
1 Parent(s): 02be0d4

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +762 -0
index.html ADDED
@@ -0,0 +1,762 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Retry - The Persistence Engine</title>
7
+ <script src="https://unpkg.com/@phosphor-icons/web"></script>
8
+ <style>
9
+ @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;600;700&display=swap');
10
+
11
+ :root {
12
+ --primary: #6366f1;
13
+ --primary-glow: rgba(99, 102, 241, 0.5);
14
+ --success: #10b981;
15
+ --success-glow: rgba(16, 185, 129, 0.5);
16
+ --danger: #ef4444;
17
+ --danger-glow: rgba(239, 68, 68, 0.5);
18
+ --warning: #f59e0b;
19
+ --bg: #0f172a;
20
+ --surface: rgba(30, 41, 59, 0.7);
21
+ --text: #f8fafc;
22
+ --text-muted: #94a3b8;
23
+ --border: rgba(148, 163, 184, 0.2);
24
+ }
25
+
26
+ * {
27
+ margin: 0;
28
+ padding: 0;
29
+ box-sizing: border-box;
30
+ }
31
+
32
+ body {
33
+ font-family: 'Space Grotesk', sans-serif;
34
+ background: var(--bg);
35
+ color: var(--text);
36
+ min-height: 100vh;
37
+ overflow-x: hidden;
38
+ background-image:
39
+ radial-gradient(circle at 20% 50%, rgba(99, 102, 241, 0.15) 0%, transparent 50%),
40
+ radial-gradient(circle at 80% 80%, rgba(16, 185, 129, 0.1) 0%, transparent 50%);
41
+ }
42
+
43
+ /* Animated background grid */
44
+ .bg-grid {
45
+ position: fixed;
46
+ inset: 0;
47
+ background-image:
48
+ linear-gradient(rgba(99, 102, 241, 0.03) 1px, transparent 1px),
49
+ linear-gradient(90deg, rgba(99, 102, 241, 0.03) 1px, transparent 1px);
50
+ background-size: 50px 50px;
51
+ pointer-events: none;
52
+ z-index: -1;
53
+ }
54
+
55
+ header {
56
+ padding: 1.5rem;
57
+ display: flex;
58
+ justify-content: space-between;
59
+ align-items: center;
60
+ border-bottom: 1px solid var(--border);
61
+ background: rgba(15, 23, 42, 0.8);
62
+ backdrop-filter: blur(12px);
63
+ position: sticky;
64
+ top: 0;
65
+ z-index: 100;
66
+ }
67
+
68
+ .logo {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 0.75rem;
72
+ font-size: 1.5rem;
73
+ font-weight: 700;
74
+ letter-spacing: -0.02em;
75
+ }
76
+
77
+ .logo i {
78
+ color: var(--primary);
79
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
80
+ }
81
+
82
+ @keyframes pulse {
83
+ 0%, 100% { opacity: 1; }
84
+ 50% { opacity: 0.5; }
85
+ }
86
+
87
+ .anycoder-link {
88
+ color: var(--text-muted);
89
+ text-decoration: none;
90
+ font-size: 0.875rem;
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 0.5rem;
94
+ padding: 0.5rem 1rem;
95
+ border-radius: 9999px;
96
+ border: 1px solid var(--border);
97
+ transition: all 0.3s ease;
98
+ background: var(--surface);
99
+ backdrop-filter: blur(8px);
100
+ }
101
+
102
+ .anycoder-link:hover {
103
+ border-color: var(--primary);
104
+ color: var(--primary);
105
+ transform: translateY(-2px);
106
+ box-shadow: 0 0 20px var(--primary-glow);
107
+ }
108
+
109
+ main {
110
+ max-width: 800px;
111
+ margin: 0 auto;
112
+ padding: 2rem 1rem;
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 2rem;
116
+ }
117
+
118
+ .game-container {
119
+ background: var(--surface);
120
+ border: 1px solid var(--border);
121
+ border-radius: 24px;
122
+ padding: 2rem;
123
+ backdrop-filter: blur(16px);
124
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
125
+ position: relative;
126
+ overflow: hidden;
127
+ }
128
+
129
+ .game-container::before {
130
+ content: '';
131
+ position: absolute;
132
+ top: 0;
133
+ left: 0;
134
+ right: 0;
135
+ height: 1px;
136
+ background: linear-gradient(90deg, transparent, var(--primary), transparent);
137
+ animation: scan 3s linear infinite;
138
+ }
139
+
140
+ @keyframes scan {
141
+ 0% { transform: translateX(-100%); }
142
+ 100% { transform: translateX(100%); }
143
+ }
144
+
145
+ .stats-bar {
146
+ display: grid;
147
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
148
+ gap: 1rem;
149
+ margin-bottom: 2rem;
150
+ }
151
+
152
+ .stat-card {
153
+ background: rgba(15, 23, 42, 0.6);
154
+ padding: 1rem;
155
+ border-radius: 16px;
156
+ border: 1px solid var(--border);
157
+ text-align: center;
158
+ transition: transform 0.2s;
159
+ }
160
+
161
+ .stat-card:hover {
162
+ transform: translateY(-2px);
163
+ border-color: var(--primary);
164
+ }
165
+
166
+ .stat-label {
167
+ font-size: 0.75rem;
168
+ text-transform: uppercase;
169
+ letter-spacing: 0.1em;
170
+ color: var(--text-muted);
171
+ margin-bottom: 0.5rem;
172
+ }
173
+
174
+ .stat-value {
175
+ font-size: 1.5rem;
176
+ font-weight: 700;
177
+ color: var(--text);
178
+ }
179
+
180
+ .stat-value.success { color: var(--success); }
181
+ .stat-value.danger { color: var(--danger); }
182
+ .stat-value.warning { color: var(--warning); }
183
+
184
+ .game-area {
185
+ position: relative;
186
+ height: 200px;
187
+ background: rgba(15, 23, 42, 0.8);
188
+ border-radius: 16px;
189
+ border: 2px solid var(--border);
190
+ overflow: hidden;
191
+ margin-bottom: 2rem;
192
+ cursor: pointer;
193
+ user-select: none;
194
+ touch-action: manipulation;
195
+ }
196
+
197
+ .track {
198
+ position: absolute;
199
+ top: 50%;
200
+ left: 0;
201
+ right: 0;
202
+ height: 4px;
203
+ background: var(--border);
204
+ transform: translateY(-50%);
205
+ }
206
+
207
+ .target-zone {
208
+ position: absolute;
209
+ top: 50%;
210
+ height: 60px;
211
+ transform: translateY(-50%);
212
+ background: linear-gradient(90deg, transparent, var(--success-glow), transparent);
213
+ border-left: 2px solid var(--success);
214
+ border-right: 2px solid var(--success);
215
+ border-radius: 4px;
216
+ transition: all 0.3s ease;
217
+ }
218
+
219
+ .mover {
220
+ position: absolute;
221
+ top: 50%;
222
+ width: 40px;
223
+ height: 40px;
224
+ background: var(--primary);
225
+ border-radius: 50%;
226
+ transform: translate(-50%, -50%);
227
+ box-shadow: 0 0 30px var(--primary-glow);
228
+ transition: left 0.1s linear;
229
+ z-index: 10;
230
+ }
231
+
232
+ .mover.perfect {
233
+ background: var(--success);
234
+ box-shadow: 0 0 40px var(--success-glow);
235
+ }
236
+
237
+ .mover.fail {
238
+ background: var(--danger);
239
+ box-shadow: 0 0 40px var(--danger-glow);
240
+ }
241
+
242
+ .trail {
243
+ position: absolute;
244
+ top: 50%;
245
+ height: 2px;
246
+ background: linear-gradient(90deg, transparent, var(--primary-glow));
247
+ transform: translateY(-50%);
248
+ opacity: 0.5;
249
+ pointer-events: none;
250
+ }
251
+
252
+ .controls {
253
+ display: flex;
254
+ gap: 1rem;
255
+ justify-content: center;
256
+ flex-wrap: wrap;
257
+ }
258
+
259
+ button {
260
+ font-family: inherit;
261
+ font-size: 1rem;
262
+ font-weight: 600;
263
+ padding: 1rem 2rem;
264
+ border: none;
265
+ border-radius: 12px;
266
+ cursor: pointer;
267
+ transition: all 0.2s;
268
+ display: inline-flex;
269
+ align-items: center;
270
+ gap: 0.5rem;
271
+ position: relative;
272
+ overflow: hidden;
273
+ }
274
+
275
+ button::before {
276
+ content: '';
277
+ position: absolute;
278
+ inset: 0;
279
+ background: linear-gradient(45deg, transparent, rgba(255,255,255,0.1), transparent);
280
+ transform: translateX(-100%);
281
+ transition: transform 0.6s;
282
+ }
283
+
284
+ button:hover::before {
285
+ transform: translateX(100%);
286
+ }
287
+
288
+ .btn-primary {
289
+ background: var(--primary);
290
+ color: white;
291
+ box-shadow: 0 4px 20px var(--primary-glow);
292
+ }
293
+
294
+ .btn-primary:hover {
295
+ transform: translateY(-2px);
296
+ box-shadow: 0 8px 30px var(--primary-glow);
297
+ }
298
+
299
+ .btn-primary:active {
300
+ transform: translateY(0);
301
+ }
302
+
303
+ .btn-secondary {
304
+ background: transparent;
305
+ color: var(--text);
306
+ border: 2px solid var(--border);
307
+ }
308
+
309
+ .btn-secondary:hover {
310
+ border-color: var(--primary);
311
+ color: var(--primary);
312
+ }
313
+
314
+ .history-section {
315
+ margin-top: 2rem;
316
+ }
317
+
318
+ .section-title {
319
+ font-size: 1.25rem;
320
+ margin-bottom: 1rem;
321
+ display: flex;
322
+ align-items: center;
323
+ gap: 0.5rem;
324
+ }
325
+
326
+ .retry-history {
327
+ display: flex;
328
+ gap: 0.5rem;
329
+ flex-wrap: wrap;
330
+ max-height: 150px;
331
+ overflow-y: auto;
332
+ padding: 1rem;
333
+ background: rgba(15, 23, 42, 0.4);
334
+ border-radius: 12px;
335
+ border: 1px solid var(--border);
336
+ }
337
+
338
+ .retry-dot {
339
+ width: 32px;
340
+ height: 32px;
341
+ border-radius: 8px;
342
+ display: flex;
343
+ align-items: center;
344
+ justify-content: center;
345
+ font-size: 0.875rem;
346
+ font-weight: 700;
347
+ animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
348
+ }
349
+
350
+ @keyframes popIn {
351
+ 0% { transform: scale(0); opacity: 0; }
352
+ 100% { transform: scale(1); opacity: 1; }
353
+ }
354
+
355
+ .retry-dot.success {
356
+ background: rgba(16, 185, 129, 0.2);
357
+ color: var(--success);
358
+ border: 1px solid var(--success);
359
+ }
360
+
361
+ .retry-dot.fail {
362
+ background: rgba(239, 68, 68, 0.2);
363
+ color: var(--danger);
364
+ border: 1px solid var(--danger);
365
+ }
366
+
367
+ .message {
368
+ text-align: center;
369
+ padding: 1rem;
370
+ border-radius: 12px;
371
+ margin-bottom: 1rem;
372
+ font-weight: 600;
373
+ opacity: 0;
374
+ transform: translateY(-10px);
375
+ transition: all 0.3s ease;
376
+ }
377
+
378
+ .message.show {
379
+ opacity: 1;
380
+ transform: translateY(0);
381
+ }
382
+
383
+ .message.success {
384
+ background: rgba(16, 185, 129, 0.1);
385
+ color: var(--success);
386
+ border: 1px solid var(--success);
387
+ }
388
+
389
+ .message.fail {
390
+ background: rgba(239, 68, 68, 0.1);
391
+ color: var(--danger);
392
+ border: 1px solid var(--danger);
393
+ }
394
+
395
+ .progress-bar {
396
+ width: 100%;
397
+ height: 4px;
398
+ background: var(--border);
399
+ border-radius: 2px;
400
+ margin-top: 2rem;
401
+ overflow: hidden;
402
+ }
403
+
404
+ .progress-fill {
405
+ height: 100%;
406
+ background: linear-gradient(90deg, var(--primary), var(--success));
407
+ transition: width 0.5s ease;
408
+ box-shadow: 0 0 10px var(--success-glow);
409
+ }
410
+
411
+ .level-indicator {
412
+ text-align: center;
413
+ margin-top: 0.5rem;
414
+ font-size: 0.875rem;
415
+ color: var(--text-muted);
416
+ }
417
+
418
+ @media (max-width: 640px) {
419
+ .game-container {
420
+ padding: 1rem;
421
+ }
422
+
423
+ .stats-bar {
424
+ grid-template-columns: repeat(2, 1fr);
425
+ }
426
+
427
+ .mover {
428
+ width: 30px;
429
+ height: 30px;
430
+ }
431
+ }
432
+
433
+ .particle {
434
+ position: absolute;
435
+ pointer-events: none;
436
+ opacity: 0;
437
+ animation: particle 1s ease-out forwards;
438
+ }
439
+
440
+ @keyframes particle {
441
+ 0% {
442
+ transform: translate(0, 0) scale(1);
443
+ opacity: 1;
444
+ }
445
+ 100% {
446
+ transform: translate(var(--tx), var(--ty)) scale(0);
447
+ opacity: 0;
448
+ }
449
+ }
450
+ </style>
451
+ </head>
452
+ <body>
453
+ <div class="bg-grid"></div>
454
+
455
+ <header>
456
+ <div class="logo">
457
+ <i class="ph ph-arrow-counter-clockwise"></i>
458
+ <span>RETRY</span>
459
+ </div>
460
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
461
+ <i class="ph ph-code"></i>
462
+ Built with anycoder
463
+ </a>
464
+ </header>
465
+
466
+ <main>
467
+ <div class="game-container">
468
+ <div class="stats-bar">
469
+ <div class="stat-card">
470
+ <div class="stat-label">Level</div>
471
+ <div class="stat-value" id="level">1</div>
472
+ </div>
473
+ <div class="stat-card">
474
+ <div class="stat-label">Retries</div>
475
+ <div class="stat-value warning" id="retries">0</div>
476
+ </div>
477
+ <div class="stat-card">
478
+ <div class="stat-label">Success Rate</div>
479
+ <div class="stat-value" id="successRate">100%</div>
480
+ </div>
481
+ <div class="stat-card">
482
+ <div class="stat-label">Streak</div>
483
+ <div class="stat-value success" id="streak">0</div>
484
+ </div>
485
+ </div>
486
+
487
+ <div id="message" class="message"></div>
488
+
489
+ <div class="game-area" id="gameArea">
490
+ <div class="track"></div>
491
+ <div class="target-zone" id="targetZone"></div>
492
+ <div class="mover" id="mover"></div>
493
+ </div>
494
+
495
+ <div class="controls">
496
+ <button class="btn-primary" id="actionBtn" onclick="game.attempt()">
497
+ <i class="ph ph-hand-tap"></i>
498
+ Stop
499
+ </button>
500
+ <button class="btn-secondary" onclick="game.reset()">
501
+ <i class="ph ph-arrow-counter-clockwise"></i>
502
+ Reset Level
503
+ </button>
504
+ </div>
505
+
506
+ <div class="progress-bar">
507
+ <div class="progress-fill" id="progressFill" style="width: 0%"></div>
508
+ </div>
509
+ <div class="level-indicator">Progress to Next Level</div>
510
+ </div>
511
+
512
+ <div class="history-section">
513
+ <h2 class="section-title">
514
+ <i class="ph ph-clock-counter-clockwise"></i>
515
+ Retry History
516
+ </h2>
517
+ <div class="retry-history" id="retryHistory">
518
+ <span style="color: var(--text-muted); font-size: 0.875rem;">No attempts yet. Start playing to see your retry pattern.</span>
519
+ </div>
520
+ </div>
521
+ </main>
522
+
523
+ <script>
524
+ class RetryGame {
525
+ constructor() {
526
+ this.level = 1;
527
+ this.retries = 0;
528
+ this.successes = 0;
529
+ this.attempts = [];
530
+ this.streak = 0;
531
+ this.isPlaying = false;
532
+ this.moverPosition = 0;
533
+ this.moverDirection = 1;
534
+ this.speed = 2;
535
+ this.animationId = null;
536
+
537
+ this.elements = {
538
+ mover: document.getElementById('mover'),
539
+ targetZone: document.getElementById('targetZone'),
540
+ gameArea: document.getElementById('gameArea'),
541
+ level: document.getElementById('level'),
542
+ retries: document.getElementById('retries'),
543
+ successRate: document.getElementById('successRate'),
544
+ streak: document.getElementById('streak'),
545
+ message: document.getElementById('message'),
546
+ actionBtn: document.getElementById('actionBtn'),
547
+ retryHistory: document.getElementById('retryHistory'),
548
+ progressFill: document.getElementById('progressFill')
549
+ };
550
+
551
+ this.init();
552
+ }
553
+
554
+ init() {
555
+ this.setupLevel();
556
+ this.startMoving();
557
+
558
+ // Touch support
559
+ this.elements.gameArea.addEventListener('touchstart', (e) => {
560
+ e.preventDefault();
561
+ this.attempt();
562
+ });
563
+
564
+ // Keyboard support
565
+ document.addEventListener('keydown', (e) => {
566
+ if (e.code === 'Space' || e.code === 'Enter') {
567
+ e.preventDefault();
568
+ this.attempt();
569
+ }
570
+ });
571
+ }
572
+
573
+ setupLevel() {
574
+ const areaWidth = this.elements.gameArea.offsetWidth;
575
+ const targetWidth = Math.max(60, 200 - (this.level * 10));
576
+ const targetLeft = Math.random() * (areaWidth - targetWidth - 40) + 20;
577
+
578
+ this.elements.targetZone.style.width = targetWidth + 'px';
579
+ this.elements.targetZone.style.left = targetLeft + 'px';
580
+
581
+ this.speed = Math.min(8, 2 + (this.level * 0.5));
582
+ this.moverPosition = 0;
583
+ this.moverDirection = 1;
584
+ }
585
+
586
+ startMoving() {
587
+ this.isPlaying = true;
588
+ this.elements.mover.classList.remove('perfect', 'fail');
589
+ this.animate();
590
+ }
591
+
592
+ animate() {
593
+ if (!this.isPlaying) return;
594
+
595
+ const areaWidth = this.elements.gameArea.offsetWidth;
596
+ this.moverPosition += this.speed * this.moverDirection;
597
+
598
+ if (this.moverPosition >= areaWidth || this.moverPosition <= 0) {
599
+ this.moverDirection *= -1;
600
+ this.moverPosition = Math.max(0, Math.min(areaWidth, this.moverPosition));
601
+ }
602
+
603
+ this.elements.mover.style.left = this.moverPosition + 'px';
604
+ this.animationId = requestAnimationFrame(() => this.animate());
605
+ }
606
+
607
+ attempt() {
608
+ if (!this.isPlaying) {
609
+ this.startMoving();
610
+ return;
611
+ }
612
+
613
+ this.isPlaying = false;
614
+ cancelAnimationFrame(this.animationId);
615
+
616
+ const targetRect = this.elements.targetZone.getBoundingClientRect();
617
+ const moverRect = this.elements.mover.getBoundingClientRect();
618
+ const gameRect = this.elements.gameArea.getBoundingClientRect();
619
+
620
+ const targetStart = targetRect.left - gameRect.left;
621
+ const targetEnd = targetStart + targetRect.width;
622
+ const moverCenter = moverRect.left - gameRect.left + (moverRect.width / 2);
623
+
624
+ const isSuccess = moverCenter >= targetStart && moverCenter <= targetEnd;
625
+
626
+ this.retries++;
627
+ this.attempts.push(isSuccess);
628
+
629
+ if (isSuccess) {
630
+ this.handleSuccess();
631
+ } else {
632
+ this.handleFail();
633
+ }
634
+
635
+ this.updateStats();
636
+ this.addToHistory(isSuccess);
637
+ }
638
+
639
+ handleSuccess() {
640
+ this.successes++;
641
+ this.streak++;
642
+ this.elements.mover.classList.add('perfect');
643
+ this.showMessage('Perfect! Level Complete', 'success');
644
+ this.createParticles();
645
+
646
+ const progress = (this.streak % 3) * 33.33;
647
+ this.elements.progressFill.style.width = progress + '%';
648
+
649
+ if (this.streak % 3 === 0) {
650
+ setTimeout(() => {
651
+ this.level++;
652
+ this.showMessage(`Level ${this.level} Unlocked!`, 'success');
653
+ this.elements.progressFill.style.width = '0%';
654
+ this.setupLevel();
655
+ this.startMoving();
656
+ }, 1000);
657
+ } else {
658
+ setTimeout(() => {
659
+ this.setupLevel();
660
+ this.startMoving();
661
+ }, 1000);
662
+ }
663
+ }
664
+
665
+ handleFail() {
666
+ this.streak = 0;
667
+ this.elements.mover.classList.add('fail');
668
+ this.showMessage('Miss! Retry?', 'fail');
669
+ this.elements.progressFill.style.width = '0%';
670
+
671
+ setTimeout(() => {
672
+ this.startMoving();
673
+ }, 800);
674
+ }
675
+
676
+ showMessage(text, type) {
677
+ const msg = this.elements.message;
678
+ msg.textContent = text;
679
+ msg.className = `message ${type} show`;
680
+
681
+ setTimeout(() => {
682
+ msg.classList.remove('show');
683
+ }, 2000);
684
+ }
685
+
686
+ createParticles() {
687
+ const rect = this.elements.mover.getBoundingClientRect();
688
+ const colors = ['#6366f1', '#10b981', '#f59e0b'];
689
+
690
+ for (let i = 0; i < 8; i++) {
691
+ const particle = document.createElement('div');
692
+ particle.className = 'particle';
693
+ particle.style.left = rect.left + rect.width/2 + 'px';
694
+ particle.style.top = rect.top + rect.height/2 + 'px';
695
+ particle.style.width = '8px';
696
+ particle.style.height = '8px';
697
+ particle.style.background = colors[Math.floor(Math.random() * colors.length)];
698
+ particle.style.borderRadius = '50%';
699
+ particle.style.setProperty('--tx', (Math.random() - 0.5) * 100 + 'px');
700
+ particle.style.setProperty('--ty', (Math.random() - 0.5) * 100 + 'px');
701
+
702
+ document.body.appendChild(particle);
703
+ setTimeout(() => particle.remove(), 1000);
704
+ }
705
+ }
706
+
707
+ addToHistory(success) {
708
+ if (this.attempts.length === 1) {
709
+ this.elements.retryHistory.innerHTML = '';
710
+ }
711
+
712
+ const dot = document.createElement('div');
713
+ dot.className = `retry-dot ${success ? 'success' : 'fail'}`;
714
+ dot.innerHTML = success ?
715
+ '<i class="ph ph-check"></i>' :
716
+ '<i class="ph ph-x"></i>';
717
+ dot.title = `Attempt #${this.attempts.length}: ${success ? 'Success' : 'Fail'}`;
718
+
719
+ this.elements.retryHistory.appendChild(dot);
720
+ this.elements.retryHistory.scrollTop = this.elements.retryHistory.scrollHeight;
721
+ }
722
+
723
+ updateStats() {
724
+ this.elements.level.textContent = this.level;
725
+ this.elements.retries.textContent = this.retries;
726
+ this.elements.streak.textContent = this.streak;
727
+
728
+ const rate = this.attempts.length > 0
729
+ ? Math.round((this.successes / this.attempts.length) * 100)
730
+ : 100;
731
+ this.elements.successRate.textContent = rate + '%';
732
+ }
733
+
734
+ reset() {
735
+ this.isPlaying = false;
736
+ cancelAnimationFrame(this.animationId);
737
+ this.streak = 0;
738
+ this.elements.progressFill.style.width = '0%';
739
+ this.setupLevel();
740
+ this.startMoving();
741
+ this.showMessage('Level Reset', 'fail');
742
+ }
743
+ }
744
+
745
+ // Initialize game when DOM is ready
746
+ let game;
747
+ document.addEventListener('DOMContentLoaded', () => {
748
+ game = new RetryGame();
749
+ });
750
+
751
+ // Prevent zoom on double tap for mobile
752
+ let lastTouchEnd = 0;
753
+ document.addEventListener('touchend', (e) => {
754
+ const now = Date.now();
755
+ if (now - lastTouchEnd <= 300) {
756
+ e.preventDefault();
757
+ }
758
+ lastTouchEnd = now;
759
+ }, false);
760
+ </script>
761
+ </body>
762
+ </html>