offerpk3 commited on
Commit
d6d1bed
·
verified ·
1 Parent(s): 522fee5

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +2013 -19
index.html CHANGED
@@ -1,19 +1,2013 @@
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>Deep Sea Breakout</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Orbitron:wght@400;700&display=swap');
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ background: radial-gradient(ellipse at bottom, #1B2735 0%, #090A0F 100%);
14
+ display: flex;
15
+ justify-content: center;
16
+ align-items: center;
17
+ min-height: 100vh;
18
+ font-family: 'Orbitron', sans-serif;
19
+ overflow: hidden;
20
+ color: #e0f7ff;
21
+ }
22
+ /* Stars for background - pure CSS */
23
+ .stars-bg::before {
24
+ content: "";
25
+ position: absolute;
26
+ top: 0;
27
+ left: 0;
28
+ right: 0;
29
+ bottom: 0;
30
+ background:
31
+ radial-gradient(2px 2px at 10% 20%, white, rgba(0,0,0,0)),
32
+ radial-gradient(2px 2px at 90% 80%, white, rgba(0,0,0,0)),
33
+ radial-gradient(2px 2px at 80% 10%, white, rgba(0,0,0,0));
34
+ background-size: 200px 200px;
35
+ animation: twinkle 15s linear infinite;
36
+ }
37
+ @keyframes twinkle {
38
+ 0% { opacity: 0.1; }
39
+ 50% { opacity: 0.7; }
40
+ 100% { opacity: 0.1; }
41
+ }
42
+ .game-container {
43
+ position: relative;
44
+ border: 3px solid #0ff;
45
+ border-radius: 12px;
46
+ box-shadow: 0 0 30px rgba(0, 255, 255, 0.5);
47
+ background: rgba(0, 10, 20, 0.85);
48
+ overflow: hidden;
49
+ }
50
+ canvas {
51
+ display: block;
52
+ background: linear-gradient(180deg,
53
+ rgba(0, 30, 60, 0.9) 0%,
54
+ rgba(0, 15, 35, 1) 100%);
55
+ }
56
+ .ui {
57
+ position: absolute;
58
+ top: 15px;
59
+ left: 15px;
60
+ right: 15px;
61
+ display: flex;
62
+ justify-content: space-between;
63
+ color: #0ff;
64
+ font-size: 18px;
65
+ font-weight: bold;
66
+ text-shadow: 0 0 10px rgba(0, 255, 255, 0.8);
67
+ pointer-events: none;
68
+ font-family: 'Orbitron', sans-serif;
69
+ }
70
+ .ui span {
71
+ background: rgba(0, 40, 80, 0.6);
72
+ padding: 5px 15px;
73
+ border-radius: 20px;
74
+ border: 1px solid rgba(0, 255, 255, 0.3);
75
+ }
76
+ .dialog-box {
77
+ position: absolute;
78
+ top: 50%;
79
+ left: 50%;
80
+ transform: translate(-50%, -50%);
81
+ background: rgba(0, 0, 20, 0.95);
82
+ color: #0ff;
83
+ padding: 30px;
84
+ border-radius: 15px;
85
+ text-align: center;
86
+ border: 2px solid #0ff;
87
+ box-shadow: 0 0 30px rgba(0, 255, 255, 0.8);
88
+ z-index: 10;
89
+ width: 80%;
90
+ max-width: 500px;
91
+ backdrop-filter: blur(5px);
92
+ }
93
+ .dialog-box h1 {
94
+ font-family: 'Press Start 2P', cursive;
95
+ font-size: 28px;
96
+ margin-bottom: 20px;
97
+ text-shadow: 0 0 10px #0ff;
98
+ color: #7ff;
99
+ }
100
+ .dialog-box h2 {
101
+ font-family: 'Press Start 2P', cursive;
102
+ font-size: 24px;
103
+ margin-bottom: 20px;
104
+ text-shadow: 0 0 10px #0ff;
105
+ color: #7ff;
106
+ }
107
+ .dialog-box p {
108
+ font-size: 16px;
109
+ margin-bottom: 25px;
110
+ line-height: 1.6;
111
+ }
112
+ .start-screen {
113
+ display: flex;
114
+ flex-direction: column;
115
+ align-items: center;
116
+ }
117
+ .pause-message {
118
+ position: absolute;
119
+ top: 50%;
120
+ left: 50%;
121
+ transform: translate(-50%, -50%);
122
+ color: #f0f;
123
+ font-size: 60px;
124
+ font-weight: bold;
125
+ text-shadow: 0 0 15px #f0f, 0 0 30px #f0f;
126
+ pointer-events: none;
127
+ display: none;
128
+ font-family: 'Press Start 2P', cursive;
129
+ z-index: 5;
130
+ animation: pulse 1.5s infinite alternate;
131
+ }
132
+ @keyframes pulse {
133
+ from { opacity: 0.6; transform: translate(-50%, -50%) scale(1); }
134
+ to { opacity: 1; transform: translate(-50%, -50%) scale(1.05); }
135
+ }
136
+ .powerup-indicator {
137
+ position: absolute;
138
+ bottom: 10px;
139
+ right: 10px;
140
+ display: flex;
141
+ gap: 10px;
142
+ z-index: 2;
143
+ }
144
+ .powerup-icon {
145
+ width: 30px;
146
+ height: 30px;
147
+ background: rgba(0, 40, 80, 0.7);
148
+ border-radius: 50%;
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ font-weight: bold;
153
+ font-size: 14px;
154
+ border: 1px solid rgba(0, 255, 255, 0.5);
155
+ position: relative;
156
+ }
157
+ .powerup-icon::after {
158
+ content: '';
159
+ position: absolute;
160
+ bottom: 0;
161
+ left: 0;
162
+ height: 3px;
163
+ background: #0f0;
164
+ border-radius: 0 0 15px 15px;
165
+ transition: width 0.1s linear;
166
+ }
167
+ .button {
168
+ background: linear-gradient(45deg, #006699, #0099cc);
169
+ border: none;
170
+ color: white;
171
+ padding: 12px 24px;
172
+ margin: 10px 5px;
173
+ border-radius: 8px;
174
+ cursor: pointer;
175
+ font-size: 16px;
176
+ transition: all 0.3s;
177
+ box-shadow: 0 4px 15px rgba(0, 150, 200, 0.3);
178
+ font-family: 'Orbitron', sans-serif;
179
+ text-transform: uppercase;
180
+ letter-spacing: 1px;
181
+ position: relative;
182
+ overflow: hidden;
183
+ }
184
+ .button::before {
185
+ content: '';
186
+ position: absolute;
187
+ top: -50%;
188
+ left: -50%;
189
+ width: 200%;
190
+ height: 200%;
191
+ background: linear-gradient(
192
+ to bottom right,
193
+ rgba(255, 255, 255, 0.3) 0%,
194
+ rgba(255, 255, 255, 0) 60%
195
+ );
196
+ transform: rotate(30deg);
197
+ transition: all 0.3s;
198
+ }
199
+ .button:hover {
200
+ transform: translateY(-3px);
201
+ box-shadow: 0 6px 20px rgba(0, 255, 255, 0.6);
202
+ }
203
+ .button:hover::before {
204
+ left: 100%;
205
+ }
206
+ .button:active {
207
+ transform: translateY(1px);
208
+ }
209
+ .instructions {
210
+ background: rgba(0, 20, 40, 0.7);
211
+ padding: 15px;
212
+ border-radius: 10px;
213
+ margin-top: 20px;
214
+ border: 1px solid rgba(0, 255, 255, 0.3);
215
+ font-size: 14px;
216
+ }
217
+ .controls {
218
+ position: absolute;
219
+ bottom: 10px;
220
+ left: 50%;
221
+ transform: translateX(-50%);
222
+ color: #7ff;
223
+ font-size: 14px;
224
+ text-align: center;
225
+ width: 90%;
226
+ background: rgba(0, 20, 40, 0.7);
227
+ padding: 8px;
228
+ border-radius: 20px;
229
+ border: 1px solid rgba(0, 255, 255, 0.3);
230
+ }
231
+ .combo-meter {
232
+ position: absolute;
233
+ top: 50%;
234
+ left: 50%;
235
+ transform: translate(-50%, -50%);
236
+ font-size: 40px;
237
+ font-weight: bold;
238
+ color: #ff0;
239
+ text-shadow: 0 0 10px rgba(255, 255, 0, 0.8);
240
+ opacity: 0;
241
+ pointer-events: none;
242
+ z-index: 4;
243
+ font-family: 'Press Start 2P', cursive;
244
+ transition: opacity 0.3s, transform 0.3s;
245
+ }
246
+ /* Responsive adjustments */
247
+ @media (max-width: 800px) {
248
+ .dialog-box h1 { font-size: 20px; }
249
+ .dialog-box h2 { font-size: 18px; }
250
+ .dialog-box { padding: 20px; width: 90%; }
251
+ .pause-message { font-size: 40px; }
252
+ .controls { font-size: 12px; padding: 6px; }
253
+ }
254
+ @media (max-height: 700px) {
255
+ .dialog-box { padding: 15px; }
256
+ .dialog-box p { margin-bottom: 15px; font-size: 14px; }
257
+ .button { padding: 8px 16px; }
258
+ }
259
+ </style>
260
+ </head>
261
+ <body class="stars-bg">
262
+ <div class="game-container">
263
+ <canvas id="gameCanvas" width="800" height="600"></canvas>
264
+ <div class="ui">
265
+ <div>Score: <span id="score">0</span></div>
266
+ <div>Lives: <span id="lives">3</span></div>
267
+ <div>Level: <span id="level">1</span></div>
268
+ <div>Combo: <span id="combo">0x</span></div>
269
+ </div>
270
+ <div class="powerup-indicator" id="powerupIndicator"></div>
271
+ <div class="start-screen dialog-box" id="startScreen">
272
+ <h1>DEEP SEA BREAKOUT</h1>
273
+ <p>Journey through the ocean depths. Clear each coral reef level while avoiding dangerous creatures. Collect powerups to aid your mission!</p>
274
+ <div class="flex gap-4 mt-4">
275
+ <button class="button" onclick="initGame('normal')">Normal</button>
276
+ <button class="button" onclick="initGame('hard')">Hardcore</button>
277
+ </div>
278
+ <div class="instructions mt-4">
279
+ <p>Controls:</p>
280
+ <p>Mouse: Move paddle</p>
281
+ <p>Click: Launch ball / Fire laser</p>
282
+ <p>P: Pause/Resume</p>
283
+ <p>M: Mute/Unmute</p>
284
+ </div>
285
+ <div class="mt-4 text-sm text-cyan-300">
286
+ <p>Look out for special rare powerups!</p>
287
+ </div>
288
+ </div>
289
+ <div class="pause-message" id="pauseMessage">PAUSED</div>
290
+ <div class="combo-meter" id="comboMeter">0x COMBO!</div>
291
+ <div class="dialog-box" id="gameOver" style="display: none;">
292
+ <h2>MISSION FAILED</h2>
293
+ <p>Your score: <span id="finalScore">0</span></p>
294
+ <p class="text-yellow-300" id="highScoreText">New High Score!</p>
295
+ <button class="button" onclick="initGame()">Try Again</button>
296
+ </div>
297
+ <div class="dialog-box" id="gameWon" style="display: none;">
298
+ <h2>MISSION COMPLETE!</h2>
299
+ <p>You conquered all 10 ocean levels!</p>
300
+ <p>Final score: <span id="winScore">0</span></p>
301
+ <p class="text-yellow-300" id="winHighScoreText"></p>
302
+ <button class="button" onclick="initGame()">Play Again</button>
303
+ </div>
304
+ <div class="controls">
305
+ Use mouse to move • Click to launch • P: Pause • M: Mute
306
+ </div>
307
+ </div>
308
+ <script>
309
+ const canvas = document.getElementById('gameCanvas');
310
+ const ctx = canvas.getContext('2d');
311
+
312
+ // --- Audio Setup ---
313
+ class AudioManager {
314
+ constructor() {
315
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
316
+ this.gainNode = this.audioContext.createGain();
317
+ this.gainNode.gain.value = 0.5; // Default volume
318
+ this.gainNode.connect(this.audioContext.destination);
319
+ this.isMuted = false;
320
+ }
321
+
322
+ init() {
323
+ if (this.audioContext.state === 'suspended') {
324
+ this.audioContext.resume();
325
+ }
326
+ }
327
+
328
+ playSound(freq1, freq2, duration, volume = 0.3, type = 'sine', attack = 0.01, decay = 0.1) {
329
+ if (this.isMuted || this.audioContext.state !== 'running') return;
330
+
331
+ const oscillator = this.audioContext.createOscillator();
332
+ const gainNode = this.audioContext.createGain();
333
+
334
+ oscillator.connect(gainNode);
335
+ gainNode.connect(this.gainNode);
336
+
337
+ oscillator.type = type;
338
+ oscillator.frequency.setValueAtTime(freq1, this.audioContext.currentTime);
339
+
340
+ if (freq2) {
341
+ oscillator.frequency.exponentialRampToValueAtTime(freq2, this.audioContext.currentTime + duration);
342
+ }
343
+
344
+ gainNode.gain.setValueAtTime(0, this.audioContext.currentTime);
345
+ gainNode.gain.linearRampToValueAtTime(volume, this.audioContext.currentTime + attack);
346
+ gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + duration + decay);
347
+
348
+ oscillator.start(this.audioContext.currentTime);
349
+ oscillator.stop(this.audioContext.currentTime + duration + decay);
350
+ }
351
+
352
+ toggleMute() {
353
+ this.isMuted = !this.isMuted;
354
+ this.gainNode.gain.value = this.isMuted ? 0 : 0.5;
355
+ return this.isMuted;
356
+ }
357
+ }
358
+
359
+ const audio = new AudioManager();
360
+
361
+ // Sound effects
362
+ function playBlockHitSound() {
363
+ audio.playSound(600, 300, 0.05, 0.2, 'triangle');
364
+ }
365
+
366
+ function playPaddleHitSound() {
367
+ audio.playSound(440, 440, 0.05, 0.3, 'square');
368
+ }
369
+
370
+ function playWallHitSound() {
371
+ audio.playSound(200, 150, 0.05, 0.15, 'sawtooth');
372
+ }
373
+
374
+ function playLoseLifeSound() {
375
+ audio.playSound(300, 50, 0.5, 0.4, 'sawtooth');
376
+ }
377
+
378
+ function playLevelUpSound() {
379
+ audio.playSound(500, 1000, 0.3, 0.3, 'sine', 0.05, 0.3);
380
+ audio.playSound(300, 600, 0.3, 0.2, 'square', 0.05, 0.3);
381
+ }
382
+
383
+ function playPowerUpSpawnSound() {
384
+ audio.playSound(700, 900, 0.1, 0.25, 'sine');
385
+ }
386
+
387
+ function playPowerUpCollectSound() {
388
+ audio.playSound(800, 1200, 0.2, 0.35, 'triangle');
389
+ }
390
+
391
+ function playComboSound(combo) {
392
+ const freq = 300 + (combo * 20);
393
+ audio.playSound(freq, freq + 200, 0.2, Math.min(0.3 + (combo * 0.02), 0.7), 'sine');
394
+ }
395
+
396
+ function playGameOverSound() {
397
+ audio.playSound(300, 100, 1.5, 0.5, 'sine');
398
+ audio.playSound(150, 75, 1.5, 0.4, 'square');
399
+ }
400
+
401
+ function playGameWonSound() {
402
+ audio.playSound(500, 1200, 2, 0.5, 'sine');
403
+ for (let i = 0; i < 5; i++) {
404
+ setTimeout(() => {
405
+ audio.playSound(800 + (i * 50), 500 + (i * 100), 0.2, 0.3, 'square');
406
+ }, i * 300);
407
+ }
408
+ }
409
+
410
+ function playRareSound() {
411
+ audio.playSound(1000, 2000, 0.5, 0.6, 'sine');
412
+ audio.playSound(800, 1500, 0.5, 0.5, 'square');
413
+ }
414
+
415
+ // --- Game Constants ---
416
+ const PADDLE_DEFAULT_WIDTH = 100;
417
+ const BALL_INITIAL_SPEED = 5;
418
+ const POWERUP_DURATION = 8000; // 8 seconds
419
+ const POWERUP_DROP_CHANCE = 0.15; // 15%
420
+ const RARE_POWERUP_CHANCE = 0.05; // 5% chance a dropped powerup is rare
421
+ const COMBO_TIMEOUT = 1500; // 1.5 seconds between hits to maintain combo
422
+
423
+ const POWERUP_TYPES = {
424
+ WIDE_PADDLE: 'WIDE_PADDLE',
425
+ STICKY_PADDLE: 'STICKY_PADDLE',
426
+ PIERCING_BALL: 'PIERCING_BALL',
427
+ LASER_PADDLE: 'LASER_PADDLE',
428
+ MULTI_BALL: 'MULTI_BALL',
429
+ SLOW_MO: 'SLOW_MO'
430
+ };
431
+
432
+ const RARE_POWERUP_TYPES = {
433
+ INVINCIBILITY: 'INVINCIBILITY',
434
+ TIME_STOP: 'TIME_STOP',
435
+ BLACK_HOLE: 'BLACK_HOLE',
436
+ PENTA_BALL: 'PENTA_BALL'
437
+ };
438
+
439
+ const creatures = ['🐠', '🐟', '🐡', '🦈', '🐙', '🦑', '🐢', '🦀', '🦞', '🦐', '🐚', '🐬', '🐋', '⭐'];
440
+ const dangerousCreatures = ['🦈', '🐙', ' squid']; // These reduce lives on contact
441
+
442
+ // Game state
443
+ let game = {
444
+ score: 0,
445
+ highScore: localStorage.getItem('highScore') || 0,
446
+ lives: 3,
447
+ level: 1,
448
+ running: false,
449
+ paused: false,
450
+ difficulty: 'normal',
451
+ particles: [],
452
+ backgroundFish: [],
453
+ backgroundBubbles: [],
454
+ fireParticles: [],
455
+ powerUpsOnScreen: [],
456
+ activePowerUps: {}, // Stores type: { endTime, isRare }
457
+ combo: 0,
458
+ lastHitTime: 0,
459
+ balls: [], // For multi-ball powerup
460
+ lasers: [],
461
+ bubbleTexts: [], // Floating text effects
462
+ blackHole: null // For black hole powerup
463
+ };
464
+
465
+ let paddle = {
466
+ x: canvas.width / 2 - PADDLE_DEFAULT_WIDTH / 2,
467
+ y: canvas.height - 30,
468
+ width: PADDLE_DEFAULT_WIDTH,
469
+ height: 15,
470
+ speed: 8
471
+ };
472
+
473
+ let blocks = [];
474
+ let mouseX = canvas.width / 2;
475
+
476
+ const blockColors = [
477
+ '#ff6b6b', '#ff8e53', '#ffc048', '#feca57', '#54a0ff', '#57ECEA',
478
+ '#976FEA', '#FEA2EF', '#6A82FB', '#76D7C4', '#E3A0DB', '#A3E4D7'
479
+ ];
480
+
481
+ // Initialize primary ball
482
+ function initPrimaryBall() {
483
+ game.balls = [{
484
+ x: canvas.width / 2,
485
+ y: paddle.y - 20,
486
+ dx: 0,
487
+ dy: 0,
488
+ radius: 8,
489
+ speed: BALL_INITIAL_SPEED,
490
+ attached: true,
491
+ piercing: false,
492
+ isPrimary: true
493
+ }];
494
+ }
495
+
496
+ // Create background elements
497
+ function createBackgroundElements() {
498
+ // Background fish
499
+ game.backgroundFish = [];
500
+ for (let i = 0; i < 15; i++) {
501
+ game.backgroundFish.push({
502
+ x: Math.random() * canvas.width,
503
+ y: Math.random() * canvas.height,
504
+ dx: (Math.random() - 0.5) * 1.5,
505
+ dy: (Math.random() - 0.5) * 0.5,
506
+ size: Math.random() * 20 + 15,
507
+ creature: ['🐠', '🐟', '🐡'][Math.floor(Math.random() * 3)],
508
+ alpha: Math.random() * 0.25 + 0.05,
509
+ flipX: Math.random() > 0.5
510
+ });
511
+ }
512
+
513
+ // Background bubbles
514
+ game.backgroundBubbles = [];
515
+ for (let i = 0; i < 30; i++) {
516
+ game.backgroundBubbles.push({
517
+ x: Math.random() * canvas.width,
518
+ y: canvas.height + Math.random() * canvas.height,
519
+ radius: Math.random() * 3 + 1,
520
+ dy: -(Math.random() * 0.8 + 0.2),
521
+ alpha: Math.random() * 0.4 + 0.1,
522
+ amplitude: Math.random() * 20 + 5,
523
+ frequency: Math.random() * 0.05 + 0.01,
524
+ initialX: 0
525
+ });
526
+ game.backgroundBubbles[i].initialX = game.backgroundBubbles[i].x;
527
+ }
528
+ }
529
+
530
+ // Create blocks for current level
531
+ function createBlocks() {
532
+ blocks = [];
533
+ const rows = Math.min(8, 3 + Math.floor(game.level * (game.difficulty === 'hard' ? 1.5 : 1));
534
+ const cols = game.difficulty === 'hard' ? 14 : 12;
535
+ const blockWidth = (canvas.width - 40) / cols;
536
+ const blockHeight = 25;
537
+ const dangerousBlockChance = Math.min(0.15, game.level * 0.01);
538
+
539
+ for (let row = 0; row < rows; row++) {
540
+ for (let col = 0; col < cols; col++) {
541
+ if (Math.random() < 0.1 && game.level > 1) continue;
542
+
543
+ const isDangerous = Math.random() < dangerousBlockChance && game.level > 2;
544
+ const maxHits = (row < 2 || isDangerous) ? (game.difficulty === 'hard' ? 3 : 2) : 1;
545
+ let creature;
546
+
547
+ if (isDangerous) {
548
+ creature = dangerousCreatures[Math.floor(Math.random() * dangerousCreatures.length)];
549
+ } else {
550
+ creature = creatures[Math.floor(Math.random() * creatures.length)];
551
+ }
552
+
553
+ blocks.push({
554
+ x: 20 + col * blockWidth,
555
+ y: 70 + row * (blockHeight + 5),
556
+ width: blockWidth - 2,
557
+ height: blockHeight,
558
+ color: blockColors[(row + col) % blockColors.length],
559
+ hits: maxHits,
560
+ maxHits: maxHits,
561
+ creature: creature,
562
+ isDangerous: isDangerous
563
+ });
564
+ }
565
+ }
566
+ }
567
+
568
+ // --- Particle Effects ---
569
+ function createFireParticles() {
570
+ if (!game.paused && game.running) {
571
+ game.balls.forEach(ball => {
572
+ if (!ball.attached) {
573
+ for (let i = 0; i < 1; i++) {
574
+ game.fireParticles.push({
575
+ x: ball.x + (Math.random() - 0.5) * ball.radius * 0.5,
576
+ y: ball.y + (Math.random() - 0.5) * ball.radius * 0.5,
577
+ dx: (Math.random() - 0.5) * 1,
578
+ dy: (Math.random() - 0.5) * 1,
579
+ life: 15,
580
+ maxLife: 15,
581
+ size: Math.random() * 2.5 + 1,
582
+ color: ball.piercing ? '#ff00ff' : '#ff9900'
583
+ });
584
+ }
585
+ }
586
+ });
587
+ }
588
+ }
589
+
590
+ function updateFireParticles() {
591
+ for (let i = game.fireParticles.length - 1; i >= 0; i--) {
592
+ const p = game.fireParticles[i];
593
+ p.x += p.dx;
594
+ p.y += p.dy;
595
+ p.life--;
596
+ p.dx *= 0.96;
597
+ p.dy *= 0.96;
598
+ if (p.life <= 0) game.fireParticles.splice(i, 1);
599
+ }
600
+ }
601
+
602
+ function drawFireParticles() {
603
+ game.fireParticles.forEach(p => {
604
+ const alpha = p.life / p.maxLife;
605
+ const hue = p.color === '#ff00ff' ? 300 : 30 - (alpha * 30);
606
+
607
+ ctx.save();
608
+ ctx.globalAlpha = alpha * 0.7;
609
+ ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
610
+ ctx.beginPath();
611
+ ctx.arc(p.x, p.y, p.size * alpha, 0, Math.PI * 2);
612
+ ctx.fill();
613
+ ctx.globalAlpha = alpha;
614
+ ctx.fillStyle = `hsl(${hue + 15}, 100%, 65%)`;
615
+ ctx.beginPath();
616
+ ctx.arc(p.x, p.y, p.size * alpha * 0.4, 0, Math.PI * 2);
617
+ ctx.fill();
618
+ ctx.restore();
619
+ });
620
+ }
621
+
622
+ function updateBackgroundFish() {
623
+ game.backgroundFish.forEach(fish => {
624
+ fish.x += fish.dx;
625
+ fish.y += fish.dy;
626
+
627
+ if (fish.x > canvas.width + 50) fish.x = -50;
628
+ if (fish.x < -50) fish.x = canvas.width + 50;
629
+ if (fish.y > canvas.height + 50) fish.y = -50;
630
+ if (fish.y < -50) fish.y = canvas.height + 50;
631
+
632
+ if (Math.random() < 0.005) {
633
+ fish.dx = (Math.random() - 0.5) * 1.5;
634
+ fish.dy = (Math.random() - 0.5) * 0.5;
635
+ fish.flipX = fish.dx > 0;
636
+ }
637
+ });
638
+ }
639
+
640
+ function drawBackgroundFish() {
641
+ game.backgroundFish.forEach(fish => {
642
+ ctx.save();
643
+ ctx.globalAlpha = fish.alpha;
644
+ ctx.font = `${fish.size}px Arial`;
645
+ ctx.textAlign = 'center';
646
+ if (fish.flipX) {
647
+ ctx.scale(-1, 1);
648
+ ctx.fillText(fish.creature, -fish.x, fish.y);
649
+ } else {
650
+ ctx.fillText(fish.creature, fish.x, fish.y);
651
+ }
652
+ ctx.restore();
653
+ });
654
+ }
655
+
656
+ function updateBackgroundBubbles() {
657
+ game.backgroundBubbles.forEach(bubble => {
658
+ bubble.y += bubble.dy;
659
+ bubble.x = bubble.initialX + Math.sin(bubble.y * bubble.frequency) * bubble.amplitude;
660
+
661
+ if (bubble.y + bubble.radius < 0) {
662
+ bubble.y = canvas.height + bubble.radius;
663
+ bubble.initialX = Math.random() * canvas.width;
664
+ bubble.x = bubble.initialX;
665
+ bubble.alpha = Math.random() * 0.4 + 0.1;
666
+ }
667
+ });
668
+ }
669
+
670
+ function drawBackgroundBubbles() {
671
+ game.backgroundBubbles.forEach(bubble => {
672
+ ctx.save();
673
+ ctx.globalAlpha = bubble.alpha;
674
+ ctx.fillStyle = 'rgba(173, 216, 230, 0.5)';
675
+ ctx.beginPath();
676
+ ctx.arc(bubble.x, bubble.y, bubble.radius, 0, Math.PI * 2);
677
+ ctx.fill();
678
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
679
+ ctx.beginPath();
680
+ ctx.arc(bubble.x - bubble.radius * 0.3, bubble.y - bubble.radius * 0.3, bubble.radius * 0.3, 0, Math.PI * 2);
681
+ ctx.fill();
682
+ ctx.restore();
683
+ });
684
+ }
685
+
686
+ function createBlockBreakParticle(x, y, color, count = 8) {
687
+ for (let i = 0; i < count; i++) {
688
+ game.particles.push({
689
+ x: x,
690
+ y: y,
691
+ dx: (Math.random() - 0.5) * 5,
692
+ dy: (Math.random() - 0.5) * 5 - 2,
693
+ life: 35,
694
+ maxLife: 35,
695
+ color: color,
696
+ size: Math.random() * 3 + 1.5,
697
+ isGlowing: color === '#ffff00'
698
+ });
699
+ }
700
+ }
701
+
702
+ function updateParticles() {
703
+ for (let i = game.particles.length - 1; i >= 0; i--) {
704
+ const p = game.particles[i];
705
+ p.x += p.dx;
706
+ p.y += p.dy;
707
+ p.life--;
708
+ p.dy += 0.08;
709
+
710
+ if (p.life <= 0) game.particles.splice(i, 1);
711
+ }
712
+ }
713
+
714
+ function drawParticles() {
715
+ game.particles.forEach(p => {
716
+ const alpha = p.life / p.maxLife;
717
+ ctx.save();
718
+ ctx.globalAlpha = alpha;
719
+
720
+ if (p.isGlowing) {
721
+ const gradient = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size * alpha);
722
+ gradient.addColorStop(0, p.color);
723
+ gradient.addColorStop(1, 'rgba(255, 255, 0, 0)');
724
+ ctx.fillStyle = gradient;
725
+ } else {
726
+ ctx.fillStyle = p.color;
727
+ }
728
+
729
+ ctx.beginPath();
730
+ ctx.arc(p.x, p.y, p.size * alpha, 0, Math.PI * 2);
731
+ ctx.fill();
732
+ ctx.restore();
733
+ });
734
+ }
735
+
736
+ // --- Drawing Functions ---
737
+ function drawPaddle() {
738
+ // Paddle glow
739
+ ctx.save();
740
+ ctx.shadowColor = '#00ffff';
741
+ ctx.shadowBlur = 15;
742
+ ctx.fillStyle = 'rgba(0, 255, 255, 0.1)';
743
+ ctx.fillRect(paddle.x - 5, paddle.y - 5, paddle.width + 10, paddle.height + 10);
744
+ ctx.restore();
745
+
746
+ // Paddle body with gradient
747
+ const gradient = ctx.createLinearGradient(paddle.x, paddle.y, paddle.x, paddle.y + paddle.height);
748
+ gradient.addColorStop(0, '#00ffff');
749
+ gradient.addColorStop(1, '#0088cc');
750
+ ctx.fillStyle = gradient;
751
+ ctx.beginPath();
752
+ ctx.roundRect(paddle.x, paddle.y, paddle.width, paddle.height, [paddle.height / 2]);
753
+ ctx.fill();
754
+
755
+ // Paddle outline
756
+ ctx.strokeStyle = '#00ffff';
757
+ ctx.lineWidth = 2;
758
+ ctx.beginPath();
759
+ ctx.roundRect(paddle.x, paddle.y, paddle.width, paddle.height, [paddle.height / 2]);
760
+ ctx.stroke();
761
+
762
+ // Draw laser cannon if active
763
+ if (game.activePowerUps[POWERUP_TYPES.LASER_PADDLE]) {
764
+ ctx.fillStyle = '#ff0000';
765
+ ctx.beginPath();
766
+ ctx.moveTo(paddle.x, paddle.y);
767
+ ctx.lineTo(paddle.x + paddle.width, paddle.y);
768
+ ctx.lineTo(paddle.x + paddle.width / 2, paddle.y - 15);
769
+ ctx.closePath();
770
+ ctx.fill();
771
+ }
772
+ }
773
+
774
+ function drawBalls() {
775
+ game.balls.forEach(ball => {
776
+ // Ball glow effect
777
+ if (!ball.attached) {
778
+ ctx.save();
779
+ ctx.globalAlpha = 0.4;
780
+ const glowSize = ball.radius * 2;
781
+ const gradient = ctx.createRadialGradient(
782
+ ball.x, ball.y, 0,
783
+ ball.x, ball.y, glowSize
784
+ );
785
+
786
+ if (ball.piercing) {
787
+ gradient.addColorStop(0, 'rgba(255, 0, 255, 0.8)');
788
+ gradient.addColorStop(1, 'rgba(255, 0, 255, 0)');
789
+ } else if (ball.isPrimary) {
790
+ gradient.addColorStop(0, 'rgba(0, 200, 255, 0.8)');
791
+ gradient.addColorStop(1, 'rgba(0, 200, 255, 0)');
792
+ } else {
793
+ gradient.addColorStop(0, 'rgba(255, 255, 255, 0.8)');
794
+ gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
795
+ }
796
+
797
+ ctx.fillStyle = gradient;
798
+ ctx.beginPath();
799
+ ctx.arc(ball.x, ball.y, glowSize, 0, Math.PI * 2);
800
+ ctx.fill();
801
+ ctx.restore();
802
+ }
803
+
804
+ // Ball main body
805
+ const gradient = ctx.createRadialGradient(
806
+ ball.x - ball.radius * 0.2,
807
+ ball.y - ball.radius * 0.2,
808
+ 0,
809
+ ball.x,
810
+ ball.y,
811
+ ball.radius
812
+ );
813
+
814
+ if (ball.piercing) {
815
+ gradient.addColorStop(0, '#ffffff');
816
+ gradient.addColorStop(0.3, '#ff99ff');
817
+ gradient.addColorStop(1, '#ff00ff');
818
+ } else if (ball.isPrimary) {
819
+ gradient.addColorStop(0, '#ffffff');
820
+ gradient.addColorStop(0.3, '#99ffff');
821
+ gradient.addColorStop(1, '#0099ff');
822
+ } else {
823
+ gradient.addColorStop(0, '#ffffff');
824
+ gradient.addColorStop(0.3, '#e0f6ff');
825
+ gradient.addColorStop(1, '#87ceeb');
826
+ }
827
+
828
+ ctx.fillStyle = gradient;
829
+ ctx.beginPath();
830
+ ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
831
+ ctx.fill();
832
+
833
+ // Ball highlight
834
+ ctx.save();
835
+ ctx.globalAlpha = 0.8;
836
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
837
+ ctx.beginPath();
838
+ ctx.arc(
839
+ ball.x - ball.radius * 0.3,
840
+ ball.y - ball.radius * 0.3,
841
+ ball.radius * 0.3,
842
+ 0,
843
+ Math.PI * 2
844
+ );
845
+ ctx.fill();
846
+ ctx.restore();
847
+
848
+ // Draw trajectory prediction line if sticky paddle is active
849
+ if (game.activePowerUps[POWERUP_TYPES.STICKY_PADDLE] && ball.attached && ball.isPrimary) {
850
+ ctx.save();
851
+ ctx.strokeStyle = 'rgba(0, 255, 255, 0.3)';
852
+ ctx.setLineDash([3, 3]);
853
+ ctx.lineWidth = 1;
854
+ ctx.beginPath();
855
+ ctx.moveTo(ball.x, ball.y);
856
+ ctx.lineTo(ball.x, 0);
857
+ ctx.stroke();
858
+ ctx.restore();
859
+ }
860
+ });
861
+ }
862
+
863
+ function drawPowerUpIndicators() {
864
+ const indicator = document.getElementById('powerupIndicator');
865
+ indicator.innerHTML = '';
866
+
867
+ for (const [type, data] of Object.entries(game.activePowerUps)) {
868
+ const remainingTime = Math.max(0, data.endTime - Date.now());
869
+ const percentage = (remainingTime / POWERUP_DURATION) * 100;
870
+ const icon = document.createElement('div');
871
+ icon.className = 'powerup-icon';
872
+ let iconText = '';
873
+ let bgColor = '';
874
+
875
+ switch(type) {
876
+ case POWERUP_TYPES.WIDE_PADDLE:
877
+ iconText = 'W';
878
+ bgColor = data.isRare ? '#00ff00' : 'rgba(0, 255, 0, 0.7)';
879
+ break;
880
+ case POWERUP_TYPES.STICKY_PADDLE:
881
+ iconText = 'S';
882
+ bgColor = data.isRare ? '#ffff00' : 'rgba(255, 255, 0, 0.7)';
883
+ break;
884
+ case POWERUP_TYPES.PIERCING_BALL:
885
+ iconText = 'P';
886
+ bgColor = data.isRare ? '#ff00ff' : 'rgba(255, 0, 255, 0.7)';
887
+ break;
888
+ case POWERUP_TYPES.LASER_PADDLE:
889
+ iconText = 'L';
890
+ bgColor = 'rgba(255, 0, 0, 0.7)';
891
+ break;
892
+ case POWERUP_TYPES.MULTI_BALL:
893
+ iconText = 'M';
894
+ bgColor = 'rgba(0, 255, 255, 0.7)';
895
+ break;
896
+ case POWERUP_TYPES.SLOW_MO:
897
+ iconText = '⌛';
898
+ bgColor = 'rgba(200, 200, 255, 0.7)';
899
+ break;
900
+ case RARE_POWERUP_TYPES.INVINCIBILITY:
901
+ iconText = '🛡️';
902
+ bgColor = 'rgba(255, 215, 0, 0.7)';
903
+ break;
904
+ case RARE_POWERUP_TYPES.TIME_STOP:
905
+ iconText = '⏸️';
906
+ bgColor = 'rgba(100, 255, 255, 0.7)';
907
+ break;
908
+ case RARE_POWERUP_TYPES.BLACK_HOLE:
909
+ iconText = '🌀';
910
+ bgColor = 'rgba(0, 0, 0, 0.7)';
911
+ break;
912
+ case RARE_POWERUP_TYPES.PENTA_BALL:
913
+ iconText = '5️⃣';
914
+ bgColor = 'rgba(255, 100, 255, 0.7)';
915
+ break;
916
+ }
917
+
918
+ icon.style.background = bgColor;
919
+ icon.textContent = iconText;
920
+ // Add time indicator
921
+ icon.style.setProperty('--time-left', `${percentage}%`);
922
+ indicator.appendChild(icon);
923
+ }
924
+ }
925
+
926
+ function drawBubbleTexts() {
927
+ for (let i = game.bubbleTexts.length - 1; i >= 0; i--) {
928
+ const text = game.bubbleTexts[i];
929
+ ctx.save();
930
+ ctx.globalAlpha = text.alpha;
931
+ ctx.font = `${text.size}px 'Press Start 2P', Arial`;
932
+ ctx.textAlign = 'center';
933
+ ctx.fillStyle = text.color;
934
+ ctx.fillText(text.text, text.x, text.y);
935
+ ctx.restore();
936
+
937
+ text.y -= 1;
938
+ text.alpha -= 0.01;
939
+
940
+ if (text.alpha <= 0) {
941
+ game.bubbleTexts.splice(i, 1);
942
+ }
943
+ }
944
+ }
945
+
946
+ function drawBlocks() {
947
+ blocks.forEach(block => {
948
+ const healthRatio = block.hits / block.maxHits;
949
+
950
+ // Block glow for dangerous blocks
951
+ if (block.isDangerous) {
952
+ ctx.save();
953
+ ctx.shadowColor = '#ff0000';
954
+ ctx.shadowBlur = 15;
955
+ ctx.globalAlpha = 0.3;
956
+ ctx.fillStyle = '#ff0000';
957
+ ctx.fillRect(
958
+ block.x - 2,
959
+ block.y - 2,
960
+ block.width + 4,
961
+ block.height + 4
962
+ );
963
+ ctx.restore();
964
+ }
965
+
966
+ // Block body
967
+ const gradient = ctx.createLinearGradient(
968
+ block.x, block.y,
969
+ block.x, block.y + block.height
970
+ );
971
+
972
+ if (block.hits > 1) {
973
+ gradient.addColorStop(0, block.color);
974
+ gradient.addColorStop(1, '#555');
975
+ } else {
976
+ gradient.addColorStop(0, block.color);
977
+ gradient.addColorStop(1, `${block.color}90`);
978
+ }
979
+
980
+ ctx.fillStyle = gradient;
981
+ ctx.beginPath();
982
+ ctx.roundRect(
983
+ block.x, block.y,
984
+ block.width, block.height,
985
+ [5]
986
+ );
987
+ ctx.fill();
988
+
989
+ // Block outline
990
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
991
+ ctx.lineWidth = 1;
992
+ ctx.beginPath();
993
+ ctx.roundRect(
994
+ block.x, block.y,
995
+ block.width, block.height,
996
+ [5]
997
+ );
998
+ ctx.stroke();
999
+
1000
+ // Block creature emoji
1001
+ ctx.font = `${Math.min(block.width, block.height) * 0.65}px Arial`;
1002
+ ctx.textAlign = 'center';
1003
+ ctx.textBaseline = 'middle';
1004
+
1005
+ // Shadow
1006
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
1007
+ ctx.fillText(
1008
+ block.creature,
1009
+ block.x + block.width/2 + 1,
1010
+ block.y + block.height/2 + 1
1011
+ );
1012
+
1013
+ // Main text
1014
+ ctx.fillStyle = healthRatio === 1 ? '#ffffff' : 'rgba(255, 255, 255, 0.6)';
1015
+ ctx.fillText(
1016
+ block.creature,
1017
+ block.x + block.width/2,
1018
+ block.y + block.height/2
1019
+ );
1020
+
1021
+ // Health bar
1022
+ if (block.hits < block.maxHits) {
1023
+ ctx.save();
1024
+ ctx.globalAlpha = 0.6;
1025
+ ctx.fillStyle = block.isDangerous ? '#ff3333' : '#ff9900';
1026
+ ctx.beginPath();
1027
+ ctx.roundRect(
1028
+ block.x,
1029
+ block.y - 5,
1030
+ block.width * healthRatio,
1031
+ 3,
1032
+ [5]
1033
+ );
1034
+ ctx.fill();
1035
+ ctx.restore();
1036
+ }
1037
+ });
1038
+ }
1039
+
1040
+ function drawLasers() {
1041
+ game.lasers.forEach((laser, index) => {
1042
+ ctx.save();
1043
+ const gradient = ctx.createLinearGradient(
1044
+ laser.x, laser.y,
1045
+ laser.x, laser.y - laser.height
1046
+ );
1047
+
1048
+ gradient.addColorStop(0, 'rgba(255, 0, 0, 0)');
1049
+ gradient.addColorStop(0.3, 'rgba(255, 0, 0, 0.8)');
1050
+ gradient.addColorStop(1, 'rgba(255, 255, 0, 0.8)');
1051
+
1052
+ ctx.fillStyle = gradient;
1053
+ ctx.fillRect(
1054
+ laser.x - laser.width/2,
1055
+ laser.y - laser.height,
1056
+ laser.width,
1057
+ laser.height
1058
+ );
1059
+
1060
+ // Update laser
1061
+ laser.height += 10;
1062
+ if (laser.height > canvas.height) {
1063
+ game.lasers.splice(index, 1);
1064
+ }
1065
+
1066
+ // Check for laser collision with blocks
1067
+ blocks.forEach((block, blockIndex) => {
1068
+ if (laser.x >= block.x && laser.x <= block.x + block.width &&
1069
+ laser.y - laser.height <= block.y + block.height &&
1070
+ laser.y - laser.height >= block.y) {
1071
+ // Destroy block
1072
+ createBlockBreakParticle(
1073
+ block.x + block.width/2,
1074
+ block.y + block.height/2,
1075
+ '#ffff00',
1076
+ 15
1077
+ );
1078
+ blocks.splice(blockIndex, 1);
1079
+ game.score += 15 * game.level;
1080
+ updateCombo();
1081
+ }
1082
+ });
1083
+ ctx.restore();
1084
+ });
1085
+ }
1086
+
1087
+ function drawBlackHole() {
1088
+ if (!game.blackHole) return;
1089
+
1090
+ ctx.save();
1091
+ // Outer glow
1092
+ const outerRadius = game.blackHole.radius * 1.5;
1093
+ const gradient = ctx.createRadialGradient(
1094
+ game.blackHole.x, game.blackHole.y, 0,
1095
+ game.blackHole.x, game.blackHole.y, outerRadius
1096
+ );
1097
+
1098
+ gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
1099
+ gradient.addColorStop(0.8, 'rgba(70, 0, 100, 0.5)');
1100
+ gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
1101
+ ctx.fillStyle = gradient;
1102
+ ctx.beginPath();
1103
+ ctx.arc(game.blackHole.x, game.blackHole.y, outerRadius, 0, Math.PI * 2);
1104
+ ctx.fill();
1105
+
1106
+ // Swirling effect
1107
+ ctx.strokeStyle = 'rgba(150, 0, 255, 0.7)';
1108
+ ctx.lineWidth = 2;
1109
+
1110
+ for (let i = 0; i < 5; i++) {
1111
+ ctx.beginPath();
1112
+ for (let angle = 0; angle < Math.PI * 2; angle += 0.1) {
1113
+ const radius = game.blackHole.radius * 0.7 + Math.sin(angle * 3 + Date.now() / 200) * 5;
1114
+ const x = game.blackHole.x + Math.cos(angle + i * Math.PI * 0.4) * radius;
1115
+ const y = game.blackHole.y + Math.sin(angle + i * Math.PI * 0.4) * radius;
1116
+ if (angle === 0) ctx.moveTo(x, y);
1117
+ else ctx.lineTo(x, y);
1118
+ }
1119
+ ctx.closePath();
1120
+ ctx.stroke();
1121
+ }
1122
+
1123
+ // Black center
1124
+ ctx.fillStyle = '#000';
1125
+ ctx.beginPath();
1126
+ ctx.arc(game.blackHole.x, game.blackHole.y, game.blackHole.radius, 0, Math.PI * 2);
1127
+ ctx.fill();
1128
+ ctx.restore();
1129
+
1130
+ // Update black hole attraction
1131
+ game.blackHole.timeLeft--;
1132
+ if (game.blackHole.timeLeft <= 0) {
1133
+ game.blackHole = null;
1134
+ return;
1135
+ }
1136
+
1137
+ // Attract balls and blocks
1138
+ game.balls.forEach(ball => {
1139
+ if (ball.attached) return;
1140
+ const dx = game.blackHole.x - ball.x;
1141
+ const dy = game.blackHole.y - ball.y;
1142
+ const distance = Math.sqrt(dx * dx + dy * dy);
1143
+
1144
+ if (distance < 200) {
1145
+ const force = (200 - distance) / 20;
1146
+ ball.dx += dx / distance * force;
1147
+ ball.dy += dy / distance * force;
1148
+ }
1149
+ });
1150
+
1151
+ blocks.forEach((block, index) => {
1152
+ const blockCenterX = block.x + block.width / 2;
1153
+ const blockCenterY = block.y + block.height / 2;
1154
+ const dx = game.blackHole.x - blockCenterX;
1155
+ const dy = game.blackHole.y - blockCenterY;
1156
+ const distance = Math.sqrt(dx * dx + dy * dy);
1157
+
1158
+ if (distance < 150) {
1159
+ // Destroy block if too close
1160
+ if (distance < game.blackHole.radius + 10) {
1161
+ createBlockBreakParticle(
1162
+ blockCenterX, blockCenterY,
1163
+ '#aa00ff',
1164
+ 20
1165
+ );
1166
+ blocks.splice(index, 1);
1167
+ game.score += 20 * game.level;
1168
+ updateCombo();
1169
+ } else {
1170
+ // Draw block being pulled
1171
+ const angle = Math.atan2(dy, dx);
1172
+ ctx.save();
1173
+ ctx.translate(block.x, block.y);
1174
+ ctx.rotate(angle);
1175
+ ctx.globalAlpha = 0.6;
1176
+ ctx.fillStyle = '#aa00ff';
1177
+ ctx.fillRect(
1178
+ block.width * 0.3,
1179
+ -2,
1180
+ block.width * 0.5 * Math.min(1, 50 / distance),
1181
+ 4
1182
+ );
1183
+ ctx.restore();
1184
+ }
1185
+ }
1186
+ });
1187
+ }
1188
+
1189
+ // --- Power-up Functions ---
1190
+ function spawnPowerUpItem(x, y) {
1191
+ const isRare = Math.random() < RARE_POWERUP_CHANCE;
1192
+ let type, text, color;
1193
+
1194
+ if (isRare) {
1195
+ const rareTypes = Object.values(RARE_POWERUP_TYPES);
1196
+ type = rareTypes[Math.floor(Math.random() * rareTypes.length)];
1197
+ switch (type) {
1198
+ case RARE_POWERUP_TYPES.INVINCIBILITY: text = '🛡️'; color = 'rgba(255, 215, 0, 0.9)'; break;
1199
+ case RARE_POWERUP_TYPES.TIME_STOP: text = '⏸️'; color = 'rgba(100, 255, 255, 0.9)'; break;
1200
+ case RARE_POWERUP_TYPES.BLACK_HOLE: text = '🌀'; color = 'rgba(0, 0, 0, 0.9)'; break;
1201
+ case RARE_POWERUP_TYPES.PENTA_BALL: text = '5️⃣'; color = 'rgba(255, 100, 255, 0.9)'; break;
1202
+ }
1203
+ playRareSound();
1204
+ } else {
1205
+ const types = Object.values(POWERUP_TYPES);
1206
+ type = types[Math.floor(Math.random() * types.length)];
1207
+ switch (type) {
1208
+ case POWERUP_TYPES.WIDE_PADDLE: text = 'W'; color = 'rgba(0, 255, 0, 0.8)'; break;
1209
+ case POWERUP_TYPES.STICKY_PADDLE: text = 'S'; color = 'rgba(255, 255, 0, 0.8)'; break;
1210
+ case POWERUP_TYPES.PIERCING_BALL: text = 'P'; color = 'rgba(255, 0, 255, 0.8)'; break;
1211
+ case POWERUP_TYPES.LASER_PADDLE: text = 'L'; color = 'rgba(255, 0, 0, 0.8)'; break;
1212
+ case POWERUP_TYPES.MULTI_BALL: text = 'M'; color = 'rgba(0, 255, 255, 0.8)'; break;
1213
+ case POWERUP_TYPES.SLOW_MO: text = '⌛'; color = 'rgba(200, 200, 255, 0.8)'; break;
1214
+ }
1215
+ playPowerUpSpawnSound();
1216
+ }
1217
+
1218
+ game.powerUpsOnScreen.push({
1219
+ x, y,
1220
+ type, text, color,
1221
+ radius: isRare ? 16 : 12,
1222
+ dy: isRare ? 1.2 : 1.5,
1223
+ isRare
1224
+ });
1225
+ }
1226
+
1227
+ function activatePowerUp(type) {
1228
+ playPowerUpCollectSound();
1229
+
1230
+ // Reset previous effect if same type is collected
1231
+ if (game.activePowerUps[type]) {
1232
+ return; // Don't reset timer, just extend duration
1233
+ }
1234
+
1235
+ // Set end time
1236
+ const duration = type in RARE_POWERUP_TYPES ? POWERUP_DURATION * 1.5 : POWERUP_DURATION;
1237
+ game.activePowerUps[type] = {
1238
+ endTime: Date.now() + duration,
1239
+ isRare: type in RARE_POWERUP_TYPES
1240
+ };
1241
+
1242
+ // Apply immediate effects
1243
+ switch (type) {
1244
+ case POWERUP_TYPES.WIDE_PADDLE:
1245
+ paddle.width = PADDLE_DEFAULT_WIDTH * 1.5;
1246
+ break;
1247
+ case RARE_POWERUP_TYPES.INVINCIBILITY:
1248
+ game.lives += 2; // Bonus lives
1249
+ addBubbleText('+2 LIVES', paddle.x + paddle.width/2, paddle.y, '#ffff00', 24);
1250
+ break;
1251
+ case RARE_POWERUP_TYPES.TIME_STOP:
1252
+ // Slows down everything except paddle for 5 seconds
1253
+ setTimeout(() => {
1254
+ delete game.activePowerUps[type];
1255
+ }, 5000);
1256
+ break;
1257
+ case RARE_POWERUP_TYPES.BLACK_HOLE:
1258
+ game.blackHole = {
1259
+ x: canvas.width / 2,
1260
+ y: canvas.height / 3,
1261
+ radius: 40,
1262
+ timeLeft: 300 // frames
1263
+ };
1264
+ break;
1265
+ case RARE_POWERUP_TYPES.PENTA_BALL:
1266
+ if (game.balls.length < 5) {
1267
+ for (let i = 0; i < 5 - game.balls.length; i++) {
1268
+ addNewBall();
1269
+ }
1270
+ }
1271
+ break;
1272
+ case POWERUP_TYPES.MULTI_BALL:
1273
+ if (game.balls.length < 3) {
1274
+ addNewBall();
1275
+ }
1276
+ break;
1277
+ case POWERUP_TYPES.SLOW_MO:
1278
+ // Makes everything slower for duration
1279
+ break;
1280
+ }
1281
+ }
1282
+
1283
+ function updatePowerUpsOnScreen() {
1284
+ for (let i = game.powerUpsOnScreen.length - 1; i >= 0; i--) {
1285
+ const pu = game.powerUpsOnScreen[i];
1286
+ pu.y += pu.dy;
1287
+
1288
+ if (pu.y - pu.radius > canvas.height) {
1289
+ game.powerUpsOnScreen.splice(i, 1);
1290
+ continue;
1291
+ }
1292
+
1293
+ // Check collision with paddle
1294
+ if (pu.y + pu.radius >= paddle.y &&
1295
+ pu.y - pu.radius <= paddle.y + paddle.height &&
1296
+ pu.x + pu.radius >= paddle.x &&
1297
+ pu.x - pu.radius <= paddle.x + paddle.width) {
1298
+ activatePowerUp(pu.type);
1299
+ addBubbleText(
1300
+ pu.type in RARE_POWERUP_TYPES ? 'RARE!' : 'POWER UP!',
1301
+ pu.x, pu.y,
1302
+ pu.color,
1303
+ pu.isRare ? 18 : 16
1304
+ );
1305
+ game.powerUpsOnScreen.splice(i, 1);
1306
+ }
1307
+ }
1308
+ }
1309
+
1310
+ function drawPowerUpsOnScreen() {
1311
+ game.powerUpsOnScreen.forEach(pu => {
1312
+ // Glow effect
1313
+ if (pu.isRare) {
1314
+ ctx.save();
1315
+ ctx.globalAlpha = 0.3;
1316
+ ctx.fillStyle = pu.color;
1317
+ ctx.beginPath();
1318
+ ctx.arc(pu.x, pu.y, pu.radius * 2, 0, Math.PI * 2);
1319
+ ctx.fill();
1320
+ ctx.globalAlpha = 0.6;
1321
+ ctx.beginPath();
1322
+ ctx.arc(pu.x, pu.y, pu.radius * 1.5, 0, Math.PI * 2);
1323
+ ctx.fill();
1324
+ ctx.restore();
1325
+ }
1326
+
1327
+ // Main circle
1328
+ ctx.fillStyle = pu.color;
1329
+ ctx.beginPath();
1330
+ ctx.arc(pu.x, pu.y, pu.radius, 0, Math.PI * 2);
1331
+ ctx.fill();
1332
+
1333
+ // Icon
1334
+ ctx.fillStyle = 'black';
1335
+ ctx.font = `bold ${pu.radius}px Arial`;
1336
+ ctx.textAlign = 'center';
1337
+ ctx.textBaseline = 'middle';
1338
+ ctx.fillText(pu.text, pu.x, pu.y + (pu.text.length > 1 ? 0 : 1));
1339
+
1340
+ // Pulse animation for rare powerups
1341
+ if (pu.isRare) {
1342
+ ctx.save();
1343
+ ctx.strokeStyle = pu.color;
1344
+ ctx.lineWidth = 2;
1345
+ ctx.globalAlpha = 0.6;
1346
+ ctx.beginPath();
1347
+ ctx.arc(pu.x, pu.y, pu.radius + Math.sin(Date.now() / 200) * 3, 0, Math.PI * 2);
1348
+ ctx.stroke();
1349
+ ctx.restore();
1350
+ }
1351
+ });
1352
+ }
1353
+
1354
+ function updateActivePowerUps() {
1355
+ const now = Date.now();
1356
+
1357
+ // Remove expired powerups
1358
+ for (const [type, data] of Object.entries(game.activePowerUps)) {
1359
+ if (now > data.endTime) {
1360
+ deactivatePowerUp(type);
1361
+ delete game.activePowerUps[type];
1362
+ // Add expiring effect
1363
+ if (type === POWERUP_TYPES.WIDE_PADDLE) {
1364
+ paddle.width = PADDLE_DEFAULT_WIDTH;
1365
+ }
1366
+ }
1367
+ }
1368
+
1369
+ // Draw powerup indicators
1370
+ drawPowerUpIndicators();
1371
+ }
1372
+
1373
+ function fireLaser() {
1374
+ // Do nothing if too many lasers already
1375
+ if (game.lasers.length >= 3) return;
1376
+
1377
+ const positions = [
1378
+ paddle.x + paddle.width * 0.2,
1379
+ paddle.x + paddle.width * 0.5,
1380
+ paddle.x + paddle.width * 0.8
1381
+ ];
1382
+
1383
+ positions.forEach(pos => {
1384
+ if (game.lasers.length < 5 && Math.random() < 0.5) {
1385
+ game.lasers.push({
1386
+ x: pos,
1387
+ y: paddle.y,
1388
+ width: 8,
1389
+ height: 10
1390
+ });
1391
+ }
1392
+ });
1393
+ }
1394
+
1395
+ function addNewBall() {
1396
+ const primaryBall = game.balls.find(b => b.isPrimary);
1397
+ game.balls.push({
1398
+ x: primaryBall ? primaryBall.x : canvas.width / 2,
1399
+ y: primaryBall ? primaryBall.y : paddle.y - 20,
1400
+ dx: (Math.random() - 0.5) * 3,
1401
+ dy: -Math.abs((Math.random() * 2 + 2),
1402
+ radius: 8,
1403
+ speed: BALL_INITIAL_SPEED,
1404
+ attached: false,
1405
+ piercing: false,
1406
+ isPrimary: false
1407
+ });
1408
+ }
1409
+
1410
+ function addBubbleText(text, x, y, color, size) {
1411
+ game.bubbleTexts.push({
1412
+ text: text,
1413
+ x: x,
1414
+ y: y,
1415
+ color: color,
1416
+ size: size,
1417
+ alpha: 1
1418
+ });
1419
+ }
1420
+
1421
+ // --- Ball and Game Logic ---
1422
+ function updateBalls() {
1423
+ for (let i = game.balls.length - 1; i >= 0; i--) {
1424
+ const ball = game.balls[i];
1425
+ if (ball.attached) continue;
1426
+
1427
+ // Apply slow motion if active
1428
+ const slowMoFactor = game.activePowerUps[POWERUP_TYPES.SLOW_MO] ? 0.6 : 1;
1429
+ const timeStopActive = game.activePowerUps[RARE_POWERUP_TYPES.TIME_STOP];
1430
+
1431
+ if (!timeStopActive) {
1432
+ ball.x += ball.dx * slowMoFactor;
1433
+ ball.y += ball.dy * slowMoTensorflow.js:1 Uncaught ReferenceError: Cannot access 'slowMoFactor' before initialization
1434
+ }
1435
+
1436
+ // Wall collisions
1437
+ if (ball.x <= ball.radius || ball.x >= canvas.width - ball.radius) {
1438
+ ball.dx = -ball.dx;
1439
+ playWallHitSound();
1440
+ createBlockBreakParticle(ball.x, ball.y, '#87ceeb');
1441
+ ball.x = Math.max(ball.radius, Math.min(ball.x, canvas.width - ball.radius));
1442
+ }
1443
+
1444
+ if (ball.y <= ball.radius) {
1445
+ ball.dy = -ball.dy;
1446
+ playWallHitSound();
1447
+ createBlockBreakParticle(ball.x, ball.y, '#87ceeb');
1448
+ ball.y = ball.radius;
1449
+ }
1450
+
1451
+ // Paddle collision
1452
+ if (ball.y + ball.radius >= paddle.y &&
1453
+ ball.y - ball.radius <= paddle.y + paddle.height &&
1454
+ ball.x >= paddle.x &&
1455
+ ball.x <= paddle.x + paddle.width &&
1456
+ ball.dy > 0) {
1457
+ // Sticky paddle behavior
1458
+ if (game.activePowerUps[POWERUP_TYPES.STICKY_PADDLE] && ball.isPrimary) {
1459
+ ball.attached = true;
1460
+ ball.dx = 0;
1461
+ ball.dy = 0;
1462
+ } else {
1463
+ // Normal bounce
1464
+ const hitPos = (ball.x - (paddle.x + paddle.width / 2)) / (paddle.width / 2);
1465
+ const bounceAngle = hitPos * (Math.PI / 3);
1466
+ ball.dx = ball.speed * Math.sin(bounceAngle);
1467
+ ball.dy = -ball.speed * Math.cos(bounceAngle);
1468
+ // Random factor to prevent repetitive angles
1469
+ ball.dx += (Math.random() - 0.5) * 0.5;
1470
+ }
1471
+ playPaddleHitSound();
1472
+ createBlockBreakParticle(ball.x, paddle.y, '#00ffff');
1473
+ ball.y = paddle.y - ball.radius;
1474
+ }
1475
+
1476
+ // Block collisions
1477
+ for (let j = blocks.length - 1; j >= 0; j--) {
1478
+ const block = blocks[j];
1479
+ if (ball.x + ball.radius > block.x &&
1480
+ ball.x - ball.radius < block.x + block.width &&
1481
+ ball.y + ball.radius > block.y &&
1482
+ ball.y - ball.radius < block.y + block.height) {
1483
+ // Handle dangerous blocks
1484
+ if (block.isDangerous && ball.isPrimary) {
1485
+ game.lives--;
1486
+ playLoseLifeSound();
1487
+ if (game.lives <= 0) {
1488
+ gameOver();
1489
+ return;
1490
+ } else {
1491
+ resetBallAndPaddle();
1492
+ continue;
1493
+ }
1494
+ }
1495
+
1496
+ playBlockHitSound();
1497
+ createBlockBreakParticle(
1498
+ block.x + block.width/2,
1499
+ block.y + block.height/2,
1500
+ block.color
1501
+ );
1502
+
1503
+ const overlapX = Math.min(
1504
+ ball.x + ball.radius - block.x,
1505
+ block.x + block.width - (ball.x - ball.radius)
1506
+ );
1507
+ const overlapY = Math.min(
1508
+ ball.y + ball.radius - block.y,
1509
+ block.y + block.height - (ball.y - ball.radius)
1510
+ );
1511
+
1512
+ // Determine collision side
1513
+ if (overlapX < overlapY) {
1514
+ ball.dx = -ball.dx;
1515
+ } else {
1516
+ ball.dy = -ball.dy;
1517
+ }
1518
+
1519
+ const isPiercing = ball.piercing ||
1520
+ game.activePowerUps[POWERUP_TYPES.PIERCING_BALL] ||
1521
+ game.activePowerUps[RARE_POWERUP_TYPES.INVINCIBILITY];
1522
+
1523
+ if (!isPiercing) {
1524
+ // Reduce block health or destroy it
1525
+ block.hits--;
1526
+ if (block.hits <= 0) {
1527
+ game.score += 10 * game.level;
1528
+ if (Math.random() < POWERUP_DROP_CHANCE) {
1529
+ spawnPowerUpItem(
1530
+ block.x + block.width / 2,
1531
+ block.y + block.height / 2
1532
+ );
1533
+ }
1534
+ // Update combo
1535
+ updateCombo();
1536
+ } else {
1537
+ game.score += 5 * game.level;
1538
+ }
1539
+ }
1540
+ }
1541
+ }
1542
+
1543
+ // Check if ball is lost
1544
+ if (ball.y > canvas.height + ball.radius * 2) {
1545
+ if (ball.isPrimary) {
1546
+ game.lives--;
1547
+ playLoseLifeSound();
1548
+ if (game.lives <= 0) {
1549
+ gameOver();
1550
+ return;
1551
+ } else {
1552
+ resetBallAndPaddle();
1553
+ }
1554
+ } else {
1555
+ game.balls.splice(i, 1);
1556
+ }
1557
+ }
1558
+ }
1559
+ }
1560
+
1561
+ function updateCombo() {
1562
+ const now = Date.now();
1563
+ // Reset combo if too much time passed since last hit
1564
+ if (now - game.lastHitTime > COMBO_TIMEOUT) {
1565
+ game.combo = 0;
1566
+ }
1567
+ game.lastHitTime = now;
1568
+ game.combo++;
1569
+
1570
+ // Update UI
1571
+ document.getElementById('combo').textContent = `${game.combo}x`;
1572
+
1573
+ // Visual effect for high combos
1574
+ if (game.combo >= 5) {
1575
+ playComboSound(game.combo);
1576
+ const comboMeter = document.getElementById('comboMeter');
1577
+ comboMeter.textContent = `${game.combo}x COMBO!`;
1578
+ comboMeter.style.opacity = '1';
1579
+ comboMeter.style.transform = 'translate(-50%, -50%) scale(1.2)';
1580
+ comboMeter.style.color = game.combo >= 10 ? '#ff69b4' : '#ffff00';
1581
+
1582
+ setTimeout(() => {
1583
+ comboMeter.style.opacity = '0';
1584
+ comboMeter.style.transform = 'translate(-50%, -50%) scale(0.8)';
1585
+ }, 1000);
1586
+ }
1587
+
1588
+ // Score multiplier
1589
+ if (game.combo >= 3) {
1590
+ const points = Math.min(10, Math.floor(game.combo / 2)) * game.level;
1591
+ game.score += points;
1592
+ addBubbleText(
1593
+ `+${points}`,
1594
+ canvas.width / 2,
1595
+ canvas.height / 3,
1596
+ '#ffff00',
1597
+ 18 + Math.min(10, game.combo)
1598
+ );
1599
+ }
1600
+ }
1601
+
1602
+ function updatePaddle() {
1603
+ if (game.activePowerUps[RARE_POWERUP_TYPES.TIME_STOP]) {
1604
+ return; // Time is stopped, paddle doesn't move
1605
+ }
1606
+
1607
+ // Move paddle towards mouse with easing
1608
+ const targetX = mouseX - paddle.width / 2;
1609
+ paddle.x += (targetX - paddle.x) * 0.2;
1610
+
1611
+ // Keep paddle within bounds
1612
+ paddle.x = Math.max(0, Math.min(paddle.x, canvas.width - paddle.width));
1613
+
1614
+ // Position attached balls
1615
+ game.balls.forEach(ball => {
1616
+ if (ball.attached) {
1617
+ ball.x = paddle.x + paddle.width / 2;
1618
+ ball.y = paddle.y - ball.radius - 1;
1619
+ }
1620
+ });
1621
+
1622
+ // Fire laser if key pressed
1623
+ if (game.activePowerUps[POWERUP_TYPES.LASER_PADDLE] && Math.random() < 0.03) {
1624
+ fireLaser();
1625
+ }
1626
+ }
1627
+
1628
+ // --- Game State Management ---
1629
+ function resetBallAndPaddle() {
1630
+ // Only reset primary ball
1631
+ const primaryBall = game.balls.find(b => b.isPrimary);
1632
+ if (primaryBall) {
1633
+ primaryBall.x = paddle.x + paddle.width / 2;
1634
+ primaryBall.y = paddle.y - 20;
1635
+ primaryBall.dx = 0;
1636
+ primaryBall.dy = 0;
1637
+ primaryBall.attached = true;
1638
+ primaryBall.piercing = false;
1639
+ }
1640
+
1641
+ // Remove non-primary balls
1642
+ game.balls = game.balls.filter(b => b.isPrimary);
1643
+
1644
+ // Reset paddle width if no wide paddle active
1645
+ if (!game.activePowerUps[POWERUP_TYPES.WIDE_PADDLE]) {
1646
+ paddle.width = PADDLE_DEFAULT_WIDTH;
1647
+ }
1648
+
1649
+ // Reset combo
1650
+ game.combo = 0;
1651
+ document.getElementById('combo').textContent = '0x';
1652
+ }
1653
+
1654
+ function nextLevel() {
1655
+ playLevelUpSound();
1656
+ // Show level up animation
1657
+ addBubbleText(
1658
+ `LEVEL ${game.level + 1}!`,
1659
+ canvas.width / 2,
1660
+ canvas.height / 2,
1661
+ '#00ff00',
1662
+ 36
1663
+ );
1664
+ game.level++;
1665
+ if (game.level > 10) {
1666
+ gameWon();
1667
+ return;
1668
+ }
1669
+
1670
+ // Speed increases
1671
+ const speedIncrease = game.difficulty === 'hard' ? 0.8 : 0.5;
1672
+ game.balls.forEach(ball => {
1673
+ ball.speed += speedIncrease;
1674
+ });
1675
+
1676
+ // Reset paddle width if not active
1677
+ if (!game.activePowerUps[POWERUP_TYPES.WIDE_PADDLE]) {
1678
+ paddle.width = PADDLE_DEFAULT_WIDTH;
1679
+ }
1680
+
1681
+ // Clear regular powerups
1682
+ for (const [type, data] of Object.entries(game.activePowerUps)) {
1683
+ if (!(type in RARE_POWERUP_TYPES)) {
1684
+ deactivatePowerUp(type);
1685
+ delete game.activePowerUps[type];
1686
+ }
1687
+ }
1688
+
1689
+ game.powerUpsOnScreen = [];
1690
+ resetBallAndPaddle();
1691
+ createBlocks();
1692
+ }
1693
+
1694
+ function gameOver() {
1695
+ game.running = false;
1696
+ document.getElementById('finalScore').textContent = game.score;
1697
+
1698
+ // Check high score
1699
+ if (game.score > game.highScore) {
1700
+ game.highScore = game.score;
1701
+ localStorage.setItem('highScore', game.highScore);
1702
+ document.getElementById('highScoreText').style.display = 'block';
1703
+ } else {
1704
+ document.getElementById('highScoreText').style.display = 'none';
1705
+ }
1706
+
1707
+ document.getElementById('gameOver').style.display = 'block';
1708
+ playGameOverSound();
1709
+ }
1710
+
1711
+ function gameWon() {
1712
+ game.running = false;
1713
+ document.getElementById('winScore').textContent = game.score;
1714
+
1715
+ // Check high score
1716
+ if (game.score > game.highScore) {
1717
+ game.highScore = game.score;
1718
+ localStorage.setItem('highScore', game.highScore);
1719
+ document.getElementById('winHighScoreText').textContent = 'New High Score!';
1720
+ } else {
1721
+ document.getElementById('winHighScoreText').textContent = `High Score: ${game.highScore}`;
1722
+ }
1723
+
1724
+ document.getElementById('gameWon').style.display = 'block';
1725
+ playGameWonSound();
1726
+ }
1727
+
1728
+ function initGame(difficulty = 'normal') {
1729
+ audio.init();
1730
+ game.score = 0;
1731
+ game.lives = difficulty === 'hard' ? 2 : 3;
1732
+ game.level = 1;
1733
+ game.paused = false;
1734
+ game.running = true;
1735
+ game.difficulty = difficulty;
1736
+ game.particles = [];
1737
+ game.fireParticles = [];
1738
+ game.powerUpsOnScreen = [];
1739
+ game.activePowerUps = {};
1740
+ game.combo = 0;
1741
+ game.balls = [];
1742
+ game.lasers = [];
1743
+ game.bubbleTexts = [];
1744
+ game.blackHole = null;
1745
+ paddle.width = PADDLE_DEFAULT_WIDTH;
1746
+ initPrimaryBall();
1747
+ createBlocks();
1748
+ createBackgroundElements();
1749
+ document.getElementById('startScreen').style.display = 'none';
1750
+ document.getElementById('gameOver').style.display = 'none';
1751
+ document.getElementById('gameWon').style.display = 'none';
1752
+ document.getElementById('pauseMessage').style.display = 'none';
1753
+ gameLoop();
1754
+ }
1755
+
1756
+ // --- Game Loop ---
1757
+ function gameLoop() {
1758
+ if (!game.running) return;
1759
+ if (game.paused) {
1760
+ requestAnimationFrame(gameLoop);
1761
+ return;
1762
+ }
1763
+
1764
+ // Clear canvas
1765
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1766
+
1767
+ // Update game elements
1768
+ updateBackgroundFish();
1769
+ updateBackgroundBubbles();
1770
+ updatePaddle();
1771
+ updateBalls();
1772
+ updateLasers();
1773
+ updateParticles();
1774
+ updateFireParticles();
1775
+ createFireParticles();
1776
+ updatePowerUpsOnScreen();
1777
+ updateActivePowerUps();
1778
+ drawBlackHole();
1779
+
1780
+ // Draw game elements
1781
+ drawBackgroundFish();
1782
+ drawBackgroundBubbles();
1783
+ drawBubbleTexts();
1784
+ drawBlocks();
1785
+ drawPowerUpsOnScreen();
1786
+ drawLasers();
1787
+ drawBlackHole();
1788
+ drawPaddle();
1789
+ drawBalls();
1790
+ drawFireParticles();
1791
+ drawParticles();
1792
+
1793
+ // Update UI
1794
+ document.getElementById('score').textContent = game.score;
1795
+ document.getElementById('lives').textContent = game.lives;
1796
+ document.getElementById('level').textContent = game.level;
1797
+
1798
+ // Check for level completion
1799
+ if (blocks.length === 0 && game.running) {
1800
+ setTimeout(nextLevel, 1500);
1801
+ }
1802
+
1803
+ requestAnimationFrame(gameLoop);
1804
+ }
1805
+
1806
+ function updateLasers() {
1807
+ // Laser update is handled in drawLasers()
1808
+ }
1809
+
1810
+ function updateBlackHole() {
1811
+ // Black hole update is handled in drawBlackHole()
1812
+ }
1813
+
1814
+ function drawBlackHole() {
1815
+ // Black hole drawing is handled in drawLasers()
1816
+ }
1817
+
1818
+ function updateParticles() {
1819
+ // Particle update logic here
1820
+ }
1821
+
1822
+ function drawParticles() {
1823
+ // Particle drawing logic here
1824
+ }
1825
+
1826
+ function updateFireParticles() {
1827
+ // Fire particle update logic here
1828
+ }
1829
+
1830
+ function drawFireParticles() {
1831
+ // Fire particle drawing logic here
1832
+ }
1833
+
1834
+ function drawPowerUpIndicators() {
1835
+ // Powerup indicators drawing logic here
1836
+ }
1837
+
1838
+ function updateCombo() {
1839
+ // Combo update logic here
1840
+ }
1841
+
1842
+ function drawBlackHole() {
1843
+ if (!game.blackHole) return;
1844
+
1845
+ ctx.save();
1846
+ // Outer glow
1847
+ const outerRadius = game.blackHole.radius * 1.5;
1848
+ const gradient = ctx.createRadialGradient(
1849
+ game.blackHole.x, game.blackHole.y, 0,
1850
+ game.blackHole.x, game.blackHole.y, outerRadius
1851
+ );
1852
+
1853
+ gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
1854
+ gradient.addColorStop(0.8, 'rgba(70, 0, 100, 0.5)');
1855
+ gradient.addColorStop(1, 'rgba(0, 255, 255, 0.8)');
1856
+ ctx.fillStyle = gradient;
1857
+ ctx.beginPath();
1858
+ ctx.arc(game.blackHole.x, game.blackHole.y, outerRadius, 0, Math.PI * 2);
1859
+ ctx.fill();
1860
+ ctx.restore();
1861
+
1862
+ // Update black hole attraction
1863
+ game.blackHole.timeLeft--;
1864
+ if (game.blackHole.timeLeft <= 0) {
1865
+ game.blackHole = null;
1866
+ return;
1867
+ }
1868
+
1869
+ // Attract balls and blocks
1870
+ game.balls.forEach(ball => {
1871
+ if (ball.attached) return;
1872
+ const dx = game.blackHole.x - ball.x;
1873
+ const dy = game.blackHole.y - ball.y;
1874
+ const distance = Math.sqrt(dx * dx + dy * dy);
1875
+
1876
+ if (distance < 200) {
1877
+ const force = (200 - distance) / 20;
1878
+ ball.dx += dx / distance * force;
1879
+ ball.dy += dy / distance * force;
1880
+ }
1881
+ });
1882
+
1883
+ blocks.forEach((block, index) => {
1884
+ const blockCenterX = block.x + block.width / 2;
1885
+ const blockCenterY = block.y + block.height / 2;
1886
+ const dx = game.blackHole.x - blockCenterX;
1887
+ const dy = game.blackHole.y - blockCenterY;
1888
+ const distance = Math.sqrt(dx * dx + dy * dy);
1889
+
1890
+ if (distance < 150) {
1891
+ // Destroy block if too close
1892
+ if (distance < game.blackHole.radius + 10) {
1893
+ createBlockBreakParticle(
1894
+ blockCenterX, blockCenterY,
1895
+ '#aa00ff', 20
1896
+ );
1897
+ blocks.splice(index, 1);
1898
+ game.score += 20 * game.level;
1899
+ updateCombo();
1900
+ } else {
1901
+ // Draw block being pulled
1902
+ const angle = Math.atan2(dy, dx);
1903
+ ctx.save();
1904
+ ctx.translate(block.x, block.y);
1905
+ ctx.rotate(angle);
1906
+ ctx.globalAlpha = 0.6;
1907
+ ctx.fillStyle = '#aa00ff';
1908
+ ctx.fillRect(
1909
+ block.width * 0.3,
1910
+ -2,
1911
+ block.width * 0.5 * (distance / 150),
1912
+ 4
1913
+ );
1914
+ ctx.restore();
1915
+ }
1916
+ }
1917
+ });
1918
+ }
1919
+
1920
+ // Event listeners
1921
+ canvas.addEventListener('mousemove', (e) => {
1922
+ if (!game.paused) {
1923
+ const rect = canvas.getBoundingClientRect();
1924
+ mouseX = e.clientX - rect.left;
1925
+ }
1926
+ });
1927
+
1928
+ canvas.addEventListener('click', () => {
1929
+ if (!game.running || game.paused) return;
1930
+
1931
+ // Launch all attached balls
1932
+ game.balls.forEach(ball => {
1933
+ if (ball.attached) {
1934
+ const angle = (Math.random() - 0.5) * Math.PI * 0.3;
1935
+ ball.dx = ball.speed * Math.sin(angle);
1936
+ ball.dy = -ball.speed * Math.cos(angle);
1937
+ ball.attached = false;
1938
+
1939
+ // Enable piercing for first 0.5s
1940
+ if (game.activePowerUps[POWERUP_TYPES.PIERCING_BALL] && !game.activePowerUps[POWERUP_TYPES.PIERCING_BALL].endTime) {
1941
+ game.activePowerUps[POWERUP_TYPES.PIERCING_BALL] = {
1942
+ endTime: Date.now() + 500,
1943
+ isRare: false
1944
+ };
1945
+ }
1946
+ }
1947
+ });
1948
+
1949
+ // Fire laser if powerup active
1950
+ if (game.activePowerUps[POWERUP_TYPES.LASER_PADDLE]) {
1951
+ fireLaser();
1952
+ }
1953
+ });
1954
+
1955
+ window.addEventListener('keydown', (e) => {
1956
+ const key = e.key.toLowerCase();
1957
+ // Pause/resume game
1958
+ if (key === 'p' && game.running) {
1959
+ game.paused = !game.paused;
1960
+ document.getElementById('pauseMessage').style.display = game.paused ? 'block' : 'none';
1961
+ }
1962
+ // Mute/unmute
1963
+ if (key === 'm') {
1964
+ const isMuted = audio.toggleMute();
1965
+ addBubbleText(isMuted ? 'MUTED' : 'UNMUTED', canvas.width / 2, canvas.height / 2, isMuted ? '#ff0000' : '#00ff00', 24);
1966
+ }
1967
+ });
1968
+
1969
+ // Initial setup
1970
+ document.getElementById('highScoreText').textContent = `High Score: ${game.highScore}`;
1971
+ document.getElementById('winHighScoreText').textContent = game.score > game.highScore ? 'New High Score!' : `High Score: ${game.highScore}`;
1972
+
1973
+ // Resize canvas based on window size
1974
+ function resizeCanvas() {
1975
+ const container = document.querySelector('.game-container');
1976
+ const padding = 20;
1977
+ const availableHeight = window.innerHeight - padding * 2;
1978
+ const aspectRatio = 800 / 600;
1979
+ const newWidth = Math.min(window.innerWidth - 40, availableHeight * aspectRatio);
1980
+ const newHeight = newWidth / aspectRatio;
1981
+
1982
+ canvas.width = newWidth;
1983
+ canvas.height = newHeight;
1984
+ canvas.style.maxWidth = '100vw';
1985
+ canvas.style.maxHeight = '100vh';
1986
+ canvas.style.width = '100%';
1987
+ canvas.style.height = '100%';
1988
+
1989
+ // Reset game after resizing
1990
+ if (game.running) {
1991
+ initPrimaryBall();
1992
+ createBlocks();
1993
+ createBackgroundElements();
1994
+ }
1995
+ }
1996
+
1997
+ // Handle window resize
1998
+ window.addEventListener('resize', resizeCanvas);
1999
+ window.addEventListener('load', resizeCanvas);
2000
+ window.addEventListener('orientationchange', () => {
2001
+ resizeCanvas();
2002
+ if (game.running) {
2003
+ initPrimaryBall();
2004
+ createBlocks();
2005
+ createBackgroundElements();
2006
+ }
2007
+ });
2008
+
2009
+ // Initialize game
2010
+ document.getElementById('startScreen').style.display = 'flex';
2011
+ </script>
2012
+ </body>
2013
+ </html>