/
DOLOR3SS
/
ddd
Обзор
Документация
Войти
/
DOLOR3SS
/
ddd
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
index.html
1 820 строк
98 KB
DOLOR3SS
Create: index.html
04 июл 2026, 01:10
Верифицирован
04 июл 2026, 01:10
ce698c3
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Любимому другу</title> <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; image-rendering: pixelated; } :root { --pixel-font: 'Press Start 2P', monospace; --color-bg: #3b3936; --color-primary: #576a71; --color-secondary: #96b8cb; --color-accent: #b6cad1; --color-light: #e2e5e2; --color-border: #96b8cb; --color-text: #e2e5e2; --color-text-muted: rgba(226, 229, 226, 0.6); } body { font-family: var(--pixel-font); background: var(--color-bg); display: flex; justify-content: center; align-items: center; min-height: 100vh; overflow: hidden; color: var(--color-text); } #gameContainer { position: relative; width: 1000px; height: 650px; background: var(--color-bg); border: 4px solid var(--color-border); border-radius: 8px; overflow: hidden; box-shadow: 0 0 60px rgba(150, 184, 203, 0.1); } .screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; opacity: 1; transition: opacity 0.6s ease; background: var(--color-bg); } .screen.hidden { opacity: 0; pointer-events: none; display: none; } .gothic-btn { background: transparent; border: 3px solid var(--color-border); color: var(--color-text); padding: 12px 32px; font-family: var(--pixel-font); font-size: 11px; letter-spacing: 2px; cursor: pointer; transition: all 0.3s ease; text-transform: uppercase; border-radius: 4px; background: rgba(87, 106, 113, 0.15); } .gothic-btn:hover:not(:disabled) { background: var(--color-secondary); color: var(--color-bg); box-shadow: 0 0 30px rgba(150, 184, 203, 0.2); transform: scale(1.05); } .gothic-btn:disabled { opacity: 0.3; cursor: not-allowed; transform: none; } .gothic-btn.small { padding: 8px 18px; font-size: 9px; letter-spacing: 1px; } .gothic-btn.danger { border-color: #8b5a5a; color: #8b5a5a; } .gothic-btn.danger:hover:not(:disabled) { background: #8b5a5a; color: var(--color-text); } .gothic-btn::before { content: '◆'; margin-right: 10px; font-size: 10px; color: var(--color-secondary); } .gothic-btn::after { content: '◆'; margin-left: 10px; font-size: 10px; color: var(--color-secondary); } .gothic-btn.small::before, .gothic-btn.small::after { content: '◇'; font-size: 8px; } .gothic-input { background: rgba(87, 106, 113, 0.15); border: 3px solid var(--color-border); color: var(--color-text); padding: 10px 20px; font-family: var(--pixel-font); font-size: 10px; border-radius: 4px; width: 220px; text-align: center; transition: all 0.3s ease; } .gothic-input:focus { outline: none; border-color: var(--color-secondary); box-shadow: 0 0 30px rgba(150, 184, 203, 0.1); background: rgba(87, 106, 113, 0.25); } .gothic-input::placeholder { color: rgba(226, 229, 226, 0.3); font-size: 8px; } .auth-container { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; justify-content: center; } .auth-error { color: #8b5a5a; font-size: 9px; width: 100%; text-align: center; margin-top: 4px; transition: all 0.3s ease; } .auth-error.hidden { opacity: 0; transform: translateY(-10px); } .menu-buttons { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; } .menu-subtitle { color: var(--color-text-muted); font-size: 9px; letter-spacing: 4px; opacity: 0.6; font-family: var(--pixel-font); } #mainMenu { background: radial-gradient(ellipse at center, #2a2a28 0%, var(--color-bg) 80%); } .menu-content { display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 20px; } .game-title { font-size: 44px; color: var(--color-text); text-shadow: 0 0 60px rgba(150, 184, 203, 0.1); letter-spacing: 4px; line-height: 1.2; text-align: center; font-family: var(--pixel-font); } .game-title::after { content: '✦'; display: block; font-size: 18px; color: var(--color-secondary); margin-top: -4px; } .character-preview { width: 120px; height: 140px; display: flex; justify-content: center; align-items: center; position: relative; cursor: pointer; } .character { position: relative; width: 60px; height: 100px; transition: transform 0.3s ease; } .character-body { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 40px; height: 50px; background: #4a4a48; border-radius: 4px 4px 2px 2px; border: 3px solid var(--color-border); } .character-head { position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 35px; height: 40px; background: #4a4a48; border-radius: 50% 50% 40% 40%; border: 3px solid var(--color-border); } .eye { position: absolute; width: 6px; height: 8px; background: var(--color-light); top: 12px; } .left-eye { left: 7px; } .right-eye { right: 7px; } .thought-bubble { position: absolute; top: -50px; left: 50%; transform: translateX(-50%) scale(0.8); background: rgba(87, 106, 113, 0.15); border: 3px solid var(--color-border); padding: 6px 14px; border-radius: 4px; font-size: 9px; color: var(--color-text); white-space: nowrap; opacity: 0; transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); font-family: var(--pixel-font); } .thought-bubble::after { content: ''; position: absolute; bottom: -8px; left: 50%; transform: translateX(-50%); border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 8px solid rgba(87, 106, 113, 0.15); } .character-preview:hover .thought-bubble { opacity: 1; transform: translateX(-50%) scale(1); } .character-preview:hover .character { animation: jumpPreview 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); } @keyframes jumpPreview { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-30px); } } #rulesScreen { background: radial-gradient(ellipse at center, #2a2a28 0%, var(--color-bg) 80%); } .rules-container { display: flex; width: 85%; height: 60%; gap: 20px; align-items: center; justify-content: center; border: 3px solid rgba(150, 184, 203, 0.15); padding: 20px; border-radius: 4px; } .rules-left, .rules-right { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; } .rules-center { flex: 2; max-width: 40%; } .rules-text-wrapper { background: rgba(87, 106, 113, 0.08); border: 3px solid rgba(150, 184, 203, 0.1); border-radius: 4px; padding: 8px; height: 100%; max-height: 250px; } .rules-text-scroll { max-height: 230px; overflow-y: auto; padding: 12px 16px; color: var(--color-text); } .rules-text-scroll::-webkit-scrollbar { width: 4px; } .rules-text-scroll::-webkit-scrollbar-thumb { background: var(--color-secondary); border-radius: 2px; } .rules-text-scroll h2 { color: var(--color-light); font-size: 14px; letter-spacing: 3px; margin-bottom: 12px; font-family: var(--pixel-font); } .rules-text-scroll ul { list-style: none; padding: 0; } .rules-text-scroll ul li { padding: 4px 0; padding-left: 20px; position: relative; font-size: 9px; line-height: 1.8; color: var(--color-text-muted); border-bottom: 1px solid rgba(150, 184, 203, 0.05); font-family: var(--pixel-font); } .rules-text-scroll ul li::before { content: '✦'; position: absolute; left: 0; color: var(--color-secondary); font-size: 8px; } .pipe-entrance { width: 40px; height: 80px; background: linear-gradient(180deg, var(--color-secondary), var(--color-primary)); border-radius: 20px 20px 4px 4px; border: 3px solid var(--color-border); position: relative; box-shadow: 0 4px 30px rgba(150, 184, 203, 0.1); } .pipe-entrance::after { content: ''; position: absolute; top: -4px; left: -6px; right: -6px; height: 10px; background: var(--color-light); border-radius: 6px 6px 0 0; opacity: 0.2; } .pipe-label { color: var(--color-text-muted); font-size: 7px; margin-top: 6px; letter-spacing: 2px; opacity: 0.5; font-family: var(--pixel-font); } #rulesCharacter { transform: scale(1.2); } #rulesStartBtn { margin-top: 16px; } #gameScreen { background: var(--color-bg); padding: 0; } #gameCanvas { width: 100%; height: 100%; display: block; image-rendering: pixelated; } .hud { position: absolute; top: 12px; left: 20px; right: 20px; display: flex; justify-content: space-between; align-items: flex-start; font-size: 9px; color: var(--color-text); letter-spacing: 1px; pointer-events: none; font-family: var(--pixel-font); background: rgba(59, 57, 54, 0.8); padding: 8px 16px; border-radius: 4px; border: 2px solid rgba(150, 184, 203, 0.1); } .hud-left { display: flex; flex-direction: column; gap: 4px; } .hud-right { display: flex; gap: 16px; align-items: center; } .hud span { text-shadow: 0 0 20px rgba(0, 0, 0, 0.5); font-size: 9px; } .health-bar-container { display: flex; align-items: center; gap: 8px; background: rgba(0, 0, 0, 0.2); padding: 2px 10px 2px 6px; border-radius: 4px; border: 2px solid rgba(150, 184, 203, 0.1); } .health-icon { font-size: 10px; color: #ff6b6b; } .health-bar-outer { width: 80px; height: 6px; background: rgba(255, 107, 107, 0.1); border-radius: 3px; overflow: hidden; border: 2px solid rgba(255, 107, 107, 0.05); } .health-bar-inner { height: 100%; width: 100%; background: linear-gradient(90deg, #ff6b6b, #ff8787); border-radius: 3px; transition: width 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); box-shadow: 0 0 20px rgba(255, 107, 107, 0.1); } .health-bar-inner.low { background: linear-gradient(90deg, #ff4444, #ff6b6b); animation: healthPulse 0.8s ease-in-out infinite; } @keyframes healthPulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } } .health-text { font-size: 7px; color: var(--color-text-muted); min-width: 20px; text-align: center; font-family: var(--pixel-font); } .level-notification { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(1); pointer-events: none; z-index: 10; opacity: 0; transition: all 0.8s cubic-bezier(0.34, 1.56, 0.64, 1); } .level-notification.visible { opacity: 1; transform: translate(-50%, -50%) scale(1); } .level-notification.fading { opacity: 0; transform: translate(-50%, -60%) scale(1.05); } .level-number { font-size: 36px; font-family: var(--pixel-font); color: var(--color-light); padding: 16px 36px; border: 4px solid var(--color-border); background: rgba(59, 57, 54, 0.85); border-radius: 8px; box-shadow: 0 0 40px rgba(150, 184, 203, 0.1), inset 0 0 40px rgba(150, 184, 203, 0.05); letter-spacing: 3px; white-space: nowrap; } .star-notification { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.8); font-size: 16px; color: var(--color-light); pointer-events: none; z-index: 10; opacity: 0; transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); background: rgba(59, 57, 54, 0.85); padding: 16px 32px; border-radius: 8px; border: 3px solid var(--color-border); text-align: center; font-family: var(--pixel-font); box-shadow: 0 8px 40px rgba(150, 184, 203, 0.05); } .star-notification.visible { opacity: 1; transform: translate(-50%, -60%) scale(1); } .star-notification .star-icon { font-size: 28px; display: block; margin-bottom: 4px; filter: drop-shadow(0 0 20px rgba(150, 184, 203, 0.2)); } .star-notification .compliment { font-size: 12px; color: var(--color-light); font-family: var(--pixel-font); } .achievement-notification { position: absolute; bottom: 80px; left: 50%; transform: translateX(-50%) translateY(20px); font-size: 10px; color: var(--color-light); background: rgba(59, 57, 54, 0.9); border: 3px solid var(--color-border); padding: 10px 24px; border-radius: 4px; pointer-events: none; z-index: 10; opacity: 0; transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); font-family: var(--pixel-font); text-align: center; max-width: 80%; } .achievement-notification.visible { opacity: 1; transform: translateX(-50%) translateY(0); } #deathScreen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.85); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 100; opacity: 0; pointer-events: none; transition: opacity 0.6s ease; } #deathScreen.visible { opacity: 1; pointer-events: all; } .death-content { text-align: center; transform: scale(0.9); transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); } #deathScreen.visible .death-content { transform: scale(1); } .death-icon { font-size: 60px; animation: deathPulse 1.5s ease-in-out infinite; } @keyframes deathPulse { 0%,100% { transform: scale(1); opacity: 0.8; } 50% { transform: scale(1.1); opacity: 1; } } .death-content h2 { color: #ff6b6b; font-size: 28px; font-family: var(--pixel-font); letter-spacing: 4px; margin: 12px 0; text-shadow: 0 0 40px rgba(255, 107, 107, 0.2); } .death-content p { color: var(--color-text-muted); font-size: 10px; margin-bottom: 20px; opacity: 0.7; font-family: var(--pixel-font); } .death-stats { display: flex; gap: 30px; margin: 12px 0 20px; color: var(--color-text-muted); font-size: 9px; font-family: var(--pixel-font); } .death-stats span { display: flex; align-items: center; gap: 6px; } .death-stats strong { color: var(--color-light); } #achievementsScreen { background: radial-gradient(ellipse at center, #2a2a28 0%, var(--color-bg) 80%); } .achievements-container { max-width: 80%; width: 100%; max-height: 80%; display: flex; flex-direction: column; align-items: center; gap: 16px; } .achievements-container h2 { color: var(--color-light); font-size: 18px; font-family: var(--pixel-font); letter-spacing: 3px; } .achievements-list { width: 100%; max-height: 400px; overflow-y: auto; padding: 12px 16px; border: 3px solid rgba(150, 184, 203, 0.1); border-radius: 4px; background: rgba(0, 0, 0, 0.2); display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } .achievements-list::-webkit-scrollbar { width: 4px; } .achievements-list::-webkit-scrollbar-thumb { background: var(--color-secondary); border-radius: 2px; } .achievement-item { display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: rgba(87, 106, 113, 0.08); border-radius: 4px; border: 2px solid rgba(150, 184, 203, 0.05); transition: all 0.3s ease; } .achievement-item.unlocked { border-color: rgba(150, 184, 203, 0.2); background: rgba(150, 184, 203, 0.05); } .achievement-item.locked { opacity: 0.4; } .achievement-item .ach-icon { font-size: 16px; } .achievement-item .ach-info { flex: 1; } .achievement-item .ach-name { color: var(--color-text); font-size: 9px; font-family: var(--pixel-font); } .achievement-item .ach-desc { color: var(--color-text-muted); font-size: 7px; opacity: 0.7; font-family: var(--pixel-font); } .achievement-item .ach-status { font-size: 9px; color: var(--color-text-muted); } .achievement-item.unlocked .ach-status { color: var(--color-secondary); } #completionScreen { background: radial-gradient(ellipse at center, #2a2a28 0%, var(--color-bg) 80%); } .completion-content { display: flex; flex-direction: column; align-items: center; gap: 16px; max-width: 70%; text-align: center; } .final-star { font-size: 60px; animation: starPulse 1.5s ease-in-out infinite; } @keyframes starPulse { 0%,100% { transform: scale(1); opacity: 1; text-shadow: 0 0 60px rgba(150, 184, 203, 0.3); } 50% { transform: scale(1.15); opacity: 0.8; text-shadow: 0 0 100px rgba(150, 184, 203, 0.5); } } .completion-content h2 { color: var(--color-light); font-size: 22px; font-family: var(--pixel-font); letter-spacing: 4px; } .completion-text-scroll { max-height: 200px; overflow-y: auto; padding: 12px 20px; color: var(--color-text); font-size: 10px; line-height: 2; border: 3px solid rgba(150, 184, 203, 0.1); border-radius: 4px; background: rgba(0, 0, 0, 0.2); font-family: var(--pixel-font); } .completion-text-scroll::-webkit-scrollbar { width: 4px; } .completion-text-scroll::-webkit-scrollbar-thumb { background: var(--color-secondary); border-radius: 2px; } .sound-controls { position: absolute; bottom: 15px; right: 15px; display: flex; gap: 8px; z-index: 50; } .sound-btn { background: rgba(59, 57, 54, 0.8); border: 2px solid rgba(150, 184, 203, 0.15); color: var(--color-text); width: 34px; height: 34px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; font-family: var(--pixel-font); } .sound-btn:hover { background: rgba(150, 184, 203, 0.1); border-color: var(--color-border); transform: scale(1.05); } .sound-btn.muted { opacity: 0.4; } @keyframes damageFlash { 0%,100% { filter: brightness(1); } 25% { filter: brightness(1.5); } 50% { filter: brightness(0.7); } 75% { filter: brightness(1.3); } } .damage-flash { animation: damageFlash 0.3s ease; } @media (max-width: 1024px) { #gameContainer { width: 95vw; height: 80vh; border-radius: 4px; } } @media (max-width: 768px) { .game-title { font-size: 28px; } .rules-container { flex-direction: column; height: auto; padding: 12px; } .rules-center { max-width: 100%; width: 100%; } .rules-left, .rules-right { display: none; } .level-number { font-size: 20px; padding: 10px 16px; } .hud { font-size: 7px; padding: 4px 8px; flex-wrap: wrap; } .health-bar-outer { width: 50px; } .achievements-list { grid-template-columns: 1fr; max-height: 300px; } .menu-buttons { flex-direction: column; align-items: center; } .auth-container { flex-direction: column; } .gothic-input { width: 100%; max-width: 200px; } .death-content h2 { font-size: 20px; } .game-title { font-size: 24px; } } </style> </head> <body> <div id="gameContainer"> <!-- МЕНЮ --> <div id="mainMenu" class="screen"> <div class="menu-content"> <h1 class="game-title">Любимомууу<br>:3</h1> <div class="auth-container"> <input type="text" id="playerNameInput" placeholder="Введи своё имя..." maxlength="20" class="gothic-input"> <button id="authButton" class="gothic-btn small">Войти</button> <div id="authError" class="auth-error hidden">Это имя уже занято!</div> </div> <div class="character-preview" id="characterPreview"> <div class="character" id="menuCharacter"> <div class="character-body"></div> <div class="character-head"> <div class="eye left-eye"></div> <div class="eye right-eye"></div> </div> <div class="thought-bubble">Привет!!</div> </div> </div> <div class="menu-buttons"> <button id="startButton" class="gothic-btn" disabled>Начать</button> <button id="achievementsMenuBtn" class="gothic-btn small">🏆 Достижения</button> <button id="resetProgressBtn" class="gothic-btn small danger">🔄 Сбросить</button> </div> <p class="menu-subtitle">~ путешествие начинается ~</p> </div> </div> <!-- ПРАВИЛА --> <div id="rulesScreen" class="screen hidden"> <div class="rules-container"> <div class="rules-left"><div class="character" id="rulesCharacter"><div class="character-body"></div><div class="character-head"><div class="eye left-eye"></div><div class="eye right-eye"></div></div></div></div> <div class="rules-center"> <div class="rules-text-wrapper"> <div class="rules-text-scroll"> <h2>Правила игры</h2> <ul> <li>Управляй стрелками или WASD</li> <li>Прыгай по блокам (Пробел или W)</li> <li>Собирай звёздочки (1-3 на уровне)</li> <li>У тебя 5 здоровья и 3 жизни</li> <li>Избегай шипов на платформах</li> <li>Запрыгни на трубу сверху, чтобы пройти уровень</li> <li>Всего 30 уровней и 50 звёзд</li> </ul> </div> </div> </div> <div class="rules-right"><div class="pipe-entrance"></div><span class="pipe-label">→ Вход</span></div> </div> <button id="rulesStartBtn" class="gothic-btn">Начать игру</button> </div> <!-- ИГРА --> <div id="gameScreen" class="screen hidden"> <canvas id="gameCanvas"></canvas> <div id="hud" class="hud"> <div class="hud-left"> <span id="playerNameDisplay">Игрок</span> <div class="health-bar-container"> <span class="health-icon">❤️</span> <div class="health-bar-outer"><div class="health-bar-inner" id="healthBar" style="width:100%"></div></div> <span class="health-text" id="healthText">5/5</span> </div> </div> <div class="hud-right"> <span id="levelDisplay">Уровень: 1</span> <span id="starsDisplay">⭐ 0</span> <span id="achievementsDisplay">🏆 0</span> <span id="livesDisplay">❤️ x3</span> </div> </div> <div class="sound-controls"> <button id="musicToggle" class="sound-btn">🎵</button> <button id="soundToggle" class="sound-btn">🔊</button> </div> <div id="levelNotification" class="level-notification hidden"><span class="level-number">Уровень №1</span></div> <div id="starNotification" class="star-notification hidden"></div> <div id="achievementNotification" class="achievement-notification hidden"></div> </div> <!-- СМЕРТЬ --> <div id="deathScreen"> <div class="death-content"> <div class="death-icon">💀</div> <h2>Ты погиб!</h2> <p>Попробуй снова! ✨</p> <div class="death-stats"> <span>⭐ Собрано: <strong id="deathStars">0</strong></span> <span>🏆 Уровень: <strong id="deathLevel">0</strong></span> <span>❤️ Жизней: <strong id="deathLives">0</strong></span> </div> <div> <button id="retryButton" class="gothic-btn small">🔄 Попробовать</button> <button id="menuFromDeathBtn" class="gothic-btn small danger">🏠 В меню</button> </div> </div> </div> <!-- ДОСТИЖЕНИЯ --> <div id="achievementsScreen" class="screen hidden"> <div class="achievements-container"> <h2>🏆 Мои достижения</h2> <div class="achievements-list" id="achievementsList"></div> <button id="achievementsBackBtn" class="gothic-btn small">Назад</button> </div> </div> <!-- ЗАВЕРШЕНИЕ --> <div id="completionScreen" class="screen hidden"> <div class="completion-content"> <div class="final-star">⭐</div> <h2>Поздравляю, <span id="completionPlayerName">друг</span>!</h2> <div class="completion-text-scroll"> <p>Ты стал для меня по-настоящему близким человеком, и это настоящее чудо. С самого начала нашего общения ты показался мне весёлым, родным и понимающим — и с каждым днём это только укрепляется.</p> <p>С тобой легко и спокойно: я могу быть собой, делиться сокровенным, смеяться до слёз или говорить о серьёзном — и знать, что меня услышат и не осудят. Ты показал мне, какой может быть настоящая любовь и забота — и я это очень ценю.</p> <p>Спасибо за твоё терпение, доброту и поддержку. За то, что всегда готов выслушать, подбодрить и заставить улыбнуться в хмурый день. Благодаря тебе я знаю, каково это — встречаться с человеком, которому можно доверять безоговорочно.</p> <p style="color: #96b8cb; margin-top: 8px;">спасибаааа тебе люблю тебя оченьь :3</p> </div> <button id="restartButton" class="gothic-btn">Начать заново</button> </div> </div> </div> <script> // ======================================== // ЗВУКОВОЙ МЕНЕДЖЕР // ======================================== class SoundManager { constructor() { this.enabled = true; this.musicEnabled = true; this.audioContext = null; this.sounds = {}; this.music = {}; this.currentMusic = null; this.musicVolume = 0.3; this.isInitialized = false; this.initAudio(); } initAudio() { try { this.audioContext = new(window.AudioContext || window.webkitAudioContext)(); this.isInitialized = true; } catch (e) { this.enabled = false; this.musicEnabled = false; } } playSound(name, volume = 0.3) { if (!this.enabled || !this.isInitialized) return; try { const osc = this.audioContext.createOscillator(); const gain = this.audioContext.createGain(); const settings = { star: { freq: 880, dur: 0.1, type: 'sine' }, compliment: { freq: 660, dur: 0.12, type: 'sine' }, achievement: { freq: 523, dur: 0.15, type: 'square' }, jump: { freq: 300, dur: 0.08, type: 'sine' }, hit: { freq: 150, dur: 0.3, type: 'sawtooth' }, levelComplete: { freq: 523, dur: 0.1, type: 'sine' }, finalStar: { freq: 523, dur: 0.2, type: 'sine' }, button: { freq: 300, dur: 0.1, type: 'sine' }, menuHover: { freq: 440, dur: 0.06, type: 'sine' }, transition: { freq: 200, dur: 0.2, type: 'sine' }, death: { freq: 200, dur: 0.8, type: 'sawtooth' } }; const cfg = settings[name] || settings.button; osc.type = cfg.type; osc.frequency.value = cfg.freq; gain.gain.setValueAtTime(volume, this.audioContext.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + cfg.dur); osc.connect(gain); gain.connect(this.audioContext.destination); osc.start(); osc.stop(this.audioContext.currentTime + cfg.dur); } catch (e) {} } playMusic(name, loop = true) { if (!this.musicEnabled || !this.isInitialized) return; if (this.currentMusic && this.currentMusic.name === name) return; this.stopMusic(); try { const osc = this.audioContext.createOscillator(); const gain = this.audioContext.createGain(); osc.type = 'sine'; osc.frequency.value = 220; gain.gain.value = this.musicVolume * 0.4; osc.connect(gain); gain.connect(this.audioContext.destination); osc.start(); osc.loop = true; this.currentMusic = { source: osc, gainNode: gain, name: name }; } catch (e) {} } stopMusic() { if (this.currentMusic) { try { this.currentMusic.source.stop(); } catch (e) {} this.currentMusic = null; } } toggleSound() { this.enabled = !this.enabled; return this.enabled; } toggleMusic() { this.musicEnabled = !this.musicEnabled; if (!this.musicEnabled) this.stopMusic(); return this .musicEnabled; } playStarSound() { this.playSound('star', 0.25); setTimeout(() => this.playSound('compliment', 0.2), 200); } playAchievementSound() { this.playSound('achievement', 0.3); } playJumpSound() { this.playSound('jump', 0.15); } playHitSound() { this.playSound('hit', 0.2); } playLevelCompleteSound() { this.playSound('levelComplete', 0.25); } playFinalStarSound() { this.playSound('finalStar', 0.3); } playButtonSound() { this.playSound('button', 0.15); } playMenuHoverSound() { this.playSound('menuHover', 0.1); } playScreenTransitionSound() { this.playSound('transition', 0.15); } playDeathSound() { this.playSound('death', 0.35); } } const soundManager = new SoundManager(); document.addEventListener('click', () => { if (soundManager.audioContext && soundManager.audioContext.state === 'suspended') { soundManager.audioContext.resume(); } }, { once: false }); // ======================================== // КОМПЛИМЕНТЫ // ======================================== const compliments = [ 'Ты очень милии!!', 'Ты прекрасный!!', 'Ты самое лучшее что было со мной!!', 'Ты очень красивыый!!', 'Ты — чудо!', 'Ты очень добрии!!', 'Ты самый лучший!!', 'Ты шикарныый!!', 'Ты очень хорошиий!!', 'Ты заботливыый!', 'Ты делаешь мою жизнь лучше!', 'Ты очень пиздатый!', 'Ты моя мечтаа! :3', 'Ты очень ахуенный!!', 'Ты очень крутойй!!', 'Ты единственный кому я доверяю', 'Ты идеален!', 'Спасибо тебе за твое существование', 'Ты безумно дорог мне', 'Ты ослепителен!', 'Ты — совершенство!', 'Ты очень круто шутишь!', 'я тебя очень ценю!', 'Ты безумно интересная личность!', 'Ты очень криятный в общении!', 'я очень боюсь тебя потерять!!', 'я очень рада что я именно с тобой!', 'Ты мой подарочеек!!', 'Ты котеноооок!!', 'Ты похож на ангелочкаа!!', 'Ты очень щедрый!', 'Ты лучик света!', 'Ты очень ласковыый!!', 'я рада каждому твоему сообщению!', 'Ты уникален :3', 'я за тебя жизнь отдам.', 'Ты самый лучший в мире', 'Ты безумно привлекателен!', 'С тобой легко и спокойно!', 'Ты очень весёлии', 'Ты понимающий', 'Ты бесценен!', 'Ты надежный!', 'Твоя душа прекрасна!', 'Ты очень вайбовый челиик', 'я тебя очень люблю!', 'Ты очень забавный!', 'Ты пресеть пряям!!', 'Ты очень умныый! ты всё сможеешь!', 'Ты — лучший!' ]; // ======================================== // ДОСТИЖЕНИЯ // ======================================== const achievements = [ { id: 'first_star', name: 'Первая звезда', desc: 'Собери свою первую звезду', icon: '⭐' }, { id: 'star_collector', name: 'Коллекционер', desc: 'Собери 10 звёзд', icon: '🌟' }, { id: 'star_hunter', name: 'Охотник', desc: 'Собери 25 звёзд', icon: '🌠' }, { id: 'star_legend', name: 'Легенда', desc: 'Собери все 50 звёзд', icon: '🌌' }, { id: 'level_5', name: 'Путешественник', desc: 'Пройди 5 уровней', icon: '🚶' }, { id: 'level_15', name: 'Исследователь', desc: 'Пройди 15 уровней', icon: '🔍' }, { id: 'level_30', name: 'Покоритель', desc: 'Пройди все 30 уровней', icon: '🏔️' }, { id: 'jump_master', name: 'Мастер прыжка', desc: 'Соверши 100 прыжков', icon: '🦘' }, { id: 'no_hit', name: 'Неуязвимый', desc: 'Пройди 5 уровней без шипов', icon: '🛡️' }, { id: 'survivor', name: 'Выживший', desc: 'Погибни 10 раз', icon: '💪' }, { id: 'dedicated', name: 'Преданный друг', desc: 'Наиграй 30 минут', icon: '⏰' }, { id: 'final_star', name: 'Последняя звезда', desc: 'Собери финальную звезду', icon: '💫' }, { id: 'perfect_game', name: 'Идеальная игра', desc: 'Пройди игру с честью', icon: '🏅' }, { id: 'friend_forever', name: 'Друг навсегда', desc: 'Заверши игру с любовью', icon: '❤️' }, { id: 'never_give_up', name: 'Никогда не сдавайся', desc: 'Погибни 5 раз на уровне и пройди его', icon: '🔥' } ]; // ======================================== // УНИКАЛЬНЫЕ ЦВЕТОВЫЕ ГАММЫ ДЛЯ КАЖДОГО УРОВНЯ // ======================================== const levelPalettes = [ { bg: '#1a1a2e', primary: '#16213e', secondary: '#0f3460', accent: '#533483', light: '#e94560' }, { bg: '#2d2424', primary: '#5c3d2e', secondary: '#b85c38', accent: '#e0c097', light: '#ffd57e' }, { bg: '#1b262c', primary: '#0f4c75', secondary: '#3282b8', accent: '#bbe1fa', light: '#f4f9f9' }, { bg: '#232931', primary: '#393e46', secondary: '#4ecca3', accent: '#eeeeee', light: '#00adb5' }, { bg: '#2c1b4d', primary: '#451952', secondary: '#662e9b', accent: '#a14ebf', light: '#f9cff2' }, { bg: '#1a1c20', primary: '#2d4059', secondary: '#ea5455', accent: '#f07b3f', light: '#ffd460' }, { bg: '#0f1923', primary: '#1a2639', secondary: '#3e4a61', accent: '#c9a96e', light: '#f4ebd0' }, { bg: '#1e2022', primary: '#302e31', secondary: '#555555', accent: '#f0a500', light: '#f4f4f4' }, { bg: '#1b1b2f', primary: '#2d2d44', secondary: '#4a4e69', accent: '#9a8c98', light: '#c9ada7' }, { bg: '#0d1b2a', primary: '#1b263b', secondary: '#415a77', accent: '#778da9', light: '#e0e1dd' }, { bg: '#1a1a1a', primary: '#2d2d2d', secondary: '#4a4a4a', accent: '#6d6d6d', light: '#ff6b6b' }, { bg: '#0f0e17', primary: '#1c1b2e', secondary: '#2e2c4f', accent: '#4c4a73', light: '#ff8906' }, { bg: '#1d1d1d', primary: '#2c3e50', secondary: '#3498db', accent: '#ecf0f1', light: '#e74c3c' }, { bg: '#0a0a0a', primary: '#1a1a2e', secondary: '#3d3d5c', accent: '#5c5c8a', light: '#ffb347' }, { bg: '#1b1b1b', primary: '#2c1810', secondary: '#4a2c17', accent: '#7a4b2c', light: '#d4a574' }, { bg: '#0c0c1d', primary: '#19194d', secondary: '#2d2d7f', accent: '#4747b8', light: '#7b7bff' }, { bg: '#1a1a1a', primary: '#2d1f1f', secondary: '#4a2e2e', accent: '#7a4a4a', light: '#ff9999' }, { bg: '#0d1b1e', primary: '#1a2f33', secondary: '#2d5259', accent: '#46828c', light: '#7acbd6' }, { bg: '#1a1a0f', primary: '#2d2d1a', secondary: '#4a4a2d', accent: '#7a7a4a', light: '#ffff99' }, { bg: '#0f1a0f', primary: '#1a2d1a', secondary: '#2d4a2d', accent: '#4a7a4a', light: '#99ff99' }, { bg: '#1a0f1a', primary: '#2d1a2d', secondary: '#4a2d4a', accent: '#7a4a7a', light: '#ff99ff' }, { bg: '#0f0f1a', primary: '#1a1a2d', secondary: '#2d2d4a', accent: '#4a4a7a', light: '#9999ff' }, { bg: '#1a100a', primary: '#2d1c14', secondary: '#4a2e20', accent: '#7a4a33', light: '#ffb380' }, { bg: '#0a1a1a', primary: '#142d2d', secondary: '#204a4a', accent: '#337a7a', light: '#80ffff' }, { bg: '#1a0a0a', primary: '#2d1414', secondary: '#4a2020', accent: '#7a3333', light: '#ff8080' }, { bg: '#0a0a1a', primary: '#14142d', secondary: '#20204a', accent: '#33337a', light: '#8080ff' }, { bg: '#1a1a0a', primary: '#2d2d14', secondary: '#4a4a20', accent: '#7a7a33', light: '#ffff80' }, { bg: '#0a1a0a', primary: '#142d14', secondary: '#204a20', accent: '#337a33', light: '#80ff80' }, { bg: '#1a0a1a', primary: '#2d142d', secondary: '#4a204a', accent: '#7a337a', light: '#ff80ff' }, { bg: '#111111', primary: '#222222', secondary: '#444444', accent: '#777777', light: '#ff4444' } ]; // ======================================== // СОХРАНЕНИЕ // ======================================== class SaveManager { constructor() { this.storageKey = 'game_save_data'; } savePlayerData(pn, data) { try { const all = this.getAllData(); all[pn] = { ...all[pn], ...data, lastSaved: Date.now() }; localStorage.setItem(this.storageKey, JSON.stringify(all)); return true; } catch (e) { return false; } } loadPlayerData(pn) { try { const all = this.getAllData(); return all[pn] || null; } catch (e) { return null; } } getAllData() { try { const data = localStorage.getItem(this.storageKey); return data ? JSON.parse(data) : {}; } catch (e) { return {}; } } playerExists(pn) { const all = this.getAllData(); return !!all[pn]; } resetPlayerData(pn) { try { const all = this.getAllData(); delete all[pn]; localStorage.setItem(this.storageKey, JSON.stringify(all)); return true; } catch (e) { return false; } } } const saveManager = new SaveManager(); // ======================================== // СОСТОЯНИЕ // ======================================== const gameState = { currentLevel: 0, stars: 0, totalStars: 0, unlockedAchievements: [], levelsCompleted: 0, jumps: 0, noHitLevels: 0, finalStarCollected: false, playerName: null, player: null, levels: [], isGameActive: false, playTime: 0, currentLevelStars: 0, totalStarsCollected: 0, usedCompliments: [], levelComplete: false, health: 5, maxHealth: 5, lives: 3, deaths: 0, isDead: false, isInvincible: false, invincibleTimer: 0, damageFlashTimer: 0, deathCountOnLevel: 0 }; // ======================================== // ПЕРСОНАЖ // ======================================== class Player { constructor() { this.x = 50; this.y = 350; this.width = 28; this.height = 42; this.vx = 0; this.vy = 0; this.speed = 3.5; this.baseSpeed = 3.5; this.jumpPower = -16; this.baseJumpPower = -16; this.gravity = 0.5; this.onGround = false; this.facingRight = true; this.isJumping = false; this.walkFrame = 0; this.walkTimer = 0; this.isMoving = false; this.smoothX = this.x; this.smoothY = this.y; } update(level) { if (gameState.isDead) return; this.vy += this.gravity; this.x += this.vx; this.y += this.vy; // 🔄 ВОЗВРАТ НА ПЛАТФОРМУ ПРИ ПАДЕНИИ if (this.y > level.height + 80) { this.x = level.spawnX || 50; this.y = level.spawnY || 350; this.smoothX = this.x; this.smoothY = this.y; this.vx = 0; this.vy = 0; this.onGround = false; return; } if (this.x < 0) this.x = 0; if (this.x + this.width > level.width) this.x = level.width - this.width; if (this.y + this.height > level.height) { this.y = level.height - this.height; this.vy = 0; this.onGround = true; this.isJumping = false; } this.onGround = false; for (const platform of level.platforms) { if (this.collidesWith(platform)) { if (this.vy > 0) { this.y = platform.y - this.height; this.vy = 0; this.onGround = true; this.isJumping = false; } else if (this.vy < 0) { this.y = platform.y + platform.height; this.vy = 0; } } } // 💀 СМЕРТЬ ТОЛЬКО ОТ ШИПОВ for (const spike of level.spikes) { if (this.collidesWith(spike)) { this.die(level); return; } } // ТРУБА - УЛУЧШЕННАЯ ПРОВЕРКА ВХОДА for (const pipe of level.pipes) { if (pipe.isEntrance && !gameState.levelComplete) { const pipeTop = pipe.y; const playerBottom = this.y + this.height; const playerLeft = this.x + 5; const playerRight = this.x + this.width - 5; const pipeLeft = pipe.x + 5; const pipeRight = pipe.x + pipe.width - 5; if (playerBottom >= pipeTop - 5 && playerBottom <= pipeTop + 15 && playerRight > pipeLeft && playerLeft < pipeRight && this.vy >= -1) { if (gameState.currentLevel < 29) { gameState.levelComplete = true; setTimeout(() => showLevelComplete(), 300); } else { gameState.levelComplete = true; setTimeout(() => showFinalStar(), 300); } return; } } } // ЗВЁЗДЫ for (const star of level.stars) { if (!star.collected && this.collidesWith(star)) { star.collected = true; gameState.stars++; gameState.totalStars++; gameState.totalStarsCollected++; gameState.currentLevelStars++; let c = compliments[Math.floor(Math.random() * compliments.length)]; let attempts = 0; while (gameState.usedCompliments.includes(c) && attempts < 50) { c = compliments[Math.floor(Math .random() * compliments.length)]; attempts++; } gameState.usedCompliments.push(c); star.compliment = c; showStarNotification(star); checkAchievements(); updateHealthDisplay(); } } if (gameState.invincibleTimer > 0) { gameState.invincibleTimer -= 1 / 60; if (gameState.invincibleTimer <= 0) gameState.isInvincible = false; } if (gameState.damageFlashTimer > 0) { gameState.damageFlashTimer -= 1 / 60; if (gameState.damageFlashTimer <= 0) { document.querySelector('#gameCanvas')?.classList.remove('damage-flash'); } } if (Math.abs(this.vx) > 0.5 && this.onGround) { this.isMoving = true; this.walkTimer++; if (this.walkTimer > 8) { this.walkTimer = 0; this.walkFrame = (this.walkFrame + 1) % 4; } } else { this.isMoving = false; this.walkFrame = 0; this.walkTimer = 0; } this.smoothX += (this.x - this.smoothX) * 0.15; this.smoothY += (this.y - this.smoothY) * 0.15; } collidesWith(obj) { return this.x < obj.x + obj.width && this.x + this.width > obj.x && this.y < obj.y + obj.height && this.y + this.height > obj.y; } die(level) { if (gameState.isDead) return; gameState.isDead = true; gameState.deaths++; gameState.lives--; gameState.deathCountOnLevel++; this.speed = this.baseSpeed; this.jumpPower = this.baseJumpPower; this.vx = 0; this.vy = 0; soundManager.playDeathSound(); if (gameState.deathCountOnLevel >= 5) { if (!gameState.unlockedAchievements.includes('never_give_up')) { gameState.unlockedAchievements.push('never_give_up'); const ach = achievements.find(a => a.id === 'never_give_up'); if (ach) showAchievementNotification(ach); } } if (gameState.deaths >= 10 && !gameState.unlockedAchievements.includes('survivor')) { gameState.unlockedAchievements.push('survivor'); const ach = achievements.find(a => a.id === 'survivor'); if (ach) showAchievementNotification(ach); } showDeathScreen(); saveProgress(); } respawn(level) { if (gameState.lives <= 0) { gameState.lives = 3; gameState.health = gameState.maxHealth; gameState.currentLevel = 0; gameState.levelsCompleted = 0; gameState.stars = 0; gameState.totalStars = 0; gameState.totalStarsCollected = 0; gameState.deaths = 0; gameState.deathCountOnLevel = 0; this.speed = this.baseSpeed; this.jumpPower = this.baseJumpPower; hideDeathScreen(); document.getElementById('gameScreen').classList.add('hidden'); document.getElementById('mainMenu').classList.remove('hidden'); gameState.isDead = false; gameState.isGameActive = false; saveProgress(); updateHealthDisplay(); return; } gameState.isDead = false; gameState.isInvincible = true; gameState.invincibleTimer = 1.5; gameState.health = gameState.maxHealth; this.speed = this.baseSpeed; this.jumpPower = this.baseJumpPower; this.vx = 0; this.vy = 0; this.x = level.spawnX || 50; this.y = level.spawnY || 350; this.smoothX = this.x; this.smoothY = this.y; for (const star of level.stars) { if (star.collected) { star.collected = false; gameState.stars--; gameState.totalStars--; gameState.totalStarsCollected--; gameState.currentLevelStars--; } } updateHealthDisplay(); hideDeathScreen(); gameState.isGameActive = true; loadLevel(gameState.currentLevel); saveProgress(); } jump() { if (this.onGround && !gameState.isDead) { this.vy = this.jumpPower; this.onGround = false; this.isJumping = true; gameState.jumps++; soundManager.playJumpSound(); } } moveLeft() { if (!gameState.isDead) { this.vx = -this.speed; this.facingRight = false; } } moveRight() { if (!gameState.isDead) { this.vx = this.speed; this.facingRight = true; } } stop() { this.vx = 0; } draw(ctx) { if (gameState.isDead) ctx.globalAlpha = 0.3; if (gameState.isInvincible && !gameState.isDead) { const blink = Math.sin(Date.now() / 100) > 0; if (blink) ctx.globalAlpha = 0.5; } const dx = this.smoothX, dy = this.smoothY; ctx.save(); ctx.translate(dx + this.width / 2, dy + this.height / 2); if (!this.facingRight) ctx.scale(-1, 1); ctx.translate(-this.width / 2, -this.height / 2); ctx.fillStyle = '#4a4a48'; ctx.shadowColor = 'rgba(150, 184, 203, 0.1)'; ctx.shadowBlur = 10; ctx.beginPath(); ctx.roundRect(2, 18, this.width - 4, this.height - 20, 3); ctx.fill(); ctx.shadowBlur = 0; ctx.strokeStyle = '#96b8cb'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#4a4a48'; ctx.shadowColor = 'rgba(150, 184, 203, 0.05)'; ctx.shadowBlur = 8; ctx.beginPath(); ctx.arc(this.width / 2, 14, 13, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; ctx.strokeStyle = '#96b8cb'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#e2e5e2'; const eyeY = 11, eyeOff = 4.5; ctx.beginPath(); ctx.arc(this.width / 2 - eyeOff, eyeY, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(this.width / 2 + eyeOff, eyeY, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#3b3936'; ctx.beginPath(); ctx.arc(this.width / 2 - eyeOff + 1.2, eyeY + 1, 1.2, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(this.width / 2 + eyeOff + 1.2, eyeY + 1, 1.2, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#96b8cb'; ctx.lineWidth = 2; const legOff = this.walkFrame % 2 === 0 ? 0 : 3; ctx.beginPath(); ctx.moveTo(6, this.height - 4); ctx.lineTo(4 + legOff, this.height + 5); ctx.stroke(); ctx.beginPath(); ctx.moveTo(this.width - 6, this.height - 4); ctx.lineTo(this.width - 4 - legOff, this.height + 5); ctx.stroke(); ctx.strokeStyle = '#96b8cb'; ctx.lineWidth = 2; if (this.isJumping) { ctx.beginPath(); ctx.moveTo(2, 20); ctx.lineTo(-5, 10); ctx.stroke(); ctx.beginPath(); ctx.moveTo(this.width - 2, 20); ctx.lineTo(this.width + 5, 10); ctx.stroke(); } else if (this.isMoving) { const swing = Math.sin(this.walkTimer * 0.5) * 3; ctx.beginPath(); ctx.moveTo(2, 22); ctx.lineTo(-2 + swing, 32 + Math.abs(swing)); ctx.stroke(); ctx.beginPath(); ctx.moveTo(this.width - 2, 22); ctx.lineTo(this.width + 2 - swing, 32 + Math.abs(swing)); ctx.stroke(); } else { ctx.beginPath(); ctx.moveTo(2, 22); ctx.lineTo(-2, 32); ctx.stroke(); ctx.beginPath(); ctx.moveTo(this.width - 2, 22); ctx.lineTo(this.width + 2, 32); ctx.stroke(); } ctx.restore(); ctx.globalAlpha = 1; } } // ======================================== // ГЕНЕРАЦИЯ УРОВНЕЙ (СВОБОДНОЕ ПРОСТРАНСТВО НАД ТРУБОЙ) // ======================================== function generateLevel(levelIndex) { const palette = levelPalettes[levelIndex % levelPalettes.length]; const level = { width: 1000, height: 600, palette: palette, platforms: [], pipes: [], stars: [], spikes: [], spawnX: 50, spawnY: 350 }; const numStars = Math.floor(Math.random() * 3) + 1; // МОСТИК ВНИЗУ УРОВНЯ (пол во всю ширину) level.platforms.push({ x: 0, y: 560, width: level.width, height: 40 }); const seed = levelIndex * 7 + 13; const rng = seededRandom(seed); const numPlatforms = 5 + Math.floor(rng() * 7); const platformList = []; // СНАЧАЛА РАЗМЕЩАЕМ ТРУБУ, ЧТОБЫ ЗНАТЬ ГДЕ ОСТАВИТЬ СВОБОДНОЕ МЕСТО let pipeData = null; // Труба на нижнем мостике для гарантии свободного пространства сверху const pipeWidth = 45 + rng() * 25; const pipeHeight = 55 + rng() * 25; const pipeX = 100 + rng() * (level.width - 200); const pipeY = 560 - pipeHeight; // На мостике pipeData = { x: pipeX, y: pipeY, width: pipeWidth, height: pipeHeight, color: palette.accent, isEntrance: true, isExit: false, isCeiling: false }; level.pipes.push(pipeData); // Теперь размещаем платформы, но не над трубой const pipeZoneLeft = pipeData.x - 40; const pipeZoneRight = pipeData.x + pipeData.width + 40; const pipeZoneTop = pipeData.y - 120; // Свободное пространство над трубой for (let i = 0; i < numPlatforms; i++) { let x, y, w, h, attempts = 0, valid; do { x = 30 + rng() * (level.width - 150); y = 100 + rng() * (level.height - 250); w = 60 + rng() * 100; h = 16 + rng() * 10; valid = true; // Проверяем, что платформа не над трубой if (x < pipeZoneRight && x + w > pipeZoneLeft && y < pipeY + 60) { valid = false; } for (const p of level.platforms) { if (Math.abs(x - p.x) < 100 && Math.abs(y - p.y) < 80) { valid = false; break; } } attempts++; } while (!valid && attempts < 25); if (valid) { const platform = { x, y, width: w, height: h }; level.platforms.push(platform); platformList.push(platform); } } // Шипы на платформах (не на всех) const numSpikes = Math.min(Math.floor(rng() * 3), platformList.length); for (let i = 0; i < numSpikes && platformList.length > 0; i++) { const idx = Math.floor(rng() * platformList.length); const p = platformList[idx]; // Не размещаем шипы рядом с трубой if (Math.abs(p.x + p.width/2 - pipeData.x) > 80) { level.spikes.push({ x: p.x + p.width / 2 - 12, y: p.y - 18, width: 24, height: 18 }); } platformList.splice(idx, 1); } const starPositions = []; for (let i = 0; i < numStars; i++) { let x, y, attempts = 0, valid; do { x = 60 + rng() * (level.width - 120); y = 60 + rng() * (level.height - 150); valid = true; for (const p of level.platforms) { if (x > p.x - 30 && x < p.x + p.width + 30 && y > p.y - 30 && y < p.y + p.height + 30) { valid = false; break; } } for (const p of level.pipes) { if (x > p.x - 30 && x < p.x + p.width + 30 && y > p.y - 30 && y < p.y + p.height + 30) { valid = false; break; } } for (const spike of level.spikes) { if (x > spike.x - 40 && x < spike.x + spike.width + 40 && y > spike.y - 40 && y < spike.y + spike.height + 40) { valid = false; break; } } for (const pos of starPositions) { if (Math.abs(x - pos.x) < 60 && Math.abs(y - pos.y) < 60) { valid = false; break; } } attempts++; } while (!valid && attempts < 25); if (valid) { starPositions.push({ x, y }); level.stars.push({ x, y, width: 20, height: 20, collected: false, compliment: '✨' }); } } level.spawnX = 50; level.spawnY = 500; return level; } function seededRandom(seed) { let s = seed; return function() { s = (s * 9301 + 49297) % 233280; return s / 233280; }; } // ======================================== // ОТРИСОВКА УРОВНЯ // ======================================== function drawLevel(ctx, level, player) { const p = level.palette; const grad = ctx.createRadialGradient(500, 300, 100, 500, 300, 650); grad.addColorStop(0, p.light || '#ffffff'); grad.addColorStop(0.3, p.accent || '#888888'); grad.addColorStop(0.7, p.secondary || '#444444'); grad.addColorStop(1, p.bg || '#111111'); ctx.fillStyle = grad; ctx.fillRect(0, 0, level.width, level.height); ctx.globalAlpha = 0.08; for (let i = 0; i < 30; i++) { ctx.fillStyle = p.accent; const x = (i * 47 + 19) % level.width; const y = (i * 31 + 23) % level.height; ctx.beginPath(); ctx.arc(x, y, 2 + (i % 3), 0, Math.PI * 2); ctx.fill(); } ctx.globalAlpha = 1; for (const platform of level.platforms) { const isBridge = platform.width === level.width && platform.y === 560; if (isBridge) { const bridgeGrad = ctx.createLinearGradient(0, platform.y, 0, platform.y + platform.height); bridgeGrad.addColorStop(0, p.secondary); bridgeGrad.addColorStop(0.5, p.primary); bridgeGrad.addColorStop(1, p.bg); ctx.fillStyle = bridgeGrad; ctx.shadowColor = p.secondary; ctx.shadowBlur = 10; ctx.beginPath(); ctx.roundRect(platform.x, platform.y, platform.width, platform.height, 2); ctx.fill(); ctx.shadowBlur = 0; ctx.strokeStyle = p.primary; ctx.lineWidth = 1; ctx.globalAlpha = 0.5; for (let bx = 0; bx < platform.width; bx += 30) { ctx.beginPath(); ctx.moveTo(bx, platform.y); ctx.lineTo(bx, platform.y + platform.height); ctx.stroke(); } ctx.globalAlpha = 1; ctx.strokeStyle = p.accent; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(platform.x, platform.y + 2); ctx.lineTo(platform.x + platform.width, platform.y + 2); ctx.stroke(); } else { const g2 = ctx.createLinearGradient(platform.x, platform.y, platform.x, platform.y + platform.height); g2.addColorStop(0, p.secondary); g2.addColorStop(1, p.primary); ctx.fillStyle = g2; ctx.shadowColor = p.secondary; ctx.shadowBlur = 6; ctx.beginPath(); ctx.roundRect(platform.x, platform.y, platform.width, platform.height, 3); ctx.fill(); ctx.shadowBlur = 0; ctx.strokeStyle = p.light; ctx.lineWidth = 1; ctx.stroke(); } } for (const spike of level.spikes) { ctx.fillStyle = '#ff4444'; ctx.shadowColor = '#ff4444'; ctx.shadowBlur = 12; const cx = spike.x + spike.width / 2; const cy = spike.y + spike.height; ctx.beginPath(); ctx.moveTo(cx, spike.y); ctx.lineTo(spike.x, cy); ctx.lineTo(spike.x + spike.width, cy); ctx.closePath(); ctx.fill(); ctx.shadowBlur = 0; ctx.fillStyle = 'rgba(255,255,255,0.2)'; ctx.beginPath(); ctx.moveTo(cx, spike.y + 3); ctx.lineTo(cx - 4, spike.y + 8); ctx.lineTo(cx + 4, spike.y + 8); ctx.closePath(); ctx.fill(); } for (const pipe of level.pipes) { ctx.fillStyle = 'rgba(0,0,0,0.3)'; ctx.beginPath(); ctx.roundRect(pipe.x + 3, pipe.y + 3, pipe.width, pipe.height, [4, 4, 0, 0]); ctx.fill(); const pipeGrad = ctx.createLinearGradient(pipe.x, pipe.y, pipe.x, pipe.y + pipe.height); pipeGrad.addColorStop(0, p.accent); pipeGrad.addColorStop(0.5, pipe.color || p.accent); pipeGrad.addColorStop(1, p.primary); ctx.fillStyle = pipeGrad; ctx.shadowColor = pipe.color || p.accent; ctx.shadowBlur = 8; ctx.beginPath(); ctx.roundRect(pipe.x, pipe.y, pipe.width, pipe.height, [4, 4, 0, 0]); ctx.fill(); ctx.shadowBlur = 0; ctx.fillStyle = p.light || '#ffffff'; ctx.globalAlpha = 0.4; ctx.beginPath(); ctx.roundRect(pipe.x - 3, pipe.y - 4, pipe.width + 6, 12, 4); ctx.fill(); ctx.globalAlpha = 1; ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.beginPath(); ctx.ellipse(pipe.x + pipe.width/2, pipe.y + 3, pipe.width/2 - 5, 8, 0, 0, Math.PI * 2); ctx.fill(); if (pipe.isEntrance) { const arrowY = pipe.y - 22; const alpha = 0.3 + Math.sin(Date.now() / 500) * 0.2; ctx.fillStyle = '#FFD700'; ctx.globalAlpha = alpha; ctx.beginPath(); ctx.arc(pipe.x + pipe.width / 2, arrowY, 16, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; ctx.fillStyle = '#FFD700'; ctx.font = '14px "Press Start 2P", monospace'; ctx.textAlign = 'center'; ctx.fillText('▼', pipe.x + pipe.width / 2, arrowY + 7); ctx.fillStyle = '#FFD700'; ctx.globalAlpha = 0.4; ctx.font = '8px "Press Start 2P", monospace'; ctx.fillText('ВХОД', pipe.x + pipe.width / 2, arrowY - 22); ctx.globalAlpha = 1; } } for (const star of level.stars) { if (star.collected) continue; const cx = star.x + star.width / 2; const cy = star.y + star.height / 2; const time = Date.now() / 1000; const bounce = Math.sin(time * 2.5 + star.x) * 2; const glow = ctx.createRadialGradient(cx, cy + bounce, 2, cx, cy + bounce, 30); glow.addColorStop(0, 'rgba(255, 215, 0, 0.5)'); glow.addColorStop(0.5, 'rgba(255, 200, 0, 0.2)'); glow.addColorStop(1, 'rgba(255, 180, 0, 0)'); ctx.fillStyle = glow; ctx.beginPath(); ctx.arc(cx, cy + bounce, 30, 0, Math.PI * 2); ctx.fill(); const outerGlow = ctx.createRadialGradient(cx, cy + bounce, 5, cx, cy + bounce, 20); outerGlow.addColorStop(0, 'rgba(255, 255, 100, 0.4)'); outerGlow.addColorStop(1, 'rgba(255, 215, 0, 0)'); ctx.fillStyle = outerGlow; ctx.beginPath(); ctx.arc(cx, cy + bounce, 20, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#FFD700'; ctx.shadowColor = '#FFD700'; ctx.shadowBlur = 20; const spikes = 5; const outerRadius = 10; const innerRadius = 4.5; ctx.beginPath(); for (let i = 0; i < spikes * 2; i++) { const radius = i % 2 === 0 ? outerRadius : innerRadius; const angle = (i * Math.PI) / spikes - Math.PI / 2 + time * 0.4; const x = cx + Math.cos(angle) * radius; const y = cy + Math.sin(angle) * radius + bounce; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.closePath(); ctx.fill(); ctx.shadowBlur = 0; ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; ctx.beginPath(); ctx.arc(cx - 2.5, cy - 2.5 + bounce, 2.5, 0, Math.PI * 2); ctx.fill(); } player.draw(ctx); } // ======================================== // УВЕДОМЛЕНИЯ // ======================================== function showLevelNotification(levelIndex) { const el = document.getElementById('levelNotification'); const palette = levelPalettes[levelIndex % levelPalettes.length]; const numEl = el.querySelector('.level-number'); if (numEl) { numEl.textContent = 'Уровень №' + (levelIndex + 1); numEl.style.borderColor = palette.secondary || '#96b8cb'; numEl.style.color = palette.light || '#e2e5e2'; numEl.style.background = palette.bg || '#3b3936'; } el.className = 'level-notification visible'; soundManager.playScreenTransitionSound(); setTimeout(() => { el.className = 'level-notification fading'; setTimeout(() => { el.className = 'level-notification hidden'; }, 800); }, 1500); } function showStarNotification(star) { const el = document.getElementById('starNotification'); el.innerHTML = '<span class="star-icon">⭐</span><span class="compliment">' + (star.compliment || 'Ты супер! ✨') + '</span>'; el.className = 'star-notification visible'; soundManager.playStarSound(); setTimeout(() => { el.className = 'star-notification hidden'; }, 4000); } function showAchievementNotification(ach) { const el = document.getElementById('achievementNotification'); el.textContent = '🏆 Достижение: ' + ach.name; el.className = 'achievement-notification visible'; soundManager.playAchievementSound(); setTimeout(() => { el.className = 'achievement-notification hidden'; }, 5000); } function showLevelComplete() { soundManager.playLevelCompleteSound(); gameState.levelsCompleted++; gameState.currentLevel++; gameState.levelComplete = false; gameState.deathCountOnLevel = 0; if (gameState.player) { gameState.player.speed = gameState.player.baseSpeed; gameState.player.jumpPower = gameState.player.baseJumpPower; } saveProgress(); setTimeout(() => { loadLevel(gameState.currentLevel); }, 400); } function showFinalStar() { const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const level = gameState.levels[gameState.currentLevel]; soundManager.playFinalStarSound(); const fs = { x: level.width / 2 - 20, y: level.height / 2 - 20, width: 40, height: 40, collected: false, isFinal: true, compliment: 'Ты - настоящий друг! ❤️' }; level.stars.push(fs); gameState.finalStarCollected = true; saveProgress(); drawLevel(ctx, level, gameState.player); setTimeout(() => { showCompletionScreen(); }, 2000); } function showDeathScreen() { const el = document.getElementById('deathScreen'); document.getElementById('deathStars').textContent = gameState.totalStars; document.getElementById('deathLevel').textContent = gameState.currentLevel + 1; document.getElementById('deathLives').textContent = gameState.lives; el.classList.add('visible'); soundManager.playDeathSound(); } function hideDeathScreen() { document.getElementById('deathScreen').classList.remove('visible'); } function showCompletionScreen() { document.getElementById('completionPlayerName').textContent = gameState.playerName || 'друг'; document.getElementById('gameScreen').classList.add('hidden'); document.getElementById('completionScreen').classList.remove('hidden'); soundManager.playMusic('victory'); } function showAchievementsScreen() { document.getElementById('mainMenu').classList.add('hidden'); document.getElementById('achievementsScreen').classList.remove('hidden'); renderAchievements(); } function hideAchievementsScreen() { document.getElementById('achievementsScreen').classList.add('hidden'); document.getElementById('mainMenu').classList.remove('hidden'); } function renderAchievements() { const container = document.getElementById('achievementsList'); container.innerHTML = ''; const unlocked = gameState.unlockedAchievements || []; for (const ach of achievements) { const isUnlocked = unlocked.includes(ach.id); const div = document.createElement('div'); div.className = 'achievement-item ' + (isUnlocked ? 'unlocked' : 'locked'); div.innerHTML = ` <span class="ach-icon">${isUnlocked ? ach.icon : '❓'}</span> <div class="ach-info"> <div class="ach-name">${isUnlocked ? ach.name : '???'}</div> <div class="ach-desc">${isUnlocked ? ach.desc : 'Не разблокировано'}</div> </div> <span class="ach-status">${isUnlocked ? '✅' : '🔒'}</span> `; container.appendChild(div); } } // ======================================== // ДОСТИЖЕНИЯ // ======================================== function checkAchievements() { const unlocked = []; if (gameState.totalStars >= 1 && !gameState.unlockedAchievements.includes('first_star')) { unlocked.push(achievements.find(a => a.id === 'first_star')); gameState.unlockedAchievements.push('first_star'); } if (gameState.totalStars >= 10 && !gameState.unlockedAchievements.includes('star_collector')) { unlocked.push(achievements.find(a => a.id === 'star_collector')); gameState.unlockedAchievements.push('star_collector'); } if (gameState.totalStars >= 25 && !gameState.unlockedAchievements.includes('star_hunter')) { unlocked.push(achievements.find(a => a.id === 'star_hunter')); gameState.unlockedAchievements.push('star_hunter'); } if (gameState.totalStars >= 50 && !gameState.unlockedAchievements.includes('star_legend')) { unlocked.push(achievements.find(a => a.id === 'star_legend')); gameState.unlockedAchievements.push('star_legend'); } if (gameState.levelsCompleted >= 5 && !gameState.unlockedAchievements.includes('level_5')) { unlocked.push(achievements.find(a => a.id === 'level_5')); gameState.unlockedAchievements.push('level_5'); } if (gameState.levelsCompleted >= 15 && !gameState.unlockedAchievements.includes('level_15')) { unlocked.push(achievements.find(a => a.id === 'level_15')); gameState.unlockedAchievements.push('level_15'); } if (gameState.levelsCompleted >= 30 && !gameState.unlockedAchievements.includes('level_30')) { unlocked.push(achievements.find(a => a.id === 'level_30')); gameState.unlockedAchievements.push('level_30'); } if (gameState.jumps >= 100 && !gameState.unlockedAchievements.includes('jump_master')) { unlocked.push(achievements.find(a => a.id === 'jump_master')); gameState.unlockedAchievements.push('jump_master'); } if (gameState.noHitLevels >= 5 && !gameState.unlockedAchievements.includes('no_hit')) { unlocked.push(achievements.find(a => a.id === 'no_hit')); gameState.unlockedAchievements.push('no_hit'); } if (gameState.finalStarCollected && !gameState.unlockedAchievements.includes('final_star')) { unlocked.push(achievements.find(a => a.id === 'final_star')); gameState.unlockedAchievements.push('final_star'); } if (gameState.levelsCompleted >= 30 && gameState.totalStars >= 50 && !gameState.unlockedAchievements.includes('perfect_game')) { unlocked.push(achievements.find(a => a.id === 'perfect_game')); gameState.unlockedAchievements.push('perfect_game'); } if (gameState.levelsCompleted >= 30 && !gameState.unlockedAchievements.includes('friend_forever')) { unlocked.push(achievements.find(a => a.id === 'friend_forever')); gameState.unlockedAchievements.push('friend_forever'); } for (const ach of unlocked) { if (ach) showAchievementNotification(ach); } document.getElementById('achievementsDisplay').textContent = '🏆 ' + gameState.unlockedAchievements.length; saveProgress(); } // ======================================== // СОХРАНЕНИЕ // ======================================== function saveProgress() { if (!gameState.playerName) return; const data = { stars: gameState.stars, totalStars: gameState.totalStars, unlockedAchievements: gameState.unlockedAchievements, levelsCompleted: gameState.levelsCompleted, jumps: gameState.jumps, finalStarCollected: gameState.finalStarCollected, totalStarsCollected: gameState.totalStarsCollected, usedCompliments: gameState.usedCompliments, playTime: gameState.playTime, lives: gameState.lives, deaths: gameState.deaths, noHitLevels: gameState.noHitLevels }; saveManager.savePlayerData(gameState.playerName, data); } function loadProgress(pn) { const data = saveManager.loadPlayerData(pn); if (data) { gameState.stars = data.stars || 0; gameState.totalStars = data.totalStars || 0; gameState.unlockedAchievements = data.unlockedAchievements || []; gameState.levelsCompleted = data.levelsCompleted || 0; gameState.jumps = data.jumps || 0; gameState.finalStarCollected = data.finalStarCollected || false; gameState.totalStarsCollected = data.totalStarsCollected || 0; gameState.usedCompliments = data.usedCompliments || []; gameState.playTime = data.playTime || 0; gameState.lives = data.lives || 3; gameState.deaths = data.deaths || 0; gameState.noHitLevels = data.noHitLevels || 0; return true; } return false; } // ======================================== // ОБНОВЛЕНИЕ ЗДОРОВЬЯ // ======================================== function updateHealthDisplay() { const bar = document.getElementById('healthBar'); const text = document.getElementById('healthText'); const lives = document.getElementById('livesDisplay'); if (bar) { const pct = (gameState.health / gameState.maxHealth) * 100; bar.style.width = Math.max(0, pct) + '%'; if (pct < 30) bar.classList.add('low'); else bar.classList.remove('low'); } if (text) text.textContent = Math.max(0, gameState.health) + '/' + gameState.maxHealth; if (lives) lives.textContent = '❤️ x' + Math.max(0, gameState.lives); } // ======================================== // ЗАГРУЗКА УРОВНЯ // ======================================== function loadLevel(index) { if (index >= 30) { showCompletionScreen(); return; } if (gameState.lives <= 0) { gameState.lives = 3; gameState.health = gameState.maxHealth; gameState.currentLevel = 0; gameState.levelsCompleted = 0; gameState.stars = 0; saveProgress(); loadLevel(0); return; } gameState.currentLevelStars = 0; gameState.levelComplete = false; const level = generateLevel(index); gameState.levels[index] = level; gameState.currentLevel = index; const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); document.getElementById('levelDisplay').textContent = 'Уровень: ' + (index + 1); document.getElementById('starsDisplay').textContent = '⭐ ' + gameState.stars; document.getElementById('playerNameDisplay').textContent = gameState.playerName || 'Игрок'; updateHealthDisplay(); showLevelNotification(index); const player = gameState.player; player.x = level.spawnX; player.y = level.spawnY; player.smoothX = player.x; player.smoothY = player.y; player.vx = 0; player.vy = 0; player.speed = player.baseSpeed; player.jumpPower = player.baseJumpPower; drawLevel(ctx, level, player); } // ======================================== // ИГРОВОЙ ЦИКЛ // ======================================== function gameLoop() { const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const level = gameState.levels[gameState.currentLevel]; const player = gameState.player; if (level && !gameState.levelComplete) { player.update(level); document.getElementById('starsDisplay').textContent = '⭐ ' + gameState.stars; drawLevel(ctx, level, player); gameState.playTime += 1 / 60; if (gameState.playTime >= 1800 && !gameState.unlockedAchievements.includes('dedicated')) { gameState.unlockedAchievements.push('dedicated'); const ach = achievements.find(a => a.id === 'dedicated'); if (ach) showAchievementNotification(ach); saveProgress(); } } requestAnimationFrame(gameLoop); } // ======================================== // ИНИЦИАЛИЗАЦИЯ (УПРАВЛЕНИЕ WASD) // ======================================== function init() { const canvas = document.getElementById('gameCanvas'); canvas.width = 1000; canvas.height = 600; gameState.player = new Player(); gameState.levels = []; // УПРАВЛЕНИЕ WASD И СТРЕЛКИ const keys = {}; document.addEventListener('keydown', (e) => { keys[e.key] = true; const p = gameState.player; // Прыжок: пробел, стрелка вверх или W if (e.key === ' ' || e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') { e.preventDefault(); p.jump(); } // Влево: стрелка влево или A if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') p.moveLeft(); // Вправо: стрелка вправо или D if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') p.moveRight(); }); document.addEventListener('keyup', (e) => { keys[e.key] = false; const p = gameState.player; // Остановка при отпускании клавиш движения if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') { if (!keys['ArrowRight'] && !keys['d'] && !keys['D']) p.stop(); } if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') { if (!keys['ArrowLeft'] && !keys['a'] && !keys['A']) p.stop(); } }); const nameInput = document.getElementById('playerNameInput'); const authBtn = document.getElementById('authButton'); const authErr = document.getElementById('authError'); const startBtn = document.getElementById('startButton'); function handleAuth() { const name = nameInput.value.trim(); if (!name) { authErr.textContent = 'Введите имя!'; authErr.classList.remove('hidden'); return; } if (name.length < 2) { authErr.textContent = 'Имя должно быть не короче 2 символов!'; authErr.classList.remove('hidden'); return; } const exists = saveManager.playerExists(name); if (exists) { loadProgress(name); gameState.playerName = name; startBtn.disabled = false; authErr.classList.add('hidden'); nameInput.style.borderColor = '#96b8cb'; document.getElementById('playerNameDisplay').textContent = name; document.getElementById('completionPlayerName').textContent = name; soundManager.playButtonSound(); renderAchievements(); updateHealthDisplay(); } else { gameState.playerName = name; gameState.stars = 0; gameState.totalStars = 0; gameState.unlockedAchievements = []; gameState.levelsCompleted = 0; gameState.jumps = 0; gameState.finalStarCollected = false; gameState.totalStarsCollected = 0; gameState.usedCompliments = []; gameState.playTime = 0; gameState.lives = 3; gameState.deaths = 0; gameState.noHitLevels = 0; startBtn.disabled = false; authErr.classList.add('hidden'); nameInput.style.borderColor = '#96b8cb'; document.getElementById('playerNameDisplay').textContent = name; document.getElementById('completionPlayerName').textContent = name; soundManager.playButtonSound(); saveProgress(); updateHealthDisplay(); } } authBtn.addEventListener('click', handleAuth); nameInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleAuth(); }); nameInput.addEventListener('input', () => { if (nameInput.value.trim()) authErr.classList.add('hidden'); }); startBtn.addEventListener('click', () => { soundManager.playButtonSound(); setTimeout(() => { document.getElementById('mainMenu').classList.add('hidden'); document.getElementById('rulesScreen').classList.remove('hidden'); soundManager.playScreenTransitionSound(); soundManager.playMusic('game'); }, 200); }); document.getElementById('rulesStartBtn').addEventListener('click', () => { soundManager.playButtonSound(); setTimeout(() => { document.getElementById('rulesScreen').classList.add('hidden'); document.getElementById('gameScreen').classList.remove('hidden'); soundManager.playScreenTransitionSound(); loadLevel(gameState.levelsCompleted); gameLoop(); }, 200); }); document.getElementById('achievementsMenuBtn').addEventListener('click', () => { if (!gameState.playerName) { authErr.textContent = 'Сначала войдите в игру!'; authErr.classList.remove('hidden'); return; } soundManager.playButtonSound(); showAchievementsScreen(); }); document.getElementById('achievementsBackBtn').addEventListener('click', () => { soundManager.playButtonSound(); hideAchievementsScreen(); }); document.getElementById('resetProgressBtn').addEventListener('click', () => { if (!gameState.playerName) { authErr.textContent = 'Сначала войдите в игру!'; authErr.classList.remove('hidden'); return; } if (confirm('Вы уверены, что хотите сбросить весь прогресс для "' + gameState.playerName + '"?')) { soundManager.playButtonSound(); saveManager.resetPlayerData(gameState.playerName); gameState.stars = 0; gameState.totalStars = 0; gameState.unlockedAchievements = []; gameState.levelsCompleted = 0; gameState.jumps = 0; gameState.finalStarCollected = false; gameState.totalStarsCollected = 0; gameState.usedCompliments = []; gameState.playTime = 0; gameState.lives = 3; gameState.deaths = 0; gameState.noHitLevels = 0; startBtn.disabled = true; nameInput.value = ''; nameInput.style.borderColor = ''; document.getElementById('playerNameDisplay').textContent = 'Игрок'; updateHealthDisplay(); authErr.textContent = 'Прогресс сброшен!'; authErr.classList.remove('hidden'); setTimeout(() => authErr.classList.add('hidden'), 3000); } }); document.getElementById('restartButton').addEventListener('click', () => { soundManager.playButtonSound(); setTimeout(() => { gameState.currentLevel = 0; gameState.stars = 0; gameState.totalStars = 0; gameState.unlockedAchievements = []; gameState.levelsCompleted = 0; gameState.jumps = 0; gameState.finalStarCollected = false; gameState.totalStarsCollected = 0; gameState.usedCompliments = []; gameState.playTime = 0; gameState.lives = 3; gameState.deaths = 0; gameState.noHitLevels = 0; document.getElementById('completionScreen').classList.add('hidden'); document.getElementById('gameScreen').classList.remove('hidden'); loadLevel(0); saveProgress(); updateHealthDisplay(); }, 200); }); document.getElementById('retryButton').addEventListener('click', () => { soundManager.playButtonSound(); const level = gameState.levels[gameState.currentLevel]; if (level && gameState.player) { gameState.player.respawn(level); } }); document.getElementById('menuFromDeathBtn').addEventListener('click', () => { soundManager.playButtonSound(); hideDeathScreen(); document.getElementById('gameScreen').classList.add('hidden'); document.getElementById('mainMenu').classList.remove('hidden'); gameState.isDead = false; gameState.isGameActive = false; soundManager.playMusic('menu'); }); const preview = document.getElementById('characterPreview'); preview.addEventListener('mouseenter', () => { soundManager.playMenuHoverSound(); const ch = document.getElementById('menuCharacter'); ch.style.animation = 'none'; setTimeout(() => { ch.style.animation = 'jumpPreview 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)'; }, 10); }); const sndToggle = document.getElementById('soundToggle'); const musToggle = document.getElementById('musicToggle'); sndToggle?.addEventListener('click', () => { const en = soundManager.toggleSound(); sndToggle.textContent = en ? '🔊' : '🔇'; sndToggle.classList.toggle('muted', !en); }); musToggle?.addEventListener('click', () => { const en = soundManager.toggleMusic(); musToggle.textContent = en ? '🎵' : '🎵'; musToggle.classList.toggle('muted', !en); }); function resizeCanvas() { const container = document.getElementById('gameContainer'); const rect = container.getBoundingClientRect(); canvas.style.width = rect.width + 'px'; canvas.style.height = rect.height + 'px'; } window.addEventListener('resize', resizeCanvas); setTimeout(resizeCanvas, 100); if (!CanvasRenderingContext2D.prototype.roundRect) { CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, radii) { const r = typeof radii === 'number' ? radii : (radii || 0); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); return this; }; } soundManager.playMusic('menu'); updateHealthDisplay(); } window.onload = init; </script> </body> </html>