offerpk3 commited on
Commit
670c460
ยท
verified ยท
1 Parent(s): 55c2b59

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +914 -19
index.html CHANGED
@@ -1,19 +1,914 @@
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>Sea Creatures Memory Match</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Arial', sans-serif;
16
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ flex-direction: column;
20
+ align-items: center;
21
+ justify-content: center;
22
+ color: white;
23
+ padding: 20px;
24
+ }
25
+
26
+ .game-header {
27
+ text-align: center;
28
+ margin-bottom: 30px;
29
+ }
30
+
31
+ .game-title {
32
+ font-size: 2.5rem;
33
+ margin-bottom: 10px;
34
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
35
+ background: linear-gradient(45deg, #00d4ff, #ffffff);
36
+ -webkit-background-clip: text;
37
+ -webkit-text-fill-color: transparent;
38
+ background-clip: text;
39
+ }
40
+
41
+ .game-stats {
42
+ display: flex;
43
+ gap: 30px;
44
+ justify-content: center;
45
+ margin-bottom: 20px;
46
+ font-size: 1.2rem;
47
+ }
48
+
49
+ .stat-item {
50
+ background: rgba(255,255,255,0.1);
51
+ padding: 10px 20px;
52
+ border-radius: 25px;
53
+ backdrop-filter: blur(10px);
54
+ border: 1px solid rgba(255,255,255,0.2);
55
+ }
56
+
57
+ .game-board {
58
+ display: grid;
59
+ grid-template-columns: repeat(4, 1fr);
60
+ gap: 15px;
61
+ max-width: 600px;
62
+ margin: 0 auto;
63
+ }
64
+
65
+ .card {
66
+ width: 120px;
67
+ height: 120px;
68
+ background: linear-gradient(145deg, #3498db, #2980b9);
69
+ border-radius: 15px;
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ cursor: pointer;
74
+ transition: all 0.3s ease;
75
+ transform-style: preserve-3d;
76
+ position: relative;
77
+ box-shadow: 0 8px 20px rgba(0,0,0,0.2);
78
+ border: 2px solid rgba(255,255,255,0.1);
79
+ }
80
+
81
+ .card:hover {
82
+ transform: translateY(-5px) scale(1.02);
83
+ box-shadow: 0 12px 25px rgba(0,0,0,0.3);
84
+ }
85
+
86
+ .card.flipped {
87
+ background: linear-gradient(145deg, #ffffff, #f0f0f0);
88
+ color: #2c3e50;
89
+ transform: rotateY(180deg);
90
+ }
91
+
92
+ .card.matched {
93
+ background: linear-gradient(145deg, #2ecc71, #27ae60);
94
+ color: white;
95
+ animation: matchPulse 0.6s ease;
96
+ }
97
+
98
+ .card-content {
99
+ font-size: 3rem;
100
+ transition: opacity 0.3s ease;
101
+ }
102
+
103
+ .card:not(.flipped) .card-content {
104
+ opacity: 0;
105
+ }
106
+
107
+ .card.flipped .card-content,
108
+ .card.matched .card-content {
109
+ opacity: 1;
110
+ }
111
+
112
+ .card-back {
113
+ position: absolute;
114
+ inset: 0;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ font-size: 2rem;
119
+ color: rgba(255,255,255,0.8);
120
+ transition: opacity 0.3s ease;
121
+ }
122
+
123
+ .card.flipped .card-back,
124
+ .card.matched .card-back {
125
+ opacity: 0;
126
+ }
127
+
128
+ @keyframes matchPulse {
129
+ 0%, 100% { transform: scale(1); }
130
+ 50% { transform: scale(1.1); }
131
+ }
132
+
133
+ .controls {
134
+ margin-top: 30px;
135
+ text-align: center;
136
+ }
137
+
138
+ .btn {
139
+ background: linear-gradient(145deg, #e74c3c, #c0392b);
140
+ color: white;
141
+ border: none;
142
+ padding: 12px 30px;
143
+ border-radius: 25px;
144
+ font-size: 1.1rem;
145
+ cursor: pointer;
146
+ transition: all 0.3s ease;
147
+ box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);
148
+ }
149
+
150
+ .btn:hover {
151
+ transform: translateY(-2px);
152
+ box-shadow: 0 6px 20px rgba(231, 76, 60, 0.4);
153
+ }
154
+
155
+ .victory-message, .level-complete-message {
156
+ position: fixed;
157
+ top: 50%;
158
+ left: 50%;
159
+ transform: translate(-50%, -50%);
160
+ background: linear-gradient(145deg, #2ecc71, #27ae60);
161
+ color: white;
162
+ padding: 30px;
163
+ border-radius: 20px;
164
+ text-align: center;
165
+ font-size: 1.5rem;
166
+ box-shadow: 0 20px 40px rgba(0,0,0,0.3);
167
+ opacity: 0;
168
+ visibility: hidden;
169
+ transition: all 0.5s ease;
170
+ z-index: 1000;
171
+ }
172
+
173
+ .level-complete-message {
174
+ background: linear-gradient(145deg, #3498db, #2980b9);
175
+ }
176
+
177
+ .victory-message.show, .level-complete-message.show {
178
+ opacity: 1;
179
+ visibility: visible;
180
+ animation: victoryBounce 0.6s ease;
181
+ }
182
+
183
+ .next-level-btn {
184
+ background: linear-gradient(145deg, #f39c12, #e67e22);
185
+ color: white;
186
+ border: none;
187
+ padding: 12px 30px;
188
+ border-radius: 25px;
189
+ font-size: 1.1rem;
190
+ cursor: pointer;
191
+ transition: all 0.3s ease;
192
+ margin-top: 15px;
193
+ }
194
+
195
+ .next-level-btn:hover {
196
+ transform: translateY(-2px);
197
+ box-shadow: 0 6px 20px rgba(243, 156, 18, 0.4);
198
+ }
199
+
200
+ .how-to-play {
201
+ background: rgba(255,255,255,0.1);
202
+ backdrop-filter: blur(15px);
203
+ border: 1px solid rgba(255,255,255,0.2);
204
+ border-radius: 20px;
205
+ padding: 25px;
206
+ margin: 20px auto;
207
+ max-width: 600px;
208
+ text-align: left;
209
+ transition: all 0.5s ease;
210
+ }
211
+
212
+ .how-to-play.hidden {
213
+ opacity: 0;
214
+ visibility: hidden;
215
+ transform: translateY(-20px);
216
+ }
217
+
218
+ .how-to-play h3 {
219
+ text-align: center;
220
+ margin-bottom: 20px;
221
+ font-size: 1.5rem;
222
+ color: #00d4ff;
223
+ }
224
+
225
+ .how-to-play ul {
226
+ list-style: none;
227
+ padding: 0;
228
+ }
229
+
230
+ .how-to-play li {
231
+ margin: 12px 0;
232
+ padding: 8px 0;
233
+ font-size: 1.1rem;
234
+ line-height: 1.4;
235
+ }
236
+
237
+ .close-guide {
238
+ background: linear-gradient(145deg, #00d4ff, #0099cc);
239
+ color: white;
240
+ border: none;
241
+ padding: 10px 25px;
242
+ border-radius: 20px;
243
+ font-size: 1rem;
244
+ cursor: pointer;
245
+ display: block;
246
+ margin: 20px auto 0;
247
+ transition: all 0.3s ease;
248
+ }
249
+
250
+ .close-guide:hover {
251
+ transform: translateY(-2px);
252
+ box-shadow: 0 6px 20px rgba(0, 153, 204, 0.4);
253
+ }
254
+
255
+ .sound-controls {
256
+ margin: 20px 0;
257
+ }
258
+
259
+ .sound-panel {
260
+ background: rgba(255,255,255,0.1);
261
+ backdrop-filter: blur(10px);
262
+ border: 1px solid rgba(255,255,255,0.2);
263
+ border-radius: 15px;
264
+ padding: 20px;
265
+ display: flex;
266
+ gap: 20px;
267
+ align-items: center;
268
+ justify-content: center;
269
+ flex-wrap: wrap;
270
+ }
271
+
272
+ .sound-item {
273
+ display: flex;
274
+ flex-direction: column;
275
+ align-items: center;
276
+ gap: 8px;
277
+ min-width: 120px;
278
+ }
279
+
280
+ .sound-item label {
281
+ font-size: 0.9rem;
282
+ font-weight: bold;
283
+ text-align: center;
284
+ }
285
+
286
+ .sound-toggle {
287
+ background: linear-gradient(145deg, #2ecc71, #27ae60);
288
+ color: white;
289
+ border: none;
290
+ padding: 6px 16px;
291
+ border-radius: 15px;
292
+ font-size: 0.8rem;
293
+ font-weight: bold;
294
+ cursor: pointer;
295
+ transition: all 0.3s ease;
296
+ min-width: 50px;
297
+ }
298
+
299
+ .sound-toggle.off {
300
+ background: linear-gradient(145deg, #e74c3c, #c0392b);
301
+ }
302
+
303
+ .sound-toggle:hover {
304
+ transform: translateY(-1px);
305
+ }
306
+
307
+ .sound-item input[type="range"] {
308
+ width: 80px;
309
+ height: 6px;
310
+ border-radius: 3px;
311
+ background: rgba(255,255,255,0.3);
312
+ outline: none;
313
+ cursor: pointer;
314
+ }
315
+
316
+ .sound-item input[type="range"]::-webkit-slider-thumb {
317
+ appearance: none;
318
+ width: 16px;
319
+ height: 16px;
320
+ border-radius: 50%;
321
+ background: #00d4ff;
322
+ cursor: pointer;
323
+ box-shadow: 0 2px 6px rgba(0,0,0,0.3);
324
+ }
325
+
326
+ .sound-item select {
327
+ background: rgba(255,255,255,0.1);
328
+ color: white;
329
+ border: 1px solid rgba(255,255,255,0.3);
330
+ border-radius: 8px;
331
+ padding: 6px 12px;
332
+ font-size: 0.9rem;
333
+ cursor: pointer;
334
+ }
335
+
336
+ .sound-item select option {
337
+ background: #2980b9;
338
+ color: white;
339
+ }
340
+
341
+ .guide-btn {
342
+ background: linear-gradient(145deg, #9b59b6, #8e44ad);
343
+ margin-left: 15px;
344
+ }
345
+
346
+ .guide-btn:hover {
347
+ box-shadow: 0 6px 20px rgba(155, 89, 182, 0.4);
348
+ }
349
+
350
+ @keyframes victoryBounce {
351
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); }
352
+ 50% { transform: translate(-50%, -50%) scale(1.05); }
353
+ }
354
+
355
+ @media (max-width: 768px) {
356
+ .game-board {
357
+ grid-template-columns: repeat(3, 1fr);
358
+ gap: 10px;
359
+ }
360
+
361
+ .card {
362
+ width: 90px;
363
+ height: 90px;
364
+ }
365
+
366
+ .card-content {
367
+ font-size: 2rem;
368
+ }
369
+
370
+ .game-title {
371
+ font-size: 2rem;
372
+ }
373
+
374
+ .game-stats {
375
+ flex-direction: column;
376
+ gap: 10px;
377
+ }
378
+
379
+ .sound-panel {
380
+ flex-direction: column;
381
+ gap: 15px;
382
+ }
383
+
384
+ .how-to-play {
385
+ margin: 10px;
386
+ padding: 20px;
387
+ }
388
+
389
+ .how-to-play li {
390
+ font-size: 1rem;
391
+ }
392
+
393
+ .controls {
394
+ flex-direction: column;
395
+ gap: 10px;
396
+ }
397
+
398
+ .guide-btn {
399
+ margin-left: 0;
400
+ }
401
+ }
402
+ </style>
403
+ </head>
404
+ <body>
405
+ <div class="game-header">
406
+ <h1 class="game-title">๐ŸŒŠ Sea Creatures Memory Match ๐ŸŒŠ</h1>
407
+
408
+ <!-- How to Play Guide -->
409
+ <div class="how-to-play" id="howToPlay">
410
+ <h3>๐ŸŽฏ How to Play</h3>
411
+ <ul>
412
+ <li>๐Ÿ–ฑ๏ธ Click any card to flip it and reveal a sea creature</li>
413
+ <li>๐Ÿ” Click a second card to find its matching pair</li>
414
+ <li>โœ… If they match, they stay revealed with a green glow!</li>
415
+ <li>โŒ If they don't match, they flip back - remember their positions!</li>
416
+ <li>๐Ÿ† Find all 8 pairs to win the game</li>
417
+ <li>โšก Try to complete it in the fewest moves and fastest time!</li>
418
+ </ul>
419
+ <button class="close-guide" onclick="toggleGuide()">Got it! ๐Ÿš€</button>
420
+ </div>
421
+
422
+ <div class="game-stats">
423
+ <div class="stat-item">
424
+ <span>Level: </span><span id="level">1</span>
425
+ </div>
426
+ <div class="stat-item">
427
+ <span>Moves: </span><span id="moves">0</span>
428
+ </div>
429
+ <div class="stat-item">
430
+ <span>Matches: </span><span id="matches">0</span>
431
+ </div>
432
+ <div class="stat-item">
433
+ <span>Time: </span><span id="timer">00:00</span>
434
+ </div>
435
+ </div>
436
+
437
+ <!-- Sound Control Panel -->
438
+ <div class="sound-controls">
439
+ <div class="sound-panel">
440
+ <div class="sound-item">
441
+ <label>๐ŸŽต Background Music</label>
442
+ <button class="sound-toggle" id="musicToggle" onclick="toggleMusic()">ON</button>
443
+ <input type="range" id="musicVolume" min="0" max="100" value="30" onchange="setMusicVolume(this.value)">
444
+ </div>
445
+ <div class="sound-item">
446
+ <label>๐Ÿ”Š Sound Effects</label>
447
+ <button class="sound-toggle" id="sfxToggle" onclick="toggleSFX()">ON</button>
448
+ <input type="range" id="sfxVolume" min="0" max="100" value="50" onchange="setSFXVolume(this.value)">
449
+ </div>
450
+ <div class="sound-item">
451
+ <label>โšก Game Speed</label>
452
+ <select id="gameSpeed" onchange="setGameSpeed(this.value)">
453
+ <option value="fast">Fast</option>
454
+ <option value="normal" selected>Normal</option>
455
+ <option value="slow">Slow</option>
456
+ </select>
457
+ </div>
458
+ </div>
459
+ </div>
460
+ </div>
461
+
462
+ <div class="game-board" id="gameBoard"></div>
463
+
464
+ <div class="controls">
465
+ <button class="btn" onclick="resetGame()">๐Ÿ”„ New Game</button>
466
+ <button class="btn guide-btn" onclick="toggleGuide()">โ“ How to Play</button>
467
+ </div>
468
+
469
+ <div class="victory-message" id="victoryMessage">
470
+ <h2>๐ŸŽ‰ Congratulations! ๐ŸŽ‰</h2>
471
+ <p>You found all the sea creature pairs!</p>
472
+ <p id="finalStats"></p>
473
+ </div>
474
+
475
+ <div class="level-complete-message" id="levelCompleteMessage">
476
+ <h2>๐ŸŒŸ Level Complete! ๐ŸŒŸ</h2>
477
+ <p>Amazing! You've completed this level!</p>
478
+ <p id="levelStats"></p>
479
+ <button class="btn next-level-btn" onclick="nextLevel()">๐Ÿš€ Next Level</button>
480
+ </div>
481
+
482
+ <script>
483
+ const seaCreatures = [
484
+ '๐Ÿ ', '๐ŸŸ', '๐Ÿก', '๐Ÿฆˆ',
485
+ '๐Ÿ™', '๐Ÿฆ‘', '๐Ÿš', '๐Ÿฆ€',
486
+ '๐Ÿ‹', '๐Ÿณ', '๐Ÿฆž', '๐Ÿข',
487
+ '๐Ÿฌ', '๐Ÿฆญ', '๐Ÿง', 'โญ'
488
+ ];
489
+
490
+ let gameBoard = [];
491
+ let flippedCards = [];
492
+ let matchedPairs = 0;
493
+ let moves = 0;
494
+ let startTime = null;
495
+ let timerInterval = null;
496
+ let gameActive = false;
497
+ let gameSpeed = 'normal';
498
+ let musicEnabled = true;
499
+ let sfxEnabled = true;
500
+ let musicVolume = 0.3;
501
+ let sfxVolume = 0.5;
502
+ let currentLevel = 1;
503
+ let totalMatches = 0;
504
+
505
+ // Audio context and sounds
506
+ let audioContext;
507
+ let backgroundMusic;
508
+ let matchSound;
509
+ let flipSound;
510
+
511
+ // Initialize audio
512
+ function initAudio() {
513
+ try {
514
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
515
+ createBackgroundMusic();
516
+ createSoundEffects();
517
+ } catch (e) {
518
+ console.log('Audio not supported');
519
+ }
520
+ }
521
+
522
+ function createBackgroundMusic() {
523
+ if (!audioContext) return;
524
+
525
+ // Stop existing music
526
+ if (backgroundMusic && backgroundMusic.oscillators) {
527
+ backgroundMusic.oscillators.forEach(osc => {
528
+ try { osc.stop(); } catch (e) {}
529
+ });
530
+ }
531
+
532
+ // Create a rich, layered ambient soundtrack
533
+ const masterGain = audioContext.createGain();
534
+ masterGain.gain.setValueAtTime(musicVolume * 0.15, audioContext.currentTime);
535
+ masterGain.connect(audioContext.destination);
536
+
537
+ const oscillators = [];
538
+
539
+ // Base ocean drone (deep foundation)
540
+ const bass = audioContext.createOscillator();
541
+ const bassGain = audioContext.createGain();
542
+ bass.type = 'sine';
543
+ bass.frequency.setValueAtTime(55, audioContext.currentTime); // A1
544
+ bassGain.gain.setValueAtTime(0.3, audioContext.currentTime);
545
+ bass.connect(bassGain);
546
+ bassGain.connect(masterGain);
547
+ oscillators.push(bass);
548
+
549
+ // Harmonic layers (creating depth)
550
+ const harmonics = [110, 165, 220, 330]; // A2, E3, A3, E4
551
+ harmonics.forEach((freq, i) => {
552
+ const osc = audioContext.createOscillator();
553
+ const gain = audioContext.createGain();
554
+ osc.type = 'triangle';
555
+ osc.frequency.setValueAtTime(freq, audioContext.currentTime);
556
+ gain.gain.setValueAtTime(0.1 / (i + 1), audioContext.currentTime);
557
+ osc.connect(gain);
558
+ gain.connect(masterGain);
559
+ oscillators.push(osc);
560
+ });
561
+
562
+ // Gentle wave motion (LFO for movement)
563
+ const waveLFO = audioContext.createOscillator();
564
+ const waveGain = audioContext.createGain();
565
+ waveLFO.type = 'sine';
566
+ waveLFO.frequency.setValueAtTime(0.3, audioContext.currentTime);
567
+ waveGain.gain.setValueAtTime(8, audioContext.currentTime);
568
+ waveLFO.connect(waveGain);
569
+ waveGain.connect(bass.frequency);
570
+ oscillators.push(waveLFO);
571
+
572
+ // Subtle bubbles effect (higher frequency sparkles)
573
+ const bubbles = audioContext.createOscillator();
574
+ const bubblesGain = audioContext.createGain();
575
+ const bubblesLFO = audioContext.createOscillator();
576
+ const bubblesLFOGain = audioContext.createGain();
577
+
578
+ bubbles.type = 'sine';
579
+ bubbles.frequency.setValueAtTime(1760, audioContext.currentTime); // A6
580
+ bubblesGain.gain.setValueAtTime(0.02, audioContext.currentTime);
581
+
582
+ bubblesLFO.type = 'sine';
583
+ bubblesLFO.frequency.setValueAtTime(0.7, audioContext.currentTime);
584
+ bubblesLFOGain.gain.setValueAtTime(200, audioContext.currentTime);
585
+
586
+ bubblesLFO.connect(bubblesLFOGain);
587
+ bubblesLFOGain.connect(bubbles.frequency);
588
+ bubbles.connect(bubblesGain);
589
+ bubblesGain.connect(masterGain);
590
+
591
+ oscillators.push(bubbles, bubblesLFO);
592
+
593
+ // Distant whale-like calls (very subtle)
594
+ const whale = audioContext.createOscillator();
595
+ const whaleGain = audioContext.createGain();
596
+ const whaleLFO = audioContext.createOscillator();
597
+ const whaleLFOGain = audioContext.createGain();
598
+
599
+ whale.type = 'sawtooth';
600
+ whale.frequency.setValueAtTime(80, audioContext.currentTime);
601
+ whaleGain.gain.setValueAtTime(0.05, audioContext.currentTime);
602
+
603
+ whaleLFO.type = 'sine';
604
+ whaleLFO.frequency.setValueAtTime(0.1, audioContext.currentTime);
605
+ whaleLFOGain.gain.setValueAtTime(20, audioContext.currentTime);
606
+
607
+ whaleLFO.connect(whaleLFOGain);
608
+ whaleLFOGain.connect(whale.frequency);
609
+ whale.connect(whaleGain);
610
+ whaleGain.connect(masterGain);
611
+
612
+ oscillators.push(whale, whaleLFO);
613
+
614
+ backgroundMusic = { oscillators, masterGain };
615
+
616
+ if (musicEnabled) {
617
+ try {
618
+ oscillators.forEach(osc => osc.start());
619
+ } catch (e) {
620
+ console.log('Could not start background music');
621
+ }
622
+ }
623
+ }
624
+
625
+ function createSoundEffects() {
626
+ // Match sound - create a pleasant success sound
627
+ matchSound = () => {
628
+ if (!audioContext || !sfxEnabled) return;
629
+
630
+ const oscillator = audioContext.createOscillator();
631
+ const gainNode = audioContext.createGain();
632
+
633
+ oscillator.connect(gainNode);
634
+ gainNode.connect(audioContext.destination);
635
+
636
+ oscillator.type = 'triangle';
637
+ oscillator.frequency.setValueAtTime(523.25, audioContext.currentTime); // C5
638
+ oscillator.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.1); // E5
639
+ oscillator.frequency.setValueAtTime(783.99, audioContext.currentTime + 0.2); // G5
640
+
641
+ gainNode.gain.setValueAtTime(sfxVolume * 0.3, audioContext.currentTime);
642
+ gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
643
+
644
+ oscillator.start(audioContext.currentTime);
645
+ oscillator.stop(audioContext.currentTime + 0.5);
646
+ };
647
+
648
+ // Flip sound - subtle click sound
649
+ flipSound = () => {
650
+ if (!audioContext || !sfxEnabled) return;
651
+
652
+ const oscillator = audioContext.createOscillator();
653
+ const gainNode = audioContext.createGain();
654
+
655
+ oscillator.connect(gainNode);
656
+ gainNode.connect(audioContext.destination);
657
+
658
+ oscillator.type = 'square';
659
+ oscillator.frequency.setValueAtTime(800, audioContext.currentTime);
660
+
661
+ gainNode.gain.setValueAtTime(sfxVolume * 0.1, audioContext.currentTime);
662
+ gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
663
+
664
+ oscillator.start(audioContext.currentTime);
665
+ oscillator.stop(audioContext.currentTime + 0.1);
666
+ };
667
+ }
668
+
669
+ function getSpeedDelay() {
670
+ const speeds = {
671
+ fast: { match: 300, flip: 600 },
672
+ normal: { match: 500, flip: 1000 },
673
+ slow: { match: 800, flip: 1500 }
674
+ };
675
+ return speeds[gameSpeed];
676
+ }
677
+
678
+ function initGame() {
679
+ // Reset game state
680
+ gameBoard = [];
681
+ flippedCards = [];
682
+ matchedPairs = 0;
683
+ moves = 0;
684
+ startTime = null;
685
+ gameActive = true;
686
+
687
+ if (timerInterval) {
688
+ clearInterval(timerInterval);
689
+ }
690
+
691
+ // Create pairs and shuffle
692
+ const creatures = seaCreatures.slice(0, 8); // Use 8 different creatures
693
+ const pairs = [...creatures, ...creatures]; // Create pairs
694
+ gameBoard = shuffle(pairs);
695
+
696
+ // Update UI
697
+ updateStats();
698
+ createBoard();
699
+ document.getElementById('victoryMessage').classList.remove('show');
700
+ document.getElementById('levelCompleteMessage').classList.remove('show');
701
+ }
702
+
703
+ function shuffle(array) {
704
+ const shuffled = [...array];
705
+ for (let i = shuffled.length - 1; i > 0; i--) {
706
+ const j = Math.floor(Math.random() * (i + 1));
707
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
708
+ }
709
+ return shuffled;
710
+ }
711
+
712
+ function createBoard() {
713
+ const board = document.getElementById('gameBoard');
714
+ board.innerHTML = '';
715
+
716
+ gameBoard.forEach((creature, index) => {
717
+ const card = document.createElement('div');
718
+ card.className = 'card';
719
+ card.dataset.index = index;
720
+ card.innerHTML = `
721
+ <div class="card-back">๐ŸŒŠ</div>
722
+ <div class="card-content">${creature}</div>
723
+ `;
724
+ card.addEventListener('click', () => flipCard(index));
725
+ board.appendChild(card);
726
+ });
727
+ }
728
+
729
+ function flipCard(index) {
730
+ if (!gameActive) return;
731
+
732
+ const card = document.querySelector(`[data-index="${index}"]`);
733
+
734
+ // Don't flip if already flipped or matched
735
+ if (card.classList.contains('flipped') || card.classList.contains('matched')) {
736
+ return;
737
+ }
738
+
739
+ // Start timer on first move
740
+ if (!startTime) {
741
+ startTimer();
742
+ // Initialize audio on first interaction (browser requirement)
743
+ if (!audioContext) {
744
+ initAudio();
745
+ }
746
+ }
747
+
748
+ // Play flip sound
749
+ if (flipSound) flipSound();
750
+
751
+ // Flip the card
752
+ card.classList.add('flipped');
753
+ flippedCards.push(index);
754
+
755
+ // Check for match when 2 cards are flipped
756
+ if (flippedCards.length === 2) {
757
+ moves++;
758
+ updateStats();
759
+ checkForMatch();
760
+ }
761
+ }
762
+
763
+ function checkForMatch() {
764
+ const [first, second] = flippedCards;
765
+ const firstCard = document.querySelector(`[data-index="${first}"]`);
766
+ const secondCard = document.querySelector(`[data-index="${second}"]`);
767
+ const delays = getSpeedDelay();
768
+
769
+ if (gameBoard[first] === gameBoard[second]) {
770
+ // Match found!
771
+ setTimeout(() => {
772
+ // Play match sound
773
+ if (matchSound) matchSound();
774
+
775
+ firstCard.classList.add('matched');
776
+ secondCard.classList.add('matched');
777
+ firstCard.classList.remove('flipped');
778
+ secondCard.classList.remove('flipped');
779
+ matchedPairs++;
780
+ totalMatches++;
781
+ updateStats();
782
+ flippedCards = [];
783
+
784
+ // Check for level completion or game win
785
+ if (matchedPairs === 8) {
786
+ if (totalMatches >= 14) {
787
+ gameWon();
788
+ } else {
789
+ levelComplete();
790
+ }
791
+ }
792
+ }, delays.match);
793
+ } else {
794
+ // No match - flip back after delay
795
+ setTimeout(() => {
796
+ firstCard.classList.remove('flipped');
797
+ secondCard.classList.remove('flipped');
798
+ flippedCards = [];
799
+ }, delays.flip);
800
+ }
801
+ }
802
+
803
+ function startTimer() {
804
+ startTime = Date.now();
805
+ timerInterval = setInterval(updateTimer, 1000);
806
+ }
807
+
808
+ function updateTimer() {
809
+ if (!startTime) return;
810
+
811
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
812
+ const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0');
813
+ const seconds = (elapsed % 60).toString().padStart(2, '0');
814
+ document.getElementById('timer').textContent = `${minutes}:${seconds}`;
815
+ }
816
+
817
+ function updateStats() {
818
+ document.getElementById('level').textContent = currentLevel;
819
+ document.getElementById('moves').textContent = moves;
820
+ document.getElementById('matches').textContent = `${totalMatches}`;
821
+ }
822
+
823
+ function levelComplete() {
824
+ gameActive = false;
825
+ clearInterval(timerInterval);
826
+
827
+ const finalTime = document.getElementById('timer').textContent;
828
+ document.getElementById('levelStats').innerHTML =
829
+ `Level ${currentLevel} Complete!<br>Time: ${finalTime}<br>Moves: ${moves}<br>Total Matches: ${totalMatches}`;
830
+
831
+ setTimeout(() => {
832
+ document.getElementById('levelCompleteMessage').classList.add('show');
833
+ }, 500);
834
+ }
835
+
836
+ function nextLevel() {
837
+ currentLevel++;
838
+ document.getElementById('levelCompleteMessage').classList.remove('show');
839
+ initGame();
840
+ }
841
+
842
+ function gameWon() {
843
+ gameActive = false;
844
+ clearInterval(timerInterval);
845
+
846
+ const finalTime = document.getElementById('timer').textContent;
847
+ document.getElementById('finalStats').innerHTML =
848
+ `๐Ÿ† Game Complete! ๐Ÿ†<br>Total Levels: ${currentLevel}<br>Final Time: ${finalTime}<br>Total Moves: ${moves}<br>Total Matches: ${totalMatches}`;
849
+
850
+ setTimeout(() => {
851
+ document.getElementById('victoryMessage').classList.add('show');
852
+ }, 500);
853
+ }
854
+
855
+ function resetGame() {
856
+ currentLevel = 1;
857
+ totalMatches = 0;
858
+ initGame();
859
+ }
860
+
861
+ // Sound and UI control functions
862
+ function toggleGuide() {
863
+ const guide = document.getElementById('howToPlay');
864
+ guide.classList.toggle('hidden');
865
+ }
866
+
867
+ function toggleMusic() {
868
+ musicEnabled = !musicEnabled;
869
+ const toggle = document.getElementById('musicToggle');
870
+ toggle.textContent = musicEnabled ? 'ON' : 'OFF';
871
+ toggle.classList.toggle('off', !musicEnabled);
872
+
873
+ if (backgroundMusic && musicEnabled) {
874
+ backgroundMusic.masterGain.gain.setValueAtTime(
875
+ musicEnabled ? musicVolume * 0.15 : 0,
876
+ audioContext.currentTime
877
+ );
878
+ }
879
+ }
880
+
881
+ function toggleSFX() {
882
+ sfxEnabled = !sfxEnabled;
883
+ const toggle = document.getElementById('sfxToggle');
884
+ toggle.textContent = sfxEnabled ? 'ON' : 'OFF';
885
+ toggle.classList.toggle('off', !sfxEnabled);
886
+ }
887
+
888
+ function setMusicVolume(value) {
889
+ musicVolume = value / 100;
890
+ if (backgroundMusic && musicEnabled) {
891
+ backgroundMusic.masterGain.gain.setValueAtTime(
892
+ musicVolume * 0.15,
893
+ audioContext.currentTime
894
+ );
895
+ }
896
+ }
897
+
898
+ function setSFXVolume(value) {
899
+ sfxVolume = value / 100;
900
+ }
901
+
902
+ function setGameSpeed(speed) {
903
+ gameSpeed = speed;
904
+ }
905
+
906
+ // Initialize the game when page loads
907
+ document.addEventListener('DOMContentLoaded', () => {
908
+ initGame();
909
+ // Hide guide initially
910
+ document.getElementById('howToPlay').classList.add('hidden');
911
+ });
912
+ </script>
913
+ </body>
914
+ </html>