Симулятор кондиционера — пульт управления
by PrismBolt10660 lines25.1 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">
<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.