🎮ArcadeLab

Симулятор кондиционера — пульт управления

by PrismBolt10
660 lines25.1 KB
▶ Play
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Симулятор кондиционера — пульт управления</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
            -webkit-tap-highlight-color: transparent;
        }

        body {
            background: #3a3f4b;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Segoe UI', 'Roboto', system-ui, sans-serif;
            padding: 16px;
        }

        /* основной контейнер */
        .simulator {
            max-width: 500px;
            width: 100%;
            background: #2c2f36;
            border-radius: 48px;
            box-shadow: 0 25px 40px rgba(0,0,0,0.5);
            padding: 20px 18px 30px;
            transition: all 0.2s;
        }

        /* заголовок */
        .title {
            text-align: center;
            color: #ddd;
            font-weight: 600;
            letter-spacing: 2px;
            margin-bottom: 20px;
            font-size: 1.3rem;
            text-shadow: 0 1px 0 #000;
        }

        /* блоки кондиционера (два блока) */
        .units {
            display: flex;
            gap: 16px;
            flex-wrap: wrap;
            margin-bottom: 28px;
        }

        .unit-card {
            flex: 1;
            background: #21242b;
            border-radius: 32px;
            padding: 16px 12px;
            box-shadow: inset 0 1px 2px rgba(255,255,255,0.05), 0 8px 16px rgba(0,0,0,0.3);
            backdrop-filter: blur(2px);
            transition: 0.1s;
        }

        .unit-title {
            font-size: 0.85rem;
            text-transform: uppercase;
            color: #9aa3bb;
            text-align: center;
            letter-spacing: 1px;
            margin-bottom: 12px;
        }

        /* внешний блок */
        .outdoor-unit {
            background: #2e333d;
            border-radius: 24px;
            padding: 10px;
            position: relative;
        }
        .fan-area {
            background: #1a1d24;
            border-radius: 100%;
            width: 90px;
            height: 90px;
            margin: 0 auto 12px;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: inset 0 0 0 2px #5f6a7a, 0 5px 12px black;
        }
        .fan-blades {
            width: 70px;
            height: 70px;
            background: #3c4455;
            border-radius: 50%;
            position: relative;
            transition: transform 0.05s linear;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .fan-blades::before, .fan-blades::after {
            content: '';
            position: absolute;
            background: #7f8c9a;
            border-radius: 12px;
        }
        .fan-blades::before {
            width: 8px;
            height: 60px;
            background: #b0bedd;
            top: 5px;
            left: 31px;
        }
        .fan-blades::after {
            width: 60px;
            height: 8px;
            background: #b0bedd;
            top: 31px;
            left: 5px;
        }
        .fan-center {
            width: 18px;
            height: 18px;
            background: #ffd966;
            border-radius: 50%;
            z-index: 2;
            position: relative;
            box-shadow: 0 0 4px gold;
        }
        .temp-outdoor {
            text-align: center;
            font-size: 1.2rem;
            font-weight: bold;
            color: #8fcbff;
            background: #10131c;
            display: inline-block;
            width: 100%;
            padding: 5px 0;
            border-radius: 30px;
            margin-top: 8px;
        }

        /* внутренний блок */
        .indoor-unit {
            background: #2b2e38;
            border-radius: 24px;
            padding: 12px;
        }
        .display-temp {
            background: #10131c;
            border-radius: 60px;
            padding: 12px;
            text-align: center;
            margin-bottom: 14px;
        }
        .current-temp {
            font-size: 2.6rem;
            font-weight: 800;
            color: #ffe484;
            letter-spacing: 4px;
            line-height: 1;
        }
        .current-mode {
            font-size: 0.8rem;
            color: #aab3cf;
            margin-top: 5px;
        }
        .indicator-lights {
            display: flex;
            justify-content: space-between;
            gap: 8px;
            font-size: 0.7rem;
            color: #ccc;
            background: #1f222b;
            padding: 8px 12px;
            border-radius: 40px;
        }
        .light {
            display: flex;
            align-items: center;
            gap: 6px;
        }
        .led {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #4a5568;
            transition: 0.1s;
        }
        .led.active {
            background: #2effb0;
            box-shadow: 0 0 6px #00ffaa;
        }

        /* пульт управления */
        .remote {
            background: #1f2128;
            border-radius: 48px;
            padding: 20px 16px;
            margin-top: 16px;
            box-shadow: 0 8px 20px black;
        }
        .temp-control {
            display: flex;
            align-items: center;
            justify-content: space-between;
            background: #0b0e14;
            padding: 12px 20px;
            border-radius: 100px;
            margin-bottom: 24px;
        }
        .temp-value {
            font-size: 2.2rem;
            font-weight: bold;
            background: #00000066;
            padding: 0 18px;
            border-radius: 50px;
            color: #ffbe76;
        }
        .temp-btn {
            background: #2c3040;
            border: none;
            font-size: 2rem;
            font-weight: bold;
            width: 56px;
            height: 56px;
            border-radius: 40px;
            color: white;
            cursor: pointer;
            transition: 0.05s linear;
            box-shadow: 0 3px 0 #0b0e14;
            touch-action: manipulation;
        }
        .temp-btn:active {
            transform: translateY(2px);
            box-shadow: 0 1px 0 #0b0e14;
        }

        .mode-buttons {
            display: flex;
            gap: 15px;
            margin-bottom: 24px;
        }
        .mode-btn {
            flex: 1;
            background: #2b2f3c;
            border: none;
            padding: 12px 0;
            border-radius: 60px;
            font-weight: bold;
            color: #ccd6f0;
            font-size: 1rem;
            cursor: pointer;
            transition: 0.08s linear;
        }
        .mode-btn.active {
            background: #3a6ea5;
            color: white;
            box-shadow: 0 0 8px #5f9eff;
        }
        .fan-speed {
            display: flex;
            justify-content: space-between;
            gap: 12px;
        }
        .speed-btn {
            flex: 1;
            background: #20232c;
            border: none;
            padding: 10px;
            border-radius: 40px;
            color: #aaa;
            font-weight: bold;
            cursor: pointer;
        }
        .speed-btn.active {
            background: #f0b27a;
            color: #1e1f2c;
            box-shadow: 0 0 4px orange;
        }
        .power-btn {
            margin-top: 24px;
            background: #e74c3c;
            width: 100%;
            border: none;
            padding: 14px;
            border-radius: 60px;
            font-weight: bold;
            font-size: 1.2rem;
            color: white;
            letter-spacing: 2px;
            cursor: pointer;
            transition: 0.05s linear;
        }
        .power-btn:active { transform: scale(0.97); }

        /* статус вкл/выкл */
        .power-status {
            text-align: center;
            margin-top: 12px;
            font-size: 0.7rem;
            color: #9aa6c0;
        }
        footer {
            font-size: 0.6rem;
            text-align: center;
            color: #5f6a80;
            margin-top: 16px;
        }
        @media (max-width: 480px) {
            .simulator { padding: 16px; }
            .temp-value { font-size: 1.8rem; }
            .temp-btn { width: 48px; height: 48px; font-size: 1.8rem; }
        }
    </style>
</head>
<body>
<div class="simulator">
    <div class="title">❄️ СИМУЛЯТОР КОНДИЦИОНЕРА 🔥</div>

    <!-- два блока: внешний + внутренний -->
    <div class="units">
        <!-- Внешний блок (уличный) -->
        <div class="unit-card">
            <div class="unit-title">🌬️ ВНЕШНИЙ БЛОК</div>
            <div class="outdoor-unit">
                <div class="fan-area">
                    <div class="fan-blades" id="fanBlades">
                        <div class="fan-center"></div>
                    </div>
                </div>
                <div class="temp-outdoor" id="outdoorTempDisplay">+25°C</div>
                <div style="font-size:10px; text-align:center; margin-top:5px;">вентилятор</div>
            </div>
        </div>

        <!-- Внутренний блок -->
        <div class="unit-card">
            <div class="unit-title">🏠 ВНУТРЕННИЙ БЛОК</div>
            <div class="indoor-unit">
                <div class="display-temp">
                    <div class="current-temp" id="roomTempDisplay">22.5°</div>
                    <div class="current-mode" id="modeTextDisplay">Режим: ОХЛАЖДЕНИЕ</div>
                </div>
                <div class="indicator-lights">
                    <div class="light"><span class="led" id="powerLed"></span> ПИТАНИЕ</div>
                    <div class="light"><span class="led" id="compressorLed"></span> КОМПРЕССОР</div>
                    <div class="light"><span class="led" id="fanLed"></span> ВЕНТИЛЯТОР</div>
                </div>
            </div>
        </div>
    </div>

    <!-- Пульт управления -->
    <div class="remote">
        <div class="temp-control">
            <button class="temp-btn" id="tempDownBtn" aria-label="уменьшить температуру">−</button>
            <div class="temp-value" id="targetTempSpan">24°</div>
            <button class="temp-btn" id="tempUpBtn" aria-label="увеличить температуру">+</button>
        </div>

        <div class="mode-buttons">
            <button class="mode-btn" data-mode="cool">❄️ ХОЛОД</button>
            <button class="mode-btn" data-mode="heat">🔥 ТЕПЛО</button>
            <button class="mode-btn" data-mode="fan">💨 ТОЛЬКО ВЕНТ</button>
        </div>

        <div class="fan-speed">
            <button class="speed-btn" data-speed="low">🐢 МАЛО</button>
            <button class="speed-btn" data-speed="medium">⚡ СРЕДНЕ</button>
            <button class="speed-btn" data-speed="high">🐇 МНОГО</button>
        </div>

        <button class="power-btn" id="powerBtn">⏻ ВКЛ / ВЫКЛ</button>
        <div class="power-status" id="powerStatusText">СОСТОЯНИЕ: ВЫКЛЮЧЕН</div>
    </div>
    <footer>Пульт управления | меняйте температуру, режим, скорость вентилятора</footer>
</div>

<script>
    (function(){
        // ---------- СОСТОЯНИЕ СИМУЛЯЦИИ ----------
        let isPowerOn = false;        // питание кондиционера
        let targetTemp = 24;          // установленная температура (16-30)
        let currentRoomTemp = 25.0;   // текущая температура в комнате
        const outdoorTempConst = 28;   // внешняя температура фиксирована (+28)
        
        let mode = "cool";             // 'cool', 'heat', 'fan'
        let fanSpeed = "medium";       // 'low', 'medium', 'high'
        
        // Коэффициенты влияния на комнатную температуру (градусов в секунду при активном режиме)
        const coolPower = { low: -0.12, medium: -0.22, high: -0.34 };
        const heatPower = { low: 0.10, medium: 0.20, high: 0.32 };
        const fanOnlyPower = { low: -0.03, medium: -0.06, high: -0.09 };  // легкое охлаждение от вентиляции
        
        let animationId = null;
        let lastTimestamp = 0;
        
        // Вращение вентилятора внешнего блока
        let fanRotation = 0;
        let fanSpinSpeed = 0; // будет обновляться в зависимости от скорости вентилятора и питания
        
        // DOM элементы
        const roomTempSpan = document.getElementById("roomTempDisplay");
        const outdoorTempDisplay = document.getElementById("outdoorTempDisplay");
        const targetTempSpan = document.getElementById("targetTempSpan");
        const modeTextDisplay = document.getElementById("modeTextDisplay");
        const powerLed = document.getElementById("powerLed");
        const compressorLed = document.getElementById("compressorLed");
        const fanLed = document.getElementById("fanLed");
        const powerStatusSpan = document.getElementById("powerStatusText");
        const fanBlades = document.getElementById("fanBlades");
        
        // кнопки
        const tempUp = document.getElementById("tempUpBtn");
        const tempDown = document.getElementById("tempDownBtn");
        const powerBtn = document.getElementById("powerBtn");
        const modeBtns = document.querySelectorAll(".mode-btn");
        const speedBtns = document.querySelectorAll(".speed-btn");
        
        // ---- вспомогательная функция обновления UI ----
        function updateUI() {
            // отображение температуры в комнате с одним знаком
            roomTempSpan.innerText = currentRoomTemp.toFixed(1) + "°";
            targetTempSpan.innerText = Math.round(targetTemp) + "°";
            outdoorTempDisplay.innerText = `+${outdoorTempConst}°C`;
            
            // Режим текст
            let modeStr = "";
            if(mode === "cool") modeStr = "ОХЛАЖДЕНИЕ ❄️";
            else if(mode === "heat") modeStr = "ОБОГРЕВ 🔥";
            else modeStr = "ВЕНТИЛЯЦИЯ 💨";
            modeTextDisplay.innerText = `Режим: ${modeStr}`;
            
            // Индикаторы (питание, компрессор, вентилятор)
            if(isPowerOn) {
                powerLed.classList.add("active");
                powerStatusSpan.innerText = "СОСТОЯНИЕ: ВКЛЮЧЕН ✅";
                // Компрессор активен только в режиме cool/heat (не в fan)
                const compressorActive = (mode === "cool" || mode === "heat");
                if(compressorActive) compressorLed.classList.add("active");
                else compressorLed.classList.remove("active");
                // вентилятор внутренний всегда активен при включении, плюс внешний крутится
                fanLed.classList.add("active");
            } else {
                powerLed.classList.remove("active");
                compressorLed.classList.remove("active");
                fanLed.classList.remove("active");
                powerStatusSpan.innerText = "СОСТОЯНИЕ: ВЫКЛЮЧЕН ⛔";
            }
            
            // Подсветка активных кнопок режима
            modeBtns.forEach(btn => {
                const btnMode = btn.getAttribute("data-mode");
                if( (isPowerOn && btnMode === mode) || (!isPowerOn && false) ) btn.classList.add("active");
                else btn.classList.remove("active");
                // даже если выключен, визуально активного режима нет
                if(!isPowerOn) btn.classList.remove("active");
            });
            // отдельно для включения режима: активен именно текущий режим только если кондиционер включен
            if(isPowerOn){
                document.querySelector(`.mode-btn[data-mode="${mode}"]`).classList.add("active");
            }
            
            // Скорость вентилятора
            speedBtns.forEach(btn => {
                const spd = btn.getAttribute("data-speed");
                if(isPowerOn && fanSpeed === spd) btn.classList.add("active");
                else btn.classList.remove("active");
            });
            
            // Скорость вращения лопастей внешнего блока
            if(isPowerOn && (mode !== "fan" || fanSpeed !== "low")) {
                // Внешний блок вращается активнее при высокой скорости и охлаждении/нагреве
                let baseSpin = 0;
                if(fanSpeed === "low") baseSpin = 5;
                else if(fanSpeed === "medium") baseSpin = 11;
                else baseSpin = 18;
                if(mode === "fan") baseSpin = baseSpin * 0.5; // при простой вентиляции медленнее
                fanSpinSpeed = baseSpin;
            } else {
                fanSpinSpeed = 0;
            }
        }
        
        // Изменение температуры в комнате с течением времени
        function updateTemperature(deltaSec) {
            if(!isPowerOn) {
                // пассивное выравнивание к комнатной температуре (медленно к 24..26)
                if(currentRoomTemp > 25.5) currentRoomTemp -= 0.03 * deltaSec;
                else if(currentRoomTemp < 24.5) currentRoomTemp += 0.02 * deltaSec;
                // ограничим
                if(currentRoomTemp > 32) currentRoomTemp = 32;
                if(currentRoomTemp < 16) currentRoomTemp = 16;
                return;
            }
            
            let effect = 0;
            if(mode === "cool") {
                // стремимся к целевой температуре, но мощность зависит от скорости
                const deltaToTarget = currentRoomTemp - targetTemp;
                let power = coolPower[fanSpeed];
                // чем больше разница, тем сильнее эффект (адаптивная логика)
                let intensity = Math.min(1.2, Math.max(0.4, Math.abs(deltaToTarget) / 7));
                effect = power * intensity * deltaSec;
                if(deltaToTarget > 0) {
                    currentRoomTemp -= Math.abs(effect);
                } else if (deltaToTarget < 0 && currentRoomTemp < targetTemp - 0.3) {
                    // дотягиваем слабо
                    currentRoomTemp += 0.05 * deltaSec;
                }
            } 
            else if(mode === "heat") {
                const deltaToTarget = targetTemp - currentRoomTemp;
                let power = heatPower[fanSpeed];
                let intensity = Math.min(1.2, Math.max(0.4, Math.abs(deltaToTarget) / 6));
                effect = power * intensity * deltaSec;
                if(deltaToTarget > 0) currentRoomTemp += effect;
                else if(deltaToTarget < -0.5 && currentRoomTemp > targetTemp + 0.5) currentRoomTemp -= 0.08 * deltaSec;
            }
            else if(mode === "fan") {
                // вентиляция слабо охлаждает
                let power = fanOnlyPower[fanSpeed];
                currentRoomTemp += power * deltaSec;
            }
            
            // границы температуры 16 - 35
            if(currentRoomTemp > 35) currentRoomTemp = 35;
            if(currentRoomTemp < 16) currentRoomTemp = 16;
            // если режим выключен, то уже пассивная стабилизация
        }
        
        // Анимация вращения вентилятора внешнего блока
        function updateFanRotation(deltaSec) {
            if(fanSpinSpeed > 0 && isPowerOn) {
                fanRotation += fanSpinSpeed * deltaSec * 6; // плавное вращение
                if(fanRotation > 360) fanRotation -= 360;
                if(fanBlades) fanBlades.style.transform = `rotate(${fanRotation}deg)`;
            } else {
                if(fanBlades && !isPowerOn) fanBlades.style.transform = `rotate(0deg)`;
                else if(!isPowerOn) fanRotation = 0;
            }
        }
        
        // основной цикл симуляции (delta time)
        let lastUpdateTime = 0;
        function simulationLoop(nowMs) {
            if(!lastUpdateTime) {
                lastUpdateTime = nowMs;
                requestAnimationFrame(simulationLoop);
                return;
            }
            let delta = Math.min(0.1, (nowMs - lastUpdateTime) / 1000);
            if(delta > 0.01) {
                updateTemperature(delta);
                updateFanRotation(delta);
                updateUI();
            }
            lastUpdateTime = nowMs;
            requestAnimationFrame(simulationLoop);
        }
        
        // -------- Управление пультом ----------
        function setPower(state) {
            isPowerOn = state;
            if(!isPowerOn) {
                // при выключении компрессор не активен, вентилятор внешний останавливается
                fanSpinSpeed = 0;
                fanRotation = 0;
                if(fanBlades) fanBlades.style.transform = `rotate(0deg)`;
                // так же можно сбросить активные индикаторы
            } else {
                // при включении синхронизируем режим
                updateUI();
            }
            updateUI();
        }
        
        function changeTemp(delta) {
            if(!isPowerOn) return;
            let newTemp = targetTemp + delta;
            if(newTemp >= 16 && newTemp <= 30) {
                targetTemp = newTemp;
                updateUI();
            }
        }
        
        function setMode(newMode) {
            if(!isPowerOn) return;
            mode = newMode;
            updateUI();
        }
        
        function setFanSpeed(speed) {
            if(!isPowerOn) return;
            fanSpeed = speed;
            updateUI();
        }
        
        // ---------- Обработчики событий ----------
        tempUp.addEventListener("click", (e) => {
            e.preventDefault();
            changeTemp(1);
        });
        tempDown.addEventListener("click", () => changeTemp(-1));
        powerBtn.addEventListener("click", () => {
            setPower(!isPowerOn);
            // если выключили, то дополнительно обновить все
            if(!isPowerOn) {
                // ничего не меняем в целевой температуре
            } else {
                // включаем с предыдущими настройками
            }
        });
        
        modeBtns.forEach(btn => {
            btn.addEventListener("click", () => {
                const m = btn.getAttribute("data-mode");
                if(isPowerOn) setMode(m);
                else {
                    // Если выключен - можно мигнуть подсказкой, но просто игнорим
                }
            });
        });
        
        speedBtns.forEach(btn => {
            btn.addEventListener("click", () => {
                const spd = btn.getAttribute("data-speed");
                if(isPowerOn) setFanSpeed(spd);
            });
        });
        
        // Инициализируем начальное состояние (выключен)
        isPowerOn = false;
        currentRoomTemp = 25.2;
        targetTemp = 24;
        mode = "cool";
        fanSpeed = "medium";
        updateUI();
        
        // чтобы внешний блок показывал температуру улицы
        outdoorTempDisplay.innerText = `+${outdoorTempConst}°C`;
        
        // запускаем цикл симуляции
        requestAnimationFrame(simulationLoop);
        
        // также для корректной работы на телефоне - убираем контекстное меню
        document.querySelectorAll('button').forEach(btn => {
            btn.addEventListener('touchstart', (e) => {
                e.preventDefault();
                btn.click();
            }, { passive: false });
        });
    })();
</script>
</body>
</html>

Game Source: Симулятор кондиционера — пульт управления

Creator: PrismBolt10

Libraries: none

Complexity: complex (660 lines, 25.1 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: -prismbolt10" to link back to the original. Then publish at arcadelab.ai/publish.