Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Deep Sea Breakout - Stable Rebuild</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Orbitron:wght@400;700&display=swap'); | |
| :root { | |
| --game-width: 800px; | |
| --game-height: 600px; | |
| } | |
| body { | |
| margin: 0; padding: 0; | |
| background: radial-gradient(ellipse at bottom, #1B2735 0%, #090A0F 100%); | |
| display: flex; justify-content: center; align-items: center; | |
| min-height: 100vh; font-family: 'Orbitron', sans-serif; | |
| overflow: hidden; color: #e0f7ff; | |
| } | |
| .stars-bg { | |
| position: fixed; top: 0; left: 0; width: 100%; height: 100%; | |
| z-index: -1; | |
| } | |
| .stars-bg::before, .stars-bg::after { | |
| content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; | |
| background-image: | |
| radial-gradient(1px 1px at 20% 30%, white, rgba(255,255,255,0)), | |
| radial-gradient(1px 1px at 50% 70%, white, rgba(255,255,255,0)), | |
| radial-gradient(2px 2px at 80% 40%, white, rgba(255,255,255,0)), | |
| radial-gradient(1px 1px at 10% 90%, white, rgba(255,255,255,0)), | |
| radial-gradient(2px 2px at 90% 10%, white, rgba(255,255,255,0)); | |
| background-size: 200px 200px; animation: twinkle 15s linear infinite alternate; opacity: 0.2; /* Reduced base opacity */ | |
| } | |
| .stars-bg::after { background-size: 300px 300px; animation-delay: -7.5s; opacity: 0.15; } | |
| @keyframes twinkle { | |
| 0% { opacity: inherit; transform: translateY(0px); } /* Inherit from parent */ | |
| 50% { opacity: 0.8; transform: translateY(-5px); } | |
| 100% { opacity: inherit; transform: translateY(0px); } | |
| } | |
| .game-container { | |
| position: relative; | |
| width: var(--game-width); | |
| height: var(--game-height); | |
| border: 3px solid #0ff; border-radius: 12px; | |
| box-shadow: 0 0 30px rgba(0, 255, 255, 0.5), 0 0 10px rgba(0,255,255,0.7) inset; | |
| background: rgba(0, 10, 20, 0.85); overflow: hidden; | |
| } | |
| canvas#gameCanvas { | |
| display: block; width: 100%; height: 100%; | |
| } | |
| .ui { | |
| position: absolute; top: 10px; left: 10px; right: 10px; | |
| display: flex; justify-content: space-between; | |
| color: #0ff; font-size: calc(var(--game-height) * 0.03); | |
| font-weight: bold; text-shadow: 0 0 10px rgba(0, 255, 255, 0.8); | |
| pointer-events: none; font-family: 'Orbitron', sans-serif; z-index: 3; | |
| } | |
| .ui > div { | |
| background: rgba(0, 40, 80, 0.7); /* Slightly more opaque */ | |
| padding: calc(var(--game-height) * 0.008) calc(var(--game-height) * 0.025); | |
| border-radius: 20px; border: 1px solid rgba(0, 255, 255, 0.4); /* More visible border */ | |
| margin: 0 3px; white-space: nowrap; | |
| } | |
| .dialog-box, .menu, .settings-panel, .leaderboard, .shop, .daily-challenge, | |
| .achievement-system, .tutorial-screen, .stats-screen, .credits, .customization, | |
| .profile, .multiplayer-info, .matchmaking, .matchmaking-invite, .matchmaking-scoreboard, | |
| #difficultySelectScreen | |
| { | |
| position: absolute; top: 50%; left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(0, 8, 20, 0.98); color: #0ff; /* Darker, more opaque */ | |
| padding: clamp(20px, 4vh, 30px); border-radius: 15px; | |
| text-align: center; border: 2px solid #0ff; | |
| box-shadow: 0 0 35px rgba(0, 255, 255, 0.7), 0 0 25px #0af inset; | |
| z-index: 20; width: 85%; max-width: 500px; | |
| backdrop-filter: blur(5px); display: none; | |
| max-height: 85vh; overflow-y: auto; | |
| } | |
| .dialog-box h1, .dialog-box h2, .menu-title, .settings-header, .leaderboard-title, | |
| .shop-title, .daily-challenge-title, .achievement-system-title, .tutorial-screen-title, | |
| .stats-screen-title, .credits-title, .customization-title, .profile-title, | |
| .multiplayer-title, .matchmaking-title, .matchmaking-scoreboard-title, | |
| #difficultySelectScreen h2 { | |
| font-family: 'Press Start 2P', cursive; | |
| font-size: clamp(18px, 3.2vw, 26px); /* Slightly smaller for balance */ | |
| margin-bottom: 20px; text-shadow: 0 0 10px #0ff; color: #9ff; /* Brighter title */ | |
| } | |
| .dialog-box p { | |
| font-size: clamp(13px, 2.1vw, 15px); margin-bottom: 18px; line-height: 1.55; color: #dff; /* Brighter text */ | |
| } | |
| .start-screen { display: flex; flex-direction: column; align-items: center; } | |
| .pause-message { /* Style seems fine */ | |
| position: absolute; top: 50%; left: 50%; | |
| transform: translate(-50%, -50%); color: #f0f; | |
| font-size: clamp(40px, 8vw, 60px); font-weight: bold; | |
| text-shadow: 0 0 15px #f0f, 0 0 30px #f0f; pointer-events: none; | |
| display: none; font-family: 'Press Start 2P', cursive; z-index: 100; | |
| animation: pulse 1.5s infinite alternate; | |
| } | |
| @keyframes pulse { | |
| from { opacity: 0.6; transform: translate(-50%, -50%) scale(1); } | |
| to { opacity: 1; transform: translate(-50%, -50%) scale(1.05); } | |
| } | |
| .powerup-indicator { /* Styles seem fine */ | |
| position: absolute; bottom: calc(var(--game-height) * 0.08); | |
| right: 10px; display: flex; flex-direction: column; gap: 8px; z-index: 5; | |
| } | |
| .powerup-icon { /* Styles seem fine */ | |
| width: clamp(30px, 4vw, 40px); height: clamp(30px, 4vw, 40px); | |
| background: rgba(0, 40, 80, 0.8); border-radius: 50%; | |
| display: flex; align-items: center; justify-content: center; | |
| font-weight: bold; font-size: clamp(12px, 2vw, 16px); | |
| border: 1px solid rgba(0, 255, 255, 0.6); position: relative; color: #fff; | |
| box-shadow: 0 0 8px rgba(0,255,255,0.4); | |
| } | |
| .powerup-icon::after { /* Timer bar */ /* Styles seem fine */ | |
| content: ''; position: absolute; bottom: -4px; left: 50%; transform: translateX(-50%); | |
| height: 5px; background: #0f0; border-radius: 2px; | |
| transition: width 0.1s linear; width: var(--time-left, 100%); | |
| } | |
| .powerup-icon.rare::before { /* Styles seem fine */ | |
| content: ''; position: absolute; inset: -3px; border-radius: 50%; | |
| border: 2px solid gold; animation: rareGlow 1s infinite alternate; opacity: 0.9; | |
| } | |
| @keyframes rareGlow { | |
| from { box-shadow: 0 0 6px gold, 0 0 12px yellow; } | |
| to { box-shadow: 0 0 12px gold, 0 0 18px yellow; } | |
| } | |
| .button, .menu-button, .sound-button, .save-button, .item-button, .challenge-button, | |
| .profile-button, .matchmaking-button, .matchmaking-control-button, .matchmaking-invite-button, | |
| .theme-button { /* Styles seem fine */ | |
| background: linear-gradient(45deg, #0077aa, #00aadd); | |
| border: 1px solid #00ccff; color: white; | |
| padding: clamp(10px, 1.8vh, 12px) clamp(18px, 2.5vw, 24px); | |
| margin: 8px 5px; border-radius: 8px; cursor: pointer; | |
| font-size: clamp(13px, 2.2vw, 16px); | |
| transition: all 0.25s ease-out; | |
| box-shadow: 0 4px 12px rgba(0, 180, 220, 0.3), 0 0 4px rgba(0,255,255,0.4) inset; | |
| font-family: 'Orbitron', sans-serif; text-transform: uppercase; | |
| letter-spacing: 0.5px; position: relative; overflow: hidden; | |
| } | |
| .button::before, .menu-button::before, .sound-button::before, .save-button::before, | |
| .item-button::before, .challenge-button::before, .profile-button::before, | |
| .matchmaking-button::before, .matchmaking-control-button::before, .matchmaking-invite-button::before, | |
| .theme-button::before { /* Styles seem fine */ | |
| content: ''; position: absolute; top: -50%; left: -150%; width: 50%; height: 200%; | |
| background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.3) 50%, rgba(255,255,255,0) 100%); | |
| transform: skewX(-25deg); transition: left 0.5s ease-in-out; | |
| } | |
| .button:hover, .menu-button:hover, .sound-button:hover, .save-button:hover, | |
| .item-button:hover, .challenge-button:hover, .profile-button:hover, | |
| .matchmaking-button:hover, .matchmaking-control-button:hover, .matchmaking-invite-button:hover, | |
| .theme-button:hover { /* Styles seem fine */ | |
| transform: translateY(-2px) scale(1.02); | |
| box-shadow: 0 6px 18px rgba(0, 255, 255, 0.6), 0 0 8px rgba(128,255,255,0.6) inset; | |
| background: linear-gradient(45deg, #0088bb, #00bbff); | |
| } | |
| .button:hover::before, .menu-button:hover::before, .sound-button:hover::before, | |
| .save-button:hover::before, .item-button:hover::before, .challenge-button:hover::before, | |
| .profile-button:hover::before, .matchmaking-button:hover::before, .matchmaking-control-button:hover::before, | |
| .matchmaking-invite-button:hover::before, .theme-button:hover::before { left: 150%; } | |
| .button:active, .menu-button:active, .sound-button:active, .save-button:active, | |
| .item-button:active, .challenge-button:active, .profile-button:active, | |
| .matchmaking-button:active, .matchmaking-control-button:active, .matchmaking-invite-button:active, | |
| .theme-button:active { transform: translateY(0px) scale(0.98); } | |
| .instructions { /* Style seems fine */ | |
| background: rgba(0, 20, 40, 0.7); padding: 15px; border-radius: 10px; | |
| margin-top: 20px; border: 1px solid rgba(0, 255, 255, 0.3); | |
| font-size: clamp(12px, 2vw, 14px); | |
| } | |
| .controls { /* Style seems fine */ | |
| position: absolute; bottom: 5px; left: 50%; transform: translateX(-50%); | |
| color: #7ff; font-size: clamp(10px, 1.8vw, 12px); text-align: center; | |
| width: 90%; max-width: 450px; background: rgba(0, 20, 40, 0.8); | |
| padding: 6px; border-radius: 20px; border: 1px solid rgba(0, 255, 255, 0.4); | |
| z-index: 5; | |
| } | |
| .combo-meter { /* Style seems fine */ | |
| position: absolute; top: 45%; left: 50%; | |
| transform: translate(-50%, -50%) scale(0.8); | |
| font-size: clamp(30px, 7vw, 45px); font-weight: bold; color: #ff0; | |
| text-shadow: 0 0 10px #ff0, 0 0 20px #f90, 3px 3px 0px #a50; | |
| opacity: 0; pointer-events: none; z-index: 4; | |
| font-family: 'Press Start 2P', cursive; | |
| transition: opacity 0.3s ease-out, transform 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28); | |
| } | |
| .achievement-popup, .notification { /* Styles seem fine */ | |
| position: fixed; top: 20px; right: 20px; | |
| background: rgba(10, 30, 50, 0.9); color: #fff; | |
| padding: 15px 20px; border-radius: 10px; border: 2px solid #0ff; | |
| box-shadow: 0 0 15px rgba(0, 255, 255, 0.5); font-family: 'Orbitron', sans-serif; | |
| z-index: 100; opacity: 0; transform: translateX(100%); | |
| transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |
| width: auto; max-width: 300px; | |
| } | |
| .achievement-popup.show, .notification.show { opacity: 1; transform: translateX(0); } | |
| .achievement-icon, .notification-icon { /* Styles seem fine */ | |
| width: 30px; height: 30px; background: #0ff; border-radius: 50%; | |
| display: flex; align-items: center; justify-content: center; | |
| float: left; margin-right: 10px; font-size: 16px; color: #036; | |
| } | |
| .achievement-title, .notification-title { font-weight: bold; font-size: 16px; margin-bottom: 5px; color: #0ff; } | |
| .achievement-description, .notification-description { font-size: 12px; color: #ccc; } | |
| .level-transition, .boss-intro, .boss-defeated, .matchmaking-result, .loading-screen { /* Styles seem fine */ | |
| position: fixed; top: 0; left: 0; width: 100%; height: 100%; | |
| background: rgba(0, 5, 10, 0.9); display: flex; flex-direction: column; | |
| align-items: center; justify-content: center; | |
| font-family: 'Press Start 2P', cursive; font-size: clamp(30px, 6vw, 48px); | |
| color: #0ff; z-index: 1000; opacity: 0; visibility: hidden; | |
| transition: opacity 0.4s ease, visibility 0.4s; | |
| } | |
| .level-transition.active, .boss-intro.active, .boss-defeated.active, | |
| .matchmaking-result.active, .loading-screen:not(.hidden) { opacity: 1; visibility: visible; } | |
| .level-number, .boss-name-large, .boss-defeated-text, .matchmaking-result-text { animation: popIn 0.8s ease-in-out; } | |
| @keyframes popIn { | |
| 0% { transform: scale(0.3) rotate(-15deg); opacity: 0; } | |
| 60% { transform: scale(1.1) rotate(5deg); opacity: 1; } | |
| 100% { transform: scale(1) rotate(0deg); opacity: 1; } | |
| } | |
| .health-bar { /* Styles seem fine */ | |
| position: absolute; top: calc(var(--game-height) * 0.03); left: calc(var(--game-height) * 0.025); | |
| width: clamp(100px, 20vw, 150px); height: calc(var(--game-height) * 0.02); | |
| background: rgba(100, 0, 0, 0.5); border: 1px solid #f00; | |
| border-radius: 5px; overflow: hidden; z-index: 3; | |
| } | |
| .health-fill { /* Styles seem fine */ | |
| height: 100%; width: 100%; | |
| background: linear-gradient(to right, #ff3333, #ff9933); | |
| border-radius: 4px 0 0 4px; | |
| transition: width 0.3s ease-in-out; | |
| } | |
| .glow-border::before { /* Styles seem fine */ | |
| content: ""; position: absolute; top: -3px; left: -3px; right: -3px; bottom: -3px; | |
| border-radius: 14px; | |
| background: repeating-linear-gradient( 45deg, | |
| rgba(0,255,255,0) 0%, rgba(0,255,255,0) 5%, | |
| rgba(0,255,255,0.2) 10%, rgba(0,255,255,0.2) 15%, | |
| rgba(0,255,255,0) 20%, rgba(0,255,255,0) 100% | |
| ); | |
| background-size: 200% 200%; | |
| animation: glowBorderMove 4s linear infinite; | |
| pointer-events: none; z-index: 0; | |
| } | |
| @keyframes glowBorderMove { | |
| 0% { background-position: 0% 0%; } | |
| 100% { background-position: -200% -200%; } | |
| } | |
| .vignette { /* Style seems fine */ | |
| position: absolute; top:0; left:0; width:100%; height:100%; | |
| pointer-events:none; box-shadow: inset 0 0 100px rgba(0,0,0,0.6); | |
| z-index:1; | |
| } | |
| .radial-glow { /* Style seems fine */ | |
| position: absolute; top:0; left:0; width:100%; height:100%; | |
| background: radial-gradient(ellipse at center, rgba(0,128,255,0.08) 0%, rgba(0,0,0,0) 70%); | |
| pointer-events:none; z-index:0; | |
| } | |
| .water-effect { /* Style seems fine */ | |
| position: absolute; bottom:0; left:0; width:100%; height:15%; | |
| background: linear-gradient(to top, rgba(0,100,150,0.15), transparent); | |
| pointer-events:none; z-index:0; overflow: hidden; | |
| } | |
| .water-effect::after { /* Style seems fine */ | |
| content:""; position: absolute; top: 0; left: -50%; width: 200%; height: 100%; | |
| background: repeating-linear-gradient(0deg, | |
| rgba(100,200,255,0.05) 0px, rgba(100,200,255,0.05) 1px, | |
| transparent 1px, transparent 15px); | |
| animation: waterRipple 8s linear infinite; | |
| } | |
| @keyframes waterRipple { | |
| 0% { transform: translateY(0%) translateX(0%); } | |
| 50% { transform: translateY(-10%) translateX(2%); } | |
| 100% { transform: translateY(0%) translateX(0%); } | |
| } | |
| .depth-meter { /* Style seems fine */ | |
| position: absolute; top: calc(var(--game-height) * 0.1); right: 15px; | |
| width: calc(var(--game-height) * 0.03); height: calc(var(--game-height) * 0.3); | |
| background: rgba(0,20,40,0.7); border: 2px solid #0ff; border-radius: 10px; | |
| overflow: hidden; z-index:3; | |
| } | |
| .depth-indicator { /* Style seems fine */ | |
| position:absolute; bottom:0; left:0; width:100%; height:0%; | |
| background: linear-gradient(to top, #00BFFF, #00FFFF); | |
| transition: height 0.5s ease-out; | |
| } | |
| .level-progress { /* Style seems fine */ | |
| position: absolute; bottom: calc(var(--game-height) * 0.08); | |
| left: 10px; width: clamp(100px, 18vw, 150px); height: calc(var(--game-height) * 0.025); | |
| background: rgba(0,20,40,0.7); border: 1px solid #0ff; border-radius: 10px; | |
| overflow: hidden; z-index:3; | |
| } | |
| .level-progress-bar { /* Style seems fine */ | |
| height:100%; width:0%; | |
| background: linear-gradient(to right, #00FFFF, #00BFFF); | |
| border-radius: 9px 0 0 9px; | |
| transition: width 0.3s ease-out; | |
| } | |
| .boss-health-container { /* Style seems fine */ | |
| position: absolute; top: calc(var(--game-height) * 0.03); left: 50%; | |
| transform: translateX(-50%); width: 60%; max-width: 400px; | |
| height: calc(var(--game-height) * 0.025); | |
| background: rgba(100,0,0,0.5); border: 2px solid #f00; | |
| border-radius: 10px; z-index:3; display: none; | |
| } | |
| .boss-health-bar { /* Style seems fine */ | |
| height:100%; width:100%; | |
| background: linear-gradient(to right, #ff3333, #ff9933); | |
| border-radius: 8px 0 0 8px; | |
| transition: width 0.3s ease-out; | |
| } | |
| .boss-name { /* Style seems fine */ | |
| position: absolute; top: calc(var(--game-height) * 0.03 + var(--game-height) * 0.025 + 5px); | |
| left: 50%; transform: translateX(-50%); | |
| color: #f33; font-size: clamp(12px, 2vw, 14px); font-weight: bold; | |
| text-shadow: 0 0 5px #f00; z-index:3; display:none; | |
| background-color: rgba(0,0,0,0.5); padding: 2px 8px; border-radius: 5px; | |
| } | |
| .boss-warning { /* Style seems fine */ | |
| position: absolute; top: calc(var(--game-height) * 0.1); left: 50%; | |
| transform: translateX(-50%); color: #f00; font-size: clamp(16px, 3vw, 20px); | |
| font-weight: bold; text-shadow: 0 0 8px #f00; z-index:5; display:none; | |
| animation: pulseWarning 1s infinite alternate; | |
| } | |
| @keyframes pulseWarning { from { opacity: 0.7; } to { opacity: 1; } } | |
| .tutorial { /* Style seems fine */ | |
| position: absolute; bottom: calc(var(--game-height) * 0.15); left: 50%; | |
| transform: translateX(-50%); background: rgba(0,10,20,0.85); | |
| padding: 10px 15px; border-radius: 8px; border: 1px solid #0ff; | |
| font-size: clamp(12px, 2vw, 14px); color: #9ff; z-index:10; | |
| opacity:0; transition: opacity 0.5s, transform 0.5s; pointer-events: none; | |
| } | |
| .tutorial.show { opacity:1; transform: translateX(-50%) translateY(-10px); } | |
| .slider-container { /* Style seems fine */ | |
| width: 60%; background: rgba(0,40,80,0.6); border-radius: 10px; | |
| padding: 5px; border: 1px solid rgba(0,255,255,0.3); | |
| } | |
| input[type="range"].volume-slider { /* Style seems fine */ | |
| width: 100%; height: 8px; background: rgba(0,255,255,0.2); | |
| border-radius: 5px; outline: none; -webkit-appearance: none; appearance: none; | |
| } | |
| input[type="range"].volume-slider::-webkit-slider-thumb { /* Style seems fine */ | |
| -webkit-appearance: none; appearance: none; | |
| width: 18px; height: 18px; border-radius: 50%; | |
| background: #0ff; cursor: pointer; | |
| box-shadow: 0 0 5px #0ff; | |
| } | |
| input[type="range"].volume-slider::-moz-range-thumb { /* Style seems fine */ | |
| width: 18px; height: 18px; border-radius: 50%; | |
| background: #0ff; cursor: pointer; border: none; | |
| box-shadow: 0 0 5px #0ff; | |
| } | |
| .loading-bar { /* Style seems fine */ | |
| width: 60%; max-width: 300px; height: 15px; | |
| background: rgba(0,40,80,0.7); border-radius: 10px; | |
| margin-top: 20px; overflow:hidden; border: 1px solid #08a; | |
| } | |
| .loading-progress { /* Style seems fine */ | |
| height:100%; width:0%; | |
| background: linear-gradient(to right, #0ff, #0aa); | |
| transition: width 0.2s ease-out; border-radius: 9px 0 0 9px; | |
| } | |
| .leaderboard-table { /* Style seems fine */ width: 100%; border-collapse: collapse; } | |
| .leaderboard-table th, .leaderboard-table td { /* Style seems fine */ | |
| padding: 8px; border-bottom: 1px solid rgba(0,255,255,0.2); | |
| font-size: clamp(12px, 2vw, 14px); | |
| } | |
| .leaderboard-table th { background:rgba(0,40,80,0.6); text-transform: uppercase; } | |
| .leaderboard-table tr:last-child td { border-bottom:none; } | |
| .leaderboard-table tr:nth-child(even) { background-color: rgba(0,30,60,0.3); } | |
| .leaderboard-table td:first-child { width: 10%; text-align: center; } | |
| .leaderboard-table td:last-child { text-align: right; } | |
| .skin-selector { /* Style seems fine */ display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; justify-content: center;} | |
| .skin { /* Style seems fine */ | |
| width: 50px; height: 25px; | |
| border-radius: 5px; display: flex; align-items: center; justify-content: center; | |
| cursor: pointer; transition: all 0.2s; border: 2px solid transparent; | |
| } | |
| .skin.selected { transform: scale(1.1); border-color: #f0f; box-shadow: 0 0 10px #f0f; } | |
| .skin span { font-size: 10px; color: white; text-shadow: 1px 1px 1px black;} | |
| .dialog-box .button-group { /* Style seems fine */ display: flex; justify-content: center; gap: 10px; margin-top: 15px; flex-wrap: wrap;} | |
| .placeholder-content p { /* Style seems fine */ margin-top: 15px; color: #789; font-style: italic; } | |
| /* Ensure tooltip is above other game UI but below dialogs */ | |
| .tooltip { | |
| position: fixed; /* Use fixed if it needs to escape canvas bounds */ | |
| background: rgba(10,20,30,0.9); color: #0ff; | |
| padding: 6px 10px; border-radius: 5px; | |
| font-size: 12px; z-index: 50; /* Above game UI, below dialogs */ | |
| pointer-events: none; opacity: 0; | |
| transition: opacity 0.2s; | |
| border: 1px solid #0aa; | |
| box-shadow: 0 0 5px #0aa; | |
| } | |
| .tooltip.show { opacity: 1; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="stars-bg"></div> | |
| <div class="game-container"> | |
| <div class="glow-border"></div> | |
| <div class="radial-glow"></div> | |
| <canvas id="gameCanvas"></canvas> | |
| <div class="ui"> | |
| <div class="health-bar" id="healthBarContainer"><div class="health-fill" id="healthFill"></div></div> | |
| <div>Score: <span id="score">0</span></div> | |
| <div>Lvl: <span id="level">1</span>/<span id="maxLevels">10</span></div> | |
| <div>Combo: <span id="combo">0x</span></div> | |
| </div> | |
| <div class="depth-meter"><div class="depth-indicator" id="depthIndicator"></div></div> | |
| <div class="level-progress"><div class="level-progress-bar" id="levelProgressBar"></div></div> | |
| <div class="boss-health-container" id="bossHealthContainer"> | |
| <div class="boss-health-bar" id="bossHealthBar"></div> | |
| </div> | |
| <div class="boss-name" id="bossName">BOSS NAME</div> | |
| <div class="boss-warning" id="bossWarning">!!! BOSS APPROACHING !!!</div> | |
| <div class="powerup-indicator" id="powerupIndicator"></div> | |
| <div class="dialog-box start-screen" id="mainMenu"> | |
| <h1>DEEP SEA BREAKOUT</h1> | |
| <p>Navigate treacherous depths, clear coral, and face menacing bosses. Collect power-ups to survive!</p> | |
| <div class="button-group"> | |
| <button class="button" onclick="window.showDifficultySelect()">Start Game</button> <!-- Ensure window scope --> | |
| <button class="button" onclick="window.showScreen('settingsPanel')">Settings</button> | |
| </div> | |
| <div class="button-group"> | |
| <button class="button" onclick="window.showScreen('leaderboardScreen')">Leaderboard</button> | |
| <button class="button" onclick="window.showScreen('tutorialScreenContent')">How to Play</button> | |
| </div> | |
| <div class="button-group"> | |
| <button class="button" onclick="window.showScreen('statsScreen')">Player Stats</button> | |
| <button class="button" onclick="window.showScreen('customizationScreen')">Customize</button> | |
| </div> | |
| <div class="button-group"> | |
| <button class="button" onclick="window.showScreen('achievementsScreen')">Achievements</button> | |
| <button class="button" onclick="window.showScreen('creditsScreen')">Credits</button> | |
| </div> | |
| <div class="button-group"> | |
| <button class="button" onclick="window.showScreenWithPlaceholder('shopScreen', 'Item Shop')">Shop</button> | |
| <button class="button" onclick="window.showScreenWithPlaceholder('dailyChallengeScreen', 'Daily Challenges')">Challenges</button> | |
| </div> | |
| <div class="instructions" style="margin-top:15px; font-size: 12px;"> | |
| <p><strong>Quick Controls:</strong> Mouse: Move | Click: Launch/Laser | P: Pause | M: Mute</p> | |
| </div> | |
| </div> | |
| <div class="dialog-box" id="difficultySelectScreen" style="display: none;"> | |
| <h2>Select Difficulty</h2> | |
| <div class="button-group"> | |
| <button class="button" onclick="initGame('normal')">Normal</button> | |
| <button class="button" onclick="initGame('hardcore')">Hardcore</button> | |
| </div> | |
| <button class="button" onclick="window.showScreen('mainMenu')" style="margin-top: 15px;">Back</button> | |
| </div> | |
| <div class="pause-message" id="pauseMessage">PAUSED</div> | |
| <div class="combo-meter" id="comboMeter">0x COMBO!</div> | |
| <div class="dialog-box" id="gameOver" style="display: none;"> | |
| <h2>MISSION FAILED</h2> | |
| <p>Your score: <span id="finalScore">0</span></p> | |
| <p id="highScoreText" style="color: #facc15;"></p> | |
| <div class="button-group"> | |
| <button class="button" onclick="initGame(game.difficulty)">Try Again</button> | |
| <button class="button" onclick="window.showScreen('mainMenu')">Main Menu</button> | |
| </div> | |
| </div> | |
| <div class="dialog-box" id="gameWon" style="display: none;"> | |
| <h2>MISSION COMPLETE!</h2> | |
| <p>You conquered all <span id="totalLevelsWon">10</span> ocean levels!</p> | |
| <p>Final score: <span id="winScore">0</span></p> | |
| <p id="winHighScoreText" style="color: #facc15;"></p> | |
| <div class="button-group"> | |
| <button class="button" onclick="initGame(game.difficulty)">Play Again</button> | |
| <button class="button" onclick="window.showScreen('mainMenu')">Main Menu</button> | |
| </div> | |
| </div> | |
| <div class="controls" id="gameControls"> | |
| Mouse: Move โข Click: Launch/Laser โข P: Pause โข M: Mute | |
| </div> | |
| <div class="achievement-popup" id="achievementPopup"> | |
| <div class="achievement-icon" id="achievementPopupIcon">๐</div> | |
| <div> | |
| <div class="achievement-title" id="achievementPopupTitle">Achievement Unlocked!</div> | |
| <div class="achievement-description" id="achievementPopupDesc">You did something awesome!</div> | |
| </div> | |
| </div> | |
| <div class="notification" id="notificationPopup"> | |
| <div class="notification-icon" id="notificationPopupIcon">โน๏ธ</div> | |
| <div> | |
| <div class="notification-title" id="notificationPopupTitle">Notification</div> | |
| <div class="notification-description" id="notificationPopupDesc">Something happened.</div> | |
| </div> | |
| </div> | |
| <div class="level-transition" id="levelTransitionScreen"><div class="level-number" id="levelTransitionNumber">LEVEL 1</div></div> | |
| <div class="boss-intro" id="bossIntroScreen"><div class="boss-name-large" id="bossIntroName">BOSS NAME</div></div> | |
| <div class="boss-defeated" id="bossDefeatedScreen"><div class="boss-defeated-text" id="bossDefeatedName">BOSS DEFEATED!</div></div> | |
| <div class="tutorial" id="tutorialPopup"><span id="tutorialText">Move mouse to control paddle!</span></div> | |
| <div class="tooltip" id="tooltip" style="display:none;">Tooltip text</div> | |
| <div class="settings-panel" id="settingsPanel"> | |
| <h2 class="settings-header">Settings</h2> | |
| <div class="setting-row"> | |
| <span class="setting-label">Master Volume:</span> | |
| <div class="slider-container"> | |
| <input type="range" min="0" max="1" step="0.01" value="0.4" class="volume-slider" id="masterVolumeSlider"> | |
| </div> | |
| </div> | |
| <div class="setting-row"> | |
| <span class="setting-label">SFX Volume:</span> | |
| <div class="slider-container"> | |
| <input type="range" min="0" max="1" step="0.01" value="0.5" class="volume-slider" id="sfxVolumeSlider"> | |
| </div> | |
| </div> | |
| <div class="setting-row"> | |
| <span class="setting-label">Visual Theme:</span> | |
| <select id="themeSelector" class="button" style="padding: 5px 10px; text-transform: none; background: #005577; border-color: #0088aa;"> | |
| <option value="deepSea">Deep Sea</option> | |
| <option value="volcanic">Volcanic Depths</option> | |
| <option value="crystalCaverns">Crystal Caverns</option> | |
| </select> | |
| </div> | |
| <div class="sound-test" style="margin-top: 15px;"> | |
| <button class="sound-button" onclick="audioManager.playSound(audioManager.definitions.blockHit)">Test Hit</button> | |
| <button class="sound-button" onclick="audioManager.playSound(audioManager.definitions.powerUpCollect)">Test PU</button> | |
| </div> | |
| <button class="button" onclick="window.showScreen('mainMenu')" style="margin-top: 20px;">Back to Menu</button> | |
| </div> | |
| <div class="leaderboard dialog-box" id="leaderboardScreen"> | |
| <h2 class="leaderboard-title">High Scores</h2> | |
| <table class="leaderboard-table" id="leaderboardTable"> | |
| <thead><tr><th>Rank</th><th>Name</th><th>Score</th></tr></thead> | |
| <tbody></tbody> | |
| </table> | |
| <button class="button" onclick="window.showScreen('mainMenu')" style="margin-top: 20px;">Back to Menu</button> | |
| </div> | |
| <div class="dialog-box" id="placeholderDialog" style="display: none;"> | |
| <h2 id="placeholderTitle">Feature Not Available</h2> | |
| <p id="placeholderMessage">This feature is currently under construction or planned for a future update. Thanks for your interest!</p> | |
| <button class="button" onclick="window.showScreen('mainMenu')">Back to Menu</button> | |
| </div> | |
| <div class="achievement-system dialog-box" id="achievementsScreen"> | |
| <h2 class="achievement-system-title">Achievements</h2> | |
| <div id="achievementsListContainer" style="max-height: 300px; overflow-y: auto; text-align: left;"></div> | |
| <button class="button" onclick="window.showScreen('mainMenu')" style="margin-top: 20px;">Back</button> | |
| </div> | |
| <div class="tutorial-screen dialog-box" id="tutorialScreenContent"> | |
| <h2 class="tutorial-screen-title">How To Play</h2> | |
| <div style="text-align: left; font-size: clamp(12px, 2vw, 14px);"> | |
| <p><strong>Objective:</strong> Clear all blocks (coral creatures) on each level by hitting them with the pearl.</p> | |
| <p><strong>Controls:</strong></p> | |
| <ul> | |
| <li>- Move your mouse to control the paddle.</li> | |
| <li>- Click the left mouse button to launch the pearl or fire lasers (if active).</li> | |
| <li>- Press 'P' to pause or resume the game.</li> | |
| <li>- Press 'M' to mute or unmute game sounds.</li> | |
| </ul> | |
| <p><strong>Gameplay:</strong></p> | |
| <ul> | |
| <li>- Don't let the primary pearl fall below your paddle, or you'll lose a life!</li> | |
| <li>- Some blocks require multiple hits. Some creatures are dangerous!</li> | |
| <li>- Collect falling power-ups for special abilities. Rare power-ups are extra potent!</li> | |
| <li>- Build combos by hitting blocks consecutively for bonus points.</li> | |
| <li>- Defeat mighty bosses on special levels to progress further into the abyss.</li> | |
| </ul> | |
| <p>Good luck, brave diver!</p> | |
| </div> | |
| <button class="button" onclick="window.showScreen('mainMenu')" style="margin-top: 20px;">Back</button> | |
| </div> | |
| <div class="stats-screen dialog-box" id="statsScreen"> | |
| <h2 class="stats-screen-title">Player Statistics</h2> | |
| <div id="statsContainer" style="text-align: left;"></div> | |
| <button class="button" onclick="window.showScreen('mainMenu')" style="margin-top: 20px;">Back</button> | |
| </div> | |
| <div class="credits dialog-box" id="creditsScreen"> | |
| <h2 class="credits-title">Credits</h2> | |
| <p>Game Design & Development: You & AI Collaborator</p> | |
| <p>Inspired by: Classic Breakout / Arkanoid Games</p> | |
| <p>Fonts: Orbitron, Press Start 2P (Google Fonts)</p> | |
| <button class="button" onclick="window.showScreen('mainMenu')">Back</button> | |
| </div> | |
| <div class="customization dialog-box" id="customizationScreen"> | |
| <h2 class="customization-title">Customize Paddle</h2> | |
| <p>Select your paddle's appearance:</p> | |
| <div class="skin-selector" id="paddleSkinSelector"></div> | |
| <button class="button" onclick="window.showScreen('mainMenu')" style="margin-top: 20px;">Back</button> | |
| </div> | |
| <div class="loading-screen hidden" id="loadingScreen"> | |
| <div id="loadingText">LOADING...</div> | |
| <div class="loading-bar"><div class="loading-progress" id="loadingProgress"></div></div> | |
| </div> | |
| <div class="vignette"></div> | |
| <div class="water-effect"></div> | |
| </div> | |
| <script> | |
| // --- Game Setup --- | |
| const gameContainer = document.querySelector('.game-container'); | |
| const canvas = document.getElementById('gameCanvas'); | |
| const GAME_WIDTH = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--game-width')) || 800; | |
| const GAME_HEIGHT = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--game-height')) || 600; | |
| // These lines were correct, ensure they are applied. | |
| gameContainer.style.width = `${GAME_WIDTH}px`; | |
| gameContainer.style.height = `${GAME_HEIGHT}px`; | |
| canvas.width = GAME_WIDTH; | |
| canvas.height = GAME_HEIGHT; | |
| const ctx = canvas.getContext('2d'); | |
| const chroma = window.chroma || { /* Basic Chroma fallback */ | |
| contrast: (c1, c2) => { const lum = (c) => 0.2126*(c.r||0) + 0.7152*(c.g||0) + 0.0722*(c.b||0); const c1L=typeof c1==='string'?lum(chroma(c1)):lum(c1); const c2L=typeof c2==='string'?lum(chroma(c2)):lum(c2); return Math.abs(c1L-c2L)>100?7:1.5; }, | |
| (colorStr) => { let r=0,g=0,b=0,a=1; if(typeof colorStr!=='string')colorStr='#000'; if(colorStr.startsWith('#')){const h=colorStr.slice(1);if(h.length===3){r=parseInt(h[0]+h[0],16);g=parseInt(h[1]+h[1],16);b=parseInt(h[2]+h[2],16);}else if(h.length>=6){r=parseInt(h.substring(0,2),16);g=parseInt(h.substring(2,4),16);b=parseInt(h.substring(4,6),16);if(h.length===8)a=parseInt(h.substring(6,8),16)/255;}}else if(colorStr.startsWith('rgb')){const p=colorStr.match(/[\d.]+/g);if(p&&p.length>=3){r=+p[0];g=+p[1];b=+p[2];if(p.length===4)a=+p[3];}} return {r,g,b,a,darken:(am=1)=>`rgb(${Math.max(0,r-30*am)},${Math.max(0,g-30*am)},${Math.max(0,b-30*am)})`,brighten:(am=1)=>`rgb(${Math.min(255,r+30*am)},${Math.min(255,g+30*am)},${Math.min(255,b+30*am)})`,alpha:(nA)=>`rgba(${r},${g},${b},${nA})`,hex:()=>`#${r.toString(16).padStart(2,'0')}${g.toString(16).padStart(2,'0')}${b.toString(16).padStart(2,'0')}`,css:()=>`rgba(${r},${g},${b},${a})`}} | |
| }; | |
| class AudioManager { /* ... AudioManager from previous, assumed okay ... */ | |
| constructor(){this.audioContext=null;this.masterGain=null;this.sfxGain=null;this.isMuted=false;this.definitions={blockHit:{freq1:600,freq2:300,duration:.05,volume:.15,type:"triangle"},paddleHit:{freq1:440,freq2:440,duration:.05,volume:.2,type:"square"},wallHit:{freq1:200,freq2:150,duration:.05,volume:.1,type:"sawtooth"},loseLife:{freq1:300,freq2:50,duration:.5,volume:.3,type:"sawtooth"},levelUp:[{freq1:500,freq2:1E3,duration:.3,volume:.25,type:"sine",attack:.05,decay:.3},{freq1:300,freq2:600,duration:.3,volume:.15,type:"square",attack:.05,decay:.3,delay:.05}],powerUpSpawn:{freq1:700,freq2:900,duration:.1,volume:.2,type:"sine"},powerUpCollect:{freq1:800,freq2:1200,duration:.2,volume:.3,type:"triangle"},rarePowerUpCollect:[{freq1:1E3,freq2:2E3,duration:.4,volume:.4,type:"sine",decay:.4},{freq1:800,freq2:1500,duration:.3,volume:.3,type:"square",decay:.3,delay:.1}],combo:cC=>({freq1:Math.min(1200,300+25*cC),freq2:Math.min(1350,450+25*cC),duration:.15,volume:Math.min(.4,.15+.02*cC),type:"sine"}),gameOver:[{freq1:300,freq2:100,duration:1.5,volume:.4,type:"sine"},{freq1:200,freq2:50,duration:1,volume:.3,type:"square",delay:.2}],gameWon:[{freq1:500,freq2:1200,duration:1,volume:.4,type:"sine"},{freq1:800,freq2:1E3,duration:.2,volume:.25,type:"triangle",delay:.2},{freq1:900,freq2:1100,duration:.2,volume:.25,type:"triangle",delay:.4},{freq1:1E3,freq2:1200,duration:.2,volume:.25,type:"triangle",delay:.6}],laserFire:{freq1:1800,freq2:1E3,duration:.08,volume:.08,type:"sawtooth",attack:.005,decay:.04},blackHoleOpen:{freq1:100,freq2:30,duration:1.5,volume:.5,type:"sawtooth",decay:1.5},blackHoleAbsorb:{freq1:200,freq2:400,duration:.05,volume:.2,type:"noise",decay:.1},bossHit:{freq1:250,freq2:150,duration:.2,volume:.35,type:"square"},bossDefeat:[{freq1:100,freq2:50,duration:1,volume:.5,type:"sawtooth"},{freq1:1E3,freq2:400,duration:.8,volume:.4,type:"sine",delay:.2}],achievement:{freq1:1200,freq2:1800,duration:.3,volume:.3,type:"triangle",attack:.02,decay:.2}}} | |
| async init(){if(!this.audioContext){try{this.audioContext=new(window.AudioContext||window.webkitAudioContext);this.masterGain=this.audioContext.createGain();this.sfxGain=this.audioContext.createGain();this.sfxGain.connect(this.masterGain);this.masterGain.connect(this.audioContext.destination);this.setVolume("master",parseFloat(localStorage.getItem("masterVolume_v2S")||".4"));this.setVolume("sfx",parseFloat(localStorage.getItem("sfxVolume_v2S")||".5"))}catch(e){console.error("Audio API init failed.",e);return}}if("suspended"===this.audioContext.state)await this.audioContext.resume()} | |
| setVolume(type,value){const gainNode="master"===type?this.masterGain:this.sfxGain;gainNode&&this.audioContext&&(gainNode.gain.setValueAtTime(this.isMuted&&"master"===type?0:value,this.audioContext.currentTime),localStorage.setItem(`${type}Volume_v2S`,value));"master"===type&&document.getElementById("masterVolumeSlider")&&(document.getElementById("masterVolumeSlider").value=value);"sfx"===type&&document.getElementById("sfxVolumeSlider")&&(document.getElementById("sfxVolumeSlider").value=value)} | |
| playSound(soundDef,dynamicParams={}){if(this.isMuted||!this.audioContext||"running"!==this.audioContext.state||!soundDef)return;const playSingle=params=>{const{freq1:freq1,freq2:freq2,duration:duration,volume:volume=.3,type:type="sine",attack:attack=.01,decay:decay=.1,pan:pan=0,delay:delay=0}=params,osc=this.audioContext.createOscillator(),gain=this.audioContext.createGain(),panner=this.audioContext.createStereoPanner();osc.connect(gain);gain.connect(panner);panner.connect(this.sfxGain);osc.type=type;panner.pan.setValueAtTime(pan,this.audioContext.currentTime+delay);osc.frequency.setValueAtTime(freq1,this.audioContext.currentTime+delay);freq2&&freq1!==freq2&&osc.frequency.exponentialRampToValueAtTime(freq2,this.audioContext.currentTime+delay+duration);gain.gain.setValueAtTime(0,this.audioContext.currentTime+delay);gain.gain.linearRampToValueAtTime(volume,this.audioContext.currentTime+delay+attack);gain.gain.exponentialRampToValueAtTime(.001,this.audioContext.currentTime+delay+duration+decay);osc.start(this.audioContext.currentTime+delay);osc.stop(this.audioContext.currentTime+delay+duration+decay+.05)};let effDef="function"===typeof soundDef?soundDef(dynamicParams):soundDef;Array.isArray(effDef)?effDef.forEach(p=>playSingle({...p,...dynamicParams})):playSingle({...effDef,...dynamicParams})} | |
| toggleMute(){if(!this.masterGain)return this.isMuted;this.isMuted=!this.isMuted;const currentMasterVolume=parseFloat(localStorage.getItem("masterVolume_v2S")||".4");this.audioContext&&this.masterGain.gain.setValueAtTime(this.isMuted?0:currentMasterVolume,this.audioContext.currentTime);return this.isMuted} | |
| } | |
| const audioManager = new AudioManager(); | |
| // --- Game Constants (using GAME_WIDTH, GAME_HEIGHT) --- | |
| const PADDLE_DEFAULT_WIDTH = GAME_WIDTH * 0.13; | |
| const PADDLE_HEIGHT = GAME_HEIGHT * 0.025; | |
| const BALL_RADIUS = GAME_WIDTH * 0.01; | |
| const BALL_INITIAL_SPEED = GAME_HEIGHT * 0.0085; | |
| const POWERUP_DURATION = 9000; | |
| const RARE_POWERUP_DURATION_MULTIPLIER = 1.4; | |
| const POWERUP_DROP_CHANCE = 0.18; | |
| const RARE_POWERUP_CHANCE = 0.08; | |
| const COMBO_TIMEOUT = 2200; | |
| const MAX_LEVELS = 10; | |
| const BOSS_LEVELS = [5, MAX_LEVELS]; | |
| const POWERUP_TYPES = { WIDE_PADDLE: 'WIDE_PADDLE', STICKY_PADDLE: 'STICKY_PADDLE', PIERCING_BALL: 'PIERCING_BALL', LASER_PADDLE: 'LASER_PADDLE', MULTI_BALL: 'MULTI_BALL', SLOW_MO: 'SLOW_MO', SHIELD: 'SHIELD' }; | |
| const RARE_POWERUP_TYPES = { INVINCIBILITY: 'INVINCIBILITY', TIME_STOP: 'TIME_STOP', BLACK_HOLE: 'BLACK_HOLE', PENTA_BALL: 'PENTA_BALL', MEGA_LASER: 'MEGA_LASER', BLOCK_BOMB: 'BLOCK_BOMB' }; | |
| const ALL_POWERUP_TYPES = {...POWERUP_TYPES, ...RARE_POWERUP_TYPES}; | |
| const BLOCK_CREATURES = ['๐ ', '๐', '๐ก', '๐ข', '๐ฆ', '๐ฆ', '๐ฆ', '๐', 'โญ', '๐', '๐ซง']; | |
| const DANGEROUS_CREATURES = ['๐ฆ', '๐', '๐ฆ', '๐']; | |
| const BOSS_CREATURES = {5: '๐๐', 10: '๐ฒ๐'}; | |
| const PADDLE_SKINS = [ | |
| { id: 'default', name: 'Classic Blue', color1: '#30A8F8', color2: '#0077cc', outline: '#60C8FF' }, | |
| { id: 'ember', name: 'Ember Glow', color1: '#ff7e5f', color2: '#feb47b', outline: '#ffcf9f' }, | |
| { id: 'forest', name: 'Forest Gem', color1: '#00b09b', color2: '#96c93d', outline: '#c0ff7d' }, | |
| { id: 'void', name: 'Void Walker', color1: '#4A00E0', color2: '#8E2DE2', outline: '#C675FF' }, | |
| { id: 'gold', name: 'Golden Pearl', color1: '#FFD700', color2: '#FFA500', outline: '#FFEEAA' }, | |
| ]; | |
| let game = {}; // Will be fully initialized in initGame | |
| let paddle = {}; | |
| let blocks = []; let mouseX = GAME_WIDTH / 2; | |
| const blockColors = [ '#FF6347','#FFD700','#ADFF2F','#00CED1','#1E90FF','#9370DB','#FF69B4','#FFA07A','#20B2AA','#7FFF00','#BA55D3','#DA70D6','#6495ED','#FF4500','#32CD32','#8A2BE2' ]; | |
| // --- UI Management --- | |
| function showScreen(screenId) { | |
| document.querySelectorAll('.dialog-box, .menu, .settings-panel, .leaderboard, .shop, .daily-challenge, .achievement-system, .tutorial-screen, .stats-screen, .credits, .customization, .profile, .multiplayer-info, .matchmaking, #difficultySelectScreen, #placeholderDialog').forEach(el => el.style.display = 'none'); | |
| const screenElement = document.getElementById(screenId); | |
| if (screenElement) { | |
| screenElement.style.display = 'flex'; | |
| game.currentScreen = screenId; | |
| if (game.running && screenId !== 'game' && !game.paused) { | |
| game.paused = true; | |
| // Only show pause message if game was active AND user didn't explicitly pause | |
| if (document.getElementById('pauseMessage').style.display !== 'block') { | |
| // This logic might need adjustment: if a dialog opens, we effectively pause. | |
| // The 'P' key pause is separate. | |
| } | |
| } | |
| if (screenId === 'leaderboardScreen') updateLeaderboardDisplay(); | |
| else if (screenId === 'statsScreen') updateStatsDisplay(); | |
| else if (screenId === 'achievementsScreen') updateAchievementsDisplay(); | |
| else if (screenId === 'customizationScreen') populatePaddleSkins(); | |
| } else { | |
| console.warn(`Screen with ID ${screenId} not found. Defaulting to mainMenu.`); | |
| document.getElementById('mainMenu').style.display = 'flex'; | |
| game.currentScreen = 'mainMenu'; | |
| } | |
| } | |
| function showScreenWithPlaceholder(screenId, title) { | |
| const placeholderDialog = document.getElementById('placeholderDialog'); | |
| document.getElementById('placeholderTitle').textContent = title; | |
| document.getElementById('placeholderMessage').textContent = `The ${title.toLowerCase()} feature is still under the sea (development!). Check back later!`; | |
| showScreen('placeholderDialog'); // This will handle pausing etc. | |
| } | |
| function showNotification(title, description, icon = 'โน๏ธ', duration = 3000) { /* ... */ const p=document.getElementById('notificationPopup');document.getElementById('notificationPopupTitle').textContent=title;document.getElementById('notificationPopupDesc').textContent=description;document.getElementById('notificationPopupIcon').textContent=icon;p.classList.add('show');setTimeout(()=>p.classList.remove('show'),duration);} | |
| function showAchievementPopup(achievement) { /* ... */ const p=document.getElementById('achievementPopup');document.getElementById('achievementPopupTitle').textContent=achievement.name;document.getElementById('achievementPopupDesc').textContent=achievement.description;document.getElementById('achievementPopupIcon').textContent=achievement.icon;p.classList.add('show');audioManager.playSound(audioManager.definitions.achievement);setTimeout(()=>p.classList.remove('show'),4000);} | |
| function initPrimaryBall() { /* ... */ const sM=game.difficulty==='hardcore'?1.3:1;const bS=BALL_INITIAL_SPEED*sM;game.balls=[{x:paddle.x+paddle.width/2,y:paddle.y-BALL_RADIUS*1.5,dx:0,dy:0,radius:BALL_RADIUS,speed:bS+(game.level-1)*(GAME_HEIGHT*0.0004)*sM,attached:true,piercing:false,isPrimary:true,trail:[],color1:game.paddleSkin.color1,color2:game.paddleSkin.color2,color3:game.paddleSkin.outline}];} | |
| function createBackgroundElements() { /* ... */ if(game.backgroundFish.length===0){for(let i=0;i<15;i++)game.backgroundFish.push({x:Math.random()*GAME_WIDTH,y:Math.random()*GAME_HEIGHT,dx:(Math.random()-0.5)*(GAME_WIDTH*.001),dy:(Math.random()-0.5)*(GAME_HEIGHT*.0005),size:Math.random()*(GAME_WIDTH*.02)+(GAME_WIDTH*.015),creature:["๐ ","๐","๐ก"][Math.floor(3*Math.random())],alpha:.2*Math.random()+.05,flipX:.5<Math.random()});}if(game.backgroundBubbles.length===0){for(let i=0;i<40;i++){let b={r:Math.random()*(GAME_WIDTH*.0035)+(GAME_WIDTH*.001),dy:-(Math.random()*(GAME_HEIGHT*.0008)+(GAME_HEIGHT*.0002)),a:.3*Math.random()+.1,amp:Math.random()*(GAME_WIDTH*.015)+(GAME_WIDTH*.004),freq:.05*Math.random()+.01};b.x=Math.random()*GAME_WIDTH;b.y=GAME_HEIGHT+Math.random()*GAME_HEIGHT+b.r;b.ix=b.x;game.backgroundBubbles.push(b);}}} | |
| function createBlocks() { /* ... */ blocks=[];if(game.currentBoss)return;const bR="hardcore"===game.difficulty?4:3;const rS=Math.min(10,bR+Math.floor(game.level*("hardcore"===game.difficulty?.9:.7)));const cS="hardcore"===game.difficulty?14:12;const tBAW=.95*GAME_WIDTH;const sM=(GAME_WIDTH-tBAW)/2;const bW=tBAW/cS*.92;const bG=tBAW/cS*.08;const bH=.04*GAME_HEIGHT;const tM=.12*GAME_HEIGHT;const dBC=Math.min(.25,game.level*.025*("hardcore"===game.difficulty?1.6:1.1));for(let r=0;r<rS;r++)for(let c=0;c<cS;c++){if(.08<Math.random()&&2<game.level)continue;const iD=.02<Math.random()*dBC&&1<game.level;let mH=1;mH="hardcore"===game.difficulty?2>r?3:5>r?2:1:1>r?2:1;iD&&(mH=Math.min(4,mH+1));mH=Math.min(5,mH+Math.floor(game.level/2.5));blocks.push({x:sM+c*(bW+bG),y:tM+r*(bH+.7*bG),width:bW,height:bH,color:blockColors[(r*cS+c+game.level)%blockColors.length],hits:mH,maxHits:mH,creature:iD?DANGEROUS_CREATURES[Math.floor(Math.random()*DANGEROUS_CREATURES.length)]:BLOCK_CREATURES[Math.floor(Math.random()*BLOCK_CREATURES.length)],isDangerous:iD,animationOffset:2*Math.random()*Math.PI,id:`block-${r}-${c}`});}game.initialBlockCount=blocks.length;updateLevelProgressUI();game.isFlawlessLevel=!0} | |
| function createBoss(level) { /* ... */ const bC=BOSS_CREATURES[level]||"๐";let bHP,bW,bH,bS,bAP;if(level===BOSS_LEVELS[0]){bHP=50+20*("hardcore"===game.difficulty?2:1);bW=.3*GAME_WIDTH;bH=.15*GAME_HEIGHT;bS=GAME_WIDTH*5E-4;bAP="tentacleSwipe"}else if(level===BOSS_LEVELS[1]){bHP=80+30*("hardcore"===game.difficulty?2:1);bW=.4*GAME_WIDTH;bH=.2*GAME_HEIGHT;bS=GAME_WIDTH*3E-4;bAP="energyBeam"}else return null;game.currentBoss={x:GAME_WIDTH/2-bW/2,y:.1*GAME_HEIGHT,width:bW,height:bH,hp:bHP,maxHp:bHP,creature:bC,color:"#FF0055",dx:bS,vulnerableTime:0,lastAttackTime:0,attackPattern:bAP,animationOffset:2*Math.random()*Math.PI};blocks=[];document.getElementById("bossHealthContainer").style.display="block";document.getElementById("bossName").textContent=bC;document.getElementById("bossName").style.display="block";updateBossHealthUI()} | |
| function defineAchievements() { /* ... */ game.achievements={FIRST_BREAK:{name:"Icebreaker",description:"Destroy your first block.",unlocked:!1,icon:"๐ฅ"},LEVEL_3:{name:"Deep Diver",description:"Reach Level 3.",unlocked:!1,icon:"๐"},LEVEL_5_BOSS:{name:"Kraken Guard Defeated",description:"Defeat the boss of Level 5.",unlocked:!1,icon:"๐"},GAME_WON:{name:"Abyssal Conqueror",description:`Clear all ${MAX_LEVELS} levels.`,unlocked:!1,icon:"๐"},HIGH_SCORE_10K:{name:"Coral Collector",description:"Achieve a score of 10,000.",unlocked:!1,icon:"๐ฐ"},POWERUP_MASTER:{name:"Power Overwhelming",description:"Collect 5 power-ups in a single game.",unlocked:!1,icon:"โก"},COMBO_15X:{name:"Combo King",description:"Achieve a 15x combo.",unlocked:!1,icon:"โจ"},NO_LIVES_LOST_LEVEL:{name:"Flawless Victory (Level)",description:"Clear a level without losing a life.",unlocked:!1,icon:"๐ก๏ธ"}};const sA=JSON.parse(localStorage.getItem("deepSeaBreakoutAchievements_v2S")||"{}");for(const k in game.achievements)sA[k]&&(game.achievements[k].unlocked=!0)} | |
| function unlockAchievement(id) { /* ... */ game.achievements[id]&&!game.achievements[id].unlocked&&(game.achievements[id].unlocked=!0,showAchievementPopup(game.achievements[id]),localStorage.setItem("deepSeaBreakoutAchievements_v2S",JSON.stringify(game.achievements)),game.score+=500,addBubbleText("+500 (Achievement!)",GAME_WIDTH/2,GAME_HEIGHT/2,"#FFD700",18))} | |
| function initPlayerStats() { /* ... */ game.playerStats=JSON.parse(localStorage.getItem("deepSeaBreakoutStats_v2S")||JSON.stringify({gamesPlayed:0,totalScore:0,totalBlocksBroken:0,totalPowerupsCollected:0,totalGameTime:0,highestLevelReached:0,bossesDefeated:0}))} | |
| function updatePlayerStat(stat, value) { /* ... */ game.playerStats.hasOwnProperty(stat)?game.playerStats[stat]=(game.playerStats[stat]||0)+value:game.playerStats[stat]=value} | |
| function savePlayerStats() { /* ... */ localStorage.setItem("deepSeaBreakoutStats_v2S",JSON.stringify(game.playerStats))} | |
| function addBubbleText(text, x, y, color, size, duration = 60) { /* ... */ game.bubbleTexts.push({text:text,x:x,y:y,color:color,size:size*(GAME_WIDTH/800),alpha:1,life:duration,initialY:y})} | |
| function createEffectParticle(x,y,color,count=8,options={}){const{sizeRange:[sMin,sMax]=[1.5,3.5],speedRange:[spMin,spMax]=[3,6],lifeRange:[lMin,lMax]=[30,40],gravity:grav=0.08,glow:glw=!1,upwardBias:uB=-2}=options;for(let i=0;i<count;i++){const ang=2*Math.random()*Math.PI,spd=Math.random()*(spMax-spMin)+spMin;game.particles.push({x:x,y:y,dx:Math.cos(ang)*spd*(GAME_WIDTH/800),dy:Math.sin(ang)*spd*(GAME_HEIGHT/600)+uB*(GAME_HEIGHT/600),life:Math.random()*(lMax-lMin)+lMin,maxLife:lMax,color:color,size:(Math.random()*(sMax-sMin)+sMin)*(GAME_WIDTH/800),glow:glw,gravity:grav})}} | |
| function updateParticles() { /* ... */ for(let i=game.particles.length-1;0<=i;i--){let p=game.particles[i];p.x+=p.dx;p.y+=p.dy;p.life--;p.dy+=p.gravity;p.dx*=.98;0>=p.life&&game.particles.splice(i,1)}} | |
| function createBallTrailParticles() { /* ... */ !game.paused&&game.running&&game.balls.forEach(b=>{!b.attached?(b.trail.push({x:b.x,y:b.y,r:b.radius*(game.activePowerUps[ALL_POWERUP_TYPES.PIERCING_BALL]||b.piercing?.7:.5)}),10<b.trail.length&&b.trail.shift()):b.trail=[]})} | |
| // --- Drawing Functions (condensed for brevity, assume correct from previous logic) --- | |
| function drawParticles(){game.particles.forEach(p=>{let a=Math.pow(p.life/p.maxLife,1.5);ctx.save();ctx.globalAlpha=a;if(p.glow){let g=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,p.size*a);g.addColorStop(0,p.color);g.addColorStop(1,`${p.color}00`);ctx.fillStyle=g}else ctx.fillStyle=p.color;ctx.beginPath();ctx.arc(p.x,p.y,p.size*a,0,2*Math.PI);ctx.fill();ctx.restore()})} | |
| function drawBallTrails(){game.balls.forEach(b=>{if(1<b.trail.length){ctx.save();let p=game.activePowerUps[ALL_POWERUP_TYPES.PIERCING_BALL]||b.piercing,c=p?[255,0,255]:b.isPrimary?[parseInt(b.color2.substring(1,3),16),parseInt(b.color2.substring(3,5),16),parseInt(b.color2.substring(5,7),16)]:[200,200,200];for(let i=0;i<b.trail.length;i++){let pt=b.trail[i],a=i/b.trail.length*.4,r=pt.r*i/b.trail.length;ctx.fillStyle=`rgba(${c[0]},${c[1]},${c[2]},${a})`;ctx.beginPath();ctx.arc(pt.x,pt.y,r,0,2*Math.PI);ctx.fill()}ctx.restore()}})} | |
| function drawBackgroundElements(){const targetHue="volcanic"===game.currentTheme?20:"crystalCaverns"===game.currentTheme?260:180+4*game.level;game.currentBackgroundHue+=.01*(targetHue-game.currentBackgroundHue);const grad=ctx.createLinearGradient(0,0,0,GAME_HEIGHT);grad.addColorStop(0,`hsla(${game.currentBackgroundHue},70%,${"volcanic"===game.currentTheme?15:20}%,0.9)`);grad.addColorStop(.7,`hsla(${game.currentBackgroundHue+10},70%,${"volcanic"===game.currentTheme?8:10}%,1)`);grad.addColorStop(1,`hsla(${game.currentBackgroundHue+20},70%,${"volcanic"===game.currentTheme?3:5}%,1)`);ctx.fillStyle=grad;ctx.fillRect(0,0,GAME_WIDTH,GAME_HEIGHT);game.backgroundFish.forEach(f=>{ctx.save();ctx.globalAlpha=f.alpha;ctx.font=`${f.size}px Arial`;ctx.textAlign="center";f.flipX?(ctx.scale(-1,1),ctx.fillText(f.creature,-f.x,f.y)):ctx.fillText(f.creature,f.x,f.y);ctx.restore()});game.backgroundBubbles.forEach(b=>{ctx.save();ctx.globalAlpha=b.a;ctx.fillStyle="rgba(173,216,230,0.4)";ctx.beginPath();ctx.arc(b.x,b.y,b.r,0,2*Math.PI);ctx.fill();ctx.fillStyle="rgba(255,255,255,0.6)";ctx.beginPath();ctx.arc(b.x-.3*b.r,b.y-.3*b.r,.4*b.r,0,2*Math.PI);ctx.fill();ctx.restore()})} | |
| function drawBubbleTexts(){for(let i=game.bubbleTexts.length-1;0<=i;i--){let b=game.bubbleTexts[i];ctx.save();ctx.globalAlpha=b.alpha;ctx.font=`bold ${b.size}px 'Orbitron',sans-serif`;ctx.textAlign="center";ctx.fillStyle=b.color;ctx.shadowColor="rgba(0,0,0,0.7)";ctx.shadowBlur=3;ctx.shadowOffsetX=1;ctx.shadowOffsetY=1;ctx.fillText(b.text,b.x,b.y);ctx.restore();b.y-=1*(GAME_HEIGHT/600);b.alpha=Math.max(0,b.life/60);b.size*=.995;b.life--;0>=b.life&&game.bubbleTexts.splice(i,1)}} | |
| function drawPaddle(){const{x:x,y:y,width:width,height:height}=paddle,s=game.paddleSkin,cR=height/2;ctx.save();ctx.shadowColor=s.outline;ctx.shadowBlur=15+3*Math.sin(Date.now()/200);ctx.fillStyle=chroma(s.outline).alpha(.15).css();ctx.beginPath();ctx.roundRect(x-5,y-5,width+10,height+10,cR+5);ctx.fill();ctx.restore();const grad=ctx.createLinearGradient(x,y,x,y+height);grad.addColorStop(0,s.color1);grad.addColorStop(1,s.color2);ctx.fillStyle=grad;ctx.beginPath();ctx.roundRect(x,y,width,height,cR);ctx.fill();ctx.strokeStyle=s.outline;ctx.lineWidth=2;ctx.beginPath();ctx.roundRect(x,y,width,height,cR);ctx.stroke();if(game.activePowerUps[POWERUP_TYPES.LASER_PADDLE]||game.activePowerUps[RARE_POWERUP_TYPES.MEGA_LASER]){const iM=game.activePowerUps[RARE_POWERUP_TYPES.MEGA_LASER];ctx.fillStyle=iM?"#FF8C00":"#ff3333";const cW=width*(iM?.15:.1),cH=height*(iM?1.2:.8);ctx.beginPath();ctx.roundRect(x+.15*width-cW/2,y-cH,cW,cH,2);ctx.fill();ctx.beginPath();ctx.roundRect(x+.85*width-cW/2,y-cH,cW,cH,2);ctx.fill();iM&&(ctx.beginPath(),ctx.roundRect(x+.5*width-.8*cW,y-1.2*cH,1.6*cW,1.2*cH,3),ctx.fill())}if(game.activePowerUps[POWERUP_TYPES.SHIELD]){ctx.save();ctx.strokeStyle="#00FFFF";ctx.lineWidth=3;ctx.globalAlpha=.5+.2*Math.sin(Date.now()/150);ctx.beginPath();ctx.arc(x+width/2,y+height/2,width/1.8,0,2*Math.PI);ctx.stroke();ctx.restore()}} | |
| function drawBalls(){game.balls.forEach(b=>{const{x:x,y:y,radius:radius,attached:attached,isPrimary:isPrimary}=b,isP=b.piercing||game.activePowerUps[ALL_POWERUP_TYPES.PIERCING_BALL]||game.activePowerUps[RARE_POWERUP_TYPES.INVINCIBILITY];if(!attached){ctx.save();const gR=radius*(isP?2.2:1.8),grad=ctx.createRadialGradient(x,y,0,x,y,gR),rgb=isP?[255,50,255]:isPrimary?[parseInt(b.color2.substring(1,3),16),parseInt(b.color2.substring(3,5),16),parseInt(b.color2.substring(5,7),16)]:[220,220,220];grad.addColorStop(0,`rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.4)`);grad.addColorStop(.5,`rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.15)`);grad.addColorStop(1,`rgba(${rgb[0]},${rgb[1]},${rgb[2]},0)`);ctx.fillStyle=grad;ctx.beginPath();ctx.arc(x,y,gR,0,2*Math.PI);ctx.fill();ctx.restore()}const bG=ctx.createRadialGradient(x-.3*radius,y-.3*radius,0,x,y,radius),c1=isP?"#ffffff":b.color1,c2=isP?"#ffccff":b.color2,c3=isP?"#ff33ff":b.color3;bG.addColorStop(0,c1);bG.addColorStop(.5,c2);bG.addColorStop(1,c3);ctx.fillStyle=bG;ctx.beginPath();ctx.arc(x,y,radius,0,2*Math.PI);ctx.fill();ctx.fillStyle="rgba(255,255,255,0.7)";ctx.beginPath();ctx.arc(x-.35*radius,y-.35*radius,.3*radius,0,2*Math.PI);ctx.fill();game.activePowerUps[POWERUP_TYPES.STICKY_PADDLE]&&attached&&isPrimary&&(ctx.save(),ctx.strokeStyle="rgba(0,255,255,0.4)",ctx.setLineDash([.5*BALL_RADIUS,.3*BALL_RADIUS]),ctx.lineWidth=1.5,ctx.beginPath(),ctx.moveTo(x,y-radius),ctx.lineTo(x,0),ctx.stroke(),ctx.restore())})} | |
| function drawBlocks(){blocks.forEach(b=>{const{x:x,y:y,width:width,height:height,color:color,hits:hits,maxHits:maxHits,creature:creature,isDangerous:isDangerous,animationOffset:animationOffset}=b,hR=hits/maxHits,bR=Math.min(width,height)*.15,ay=isDangerous?Math.sin(Date.now()/250+animationOffset)*height*.05:0;isDangerous&&(ctx.save(),ctx.shadowColor=`rgba(255,${60+40*Math.sin(Date.now()/180+animationOffset)},0,0.7)`,ctx.shadowBlur=12+4*Math.sin(Date.now()/180+animationOffset),ctx.fillStyle=`rgba(180,0,0,${.08+.04*Math.sin(Date.now()/180+animationOffset)})`,ctx.beginPath(),ctx.roundRect(x-2,y-2+ay,width+4,height+4,bR+2),ctx.fill(),ctx.restore());const grad=ctx.createLinearGradient(x,y,x,y+height);1<hits?(grad.addColorStop(0,color),grad.addColorStop(1,chroma(color).darken(1.8).hex())):(grad.addColorStop(0,color),grad.addColorStop(1,chroma(color).darken(.8).hex()+"E0"));ctx.fillStyle=grad;ctx.beginPath();ctx.roundRect(x,y+ay,width,height,bR);ctx.fill();ctx.strokeStyle="rgba(255,255,255,0.15)";ctx.lineWidth=1;ctx.beginPath();ctx.roundRect(x,y+ay,width,height,bR);ctx.stroke();ctx.font=`${.55*Math.min(width,height)}px Arial`;ctx.textAlign="center";ctx.textBaseline="middle";const cY=y+height/2+ay;ctx.fillStyle="rgba(0,0,0,0.3)";ctx.fillText(creature,x+width/2+1,cY+1);ctx.fillStyle=1===hR?"#fff":`rgba(255,255,255,${.4+.6*hR})`;ctx.fillText(creature,x+width/2,cY);hits<maxHits&&0<hits&&(ctx.save(),ctx.fillStyle="rgba(0,0,0,0.3)",ctx.beginPath(),ctx.roundRect(x,y-.18*height+ay,width,.08*height,.04*height),ctx.fill(),ctx.fillStyle=.5<hR?"#5f5":.2<hR?"#ff5":"#f55",ctx.beginPath(),ctx.roundRect(x,y-.18*height+ay,width*hR,.08*height,.04*height),ctx.fill(),ctx.restore())})} | |
| function drawLasers(){game.lasers.forEach(l=>{ctx.save();let g=ctx.createLinearGradient(l.x,l.y,l.x,l.y-l.h);g.addColorStop(0,"rgba(255,0,0,0)");g.addColorStop(.3,l.isMega?"rgba(255,100,0,1)":"rgba(255,50,50,1)");g.addColorStop(1,l.isMega?"rgba(255,255,0,1)":"rgba(255,200,0,1)");ctx.fillStyle=g;ctx.beginPath();ctx.moveTo(l.x-l.w/2,l.y);ctx.lineTo(l.x+l.w/2,l.y);ctx.lineTo(l.x,l.y-l.h);ctx.closePath();ctx.fill();ctx.fillStyle="rgba(255,255,255,0.8)";ctx.beginPath();ctx.moveTo(l.x-l.w/4,l.y);ctx.lineTo(l.x+l.w/4,l.y);ctx.lineTo(l.x,l.y-l.h);ctx.closePath();ctx.fill();ctx.restore()})} | |
| function drawBlackHole(){if(!game.blackHole)return;const{x:x,y:y,radius:radius,timeLeft:timeLeft,maxTimeLeft:maxTimeLeft}=game.blackHole,effScale=Math.min(1,(maxTimeLeft-timeLeft)/(maxTimeLeft*.2)),curRad=radius*effScale;ctx.save();const ogRad=curRad*(2.5+Math.sin(Date.now()/150)*.3);let grad=ctx.createRadialGradient(x,y,.8*curRad,x,y,ogRad);grad.addColorStop(0,"rgba(100,0,150,0)");grad.addColorStop(.5,`rgba(100,0,150,${.4*effScale})`);grad.addColorStop(1,"rgba(100,0,150,0)");ctx.fillStyle=grad;ctx.beginPath();ctx.arc(x,y,ogRad,0,2*Math.PI);ctx.fill();ctx.strokeStyle=`rgba(150,50,255,${.6*effScale})`;ctx.lineWidth=2;const numT=5;for(let i=0;i<numT;i++){ctx.beginPath();const rot=Date.now()/500+i*Math.PI*2/numT;for(let ang=0;ang<1.5*Math.PI;ang+=.1){const r=.3*curRad+ang*.2*curRad+Math.sin(5*ang+Date.now()/200)*.1*curRad,sx=x+Math.cos(ang+rot)*r,sy=y+Math.sin(ang+rot)*r;0===ang?ctx.moveTo(sx,sy):ctx.lineTo(sx,sy)}ctx.stroke()}ctx.fillStyle=`rgba(0,0,0,${.9*effScale})`;ctx.beginPath();ctx.arc(x,y,curRad,0,2*Math.PI);ctx.fill();ctx.restore()} | |
| function drawPowerUpsOnScreen(){game.powerUpsOnScreen.forEach(pu=>{const{x:x,y:y,radius:radius,color:color,text:text,isRare:isRare}=pu,eR=radius*(1+Math.sin(Date.now()/180+x*.1)*(isRare?.15:.08));isRare&&(ctx.save(),ctx.fillStyle=chroma(color).alpha(.5).css(),ctx.beginPath(),ctx.arc(x,y,1.8*eR,0,2*Math.PI),ctx.fill(),ctx.restore());ctx.fillStyle=color;ctx.beginPath();ctx.arc(x,y,eR,0,2*Math.PI);ctx.fill();ctx.fillStyle=chroma.contrast(chroma(color),"black")>chroma.contrast(chroma(color),"white")?"black":"white";ctx.font=`bold ${eR*(1<text.length?.75:.95)}px 'Orbitron'`;ctx.textAlign="center";ctx.textBaseline="middle";ctx.fillText(text,x,y+.05*eR)})} | |
| function drawBoss(){if(!game.currentBoss)return;const b=game.currentBoss,{x:x,y:y,width:width,height:height,creature:creature,color:color,animationOffset:animationOffset}=b,aY=y+Math.sin(Date.now()/400+animationOffset)*height*.05;ctx.save();ctx.shadowColor=chroma(color).darken(1).hex();ctx.shadowBlur=20+5*Math.sin(Date.now()/200);ctx.fillStyle=chroma(color).alpha(.2).css();ctx.beginPath();ctx.ellipse(x+width/2,aY+height/2+10,width/1.8,height/3,0,0,2*Math.PI);ctx.fill();ctx.shadowColor="transparent";const grad=ctx.createRadialGradient(x+width/2,aY+height/2,0,x+width/2,aY+height/2,Math.max(width,height)/2);grad.addColorStop(0,chroma(color).brighten(1).hex());grad.addColorStop(.7,color);grad.addColorStop(1,chroma(color).darken(1).hex());ctx.fillStyle=grad;ctx.fillRect(x,aY,width,height);ctx.font=`${.6*Math.min(width,height)}px Arial`;ctx.textAlign="center";ctx.textBaseline="middle";ctx.fillStyle="#FFF";ctx.fillText(creature,x+width/2,aY+height/2);ctx.restore()} | |
| function drawPowerUpIndicatorsUI(){const c=document.getElementById("powerupIndicator");c.innerHTML="";Object.entries(game.activePowerUps).forEach(([t,d])=>{const rT=Math.max(0,d.endTime-Date.now()),tD=d.isRare?POWERUP_DURATION*RARE_POWERUP_DURATION_MULTIPLIER:POWERUP_DURATION,pc=rT/tD*100,iD=document.createElement("div");iD.className="powerup-icon";d.isRare&&iD.classList.add("rare");const pI=getPowerupInfo(t);iD.textContent=pI.icon;iD.style.background=pI.color;iD.style.setProperty("--time-left",`${pc}%`);c.appendChild(iD)})} | |
| // --- UI Update Functions --- | |
| function updateHealthBarUI(){const hP=game.lives/game.maxLives*100;document.getElementById("healthFill").style.width=`${hP}%`} | |
| function updateDepthMeterUI(){const dP=game.level/MAX_LEVELS*100;document.getElementById("depthIndicator").style.height=`${Math.min(100,dP)}%`} | |
| function updateLevelProgressUI(){const p=0<game.initialBlockCount?(game.initialBlockCount-blocks.length)/game.initialBlockCount*100:game.currentBoss?0:100;document.getElementById("levelProgressBar").style.width=`${p}%`} | |
| function updateBossHealthUI(){if(game.currentBoss){const hP=game.currentBoss.hp/game.currentBoss.maxHp*100;document.getElementById("bossHealthBar").style.width=`${hP}%`}} | |
| function showTutorialMessage(text,duration=4E3){const tP=document.getElementById("tutorialPopup");document.getElementById("tutorialText").textContent=text;tP.classList.add("show");setTimeout(()=>tP.classList.remove("show"),duration)} | |
| function updateLeaderboardDisplay(){const scores=JSON.parse(localStorage.getItem("deepSeaBreakoutLeaderboard_v2S")||"[]");scores.sort((a,b)=>b.score-a.score);const tB=document.getElementById("leaderboardTable").getElementsByTagName("tbody")[0];tB.innerHTML="";0===scores.length?(tB.insertRow().insertCell().colSpan=3,tB.rows[0].cells[0].textContent="No high scores yet. Be the first!",tB.rows[0].cells[0].style.textAlign="center"):scores.slice(0,10).forEach((entry,index)=>{let row=tB.insertRow();row.insertCell().textContent=index+1;row.insertCell().textContent=entry.name||"Player";row.insertCell().textContent=entry.score})} | |
| function updateStatsDisplay(){const c=document.getElementById("statsContainer");c.innerHTML=`<p><strong>Games Played:</strong> ${game.playerStats.gamesPlayed||0}</p>\n<p><strong>Total Score Accumulated:</strong> ${game.playerStats.totalScore||0}</p>\n<p><strong>Highest Level Reached:</strong> ${game.playerStats.highestLevelReached||0}</p>\n<p><strong>Total Blocks Broken:</strong> ${game.playerStats.totalBlocksBroken||0}</p>\n<p><strong>Power-ups Collected:</strong> ${game.playerStats.totalPowerupsCollected||0}</p>\n<p><strong>Bosses Defeated:</strong> ${game.playerStats.bossesDefeated||0}</p>\n<p><strong>Total Playtime:</strong> ${formatTime(game.playerStats.totalGameTime||0)}</p>`} | |
| function formatTime(seconds){const h=Math.floor(seconds/3600),m=Math.floor(seconds%3600/60),s=Math.floor(seconds%60);return`${0<h?h+"h ":""}${0<m?m+"m ":""}${s}s`} | |
| function updateAchievementsDisplay(){const c=document.getElementById("achievementsListContainer");c.innerHTML="";Object.values(game.achievements).forEach(ach=>{const d=document.createElement("div");d.className="achievement";d.innerHTML=`<div class="achievement-icon">${ach.icon}</div>\n<div class="achievement-details">\n <div class="achievement-title">${ach.name}</div>\n <div class="achievement-description">${ach.description}</div>\n</div>\n<div class="achievement-status ${ach.unlocked?"completed":""}">${ach.unlocked?"Unlocked":"Locked"}</div>`;c.appendChild(d)})} | |
| function populatePaddleSkins(){const s=document.getElementById("paddleSkinSelector");s.innerHTML="";PADDLE_SKINS.forEach(skin=>{const d=document.createElement("div");d.className="skin";d.style.background=`linear-gradient(45deg, ${skin.color1}, ${skin.color2})`;d.style.borderColor=skin.outline;skin.id===game.paddleSkin.id&&d.classList.add("selected");d.title=skin.name;d.onclick=()=>{game.paddleSkin=skin;localStorage.setItem("deepSeaBreakoutPaddleSkin_v2S",skin.id);populatePaddleSkins()};s.appendChild(d)})} | |
| // --- Update Logic (condensed) --- | |
| function updatePaddle() { /* ... */ if(game.activePowerUps[RARE_POWERUP_TYPES.TIME_STOP]&&!game.paused)return;paddle.x+=(paddle.targetX-paddle.x)*.28;paddle.x=Math.max(0,Math.min(paddle.x,GAME_WIDTH-paddle.width));game.balls.forEach(b=>{if(b.attached){b.x=paddle.x+paddle.width/2;b.y=paddle.y-b.radius-1}})} | |
| function updateBalls() { /* Condensed, full logic as before */ | |
| for(let i=game.balls.length-1;i>=0;i--){ | |
| const ball=game.balls[i];if(ball.attached)continue; | |
| let sF=1;if(game.activePowerUps[POWERUP_TYPES.SLOW_MO])sF=0.6;if(game.activePowerUps[RARE_POWERUP_TYPES.TIME_STOP]&&!ball.isPrimary&&!game.paused)sF=0.05; | |
| ball.x+=ball.dx*sF;ball.y+=ball.dy*sF; | |
| if(ball.x<=ball.radius||ball.x>=GAME_WIDTH-ball.radius){ball.dx*=-1;audioManager.playSound(audioManager.definitions.wallHit,{pan:(ball.x/GAME_WIDTH)*2-1});createEffectParticle(ball.x,ball.y,'#87ceeb',5,{sizeRange:[1,2.5]});ball.x=Math.max(ball.radius,Math.min(ball.x,GAME_WIDTH-ball.radius));} | |
| if(ball.y<=ball.radius){ball.dy*=-1;audioManager.playSound(audioManager.definitions.wallHit,{pan:(ball.x/GAME_WIDTH)*2-1});createEffectParticle(ball.x,ball.y,'#87ceeb',5,{sizeRange:[1,2.5]});ball.y=ball.radius;} | |
| if(ball.dy>0&&ball.y+ball.radius>=paddle.y&&ball.y-ball.radius<=paddle.y+paddle.height&&ball.x+ball.radius>=paddle.x&&ball.x-ball.radius<=paddle.x+paddle.width){ | |
| if(game.activePowerUps[POWERUP_TYPES.STICKY_PADDLE]&&ball.isPrimary){ball.attached=true;ball.dx=0;ball.dy=0;} | |
| else{const hpN=(ball.x-(paddle.x+paddle.width/2))/(paddle.width/2);const bA=hpN*(Math.PI/2.7);const tS=ball.speed*(1+game.level*0.025);ball.dx=tS*Math.sin(bA);ball.dy=-tS*Math.cos(bA);ball.dx+=(Math.random()-0.5)*0.15*tS;} | |
| audioManager.playSound(audioManager.definitions.paddleHit);createEffectParticle(ball.x,paddle.y,'#00ffff',8,{sizeRange:[1.5,3],upwardBias:-1});ball.y=paddle.y-ball.radius; | |
| if(ball.isPrimary&&game.activePowerUps[POWERUP_TYPES.SHIELD]){delete game.activePowerUps[POWERUP_TYPES.SHIELD];addBubbleText("SHIELD BROKEN!",paddle.x+paddle.width/2,paddle.y-20,"#FF8C00",16);drawPowerUpIndicatorsUI();} | |
| if(!ball.isPrimary&&game.combo>0&&!game.activePowerUps[RARE_POWERUP_TYPES.PENTA_BALL]){game.combo=Math.floor(game.combo/2);document.getElementById('combo').textContent=`${game.combo}x`;} | |
| } | |
| const isInv=game.activePowerUps[RARE_POWERUP_TYPES.INVINCIBILITY]; | |
| ball.piercing=game.activePowerUps[POWERUP_TYPES.PIERCING_BALL]||isInv; | |
| if(game.currentBoss){const b=game.currentBoss;if(ball.x+ball.radius>b.x&&ball.x-ball.radius<b.x+b.width&&ball.y+ball.radius>b.y&&ball.y-ball.radius<b.y+b.height){if(b.vulnerableTime>0||isInv){b.hp--;game.score+=50*game.level;audioManager.playSound(audioManager.definitions.bossHit);createEffectParticle(ball.x,ball.y,b.color,15,{glow:true,sizeRange:[2,5]});updateBossHealthUI();if(b.hp<=0)bossDefeated();}else{audioManager.playSound(audioManager.definitions.wallHit,{pan:(ball.x/GAME_WIDTH)*2-1});createEffectParticle(ball.x,ball.y,'#AAAAFF',8,{sizeRange:[1,3]});}const dX=ball.x-(b.x+b.width/2),dY=ball.y-(b.y+b.height/2);if(Math.abs(dX)/(b.width/2)>Math.abs(dY)/(b.height/2))ball.dx*=-1;else ball.dy*=-1;}} | |
| else{for(let j=blocks.length-1;j>=0;j--){const blk=blocks[j];if(ball.x+ball.radius>blk.x&&ball.x-ball.radius<blk.x+blk.width&&ball.y+ball.radius>blk.y&&ball.y-ball.radius<blk.y+blk.height){ | |
| if(blk.isDangerous&&ball.isPrimary&&!isInv&&!game.activePowerUps[POWERUP_TYPES.SHIELD]){game.lives--;game.isFlawlessLevel=false;audioManager.playSound(audioManager.definitions.loseLife);addBubbleText("-1 LIFE!",ball.x,ball.y,"#ff4444",20);createEffectParticle(ball.x,ball.y,"#ff0000",20,{glow:true});updateHealthBarUI();if(game.lives<=0){gameOver();return;}else{resetPrimaryBallAndPaddleState();continue;}} | |
| else if(blk.isDangerous&&ball.isPrimary&&game.activePowerUps[POWERUP_TYPES.SHIELD]){delete game.activePowerUps[POWERUP_TYPES.SHIELD];addBubbleText("SHIELD PROTECTED!",paddle.x+paddle.width/2,paddle.y-20,"#00FFFF",16);drawPowerUpIndicatorsUI();} | |
| audioManager.playSound(audioManager.definitions.blockHit);if(blocks.length===game.initialBlockCount)unlockAchievement('FIRST_BREAK');updatePlayerStat('totalBlocksBroken',1);createEffectParticle(blk.x+blk.width/2,blk.y+blk.height/2,blk.color,10,{sizeRange:[1,3]}); | |
| if(!ball.piercing){const bX=ball.x,bY=ball.y,blX=blk.x+blk.width/2,blY=blk.y+blk.height/2,dX=bX-blX,dY=bY-blY,wTH=(ball.radius+blk.width/2),hTH=(ball.radius+blk.height/2);if(Math.abs(dX)/wTH>Math.abs(dY)/hTH){ball.dx*=-1;ball.x+=ball.dx>0?0.1:-0.1;}else{ball.dy*=-1;ball.y+=ball.dy>0?0.1:-0.1;}} | |
| blk.hits--;game.score+=5*game.level*(blk.isDangerous?1.5:1); | |
| if(blk.hits<=0){game.score+=(10+(ball.piercing?5:0))*game.level*(blk.isDangerous?1.5:1);blocks.splice(j,1);updateLevelProgressUI();if(Math.random()<POWERUP_DROP_CHANCE)spawnPowerUpItem(blk.x+blk.width/2,blk.y+blk.height/2);} | |
| updateCombo();if(!ball.piercing)break;}}} | |
| if(ball.y>GAME_HEIGHT+ball.radius*3){if(ball.isPrimary){if(!isInv&&!game.activePowerUps[POWERUP_TYPES.SHIELD]){game.lives--;game.isFlawlessLevel=false;}else if(game.activePowerUps[POWERUP_TYPES.SHIELD]){delete game.activePowerUps[POWERUP_TYPES.SHIELD];addBubbleText("SHIELD SAVED BALL!",paddle.x+paddle.width/2,paddle.y-20,"#00FFFF",16);drawPowerUpIndicatorsUI();}audioManager.playSound(audioManager.definitions.loseLife);updateHealthBarUI();if(game.lives<=0&&!isInv){gameOver();return;}else{resetPrimaryBallAndPaddleState();}}else{game.balls.splice(i,1);}} | |
| } | |
| } | |
| function updateCombo() { /* ... */ const now=Date.now();if(now-game.lastHitTime>COMBO_TIMEOUT)game.combo=0;game.lastHitTime=now;game.combo++;document.getElementById("combo").textContent=`${game.combo}x`;if(3<=game.combo){const pts=Math.min(25,game.combo)*game.level;game.score+=pts;addBubbleText(`+${pts} COMBO!`,GAME_WIDTH/2,.4*GAME_HEIGHT,"#ffff00",16+Math.min(10,game.combo))}5<=game.combo&&game.combo%2===(10>game.combo?1:0)&&(audioManager.playSound(audioManager.definitions.combo(game.combo)),5<=game.combo&&setTimeout(()=>{const cm=document.getElementById("comboMeter");cm.textContent=`${game.combo}X COMBO!`;cm.style.opacity="1";cm.style.transform="translate(-50%, -50%)scale(1.1)";cm.style.color=15<=game.combo?"#ff69b4":10<=game.combo?"#ffaa00":"#ffff00"},0),setTimeout(()=>{document.getElementById("comboMeter").style.opacity="0";document.getElementById("comboMeter").style.transform="translate(-50%, -50%)scale(0.8)"},800));15<=game.combo&&unlockAchievement("COMBO_15X")} | |
| function updateLasers() { /* ... */ for(let i=game.lasers.length-1;0<=i;i--){let l=game.lasers[i];l.y-=l.speed;if(0>l.y+l.h){game.lasers.splice(i,1);continue}for(let j=blocks.length-1;0<=j;j--){let b=blocks[j];if(l.x>=b.x&&l.x<=b.x+b.width&&l.y<=b.y+b.height&&l.y+l.h>=b.y){createEffectParticle(l.x,b.y+b.height/2,l.isMega?"#FFA500":"#ffff00",10,{glow:!0});l.isMega?b.hits=0:b.hits-=2;0>=b.hits&&(blocks.splice(j,1),updateLevelProgressUI());game.score+=(l.isMega?25:15)*game.level;updateCombo();game.lasers.splice(i,1);break}}if(game.currentBoss&&game.lasers[i]){const boss=game.currentBoss,laser=game.lasers[i];laser.x>=boss.x&&laser.x<=boss.x+boss.width&&laser.y<=boss.y+boss.height&&laser.y+laser.height>=boss.y&&(0<boss.vulnerableTime&&(boss.hp-=(laser.isMega?3:1),game.score+=(laser.isMega?100:75)*game.level,audioManager.playSound(audioManager.definitions.bossHit),createEffectParticle(laser.x,laser.y,boss.color,10,{glow:!0,sizeRange:[1.5,4]}),updateBossHealthUI(),0>=boss.hp&&bossDefeated()),game.lasers.splice(i,1))}}} | |
| function updateBoss() { /* ... */ if(!game.currentBoss)return;const b=game.currentBoss;b.x+=b.dx;(0>=b.x||b.x+b.width>=GAME_WIDTH)&&(b.dx*=-1,b.vulnerableTime=120);0<b.vulnerableTime&&b.vulnerableTime--;Date.now()-b.lastAttackTime>3E3&&0>=b.vulnerableTime&&(addBubbleText("BOSS ATTACK!",b.x+b.width/2,b.y+b.height,"#FF5555",18),b.lastAttackTime=Date.now())} | |
| function updateBlackHole() { /* ... */ if(!game.blackHole)return;const bh=game.blackHole;bh.timeLeft--;if(0>=bh.timeLeft){game.blackHole=null;return}game.balls.forEach(b=>{if(b.attached)return;const dx=bh.x-b.x,dy=bh.y-b.y,dSq=dx*dx+dy*dy;dSq<Math.pow(5*bh.radius,2)&&10<dSq&&(d=Math.sqrt(dSq),f=bh.strength/dSq,b.dx+=dx/d*f,b.dy+=dy/d*f,s=Math.sqrt(b.dx*b.dx+b.dy*b.dy),s>1.5*b.speed&&(b.dx=b.dx/s*1.5*b.speed,b.dy=b.dy/s*1.5*b.speed))});for(let i=blocks.length-1;0<=i;i--){const bl=blocks[i],bX=bl.x+bl.width/2,bY=bl.y+bl.height/2,dx=bh.x-bX,dy=bh.y-bY,dSq=dx*dx+dy*dy;dSq<Math.pow(3*bh.radius,2)&&(dSq<Math.pow(.8*bh.radius,2)?(audioManager.playSound(audioManager.definitions.blackHoleAbsorb),createEffectParticle(bX,bY,chroma(bl.color).brighten(1).hex(),15,{upwardBias:0,speedRange:[1,3]}),blocks.splice(i,1),updateLevelProgressUI(),game.score+=20*game.level,updateCombo()):0)}} | |
| function getPowerupInfo(type) { /* ... */ switch(type){case POWERUP_TYPES.WIDE_PADDLE:return{icon:"Wโ",color:"rgba(0,200,0,0.8)"};case POWERUP_TYPES.STICKY_PADDLE:return{icon:"S๐งฒ",color:"rgba(255,200,0,0.8)"};case POWERUP_TYPES.PIERCING_BALL:return{icon:"P๐ฅ",color:"rgba(255,0,200,0.8)"};case POWERUP_TYPES.LASER_PADDLE:return{icon:"Lโก",color:"rgba(255,50,50,0.8)"};case POWERUP_TYPES.MULTI_BALL:return{icon:"M๐ฅ",color:"rgba(0,200,255,0.8)"};case POWERUP_TYPES.SLOW_MO:return{icon:"โ",color:"rgba(180,180,255,0.8)"};case POWERUP_TYPES.SHIELD:return{icon:"๐ก๏ธ",color:"rgba(0,255,255,0.8)"};case RARE_POWERUP_TYPES.INVINCIBILITY:return{icon:"๐",color:"rgba(255,215,0,0.9)"};case RARE_POWERUP_TYPES.TIME_STOP:return{icon:"๐",color:"rgba(100,220,255,0.9)"};case RARE_POWERUP_TYPES.BLACK_HOLE:return{icon:"๐",color:"rgba(50,0,80,0.9)"};case RARE_POWERUP_TYPES.PENTA_BALL:return{icon:"5๏ธโฃ",color:"rgba(255,100,255,0.9)"};case RARE_POWERUP_TYPES.MEGA_LASER:return{icon:"L๐ฅ",color:"rgba(255,120,0,0.9)"};case RARE_POWERUP_TYPES.BLOCK_BOMB:return{icon:"๐ฃ",color:"rgba(100,100,100,0.9)"};default:return{icon:"?",color:"grey"}}} | |
| function spawnPowerUpItem(x, y) { /* ... */ const isR=.08>Math.random();let type,text,color;const puR=GAME_WIDTH*(isR?.02:.015),sTs=Object.values(POWERUP_TYPES),rTs=Object.values(RARE_POWERUP_TYPES);isR?(type=rTs[Math.floor(Math.random()*rTs.length)],audioManager.playSound(audioManager.definitions.rarePowerUpCollect)):(type=sTs[Math.floor(Math.random()*sTs.length)],audioManager.playSound(audioManager.definitions.powerUpSpawn));const pI=getPowerupInfo(type);text=pI.icon;color=pI.color;game.powerUpsOnScreen.push({x:x,y:y,type:type,text:text,color:color,radius:puR,dy:GAME_HEIGHT*(isR?.0015:.002),isRare:isR})} | |
| function updatePowerUpsOnScreen() { /* ... */ for(let i=game.powerUpsOnScreen.length-1;0<=i;i--){let pu=game.powerUpsOnScreen[i];pu.y+=pu.dy;if(pu.y-pu.radius>GAME_HEIGHT){game.powerUpsOnScreen.splice(i,1);continue}pu.y+pu.radius>=paddle.y&&pu.y-pu.radius<=paddle.y+paddle.height&&pu.x+pu.radius>=paddle.x&&pu.x-pu.radius<=paddle.x+paddle.width&&(activatePowerUp(pu.type,pu.isRare),updatePlayerStat("totalPowerupsCollected",1),5<=game.playerStats.totalPowerupsCollected&&unlockAchievement("POWERUP_MASTER"),addBubbleText((pu.isRare?"RARE: ":"")+pu.type.replace(/_/g," ")+"!",pu.x,pu.y,pu.isRare?"gold":"#00dddd",16),game.powerUpsOnScreen.splice(i,1))}} | |
| function activatePowerUp(type, isRare) { /* ... */ isRare?audioManager.playSound(audioManager.definitions.rarePowerUpCollect):audioManager.playSound(audioManager.definitions.powerUpCollect);const dur=(isRare?POWERUP_DURATION*RARE_POWERUP_DURATION_MULTIPLIER:POWERUP_DURATION);game.activePowerUps[type]?game.activePowerUps[type].endTime=Math.max(game.activePowerUps[type].endTime,Date.now()+dur):game.activePowerUps[type]={endTime:Date.now()+dur,isRare:isRare,data:{}};switch(type){case POWERUP_TYPES.WIDE_PADDLE:paddle.width=1.6*PADDLE_DEFAULT_WIDTH;break;case POWERUP_TYPES.MULTI_BALL:addNewBall();break;case RARE_POWERUP_TYPES.PENTA_BALL:for(let k=0;4>k;k++)addNewBall();break;case RARE_POWERUP_TYPES.INVINCIBILITY:game.lives=Math.min(game.lives+1,game.maxLives);addBubbleText("+1 LIFE!",paddle.x+paddle.width/2,paddle.y,"#ffff00",20);updateHealthBarUI();break;case RARE_POWERUP_TYPES.BLACK_HOLE:audioManager.playSound(audioManager.definitions.blackHoleOpen);game.blackHole={x:GAME_WIDTH/2,y:GAME_HEIGHT/3,radius:.06*GAME_WIDTH,strength:.12*GAME_WIDTH,timeLeft:360,maxTimeLeft:360};break;case RARE_POWERUP_TYPES.BLOCK_BOMB:const bX=paddle.x+paddle.width/2,bY=paddle.y-20;createEffectParticle(bX,bY,"#FF8C00",30,{sizeRange:[3,8],glow:!0,speedRange:[5,10]});const bR=.2*GAME_WIDTH;for(let i=blocks.length-1;0<=i;i--){const b=blocks[i];Math.pow(b.x+b.width/2-bX,2)+Math.pow(b.y+b.height/2-bY,2)<Math.pow(bR,2)&&(game.score+=10*game.level,blocks.splice(i,1),updateLevelProgressUI(),createEffectParticle(b.x+b.width/2,b.y+b.height/2,b.color,5))}updateCombo();break;}} | |
| function updateActivePowerUps() { /* ... */ const now=Date.now();Object.entries(game.activePowerUps).forEach(([t,d])=>{now>d.endTime&&(deactivatePowerUp(t),delete game.activePowerUps[t])});drawPowerUpIndicatorsUI()} | |
| function deactivatePowerUp(type) { /* ... */ switch(type){case POWERUP_TYPES.WIDE_PADDLE:paddle.width=PADDLE_DEFAULT_WIDTH;break;case POWERUP_TYPES.PIERCING_BALL:game.balls.forEach(b=>b.piercing=!1);break;}} | |
| function fireLaser() { /* ... */ const iM=game.activePowerUps[RARE_POWERUP_TYPES.MEGA_LASER];if(game.lasers.length>=(iM?2:4)||!(game.activePowerUps[POWERUP_TYPES.LASER_PADDLE]||iM))return;audioManager.playSound(audioManager.definitions.laserFire);const lW=paddle.width*(iM?.1:.05),lS=GAME_HEIGHT*(iM?.025:.02),lH=PADDLE_HEIGHT*(iM?2.5:1.8);iM?game.lasers.push({x:paddle.x+paddle.width/2,y:paddle.y,width:1.5*lW,height:lH,speed:lS,isMega:!0,h:lH}):(game.lasers.push({x:paddle.x+.2*paddle.width,y:paddle.y,width:lW,height:lH,speed:lS,isMega:!1,h:lH}),game.lasers.push({x:paddle.x+.8*paddle.width,y:paddle.y,width:lW,height:lH,speed:lS,isMega:!1,h:lH}))} | |
| function addNewBall() { /* ... */ const pB=game.balls.find(b=>b.isPrimary);if(!pB||6<=game.balls.length)return;const nB={...pB};nB.isPrimary=!1;nB.attached=!1;nB.x=pB.x+(Math.random()-.5)*pB.radius;nB.y=pB.y-pB.radius;nB.dx=(Math.random()-.5)*.6*pB.speed;nB.dy=-pB.speed*(.7+.3*Math.random());nB.trail=[];game.balls.push(nB)} | |
| // --- Game State Management --- | |
| function resetPrimaryBallAndPaddleState() { /* ... */ const pB=game.balls.find(b=>b.isPrimary);pB&&(pB.x=paddle.x+paddle.width/2,pB.y=paddle.y-1.5*pB.radius,pB.dx=0,pB.dy=0,pB.attached=!0,pB.piercing=!1,pB.trail=[]);game.balls.some(b=>b.isPrimary&&b.attached)&&(game.balls=game.balls.filter(b=>b.isPrimary));game.activePowerUps[POWERUP_TYPES.WIDE_PADDLE]||(paddle.width=PADDLE_DEFAULT_WIDTH);game.combo=0;document.getElementById("combo").textContent="0x"} | |
| async function showTransitionScreen(screenId, text, duration = 1500) { const s=document.getElementById(screenId); s.querySelector('div[class*="-number"], div[class*="-name-large"], div[class*="-defeated-text"]').textContent=text; s.classList.add('active'); await new Promise(r=>setTimeout(r,duration)); s.classList.remove('active'); await new Promise(r=>setTimeout(r,400));} | |
| async function nextLevel() { | |
| if(game.levelTransitioning) return; game.levelTransitioning = true; | |
| if(game.isFlawlessLevel && game.level > 0 && !BOSS_LEVELS.includes(game.level)) unlockAchievement('NO_LIVES_LOST_LEVEL'); | |
| await showTransitionScreen('levelTransitionScreen', `LEVEL ${game.level + 1}`); | |
| audioManager.playSound(audioManager.definitions.levelUp); | |
| game.level++; game.levelStartTime = Date.now(); | |
| updatePlayerStat('highestLevelReached', Math.max(game.playerStats.highestLevelReached || 0, game.level)); | |
| if(game.level > MAX_LEVELS){gameWon();game.levelTransitioning=false;return;} | |
| if(game.level===3)unlockAchievement('LEVEL_3'); | |
| game.balls.forEach(b=>b.speed+=(GAME_HEIGHT*0.0005)*(game.difficulty==='hardcore'?1.8:1.2)); | |
| Object.entries(game.activePowerUps).forEach(([t,d])=>{if(!d.isRare||t===RARE_POWERUP_TYPES.BLACK_HOLE){deactivatePowerUp(t);delete game.activePowerUps[t];}}); | |
| game.powerUpsOnScreen=[];game.lasers=[];if(game.blackHole)game.blackHole=null; | |
| paddle.width=PADDLE_DEFAULT_WIDTH;initPrimaryBall(); | |
| if(BOSS_LEVELS.includes(game.level)){await showTransitionScreen('bossIntroScreen',BOSS_CREATURES[game.level], 2500);createBoss(game.level);game.initialBlockCount=0;} | |
| else{game.currentBoss=null;document.getElementById('bossHealthContainer').style.display='none';document.getElementById('bossName').style.display='none';createBlocks();} | |
| updateDepthMeterUI();updateLevelProgressUI(); | |
| if(game.level===1&& (game.playerStats.gamesPlayed || 0) <=1)showTutorialMessage("Click to launch the Pearl!",5000); | |
| game.levelTransitioning=false; | |
| } | |
| async function bossDefeated() { | |
| const bossName = game.currentBoss ? game.currentBoss.creature : "BOSS"; | |
| await showTransitionScreen('bossDefeatedScreen', `${bossName} DEFEATED!`, 2500); | |
| audioManager.playSound(audioManager.definitions.bossDefeat); | |
| addBubbleText(`${bossName} Vanquished!`, GAME_WIDTH/2, GAME_HEIGHT*0.4, '#FFD700', 30, 150); | |
| game.score += 2000 * game.level; updatePlayerStat('bossesDefeated', 1); | |
| if (game.level === BOSS_LEVELS[0]) unlockAchievement('LEVEL_5_BOSS'); | |
| game.currentBoss=null;document.getElementById('bossHealthContainer').style.display='none';document.getElementById('bossName').style.display='none'; | |
| for(let i=0;i<3;i++)spawnPowerUpItem(GAME_WIDTH/2+(i-1)*50,GAME_HEIGHT/3); | |
| // Next level (or game won) will be triggered by gameLoop detecting blocks.length === 0 and no boss | |
| } | |
| function updateHighScore() { /* ... */ if(game.score>game.highScore){game.highScore=game.score;localStorage.setItem("deepSeaBreakoutHighScore_v2S",game.highScore);1E4<=game.score&&unlockAchievement("HIGH_SCORE_10K");return!0}return!1} | |
| function gameOver() { /* ... */ game.running=!1;updatePlayerStat("totalGameTime",(Date.now()-game.gameSessionStartTime)/1E3);savePlayerStats();const nH=updateHighScore();document.getElementById("finalScore").textContent=game.score;document.getElementById("highScoreText").textContent=nH?`NEW HIGH SCORE: ${game.highScore}`:`High Score: ${game.highScore}`;showScreen("gameOver");audioManager.playSound(audioManager.definitions.gameOver)} | |
| function gameWon() { /* ... */ game.running=!1;updatePlayerStat("totalGameTime",(Date.now()-game.gameSessionStartTime)/1E3);savePlayerStats();unlockAchievement("GAME_WON");const nH=updateHighScore();document.getElementById("winScore").textContent=game.score;document.getElementById("totalLevelsWon").textContent=MAX_LEVELS;document.getElementById("winHighScoreText").textContent=nH?`NEW HIGH SCORE: ${game.highScore}!`:`High Score: ${game.highScore}`;showScreen("gameWon");audioManager.playSound(audioManager.definitions.gameWon)} | |
| async function initGame(difficulty = 'normal') { | |
| const ldSc=document.getElementById('loadingScreen'),ldTxt=document.getElementById('loadingText'),ldPr=document.getElementById('loadingProgress'); | |
| ldSc.classList.remove('hidden');ldTxt.textContent="INIT AUDIO...";ldPr.style.width='10%';await audioManager.init(); | |
| ldTxt.textContent="GAME STATE...";ldPr.style.width='30%';await new Promise(r=>setTimeout(r,50)); // Reduced artificial delay | |
| game = { // Full reset | |
| score:0, highScore: parseInt(localStorage.getItem("deepSeaBreakoutHighScore_v2S")||"0"), | |
| lives:3, maxLives:3, level:1, running:true, paused:false, difficulty:'normal', | |
| initialBlockCount:0, particles:[], backgroundFish:[], backgroundBubbles:[], | |
| powerUpsOnScreen:[], activePowerUps:{}, combo:0, lastHitTime:0, | |
| balls:[], lasers:[], bubbleTexts:[], blackHole:null, currentBoss:null, | |
| currentBackgroundHue:180, currentTheme:'deepSea', | |
| achievements:{}, playerStats:{}, paddleSkin:PADDLE_SKINS[0], | |
| levelTransitioning:false, gameTime:0, currentScreen:'loadingScreen', | |
| gameSessionStartTime:Date.now(), levelStartTime:Date.now(), isFlawlessLevel:true | |
| }; | |
| game.difficulty=difficulty; game.maxLives=game.difficulty==='hardcore'?2:3; game.lives=game.maxLives; | |
| game.paddleSkin=PADDLE_SKINS.find(s=>s.id===localStorage.getItem("deepSeaBreakoutPaddleSkin_v2S"))||PADDLE_SKINS[0]; | |
| updatePlayerStat('gamesPlayed',1);defineAchievements();initPlayerStats(); | |
| paddle={x:GAME_WIDTH/2-PADDLE_DEFAULT_WIDTH/2,y:GAME_HEIGHT-PADDLE_HEIGHT*2.5,width:PADDLE_DEFAULT_WIDTH,height:PADDLE_HEIGHT,targetX:GAME_WIDTH/2-PADDLE_DEFAULT_WIDTH/2}; | |
| initPrimaryBall(); | |
| ldTxt.textContent="BUILDING LEVEL...";ldPr.style.width='60%';await new Promise(r=>setTimeout(r,50));createBlocks(); | |
| ldTxt.textContent="SUMMONING LIFE...";ldPr.style.width='80%';await new Promise(r=>setTimeout(r,50));createBackgroundElements(); | |
| // Hide all dialogs except game area related UI | |
| document.querySelectorAll('.dialog-box, .menu, .settings-panel, .leaderboard, .shop, .daily-challenge, .achievement-system, .tutorial-screen, .stats-screen, .credits, .customization, .profile, .multiplayer-info, .matchmaking, #difficultySelectScreen, #placeholderDialog').forEach(el=>el.style.display='none'); | |
| document.getElementById('bossHealthContainer').style.display='none';document.getElementById('bossName').style.display='none';document.getElementById('pauseMessage').style.display='none'; | |
| updateHealthBarUI();updateDepthMeterUI();updateLevelProgressUI(); | |
| document.getElementById('score').textContent='0'; | |
| document.getElementById('level').textContent='1'; | |
| document.getElementById('combo').textContent='0x'; | |
| document.getElementById('maxLevels').textContent=MAX_LEVELS; | |
| drawPowerUpIndicatorsUI(); | |
| ldTxt.textContent="GAME READY!";ldPr.style.width='100%';await new Promise(r=>setTimeout(r,100));ldSc.classList.add('hidden'); | |
| game.currentScreen='game'; // Set current screen to game playing area | |
| if((game.playerStats.gamesPlayed||0)<=1)showTutorialMessage("Move mouse to control paddle!",5000); | |
| if(animationFrameId)cancelAnimationFrame(animationFrameId); | |
| lastTime=performance.now(); | |
| gameLoop(lastTime); | |
| } | |
| // --- Game Loop --- | |
| let lastTime = 0; let animationFrameId; let frameCount = 0; | |
| function gameLoop(timestamp) { | |
| animationFrameId=requestAnimationFrame(gameLoop); | |
| if(!game.running) return; // Stop loop if game is not running (e.g., after game over) | |
| const deltaTime=(timestamp-lastTime)/1000||0;lastTime=timestamp; | |
| // Critical check: only update/draw if game screen is active and not paused/transitioning | |
| if (game.currentScreen === 'game' && !game.paused && !game.levelTransitioning) { | |
| game.gameTime += deltaTime; // Accumulate game time only when active | |
| ctx.clearRect(0,0,GAME_WIDTH,GAME_HEIGHT); // Clear canvas | |
| // Update game elements | |
| updateBackgroundElements(); updatePaddle(); updateBalls(); updateBoss(); | |
| updateLasers(); updateParticles(); createBallTrailParticles(); | |
| updatePowerUpsOnScreen(); updateActivePowerUps(); updateBlackHole(); | |
| // Draw game elements | |
| drawBackgroundElements(); drawBlocks(); drawBoss(); drawPowerUpsOnScreen(); | |
| drawLasers(); drawBlackHole(); drawBallTrails(); drawBalls(); | |
| drawPaddle(); drawParticles(); drawBubbleTexts(); | |
| // Update UI text elements (score, level, etc.) | |
| document.getElementById('score').textContent=game.score; | |
| document.getElementById('level').textContent=game.level; | |
| // Combo is updated in its own function | |
| // Check for level completion (no boss AND no blocks) | |
| if(!game.currentBoss && blocks.length === 0 && game.running && !game.levelTransitioning){ | |
| nextLevel(); | |
| } | |
| frameCount++; | |
| } | |
| // If not in active game screen, or paused, or transitioning, the loop still runs | |
| // (via requestAnimationFrame) but skips the update/draw logic above. | |
| // This allows UI interactions (like closing a dialog) to change game.currentScreen or game.paused | |
| // and then the next frame of the loop will correctly resume game logic. | |
| } | |
| // --- Event Listeners & UI Helpers --- | |
| let eventController = new AbortController(); | |
| function setupEventListeners() { | |
| eventController.abort();eventController=new AbortController();const{signal}=eventController; | |
| canvas.addEventListener('mousemove',e=>{if(game.currentScreen==='game'&&!game.paused){const r=canvas.getBoundingClientRect();paddle.targetX=e.clientX-r.left-paddle.width/2;mouseX=e.clientX-r.left;}},{signal}); | |
| canvas.addEventListener('click',async()=>{if(!game.running||game.paused||game.currentScreen!=='game'||game.levelTransitioning)return;await audioManager.init();let bL=!1;game.balls.forEach(b=>{if(b.attached){const aO=(Math.random()-.5)*Math.PI*.1,bA=-Math.PI/2;b.dx=b.speed*Math.sin(bA+aO);b.dy=b.speed*Math.cos(bA+aO);b.attached=!1;bL=!0}});!bL&&(game.activePowerUps[POWERUP_TYPES.LASER_PADDLE]||game.activePowerUps[RARE_POWERUP_TYPES.MEGA_LASER])&&fireLaser()},{signal}); | |
| window.addEventListener('keydown',e=>{const k=e.key.toLowerCase(); | |
| if(k==='p'&&game.running&&game.currentScreen==='game'&&!game.levelTransitioning){game.paused=!game.paused;document.getElementById('pauseMessage').style.display=game.paused?'block':'none';if(!game.paused&&animationFrameId){lastTime=performance.now();}} // No need to re-request animationFrame, it's always running | |
| if(k==='m'){const iM=audioManager.toggleMute();showNotification(iM?"Audio Muted":"Audio Unmuted","",iM?"๐":"๐",1500);} | |
| if(k==='n'&&e.shiftKey&&game.running&&game.currentScreen==='game'&&!game.levelTransitioning){game.currentBoss?bossDefeated():blocks=[];console.log("Debug: Level skip.");} | |
| },{signal}); | |
| document.getElementById('masterVolumeSlider').addEventListener('input',(e)=>audioManager.setVolume('master',parseFloat(e.target.value)),{signal}); | |
| document.getElementById('sfxVolumeSlider').addEventListener('input',(e)=>audioManager.setVolume('sfx',parseFloat(e.target.value)),{signal}); | |
| document.getElementById('themeSelector').addEventListener('change',(e)=>{game.currentTheme=e.target.value;localStorage.setItem("deepSeaTheme_v2S",game.currentTheme);},{signal}); | |
| const comboUIEl = document.querySelector('.ui div:nth-child(4)'); // Assuming Combo is 4th UI element | |
| if(comboUIEl) { | |
| comboUIEl.addEventListener('mouseenter', (e) => { | |
| const tooltip = document.getElementById('tooltip'); | |
| if (tooltip) { | |
| tooltip.textContent = "Consecutive hits score bonus points!"; | |
| const canvasRect = canvas.getBoundingClientRect(); // Tooltip relative to canvas/viewport | |
| tooltip.style.left = `${e.clientX - canvasRect.left + 15}px`; | |
| tooltip.style.top = `${e.clientY - canvasRect.top - 30}px`; | |
| tooltip.style.display = 'block'; | |
| setTimeout(() => tooltip.classList.add('show'), 10); // Allow display block to apply before transition | |
| } | |
| }, {signal}); | |
| comboUIEl.addEventListener('mouseleave', () => { | |
| const tooltip = document.getElementById('tooltip'); | |
| if (tooltip) { | |
| tooltip.classList.remove('show'); | |
| setTimeout(() => tooltip.style.display = 'none', 200); // Hide after transition | |
| } | |
| }, {signal}); | |
| } | |
| } | |
| document.addEventListener('DOMContentLoaded', () => { | |
| game.gameSessionStartTime = Date.now(); // Set session start time | |
| game.highScore = parseInt(localStorage.getItem("deepSeaBreakoutHighScore_v2S") || "0"); | |
| game.paddleSkin = PADDLE_SKINS.find(s => s.id === localStorage.getItem("deepSeaBreakoutPaddleSkin_v2S")) || PADDLE_SKINS[0]; | |
| game.currentTheme = localStorage.getItem("deepSeaTheme_v2S") || 'deepSea'; | |
| document.getElementById('themeSelector').value = game.currentTheme; | |
| defineAchievements(); // Define achievements structure | |
| initPlayerStats(); // Load stats | |
| populatePaddleSkins(); // Populate customization screen | |
| // Make UI functions globally accessible for HTML onclick | |
| window.showScreen = showScreen; | |
| window.showDifficultySelect = () => showScreen('difficultySelectScreen'); | |
| window.showScreenWithPlaceholder = showScreenWithPlaceholder; | |
| window.initGame = initGame; // Make initGame global for buttons | |
| showScreen('mainMenu'); // Start with the main menu | |
| setupEventListeners(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |