NEBULA CALC + СЕКРЕТ | SubVS
by SparkHawk26687 lines29.5 KB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>NEBULA CALC + СЕКРЕТ | SubVS</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
body {
min-height: 100vh;
background: radial-gradient(circle at 30% 10%, #0c0a1a, #020105);
display: flex;
justify-content: center;
align-items: center;
font-family: 'Segoe UI', 'Poppins', 'Courier New', monospace;
padding: 20px;
transition: all 0.3s;
}
/* секретный режим (игра вместо калькулятора) */
body.game-mode .calculator-wrapper {
display: none;
}
body.game-mode .game-container {
display: block;
}
.game-container {
display: none;
width: 100%;
max-width: 1000px;
margin: 0 auto;
background: #0c0820;
border-radius: 56px;
padding: 20px;
box-shadow: 0 25px 45px rgba(0,0,0,0.8), inset 0 1px 1px rgba(156,80,255,0.2);
border: 1px solid #7f4ef0;
}
/* Главный калькулятор в черно-фиолетовом стиле */
.calculator-wrapper {
width: 100%;
max-width: 450px;
margin: 0 auto;
}
.calculator {
background: linear-gradient(145deg, #1e1a2f, #0f0b1a);
border-radius: 56px;
padding: 24px 20px 30px 20px;
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.6), inset 0 1px 1px rgba(255, 255, 255, 0.08);
border: 1px solid rgba(156, 80, 255, 0.35);
transition: 0.2s;
}
.screen {
background: #07050e;
padding: 25px 20px;
border-radius: 48px;
margin-bottom: 30px;
box-shadow: inset 0 5px 12px rgba(0, 0, 0, 0.6), 0 2px 3px rgba(156, 80, 255, 0.2);
border: 1px solid #3c2a6e;
}
.previous-operand {
min-height: 32px;
font-size: 1.2rem;
color: #b58eff;
text-align: right;
word-wrap: break-word;
letter-spacing: 1px;
font-family: monospace;
opacity: 0.85;
}
.current-operand {
font-size: 3rem;
font-weight: 700;
color: #f0e6ff;
text-align: right;
word-wrap: break-word;
word-break: break-all;
text-shadow: 0 0 6px #9b4dff;
font-family: 'Segoe UI', monospace;
}
.buttons {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
}
button {
background: #15102a;
border: none;
padding: 18px 0;
font-size: 1.6rem;
font-weight: bold;
border-radius: 48px;
color: #e2d6ff;
cursor: pointer;
transition: 0.08s linear;
box-shadow: 0 6px 0 #0a0720;
font-family: monospace;
border: 1px solid rgba(128, 90, 240, 0.4);
}
button:active {
transform: translateY(3px);
box-shadow: 0 2px 0 #0a0720;
}
.operator {
background: #2a1a55;
color: #dbbaff;
text-shadow: 0 0 3px #b77eff;
font-size: 1.9rem;
}
.equals {
background: #9b4dff;
color: #1a0f2e;
text-shadow: 0 1px 1px white;
box-shadow: 0 6px 0 #5c2ab3;
}
.clear, .delete {
background: #3d1f5e;
}
.delete {
background: #5a2a82;
}
.style-badge {
text-align: center;
margin-top: 20px;
font-size: 0.7rem;
color: #8a6fcf;
font-weight: bold;
}
/* Игровой интерфейс внутри секретного меню */
.game-header {
display: flex;
justify-content: space-between;
background: #180e2e;
padding: 12px 20px;
border-radius: 60px;
margin-bottom: 18px;
color: #f2dd9f;
font-weight: bold;
flex-wrap: wrap;
gap: 10px;
}
.game-canvas-box {
background: #00000066;
border-radius: 32px;
padding: 8px;
border: 2px solid #9b4dff;
}
canvas {
display: block;
margin: 0 auto;
border-radius: 24px;
width: 100%;
height: auto;
background: #0d0a1a;
cursor: pointer;
}
.secret-controls {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 20px;
}
.sec-btn {
background: #2d1f53;
padding: 10px 20px;
font-size: 1.4rem;
box-shadow: 0 4px 0 #0e0820;
}
.back-btn {
background: #671b5e;
color: #ffe5b4;
}
.cheat-msg {
text-align: center;
margin-top: 12px;
color: #ffdc97;
background: #00000088;
padding: 6px;
border-radius: 60px;
font-size: 0.8rem;
}
@media (max-width: 560px) {
.buttons { gap: 8px; }
button { padding: 12px 0; font-size: 1.3rem; }
.game-header { font-size: 0.9rem; }
}
</style>
</head>
<body>
<!-- Обычный калькулятор (черно-фиолетовый) -->
<div class="calculator-wrapper">
<div class="calculator">
<div class="screen">
<div class="previous-operand" id="previousOperand"></div>
<div class="current-operand" id="currentOperand">0</div>
</div>
<div class="buttons">
<button class="clear" data-action="clear">C</button>
<button class="delete" data-action="delete">⌫</button>
<button class="operator" data-operator="%">%</button>
<button class="operator" data-operator="/">÷</button>
<button data-number="7">7</button>
<button data-number="8">8</button>
<button data-number="9">9</button>
<button class="operator" data-operator="*">×</button>
<button data-number="4">4</button>
<button data-number="5">5</button>
<button data-number="6">6</button>
<button class="operator" data-operator="-">−</button>
<button data-number="1">1</button>
<button data-number="2">2</button>
<button data-number="3">3</button>
<button class="operator" data-operator="+">+</button>
<button data-number="0">0</button>
<button data-number=".">.</button>
<button class="equals" data-equals id="equalsBtn">=</button>
</div>
<div class="style-badge">✦ VIOLET PHANTOM | введи код: 25 84 25 ✦</div>
</div>
</div>
<!-- СЕКРЕТНЫЙ КОНТЕЙНЕР: игра SubVS (наш Subway Surfers стиль) -->
<div class="game-container" id="secretGameContainer">
<div class="game-header">
<span>🌀 SUBVS: SURF & HAMSTERS 🐹</span>
<span>🏆 ОЧКИ: <span id="gameScore">0</span></span>
<span>🐹 ХОМЯКИ: <span id="gameCoins">0</span></span>
<span>⚡ РЕКОРД: <span id="gameBest">0</span></span>
</div>
<div class="game-canvas-box">
<canvas id="subCanvas" width="800" height="500" style="width:100%; height:auto; max-width:800px; aspect-ratio:800/500"></canvas>
</div>
<div class="secret-controls">
<button class="sec-btn" id="gameLeftBtn">◀ ЛЕВО</button>
<button class="sec-btn" id="gameRightBtn">ПРАВО ▶</button>
<button class="sec-btn back-btn" id="exitGameBtn">🔮 ВЫЙТИ ИЗ СЕКРЕТА</button>
</div>
<div class="cheat-msg">✨ ЧИТ-МЕНЮ АКТИВИРОВАНО! Беги, собирай крутящихся хомяков ✨</div>
</div>
<script>
// ---------- КАЛЬКУЛЯТОР С ФУНКЦИЕЙ ПАРОЛЯ ----------
(function() {
// Элементы калькулятора
const previousOperandEl = document.getElementById('previousOperand');
const currentOperandEl = document.getElementById('currentOperand');
let currentOperand = "0";
let previousOperand = "";
let operation = null;
let shouldResetScreen = false;
// Секретная комбинация: "25", "84", "25"
let secretBuffer = [];
const SECRET_CODE = ["25", "84", "25"];
function formatNumber(number) {
const str = number.toString();
const parts = str.split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, " ");
return parts.join('.');
}
function updateDisplay() {
currentOperandEl.innerText = formatNumber(currentOperand);
if (operation != null && previousOperand !== "") {
previousOperandEl.innerText = `${formatNumber(previousOperand)} ${getOperatorSymbol(operation)}`;
} else {
previousOperandEl.innerText = previousOperand ? formatNumber(previousOperand) : "";
}
}
function getOperatorSymbol(op) {
switch(op) {
case '+': return '+'; case '-': return '−'; case '*': return '×';
case '/': return '÷'; case '%': return '%'; default: return '';
}
}
function appendNumber(number) {
if (shouldResetScreen) {
currentOperand = "";
shouldResetScreen = false;
}
if (number === '.' && currentOperand.includes('.')) return;
if (currentOperand === "0" && number !== '.') currentOperand = number;
else currentOperand += number;
updateDisplay();
// ПРОВЕРКА СЕКРЕТНОЙ КОМБИНАЦИИ НА ЛЕТУ (цифры, но мы проверяем именно ввод целых чисел)
// Проверяем последнее введенное "число" — пользователь вводит последовательно цифры.
// Чтобы активировать игру при вводе 25 84 25 (подряд как числа), будем отслеживать числа через точку?
// Лучше: мониторить текущий ввод после нажатия оператора/равно? но проще всего сделать: если пользователь нажал "=" и результат равен секретному, но у нас проще ловить последовательность чисел прямо при вводе цифр.
// Имитируем ввод через пробел: пароль вводится набором цифр с последующим нажатием "=" или просто подряд?
// Сделаем детектор: каждый раз когда текущий операнд совпадает с очередным ожидаемым числом (по целому числу) и после него нажат оператор или равно — но проще сделать так:
// Будем детектить ПОЛНОЕ совпадение экрана при нажатии "=" если строка экрана равна "258425"? Но юзер хочет именно "двадцать пять восемьдесят четыре двадцать пять".
// Лучший способ: слушать кнопку равно и сравнивать текущий операнд с числом 258425? Нет. Нам нужно три отдельных числа.
// Реализуем отложенную проверку по мере набора: когда пользователь ввел число и нажал какую-либо операцию (+, -, ×...), мы кладём число в буфер.
// но проще ловить прямо по вводу: при каждом изменении currentOperand (целое число) проверяем, является ли оно частью кода.
}
// Функция проверки секретного кода (вызываем когда число "завершено" — либо после оператора, либо равно)
function checkSecretSequence(value) {
if (!value) return false;
// убираем пробелы форматирования, оставляем только цифры
let cleanVal = value.toString().replace(/\s/g, '');
if (cleanVal === SECRET_CODE[secretBuffer.length]) {
secretBuffer.push(cleanVal);
if (secretBuffer.length === SECRET_CODE.length) {
// секрет активирован!
activateSecretGame();
secretBuffer = [];
return true;
}
} else {
// сброс буфера при несовпадении, но с проверкой заново: может начаться новая последовательность
let newBuf = [];
let total = secretBuffer.join('') + cleanVal;
// ищем суффикс, который может быть началом кода
for (let i = 0; i < SECRET_CODE.length; i++) {
if (cleanVal === SECRET_CODE[i]) {
newBuf = [cleanVal];
break;
}
}
if (newBuf.length === 0) secretBuffer = [];
else secretBuffer = newBuf;
}
return false;
}
function activateSecretGame() {
// Показать игровой контейнер, скрыть калькулятор
document.body.classList.add('game-mode');
// Запустить игру, если ещё не запущена
if (typeof initGame === 'function') {
initGame();
}
}
// Перехватываем действия для отслеживания "чисел" (операндов)
function commitCurrentNumber() {
if (currentOperand && currentOperand !== "") {
let numRaw = currentOperand.replace(/\s/g, '');
if (!isNaN(parseFloat(numRaw))) {
checkSecretSequence(numRaw);
}
}
}
// Доработаем вычисления и операции чтобы проверять код при фиксации числа
const origChooseOperation = null;
function customChooseOperation(op) {
if (currentOperand === "" && previousOperand === "") return;
if (previousOperand !== "" && !shouldResetScreen) customCompute();
commitCurrentNumber(); // проверяем текущее число перед операцией
if (currentOperand === "") return;
operation = op;
previousOperand = currentOperand;
currentOperand = "";
shouldResetScreen = false;
updateDisplay();
}
function customCompute() {
if (operation === null || previousOperand === "" || currentOperand === "") return;
let result;
const prev = parseFloat(previousOperand.replace(/\s/g, ''));
const cur = parseFloat(currentOperand.replace(/\s/g, ''));
if (isNaN(prev) || isNaN(cur)) return;
switch(operation) {
case '+': result = prev + cur; break;
case '-': result = prev - cur; break;
case '*': result = prev * cur; break;
case '/': result = cur === 0 ? "Ошибка" : prev / cur; break;
case '%': result = prev % cur; break;
default: return;
}
if (result === "Ошибка") {
currentOperand = "∞";
previousOperand = "";
operation = null;
shouldResetScreen = true;
updateDisplay();
return;
}
result = parseFloat(result.toFixed(8));
currentOperand = result.toString();
operation = null;
previousOperand = "";
shouldResetScreen = true;
updateDisplay();
// после вычисления тоже проверяем результат на совпадение с кодом?
// но по заданию достаточно при вводе чисел. Однако для надёжности проверим и результат
if (currentOperand && currentOperand !== "∞") {
let resClean = currentOperand.replace(/\s/g, '');
if (!isNaN(parseFloat(resClean))) checkSecretSequence(resClean);
}
}
function customClear() {
currentOperand = "0";
previousOperand = "";
operation = null;
shouldResetScreen = false;
updateDisplay();
}
function customDelete() {
if (shouldResetScreen) return;
if (currentOperand.length === 1 || (currentOperand === "0")) {
currentOperand = "0";
} else {
currentOperand = currentOperand.slice(0, -1);
}
updateDisplay();
}
function appendNumberHandler(num) {
if (shouldResetScreen) {
currentOperand = "";
shouldResetScreen = false;
}
if (num === '.' && currentOperand.includes('.')) return;
if (currentOperand === "0" && num !== '.') currentOperand = num;
else currentOperand += num;
updateDisplay();
}
// Переопределяем кнопки
document.querySelectorAll('[data-number]').forEach(btn => {
btn.addEventListener('click', () => {
appendNumberHandler(btn.getAttribute('data-number'));
});
});
document.querySelectorAll('[data-operator]').forEach(btn => {
btn.addEventListener('click', () => {
customChooseOperation(btn.getAttribute('data-operator'));
});
});
document.querySelector('[data-action="clear"]').addEventListener('click', customClear);
document.querySelector('[data-action="delete"]').addEventListener('click', customDelete);
document.getElementById('equalsBtn').addEventListener('click', () => {
commitCurrentNumber();
customCompute();
});
// Дополнительно отслеживаем каждый клик для скрытой инициализации буфера
updateDisplay();
window.activateSecretGame = activateSecretGame;
})();
// ---------- ИГРА SUBVS (стиль subway surfers, но с 3D-эффектом? максимально похоже на 2.5D с хомяками) ----------
let gameRunning = true;
let score = 0;
let coinsCollected = 0;
let bestScore = localStorage.getItem('subvsBest') ? parseInt(localStorage.getItem('subvsBest')) : 0;
let playerLane = 1; // 0,1,2
let trains = [];
let hamsters = [];
let frame = 0;
let currentSpeed = 4.5;
let animationId = null;
const LANES = 3;
const LANE_WIDTH = 110;
const BASE_X = 180;
const PLAYER_W = 38;
const PLAYER_H = 48;
const TRAIN_W = 50;
const TRAIN_H = 60;
const HAMSTER_SIZE = 34;
let canvas = null, ctx = null;
let gameActive = true;
function updateUI() {
document.getElementById('gameScore').innerText = Math.floor(score);
document.getElementById('gameCoins').innerText = coinsCollected;
document.getElementById('gameBest').innerText = bestScore;
}
function spawnTrain() {
let lane = Math.floor(Math.random() * LANES);
trains.push({ lane: lane, y: -TRAIN_H - Math.random() * 40 });
}
function spawnHamster() {
let lane = Math.floor(Math.random() * LANES);
hamsters.push({ lane: lane, y: -HAMSTER_SIZE - Math.random() * 70, rot: Math.random() * Math.PI * 2 });
}
function updateGame() {
if (!gameActive) return;
currentSpeed = 4.2 + Math.floor(score / 450) * 0.6;
if (currentSpeed > 11.5) currentSpeed = 11.5;
for (let t of trains) t.y += currentSpeed;
trains = trains.filter(t => t.y < canvas.height + 120);
for (let h of hamsters) {
h.y += currentSpeed;
h.rot = (h.rot + 0.12) % (Math.PI * 2);
}
hamsters = hamsters.filter(h => h.y < canvas.height + 100);
let spawnRateTrains = Math.max(20, 48 - Math.floor(score / 140));
let spawnRateHam = Math.max(14, 32 - Math.floor(score / 110));
if (frame % spawnRateTrains === 0) spawnTrain();
if (frame % spawnRateHam === 0) spawnHamster();
const playerX = BASE_X + playerLane * LANE_WIDTH + (LANE_WIDTH/2) - PLAYER_W/2;
const playerRect = { x: playerX, y: canvas.height - PLAYER_H - 20, w: PLAYER_W, h: PLAYER_H };
// столкновение с поездами
for (let i=0; i<trains.length; i++) {
const t = trains[i];
const trainX = BASE_X + t.lane * LANE_WIDTH + (LANE_WIDTH/2) - TRAIN_W/2;
if (playerRect.x < trainX + TRAIN_W && playerRect.x + PLAYER_W > trainX &&
playerRect.y < t.y + TRAIN_H && playerRect.y + PLAYER_H > t.y) {
gameActive = false;
if (Math.floor(score) > bestScore) {
bestScore = Math.floor(score);
localStorage.setItem('subvsBest', bestScore);
}
updateUI();
return;
}
}
// сбор хомяков
for (let i=0; i<hamsters.length; i++) {
const h = hamsters[i];
const hamX = BASE_X + h.lane * LANE_WIDTH + (LANE_WIDTH/2) - HAMSTER_SIZE/2;
if (playerRect.x < hamX + HAMSTER_SIZE && playerRect.x + PLAYER_W > hamX &&
playerRect.y < h.y + HAMSTER_SIZE && playerRect.y + PLAYER_H > h.y) {
coinsCollected++;
hamsters.splice(i,1);
i--;
updateUI();
}
}
score += 0.27;
updateUI();
frame++;
}
function draw() {
if (!ctx) return;
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = "#16122b";
ctx.fillRect(0,0,canvas.width,canvas.height);
// рельсы
for(let i=0;i<=LANES;i++){
let railX = BASE_X + i*LANE_WIDTH - 6;
ctx.beginPath();
ctx.strokeStyle = "#b886ff";
ctx.lineWidth = 3;
for(let s=0;s<canvas.height+30;s+=35){
ctx.beginPath();
ctx.moveTo(railX,s);
ctx.lineTo(railX+10,s+18);
ctx.stroke();
}
}
// поезда 3D-подобные
for(let t of trains){
let x = BASE_X + t.lane*LANE_WIDTH + (LANE_WIDTH/2) - TRAIN_W/2;
ctx.fillStyle = "#482d6b";
ctx.shadowBlur = 6;
ctx.fillRect(x,t.y, TRAIN_W, TRAIN_H);
ctx.fillStyle = "#aa86db";
ctx.fillRect(x+8,t.y+12, TRAIN_W-16,12);
ctx.fillStyle = "#ffcf70";
ctx.beginPath();
ctx.arc(x+TRAIN_W/2, t.y+14, 8,0,Math.PI*2);
ctx.fill();
}
// хомяки крутящиеся
for(let h of hamsters){
let x = BASE_X + h.lane*LANE_WIDTH + (LANE_WIDTH/2) - HAMSTER_SIZE/2;
ctx.save();
ctx.translate(x+HAMSTER_SIZE/2, h.y+HAMSTER_SIZE/2);
ctx.rotate(h.rot);
ctx.fillStyle = "#bc8a5a";
ctx.beginPath();
ctx.ellipse(0,0, HAMSTER_SIZE/2.2, HAMSTER_SIZE/2.2,0,0,Math.PI*2);
ctx.fill();
ctx.fillStyle = "#5d3a1a";
ctx.beginPath();
ctx.ellipse(-6,-5,4,5,0,0,Math.PI*2);
ctx.ellipse(6,-5,4,5,0,0,Math.PI*2);
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(-5,-7,1.8,0,Math.PI*2);
ctx.arc(5,-7,1.8,0,Math.PI*2);
ctx.fill();
ctx.fillStyle = "#ff9966";
ctx.font = `${Math.floor(HAMSTER_SIZE/1.6)}px monospace`;
ctx.fillText("🐹", -8,-6);
ctx.restore();
}
// игрок
let plX = BASE_X + playerLane*LANE_WIDTH + (LANE_WIDTH/2) - PLAYER_W/2;
let plY = canvas.height - PLAYER_H - 20;
ctx.fillStyle = "#a55ef0";
ctx.fillRect(plX,plY, PLAYER_W, PLAYER_H);
ctx.fillStyle = "#ffb86b";
ctx.beginPath();
ctx.ellipse(plX+PLAYER_W/2, plY-5, 14,10,0,0,Math.PI*2);
ctx.fill();
ctx.fillStyle = "white";
ctx.fillRect(plX+10,plY+12,6,8);
ctx.fillRect(plX+PLAYER_W-16,plY+12,6,8);
ctx.fillStyle = "#d43f1f";
ctx.fillRect(plX-4,plY+PLAYER_H-6, PLAYER_W+8, 6);
if(!gameActive){
ctx.font = "bold 26monospace";
ctx.fillStyle = "#ffdbb5";
ctx.fillText("💀 GAME OVER", canvas.width/2-110, canvas.height/2);
}
}
function gameLoop() {
if(!canvas || !ctx) return;
if(gameActive) updateGame();
draw();
animationId = requestAnimationFrame(gameLoop);
}
function resetGameSession() {
gameActive = true;
score = 0;
coinsCollected = 0;
trains = [];
hamsters = [];
playerLane = 1;
frame = 0;
currentSpeed = 4.5;
updateUI();
}
function initGame() {
canvas = document.getElementById('subCanvas');
if(!canvas) return;
ctx = canvas.getContext('2d');
resetGameSession();
if(animationId) cancelAnimationFrame(animationId);
gameLoop();
// управление
document.getElementById('gameLeftBtn').onclick = () => { if(gameActive && playerLane>0) playerLane--; };
document.getElementById('gameRightBtn').onclick = () => { if(gameActive && playerLane<LANES-1) playerLane++; };
document.getElementById('exitGameBtn').onclick = () => {
document.body.classList.remove('game-mode');
if(animationId) cancelAnimationFrame(animationId);
gameActive = false;
};
window.addEventListener('keydown', (e) => {
if(document.body.classList.contains('game-mode')){
if(e.key === 'ArrowLeft') { if(gameActive && playerLane>0) playerLane--; e.preventDefault();}
if(e.key === 'ArrowRight') { if(gameActive && playerLane<LANES-1) playerLane++; e.preventDefault();}
}
});
}
window.initGame = initGame;
// показ секретной игры и инициализация
const origActivate = window.activateSecretGame;
window.activateSecretGame = function() {
if(origActivate) origActivate();
setTimeout(() => { if(document.body.classList.contains('game-mode')) initGame(); }, 50);
};
</script>
</body>
</html>Game Source: NEBULA CALC + СЕКРЕТ | SubVS
Creator: SparkHawk26
Libraries: none
Complexity: complex (687 lines, 29.5 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: nebula-calc-subvs-sparkhawk26" to link back to the original. Then publish at arcadelab.ai/publish.