Melfi commited on
Commit
669f2bb
·
verified ·
1 Parent(s): 755f8e7

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +1575 -19
index.html CHANGED
@@ -1,19 +1,1575 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </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>Waterful Ring Toss</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.4/gsap.min.js"></script>
10
+ <style>
11
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');
12
+
13
+ :root {
14
+ --water-color: rgba(64, 164, 223, 0.7);
15
+ --container-color: rgba(255, 255, 255, 0.15);
16
+ --container-border: 3px solid rgba(255, 255, 255, 0.3);
17
+ }
18
+
19
+ body {
20
+ font-family: 'Poppins', sans-serif;
21
+ overflow: hidden;
22
+ touch-action: none;
23
+ -webkit-touch-callout: none;
24
+ -webkit-user-select: none;
25
+ user-select: none;
26
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
27
+ }
28
+
29
+ #gameView {
30
+ position: relative;
31
+ width: 100vw;
32
+ height: 100vh;
33
+ overflow: hidden;
34
+ }
35
+
36
+ .water-layer {
37
+ position: absolute;
38
+ bottom: 0;
39
+ left: 0;
40
+ width: 100%;
41
+ height: 45%;
42
+ background: var(--water-color);
43
+ z-index: 5;
44
+ border-top-left-radius: 50% 30%;
45
+ border-top-right-radius: 50% 30%;
46
+ box-shadow: inset 0 -15px 30px rgba(0, 119, 182, 0.5);
47
+ pointer-events: none;
48
+ overflow: hidden;
49
+ }
50
+
51
+ .water-surface {
52
+ position: absolute;
53
+ top: 0;
54
+ left: 0;
55
+ width: 200%;
56
+ height: 100px;
57
+ background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.3) 50%, rgba(255,255,255,0) 100%);
58
+ animation: waterFlow 8s linear infinite;
59
+ pointer-events: none;
60
+ }
61
+
62
+ @keyframes waterFlow {
63
+ 0% { transform: translateX(-50%) rotate(0.5deg); }
64
+ 100% { transform: translateX(0%) rotate(-0.5deg); }
65
+ }
66
+
67
+ .water-ripple {
68
+ position: absolute;
69
+ width: 100px;
70
+ height: 20px;
71
+ background: radial-gradient(ellipse at center, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0) 70%);
72
+ border-radius: 50%;
73
+ pointer-events: none;
74
+ }
75
+
76
+ .container {
77
+ position: absolute;
78
+ bottom: 10%;
79
+ left: 50%;
80
+ width: 80%;
81
+ height: 60%;
82
+ transform: translateX(-50%);
83
+ background: var(--container-color);
84
+ border: var(--container-border);
85
+ border-bottom: none;
86
+ border-top-left-radius: 20px;
87
+ border-top-right-radius: 20px;
88
+ backdrop-filter: blur(5px);
89
+ z-index: 10;
90
+ overflow: hidden;
91
+ }
92
+
93
+ .container-glass {
94
+ position: absolute;
95
+ top: 0;
96
+ left: 0;
97
+ width: 100%;
98
+ height: 100%;
99
+ background: radial-gradient(ellipse at top, rgba(255,255,255,0.3) 0%, transparent 70%);
100
+ pointer-events: none;
101
+ z-index: 11;
102
+ }
103
+
104
+ .ring {
105
+ width: 40px;
106
+ height: 40px;
107
+ border-radius: 50%;
108
+ position: absolute;
109
+ z-index: 20;
110
+ border: 3px solid;
111
+ box-shadow: 0 4px 10px rgba(0,0,0,0.3);
112
+ transition: transform 0.2s, box-shadow 0.2s;
113
+ pointer-events: all;
114
+ }
115
+
116
+ .ring:hover {
117
+ transform: scale(1.05);
118
+ box-shadow: 0 6px 15px rgba(0,0,0,0.4);
119
+ }
120
+
121
+ .active-ring {
122
+ border: 3px dashed;
123
+ opacity: 0.8;
124
+ z-index: 25;
125
+ }
126
+
127
+ .peg {
128
+ position: absolute;
129
+ width: 14px;
130
+ height: 14px;
131
+ background: #FF5722;
132
+ border-radius: 50%;
133
+ z-index: 15;
134
+ box-shadow: 0 2px 5px rgba(0,0,0,0.3), inset 0 -2px 3px rgba(0,0,0,0.2);
135
+ border: 2px solid #E64A19;
136
+ }
137
+
138
+ .bubble {
139
+ position: absolute;
140
+ width: 20px;
141
+ height: 20px;
142
+ background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0.4) 60%);
143
+ border-radius: 50%;
144
+ z-index: 6;
145
+ pointer-events: none;
146
+ box-shadow: 0 0 10px rgba(255,255,255,0.4);
147
+ }
148
+
149
+ @keyframes bubbleFloat {
150
+ 0% { transform: translateY(0) scale(1); opacity: 0.8; }
151
+ 100% { transform: translateY(-150px) scale(1.5); opacity: 0; }
152
+ }
153
+
154
+ .bubble-float {
155
+ animation: bubbleFloat 2s ease-out forwards;
156
+ }
157
+
158
+ .bubble-btn {
159
+ position: absolute;
160
+ bottom: 20px;
161
+ left: 50%;
162
+ transform: translateX(-50%);
163
+ width: 60px;
164
+ height: 60px;
165
+ background: radial-gradient(circle, #4CAF50, #2E7D32);
166
+ border-radius: 50%;
167
+ border: 3px solid #1B5E20;
168
+ box-shadow: 0 4px 10px rgba(0,0,0,0.3), inset 0 -4px 10px rgba(0,0,0,0.2);
169
+ color: white;
170
+ font-size: 12px;
171
+ font-weight: bold;
172
+ text-align: center;
173
+ line-height: 60px;
174
+ cursor: pointer;
175
+ z-index: 50;
176
+ transition: all 0.2s;
177
+ overflow: hidden;
178
+ }
179
+
180
+ .bubble-btn::after {
181
+ content: '';
182
+ position: absolute;
183
+ top: -10px;
184
+ left: -10px;
185
+ right: -10px;
186
+ bottom: -10px;
187
+ background: radial-gradient(circle, rgba(76, 175, 80, 0.5), transparent);
188
+ border-radius: 50%;
189
+ opacity: 0;
190
+ pointer-events: none;
191
+ }
192
+
193
+ .bubble-btn:active {
194
+ transform: translateX(-50%) scale(0.95);
195
+ box-shadow: 0 2px 5px rgba(0,0,0,0.3);
196
+ }
197
+
198
+ .bubble-btn:active::after {
199
+ animation: ripple 0.6s ease-out;
200
+ }
201
+
202
+ @keyframes ripple {
203
+ 0% { transform: scale(0.3); opacity: 1; }
204
+ 100% { transform: scale(2); opacity: 0; }
205
+ }
206
+
207
+ .screen {
208
+ position: fixed;
209
+ top: 0;
210
+ left: 0;
211
+ width: 100%;
212
+ height: 100%;
213
+ background: rgba(0, 0, 0, 0.8);
214
+ z-index: 1000;
215
+ display: flex;
216
+ flex-direction: column;
217
+ justify-content: center;
218
+ align-items: center;
219
+ color: white;
220
+ transition: opacity 0.3s;
221
+ opacity: 1;
222
+ pointer-events: all;
223
+ }
224
+
225
+ .screen.hidden {
226
+ opacity: 0;
227
+ pointer-events: none;
228
+ }
229
+
230
+ .screen-title {
231
+ font-size: 3rem;
232
+ font-weight: bold;
233
+ margin-bottom: 2rem;
234
+ text-shadow: 0 4px 8px rgba(0,0,0,0.5);
235
+ background: linear-gradient(45deg, #4FC3F7, #2196F3);
236
+ -webkit-background-clip: text;
237
+ background-clip: text;
238
+ color: transparent;
239
+ }
240
+
241
+ .screen-content {
242
+ background: rgba(30, 30, 40, 0.9);
243
+ padding: 2rem;
244
+ border-radius: 20px;
245
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
246
+ width: 90%;
247
+ max-width: 500px;
248
+ max-height: 80vh;
249
+ overflow-y: auto;
250
+ }
251
+
252
+ .btn {
253
+ background: linear-gradient(145deg, #4FC3F7, #29B6F6);
254
+ border: none;
255
+ color: white;
256
+ padding: 12px 24px;
257
+ border-radius: 30px;
258
+ font-weight: bold;
259
+ cursor: pointer;
260
+ margin: 10px;
261
+ box-shadow: 0 4px 15px rgba(0,0,0,0.2);
262
+ transition: all 0.3s;
263
+ text-align: center;
264
+ min-width: 200px;
265
+ }
266
+
267
+ .btn:hover {
268
+ transform: translateY(-3px);
269
+ box-shadow: 0 8px 20px rgba(0,0,0,0.3);
270
+ }
271
+
272
+ .btn:active {
273
+ transform: translateY(0);
274
+ box-shadow: 0 2px 10px rgba(0,0,0,0.2);
275
+ }
276
+
277
+ .btn-secondary {
278
+ background: linear-gradient(145deg, #666, #444);
279
+ }
280
+
281
+ .btn-challenge {
282
+ background: linear-gradient(145deg, #FFA726, #FB8C00);
283
+ }
284
+
285
+ .game-display {
286
+ position: fixed;
287
+ background: rgba(0,0,0,0.7);
288
+ color: white;
289
+ padding: 10px 15px;
290
+ border-radius: 10px;
291
+ z-index: 100;
292
+ font-size: 1rem;
293
+ }
294
+
295
+ #scoreDisplay {
296
+ top: 20px;
297
+ right: 20px;
298
+ }
299
+
300
+ #timerDisplay {
301
+ top: 20px;
302
+ left: 20px;
303
+ }
304
+
305
+ #ringCounter {
306
+ bottom: 100px;
307
+ right: 20px;
308
+ display: flex;
309
+ flex-direction: column;
310
+ align-items: center;
311
+ }
312
+
313
+ .ring-indicator {
314
+ width: 30px;
315
+ height: 30px;
316
+ border-radius: 50%;
317
+ border: 2px solid;
318
+ margin: 5px;
319
+ }
320
+
321
+ .settings-group {
322
+ margin-bottom: 1.5rem;
323
+ }
324
+
325
+ .settings-label {
326
+ display: block;
327
+ margin-bottom: 0.5rem;
328
+ color: #BBBBBB;
329
+ }
330
+
331
+ .slider-container {
332
+ display: flex;
333
+ align-items: center;
334
+ gap: 10px;
335
+ }
336
+
337
+ .slider {
338
+ -webkit-appearance: none;
339
+ width: 100%;
340
+ height: 8px;
341
+ border-radius: 4px;
342
+ background: #444;
343
+ outline: none;
344
+ }
345
+
346
+ .slider::-webkit-slider-thumb {
347
+ -webkit-appearance: none;
348
+ appearance: none;
349
+ width: 18px;
350
+ height: 18px;
351
+ border-radius: 50%;
352
+ background: #4FC3F7;
353
+ cursor: pointer;
354
+ }
355
+
356
+ .settings-option {
357
+ margin-bottom: 1rem;
358
+ padding-bottom: 1rem;
359
+ border-bottom: 1px solid #444;
360
+ }
361
+
362
+ .lang-option {
363
+ display: flex;
364
+ align-items: center;
365
+ margin-bottom: 0.5rem;
366
+ }
367
+
368
+ .lang-flag {
369
+ width: 24px;
370
+ height: 16px;
371
+ margin-right: 10px;
372
+ border-radius: 2px;
373
+ }
374
+
375
+ .difficulty-option {
376
+ display: flex;
377
+ justify-content: space-between;
378
+ margin-top: 0.5rem;
379
+ }
380
+
381
+ .difficulty-btn {
382
+ padding: 8px 16px;
383
+ margin: 0;
384
+ min-width: auto;
385
+ background: #444;
386
+ }
387
+
388
+ .difficulty-btn.active {
389
+ background: linear-gradient(145deg, #4FC3F7, #29B6F6);
390
+ }
391
+
392
+ .credits-content {
393
+ text-align: center;
394
+ line-height: 1.8;
395
+ }
396
+
397
+ .credits-name {
398
+ font-size: 1.5rem;
399
+ margin-bottom: 1rem;
400
+ color: #4FC3F7;
401
+ }
402
+
403
+ .credits-title {
404
+ color: #FFA726;
405
+ margin: 1rem 0;
406
+ }
407
+
408
+ .credits-text {
409
+ color: #BBBBBB;
410
+ }
411
+
412
+ .game-overlay {
413
+ position: fixed;
414
+ top: 0;
415
+ left: 0;
416
+ width: 100%;
417
+ height: 100%;
418
+ background: linear-gradient(135deg, rgba(0,0,0,0.3), rgba(0,0,0,0.1));
419
+ z-index: 500;
420
+ display: none;
421
+ }
422
+
423
+ .modal {
424
+ position: fixed;
425
+ top: 50%;
426
+ left: 50%;
427
+ transform: translate(-50%, -50%);
428
+ background: rgba(30, 30, 40, 0.95);
429
+ padding: 2rem;
430
+ border-radius: 20px;
431
+ box-shadow: 0 10px 30px rgba(0,0,0,0.8);
432
+ max-width: 80%;
433
+ max-height: 80vh;
434
+ overflow-y: auto;
435
+ z-index: 501;
436
+ display: none;
437
+ }
438
+
439
+ .modal-title {
440
+ font-size: 2rem;
441
+ font-weight: bold;
442
+ margin-bottom: 1.5rem;
443
+ text-align: center;
444
+ color: #4FC3F7;
445
+ }
446
+
447
+ .modal-text {
448
+ margin-bottom: 2rem;
449
+ line-height: 1.6;
450
+ }
451
+
452
+ .modal-buttons {
453
+ display: flex;
454
+ justify-content: center;
455
+ gap: 1rem;
456
+ margin-top: 1.5rem;
457
+ }
458
+
459
+ .modal-btn {
460
+ min-width: 120px;
461
+ }
462
+
463
+ /* Responsive adjustments */
464
+ @media (max-width: 768px) {
465
+ .screen-title {
466
+ font-size: 2rem;
467
+ margin-bottom: 1.5rem;
468
+ }
469
+
470
+ .screen-content {
471
+ padding: 1.5rem;
472
+ }
473
+
474
+ .btn {
475
+ min-width: 160px;
476
+ padding: 10px 20px;
477
+ margin: 8px;
478
+ }
479
+
480
+ .container {
481
+ width: 90%;
482
+ height: 55%;
483
+ }
484
+ }
485
+
486
+ @media (max-width: 480px) {
487
+ .screen-title {
488
+ font-size: 1.8rem;
489
+ margin-bottom: 1rem;
490
+ }
491
+
492
+ .screen-content {
493
+ padding: 1rem;
494
+ }
495
+
496
+ .btn {
497
+ min-width: 140px;
498
+ padding: 8px 16px;
499
+ margin: 5px;
500
+ font-size: 0.9rem;
501
+ }
502
+
503
+ #scoreDisplay, #timerDisplay {
504
+ font-size: 0.9rem;
505
+ padding: 8px 12px;
506
+ }
507
+
508
+ .container {
509
+ width: 95%;
510
+ height: 50%;
511
+ }
512
+ }
513
+ </style>
514
+ </head>
515
+ <body>
516
+ <!-- Game View -->
517
+ <div id="gameView">
518
+ <!-- Water Background -->
519
+ <div class="water-layer">
520
+ <div class="water-surface"></div>
521
+ </div>
522
+
523
+ <!-- Game Container -->
524
+ <div class="container">
525
+ <div class="container-glass"></div>
526
+ </div>
527
+
528
+ <!-- Game UI Elements -->
529
+ <div id="scoreDisplay" class="game-display">
530
+ Score: <span id="scoreValue">0</span>
531
+ </div>
532
+
533
+ <div id="timerDisplay" class="game-display">
534
+ Time: <span id="timeValue">60</span>s
535
+ </div>
536
+
537
+ <div id="ringCounter" class="game-display">
538
+ <div>Rings Left:</div>
539
+ <div id="ringIndicators" class="flex"></div>
540
+ </div>
541
+
542
+ <div id="bubbleBtn" class="bubble-btn">Bubble</div>
543
+ </div>
544
+
545
+ <!-- Screens -->
546
+ <div id="startScreen" class="screen">
547
+ <div class="screen-content text-center">
548
+ <h2 class="screen-title">Waterful Ring Toss</h2>
549
+ <button id="startBtn" class="btn">Play Game</button>
550
+ <button id="optionsBtn" class="btn btn-secondary">Options</button>
551
+ <button id="creditsBtn" class="btn btn-secondary">Credits</button>
552
+ </div>
553
+ </div>
554
+
555
+ <div id="modeScreen" class="screen hidden">
556
+ <div class="screen-content text-center">
557
+ <h2 class="screen-title">Select Mode</h2>
558
+ <button id="classicModeBtn" class="btn">Classic Mode</button>
559
+ <button id="challengeModeBtn" class="btn btn-challenge">Challenge Mode</button>
560
+ <button id="backToStartBtn" class="btn btn-secondary">Back</button>
561
+ </div>
562
+ </div>
563
+
564
+ <div id="optionsScreen" class="screen hidden">
565
+ <div class="screen-content">
566
+ <h2 class="screen-title mb-6">Options</h2>
567
+
568
+ <div class="settings-group">
569
+ <div class="settings-option">
570
+ <label class="settings-label">Music Volume</label>
571
+ <div class="slider-container">
572
+ <input type="range" min="0" max="100" value="50" class="slider" id="musicSlider">
573
+ <span id="musicValue">50%</span>
574
+ </div>
575
+ </div>
576
+
577
+ <div class="settings-option">
578
+ <label class="settings-label">Sound Effects</label>
579
+ <div class="slider-container">
580
+ <input type="range" min="0" max="100" value="70" class="slider" id="sfxSlider">
581
+ <span id="sfxValue">70%</span>
582
+ </div>
583
+ </div>
584
+
585
+ <div class="settings-option">
586
+ <label class="settings-label">Language</label>
587
+ <div class="lang-option">
588
+ <img src="https://flagcdn.com/w20/us.png" class="lang-flag" alt="English">
589
+ <span>English (US)</span>
590
+ </div>
591
+ <div class="lang-option">
592
+ <img src="https://flagcdn.com/w20/es.png" class="lang-flag" alt="Spanish">
593
+ <span>Español</span>
594
+ </div>
595
+ <div class="lang-option">
596
+ <img src="https://flagcdn.com/w20/fr.png" class="lang-flag" alt="French">
597
+ <span>Français</span>
598
+ </div>
599
+ </div>
600
+
601
+ <div class="settings-option">
602
+ <label class="settings-label">Game Difficulty</label>
603
+ <div class="difficulty-option">
604
+ <button class="difficulty-btn btn-secondary active">Easy</button>
605
+ <button class="difficulty-btn btn-secondary">Medium</button>
606
+ <button class="difficulty-btn btn-secondary">Hard</button>
607
+ </div>
608
+ </div>
609
+ </div>
610
+
611
+ <div class="text-center mt-6">
612
+ <button id="saveOptionsBtn" class="btn">Save Settings</button>
613
+ <button id="cancelOptionsBtn" class="btn btn-secondary">Cancel</button>
614
+ </div>
615
+ </div>
616
+ </div>
617
+
618
+ <div id="creditsScreen" class="screen hidden">
619
+ <div class="screen-content">
620
+ <h2 class="screen-title">Credits</h2>
621
+
622
+ <div class="credits-content">
623
+ <div class="credits-name">WATERFUL RING TOSS</div>
624
+ <div class="credits-text">A digital recreation of the classic arcade game</div>
625
+
626
+ <div class="credits-title">Developed By</div>
627
+ <div class="credits-name">Your Name Here</div>
628
+ <div class="credits-text">© 2023 All Rights Reserved</div>
629
+
630
+ <div class="credits-title">Inspired By</div>
631
+ <div class="credits-text">Tomy's Original Waterful Ring Toss Game</div>
632
+
633
+ <div class="credits-title">Special Thanks</div>
634
+ <div class="credits-text">To everyone who enjoys classic arcade games!</div>
635
+ </div>
636
+
637
+ <div class="text-center mt-6">
638
+ <button id="backFromCreditsBtn" class="btn">Back</button>
639
+ </div>
640
+ </div>
641
+ </div>
642
+
643
+ <!-- Game Overlay and Modals -->
644
+ <div id="gameOverlay" class="game-overlay"></div>
645
+
646
+ <div id="resultModal" class="modal">
647
+ <h3 class="modal-title">Success!</h3>
648
+ <p class="modal-text" id="resultText">You landed the ring on a peg!</p>
649
+ <div class="modal-buttons">
650
+ <button id="continueBtn" class="btn modal-btn">Continue</button>
651
+ </div>
652
+ </div>
653
+
654
+ <div id="pauseModal" class="modal">
655
+ <h3 class="modal-title">Game Paused</h3>
656
+ <div class="modal-buttons">
657
+ <button id="resumeBtn" class="btn modal-btn">Resume</button>
658
+ <button id="quitBtn" class="btn btn-secondary modal-btn">Quit</button>
659
+ </div>
660
+ </div>
661
+
662
+ <div id="gameOverModal" class="modal">
663
+ <h3 class="modal-title">Game Over</h3>
664
+ <p class="modal-text" id="finalScoreText">Your final score: <span id="finalScoreValue">0</span></p>
665
+ <div class="modal-buttons">
666
+ <button id="playAgainBtn" class="btn modal-btn">Play Again</button>
667
+ <button id="mainMenuBtn" class="btn btn-secondary modal-btn">Main Menu</button>
668
+ </div>
669
+ </div>
670
+
671
+ <div id="countdownDisplay" class="fixed inset-0 flex items-center justify-center text-white text-9xl font-bold text-shadow-lg z-1000 hidden">
672
+ 3
673
+ </div>
674
+
675
+ <!-- Game Elements (dynamically added) -->
676
+ <div id="gameElements"></div>
677
+
678
+ <script>
679
+ // Game Model - Manages data and game logic
680
+ class GameModel {
681
+ constructor() {
682
+ this.mode = null; // 'classic' or 'challenge'
683
+ this.score = 0;
684
+ this.timeLeft = 60;
685
+ this.ringsLeft = 0;
686
+ this.difficulty = 'easy';
687
+ this.musicVolume = 50;
688
+ this.sfxVolume = 70;
689
+ this.language = 'en';
690
+ this.isPaused = false;
691
+ this.gameActive = false;
692
+ this.pegs = [];
693
+ this.rings = [];
694
+ this.activeRing = null;
695
+ this.physicsEngine = null;
696
+ }
697
+
698
+ setMode(mode) {
699
+ this.mode = mode;
700
+ if (mode === 'classic') {
701
+ this.ringsLeft = 10;
702
+ } else {
703
+ this.ringsLeft = 15;
704
+ this.timeLeft = 60;
705
+ }
706
+ this.score = 0;
707
+ }
708
+
709
+ updateScore(points) {
710
+ this.score += points;
711
+ return this.score;
712
+ }
713
+
714
+ decrementTime() {
715
+ if (this.timeLeft > 0 && !this.isPaused) {
716
+ this.timeLeft--;
717
+ }
718
+ return this.timeLeft;
719
+ }
720
+
721
+ decrementRings() {
722
+ if (this.ringsLeft > 0) {
723
+ this.ringsLeft--;
724
+ }
725
+ return this.ringsLeft;
726
+ }
727
+
728
+ addTime(seconds) {
729
+ this.timeLeft += seconds;
730
+ return this.timeLeft;
731
+ }
732
+
733
+ togglePause() {
734
+ this.isPaused = !this.isPaused;
735
+ return this.isPaused;
736
+ }
737
+
738
+ resetGame() {
739
+ this.score = 0;
740
+ if (this.mode === 'classic') {
741
+ this.ringsLeft = 10;
742
+ } else {
743
+ this.ringsLeft = 15;
744
+ this.timeLeft = 60;
745
+ }
746
+ this.isPaused = false;
747
+ }
748
+ }
749
+
750
+ // Game View - Handles rendering and UI updates
751
+ class GameView {
752
+ constructor() {
753
+ // DOM elements
754
+ this.startScreen = document.getElementById('startScreen');
755
+ this.modeScreen = document.getElementById('modeScreen');
756
+ this.optionsScreen = document.getElementById('optionsScreen');
757
+ this.creditsScreen = document.getElementById('creditsScreen');
758
+ this.scoreDisplay = document.getElementById('scoreDisplay');
759
+ this.scoreValue = document.getElementById('scoreValue');
760
+ this.timeDisplay = document.getElementById('timerDisplay');
761
+ this.timeValue = document.getElementById('timeValue');
762
+ this.ringIndicators = document.getElementById('ringIndicators');
763
+ this.bubbleBtn = document.getElementById('bubbleBtn');
764
+ this.gameView = document.getElementById('gameView');
765
+ this.container = document.querySelector('.container');
766
+ this.gameElements = document.getElementById('gameElements');
767
+ this.countdownDisplay = document.getElementById('countdownDisplay');
768
+
769
+ // Buttons
770
+ this.startBtn = document.getElementById('startBtn');
771
+ this.optionsBtn = document.getElementById('optionsBtn');
772
+ this.creditsBtn = document.getElementById('creditsBtn');
773
+ this.classicModeBtn = document.getElementById('classicModeBtn');
774
+ this.challengeModeBtn = document.getElementById('challengeModeBtn');
775
+ this.backToStartBtn = document.getElementById('backToStartBtn');
776
+ this.saveOptionsBtn = document.getElementById('saveOptionsBtn');
777
+ this.cancelOptionsBtn = document.getElementById('cancelOptionsBtn');
778
+ this.backFromCreditsBtn = document.getElementById('backFromCreditsBtn');
779
+
780
+ // Modals
781
+ this.gameOverlay = document.getElementById('gameOverlay');
782
+ this.resultModal = document.getElementById('resultModal');
783
+ this.resultText = document.getElementById('resultText');
784
+ this.continueBtn = document.getElementById('continueBtn');
785
+ this.pauseModal = document.getElementById('pauseModal');
786
+ this.resumeBtn = document.getElementById('resumeBtn');
787
+ this.quitBtn = document.getElementById('quitBtn');
788
+ this.gameOverModal = document.getElementById('gameOverModal');
789
+ this.finalScoreText = document.getElementById('finalScoreText');
790
+ this.finalScoreValue = document.getElementById('finalScoreValue');
791
+ this.playAgainBtn = document.getElementById('playAgainBtn');
792
+ this.mainMenuBtn = document.getElementById('mainMenuBtn');
793
+ }
794
+
795
+ updateScore(score) {
796
+ this.scoreValue.textContent = score;
797
+ }
798
+
799
+ updateTime(time) {
800
+ this.timeValue.textContent = time;
801
+ }
802
+
803
+ updateRings(ringsLeft, totalRings) {
804
+ this.ringIndicators.innerHTML = '';
805
+
806
+ for (let i = 0; i < totalRings; i++) {
807
+ const indicator = document.createElement('div');
808
+ indicator.className = 'ring-indicator';
809
+
810
+ if (i < ringsLeft) {
811
+ const randomColor = this.getRandomRingColor();
812
+ indicator.style.borderColor = randomColor;
813
+ indicator.style.backgroundColor = `${randomColor}40`;
814
+ } else {
815
+ indicator.style.borderColor = '#666';
816
+ indicator.style.backgroundColor = 'transparent';
817
+ }
818
+
819
+ this.ringIndicators.appendChild(indicator);
820
+ }
821
+ }
822
+
823
+ getRandomRingColor() {
824
+ const colors = ['#FF5722', '#4CAF50', '#2196F3', '#FFC107', '#9C27B0'];
825
+ return colors[Math.floor(Math.random() * colors.length)];
826
+ }
827
+
828
+ showScreen(screenName) {
829
+ document.getElementById('startScreen').classList.add('hidden');
830
+ document.getElementById('modeScreen').classList.add('hidden');
831
+ document.getElementById('optionsScreen').classList.add('hidden');
832
+ document.getElementById('creditsScreen').classList.add('hidden');
833
+
834
+ if (screenName) {
835
+ document.getElementById(screenName).classList.remove('hidden');
836
+ }
837
+ }
838
+
839
+ showGameUI() {
840
+ this.scoreDisplay.classList.remove('hidden');
841
+ this.timeDisplay.classList.remove('hidden');
842
+ this.ringCounter.classList.remove('hidden');
843
+ this.bubbleBtn.classList.remove('hidden');
844
+ }
845
+
846
+ hideGameUI() {
847
+ this.scoreDisplay.classList.add('hidden');
848
+ this.timeDisplay.classList.add('hidden');
849
+ this.ringCounter.classList.add('hidden');
850
+ this.bubbleBtn.classList.add('hidden');
851
+ }
852
+
853
+ createRing(x, y, size, color) {
854
+ const ring = document.createElement('div');
855
+ ring.className = 'ring active-ring';
856
+ ring.style.width = `${size}px`;
857
+ ring.style.height = `${size}px`;
858
+ ring.style.left = `${x - size/2}px`;
859
+ ring.style.top = `${y - size/2}px`;
860
+ ring.style.borderColor = color;
861
+ ring.style.backgroundColor = `${color}40`;
862
+
863
+ this.gameElements.appendChild(ring);
864
+ return ring;
865
+ }
866
+
867
+ createPeg(x, y, size, color, points) {
868
+ const peg = document.createElement('div');
869
+ peg.className = 'peg';
870
+ peg.style.left = `${x - size/2}px`;
871
+ peg.style.top = `${y - size/2}px`;
872
+ peg.style.width = `${size}px`;
873
+ peg.style.height = `${size}px`;
874
+ peg.style.backgroundColor = color;
875
+
876
+ this.container.appendChild(peg);
877
+ return peg;
878
+ }
879
+
880
+ createBubble(x, y) {
881
+ const size = 15 + Math.random() * 20;
882
+ const bubble = document.createElement('div');
883
+ bubble.className = 'bubble bubble-float';
884
+ bubble.style.width = `${size}px`;
885
+ bubble.style.height = `${size}px`;
886
+ bubble.style.left = `${x - size/2}px`;
887
+ bubble.style.top = `${y - size/2}px`;
888
+
889
+ this.gameView.appendChild(bubble);
890
+
891
+ setTimeout(() => {
892
+ bubble.remove();
893
+ }, 2000);
894
+ }
895
+
896
+ showResult(message) {
897
+ this.resultText.textContent = message;
898
+ this.gameOverlay.style.display = 'block';
899
+ this.resultModal.style.display = 'block';
900
+ }
901
+
902
+ hideResult() {
903
+ this.gameOverlay.style.display = 'none';
904
+ this.resultModal.style.display = 'none';
905
+ }
906
+
907
+ showPauseMenu() {
908
+ this.gameOverlay.style.display = 'block';
909
+ this.pauseModal.style.display = 'block';
910
+ }
911
+
912
+ hidePauseMenu() {
913
+ this.gameOverlay.style.display = 'none';
914
+ this.pauseModal.style.display = 'none';
915
+ }
916
+
917
+ showGameOver(score) {
918
+ this.finalScoreValue.textContent = score;
919
+ this.gameOverlay.style.display = 'block';
920
+ this.gameOverModal.style.display = 'block';
921
+ }
922
+
923
+ hideGameOver() {
924
+ this.gameOverlay.style.display = 'none';
925
+ this.gameOverModal.style.display = 'none';
926
+ }
927
+
928
+ showCountdown(count, callback) {
929
+ this.countdownDisplay.textContent = count;
930
+ this.countdownDisplay.classList.remove('hidden');
931
+
932
+ let current = count;
933
+ const interval = setInterval(() => {
934
+ current--;
935
+ if (current > 0) {
936
+ this.countdownDisplay.textContent = current;
937
+ } else {
938
+ this.countdownDisplay.textContent = 'GO!';
939
+ setTimeout(() => {
940
+ this.countdownDisplay.classList.add('hidden');
941
+ clearInterval(interval);
942
+ if (callback) callback();
943
+ }, 500);
944
+ }
945
+ }, 1000);
946
+ }
947
+
948
+ clearGameElements() {
949
+ this.gameElements.innerHTML = '';
950
+ const pegs = document.querySelectorAll('.peg');
951
+ pegs.forEach(peg => peg.remove());
952
+ }
953
+ }
954
+
955
+ // Game Controller - Manages game flow and user interactions
956
+ class GameController {
957
+ constructor(model, view) {
958
+ this.model = model;
959
+ this.view = view;
960
+ this.isDragging = false;
961
+ this.startDragPos = { x: 0, y: 0 };
962
+ this.currentDragPos = { x: 0, y: 0 };
963
+ this.gameInterval = null;
964
+ this.physicsWorld = null;
965
+ this.physicsRings = [];
966
+ this.activeRingElement = null;
967
+
968
+ // Initialize event listeners
969
+ this.initEventListeners();
970
+
971
+ // Initialize physics engine (Matter.js)
972
+ this.initPhysics();
973
+ }
974
+
975
+ initEventListeners() {
976
+ // Screen navigation
977
+ this.view.startBtn.addEventListener('click', () => {
978
+ this.view.showScreen('modeScreen');
979
+ });
980
+
981
+ this.view.optionsBtn.addEventListener('click', () => {
982
+ this.view.showScreen('optionsScreen');
983
+ });
984
+
985
+ this.view.creditsBtn.addEventListener('click', () => {
986
+ this.view.showScreen('creditsScreen');
987
+ });
988
+
989
+ this.view.backToStartBtn.addEventListener('click', () => {
990
+ this.view.showScreen('startScreen');
991
+ });
992
+
993
+ this.view.backFromCreditsBtn.addEventListener('click', () => {
994
+ this.view.showScreen('startScreen');
995
+ });
996
+
997
+ this.view.cancelOptionsBtn.addEventListener('click', () => {
998
+ this.view.showScreen('startScreen');
999
+ });
1000
+
1001
+ // Game mode selection
1002
+ this.view.classicModeBtn.addEventListener('click', () => {
1003
+ this.startGame('classic');
1004
+ });
1005
+
1006
+ this.view.challengeModeBtn.addEventListener('click', () => {
1007
+ this.startGame('challenge');
1008
+ });
1009
+
1010
+ // Bubble button
1011
+ this.view.bubbleBtn.addEventListener('click', (e) => {
1012
+ this.createBubble();
1013
+ });
1014
+
1015
+ // Pause/resume game
1016
+ document.addEventListener('keydown', (e) => {
1017
+ if (e.key === 'Escape' && this.model.gameActive) {
1018
+ this.togglePause();
1019
+ }
1020
+ });
1021
+
1022
+ // For mobile devices, add a pause button if needed
1023
+
1024
+ // Modal controls
1025
+ this.view.continueBtn.addEventListener('click', () => {
1026
+ this.view.hideResult();
1027
+ if (this.model.ringsLeft > 0) {
1028
+ this.createNewRing();
1029
+ } else if (this.model.mode === 'challenge' && this.model.timeLeft <= 0) {
1030
+ this.endGame();
1031
+ }
1032
+ });
1033
+
1034
+ this.view.resumeBtn.addEventListener('click', () => {
1035
+ this.togglePause();
1036
+ });
1037
+
1038
+ this.view.quitBtn.addEventListener('click', () => {
1039
+ this.endGame();
1040
+ });
1041
+
1042
+ this.view.playAgainBtn.addEventListener('click', () => {
1043
+ this.view.hideGameOver();
1044
+ this.startGame(this.model.mode);
1045
+ });
1046
+
1047
+ this.view.mainMenuBtn.addEventListener('click', () => {
1048
+ this.view.hideGameOver();
1049
+ this.view.showScreen('startScreen');
1050
+ });
1051
+
1052
+ // Ring dragging controls
1053
+ this.view.gameView.addEventListener('touchstart', (e) => this.handleStart(e));
1054
+ this.view.gameView.addEventListener('mousedown', (e) => this.handleStart(e));
1055
+
1056
+ this.view.gameView.addEventListener('touchmove', (e) => this.handleMove(e));
1057
+ this.view.gameView.addEventListener('mousemove', (e) => this.handleMove(e));
1058
+
1059
+ this.view.gameView.addEventListener('touchend', (e) => this.handleEnd(e));
1060
+ this.view.gameView.addEventListener('mouseup', (e) => this.handleEnd(e));
1061
+
1062
+ // Window resize
1063
+ window.addEventListener('resize', () => this.handleResize());
1064
+ }
1065
+
1066
+ initPhysics() {
1067
+ const engine = Matter.Engine.create({
1068
+ gravity: { x: 0, y: 1 }
1069
+ });
1070
+ this.physicsWorld = engine.world;
1071
+
1072
+ // Create invisible boundaries
1073
+ const ground = Matter.Bodies.rectangle(window.innerWidth / 2, window.innerHeight + 50, window.innerWidth, 100, {
1074
+ isStatic: true,
1075
+ render: { fillStyle: 'transparent' },
1076
+ label: 'ground'
1077
+ });
1078
+
1079
+ const leftWall = Matter.Bodies.rectangle(-50, window.innerHeight / 2, 100, window.innerHeight, {
1080
+ isStatic: true,
1081
+ render: { fillStyle: 'transparent' },
1082
+ label: 'leftWall'
1083
+ });
1084
+
1085
+ const rightWall = Matter.Bodies.rectangle(window.innerWidth + 50, window.innerHeight / 2, 100, window.innerHeight, {
1086
+ isStatic: true,
1087
+ render: { fillStyle: 'transparent' },
1088
+ label: 'rightWall'
1089
+ });
1090
+
1091
+ const ceiling = Matter.Bodies.rectangle(window.innerWidth / 2, -50, window.innerWidth, 100, {
1092
+ isStatic: true,
1093
+ render: { fillStyle: 'transparent' },
1094
+ label: 'ceiling'
1095
+ });
1096
+
1097
+ Matter.World.add(this.physicsWorld, [ground, leftWall, rightWall, ceiling]);
1098
+
1099
+ // Start the engine
1100
+ Matter.Engine.run(engine);
1101
+ }
1102
+
1103
+ startGame(mode) {
1104
+ this.model.setMode(mode);
1105
+ this.view.showGameUI();
1106
+ this.view.updateScore(0);
1107
+ this.view.updateRings(this.model.ringsLeft, this.model.ringsLeft);
1108
+
1109
+ if (mode === 'challenge') {
1110
+ this.view.timeDisplay.classList.remove('hidden');
1111
+ this.view.updateTime(this.model.timeLeft);
1112
+ } else {
1113
+ this.view.timeDisplay.classList.add('hidden');
1114
+ }
1115
+
1116
+ // Clear previous game elements
1117
+ this.view.clearGameElements();
1118
+ this.physicsRings = [];
1119
+
1120
+ // Generate pegs
1121
+ this.generatePegs();
1122
+
1123
+ // Show countdown
1124
+ this.view.showCountdown(3, () => {
1125
+ this.model.gameActive = true;
1126
+
1127
+ // Start game timer if in challenge mode
1128
+ if (mode === 'challenge') {
1129
+ this.gameInterval = setInterval(() => {
1130
+ const timeLeft = this.model.decrementTime();
1131
+ this.view.updateTime(timeLeft);
1132
+
1133
+ if (timeLeft <= 0 && this.model.ringsLeft <= 0) {
1134
+ clearInterval(this.gameInterval);
1135
+ this.endGame();
1136
+ }
1137
+ }, 1000);
1138
+ }
1139
+
1140
+ // Create the first ring
1141
+ this.createNewRing();
1142
+ });
1143
+ }
1144
+
1145
+ generatePegs() {
1146
+ // Clear previous pegs
1147
+ this.model.pegs = [];
1148
+
1149
+ // Generate pegs based on game mode
1150
+ const pegCount = this.model.mode === 'classic' ? 8 : 12;
1151
+
1152
+ // Get container dimensions
1153
+ const containerRect = this.view.container.getBoundingClientRect();
1154
+ const containerWidth = containerRect.width;
1155
+ const containerHeight = containerRect.height;
1156
+ const containerLeft = containerRect.left;
1157
+ const containerTop = containerRect.top;
1158
+
1159
+ // Create pegs with physical bodies
1160
+ for (let i = 0; i < pegCount; i++) {
1161
+ const pegSize = 12 + Math.random() * 8; // Random size between 12 and 20
1162
+ const pegRadius = pegSize / 2;
1163
+
1164
+ // Position pegs within the container
1165
+ let pegX, pegY;
1166
+ let validPosition = false;
1167
+ let attempts = 0;
1168
+
1169
+ // Keep trying until we find a position that doesn't overlap
1170
+ while (!validPosition && attempts < 100) {
1171
+ attempts++;
1172
+ pegX = containerLeft + 30 + Math.random() * (containerWidth - 60);
1173
+ pegY = containerTop + 30 + Math.random() * (containerHeight - 60);
1174
+
1175
+ validPosition = true;
1176
+
1177
+ // Check for overlap with existing pegs
1178
+ for (const existingPeg of this.model.pegs) {
1179
+ const dx = pegX - existingPeg.x;
1180
+ const dy = pegY - existingPeg.y;
1181
+ const distance = Math.sqrt(dx * dx + dy * dy);
1182
+
1183
+ if (distance < pegRadius + existingPeg.radius + 15) { // 15px minimum spacing
1184
+ validPosition = false;
1185
+ break;
1186
+ }
1187
+ }
1188
+ }
1189
+
1190
+ if (validPosition) {
1191
+ // Choose a color for the peg
1192
+ const colors = ['#FF5722', '#4CAF50', '#2196F3', '#FFC107', '#9C27B0'];
1193
+ const points = [10, 20, 30, 40, 50];
1194
+ const colorIndex = Math.floor(Math.random() * colors.length);
1195
+ const pegColor = colors[colorIndex];
1196
+ const pegPoints = points[colorIndex];
1197
+
1198
+ // Create physics body for the peg
1199
+ const pegBody = Matter.Bodies.circle(
1200
+ pegX,
1201
+ pegY,
1202
+ pegRadius,
1203
+ {
1204
+ isStatic: true,
1205
+ render: {
1206
+ fillStyle: pegColor,
1207
+ strokeStyle: '#000',
1208
+ lineWidth: 1
1209
+ }
1210
+ }
1211
+ );
1212
+
1213
+ Matter.World.add(this.physicsWorld, pegBody);
1214
+
1215
+ // Store peg data
1216
+ this.model.pegs.push({
1217
+ body: pegBody,
1218
+ x: pegX,
1219
+ y: pegY,
1220
+ radius: pegRadius,
1221
+ size: pegSize,
1222
+ color: pegColor,
1223
+ points: pegPoints
1224
+ });
1225
+
1226
+ // Create visual peg
1227
+ this.view.createPeg(pegX, pegY, pegSize, pegColor, pegPoints);
1228
+ }
1229
+ }
1230
+ }
1231
+
1232
+ createNewRing() {
1233
+ if (!this.model.gameActive || this.model.isPaused || this.model.ringsLeft <= 0) return;
1234
+
1235
+ // Create a new active ring at the bottom right position
1236
+ const holderRect = this.view.ringCounter.getBoundingClientRect();
1237
+ const ringX = holderRect.left + holderRect.width / 2;
1238
+ const ringY = holderRect.top + holderRect.height / 2;
1239
+
1240
+ // Set active ring position (hold position)
1241
+ this.startDragPos = { x: ringX, y: ringY };
1242
+ this.currentDragPos = { x: ringX, y: ringY };
1243
+
1244
+ // Style the active ring
1245
+ const ringSize = 30 + Math.floor(Math.random() * 20); // 30-50px diameter
1246
+ const ringColor = this.view.getRandomRingColor();
1247
+
1248
+ // Create visual ring
1249
+ this.activeRingElement = this.view.createRing(ringX, ringY, ringSize, ringColor);
1250
+
1251
+ // Store active ring data
1252
+ this.activeRing = {
1253
+ x: ringX,
1254
+ y: ringY,
1255
+ size: ringSize,
1256
+ radius: ringSize / 2,
1257
+ color: ringColor,
1258
+ element: this.activeRingElement
1259
+ };
1260
+ }
1261
+
1262
+ handleStart(e) {
1263
+ if (!this.model.gameActive || this.model.isPaused || !this.activeRing || this.isDragging) return;
1264
+
1265
+ e.preventDefault();
1266
+ this.isDragging = true;
1267
+
1268
+ const clientX = e.clientX || e.touches[0].clientX;
1269
+ const clientY = e.clientY || e.touches[0].clientY;
1270
+
1271
+ this.startDragPos = { x: clientX, y: clientY };
1272
+ this.currentDragPos = { x: clientX, y: clientY };
1273
+
1274
+ // Style the active ring when dragging
1275
+ this.activeRingElement.style.transform = 'scale(1.1)';
1276
+ this.activeRingElement.style.opacity = '1';
1277
+ this.activeRingElement.style.border = '3px solid';
1278
+
1279
+ // Update ring position
1280
+ this.updateActiveRingPosition(clientX, clientY);
1281
+ }
1282
+
1283
+ handleMove(e) {
1284
+ if (!this.isDragging) return;
1285
+
1286
+ e.preventDefault();
1287
+
1288
+ const clientX = e.clientX || e.touches[0].clientX;
1289
+ const clientY = e.clientY || e.touches[0].clientY;
1290
+
1291
+ this.currentDragPos = { x: clientX, y: clientY };
1292
+
1293
+ // Update ring position
1294
+ this.updateActiveRingPosition(clientX, clientY);
1295
+ }
1296
+
1297
+ handleEnd(e) {
1298
+ if (!this.isDragging) return;
1299
+
1300
+ e.preventDefault();
1301
+
1302
+ const clientX = e.clientX || (e.changedTouches ? e.changedTouches[0].clientX : 0);
1303
+ const clientY = e.clientY || (e.changedTouches ? e.changedTouches[0].clientY : 0);
1304
+
1305
+ if (clientX && clientY) {
1306
+ this.currentDragPos = { x: clientX, y: clientY };
1307
+
1308
+ // Calculate throw velocity
1309
+ const velocityX = (this.startDragPos.x - this.currentDragPos.x) * 0.25;
1310
+ const velocityY = (this.startDragPos.y - this.currentDragPos.y) * 0.15 - 2; // Slight upward boost
1311
+
1312
+ // Calculate throw position (adjust for aiming)
1313
+ const throwX = this.currentDragPos.x;
1314
+ const throwY = this.currentDragPos.y - 10; // Slightly higher for better aiming
1315
+
1316
+ // Throw the ring
1317
+ this.throwRing(throwX, throwY, velocityX, velocityY);
1318
+ }
1319
+
1320
+ // Reset dragging state
1321
+ this.isDragging = false;
1322
+
1323
+ // Reset active ring style
1324
+ if (this.activeRingElement) {
1325
+ this.activeRingElement.style.transform = 'scale(1)';
1326
+ this.activeRingElement.style.opacity = '0.8';
1327
+ this.activeRingElement.style.border = '3px dashed';
1328
+ }
1329
+ }
1330
+
1331
+ updateActiveRingPosition(x, y) {
1332
+ if (this.activeRingElement) {
1333
+ this.activeRingElement.style.left = `${x - this.activeRing.size/2}px`;
1334
+ this.activeRingElement.style.top = `${y - this.activeRing.size/2}px`;
1335
+ }
1336
+ }
1337
+
1338
+ throwRing(x, y, velocityX, velocityY) {
1339
+ if (!this.activeRing) return;
1340
+
1341
+ // Remove the visual active ring
1342
+ this.activeRingElement.remove();
1343
+
1344
+ // Create physics ring
1345
+ const ring = Matter.Bodies.circle(
1346
+ x,
1347
+ y,
1348
+ this.activeRing.radius,
1349
+ {
1350
+ restitution: 0.5,
1351
+ friction: 0.05,
1352
+ density: 0.1,
1353
+ render: {
1354
+ fillStyle: this.activeRing.color,
1355
+ strokeStyle: this.activeRing.color,
1356
+ lineWidth: 3
1357
+ }
1358
+ }
1359
+ );
1360
+
1361
+ // Apply initial velocity
1362
+ Matter.Body.setVelocity(ring, { x: velocityX, y: velocityY });
1363
+ Matter.Body.setAngularVelocity(ring, Math.random() * 0.1 - 0.05);
1364
+
1365
+ // Add ring to world
1366
+ Matter.World.add(this.physicsWorld, ring);
1367
+
1368
+ // Store ring reference with additional data
1369
+ this.physicsRings.push({
1370
+ body: ring,
1371
+ color: this.activeRing.color,
1372
+ points: 0, // Will be set when landing on a peg
1373
+ landed: false
1374
+ });
1375
+
1376
+ // Create splash effect
1377
+ this.createSplash(x, y + this.activeRing.radius);
1378
+
1379
+ // Decrement rings left and update UI
1380
+ const ringsLeft = this.model.decrementRings();
1381
+ this.view.updateRings(ringsLeft, this.model.mode === 'classic' ? 10 : 15);
1382
+
1383
+ // Clear the active ring
1384
+ this.activeRing = null;
1385
+ this.activeRingElement = null;
1386
+
1387
+ // Setup collision detection for this ring
1388
+ this.setupRingCollisions(ring);
1389
+
1390
+ // When the ring comes to rest
1391
+ Events.on(this.physicsWorld.engine, 'afterUpdate', () => {
1392
+ if (ring && ring.isSleeping && !this.model.isPaused) {
1393
+ // Show the active ring again (if there are rings left)
1394
+ if (this.model.ringsLeft > 0) {
1395
+ setTimeout(() => {
1396
+ this.createNewRing();
1397
+ }, 500);
1398
+ } else if (this.model.mode === 'challenge' && this.model.timeLeft <= 0) {
1399
+ this.endGame();
1400
+ }
1401
+ }
1402
+ });
1403
+ }
1404
+
1405
+ setupRingCollisions(ring) {
1406
+ // Current ring index
1407
+ const ringIndex = this.physicsRings.length - 1;
1408
+
1409
+ Events.on(this.physicsWorld.engine, 'collisionStart', (event) => {
1410
+ const pairs = event.pairs;
1411
+
1412
+ for (let i = 0; i < pairs.length; i++) {
1413
+ const pair = pairs[i];
1414
+ const ringBody = this.physicsRings[ringIndex]?.body;
1415
+
1416
+ // Check if our ring collided with a peg
1417
+ if ((pair.bodyA === ringBody && this.model.pegs.some(peg => peg.body === pair.bodyB)) ||
1418
+ (pair.bodyB === ringBody && this.model.pegs.some(peg => peg.body === pair.bodyA))) {
1419
+
1420
+ const peg = this.model.pegs.find(peg =>
1421
+ peg.body === pair.bodyA || peg.body === pair.bodyB
1422
+ );
1423
+
1424
+ // Check if the ring is moving slowly enough to count as a successful toss
1425
+ const speed = Math.sqrt(ringBody.velocity.x * ringBody.velocity.x +
1426
+ ringBody.velocity.y * ringBody.velocity.y);
1427
+ const angularSpeed = Math.abs(ringBody.angularVelocity);
1428
+
1429
+ if (speed < 0.5 && angularSpeed < 0.1 && !this.physicsRings[ringIndex].landed) {
1430
+ // Successful toss
1431
+ this.handleSuccessfulToss(ringIndex, peg);
1432
+ }
1433
+ }
1434
+
1435
+ // Check if the ring hit the water surface
1436
+ if ((pair.bodyA === ringBody && pair.bodyB.position.y > window.innerHeight * 0.55) ||
1437
+ (pair.bodyB === ringBody && pair.bodyA.position.y > window.innerHeight * 0.55)) {
1438
+ // Create bubble effect
1439
+ this.createBubble(
1440
+ ringBody.position.x,
1441
+ ringBody.position.y + this.physicsRings[ringIndex].body.circleRadius
1442
+ );
1443
+ }
1444
+ }
1445
+ });
1446
+ }
1447
+
1448
+ handleSuccessfulToss(ringIndex, peg) {
1449
+ // Mark ring as landed
1450
+ this.physicsRings[ringIndex].landed = true;
1451
+ this.physicsRings[ringIndex].points = peg.points;
1452
+
1453
+ // Add to score
1454
+ const newScore = this.model.updateScore(peg.points);
1455
+ this.view.updateScore(newScore);
1456
+
1457
+ // Show success message
1458
+ if (this.model.mode === 'challenge') {
1459
+ this.model.addTime(3); // 3 second bonus
1460
+ this.view.updateTime(this.model.timeLeft);
1461
+ this.view.showResult(`You scored ${peg.points} points! +3 sec bonus`);
1462
+ } else {
1463
+ this.view.showResult(`You scored ${peg.points} points!`);
1464
+ }
1465
+
1466
+ // Create ripple effect
1467
+ this.createRipple(
1468
+ this.physicsRings[ringIndex].body.position.x,
1469
+ this.physicsRings[ringIndex].body.position.y,
1470
+ peg.color
1471
+ );
1472
+
1473
+ // Change ring color to match peg
1474
+ this.physicsRings[ringIndex].body.render.fillStyle = peg.color;
1475
+ this.physicsRings[ringIndex].body.render.strokeStyle = peg.color;
1476
+
1477
+ // Make the ring static so it stays on the peg
1478
+ Matter.Body.setStatic(this.physicsRings[ringIndex].body, true);
1479
+ }
1480
+
1481
+ createBubble() {
1482
+ // Create bubble in the center of the container
1483
+ const containerRect = this.view.container.getBoundingClientRect();
1484
+ const bubbleX = containerRect.left + containerRect.width / 2 + (Math.random() * 60 - 30);
1485
+ const bubbleY = containerRect.top + containerRect.height;
1486
+
1487
+ // Create visual bubble
1488
+ this.view.createBubble(bubbleX, bubbleY);
1489
+
1490
+ // Apply upward force to rings in the water
1491
+ const waterY = containerRect.top + containerRect.height * 0.6;
1492
+
1493
+ for (const ring of this.physicsRings) {
1494
+ if (ring.body.position.y > waterY && !ring.landed) {
1495
+ const forceMagnitude = 0.002 * ring.body.mass;
1496
+ const forceDirection = {
1497
+ x: (Math.random() - 0.5) * 0.1,
1498
+ y: -1
1499
+ };
1500
+
1501
+ Matter.Body.applyForce(ring.body, ring.body.position, {
1502
+ x: forceDirection.x * forceMagnitude,
1503
+ y: forceDirection.y * forceMagnitude
1504
+ });
1505
+ }
1506
+ }
1507
+ }
1508
+
1509
+ createSplash(x, y) {
1510
+ // Create multiple bubbles for splash effect
1511
+ for (let i = 0; i < 5; i++) {
1512
+ const offsetX = (Math.random() - 0.5) * 30;
1513
+ const offsetY = (Math.random() - 0.5) * 10;
1514
+ this.view.createBubble(x + offsetX, y + offsetY);
1515
+ }
1516
+ }
1517
+
1518
+ createRipple(x, y, color) {
1519
+ const ripple = document.createElement('div');
1520
+ ripple.className = 'water-ripple';
1521
+ ripple.style.left = `${x - 50}px`;
1522
+ ripple.style.top = `${y - 10}px`;
1523
+ ripple.style.background = `radial-gradient(ellipse at center, ${color}40 0%, rgba(255,255,255,0) 70%)`;
1524
+
1525
+ this.view.gameView.appendChild(ripple);
1526
+
1527
+ gsap.to(ripple, {
1528
+ width: 200,
1529
+ height: 40,
1530
+ opacity: 0,
1531
+ duration: 1,
1532
+ onComplete: () => ripple.remove()
1533
+ });
1534
+ }
1535
+
1536
+ togglePause() {
1537
+ const isPaused = this.model.togglePause();
1538
+
1539
+ if (isPaused) {
1540
+ this.view.showPauseMenu();
1541
+ Matter.Engine.clear(this.physicsWorld.engine);
1542
+ } else {
1543
+ this.view.hidePauseMenu();
1544
+ Matter.Engine.run(this.physicsWorld.engine);
1545
+ }
1546
+ }
1547
+
1548
+ endGame() {
1549
+ this.model.gameActive = false;
1550
+
1551
+ if (this.gameInterval) {
1552
+ clearInterval(this.gameInterval);
1553
+ }
1554
+
1555
+ this.view.showGameOver(this.model.score);
1556
+ }
1557
+
1558
+ handleResize() {
1559
+ // Handle window resize if needed
1560
+ // This would need to reposition elements and update physics bounds
1561
+ }
1562
+ }
1563
+
1564
+ // Initialize the game
1565
+ document.addEventListener('DOMContentLoaded', () => {
1566
+ const model = new GameModel();
1567
+ const view = new GameView();
1568
+ const controller = new GameController(model, view);
1569
+
1570
+ // Show start screen initially
1571
+ view.showScreen('startScreen');
1572
+ });
1573
+ </script>
1574
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Melfi/Retrorings" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1575
+ </html>