Kode Progam Web Server

This commit is contained in:
KevinRiang 2025-06-24 16:30:06 +07:00 committed by GitHub
parent be170e8213
commit ecdbe9689c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1216 additions and 0 deletions

708
data/dashboard1.html Normal file
View File

@ -0,0 +1,708 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<title>Dashboard Inkubator Telur Ayam</title>
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Poppins', sans-serif;
background-color: #f7f7f7;
display: flex;
height: 100vh;
overflow: hidden;
font-size: 18px;
}
.sidebar {
width: 80px;
background: #ffc107;
color: #5d4037;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
justify-content: space-between;
height: 100vh;
height: 100%;
padding: 20px 0;
gap: 0;
}
.sidebar .menu {
display: flex;
flex-direction: column;
gap: 30px;
justify-content: center;
flex-grow: 1;
z-index: 2;
height: 100%;
}
.sidebar .menu a {
color: #5d4037;
font-size: 24px;
text-decoration: none;
transition: transform 0.2s, color 0.2s;
}
.menu-icon {
width: 60px;
height: 60px;
transition: transform 0.2s;
}
.sidebar .menu a:hover .menu-icon {
transform: scale(1.2);
}
.sidebar .menu a.active .menu-icon,
.sidebar .menu a.active .logo,
.sidebar .menu a.logo-link.active .logo {
transform: scale(1.2); /* Tetap timbul */
}
.sidebar .menu a.active {
color: #3e2723; /* Warna teks lebih gelap */
font-weight: 600; /* Lebih tebal */
}
.menu-tentang {
margin-top: 0px;
}
.sidebar a.logo-link {
display: flex;
justify-content: center;
}
.logo {
width: 60px;
height: 60px;
transition: transform 0.2s;
}
.logo:hover {
transform: scale(1.1);
}
.main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.topbar {
height: 60px;
background: #ffe082;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 25px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.topbar .user {
position: relative;
}
.profile {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
background-color: #ffe082;
padding: 8px 12px;
border-radius: 20px;
transition: background-color 0.3s ease;
}
.profile:hover {
background-color: #ffd54f;
}
.profile span {
font-size: 16px;
}
.profile img {
width: 36px;
height: 36px;
border-radius: 50%;
}
.dropdown {
display: block;
position: absolute;
top: 48px;
right: 0;
background: #fff8e1;
border: 1px solid #ffc107;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
padding: 8px;
opacity: 0;
transform: translateY(-10px);
transition: all 0.3s ease;
pointer-events: none;
z-index: 10;
}
.dropdown.show {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.dropdown button {
width: 100%;
padding: 10px 12px;
background: #d84315;
color: white;
border: none;
border-radius: 6px;
font-weight: bold;
cursor: pointer;
transition: background 0.3s ease;
}
.dropdown button:hover {
background: #bf360c;
}
.content {
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1.2fr;
gap: 15px;
padding: 20px;
}
.card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
display: flex;
flex-direction: column;
justify-content: center;
gap: 15px;
}
.card h3 {
font-size: 20px;
margin-bottom: 5px;
color: #6d4c41;
}
.countdown {
font-size: 36px;
font-weight: 600;
text-align: center;
color: #4e342e;
letter-spacing: 20px;
margin-top: 85px;
margin-bottom: 85px;
}
.status-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
}
.status-box {
background: #fff8e1;
padding: 14px;
border-radius: 10px;
text-align: center;
font-size: 16px;
color: #6d4c41;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.status-box span {
display: block;
font-size: 22px;
color: #e65100;
}
input[type="number"] {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 8px;
font-size: 16px;
}
button {
padding: 12px;
border: none;
border-radius: 8px;
background: #ffb300;
color: white;
font-weight: bold;
font-size: 16px;
cursor: pointer;
}
button:hover { background: #ffa000; }
button:disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.7;
}
hr {
border: 0;
border-top: 1px solid #eee;
margin: 10px 0;
}
.indicator-dot {
width: 14px;
height: 14px;
border-radius: 50%;
background-color: #ccc;
margin: 8px auto 0;
transition: background-color 0.3s ease;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.indicator-dot.on {
background-color: #4CAF50;
box-shadow: 0 0 8px rgba(76, 175, 80, 0.6);
}
.indicator-dot.off {
background-color: #f44336;
box-shadow: 0 0 8px rgba(244, 67, 54, 0.6);
}
.status-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.status-box span {
margin-bottom: 5px;
}
@media (min-width: 1601px) and (max-width: 1920px) {
.content {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: auto auto;
}
.status-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1921px) {
.content {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: auto auto;
gap: 20px;
padding: 30px 40px;
}
.card h3 {
font-size: 24px;
}
.countdown {
font-size: 42px;
letter-spacing: 24px;
margin: 100px 0;
}
.status-grid {
grid-template-columns: repeat(3, 1fr);
}
.status-box {
font-size: 18px;
}
.status-box span {
font-size: 26px;
}
input[type="number"],
button {
font-size: 18px;
padding: 16px;
}
}
#toggleRackButton {
background-color: #007bff; /* Warna biru untuk identifikasi */
}
#toggleRackButton:hover {
background-color: #0056b3;
}
#toggleRackButton:disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.7;
}
</style>
</head>
<body>
<script>
if (localStorage.getItem("loggedIn") !== "true") {
window.location.href = "login1.html";
}
</script>
<div class="sidebar">
<div class="menu" id="menuContainer">
<a href="tentang1.html" class="menu-tentang" title="Tentang" id="menu-tentang">
<img src="tentang.png" alt="Tentang" class="menu-icon" />
</a>
<a href="dashboard1.html" title="Dashboard" id="menuLogo">
<img src="logo.png" class="logo" alt="Logo" />
</a>
</div>
</div>
<div class="main">
<div class="topbar">
<div class="user">
<div class="profile" onclick="toggleDropdown()">
<img
src="https://cdn-icons-png.flaticon.com/512/2922/2922510.png"
alt="User"
/>
<span>admin</span>
</div>
<div class="dropdown" id="dropdownMenu">
<button onclick="logout()">Logout</button>
</div>
</div>
</div>
<div class="content">
<div class="card" style="grid-column: 1 / -1;">
<h3>⏳ Countdown Inkubasi</h3>
<div class="countdown" id="countdown">-- : -- : -- : --</div>
</div>
<div class="card">
<h3>📈 Monitoring</h3>
<div class="status-grid">
<div class="status-box">
🌡️ Suhu<span id="suhu">-- °C</span>
<div class="indicator-dot" id="heaterIndicator"></div>
</div>
<div class="status-box">
💧 Kelembaban<span id="hum">-- %</span>
<div class="indicator-dot" id="mistMakerIndicator"></div>
</div>
<div class="status-box">
📅 Hari Inkubasi<span id="hari">--</span>
</div>
<div class="status-box">
🔄 Kondisi Rak<span id="sudutRak">--</span>
<div class="indicator-dot" id="rackMotorIndicator"></div>
</div>
<div class="status-box">
🌬️ Ventilasi<span id="sudutVentilasi">-- °</span>
</div>
<div class="status-box">
🔥 PWM Heater<span id="pwmHeater">-- %</span>
</div>
</div>
</div>
<div class="card">
<h3>⚙️ Atur & Kontrol</h3>
<input
type="number"
id="hariInput"
min="1"
max="21"
placeholder="Masukkan hari (1-21)"
/>
<button id="setHariButton" onclick="setHariInkubasi()">Start Inkubasi</button>
<button id="stopInkubasiButton" onclick="stopInkubasi()">Stop Inkubasi</button>
<hr /> <button id="toggleRackButton" onclick="toggleRackManual()">Putar Rak Telur Manual</button>
</div>
</div>
</div>
<script>
let countdownInterval = null;
let isInkubasiActive = false;
const maxDay = 21;
let incubationStartDate = null;
const setHariButton = document.getElementById("setHariButton");
const stopInkubasiButton = document.getElementById("stopInkubasiButton");
const hariInput = document.getElementById("hariInput");
const toggleRackButton = document.getElementById("toggleRackButton");
let isAutoMode = true;
let lastRtcTime = 0;
let browserTimeAtLastRtcRead = 0;
function logout() {
localStorage.removeItem("loggedIn");
window.location.href = "login1.html";
}
function toggleDropdown() {
const dropdown = document.getElementById("dropdownMenu");
dropdown.classList.toggle("show");
}
window.addEventListener("click", function (e) {
const profile = document.querySelector(".profile");
const dropdown = document.getElementById("dropdownMenu");
if (!profile.contains(e.target) && !dropdown.contains(e.target)) {
dropdown.classList.remove("show");
}
});
function animateUpdate(id, value) {
const el = document.getElementById(id);
if (el) {
if (el.textContent !== value) {
el.textContent = value;
el.style.transform = "scale(1.1)";
setTimeout(() => {
el.style.transform = "scale(1)";
}, 200);
}
}
}
async function updateData() {
try {
const res = await fetch("/data");
if (!res.ok) throw new Error("HTTP error " + res.status);
const data = await res.json();
animateUpdate("suhu", data.temp + " °C");
animateUpdate("hum", data.hum + " %");
animateUpdate("hari", data.day);
animateUpdate("sudutRak", data.sudutRak + " ");
animateUpdate("sudutVentilasi", data.sudutVentilasi + " °");
animateUpdate("pwmHeater", (data.pwmHeater).toFixed(0) + " %");
function updateIndicator(elementId, isOn) {
const indicator = document.getElementById(elementId);
if (indicator) {
indicator.classList.remove('on', 'off');
if (isOn) {
indicator.classList.add('on');
} else {
indicator.classList.add('off');
}
}
}
updateIndicator("heaterIndicator", data.isHeaterOn);
updateIndicator("mistMakerIndicator", data.isMistMakerOn);
updateIndicator("rackMotorIndicator", data.isRackMotorMoving);
isInkubasiActive = data.isIncubationStarted;
if (data.startDate && typeof data.startDate === 'string' && data.startDate.match(/^\d{4}-\d{2}-\d{2}$/)) {
const newStartDate = new Date(data.startDate);
if (!incubationStartDate || newStartDate.getTime() !== incubationStartDate.getTime() || countdownInterval === null) {
incubationStartDate = newStartDate;
startCountdown(data.day);
}
} else {
clearInterval(countdownInterval);
countdownInterval = null;
document.getElementById("countdown").textContent = "-- : -- : -- : --";
incubationStartDate = null;
}
if (isInkubasiActive) {
setHariButton.disabled = true; // Nonaktifkan tombol "Start Inkubasi"
hariInput.disabled = true; // Nonaktifkan input hari
stopInkubasiButton.disabled = false; // Aktifkan tombol "Stop Inkubasi"
} else {
setHariButton.disabled = false; // Aktifkan tombol "Start Inkubasi"
hariInput.disabled = false; // Aktifkan input hari
stopInkubasiButton.disabled = true; // Nonaktifkan tombol "Stop Inkubasi"
}
if (!isInkubasiActive) { // Jika inkubasi TIDAK aktif
toggleRackButton.disabled = false; // Aktifkan tombol
toggleRackButton.textContent = "Putar Rak Telur Manual"; // Kembalikan teks normal
} else { // Jika inkubasi sedang aktif
toggleRackButton.disabled = true; // Nonaktifkan tombol
toggleRackButton.textContent = "Putar Rak Telur Manual (Nonaktif saat Inkubasi)"; // Teks baru yang lebih jelas
}
} catch (err) {
console.error("Gagal mengambil data:", err);
clearInterval(countdownInterval);
countdownInterval = null;
document.getElementById("countdown").textContent = "-- : -- : -- : --";
incubationStartDate = null;
setHariButton.disabled = false;
hariInput.disabled = false;
stopInkubasiButton.disabled = true;
toggleRackButton.disabled = true;
toggleRackButton.textContent = "Putar Rak Telur Manual (Mode Otomatis)";
}
}
async function setHariInkubasi() {
const hari = parseInt(document.getElementById("hariInput").value);
if (isNaN(hari) || hari < 1 || hari > maxDay) {
alert("Masukkan hari antara 1 sampai " + maxDay);
return;
}
try {
const res = await fetch("/setday", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ day: hari }),
});
if (!res.ok) throw new Error("Gagal mengatur hari inkubasi.");
const data = await res.json();
alert(data.message || "Hari inkubasi berhasil diatur.");
await updateData();
} catch (err) {
alert("Gagal mengirim data: " + err);
setHariButton.disabled = false;
hariInput.disabled = false;
}
}
async function stopInkubasi() {
if (!confirm("Apakah Anda yakin ingin menghentikan inkubasi? Semua proses akan dihentikan dan data hari inkubasi akan direset.")) {
return;
}
try {
const res = await fetch("/stop", { method: "POST" });
if (!res.ok) throw new Error("Gagal menghentikan inkubasi");
clearInterval(countdownInterval);
countdownInterval = null;
document.getElementById("countdown").textContent = "-- : -- : -- : --";
incubationStartDate = null;
updateData();
alert("Inkubasi dihentikan.");
} catch (err) {
alert(err);
stopInkubasiButton.disabled = false;
}
}
async function toggleRackManual() {
try {
toggleRackButton.disabled = true; // Nonaktifkan tombol sementara
const res = await fetch("/toggleRack", { method: "POST" });
if (!res.ok) throw new Error("Gagal mengirim perintah putar rak.");
const data = await res.json();
alert(data.message || "Perintah putar rak berhasil dikirim.");
} catch (err) {
alert("Gagal memutar rak: " + err);
} finally {
toggleRackButton.disabled = false; // Aktifkan kembali tombol setelah respons
}
}
function startCountdown(currentDayFromEsp) {
if (countdownInterval) clearInterval(countdownInterval);
if (!isInkubasiActive || !incubationStartDate) {
document.getElementById("countdown").textContent = "-- : -- : -- : --";
return;
}
const startTimeMs = incubationStartDate.getTime();
const totalIncubationDurationMs = maxDay * 24 * 60 * 60 * 1000;
const endDateMs = startTimeMs + totalIncubationDurationMs;
countdownInterval = setInterval(() => {
const nowMs = new Date().getTime();
let timeLeft = endDateMs - nowMs;
if (timeLeft < 0) {
clearInterval(countdownInterval);
countdownInterval = null;
document.getElementById("countdown").textContent = "00 : 00 : 00 : 00";
return;
}
const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24));
const hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
document.getElementById("countdown").textContent =
`${String(days).padStart(2, "0")} : ${String(hours).padStart(2, "0")} : ${String(minutes).padStart(2, "0")} : ${String(seconds).padStart(2, "0")}`;
}, 1000);
}
function setMenuActive() {
const currentPath = window.location.pathname.split('/').pop();
const menuLinks = document.querySelectorAll('.sidebar .menu a');
menuLinks.forEach(link => {
link.classList.remove('active');
const linkPath = link.getAttribute('href');
if (linkPath === currentPath) {
link.classList.add('active');
}
});
}
document.addEventListener('DOMContentLoaded', setMenuActive);
updateData();
setInterval(updateData, 5000);
</script>
</body>
</html>

BIN
data/diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

136
data/login1.html Normal file
View File

@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Login Inkubator</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
* { box-sizing: border-box; }
body {
font-family: 'Poppins', sans-serif;
background: linear-gradient(to bottom right, #ffe082, #fff3e0);
margin: 0;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.login-container {
background: #fffdf5;
padding: 40px 30px;
border-radius: 12px;
box-shadow: 0 6px 25px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
text-align: center;
}
.login-container h2 {
margin-bottom: 25px;
color: #6d4c41;
}
.form-group {
position: relative;
margin: 10px 0;
}
input[type="text"], input[type="password"] {
width: 100%;
padding: 12px 40px 12px 12px;
border: 1px solid #ccc;
border-radius: 8px;
font-size: 16px;
}
.toggle-password {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: #666;
cursor: pointer;
font-size: 18px;
}
button {
width: 100%;
padding: 14px;
background-color: #ffb300;
color: #fff;
font-weight: bold;
border: none;
border-radius: 8px;
font-size: 16px;
margin-top: 20px;
cursor: pointer;
transition: background 0.3s;
}
button:hover {
background-color: #ffa000;
}
.error {
color: red;
font-size: 14px;
margin-top: 10px;
display: none;
}
.chick-icon {
width: 100px;
margin-bottom: 15px;
}
</style>
</head>
<body>
<div class="login-container">
<img src="logo.png" class="chick-icon" alt="logo">
<h2>Login Inkubator</h2>
<form onsubmit="handleSubmit(event)">
<div class="form-group">
<input type="text" id="username" placeholder="Username" required>
</div>
<div class="form-group">
<input type="password" id="password" placeholder="Password" required>
<i class="fas fa-eye toggle-password" id="toggleIcon" onclick="togglePassword()"></i>
</div>
<button type="submit">Masuk</button>
<p class="error" id="errorMsg">Username atau password salah</p>
</form>
</div>
<script>
function handleSubmit(event) {
event.preventDefault();
const user = document.getElementById("username").value;
const pass = document.getElementById("password").value;
const error = document.getElementById("errorMsg");
if (user === "admin" && pass === "admin123") {
localStorage.setItem("loggedIn", "true");
window.location.href = "dashboard1.html";
} else {
error.style.display = "block";
}
}
function togglePassword() {
const passwordInput = document.getElementById("password");
const toggleIcon = document.getElementById("toggleIcon");
if (passwordInput.type === "password") {
passwordInput.type = "text";
toggleIcon.classList.remove("fa-eye");
toggleIcon.classList.add("fa-eye-slash");
} else {
passwordInput.type = "password";
toggleIcon.classList.remove("fa-eye-slash");
toggleIcon.classList.add("fa-eye");
}
}
</script>
</body>
</html>

BIN
data/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
data/tentang.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

372
data/tentang1.html Normal file
View File

@ -0,0 +1,372 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Tentang Inkubator Telur Ayam</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Sama seperti dashboard */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Poppins', sans-serif;
background-color: #f7f7f7;
display: flex;
height: 100vh;
overflow: hidden;
font-size: 18px;
}
.sidebar {
width: 80px;
background: #ffc107;
color: #5d4037;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
justify-content: space-between; /* Ganti center jadi space-between */
height: 100vh;
height: 100%; /* tetap supaya memenuhi tinggi sidebar */
padding: 20px 0; /* optional agar ada space atas bawah */
gap: 0; /* hapus gap supaya jarak antar elemen dikontrol space-between */
}
.sidebar .menu {
display: flex;
flex-direction: column;
gap: 30px;
justify-content: center; /* Bisa juga space-around atau space-evenly */
flex-grow: 1;
z-index: 2;
height: 100%; /* pastikan memenuhi tinggi sidebar */
}
.sidebar .menu a {
color: #5d4037;
font-size: 24px;
text-decoration: none;
transition: transform 0.2s, color 0.2s;
}
.menu-icon {
width: 60px;
height: 60px;
transition: transform 0.2s;
}
.sidebar .menu a:hover .menu-icon {
transform: scale(1.2);
}
.sidebar .menu a.active .menu-icon,
.sidebar .menu a.active .logo {
transform: scale(1.2); /* Tetap timbul */
}
.sidebar .menu a.active {
color: #3e2723; /* Warna teks lebih gelap */
font-weight: 600; /* Lebih tebal */
}
.menu-tentang {
margin-top: 0px;
}
.sidebar a.logo-link {
display: flex;
justify-content: center;
}
.logo {
width: 60px;
height: 60px;
transition: transform 0.2s;
}
.logo:hover {
transform: scale(1.1);
}
.main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.topbar {
height: 50px;
background: #ffe082;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 25px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.topbar .user {
position: relative;
}
.profile {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
background-color: #ffe082;
padding: 8px 12px;
border-radius: 20px;
transition: background-color 0.3s ease;
}
.profile:hover {
background-color: #ffd54f;
}
.profile span {
font-size: 16px;
}
.profile img {
width: 36px;
height: 36px;
border-radius: 50%;
}
.dropdown {
display: block;
position: absolute;
top: 48px;
right: 0;
background: #fff8e1;
border: 1px solid #ffc107;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
padding: 8px;
opacity: 0;
transform: translateY(-10px);
transition: all 0.3s ease;
pointer-events: none;
z-index: 10;
}
.dropdown.show {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.dropdown button {
width: 100%;
padding: 10px 12px;
background: #d84315;
color: white;
border: none;
border-radius: 6px;
font-weight: bold;
cursor: pointer;
transition: background 0.3s ease;
}
.dropdown button:hover {
background: #bf360c;
}
.content {
flex: 1;
padding: 40px;
overflow-y: auto;
}
.card {
background: white;
padding: 30px;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
max-width: 1200px; /* Contoh: diperlebar jadi 1200px */
margin: 0 auto;
}
.card h1 {
font-size: 28px;
margin-bottom: 20px;
color: #6d4c41;
}
.card p {
font-size: 18px;
line-height: 1.6;
color: #4e342e;
}
@media (min-width: 1601px) and (max-width: 1920px) {
.content {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: auto auto;
}
}
@media (min-width: 1921px) {
.content {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: auto auto;
gap: 20px;
padding: 30px 40px;
}
.card h3 {
font-size: 24px;
}
.countdown {
font-size: 42px;
letter-spacing: 24px;
margin: 100px 0;
}
.status-box {
font-size: 18px;
}
.status-box span {
font-size: 26px;
}
input[type="number"],
button {
font-size: 18px;
padding: 16px;
}
}
</style>
</head>
<body>
<script>
if (localStorage.getItem("loggedIn") !== "true") {
window.location.href = "login1.html";
}
</script>
<!-- Sidebar -->
<div class="sidebar">
<div class="menu" id="menuContainer">
<a href="tentang1.html" class="menu-tentang" title="Tentang" id="menu-tentang">
<img src="tentang.png" alt="Tentang" class="menu-icon">
</a>
<!-- Posisikan logo dulu -->
<a href="dashboard1.html" class="logo-link" title="Dashboard" id="menuLogo">
<img src="logo.png" class="logo" alt="Logo">
</a>
</div>
</div>
<!-- Main content -->
<div class="main">
<div class="topbar">
<div class="user">
<div class="profile" onclick="toggleDropdown()">
<img src="https://cdn-icons-png.flaticon.com/512/2922/2922510.png" alt="User">
<span>admin</span>
</div>
<div class="dropdown" id="dropdownMenu">
<button onclick="logout()">Logout</button>
</div>
</div>
</div>
<div class="content">
<div class="card">
<h1>Tentang Inkubator Telur Ayam Otomatis</h1>
<p>
Inkubator telur ayam otomatis ini dirancang khusus untuk **meniru kondisi alami pengeraman induk ayam, memastikan telur berkembang optimal hingga menetas. Dengan teknologi canggih dan sistem kontrol presisi, inkubator ini menjadi solusi ideal bagi peternak yang ingin meningkatkan efisiensi penetasan dan menjaga kualitas telur.
</p>
<h2>Mengapa Inkubator Otomatis?</h2>
<p>
Proses inkubasi telur memerlukan lingkungan yang stabil dan terkontrol. Inkubator otomatis kami mengatasi tantangan ini dengan:
</p>
<ul>
<li>✅ Stabilitas Lingkungan: Mempertahankan suhu dan kelembaban pada tingkat optimal secara konsisten.</li>
<li>🔄 Pemerataan Panas: Mencegah titik panas atau dingin yang bisa merusak embrio.</li>
<li>⏲️ Efisiensi Waktu: Mengurangi kebutuhan intervensi manual, sehingga peternak bisa fokus pada tugas lain.</li>
<li>📈 Peningkatan Angka Penetasan: Kondisi ideal yang terjaga meningkatkan peluang keberhasilan penetasan telur.</li>
</ul>
<h2>Komponen Utama dan Fungsinya</h2>
<p>
Sistem inkubator ini terintegrasi dengan beberapa komponen kunci, masing-masing dengan peran vital dalam menjaga kondisi inkubasi:
</p>
<ul>
<li>🌡️ Sensor Suhu & Kelembaban (DHT11/DHT22): Memantau kondisi internal inkubator secara *real-time*. Data ini menjadi dasar untuk semua kontrol otomatis.</li>
<li>🔥 Heater (Pemanas): Bertanggung jawab untuk menaikkan dan menjaga suhu internal sesuai target. Dijalankan dengan kontrol PWM untuk regulasi panas yang halus dan akurat.</li>
<li>💧 Mist Maker (Pembuat Kabut/Pelembab): Menghasilkan uap air untuk menjaga tingkat kelembaban yang optimal di dalam inkubator, krusial untuk perkembangan embrio.</li>
<li>⚙️ Motor Rak Telur (Servo/Stepper Motor): Menggerakkan rak telur secara berkala untuk memutar telur (umumnya 45 derajat ke kedua sisi). Proses pemutaran ini sangat penting untuk mencegah embrio menempel pada cangkang dan memastikan perkembangan yang merata.</li>
<li>🌬️ Motor Ventilasi (Servo Motor): Mengatur bukaan ventilasi untuk mengontrol aliran udara dan pertukaran oksigen, membantu mengeluarkan karbon dioksida berlebih dan memasukkan udara segar.</li>
<li>🧠 Mikrokontroler (ESP32/Arduino): Sebagai "otak" sistem, mengumpulkan data sensor, menjalankan algoritma kontrol (PID, logika if-else, dll.), dan mengelola komunikasi web.</li>
</ul>
<h2>Fitur Unggulan Aplikasi Web Ini</h2>
<p>
Antarmuka web yang intuitif ini dirancang untuk memberikan kendali penuh dan informasi akurat di genggaman Anda:
</p>
<ul>
<li>⏱️ Countdown Hari Inkubasi: Memantau sisa hari hingga penetasan, memberikan estimasi waktu yang jelas.</li>
<li>📊 Monitoring Data *Real-time*: Lihat suhu, kelembaban, posisi rak, dan sudut ventilasi secara langsung.</li>
<li>📈 Grafik Data Historis: Analisis tren suhu, kelembaban, dan pergerakan aktuator selama siklus inkubasi.</li>
<li>⚙️ Mode Kontrol Otomatis & Manual: Pilih antara mode otomatis (sistem mengontrol sendiri) atau mode manual untuk mengambil alih kendali penuh.</li>
<li>🔒 Sistem Login Admin: Menjaga keamanan akses ke pengaturan dan kontrol inkubator.</li>
<li>📱 Akses Fleksibel: Dapat diakses dari berbagai perangkat melalui browser web.</li>
</ul>
</div>
<script>
function logout() {
localStorage.removeItem("loggedIn");
window.location.href = "login1.html";
}
function toggleDropdown() {
const dropdown = document.getElementById("dropdownMenu");
dropdown.classList.toggle("show");
}
window.addEventListener("click", function(e) {
const profile = document.querySelector(".profile");
const dropdown = document.getElementById("dropdownMenu");
if (!profile.contains(e.target) && !dropdown.contains(e.target)) {
dropdown.classList.remove("show");
}
});
// Fungsi untuk menandai menu aktif
function setMenuActive() {
// Dapatkan path nama file dari URL saat ini (misal: "dashboard1.html")
const currentPath = window.location.pathname.split('/').pop();
// Dapatkan semua tautan menu di sidebar
const menuLinks = document.querySelectorAll('.sidebar .menu a');
menuLinks.forEach(link => {
// Hapus kelas 'active' dari semua tautan terlebih dahulu
link.classList.remove('active');
// Dapatkan path nama file dari atribut href tautan
const linkPath = link.getAttribute('href');
// Jika path tautan cocok dengan path halaman saat ini, tambahkan kelas 'active'
if (linkPath === currentPath) {
link.classList.add('active');
}
});
}
// Panggil fungsi ini saat halaman dimuat
document.addEventListener('DOMContentLoaded', setMenuActive);
</script>
</body>
</html>