Spaces:
Running
Running
Add 1 files
Browse files- index.html +965 -1387
index.html
CHANGED
|
@@ -3,1523 +3,1101 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>
|
|
|
|
| 7 |
<style>
|
| 8 |
-
/* Google Fonts Import */
|
| 9 |
-
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Ubuntu+Mono:wght@400;700&display=swap');
|
| 10 |
-
|
| 11 |
-
/* Base Styles */
|
| 12 |
:root {
|
| 13 |
-
--primary: #
|
| 14 |
-
--secondary: #
|
| 15 |
-
--
|
| 16 |
-
--
|
| 17 |
-
--light: #
|
| 18 |
-
--
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
--scoreboard: #333;
|
| 22 |
-
}
|
| 23 |
-
|
| 24 |
* {
|
| 25 |
margin: 0;
|
| 26 |
padding: 0;
|
| 27 |
box-sizing: border-box;
|
|
|
|
| 28 |
}
|
| 29 |
-
|
| 30 |
body {
|
| 31 |
-
font-family: 'Ubuntu Mono', monospace;
|
| 32 |
background-color: var(--dark);
|
| 33 |
color: var(--light);
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
position: relative;
|
| 50 |
-
width: 800px;
|
| 51 |
-
height: 500px;
|
| 52 |
-
background-color: var(--dark);
|
| 53 |
-
border: 4px solid var(--light);
|
| 54 |
-
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
| 55 |
-
border-radius: 8px;
|
| 56 |
overflow: hidden;
|
| 57 |
}
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
| 61 |
width: 100%;
|
| 62 |
height: 100%;
|
| 63 |
-
background
|
|
|
|
| 64 |
}
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
top: 20px;
|
| 70 |
-
width: 100%;
|
| 71 |
-
display: flex;
|
| 72 |
-
justify-content: space-between;
|
| 73 |
-
padding: 0 30px;
|
| 74 |
-
z-index: 10;
|
| 75 |
-
font-family: 'Press Start 2P', cursive;
|
| 76 |
-
font-size: 20px;
|
| 77 |
-
color: var(--light);
|
| 78 |
-
text-shadow: 0 0 5px var(--primary);
|
| 79 |
}
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
| 86 |
}
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
bottom:
|
| 92 |
-
|
| 93 |
-
transform: translateX(-50%);
|
| 94 |
-
display: flex;
|
| 95 |
-
gap: 10px;
|
| 96 |
-
z-index: 10;
|
| 97 |
}
|
| 98 |
-
|
| 99 |
-
.
|
| 100 |
-
width: 30px;
|
| 101 |
-
height: 30px;
|
| 102 |
-
border-radius: 50%;
|
| 103 |
-
border: 2px solid var(--light);
|
| 104 |
display: flex;
|
| 105 |
-
align-items: center;
|
| 106 |
justify-content: center;
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
opacity: 0;
|
| 110 |
-
transition: opacity 0.3s;
|
| 111 |
}
|
| 112 |
-
|
| 113 |
-
.
|
| 114 |
-
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
}
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
}
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
position: absolute;
|
| 127 |
top: 0;
|
| 128 |
left: 0;
|
| 129 |
width: 100%;
|
| 130 |
height: 100%;
|
| 131 |
-
background
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
font-family: 'Press Start 2P', cursive;
|
| 138 |
text-align: center;
|
| 139 |
-
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
.
|
| 143 |
-
font-size:
|
| 144 |
-
color:
|
| 145 |
-
margin-bottom:
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
font-size: 1.5rem;
|
| 157 |
color: var(--accent);
|
| 158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
}
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
color: var(--light);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
border: none;
|
| 165 |
-
|
| 166 |
-
font-size: 1.
|
| 167 |
-
font-family: 'Press Start 2P', cursive;
|
| 168 |
cursor: pointer;
|
| 169 |
-
|
| 170 |
-
|
| 171 |
transition: all 0.3s;
|
| 172 |
}
|
| 173 |
-
|
| 174 |
-
.
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
| 178 |
}
|
| 179 |
-
|
| 180 |
-
.
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
}
|
| 183 |
-
|
| 184 |
-
.
|
| 185 |
-
|
|
|
|
| 186 |
}
|
| 187 |
-
|
| 188 |
-
.
|
| 189 |
-
|
| 190 |
}
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
}
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
border-radius: 10px;
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
}
|
| 211 |
-
|
| 212 |
-
.
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
margin-bottom: 10px;
|
| 216 |
}
|
| 217 |
-
|
| 218 |
-
.
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
}
|
| 223 |
-
|
| 224 |
-
.
|
| 225 |
-
|
| 226 |
font-weight: bold;
|
|
|
|
| 227 |
}
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
background-color: rgba(20, 33, 61, 0.9);
|
| 236 |
-
border: 3px solid var(--light);
|
| 237 |
-
padding: 15px;
|
| 238 |
-
border-radius: 10px;
|
| 239 |
-
z-index: 20;
|
| 240 |
-
text-align: center;
|
| 241 |
-
max-width: 500px;
|
| 242 |
-
animation: modifierFadeIn 0.5s;
|
| 243 |
}
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
to { opacity: 1; transform: translate(-50%, -50%); }
|
| 248 |
}
|
| 249 |
-
|
| 250 |
-
.
|
| 251 |
-
|
| 252 |
-
color: var(--success);
|
| 253 |
-
margin-bottom: 10px;
|
| 254 |
}
|
| 255 |
-
|
| 256 |
-
.
|
| 257 |
-
font-
|
| 258 |
-
|
|
|
|
| 259 |
}
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
transform: translate(-50%, -50%);
|
| 267 |
-
background-color: rgba(76, 201, 240, 0.2);
|
| 268 |
-
border: 2px solid var(--light);
|
| 269 |
-
padding: 10px 20px;
|
| 270 |
-
border-radius: 5px;
|
| 271 |
-
z-index: 20;
|
| 272 |
-
text-align: center;
|
| 273 |
-
opacity: 0;
|
| 274 |
-
transition: all 0.3s;
|
| 275 |
}
|
| 276 |
-
|
| 277 |
-
.
|
| 278 |
-
|
| 279 |
-
|
|
|
|
|
|
|
| 280 |
}
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
}
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
.run-stats {
|
| 290 |
display: flex;
|
| 291 |
flex-wrap: wrap;
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
margin: 20px 0;
|
| 295 |
}
|
| 296 |
-
|
| 297 |
-
.
|
| 298 |
-
background
|
| 299 |
-
padding:
|
| 300 |
-
border-radius:
|
| 301 |
-
|
| 302 |
-
min-width: 150px;
|
| 303 |
}
|
| 304 |
-
|
| 305 |
-
.
|
| 306 |
-
|
| 307 |
-
|
|
|
|
|
|
|
| 308 |
}
|
| 309 |
-
|
| 310 |
-
.
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
|
|
|
| 314 |
}
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
|
|
|
|
|
|
| 318 |
display: flex;
|
| 319 |
-
|
| 320 |
-
justify-content: center;
|
| 321 |
-
gap: 15px;
|
| 322 |
-
margin-top: 20px;
|
| 323 |
-
max-width: 600px;
|
| 324 |
-
}
|
| 325 |
-
|
| 326 |
-
.upgrade-item {
|
| 327 |
-
background-color: rgba(20, 33, 61, 0.8);
|
| 328 |
-
border: 2px solid var(--accent);
|
| 329 |
-
border-radius: 5px;
|
| 330 |
-
padding: 15px;
|
| 331 |
-
width: 180px;
|
| 332 |
-
cursor: pointer;
|
| 333 |
-
transition: all 0.3s;
|
| 334 |
}
|
| 335 |
-
|
| 336 |
-
.
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
border-color: var(--success);
|
| 340 |
-
}
|
| 341 |
-
|
| 342 |
-
.upgrade-item.purchased {
|
| 343 |
-
border-color: var(--success);
|
| 344 |
-
background-color: rgba(74, 223, 134, 0.1);
|
| 345 |
-
}
|
| 346 |
-
|
| 347 |
-
.upgrade-item.disabled {
|
| 348 |
-
opacity: 0.6;
|
| 349 |
-
cursor: not-allowed;
|
| 350 |
-
}
|
| 351 |
-
|
| 352 |
-
.upgrade-name {
|
| 353 |
-
color: var(--warning);
|
| 354 |
-
margin-bottom: 5px;
|
| 355 |
-
font-size: 0.9rem;
|
| 356 |
}
|
| 357 |
-
|
| 358 |
-
.
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
}
|
| 363 |
-
|
| 364 |
-
.
|
| 365 |
-
|
| 366 |
-
|
|
|
|
| 367 |
}
|
| 368 |
-
|
| 369 |
-
.
|
|
|
|
| 370 |
position: absolute;
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
padding: 8px 15px;
|
| 375 |
-
border-radius: 5px;
|
| 376 |
-
border: 2px solid var(--warning);
|
| 377 |
-
font-family: 'Press Start 2P', cursive;
|
| 378 |
-
font-size: 0.9rem;
|
| 379 |
-
color: var(--warning);
|
| 380 |
-
}
|
| 381 |
-
|
| 382 |
-
/* Responsive */
|
| 383 |
-
@media (max-width: 850px) {
|
| 384 |
-
.game-container {
|
| 385 |
-
width: 95%;
|
| 386 |
-
height: 400px;
|
| 387 |
-
}
|
| 388 |
-
|
| 389 |
-
.title {
|
| 390 |
-
font-size: 2rem;
|
| 391 |
-
}
|
| 392 |
-
|
| 393 |
-
.subtitle {
|
| 394 |
-
font-size: 1.2rem;
|
| 395 |
-
}
|
| 396 |
-
|
| 397 |
-
.btn {
|
| 398 |
-
padding: 10px 20px;
|
| 399 |
-
font-size: 1rem;
|
| 400 |
-
}
|
| 401 |
-
|
| 402 |
-
.opponent-intro, .modifier-indicator {
|
| 403 |
-
width: 90%;
|
| 404 |
-
}
|
| 405 |
-
|
| 406 |
-
.opponent-name {
|
| 407 |
-
font-size: 1.2rem;
|
| 408 |
-
}
|
| 409 |
-
}
|
| 410 |
-
|
| 411 |
-
@media (max-height: 700px) {
|
| 412 |
-
.game-container {
|
| 413 |
-
height: 350px;
|
| 414 |
-
}
|
| 415 |
}
|
| 416 |
-
</style>
|
| 417 |
-
</head>
|
| 418 |
-
<body>
|
| 419 |
-
<div class="game-container">
|
| 420 |
-
<canvas id="gameCanvas"></canvas>
|
| 421 |
-
|
| 422 |
-
<!-- Score UI -->
|
| 423 |
-
<div class="score-board">
|
| 424 |
-
<div class="player-score">YOU: 0</div>
|
| 425 |
-
<div class="opponent-score">CPU: 0</div>
|
| 426 |
-
</div>
|
| 427 |
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
|
|
|
| 434 |
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
|
|
|
| 442 |
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
<div class="stat-value" id="stat-rounds">0</div>
|
| 451 |
-
<div class="stat-label">Opponents Defeated</div>
|
| 452 |
-
</div>
|
| 453 |
-
<div class="stat-item">
|
| 454 |
-
<div class="stat-value" id="stat-score">0</div>
|
| 455 |
-
<div class="stat-label">Total Points</div>
|
| 456 |
-
</div>
|
| 457 |
-
<div class="stat-item">
|
| 458 |
-
<div class="stat-value" id="stat-time">0:00</div>
|
| 459 |
-
<div class="stat-label">Time Played</div>
|
| 460 |
-
</div>
|
| 461 |
-
</div>
|
| 462 |
-
|
| 463 |
-
<p>Earned <span id="earned-currency" style="color: var(--warning);">0</span> upgrade points</p>
|
| 464 |
-
|
| 465 |
-
<button class="btn" id="restartGame">Try Again</button>
|
| 466 |
-
<button class="btn btn-secondary" id="backToMenu">Main Menu</button>
|
| 467 |
-
</div>
|
| 468 |
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
Points: <span id="currency-amount">0</span>
|
| 474 |
-
</div>
|
| 475 |
-
|
| 476 |
-
<div class="upgrades-container">
|
| 477 |
-
<!-- Upgrade items will be added here dynamically -->
|
| 478 |
-
</div>
|
| 479 |
-
|
| 480 |
-
<button class="btn" id="backFromUpgrades">Back</button>
|
| 481 |
-
</div>
|
| 482 |
-
</div>
|
| 483 |
-
|
| 484 |
-
<script>
|
| 485 |
-
// Game Constants
|
| 486 |
-
const GAME_WIDTH = 800;
|
| 487 |
-
const GAME_HEIGHT = 500;
|
| 488 |
-
const PADDLE_WIDTH = 100;
|
| 489 |
-
const PADDLE_HEIGHT = 15;
|
| 490 |
-
const PADDLE_OFFSET = 20;
|
| 491 |
-
const BALL_SIZE = 10;
|
| 492 |
-
const BALL_INITIAL_SPEED = 5;
|
| 493 |
-
const WIN_SCORE = 11;
|
| 494 |
-
const POWERUP_SIZE = 15;
|
| 495 |
-
const POWERUP_SPAWN_CHANCE = 0.02; // 2% chance per frame
|
| 496 |
-
const POWERUP_DURATION = 10000; // 10 seconds
|
| 497 |
-
|
| 498 |
-
// Game Variables
|
| 499 |
-
let canvas, ctx;
|
| 500 |
-
let gameRunning = false;
|
| 501 |
-
let gameOver = false;
|
| 502 |
-
let animationFrameId;
|
| 503 |
-
let runStarted = false;
|
| 504 |
-
let winScore = WIN_SCORE;
|
| 505 |
-
|
| 506 |
-
// Game Objects
|
| 507 |
-
let player = {
|
| 508 |
-
x: GAME_WIDTH / 2 - PADDLE_WIDTH / 2,
|
| 509 |
-
y: GAME_HEIGHT - PADDLE_HEIGHT - PADDLE_OFFSET,
|
| 510 |
-
width: PADDLE_WIDTH,
|
| 511 |
-
height: PADDLE_HEIGHT,
|
| 512 |
-
color: '#4CC9F0',
|
| 513 |
-
speed: 8
|
| 514 |
-
};
|
| 515 |
-
|
| 516 |
-
let opponent = {
|
| 517 |
-
x: GAME_WIDTH / 2 - PADDLE_WIDTH / 2,
|
| 518 |
-
y: PADDLE_OFFSET,
|
| 519 |
-
width: PADDLE_WIDTH,
|
| 520 |
-
height: PADDLE_HEIGHT,
|
| 521 |
-
color: '#FF3E41',
|
| 522 |
-
speed: 5,
|
| 523 |
-
reactionTime: 0.5 // 0-1, where 1 is perfect reaction
|
| 524 |
-
};
|
| 525 |
-
|
| 526 |
-
let ball = {
|
| 527 |
-
x: GAME_WIDTH / 2,
|
| 528 |
-
y: GAME_HEIGHT / 2,
|
| 529 |
-
size: BALL_SIZE,
|
| 530 |
-
color: '#F8F9FA',
|
| 531 |
-
speedX: 0,
|
| 532 |
-
speedY: 0,
|
| 533 |
-
speed: BALL_INITIAL_SPEED,
|
| 534 |
-
active: false,
|
| 535 |
-
trail: []
|
| 536 |
-
};
|
| 537 |
-
|
| 538 |
-
// Power-ups
|
| 539 |
-
let powerups = [];
|
| 540 |
-
let activePowerups = [];
|
| 541 |
-
let powerupTypes = [
|
| 542 |
-
{ id: 'paddleXL', name: 'Paddle XL', color: '#4ADF86', effect: 'Increase paddle size by 50%', duration: POWERUP_DURATION, icon: 'XL' },
|
| 543 |
-
{ id: 'paddleXS', name: 'Paddle XS', color: '#E63946', effect: 'Decrease paddle size by 50%', duration: POWERUP_DURATION, icon: 'XS' },
|
| 544 |
-
{ id: 'speedBoost', name: 'Speed Boost', color: '#F9C846', effect: 'Increase ball speed for opponent', duration: POWERUP_DURATION, icon: '⚡' },
|
| 545 |
-
{ id: 'multiBall', name: 'Multi Ball', color: '#39A0ED', effect: 'Spawn 3 extra balls', duration: POWERUP_DURATION, icon: '③' },
|
| 546 |
-
{ id: 'slowMotion', name: 'Slow Mo', color: '#9B59B6', effect: 'Slow down time', duration: POWERUP_DURATION, icon: '⌛' },
|
| 547 |
-
{ id: 'magnet', name: 'Ball Magnet', color: '#FF7F50', effect: 'Ball gravitates to your paddle', duration: POWERUP_DURATION, icon: '🧲' }
|
| 548 |
-
];
|
| 549 |
-
|
| 550 |
-
// Opponent Types
|
| 551 |
-
let opponents = [
|
| 552 |
-
{
|
| 553 |
-
name: "The Wall",
|
| 554 |
-
description: "A defensive opponent that covers a wide area.",
|
| 555 |
-
width: 150,
|
| 556 |
-
speed: 4,
|
| 557 |
-
reactionTime: 0.7,
|
| 558 |
-
color: "#2ECC71"
|
| 559 |
-
},
|
| 560 |
-
{
|
| 561 |
-
name: "Speed Demon",
|
| 562 |
-
description: "Moves extremely fast but has a smaller paddle.",
|
| 563 |
-
width: 70,
|
| 564 |
-
speed: 9,
|
| 565 |
-
reactionTime: 0.8,
|
| 566 |
-
color: "#E74C3C"
|
| 567 |
-
},
|
| 568 |
-
{
|
| 569 |
-
name: "The Trickster",
|
| 570 |
-
description: "Adjusts reaction time unpredictably.",
|
| 571 |
-
width: 100,
|
| 572 |
-
speed: 6,
|
| 573 |
-
reactionTime: 0.5,
|
| 574 |
-
color: "#9B59B6",
|
| 575 |
-
special: "Changes reaction time randomly during the match"
|
| 576 |
-
},
|
| 577 |
-
{
|
| 578 |
-
name: "The Turtle",
|
| 579 |
-
description: "Very slow but has perfect reaction time.",
|
| 580 |
-
width: 120,
|
| 581 |
-
speed: 3,
|
| 582 |
-
reactionTime: 1.0,
|
| 583 |
-
color: "#F39C12",
|
| 584 |
-
special: "Never misses the ball if it's within reach"
|
| 585 |
-
},
|
| 586 |
-
{
|
| 587 |
-
name: "The Juggernaut",
|
| 588 |
-
description: "Massive paddle but moves slowly.",
|
| 589 |
-
width: 200,
|
| 590 |
-
speed: 2.5,
|
| 591 |
-
reactionTime: 0.6,
|
| 592 |
-
color: "#16A085"
|
| 593 |
-
}
|
| 594 |
-
];
|
| 595 |
-
|
| 596 |
-
// Stage Modifiers
|
| 597 |
-
let stageModifiers = [
|
| 598 |
-
{
|
| 599 |
-
name: "Wind Gusts",
|
| 600 |
-
description: "Random wind affects ball movement.",
|
| 601 |
-
effect: function() {
|
| 602 |
-
let windForce = (Math.random() - 0.5) * 0.5;
|
| 603 |
-
ball.speedX += windForce;
|
| 604 |
-
if (frameCount % 60 === 0) windForce = (Math.random() - 0.5) * 0.5;
|
| 605 |
-
},
|
| 606 |
-
color: "#A2D9CE"
|
| 607 |
-
},
|
| 608 |
-
{
|
| 609 |
-
name: "Gravity Well",
|
| 610 |
-
description: "Ball accelerates downward faster.",
|
| 611 |
-
effect: function() {
|
| 612 |
-
if (ball.speedY > 0) ball.speedY *= 1.02;
|
| 613 |
-
else ball.speedY /= 1.02;
|
| 614 |
-
},
|
| 615 |
-
color: "#95A5A6"
|
| 616 |
-
},
|
| 617 |
-
{
|
| 618 |
-
name: "Rebound Chaos",
|
| 619 |
-
description: "Ball speed increases unpredictably on bounces.",
|
| 620 |
-
effect: function() {
|
| 621 |
-
ball.trail.forEach((pos, i) => {
|
| 622 |
-
ctx.fillStyle = `rgba(76, 201, 240, ${0.2 + (i*0.1)})`;
|
| 623 |
-
ctx.fillRect(pos.x, pos.y, ball.size, ball.size);
|
| 624 |
-
});
|
| 625 |
-
},
|
| 626 |
-
color: "#D2B4DE"
|
| 627 |
-
},
|
| 628 |
-
{
|
| 629 |
-
name: "Multi-Ball Madness",
|
| 630 |
-
description: "Random chance for extra balls to spawn.",
|
| 631 |
-
effect: function() {
|
| 632 |
-
if (Math.random() < 0.005) spawnPowerup('multiBall');
|
| 633 |
-
},
|
| 634 |
-
color: "#3498DB",
|
| 635 |
-
special: "Watch out for unexpected extra balls!"
|
| 636 |
-
}
|
| 637 |
-
];
|
| 638 |
-
|
| 639 |
-
// Game State
|
| 640 |
-
let gameState = {
|
| 641 |
-
playerScore: 0,
|
| 642 |
-
opponentScore: 0,
|
| 643 |
-
currentOpponent: 0,
|
| 644 |
-
currentModifier: null,
|
| 645 |
-
runStartTime: 0,
|
| 646 |
-
runStats: {
|
| 647 |
-
opponentsDefeated: 0,
|
| 648 |
-
totalPoints: 0,
|
| 649 |
-
powerupsCollected: 0,
|
| 650 |
-
roundsPlayed: 0
|
| 651 |
-
},
|
| 652 |
-
powerupsActive: {},
|
| 653 |
-
frameCount: 0
|
| 654 |
-
};
|
| 655 |
-
|
| 656 |
-
// Player progression
|
| 657 |
-
let playerMeta = {
|
| 658 |
-
currency: 0,
|
| 659 |
-
upgrades: {
|
| 660 |
-
paddleStartSize: 0, // 0-2 (0: default, 1: +20%, 2: +40%)
|
| 661 |
-
ballStartSpeed: 0, // 0-2 (0: default, 1: -10%, 2: -20%)
|
| 662 |
-
powerupDuration: 0, // 0-2 (0: default, 1: +25%, 2: +50%)
|
| 663 |
-
extraLife: false, // Start with an extra life
|
| 664 |
-
powerupSlots: 1 // Up to 3
|
| 665 |
-
}
|
| 666 |
-
};
|
| 667 |
-
|
| 668 |
-
// Upgrade definitions
|
| 669 |
-
let upgradeDefinitions = [
|
| 670 |
-
{
|
| 671 |
-
id: 'paddleSize1',
|
| 672 |
-
name: 'Paddle +20%',
|
| 673 |
-
description: 'Start with a 20% larger paddle',
|
| 674 |
-
cost: 100,
|
| 675 |
-
maxLevel: 2,
|
| 676 |
-
apply: function() {
|
| 677 |
-
playerMeta.upgrades.paddleStartSize = Math.min(playerMeta.upgrades.paddleStartSize + 1, this.maxLevel);
|
| 678 |
-
resetPaddleSizes();
|
| 679 |
-
},
|
| 680 |
-
getCurrentLevel: function() {
|
| 681 |
-
return playerMeta.upgrades.paddleStartSize;
|
| 682 |
-
}
|
| 683 |
-
},
|
| 684 |
-
{
|
| 685 |
-
id: 'ballSpeed1',
|
| 686 |
-
name: 'Slower Ball',
|
| 687 |
-
description: 'Start with 10% slower ball speed',
|
| 688 |
-
cost: 150,
|
| 689 |
-
maxLevel: 2,
|
| 690 |
-
apply: function() {
|
| 691 |
-
playerMeta.upgrades.ballStartSpeed = Math.min(playerMeta.upgrades.ballStartSpeed + 1, this.maxLevel);
|
| 692 |
-
},
|
| 693 |
-
getCurrentLevel: function() {
|
| 694 |
-
return playerMeta.upgrades.ballStartSpeed;
|
| 695 |
-
}
|
| 696 |
-
},
|
| 697 |
-
{
|
| 698 |
-
id: 'powerupDuration1',
|
| 699 |
-
name: 'Powerup +25%',
|
| 700 |
-
description: 'Powerups last 25% longer',
|
| 701 |
-
cost: 125,
|
| 702 |
-
maxLevel: 2,
|
| 703 |
-
apply: function() {
|
| 704 |
-
playerMeta.upgrades.powerupDuration = Math.min(playerMeta.upgrades.powerupDuration + 1, this.maxLevel);
|
| 705 |
-
},
|
| 706 |
-
getCurrentLevel: function() {
|
| 707 |
-
return playerMeta.upgrades.powerupDuration;
|
| 708 |
-
}
|
| 709 |
-
},
|
| 710 |
-
{
|
| 711 |
-
id: 'extraLife',
|
| 712 |
-
name: 'Extra Life',
|
| 713 |
-
description: 'Start with one additional chance per run',
|
| 714 |
-
cost: 250,
|
| 715 |
-
maxLevel: 1,
|
| 716 |
-
apply: function() {
|
| 717 |
-
playerMeta.upgrades.extraLife = true;
|
| 718 |
-
},
|
| 719 |
-
getCurrentLevel: function() {
|
| 720 |
-
return playerMeta.upgrades.extraLife ? 1 : 0;
|
| 721 |
-
}
|
| 722 |
-
},
|
| 723 |
-
{
|
| 724 |
-
id: 'powerupSlot2',
|
| 725 |
-
name: 'Powerup Slot #2',
|
| 726 |
-
description: 'Unlock an additional powerup slot',
|
| 727 |
-
cost: 200,
|
| 728 |
-
maxLevel: 1,
|
| 729 |
-
apply: function() {
|
| 730 |
-
playerMeta.upgrades.powerupSlots = Math.min(playerMeta.upgrades.powerupSlots + 1, 3);
|
| 731 |
-
updatePowerupHud();
|
| 732 |
-
},
|
| 733 |
-
getCurrentLevel: function() {
|
| 734 |
-
return playerMeta.upgrades.powerupSlots >= 2 ? 1 : 0;
|
| 735 |
-
}
|
| 736 |
-
},
|
| 737 |
-
{
|
| 738 |
-
id: 'powerupSlot3',
|
| 739 |
-
name: 'Powerup Slot #3',
|
| 740 |
-
description: 'Unlock a third powerup slot',
|
| 741 |
-
cost: 300,
|
| 742 |
-
maxLevel: 1,
|
| 743 |
-
prereq: 'powerupSlot2',
|
| 744 |
-
apply: function() {
|
| 745 |
-
playerMeta.upgrades.powerupSlots = Math.min(playerMeta.upgrades.powerupSlots + 1, 3);
|
| 746 |
-
updatePowerupHud();
|
| 747 |
-
},
|
| 748 |
-
getCurrentLevel: function() {
|
| 749 |
-
return playerMeta.upgrades.powerupSlots >= 3 ? 1 : 0;
|
| 750 |
-
}
|
| 751 |
-
}
|
| 752 |
-
];
|
| 753 |
-
|
| 754 |
-
// DOM Elements
|
| 755 |
-
let mainMenu, gameOverScreen, upgradesScreen;
|
| 756 |
-
let startGameBtn, restartGameBtn, backToMenuBtn, upgradesBtn, backFromUpgradesBtn;
|
| 757 |
-
|
| 758 |
-
// Initialize the game
|
| 759 |
-
function init() {
|
| 760 |
-
canvas = document.getElementById('gameCanvas');
|
| 761 |
-
ctx = canvas.getContext('2d');
|
| 762 |
-
canvas.width = GAME_WIDTH;
|
| 763 |
-
canvas.height = GAME_HEIGHT;
|
| 764 |
-
|
| 765 |
-
// Get DOM elements
|
| 766 |
-
mainMenu = document.getElementById('mainMenu');
|
| 767 |
-
gameOverScreen = document.getElementById('gameOver');
|
| 768 |
-
upgradesScreen = document.getElementById('upgradesScreen');
|
| 769 |
-
|
| 770 |
-
startGameBtn = document.getElementById('startGame');
|
| 771 |
-
restartGameBtn = document.getElementById('restartGame');
|
| 772 |
-
backToMenuBtn = document.getElementById('backToMenu');
|
| 773 |
-
upgradesBtn = document.getElementById('upgradesBtn');
|
| 774 |
-
backFromUpgradesBtn = document.getElementById('backFromUpgrades');
|
| 775 |
-
|
| 776 |
-
// Event listeners
|
| 777 |
-
startGameBtn.addEventListener('click', startGame);
|
| 778 |
-
restartGameBtn.addEventListener('click', startGame);
|
| 779 |
-
backToMenuBtn.addEventListener('click', showMainMenu);
|
| 780 |
-
upgradesBtn.addEventListener('click', showUpgradesScreen);
|
| 781 |
-
backFromUpgradesBtn.addEventListener('click', showMainMenu);
|
| 782 |
-
|
| 783 |
-
// Load saved data
|
| 784 |
-
loadGame();
|
| 785 |
-
|
| 786 |
-
// Draw initial state
|
| 787 |
-
draw();
|
| 788 |
-
|
| 789 |
-
// Set up keyboard controls
|
| 790 |
-
document.addEventListener('keydown', keyDownHandler);
|
| 791 |
-
document.addEventListener('keyup', keyUpHandler);
|
| 792 |
-
|
| 793 |
-
// Initialize powerup HUD
|
| 794 |
-
updatePowerupHud();
|
| 795 |
-
}
|
| 796 |
-
|
| 797 |
-
// Load game state from localStorage
|
| 798 |
-
function loadGame() {
|
| 799 |
-
const savedGame = localStorage.getItem('paddleboundSave');
|
| 800 |
-
if (savedGame) {
|
| 801 |
-
try {
|
| 802 |
-
const savedData = JSON.parse(savedGame);
|
| 803 |
-
playerMeta.currency = savedData.currency || 0;
|
| 804 |
-
playerMeta.upgrades = savedData.upgrades || {
|
| 805 |
-
paddleStartSize: 0,
|
| 806 |
-
ballStartSpeed: 0,
|
| 807 |
-
powerupDuration: 0,
|
| 808 |
-
extraLife: false,
|
| 809 |
-
powerupSlots: 1
|
| 810 |
-
};
|
| 811 |
-
|
| 812 |
-
// Update currency display
|
| 813 |
-
updateCurrencyDisplay();
|
| 814 |
-
} catch (e) {
|
| 815 |
-
console.error("Failed to load save data:", e);
|
| 816 |
-
}
|
| 817 |
-
}
|
| 818 |
}
|
| 819 |
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
const saveData = {
|
| 823 |
-
currency: playerMeta.currency,
|
| 824 |
-
upgrades: playerMeta.upgrades
|
| 825 |
-
};
|
| 826 |
-
localStorage.setItem('paddleboundSave', JSON.stringify(saveData));
|
| 827 |
-
}
|
| 828 |
-
|
| 829 |
-
// Reset game state for a new run
|
| 830 |
-
function resetGame() {
|
| 831 |
-
gameState = {
|
| 832 |
-
playerScore: 0,
|
| 833 |
-
opponentScore: 0,
|
| 834 |
-
currentOpponent: 0,
|
| 835 |
-
currentModifier: null,
|
| 836 |
-
runStartTime: Date.now(),
|
| 837 |
-
runStats: {
|
| 838 |
-
opponentsDefeated: 0,
|
| 839 |
-
totalPoints: 0,
|
| 840 |
-
powerupsCollected: 0,
|
| 841 |
-
roundsPlayed: 0
|
| 842 |
-
},
|
| 843 |
-
powerupsActive: {},
|
| 844 |
-
frameCount: 0
|
| 845 |
-
};
|
| 846 |
-
|
| 847 |
-
powerups = [];
|
| 848 |
-
activePowerups = [];
|
| 849 |
-
ball.trail = [];
|
| 850 |
-
|
| 851 |
-
// Reset player and opponent
|
| 852 |
-
resetPaddleSizes();
|
| 853 |
-
|
| 854 |
-
// Apply upgrades
|
| 855 |
-
if (playerMeta.upgrades.paddleStartSize > 0) {
|
| 856 |
-
player.width = PADDLE_WIDTH * (1 + 0.2 * playerMeta.upgrades.paddleStartSize);
|
| 857 |
-
opponent.width = PADDLE_WIDTH * (1 + 0.2 * playerMeta.upgrades.paddleStartSize);
|
| 858 |
-
}
|
| 859 |
-
|
| 860 |
-
// Position player and opponent
|
| 861 |
-
player.x = GAME_WIDTH / 2 - player.width / 2;
|
| 862 |
-
player.y = GAME_HEIGHT - PADDLE_HEIGHT - PADDLE_OFFSET;
|
| 863 |
-
|
| 864 |
-
opponent.x = GAME_WIDTH / 2 - opponent.width / 2;
|
| 865 |
-
opponent.y = PADDLE_OFFSET;
|
| 866 |
-
|
| 867 |
-
// Setup first opponent
|
| 868 |
-
setupOpponent(gameState.currentOpponent);
|
| 869 |
-
|
| 870 |
-
// Setup stage modifier
|
| 871 |
-
if (Math.random() > 0.3) { // 70% chance to have a modifier
|
| 872 |
-
gameState.currentModifier = stageModifiers[Math.floor(Math.random() * stageModifiers.length)];
|
| 873 |
-
showModifier(gameState.currentModifier);
|
| 874 |
-
}
|
| 875 |
-
|
| 876 |
-
// Reset ball
|
| 877 |
-
resetBall();
|
| 878 |
-
|
| 879 |
-
// Start with ball inactive (waits for player serve)
|
| 880 |
-
ball.active = false;
|
| 881 |
}
|
| 882 |
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 886 |
}
|
| 887 |
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
opponent = {
|
| 893 |
-
...opponent,
|
| 894 |
-
width: opponentType.width,
|
| 895 |
-
speed: opponentType.speed,
|
| 896 |
-
reactionTime: opponentType.reactionTime,
|
| 897 |
-
color: opponentType.color,
|
| 898 |
-
name: opponentType.name,
|
| 899 |
-
description: opponentType.description,
|
| 900 |
-
special: opponentType.special
|
| 901 |
-
};
|
| 902 |
-
|
| 903 |
-
opponent.x = GAME_WIDTH / 2 - opponent.width / 2;
|
| 904 |
-
opponent.y = PADDLE_OFFSET;
|
| 905 |
-
|
| 906 |
-
// Show opponent introduction
|
| 907 |
-
showOpponentIntro(opponentType);
|
| 908 |
-
}
|
| 909 |
-
|
| 910 |
-
// Show opponent introduction
|
| 911 |
-
function showOpponentIntro(opponent) {
|
| 912 |
-
const introElement = document.createElement('div');
|
| 913 |
-
introElement.className = 'opponent-intro';
|
| 914 |
-
introElement.innerHTML = `
|
| 915 |
-
<div class="opponent-name">${opponent.name}</div>
|
| 916 |
-
<div class="opponent-description">${opponent.description}</div>
|
| 917 |
-
${opponent.special ? `<div class="opponent-special">Special: ${opponent.special}</div>` : ''}
|
| 918 |
-
<button class="btn" id="startRound" style="margin-top: 15px;">Start Round</button>
|
| 919 |
-
`;
|
| 920 |
-
|
| 921 |
-
document.querySelector('.game-container').appendChild(introElement);
|
| 922 |
-
|
| 923 |
-
document.getElementById('startRound').addEventListener('click', () => {
|
| 924 |
-
introElement.remove();
|
| 925 |
-
ball.active = true;
|
| 926 |
-
});
|
| 927 |
}
|
| 928 |
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
const modifierElement = document.createElement('div');
|
| 932 |
-
modifierElement.className = 'modifier-indicator';
|
| 933 |
-
modifierElement.innerHTML = `
|
| 934 |
-
<div class="modifier-title">Stage Modifier: ${modifier.name}</div>
|
| 935 |
-
<div class="modifier-effect">${modifier.description}</div>
|
| 936 |
-
${modifier.special ? `<div class="opponent-special">Effect: ${modifier.special}</div>` : ''}
|
| 937 |
-
<button class="btn" id="continueGame" style="margin-top: 15px;">Continue</button>
|
| 938 |
-
`;
|
| 939 |
-
|
| 940 |
-
document.querySelector('.game-container').appendChild(modifierElement);
|
| 941 |
-
|
| 942 |
-
document.getElementById('continueGame').addEventListener('click', () => {
|
| 943 |
-
modifierElement.remove();
|
| 944 |
-
if (!ball.active) {
|
| 945 |
-
// If the ball was waiting to be served
|
| 946 |
-
ball.active = true;
|
| 947 |
-
}
|
| 948 |
-
});
|
| 949 |
}
|
| 950 |
|
| 951 |
-
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
|
| 959 |
-
gameOver = false;
|
| 960 |
-
|
| 961 |
-
// Reset scores
|
| 962 |
-
gameState.playerScore = 0;
|
| 963 |
-
gameState.opponentScore = 0;
|
| 964 |
-
gameState.currentOpponent = 0;
|
| 965 |
-
|
| 966 |
-
updateScores();
|
| 967 |
-
|
| 968 |
-
// Start game loop
|
| 969 |
-
if (animationFrameId) {
|
| 970 |
-
cancelAnimationFrame(animationFrameId);
|
| 971 |
-
}
|
| 972 |
-
|
| 973 |
-
gameLoop();
|
| 974 |
}
|
| 975 |
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
update();
|
| 981 |
-
draw();
|
| 982 |
-
|
| 983 |
-
animationFrameId = requestAnimationFrame(gameLoop);
|
| 984 |
}
|
| 985 |
|
| 986 |
-
|
| 987 |
-
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
gameState.frameCount++;
|
| 991 |
-
|
| 992 |
-
// Apply stage modifier if active
|
| 993 |
-
if (gameState.currentModifier) {
|
| 994 |
-
gameState.currentModifier.effect();
|
| 995 |
}
|
| 996 |
|
| 997 |
-
|
| 998 |
-
|
| 999 |
-
|
| 1000 |
-
// Ball movement
|
| 1001 |
-
if (ball.active) {
|
| 1002 |
-
// Store current position for trail
|
| 1003 |
-
ball.trail.push({x: ball.x, y: ball.y});
|
| 1004 |
-
if (ball.trail.length > 10) {
|
| 1005 |
-
ball.trail.shift();
|
| 1006 |
-
}
|
| 1007 |
-
|
| 1008 |
-
// Move ball
|
| 1009 |
-
ball.x += ball.speedX;
|
| 1010 |
-
ball.y += ball.speedY;
|
| 1011 |
-
|
| 1012 |
-
// Ball collisions with walls
|
| 1013 |
-
if (ball.x + ball.size > GAME_WIDTH || ball.x < 0) {
|
| 1014 |
-
ball.speedX = -ball.speedX;
|
| 1015 |
-
ball.x = Math.max(0, Math.min(GAME_WIDTH - ball.size, ball.x));
|
| 1016 |
-
}
|
| 1017 |
-
|
| 1018 |
-
// Ball collisions with player paddle
|
| 1019 |
-
if (
|
| 1020 |
-
ball.y + ball.size > player.y &&
|
| 1021 |
-
ball.y < player.y + player.height &&
|
| 1022 |
-
ball.x + ball.size > player.x &&
|
| 1023 |
-
ball.x < player.x + player.width
|
| 1024 |
-
) {
|
| 1025 |
-
// Calculate angle based on where it hits the paddle
|
| 1026 |
-
const hitPosition = (ball.x - player.x) / player.width;
|
| 1027 |
-
const angle = hitPosition * Math.PI - Math.PI / 2; // -90 to 90 degrees
|
| 1028 |
-
|
| 1029 |
-
ball.speedX = Math.sin(angle) * ball.speed;
|
| 1030 |
-
ball.speedY = -Math.cos(angle) * ball.speed;
|
| 1031 |
-
|
| 1032 |
-
// Ball magnet effect
|
| 1033 |
-
if (gameState.powerupsActive.magnet) {
|
| 1034 |
-
ball.speedY *= 0.8; // Reduce vertical speed to make it easier to hit back
|
| 1035 |
-
}
|
| 1036 |
-
|
| 1037 |
-
// Powerup spawn chance
|
| 1038 |
-
if (Math.random() < POWERUP_SPAWN_CHANCE) {
|
| 1039 |
-
spawnPowerup();
|
| 1040 |
-
}
|
| 1041 |
-
}
|
| 1042 |
-
|
| 1043 |
-
// Ball collisions with opponent paddle
|
| 1044 |
-
if (
|
| 1045 |
-
ball.y < opponent.y + opponent.height &&
|
| 1046 |
-
ball.y + ball.size > opponent.y &&
|
| 1047 |
-
ball.x + ball.size > opponent.x &&
|
| 1048 |
-
ball.x < opponent.x + opponent.width
|
| 1049 |
-
) {
|
| 1050 |
-
// Basic AI reaction calculation
|
| 1051 |
-
const paddleCenter = opponent.x + opponent.width / 2;
|
| 1052 |
-
const ballCenter = ball.x + ball.size / 2;
|
| 1053 |
-
const distanceFromCenter = ballCenter - paddleCenter;
|
| 1054 |
-
|
| 1055 |
-
// Calculate angle based on where it hits the paddle (simplified for AI)
|
| 1056 |
-
const angle = (distanceFromCenter / (opponent.width / 2)) * (Math.PI / 3); // -60 to 60 degrees
|
| 1057 |
-
|
| 1058 |
-
ball.speedX = Math.sin(angle) * ball.speed;
|
| 1059 |
-
ball.speedY = Math.cos(angle) * ball.speed;
|
| 1060 |
-
}
|
| 1061 |
-
|
| 1062 |
-
// Ball out of bounds - scoring
|
| 1063 |
-
if (ball.y < 0) {
|
| 1064 |
-
// Player scores
|
| 1065 |
-
gameState.playerScore++;
|
| 1066 |
-
gameState.runStats.totalPoints++;
|
| 1067 |
-
updateScores();
|
| 1068 |
-
resetBall();
|
| 1069 |
-
|
| 1070 |
-
// Check for win
|
| 1071 |
-
if (gameState.playerScore >= winScore && gameState.playerScore - gameState.opponentScore >= 2) {
|
| 1072 |
-
roundWon();
|
| 1073 |
-
}
|
| 1074 |
-
} else if (ball.y + ball.size > GAME_HEIGHT) {
|
| 1075 |
-
// Opponent scores
|
| 1076 |
-
gameState.opponentScore++;
|
| 1077 |
-
updateScores();
|
| 1078 |
-
resetBall();
|
| 1079 |
-
|
| 1080 |
-
// Check for loss
|
| 1081 |
-
if (gameState.opponentScore >= winScore && gameState.opponentScore - gameState.playerScore >= 2) {
|
| 1082 |
-
roundLost();
|
| 1083 |
-
}
|
| 1084 |
-
}
|
| 1085 |
}
|
| 1086 |
|
| 1087 |
-
|
| 1088 |
-
|
| 1089 |
-
const paddleCenter = opponent.x + opponent.width / 2;
|
| 1090 |
-
const ballFutureX = ball.x + ball.speedX * 5; // Predict future position slightly
|
| 1091 |
-
const targetX = ballFutureX - opponent.width / 2;
|
| 1092 |
-
|
| 1093 |
-
// Apply reaction time
|
| 1094 |
-
const reactionSpeed = opponent.reactionTime * opponent.speed;
|
| 1095 |
-
|
| 1096 |
-
if (paddleCenter < ballFutureX - 10) {
|
| 1097 |
-
opponent.x += reactionSpeed;
|
| 1098 |
-
} else if (paddleCenter > ballFutureX + 10) {
|
| 1099 |
-
opponent.x -= reactionSpeed;
|
| 1100 |
-
}
|
| 1101 |
-
|
| 1102 |
-
// Keep opponent within bounds
|
| 1103 |
-
opponent.x = Math.max(0, Math.min(GAME_WIDTH - opponent.width, opponent.x));
|
| 1104 |
}
|
| 1105 |
|
| 1106 |
-
|
| 1107 |
-
|
| 1108 |
-
}
|
| 1109 |
-
|
| 1110 |
-
// Update active powerups
|
| 1111 |
-
function updatePowerups() {
|
| 1112 |
-
const now = Date.now();
|
| 1113 |
-
for (const powerupId in gameState.powerupsActive) {
|
| 1114 |
-
if (gameState.powerupsActive[powerupId].endTime <= now) {
|
| 1115 |
-
removePowerup(powerupId);
|
| 1116 |
-
}
|
| 1117 |
-
}
|
| 1118 |
-
|
| 1119 |
-
// Update powerup HUD
|
| 1120 |
-
updatePowerupHud();
|
| 1121 |
-
}
|
| 1122 |
-
|
| 1123 |
-
// Update powerup positions and check for collection
|
| 1124 |
-
function updatePowerupPositions() {
|
| 1125 |
-
for (let i = powerups.length - 1; i >= 0; i--) {
|
| 1126 |
-
const powerup = powerups[i];
|
| 1127 |
-
|
| 1128 |
-
// Move powerup down
|
| 1129 |
-
powerup.y += 1;
|
| 1130 |
-
|
| 1131 |
-
// Check if collected by player
|
| 1132 |
-
if (
|
| 1133 |
-
powerup.y + POWERUP_SIZE > player.y &&
|
| 1134 |
-
powerup.y < player.y + player.height &&
|
| 1135 |
-
powerup.x + POWERUP_SIZE > player.x &&
|
| 1136 |
-
powerup.x < player.x + player.width
|
| 1137 |
-
) {
|
| 1138 |
-
// Apply powerup effect
|
| 1139 |
-
applyPowerup(powerup.type);
|
| 1140 |
-
|
| 1141 |
-
// Remove from array
|
| 1142 |
-
powerups.splice(i, 1);
|
| 1143 |
-
gameState.runStats.powerupsCollected++;
|
| 1144 |
-
|
| 1145 |
-
// Show collection notification
|
| 1146 |
-
showPowerupNotification(powerupTypes.find(p => p.id === powerup.type).name);
|
| 1147 |
-
}
|
| 1148 |
-
|
| 1149 |
-
// Remove if out of bounds
|
| 1150 |
-
if (powerup.y > GAME_HEIGHT) {
|
| 1151 |
-
powerups.splice(i, 1);
|
| 1152 |
-
}
|
| 1153 |
}
|
| 1154 |
}
|
| 1155 |
|
| 1156 |
-
/
|
| 1157 |
-
|
| 1158 |
-
|
| 1159 |
-
|
| 1160 |
-
|
| 1161 |
-
|
| 1162 |
-
|
| 1163 |
-
type: type,
|
| 1164 |
-
size: POWERUP_SIZE
|
| 1165 |
-
});
|
| 1166 |
}
|
| 1167 |
|
| 1168 |
-
|
| 1169 |
-
|
| 1170 |
-
|
| 1171 |
-
|
| 1172 |
-
|
| 1173 |
-
|
| 1174 |
-
|
| 1175 |
-
duration *= 1 + 0.25 * playerMeta.upgrades.powerupDuration;
|
| 1176 |
-
}
|
| 1177 |
-
|
| 1178 |
-
// Handle powerup effects
|
| 1179 |
-
switch (powerupId) {
|
| 1180 |
-
case 'paddleXL':
|
| 1181 |
-
gameState.powerupsActive.paddleXL = {
|
| 1182 |
-
originalWidth: player.width,
|
| 1183 |
-
endTime: now + duration
|
| 1184 |
-
};
|
| 1185 |
-
player.width *= 1.5;
|
| 1186 |
-
break;
|
| 1187 |
-
|
| 1188 |
-
case 'paddleXS':
|
| 1189 |
-
gameState.powerupsActive.paddleXS = {
|
| 1190 |
-
originalWidth: player.width,
|
| 1191 |
-
endTime: now + duration
|
| 1192 |
-
};
|
| 1193 |
-
player.width *= 0.5;
|
| 1194 |
-
break;
|
| 1195 |
-
|
| 1196 |
-
case 'speedBoost':
|
| 1197 |
-
gameState.powerupsActive.speedBoost = {
|
| 1198 |
-
originalSpeed: ball.speed,
|
| 1199 |
-
endTime: now + duration
|
| 1200 |
-
};
|
| 1201 |
-
ball.speed *= 1.5;
|
| 1202 |
-
break;
|
| 1203 |
-
|
| 1204 |
-
case 'multiBall':
|
| 1205 |
-
// Spawn 3 extra balls
|
| 1206 |
-
for (let i = 0; i < 3; i++) {
|
| 1207 |
-
const angle = Math.random() * Math.PI * 2;
|
| 1208 |
-
const speed = ball.speed * (0.8 + Math.random() * 0.4);
|
| 1209 |
-
|
| 1210 |
-
powerups.push({
|
| 1211 |
-
x: ball.x,
|
| 1212 |
-
y: ball.y,
|
| 1213 |
-
type: 'extraBall',
|
| 1214 |
-
size: BALL_SIZE,
|
| 1215 |
-
speedX: Math.cos(angle) * speed,
|
| 1216 |
-
speedY: Math.sin(angle) * speed,
|
| 1217 |
-
active: true,
|
| 1218 |
-
lifetime: 300 // frames
|
| 1219 |
-
});
|
| 1220 |
-
}
|
| 1221 |
-
break;
|
| 1222 |
-
|
| 1223 |
-
case 'slowMotion':
|
| 1224 |
-
gameState.powerupsActive.slowMotion = {
|
| 1225 |
-
endTime: now + duration
|
| 1226 |
-
};
|
| 1227 |
-
// Effect handled in the update loop
|
| 1228 |
-
break;
|
| 1229 |
-
|
| 1230 |
-
case 'magnet':
|
| 1231 |
-
gameState.powerupsActive.magnet = {
|
| 1232 |
-
endTime: now + duration
|
| 1233 |
-
};
|
| 1234 |
-
break;
|
| 1235 |
-
}
|
| 1236 |
}
|
| 1237 |
|
| 1238 |
-
|
| 1239 |
-
|
| 1240 |
-
|
| 1241 |
-
|
| 1242 |
-
|
| 1243 |
-
|
| 1244 |
-
|
| 1245 |
-
|
| 1246 |
-
case 'speedBoost':
|
| 1247 |
-
ball.speed = gameState.powerupsActive[powerupId].originalSpeed;
|
| 1248 |
-
break;
|
| 1249 |
-
}
|
| 1250 |
-
|
| 1251 |
-
delete gameState.powerupsActive[powerupId];
|
| 1252 |
-
updatePowerupHud();
|
| 1253 |
}
|
| 1254 |
|
| 1255 |
-
|
| 1256 |
-
|
| 1257 |
-
|
| 1258 |
-
|
| 1259 |
-
|
| 1260 |
-
|
| 1261 |
-
|
| 1262 |
-
notifElement.textContent = `Powerup Collected: ${powerupName}`;
|
| 1263 |
-
document.querySelector('.game-container').appendChild(notifElement);
|
| 1264 |
-
|
| 1265 |
-
setTimeout(() => {
|
| 1266 |
-
notifElement.classList.add('show');
|
| 1267 |
-
}, 10);
|
| 1268 |
-
|
| 1269 |
-
setTimeout(() => {
|
| 1270 |
-
notifElement.remove();
|
| 1271 |
-
}, 2000);
|
| 1272 |
-
} else {
|
| 1273 |
-
notification.textContent = `Powerup Collected: ${powerupName}`;
|
| 1274 |
-
notification.classList.remove('show');
|
| 1275 |
-
|
| 1276 |
-
setTimeout(() => {
|
| 1277 |
-
notification.classList.add('show');
|
| 1278 |
-
}, 10);
|
| 1279 |
-
}
|
| 1280 |
}
|
| 1281 |
|
| 1282 |
-
|
| 1283 |
-
|
| 1284 |
-
|
| 1285 |
-
ball.y = GAME_HEIGHT / 2 - ball.size / 2;
|
| 1286 |
-
|
| 1287 |
-
// Initial direction - slightly randomized
|
| 1288 |
-
const angle = Math.random() * Math.PI / 2 - Math.PI / 4; // -45 to 45 degrees
|
| 1289 |
-
const speed = BALL_INITIAL_SPEED;
|
| 1290 |
-
|
| 1291 |
-
if (playerMeta.upgrades.ballStartSpeed > 0) {
|
| 1292 |
-
speed *= 1 - 0.1 * playerMeta.upgrades.ballStartSpeed;
|
| 1293 |
}
|
| 1294 |
-
|
| 1295 |
-
|
| 1296 |
-
ball.speedX = Math.sin(angle) * ball.speed;
|
| 1297 |
-
ball.speedY = Math.cos(angle) * ball.speed;
|
| 1298 |
-
|
| 1299 |
-
// Randomly choose who serves (positive or negative Y direction)
|
| 1300 |
-
if (Math.random() > 0.5) {
|
| 1301 |
-
ball.speedY = -ball.speedY;
|
| 1302 |
}
|
| 1303 |
-
|
| 1304 |
-
ball.active = false; // Wait for player input (space) to serve
|
| 1305 |
-
ball.trail = [];
|
| 1306 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1307 |
|
| 1308 |
-
|
| 1309 |
-
|
| 1310 |
-
|
| 1311 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1312 |
|
| 1313 |
-
|
| 1314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1315 |
|
| 1316 |
-
|
| 1317 |
-
|
| 1318 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1319 |
|
| 1320 |
-
|
| 1321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1322 |
|
| 1323 |
-
|
| 1324 |
-
|
| 1325 |
-
|
| 1326 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1327 |
|
| 1328 |
-
|
| 1329 |
-
|
| 1330 |
-
|
| 1331 |
-
|
| 1332 |
-
|
| 1333 |
-
|
| 1334 |
-
|
| 1335 |
-
|
| 1336 |
-
|
| 1337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1338 |
|
| 1339 |
-
|
| 1340 |
-
|
| 1341 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1342 |
|
| 1343 |
-
|
| 1344 |
-
|
| 1345 |
-
|
| 1346 |
-
|
| 1347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1348 |
|
| 1349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1350 |
|
| 1351 |
-
// Show
|
| 1352 |
-
|
|
|
|
| 1353 |
}
|
| 1354 |
|
| 1355 |
-
//
|
| 1356 |
-
|
| 1357 |
-
document.
|
| 1358 |
-
document.
|
| 1359 |
-
|
| 1360 |
-
|
| 1361 |
-
|
| 1362 |
-
|
| 1363 |
-
|
| 1364 |
-
|
| 1365 |
-
|
| 1366 |
-
|
| 1367 |
-
powerupElements.forEach(el => {
|
| 1368 |
-
el.textContent = '?';
|
| 1369 |
-
el.style.backgroundColor = 'rgba(76, 201, 240, 0.2)';
|
| 1370 |
-
el.classList.remove('active');
|
| 1371 |
});
|
| 1372 |
|
| 1373 |
-
|
| 1374 |
-
|
| 1375 |
-
if (
|
| 1376 |
-
|
| 1377 |
-
if (powerup) {
|
| 1378 |
-
powerupElements[index].textContent = powerup.icon;
|
| 1379 |
-
powerupElements[index].style.backgroundColor = powerup.color;
|
| 1380 |
-
powerupElements[index].classList.add('active');
|
| 1381 |
-
|
| 1382 |
-
// Calculate remaining time percentage
|
| 1383 |
-
const remaining = (gameState.powerupsActive[powerupId].endTime - Date.now()) / POWERUP_DURATION;
|
| 1384 |
-
powerupElements[index].style.backgroundImage =
|
| 1385 |
-
`linear-gradient(to top, ${powerup.color} ${remaining * 100}%, rgba(76, 201, 240, 0.2) ${remaining * 100}%)`;
|
| 1386 |
-
}
|
| 1387 |
}
|
| 1388 |
});
|
| 1389 |
-
}
|
| 1390 |
-
|
| 1391 |
-
// Draw everything
|
| 1392 |
-
function draw() {
|
| 1393 |
-
// Clear canvas
|
| 1394 |
-
ctx.fillStyle = '#14213D';
|
| 1395 |
-
ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
|
| 1396 |
|
| 1397 |
-
//
|
| 1398 |
-
|
| 1399 |
-
|
| 1400 |
-
|
| 1401 |
-
|
| 1402 |
-
|
| 1403 |
-
ctx.lineWidth = 2;
|
| 1404 |
-
ctx.stroke();
|
| 1405 |
-
ctx.setLineDash([]);
|
| 1406 |
-
|
| 1407 |
-
// Draw ball trail
|
| 1408 |
-
if (ball.active) {
|
| 1409 |
-
ball.trail.forEach((pos, i) => {
|
| 1410 |
-
const alpha = 0.1 + (i / ball.trail.length) * 0.4;
|
| 1411 |
-
ctx.fillStyle = `rgba(248, 249, 250, ${alpha})`;
|
| 1412 |
-
ctx.fillRect(pos.x, pos.y, ball.size, ball.size);
|
| 1413 |
-
});
|
| 1414 |
}
|
| 1415 |
-
|
| 1416 |
-
|
| 1417 |
-
|
| 1418 |
-
|
| 1419 |
-
|
| 1420 |
-
|
| 1421 |
-
|
| 1422 |
-
|
| 1423 |
-
|
| 1424 |
-
|
| 1425 |
-
|
| 1426 |
-
|
| 1427 |
-
|
| 1428 |
-
|
| 1429 |
-
|
| 1430 |
-
|
| 1431 |
-
ctx.fillStyle = '#FFFFFF';
|
| 1432 |
-
ctx.fillRect(powerup.x, powerup.y, powerup.size, powerup.size);
|
| 1433 |
}
|
| 1434 |
});
|
| 1435 |
-
|
| 1436 |
-
|
| 1437 |
-
|
| 1438 |
-
|
| 1439 |
-
|
| 1440 |
-
|
| 1441 |
-
|
| 1442 |
-
|
| 1443 |
-
|
| 1444 |
-
// Draw opponent paddle
|
| 1445 |
-
ctx.fillStyle = opponent.color;
|
| 1446 |
-
ctx.fillRect(opponent.x, opponent.y, opponent.width, opponent.height);
|
| 1447 |
-
|
| 1448 |
-
// Draw ball direction indicator when waiting to serve
|
| 1449 |
-
if (!ball.active) {
|
| 1450 |
-
ctx.setLineDash([3, 3]);
|
| 1451 |
-
ctx.beginPath();
|
| 1452 |
-
ctx.moveTo(ball.x + ball.size / 2, ball.y + ball.size / 2);
|
| 1453 |
-
ctx.lineTo(
|
| 1454 |
-
ball.x + ball.size / 2 + ball.speedX * 30,
|
| 1455 |
-
ball.y + ball.size / 2 + ball.speedY * 30
|
| 1456 |
-
);
|
| 1457 |
-
ctx.strokeStyle = 'rgba(248, 249, 250, 0.7)';
|
| 1458 |
-
ctx.lineWidth = 2;
|
| 1459 |
-
ctx.stroke();
|
| 1460 |
-
ctx.setLineDash([]);
|
| 1461 |
-
|
| 1462 |
-
// Draw serve prompt
|
| 1463 |
-
ctx.fillStyle = 'rgba(248, 249, 250, 0.8)';
|
| 1464 |
-
ctx.font = '16px "Press Start 2P"';
|
| 1465 |
-
ctx.textAlign = 'center';
|
| 1466 |
-
ctx.fillText('PRESS SPACE TO SERVE', GAME_WIDTH / 2, 50);
|
| 1467 |
-
}
|
| 1468 |
-
|
| 1469 |
-
// If game is over but not showing game over screen (transition)
|
| 1470 |
-
if (gameOver && gameOverScreen.style.display === 'none') {
|
| 1471 |
-
ctx.fillStyle = 'rgba(230, 57, 70, 0.7)';
|
| 1472 |
-
ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
|
| 1473 |
|
| 1474 |
-
|
| 1475 |
-
|
| 1476 |
-
|
| 1477 |
-
|
| 1478 |
-
}
|
| 1479 |
}
|
| 1480 |
|
| 1481 |
-
//
|
| 1482 |
-
|
| 1483 |
-
|
| 1484 |
-
|
| 1485 |
-
|
| 1486 |
-
|
| 1487 |
-
rightPressed = true;
|
| 1488 |
-
} else if (e.key === 'Left' || e.key === 'ArrowLeft') {
|
| 1489 |
-
leftPressed = true;
|
| 1490 |
-
} else if (e.key === ' ' && !ball.active) {
|
| 1491 |
-
// Serve ball on space
|
| 1492 |
-
if (gameRunning && !gameOver) {
|
| 1493 |
-
ball.active = true;
|
| 1494 |
}
|
| 1495 |
-
}
|
| 1496 |
-
|
| 1497 |
-
// Pause game
|
| 1498 |
-
gameRunning = false;
|
| 1499 |
-
} else if (gameOverScreen.style.display === 'flex') {
|
| 1500 |
-
showMainMenu();
|
| 1501 |
-
} else if (upgradesScreen.style.display === 'flex') {
|
| 1502 |
-
showMainMenu();
|
| 1503 |
-
} else {
|
| 1504 |
-
// Resume game from pause
|
| 1505 |
-
gameRunning = true;
|
| 1506 |
-
gameLoop();
|
| 1507 |
-
}
|
| 1508 |
-
}
|
| 1509 |
-
}
|
| 1510 |
|
| 1511 |
-
|
| 1512 |
-
|
| 1513 |
-
|
| 1514 |
-
} else if (e.key === 'Left' || e.key === 'ArrowLeft') {
|
| 1515 |
-
leftPressed = false;
|
| 1516 |
-
}
|
| 1517 |
-
}
|
| 1518 |
|
| 1519 |
-
//
|
| 1520 |
-
|
| 1521 |
-
|
| 1522 |
-
}
|
| 1523 |
-
|
| 1524 |
-
|
| 1525 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Nebula Storm - Game Design Document</title>
|
| 7 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 8 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
:root {
|
| 10 |
+
--primary: #6a00f4;
|
| 11 |
+
--secondary: #ff3e7f;
|
| 12 |
+
--dark: #0f0526;
|
| 13 |
+
--darker: #080117;
|
| 14 |
+
--light: #e0d6ff;
|
| 15 |
+
--accent: #00e1ff;
|
| 16 |
+
}
|
| 17 |
+
|
|
|
|
|
|
|
|
|
|
| 18 |
* {
|
| 19 |
margin: 0;
|
| 20 |
padding: 0;
|
| 21 |
box-sizing: border-box;
|
| 22 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 23 |
}
|
| 24 |
+
|
| 25 |
body {
|
|
|
|
| 26 |
background-color: var(--dark);
|
| 27 |
color: var(--light);
|
| 28 |
+
line-height: 1.6;
|
| 29 |
+
overflow-x: hidden;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.container {
|
| 33 |
+
max-width: 1200px;
|
| 34 |
+
margin: 0 auto;
|
| 35 |
+
padding: 0 20px;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
header {
|
| 39 |
+
background: linear-gradient(135deg, var(--primary), var(--darker));
|
| 40 |
+
padding: 2rem 0;
|
| 41 |
+
text-align: center;
|
| 42 |
+
border-bottom: 3px solid var(--accent);
|
| 43 |
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
overflow: hidden;
|
| 45 |
}
|
| 46 |
+
|
| 47 |
+
header::before {
|
| 48 |
+
content: "";
|
| 49 |
+
position: absolute;
|
| 50 |
+
top: 0;
|
| 51 |
+
left: 0;
|
| 52 |
width: 100%;
|
| 53 |
height: 100%;
|
| 54 |
+
background: radial-gradient(circle at center, transparent 0%, rgba(0,0,0,0.7) 100%);
|
| 55 |
+
pointer-events: none;
|
| 56 |
}
|
| 57 |
+
|
| 58 |
+
.header-content {
|
| 59 |
+
position: relative;
|
| 60 |
+
z-index: 2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
}
|
| 62 |
+
|
| 63 |
+
h1 {
|
| 64 |
+
font-size: 3.5rem;
|
| 65 |
+
margin-bottom: 1rem;
|
| 66 |
+
background: linear-gradient(to right, var(--accent), var(--secondary));
|
| 67 |
+
-webkit-background-clip: text;
|
| 68 |
+
-webkit-text-fill-color: transparent;
|
| 69 |
+
text-shadow: 0 0 15px rgba(106, 0, 244, 0.3);
|
| 70 |
}
|
| 71 |
+
|
| 72 |
+
.subtitle {
|
| 73 |
+
font-size: 1.5rem;
|
| 74 |
+
color: var(--light);
|
| 75 |
+
margin-bottom: 2rem;
|
| 76 |
+
opacity: 0.9;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
}
|
| 78 |
+
|
| 79 |
+
.tag-list {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
display: flex;
|
|
|
|
| 81 |
justify-content: center;
|
| 82 |
+
gap: 1rem;
|
| 83 |
+
margin-bottom: 2rem;
|
|
|
|
|
|
|
| 84 |
}
|
| 85 |
+
|
| 86 |
+
.tag {
|
| 87 |
+
background-color: rgba(255, 255, 255, 0.1);
|
| 88 |
+
padding: 0.5rem 1rem;
|
| 89 |
+
border-radius: 50px;
|
| 90 |
+
font-size: 0.9rem;
|
| 91 |
+
border: 1px solid var(--accent);
|
| 92 |
+
backdrop-filter: blur(5px);
|
| 93 |
}
|
| 94 |
+
|
| 95 |
+
.screenshot-placeholder {
|
| 96 |
+
width: 100%;
|
| 97 |
+
height: 400px;
|
| 98 |
+
background: linear-gradient(135deg, #2a0064, #4a00a0);
|
| 99 |
+
border-radius: 10px;
|
| 100 |
+
margin: 2rem 0;
|
| 101 |
+
display: flex;
|
| 102 |
+
align-items: center;
|
| 103 |
+
justify-content: center;
|
| 104 |
+
position: relative;
|
| 105 |
+
overflow: hidden;
|
| 106 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
| 107 |
}
|
| 108 |
+
|
| 109 |
+
.screenshot-placeholder::before {
|
| 110 |
+
content: "";
|
| 111 |
position: absolute;
|
| 112 |
top: 0;
|
| 113 |
left: 0;
|
| 114 |
width: 100%;
|
| 115 |
height: 100%;
|
| 116 |
+
background:
|
| 117 |
+
radial-gradient(circle at 30% 70%, rgba(0, 225, 255, 0.2) 0%, transparent 50%),
|
| 118 |
+
radial-gradient(circle at 70% 30%, rgba(255, 62, 127, 0.2) 0%, transparent 50%);
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
.placeholder-content {
|
|
|
|
| 122 |
text-align: center;
|
| 123 |
+
z-index: 2;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.placeholder-content i {
|
| 127 |
+
font-size: 5rem;
|
| 128 |
+
color: rgba(255, 255, 255, 0.1);
|
| 129 |
+
margin-bottom: 1rem;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
section {
|
| 133 |
+
padding: 3rem 0;
|
| 134 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
h2 {
|
| 138 |
+
font-size: 2.5rem;
|
| 139 |
+
margin-bottom: 1.5rem;
|
|
|
|
| 140 |
color: var(--accent);
|
| 141 |
+
position: relative;
|
| 142 |
+
display: inline-block;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
h2::after {
|
| 146 |
+
content: "";
|
| 147 |
+
position: absolute;
|
| 148 |
+
bottom: -10px;
|
| 149 |
+
left: 0;
|
| 150 |
+
width: 100px;
|
| 151 |
+
height: 3px;
|
| 152 |
+
background: var(--secondary);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
h3 {
|
| 156 |
+
font-size: 1.8rem;
|
| 157 |
+
margin: 2rem 0 1rem;
|
| 158 |
+
color: var(--secondary);
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
p {
|
| 162 |
+
margin-bottom: 1.5rem;
|
| 163 |
+
font-size: 1.1rem;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
ul, ol {
|
| 167 |
+
margin-bottom: 1.5rem;
|
| 168 |
+
padding-left: 2rem;
|
| 169 |
}
|
| 170 |
+
|
| 171 |
+
li {
|
| 172 |
+
margin-bottom: 0.5rem;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
.features {
|
| 176 |
+
display: grid;
|
| 177 |
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
| 178 |
+
gap: 2rem;
|
| 179 |
+
margin-top: 2rem;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
.feature-card {
|
| 183 |
+
background: rgba(15, 5, 38, 0.7);
|
| 184 |
+
border-radius: 10px;
|
| 185 |
+
padding: 1.5rem;
|
| 186 |
+
border: 1px solid rgba(106, 0, 244, 0.3);
|
| 187 |
+
transition: transform 0.3s, box-shadow 0.3s;
|
| 188 |
+
backdrop-filter: blur(5px);
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
.feature-card:hover {
|
| 192 |
+
transform: translateY(-5px);
|
| 193 |
+
box-shadow: 0 10px 20px rgba(106, 0, 244, 0.3);
|
| 194 |
+
border-color: var(--accent);
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.feature-icon {
|
| 198 |
+
font-size: 2rem;
|
| 199 |
+
color: var(--accent);
|
| 200 |
+
margin-bottom: 1rem;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.feature-title {
|
| 204 |
+
font-size: 1.3rem;
|
| 205 |
+
margin-bottom: 0.5rem;
|
| 206 |
color: var(--light);
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.control-scheme {
|
| 210 |
+
display: grid;
|
| 211 |
+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
| 212 |
+
gap: 1.5rem;
|
| 213 |
+
margin-top: 2rem;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
.control-item {
|
| 217 |
+
background: rgba(15, 5, 38, 0.7);
|
| 218 |
+
border-radius: 10px;
|
| 219 |
+
padding: 1.5rem;
|
| 220 |
+
border-left: 4px solid var(--accent);
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
.control-action {
|
| 224 |
+
font-weight: bold;
|
| 225 |
+
color: var(--secondary);
|
| 226 |
+
margin-bottom: 0.5rem;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
.tabs {
|
| 230 |
+
display: flex;
|
| 231 |
+
margin-bottom: 2rem;
|
| 232 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
.tab-button {
|
| 236 |
+
padding: 1rem 2rem;
|
| 237 |
+
background: transparent;
|
| 238 |
border: none;
|
| 239 |
+
color: var(--light);
|
| 240 |
+
font-size: 1.1rem;
|
|
|
|
| 241 |
cursor: pointer;
|
| 242 |
+
position: relative;
|
| 243 |
+
opacity: 0.7;
|
| 244 |
transition: all 0.3s;
|
| 245 |
}
|
| 246 |
+
|
| 247 |
+
.tab-button.active {
|
| 248 |
+
opacity: 1;
|
| 249 |
+
color: var(--accent);
|
|
|
|
| 250 |
}
|
| 251 |
+
|
| 252 |
+
.tab-button.active::after {
|
| 253 |
+
content: "";
|
| 254 |
+
position: absolute;
|
| 255 |
+
bottom: -1px;
|
| 256 |
+
left: 0;
|
| 257 |
+
width: 100%;
|
| 258 |
+
height: 3px;
|
| 259 |
+
background: var(--accent);
|
| 260 |
}
|
| 261 |
+
|
| 262 |
+
.tab-content {
|
| 263 |
+
display: none;
|
| 264 |
+
animation: fadeIn 0.5s ease-in-out;
|
| 265 |
}
|
| 266 |
+
|
| 267 |
+
.tab-content.active {
|
| 268 |
+
display: block;
|
| 269 |
}
|
| 270 |
+
|
| 271 |
+
@keyframes fadeIn {
|
| 272 |
+
from { opacity: 0; transform: translateY(10px); }
|
| 273 |
+
to { opacity: 1; transform: translateY(0); }
|
| 274 |
}
|
| 275 |
+
|
| 276 |
+
.enemy-grid {
|
| 277 |
+
display: grid;
|
| 278 |
+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
| 279 |
+
gap: 2rem;
|
| 280 |
+
margin-top: 2rem;
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
.enemy-card {
|
| 284 |
+
background: rgba(15, 5, 38, 0.7);
|
| 285 |
border-radius: 10px;
|
| 286 |
+
overflow: hidden;
|
| 287 |
+
border: 1px solid rgba(106, 0, 244, 0.3);
|
| 288 |
+
transition: transform 0.3s;
|
| 289 |
}
|
| 290 |
+
|
| 291 |
+
.enemy-card:hover {
|
| 292 |
+
transform: translateY(-5px);
|
| 293 |
+
box-shadow: 0 10px 20px rgba(106, 0, 244, 0.3);
|
|
|
|
| 294 |
}
|
| 295 |
+
|
| 296 |
+
.enemy-header {
|
| 297 |
+
background: linear-gradient(135deg, var(--secondary), var(--primary));
|
| 298 |
+
padding: 1rem;
|
| 299 |
+
text-align: center;
|
| 300 |
}
|
| 301 |
+
|
| 302 |
+
.enemy-name {
|
| 303 |
+
font-size: 1.2rem;
|
| 304 |
font-weight: bold;
|
| 305 |
+
margin-bottom: 0.5rem;
|
| 306 |
}
|
| 307 |
+
|
| 308 |
+
.enemy-difficulty {
|
| 309 |
+
font-size: 0.9rem;
|
| 310 |
+
display: inline-block;
|
| 311 |
+
padding: 0.2rem 0.5rem;
|
| 312 |
+
border-radius: 50px;
|
| 313 |
+
background-color: rgba(0, 0, 0, 0.3);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
}
|
| 315 |
+
|
| 316 |
+
.enemy-body {
|
| 317 |
+
padding: 1.5rem;
|
|
|
|
| 318 |
}
|
| 319 |
+
|
| 320 |
+
.enemy-attribute {
|
| 321 |
+
margin-bottom: 1rem;
|
|
|
|
|
|
|
| 322 |
}
|
| 323 |
+
|
| 324 |
+
.attribute-title {
|
| 325 |
+
font-weight: bold;
|
| 326 |
+
color: var(--accent);
|
| 327 |
+
margin-bottom: 0.3rem;
|
| 328 |
}
|
| 329 |
+
|
| 330 |
+
.weapons-list {
|
| 331 |
+
display: grid;
|
| 332 |
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
| 333 |
+
gap: 1.5rem;
|
| 334 |
+
margin-top: 2rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
}
|
| 336 |
+
|
| 337 |
+
.weapon-card {
|
| 338 |
+
background: rgba(15, 5, 38, 0.7);
|
| 339 |
+
border-radius: 10px;
|
| 340 |
+
padding: 1.5rem;
|
| 341 |
+
border-left: 4px solid var(--secondary);
|
| 342 |
}
|
| 343 |
+
|
| 344 |
+
.weapon-name {
|
| 345 |
+
font-size: 1.2rem;
|
| 346 |
+
margin-bottom: 0.5rem;
|
| 347 |
+
color: var(--secondary);
|
| 348 |
}
|
| 349 |
+
|
| 350 |
+
.weapon-stats {
|
|
|
|
| 351 |
display: flex;
|
| 352 |
flex-wrap: wrap;
|
| 353 |
+
gap: 0.5rem;
|
| 354 |
+
margin-top: 1rem;
|
|
|
|
| 355 |
}
|
| 356 |
+
|
| 357 |
+
.weapon-stat {
|
| 358 |
+
background: rgba(106, 0, 244, 0.2);
|
| 359 |
+
padding: 0.3rem 0.7rem;
|
| 360 |
+
border-radius: 50px;
|
| 361 |
+
font-size: 0.8rem;
|
|
|
|
| 362 |
}
|
| 363 |
+
|
| 364 |
+
.art-style-cards {
|
| 365 |
+
display: grid;
|
| 366 |
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
| 367 |
+
gap: 2rem;
|
| 368 |
+
margin-top: 2rem;
|
| 369 |
}
|
| 370 |
+
|
| 371 |
+
.art-style-card {
|
| 372 |
+
background: rgba(15, 5, 38, 0.7);
|
| 373 |
+
border-radius: 10px;
|
| 374 |
+
padding: 1.5rem;
|
| 375 |
+
border: 1px solid rgba(106, 0, 244, 0.3);
|
| 376 |
}
|
| 377 |
+
|
| 378 |
+
.art-style-title {
|
| 379 |
+
font-size: 1.3rem;
|
| 380 |
+
margin-bottom: 1rem;
|
| 381 |
+
color: var(--accent);
|
| 382 |
display: flex;
|
| 383 |
+
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
}
|
| 385 |
+
|
| 386 |
+
.art-style-title i {
|
| 387 |
+
margin-right: 1rem;
|
| 388 |
+
font-size: 1.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
}
|
| 390 |
+
|
| 391 |
+
.art-style-pros {
|
| 392 |
+
margin-top: 1rem;
|
| 393 |
+
padding-left: 1.5rem;
|
| 394 |
+
border-left: 3px solid var(--accent);
|
| 395 |
}
|
| 396 |
+
|
| 397 |
+
.pro-item {
|
| 398 |
+
position: relative;
|
| 399 |
+
padding-left: 1.5rem;
|
| 400 |
+
margin-bottom: 0.5rem;
|
| 401 |
}
|
| 402 |
+
|
| 403 |
+
.pro-item::before {
|
| 404 |
+
content: "✓";
|
| 405 |
position: absolute;
|
| 406 |
+
left: 0;
|
| 407 |
+
color: var(--accent);
|
| 408 |
+
font-weight: bold;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
|
| 411 |
+
.cons-item::before {
|
| 412 |
+
content: "✗";
|
| 413 |
+
position: absolute;
|
| 414 |
+
left: 0;
|
| 415 |
+
color: var(--secondary);
|
| 416 |
+
font-weight: bold;
|
| 417 |
+
}
|
| 418 |
|
| 419 |
+
.progress-bar {
|
| 420 |
+
width: 100%;
|
| 421 |
+
height: 20px;
|
| 422 |
+
background-color: rgba(255, 255, 255, 0.1);
|
| 423 |
+
border-radius: 10px;
|
| 424 |
+
margin: 1rem 0;
|
| 425 |
+
overflow: hidden;
|
| 426 |
+
}
|
| 427 |
|
| 428 |
+
.progress-fill {
|
| 429 |
+
height: 100%;
|
| 430 |
+
background: linear-gradient(90deg, var(--primary), var(--accent));
|
| 431 |
+
border-radius: 10px;
|
| 432 |
+
transition: width 0.5s ease-in-out;
|
| 433 |
+
width: 0;
|
| 434 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 435 |
|
| 436 |
+
.progress-labels {
|
| 437 |
+
display: flex;
|
| 438 |
+
justify-content: space-between;
|
| 439 |
+
margin-bottom: 0.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
}
|
| 441 |
|
| 442 |
+
.progress-label {
|
| 443 |
+
font-size: 0.9rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
}
|
| 445 |
|
| 446 |
+
.section-nav {
|
| 447 |
+
position: fixed;
|
| 448 |
+
top: 50%;
|
| 449 |
+
right: 2rem;
|
| 450 |
+
transform: translateY(-50%);
|
| 451 |
+
background: rgba(15, 5, 38, 0.8);
|
| 452 |
+
border-radius: 10px;
|
| 453 |
+
padding: 1rem;
|
| 454 |
+
border: 1px solid rgba(106, 0, 244, 0.5);
|
| 455 |
+
backdrop-filter: blur(5px);
|
| 456 |
+
z-index: 1000;
|
| 457 |
+
display: none;
|
| 458 |
}
|
| 459 |
|
| 460 |
+
.section-nav ul {
|
| 461 |
+
list-style: none;
|
| 462 |
+
padding: 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 463 |
}
|
| 464 |
|
| 465 |
+
.section-nav li {
|
| 466 |
+
margin-bottom: 0.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
}
|
| 468 |
|
| 469 |
+
.section-nav a {
|
| 470 |
+
color: var(--light);
|
| 471 |
+
text-decoration: none;
|
| 472 |
+
font-size: 0.9rem;
|
| 473 |
+
transition: color 0.3s;
|
| 474 |
+
display: block;
|
| 475 |
+
padding: 0.5rem;
|
| 476 |
+
border-radius: 5px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
}
|
| 478 |
|
| 479 |
+
.section-nav a:hover, .section-nav a.active {
|
| 480 |
+
color: var(--accent);
|
| 481 |
+
background: rgba(106, 0, 244, 0.3);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
}
|
| 483 |
|
| 484 |
+
@media (max-width: 768px) {
|
| 485 |
+
h1 {
|
| 486 |
+
font-size: 2.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
}
|
| 488 |
|
| 489 |
+
.subtitle {
|
| 490 |
+
font-size: 1.2rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
}
|
| 492 |
|
| 493 |
+
.features, .control-scheme, .enemy-grid, .weapons-list, .art-style-cards {
|
| 494 |
+
grid-template-columns: 1fr;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 495 |
}
|
| 496 |
|
| 497 |
+
.section-nav {
|
| 498 |
+
display: none !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
}
|
| 500 |
}
|
| 501 |
|
| 502 |
+
/* Animated background elements */
|
| 503 |
+
.bg-element {
|
| 504 |
+
position: fixed;
|
| 505 |
+
border-radius: 50%;
|
| 506 |
+
filter: blur(60px);
|
| 507 |
+
opacity: 0.1;
|
| 508 |
+
z-index: -1;
|
|
|
|
|
|
|
|
|
|
| 509 |
}
|
| 510 |
|
| 511 |
+
.bg-element-1 {
|
| 512 |
+
width: 300px;
|
| 513 |
+
height: 300px;
|
| 514 |
+
background: var(--primary);
|
| 515 |
+
top: -100px;
|
| 516 |
+
left: -100px;
|
| 517 |
+
animation: float 15s infinite alternate ease-in-out;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 518 |
}
|
| 519 |
|
| 520 |
+
.bg-element-2 {
|
| 521 |
+
width: 400px;
|
| 522 |
+
height: 400px;
|
| 523 |
+
background: var(--secondary);
|
| 524 |
+
bottom: -150px;
|
| 525 |
+
right: -150px;
|
| 526 |
+
animation: float 20s infinite alternate-reverse ease-in-out;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 527 |
}
|
| 528 |
|
| 529 |
+
.bg-element-3 {
|
| 530 |
+
width: 200px;
|
| 531 |
+
height: 200px;
|
| 532 |
+
background: var(--accent);
|
| 533 |
+
top: 40%;
|
| 534 |
+
right: 20%;
|
| 535 |
+
animation: float 25s infinite alternate ease-in-out;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
}
|
| 537 |
|
| 538 |
+
@keyframes float {
|
| 539 |
+
0%, 100% {
|
| 540 |
+
transform: translate(0, 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
}
|
| 542 |
+
50% {
|
| 543 |
+
transform: translate(50px, 50px);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 544 |
}
|
|
|
|
|
|
|
|
|
|
| 545 |
}
|
| 546 |
+
</style>
|
| 547 |
+
</head>
|
| 548 |
+
<body>
|
| 549 |
+
<div class="bg-element bg-element-1"></div>
|
| 550 |
+
<div class="bg-element bg-element-2"></div>
|
| 551 |
+
<div class="bg-element bg-element-3"></div>
|
| 552 |
+
|
| 553 |
+
<header>
|
| 554 |
+
<div class="container header-content">
|
| 555 |
+
<h1>NEBULA STORM</h1>
|
| 556 |
+
<p class="subtitle">A fast-paced sci-fi roguelike shooter with endless alien threats</p>
|
| 557 |
+
<div class="tag-list">
|
| 558 |
+
<span class="tag">Roguelike</span>
|
| 559 |
+
<span class="tag">Twin-stick Shooter</span>
|
| 560 |
+
<span class="tag">Procedural Generation</span>
|
| 561 |
+
<span class="tag">Permadeath</span>
|
| 562 |
+
<span class="tag">Sci-fi</span>
|
| 563 |
+
</div>
|
| 564 |
+
|
| 565 |
+
<div class="screenshot-placeholder">
|
| 566 |
+
<div class="placeholder-content">
|
| 567 |
+
<i class="fas fa-gamepad"></i>
|
| 568 |
+
<h3>Intense Alien Combat</h3>
|
| 569 |
+
<p>Concept screenshot placeholder</p>
|
| 570 |
+
</div>
|
| 571 |
+
</div>
|
| 572 |
+
</div>
|
| 573 |
+
</header>
|
| 574 |
+
|
| 575 |
+
<div class="section-nav">
|
| 576 |
+
<ul>
|
| 577 |
+
<li><a href="#overview" class="active">Overview</a></li>
|
| 578 |
+
<li><a href="#gameplay">Gameplay</a></li>
|
| 579 |
+
<li><a href="#controls">Controls</a></li>
|
| 580 |
+
<li><a href="#progression">Progression</a></li>
|
| 581 |
+
<li><a href="#enemies">Enemies</a></li>
|
| 582 |
+
<li><a href="#weapons">Weapons</a></li>
|
| 583 |
+
<li><a href="#art-style">Art Style</a></li>
|
| 584 |
+
</ul>
|
| 585 |
+
</div>
|
| 586 |
+
|
| 587 |
+
<main class="container">
|
| 588 |
+
<section id="overview">
|
| 589 |
+
<h2>Game Overview</h2>
|
| 590 |
+
<p>Nebula Storm is a fast-paced roguelike shooter set in a distant future where humanity has awakened an ancient alien threat while exploring the edge of known space. As one of the last elite Void Marines, players must battle through procedurally generated alien-infested sectors, upgrading their arsenal and abilities while facing increasingly deadly threats.</p>
|
| 591 |
+
<p>Each run is unique with randomized levels, enemies, and loot, but successful runs unlock permanent upgrades that make subsequent attempts easier. The game combines intense twin-stick shooter action with strategic decision-making about weapon and ability upgrades to create a deeply replayable experience.</p>
|
| 592 |
+
|
| 593 |
+
<div class="features">
|
| 594 |
+
<div class="feature-card">
|
| 595 |
+
<div class="feature-icon"><i class="fas fa-random"></i></div>
|
| 596 |
+
<h3 class="feature-title">Procedural Generation</h3>
|
| 597 |
+
<p>Every run features unique levels, enemy placements, and loot distribution ensuring no two playthroughs are the same.</p>
|
| 598 |
+
</div>
|
| 599 |
+
<div class="feature-card">
|
| 600 |
+
<div class="feature-icon"><i class="fas fa-bomb"></i></div>
|
| 601 |
+
<h3 class="feature-title">Intense Combat</h3>
|
| 602 |
+
<p>Fast-paced, skill-based shooting with satisfying weapon feedback and impactful dodging mechanics.</p>
|
| 603 |
+
</div>
|
| 604 |
+
<div class="feature-card">
|
| 605 |
+
<div class="feature-icon"><i class="fas fa-sync-alt"></i></div>
|
| 606 |
+
<h3 class="feature-title">Permadeath</h3>
|
| 607 |
+
<p>Death is permanent, but you unlock persistent upgrades that carry over between runs.</p>
|
| 608 |
+
</div>
|
| 609 |
+
<div class="feature-card">
|
| 610 |
+
<div class="feature-icon"><i class="fas fa-cogs"></i></div>
|
| 611 |
+
<h3 class="feature-title">Deep Progression</h3>
|
| 612 |
+
<p>A complex web of upgrades, abilities, and weapon modifications to keep players engaged for hours.</p>
|
| 613 |
+
</div>
|
| 614 |
+
</div>
|
| 615 |
+
</section>
|
| 616 |
|
| 617 |
+
<section id="gameplay">
|
| 618 |
+
<h2>Core Gameplay Loop</h2>
|
| 619 |
+
<p>The game follows a tight roguelike loop where each run presents new challenges and opportunities. Here's how a typical playthrough progresses:</p>
|
| 620 |
+
|
| 621 |
+
<div class="tabs">
|
| 622 |
+
<button class="tab-button active" onclick="openTab(event, 'gameplay-tab-1')">Level Start</button>
|
| 623 |
+
<button class="tab-button" onclick="openTab(event, 'gameplay-tab-2')">Exploration</button>
|
| 624 |
+
<button class="tab-button" onclick="openTab(event, 'gameplay-tab-3')">Combat</button>
|
| 625 |
+
<button class="tab-button" onclick="openTab(event, 'gameplay-tab-4')">Progression</button>
|
| 626 |
+
</div>
|
| 627 |
|
| 628 |
+
<div id="gameplay-tab-1" class="tab-content active">
|
| 629 |
+
<h3>Begin Your Run</h3>
|
| 630 |
+
<p>Each run starts with basic equipment scaled to your permanent upgrade progress. The procedural generation system creates a unique sector layout with interconnected rooms and corridors containing various enemy types and loot.</p>
|
| 631 |
+
<div class="progress-labels">
|
| 632 |
+
<span class="progress-label">Difficulty Spread</span>
|
| 633 |
+
</div>
|
| 634 |
+
<div class="progress-bar">
|
| 635 |
+
<div class="progress-fill" style="width: 30%"></div>
|
| 636 |
+
</div>
|
| 637 |
+
</div>
|
| 638 |
|
| 639 |
+
<div id="gameplay-tab-2" class="tab-content">
|
| 640 |
+
<h3>Explore the Sector</h3>
|
| 641 |
+
<p>As you navigate the level, you'll encounter various challenges and opportunities. Rooms contain different combinations of enemies, environmental hazards, and potential rewards like chests, shops, or special events.</p>
|
| 642 |
+
<div class="progress-labels">
|
| 643 |
+
<span class="progress-label">Exploration Intensity</span>
|
| 644 |
+
</div>
|
| 645 |
+
<div class="progress-bar">
|
| 646 |
+
<div class="progress-fill" style="width: 20%"></div>
|
| 647 |
+
</div>
|
| 648 |
+
</div>
|
| 649 |
|
| 650 |
+
<div id="gameplay-tab-3" class="tab-content">
|
| 651 |
+
<h3>Engage in Combat</h3>
|
| 652 |
+
<p>When encountering enemies, the game shifts to fast-paced combat sequences. Multiple enemy types with different behaviors require players to adapt their playstyle using movement, dodging, and smart weapon choice.</p>
|
| 653 |
+
<div class="progress-labels">
|
| 654 |
+
<span class="progress-label">Combat Intensity</span>
|
| 655 |
+
</div>
|
| 656 |
+
<div class="progress-bar">
|
| 657 |
+
<div class="progress-fill" style="width: 50%"></div>
|
| 658 |
+
</div>
|
| 659 |
+
</div>
|
| 660 |
|
| 661 |
+
<div id="gameplay-tab-4" class="tab-content">
|
| 662 |
+
<h3>Progress Your Build</h3>
|
| 663 |
+
<p>After clearing areas, players gain loot drops that can significantly change their power level. Careful choices about which weapons, upgrades, and abilities to take create emergent synergies that define each run's playstyle.</p>
|
| 664 |
+
<div class="progress-labels">
|
| 665 |
+
<span class="progress-label">Build Potential</span>
|
| 666 |
+
</div>
|
| 667 |
+
<div class="progress-bar">
|
| 668 |
+
<div class="progress-fill" style="width: 80%"></div>
|
| 669 |
+
</div>
|
| 670 |
+
</div>
|
| 671 |
+
</section>
|
| 672 |
|
| 673 |
+
<section id="controls">
|
| 674 |
+
<h2>Control Scheme</h2>
|
| 675 |
+
<p>Nebula Storm offers two control schemes to accommodate different player preferences. Both provide precise control for the game's demanding combat scenarios.</p>
|
| 676 |
+
|
| 677 |
+
<h3>Control Options</h3>
|
| 678 |
+
<div class="control-scheme">
|
| 679 |
+
<div class="control-item">
|
| 680 |
+
<div class="control-action">Twin-Stick Controls</div>
|
| 681 |
+
<p>Left stick: Movement<br>
|
| 682 |
+
Right stick: Aim/shooting direction<br>
|
| 683 |
+
RT/R2: Fire weapon<br>
|
| 684 |
+
A: Dodge/evade<br>
|
| 685 |
+
X: Use ability 1<br>
|
| 686 |
+
Y: Use ability 2<br>
|
| 687 |
+
B: Interact/pickup</p>
|
| 688 |
+
</div>
|
| 689 |
+
<div class="control-item">
|
| 690 |
+
<div class="control-action">Mouse & Keyboard</div>
|
| 691 |
+
<p>WASD: Movement<br>
|
| 692 |
+
Mouse: Aim/shooting direction<br>
|
| 693 |
+
Left click: Fire weapon<br>
|
| 694 |
+
Shift: Dodge/evade<br>
|
| 695 |
+
Q: Use ability 1<br>
|
| 696 |
+
E: Use ability 2<br>
|
| 697 |
+
F: Interact/pickup</p>
|
| 698 |
+
</div>
|
| 699 |
+
<div class="control-item">
|
| 700 |
+
<div class="control-action">Common Controls</div>
|
| 701 |
+
<p>Tab: Open map<br>
|
| 702 |
+
ESC: Pause menu<br>
|
| 703 |
+
1-4: Quick select weapons<br>
|
| 704 |
+
Mouse wheel: Cycle weapons<br>
|
| 705 |
+
Space: Confirm/select (menus)</p>
|
| 706 |
+
</div>
|
| 707 |
+
<div class="control-item">
|
| 708 |
+
<div class="control-action">Gameplay Actions</div>
|
| 709 |
+
<p>Dodging grants brief invulnerability<br>
|
| 710 |
+
Perfect dodges (just before hit) slow time briefly<br>
|
| 711 |
+
Some weapons have alt-fire modes (press L3)<br>
|
| 712 |
+
Toggle sprint on/off for precision movement</p>
|
| 713 |
+
</div>
|
| 714 |
+
</div>
|
| 715 |
+
</section>
|
| 716 |
+
|
| 717 |
+
<section id="progression">
|
| 718 |
+
<h2>Progression Systems</h2>
|
| 719 |
+
<p>Nebula Storm features a dual progression system with temporary run-based upgrades and permanent meta-progression that carries between attempts.</p>
|
| 720 |
+
|
| 721 |
+
<h3>In-Run Progression</h3>
|
| 722 |
+
<p>During each playthrough, players collect various upgrades that enhance their capabilities for that specific run:</p>
|
| 723 |
+
<ul>
|
| 724 |
+
<li><strong>Weapons:</strong> Different weapon types with unique properties and upgrade paths</li>
|
| 725 |
+
<li><strong>Passive Modifiers:</strong> Stat boosts and special effects that enhance your capabilities</li>
|
| 726 |
+
<li><strong>Abilities:</strong> Special powers with cooldowns that provide combat advantages</li>
|
| 727 |
+
<li><strong>Nanite Infusions:</strong> Temporary buffs that last until death or can be replaced</li>
|
| 728 |
+
</ul>
|
| 729 |
+
|
| 730 |
+
<h3>Permanent Progression</h3>
|
| 731 |
+
<p>Between runs, players spend collected Void Crystals on upgrades that persist indefinitely:</p>
|
| 732 |
+
<div class="features">
|
| 733 |
+
<div class="feature-card">
|
| 734 |
+
<div class="feature-icon"><i class="fas fa-shield-alt"></i></div>
|
| 735 |
+
<h3 class="feature-title">Base Stats</h3>
|
| 736 |
+
<p>Increase starting health, movement speed, damage resistance, etc.</p>
|
| 737 |
+
</div>
|
| 738 |
+
<div class="feature-card">
|
| 739 |
+
<div class="feature-icon"><i class="fas fa-box-open"></i></div>
|
| 740 |
+
<h3 class="feature-title">Starting Options</h3>
|
| 741 |
+
<p>Unlock new starting weapons, abilities, or equipment loadouts.</p>
|
| 742 |
+
</div>
|
| 743 |
+
<div class="feature-card">
|
| 744 |
+
<div class="feature-icon"><i class="fas fa-dice"></i></div>
|
| 745 |
+
<h3 class="feature-title">Loot Quality</h3>
|
| 746 |
+
<p>Improve the chances of finding higher rarity items during runs.</p>
|
| 747 |
+
</div>
|
| 748 |
+
<div class="feature-card">
|
| 749 |
+
<div class="feature-icon"><i class="fas fa-trophy"></i></div>
|
| 750 |
+
<h3 class="feature-title">Content Unlocks</h3>
|
| 751 |
+
<p>Discover new enemy variants, levels, and challenges as you progress.</p>
|
| 752 |
+
</div>
|
| 753 |
+
</div>
|
| 754 |
+
</section>
|
| 755 |
+
|
| 756 |
+
<section id="enemies">
|
| 757 |
+
<h2>Alien Enemy Types</h2>
|
| 758 |
+
<p>The game features a roster of unique alien enemies, each with distinct behaviors and attack patterns that require different strategies to defeat.</p>
|
| 759 |
+
|
| 760 |
+
<div class="enemy-grid">
|
| 761 |
+
<div class="enemy-card">
|
| 762 |
+
<div class="enemy-header">
|
| 763 |
+
<div class="enemy-name">Screecher</div>
|
| 764 |
+
<div class="enemy-difficulty">Low Threat</div>
|
| 765 |
+
</div>
|
| 766 |
+
<div class="enemy-body">
|
| 767 |
+
<div class="enemy-attribute">
|
| 768 |
+
<div class="attribute-title">Behavior</div>
|
| 769 |
+
<p>Fast-moving melee attacker that rushes the player when sighted.</p>
|
| 770 |
+
</div>
|
| 771 |
+
<div class="enemy-attribute">
|
| 772 |
+
<div class="attribute-title">Attacks</div>
|
| 773 |
+
<p>Close-range claw swipe that causes bleeding (damage over time).</p>
|
| 774 |
+
</div>
|
| 775 |
+
<div class="enemy-attribute">
|
| 776 |
+
<div class="attribute-title">Tactics</div>
|
| 777 |
+
<p>Dodge when they leap, then punish while they recover.</p>
|
| 778 |
+
</div>
|
| 779 |
+
</div>
|
| 780 |
+
</div>
|
| 781 |
+
|
| 782 |
+
<div class="enemy-card">
|
| 783 |
+
<div class="enemy-header">
|
| 784 |
+
<div class="enemy-name">Spitter</div>
|
| 785 |
+
<div class="enemy-difficulty">Medium Threat</div>
|
| 786 |
+
</div>
|
| 787 |
+
<div class="enemy-body">
|
| 788 |
+
<div class="enemy-attribute">
|
| 789 |
+
<div class="attribute-title">Behavior</div>
|
| 790 |
+
<p>Ranged attacker that prefers to keep distance and fire projectile volleys.</p>
|
| 791 |
+
</div>
|
| 792 |
+
<div class="enemy-attribute">
|
| 793 |
+
<div class="attribute-title">Attacks</div>
|
| 794 |
+
<p>Acid globs that explode into pools of damaging liquid.</p>
|
| 795 |
+
</div>
|
| 796 |
+
<div class="enemy-attribute">
|
| 797 |
+
<div class="attribute-title">Tactics</div>
|
| 798 |
+
<p>Move perpendicular to Spitters when they telegraph attacks.</p>
|
| 799 |
+
</div>
|
| 800 |
+
</div>
|
| 801 |
+
</div>
|
| 802 |
+
|
| 803 |
+
<div class="enemy-card">
|
| 804 |
+
<div class="enemy-header">
|
| 805 |
+
<div class="enemy-name">Charger</div>
|
| 806 |
+
<div class="enemy-difficulty">High Threat</div>
|
| 807 |
+
</div>
|
| 808 |
+
<div class="enemy-body">
|
| 809 |
+
<div class="enemy-attribute">
|
| 810 |
+
<div class="attribute-title">Behavior</div>
|
| 811 |
+
<p>Bulky enemy that periodically rushes the player in straight lines.</p>
|
| 812 |
+
</div>
|
| 813 |
+
<div class="enemy-attribute">
|
| 814 |
+
<div class="attribute-title">Attacks</div>
|
| 815 |
+
<p>Devastating charge attack that knocks back and stuns the player.</p>
|
| 816 |
+
</div>
|
| 817 |
+
<div class="enemy-attribute">
|
| 818 |
+
<div class="attribute-title">Tactics</div>
|
| 819 |
+
<p>Dodge sideways at the last moment before impact.</p>
|
| 820 |
+
</div>
|
| 821 |
+
</div>
|
| 822 |
+
</div>
|
| 823 |
+
|
| 824 |
+
<div class="enemy-card">
|
| 825 |
+
<div class="enemy-header">
|
| 826 |
+
<div class="enemy-name">Broodmother</div>
|
| 827 |
+
<div class="enemy-difficulty">Boss</div>
|
| 828 |
+
</div>
|
| 829 |
+
<div class="enemy-body">
|
| 830 |
+
<div class="enemy-attribute">
|
| 831 |
+
<div class="attribute-title">Behavior</div>
|
| 832 |
+
<p>Spawns additional enemies and requires repositioning during combat.</p>
|
| 833 |
+
</div>
|
| 834 |
+
<div class="enemy-attribute">
|
| 835 |
+
<div class="attribute-title">Attacks</div>
|
| 836 |
+
<p>Multiple stage battle with area-denial acid pools and energy projectiles.</p>
|
| 837 |
+
</div>
|
| 838 |
+
<div class="enemy-attribute">
|
| 839 |
+
<div class="attribute-title">Tactics</div>
|
| 840 |
+
<p>Focus on clearing minions between damage phases on the boss.</p>
|
| 841 |
+
</div>
|
| 842 |
+
</div>
|
| 843 |
+
</div>
|
| 844 |
+
</div>
|
| 845 |
+
</section>
|
| 846 |
+
|
| 847 |
+
<section id="weapons">
|
| 848 |
+
<h2>Weapons & Abilities</h2>
|
| 849 |
+
<p>The game features a wide arsenal of weapons and abilities, each with multiple upgrade paths that allow for diverse builds and playstyles.</p>
|
| 850 |
+
|
| 851 |
+
<h3>Weapon Types</h3>
|
| 852 |
+
<div class="weapons-list">
|
| 853 |
+
<div class="weapon-card">
|
| 854 |
+
<div class="weapon-name">Pulse Pistol</div>
|
| 855 |
+
<p>Standard semi-auto sidearm with good accuracy and moderate damage.</p>
|
| 856 |
+
<div class="weapon-stats">
|
| 857 |
+
<span class="weapon-stat">Damage: Medium</span>
|
| 858 |
+
<span class="weapon-stat">ROF: Medium</span>
|
| 859 |
+
<span class="weapon-stat">Range: Medium</span>
|
| 860 |
+
</div>
|
| 861 |
+
</div>
|
| 862 |
+
|
| 863 |
+
<div class="weapon-card">
|
| 864 |
+
<div class="weapon-name">Plasma Cutter</div>
|
| 865 |
+
<p>Short-range weapon that emits a continuous beam of superheated plasma.</p>
|
| 866 |
+
<div class="weapon-stats">
|
| 867 |
+
<span class="weapon-stat">Damage: High</span>
|
| 868 |
+
<span class="weapon-stat">ROF: Continuous</span>
|
| 869 |
+
<span class="weapon-stat">Range: Short</span>
|
| 870 |
+
</div>
|
| 871 |
+
</div>
|
| 872 |
+
|
| 873 |
+
<div class="weapon-card">
|
| 874 |
+
<div class="weapon-name">Gauss Rifle</div>
|
| 875 |
+
<p>High-velocity railgun with armor-piercing rounds and slight recoil.</p>
|
| 876 |
+
<div class="weapon-stats">
|
| 877 |
+
<span class="weapon-stat">Damage: Very High</span>
|
| 878 |
+
<span class="weapon-stat">ROF: Slow</span>
|
| 879 |
+
<span class="weapon-stat">Range: Long</span>
|
| 880 |
+
</div>
|
| 881 |
+
</div>
|
| 882 |
+
|
| 883 |
+
<div class="weapon-card">
|
| 884 |
+
<div class="weapon-name">Nova Launcher</div>
|
| 885 |
+
<p>Explosive weapon that fires energy orbs with splash damage.</p>
|
| 886 |
+
<div class="weapon-stats">
|
| 887 |
+
<span class="weapon-stat">Damage: High AOE</span>
|
| 888 |
+
<span class="weapon-stat">ROF: Very Slow</span>
|
| 889 |
+
<span class="weapon-stat">Range: Medium</span>
|
| 890 |
+
</div>
|
| 891 |
+
</div>
|
| 892 |
+
|
| 893 |
+
<div class="weapon-card">
|
| 894 |
+
<div class="weapon-name">Shock Baton</div>
|
| 895 |
+
<p>Melee weapon with chain lightning and stagger effects.</p>
|
| 896 |
+
<div class="weapon-stats">
|
| 897 |
+
<span class="weapon-stat">Damage: Medium</span>
|
| 898 |
+
<span class="weapon-stat">ROF: Fast</span>
|
| 899 |
+
<span class="weapon-stat">Range: Melee</span>
|
| 900 |
+
</div>
|
| 901 |
+
</div>
|
| 902 |
+
</div>
|
| 903 |
|
| 904 |
+
<h3>Ability Examples</h3>
|
| 905 |
+
<div class="weapons-list">
|
| 906 |
+
<div class="weapon-card">
|
| 907 |
+
<div class="weapon-name">Blink</div>
|
| 908 |
+
<p>Teleport short distances to reposition or bypass obstacles.</p>
|
| 909 |
+
<div class="weapon-stats">
|
| 910 |
+
<span class="weapon-stat">Cooldown: 6s</span>
|
| 911 |
+
</div>
|
| 912 |
+
</div>
|
| 913 |
+
|
| 914 |
+
<div class="weapon-card">
|
| 915 |
+
<div class="weapon-name">Power Shield</div>
|
| 916 |
+
<p>Creates a temporary barrier that blocks incoming projectiles.</p>
|
| 917 |
+
<div class="weapon-stats">
|
| 918 |
+
<span class="weapon-stat">Duration: 4s</span>
|
| 919 |
+
<span class="weapon-stat">Cooldown: 12s</span>
|
| 920 |
+
</div>
|
| 921 |
+
</div>
|
| 922 |
+
|
| 923 |
+
<div class="weapon-card">
|
| 924 |
+
<div class="weapon-name">Gravity Well</div>
|
| 925 |
+
<p>Creates a zone that slows and damages enemies.</p>
|
| 926 |
+
<div class="weapon-stats">
|
| 927 |
+
<span class="weapon-stat">Duration: 5s</span>
|
| 928 |
+
<span class="weapon-stat">Cooldown: 18s</span>
|
| 929 |
+
</div>
|
| 930 |
+
</div>
|
| 931 |
+
</div>
|
| 932 |
+
</section>
|
| 933 |
+
|
| 934 |
+
<section id="art-style">
|
| 935 |
+
<h2>Art Style Direction</h2>
|
| 936 |
+
<p>The game's visual style aims to create a compelling sci-fi atmosphere with clear readability during frantic combat situations.</p>
|
| 937 |
+
|
| 938 |
+
<div class="art-style-cards">
|
| 939 |
+
<div class="art-style-card">
|
| 940 |
+
<h3 class="art-style-title"><i class="fas fa-moon"></i>Dark Sci-Fi Realism</h3>
|
| 941 |
+
<p>A gritty, high-detail aesthetic emphasizing realistic lighting and materials with a dark color palette. Perfect for creating tension and horror elements.</p>
|
| 942 |
+
<div class="art-style-pros">
|
| 943 |
+
<div class="pro-item">Immersion and tension</div>
|
| 944 |
+
<div class="pro-item">Natural dynamic lighting effects</div>
|
| 945 |
+
<div class="pro-item">Strong environmental storytelling</div>
|
| 946 |
+
</div>
|
| 947 |
+
</div>
|
| 948 |
+
|
| 949 |
+
<div class="art-style-card">
|
| 950 |
+
<h3 class="art-style-title"><i class="fas fa-atom"></i>Neon Cyberpunk</h3>
|
| 951 |
+
<p>Vibrant neon colors against dark backgrounds with synthetic lighting effects. Cyberpunk influences make the alien structures feel artificial and high-tech.</p>
|
| 952 |
+
<div class="art-style-pros">
|
| 953 |
+
<div class="pro-item">Vibrant visual feedback</div>
|
| 954 |
+
<div class="pro-item">Clear enemy/player contrast</div>
|
| 955 |
+
<div class="pro-item">Modern, stylish appeal</div>
|
| 956 |
+
</div>
|
| 957 |
+
</div>
|
| 958 |
+
|
| 959 |
+
<div class="art-style-card">
|
| 960 |
+
<h3 class="art-style-title"><i class="fas fa-paint-brush"></i>Stylized Sci-Fi</h3>
|
| 961 |
+
<p>A more artistic approach with exaggerated shapes and simplified details. Uses bold colors and clear silhouettes to ensure excellent gameplay readability.</p>
|
| 962 |
+
<div class="art-style-pros">
|
| 963 |
+
<div class="pro-item">Excellent gameplay clarity</div>
|
| 964 |
+
<div class="pro-item">More forgiving asset production</div>
|
| 965 |
+
<div class="pro-item">Timeless artistic appeal</div>
|
| 966 |
+
</div>
|
| 967 |
+
</div>
|
| 968 |
+
</div>
|
| 969 |
|
| 970 |
+
<h3>Sound Design</h3>
|
| 971 |
+
<p>The audio experience will complement the visual style with:</p>
|
| 972 |
+
<ul>
|
| 973 |
+
<li><strong>Distinct Weapon Sounds:</strong> Each weapon type has unique auditory feedback that matches its visual effects</li>
|
| 974 |
+
<li><strong>Alien Vocalizations:</strong> Enemy sounds telegraph their actions and increase tension</li>
|
| 975 |
+
<li><strong>Dynamic Music:</strong> Adaptive soundtrack that shifts intensity with combat encounters</li>
|
| 976 |
+
<li><strong>Environmental Audio:</strong> Ambient sounds that sell the sci-fi setting and alert players to dangers</li>
|
| 977 |
+
</ul>
|
| 978 |
+
|
| 979 |
+
<h3>Dynamic Lighting</h3>
|
| 980 |
+
<p>The game will use lighting strategically to:</p>
|
| 981 |
+
<ul>
|
| 982 |
+
<li>Guide player attention toward important elements</li>
|
| 983 |
+
<li>Create atmosphere and mood</li>
|
| 984 |
+
<li>Enhance gameplay clarity (e.g. enemy attack wind-ups)</li>
|
| 985 |
+
<li>Support dramatic moments (pulsing emergency lights, etc.)</li>
|
| 986 |
+
</ul>
|
| 987 |
+
</section>
|
| 988 |
+
</main>
|
| 989 |
+
|
| 990 |
+
<footer style="background: var(--darker); padding: 3rem 0; text-align: center; margin-top: 3rem;">
|
| 991 |
+
<div class="container">
|
| 992 |
+
<h2>NEBULA STORM</h2>
|
| 993 |
+
<p>© 2024 Cosmic Forge Games</p>
|
| 994 |
+
<div style="margin-top: 2rem; display: flex; justify-content: center; gap: 2rem;">
|
| 995 |
+
<a href="#" style="color: var(--light); font-size: 1.5rem;"><i class="fab fa-twitter"></i></a>
|
| 996 |
+
<a href="#" style="color: var(--light); font-size: 1.5rem;"><i class="fab fa-discord"></i></a>
|
| 997 |
+
<a href="#" style="color: var(--light); font-size: 1.5rem;"><i class="fab fa-steam"></i></a>
|
| 998 |
+
</div>
|
| 999 |
+
</div>
|
| 1000 |
+
</footer>
|
| 1001 |
+
|
| 1002 |
+
<script>
|
| 1003 |
+
// Tab functionality
|
| 1004 |
+
function openTab(evt, tabName) {
|
| 1005 |
+
// Get all tab content and hide them
|
| 1006 |
+
var tabcontent = document.getElementsByClassName("tab-content");
|
| 1007 |
+
for (var i = 0; i < tabcontent.length; i++) {
|
| 1008 |
+
tabcontent[i].classList.remove("active");
|
| 1009 |
+
}
|
| 1010 |
|
| 1011 |
+
// Get all tab buttons and remove 'active' class
|
| 1012 |
+
var tabbuttons = document.getElementsByClassName("tab-button");
|
| 1013 |
+
for (var i = 0; i < tabbuttons.length; i++) {
|
| 1014 |
+
tabbuttons[i].classList.remove("active");
|
| 1015 |
+
}
|
| 1016 |
|
| 1017 |
+
// Show the current tab and add 'active' class to the button
|
| 1018 |
+
document.getElementById(tabName).classList.add("active");
|
| 1019 |
+
evt.currentTarget.classList.add("active");
|
| 1020 |
}
|
| 1021 |
|
| 1022 |
+
// Section navigation highlighting
|
| 1023 |
+
window.addEventListener('scroll', function() {
|
| 1024 |
+
var sections = document.querySelectorAll('section');
|
| 1025 |
+
var navLinks = document.querySelectorAll('.section-nav a');
|
| 1026 |
+
var currentSection = '';
|
| 1027 |
+
|
| 1028 |
+
sections.forEach(function(section) {
|
| 1029 |
+
var sectionTop = section.offsetTop;
|
| 1030 |
+
var sectionHeight = section.clientHeight;
|
| 1031 |
+
if (window.pageYOffset >= (sectionTop - 200)) {
|
| 1032 |
+
currentSection = section.getAttribute('id');
|
| 1033 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1034 |
});
|
| 1035 |
|
| 1036 |
+
navLinks.forEach(function(link) {
|
| 1037 |
+
link.classList.remove('active');
|
| 1038 |
+
if (link.getAttribute('href') === '#' + currentSection) {
|
| 1039 |
+
link.classList.add('active');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1040 |
}
|
| 1041 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1042 |
|
| 1043 |
+
// Show/hide nav based on scroll position
|
| 1044 |
+
var sectionNav = document.querySelector('.section-nav');
|
| 1045 |
+
if (window.pageYOffset > 600) {
|
| 1046 |
+
sectionNav.style.display = 'block';
|
| 1047 |
+
} else {
|
| 1048 |
+
sectionNav.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1049 |
}
|
| 1050 |
+
});
|
| 1051 |
+
|
| 1052 |
+
// Smooth scrolling for navigation
|
| 1053 |
+
document.querySelectorAll('.section-nav a').forEach(anchor => {
|
| 1054 |
+
anchor.addEventListener('click', function(e) {
|
| 1055 |
+
e.preventDefault();
|
| 1056 |
+
|
| 1057 |
+
const targetId = this.getAttribute('href');
|
| 1058 |
+
if (targetId === '#') return;
|
| 1059 |
+
|
| 1060 |
+
const targetElement = document.querySelector(targetId);
|
| 1061 |
+
if (targetElement) {
|
| 1062 |
+
window.scrollTo({
|
| 1063 |
+
top: targetElement.offsetTop - 80,
|
| 1064 |
+
behavior: 'smooth'
|
| 1065 |
+
});
|
|
|
|
|
|
|
| 1066 |
}
|
| 1067 |
});
|
| 1068 |
+
});
|
| 1069 |
+
|
| 1070 |
+
// Animate progress bars on scroll
|
| 1071 |
+
function animateProgressBars() {
|
| 1072 |
+
const progressBars = document.querySelectorAll('.progress-fill');
|
| 1073 |
+
progressBars.forEach(bar => {
|
| 1074 |
+
const width = bar.style.width;
|
| 1075 |
+
bar.style.width = '0';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1076 |
|
| 1077 |
+
setTimeout(() => {
|
| 1078 |
+
bar.style.width = width;
|
| 1079 |
+
}, 100);
|
| 1080 |
+
});
|
|
|
|
| 1081 |
}
|
| 1082 |
|
| 1083 |
+
// Intersection Observer for progress bars
|
| 1084 |
+
const observer = new IntersectionObserver((entries) => {
|
| 1085 |
+
entries.forEach(entry => {
|
| 1086 |
+
if (entry.isIntersecting) {
|
| 1087 |
+
animateProgressBars();
|
| 1088 |
+
observer.unobserve(entry.target);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1089 |
}
|
| 1090 |
+
});
|
| 1091 |
+
}, {threshold: 0.5});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1092 |
|
| 1093 |
+
document.querySelectorAll('.progress-bar').forEach(bar => {
|
| 1094 |
+
observer.observe(bar);
|
| 1095 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1096 |
|
| 1097 |
+
// Initialize - make sure first tab is active
|
| 1098 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 1099 |
+
animateProgressBars();
|
| 1100 |
+
});
|
| 1101 |
+
</script>
|
| 1102 |
+
</body>
|
| 1103 |
</html>
|