<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AstroCalc Pro - Durasi & Waktu Matahari</title>
<!-- Google Fonts: Poppins -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- FontAwesome untuk Ikon -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* CSS Variables untuk Tema UI Modern (Glassmorphism + Gradien Dinamis) */
:root {
--color-primary: #4f46e5;
--color-primary-hover: #4338ca;
--color-secondary: #0ea5e9;
--color-text-main: #0f172a;
--color-text-muted: #64748b;
--color-bg-light: rgba(255, 255, 255, 0.85);
--color-white: #ffffff;
--shadow-soft: 0 10px 40px -10px rgba(0,0,0,0.1);
--border-subtle: 1px solid rgba(226, 232, 240, 0.8);
--radius-lg: 20px;
--radius-md: 12px;
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: #4158D0;
background-image: linear-gradient(43deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%);
background-size: 200% 200%;
animation: gradientShift 10s ease infinite;
padding: 20px;
color: var(--color-text-main);
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.app-container {
width: 100%;
max-width: 550px;
background: var(--color-bg-light);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: var(--border-subtle);
border-radius: var(--radius-lg);
padding: 35px;
box-shadow: var(--shadow-soft);
}
.app-header {
text-align: center;
margin-bottom: 25px;
}
.app-header__title {
font-size: 1.6rem;
font-weight: 700;
color: var(--color-primary);
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.app-header__subtitle {
font-size: 0.85rem;
color: var(--color-text-muted);
margin-top: 5px;
}
/* Layout Grid untuk Form */
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 20px;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group--full {
grid-column: span 2;
}
.form-label {
font-size: 0.8rem;
font-weight: 600;
color: var(--color-text-main);
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-input-wrapper {
position: relative;
}
.form-input-wrapper i {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: var(--color-text-muted);
font-size: 1rem;
}
.form-input {
width: 100%;
padding: 12px 12px 12px 40px;
background: var(--color-white);
border: 1px solid #cbd5e1;
border-radius: var(--radius-md);
font-size: 0.95rem;
color: var(--color-text-main);
outline: none;
transition: var(--transition-smooth);
}
.form-input:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
}
/* Tombol Utama */
.btn {
width: 100%;
padding: 14px;
border: none;
border-radius: var(--radius-md);
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: var(--transition-smooth);
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
grid-column: span 2;
}
.btn--primary {
background: var(--color-primary);
color: var(--color-white);
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.2);
margin-top: 10px;
}
.btn--primary:hover {
background: var(--color-primary-hover);
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(79, 70, 229, 0.3);
}
.btn--primary:active {
transform: translateY(0);
}
/* Area Hasil Kalkulasi (Grid 2x2) */
.results-section {
margin-top: 25px;
display: none;
flex-direction: column;
gap: 15px;
animation: fadeIn 0.5s ease forwards;
}
.results-section.show {
display: flex;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(15px); }
to { opacity: 1; transform: translateY(0); }
}
.result-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.result-card {
background: var(--color-white);
padding: 18px 15px;
border-radius: var(--radius-md);
border: 1px solid rgba(226, 232, 240, 0.8);
text-align: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.03);
transition: var(--transition-smooth);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.result-card:hover {
border-color: var(--color-primary);
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0,0,0,0.06);
}
.result-icon {
font-size: 1.8rem;
margin-bottom: 10px;
}
/* Palet Warna Ikon Kartu */
.icon-sunrise { color: #f59e0b; }
.icon-sunset { color: #ef4444; }
.icon-day { color: #0ea5e9; }
.icon-night { color: #6366f1; }
.result-label {
font-size: 0.75rem;
color: var(--color-text-muted);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.result-value {
font-size: 1.25rem;
font-weight: 700;
color: var(--color-text-main);
margin-top: 4px;
}
/* Toast Notifikasi Kustom */
.toast {
position: fixed;
bottom: 20px;
right: 20px;
background: var(--color-white);
padding: 15px 20px;
border-radius: var(--radius-md);
box-shadow: 0 10px 25px rgba(0,0,0,0.15);
display: flex;
align-items: center;
gap: 12px;
font-weight: 500;
font-size: 0.9rem;
transform: translateY(150%);
transition: transform 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
z-index: 1000;
border-left: 4px solid #ef4444;
}
.toast.show { transform: translateY(0); }
.toast i { color: #ef4444; font-size: 1.2rem; }
/* Responsivitas untuk Mobile */
@media (max-width: 480px) {
.app-container { padding: 25px 20px; }
.form-grid { grid-template-columns: 1fr; gap: 12px; }
.form-group, .form-group--full, .btn { grid-column: span 1; }
/* Ubah Grid Hasil menjadi 1 kolom di layar sempit */
.result-row { grid-template-columns: 1fr; gap: 12px; }
}
</style>
</head>
<body>
<!-- Kontainer Aplikasi -->
<main class="app-container">
<header class="app-header">
<h1 class="app-header__title"><i class="fa-solid fa-compass"></i> AstroCalc Pro</h1>
<p class="app-header__subtitle">Presisi tinggi waktu & durasi siang-malam</p>
</header>
<!-- Form Input -->
<section class="calculator-form">
<div class="form-grid">
<!-- Baris 1: Tanggal -->
<div class="form-group form-group--full">
<label class="form-label" for="inputDate">Tanggal Pengamatan</label>
<div class="form-input-wrapper">
<i class="fa-regular fa-calendar"></i>
<input type="date" id="inputDate" class="form-input" required>
</div>
</div>
<!-- Baris 2: Lintang & Bujur -->
<div class="form-group">
<label class="form-label" for="inputLat">Lintang (Lat)</label>
<div class="form-input-wrapper">
<i class="fa-solid fa-arrows-up-down"></i>
<input type="number" id="inputLat" class="form-input" placeholder="-7.0" step="any" required>
</div>
</div>
<div class="form-group">
<label class="form-label" for="inputLon">Bujur (Lon)</label>
<div class="form-input-wrapper">
<i class="fa-solid fa-arrows-left-right"></i>
<input type="number" id="inputLon" class="form-input" placeholder="110.4" step="any" required>
</div>
</div>
<!-- Baris 3: Timezone -->
<div class="form-group form-group--full">
<label class="form-label" for="inputTz">Zona Waktu (GMT/UTC +)</label>
<div class="form-input-wrapper">
<i class="fa-regular fa-clock"></i>
<input type="number" id="inputTz" class="form-input" value="7" placeholder="7 untuk WIB" required>
</div>
</div>
<button type="button" id="btnCalculate" class="btn btn--primary">
<i class="fa-solid fa-bolt"></i> Analisis Data
</button>
</div>
</section>
<!-- Area Hasil (4 Kartu) -->
<section id="resultsArea" class="results-section">
<div class="result-row">
<!-- Kartu Terbit -->
<div class="result-card">
<i class="fa-solid fa-sun result-icon icon-sunrise"></i>
<div class="result-label">Matahari Terbit</div>
<div class="result-value" id="valSunrise">00:00</div>
</div>
<!-- Kartu Terbenam -->
<div class="result-card">
<i class="fa-regular fa-sun result-icon icon-sunset"></i>
<div class="result-label">Matahari Terbenam</div>
<div class="result-value" id="valSunset">00:00</div>
</div>
</div>
<div class="result-row">
<!-- Kartu Durasi Siang -->
<div class="result-card">
<i class="fa-solid fa-cloud-sun result-icon icon-day"></i>
<div class="result-label">Durasi Siang</div>
<div class="result-value" id="valDurationDay">0 Jam 0 Menit</div>
</div>
<!-- Kartu Durasi Malam -->
<div class="result-card">
<i class="fa-solid fa-moon result-icon icon-night"></i>
<div class="result-label">Durasi Malam</div>
<div class="result-value" id="valDurationNight">0 Jam 0 Menit</div>
</div>
</div>
</section>
</main>
<!-- Toast Modal -->
<div id="toastMessage" class="toast">
<i class="fa-solid fa-circle-exclamation"></i>
<span id="toastText">Pesan error di sini</span>
</div>
<!-- Logika JavaScript -->
<script>
document.addEventListener('DOMContentLoaded', () => {
// Referensi DOM
const inputDate = document.getElementById('inputDate');
const inputLat = document.getElementById('inputLat');
const inputLon = document.getElementById('inputLon');
const inputTz = document.getElementById('inputTz');
const btnCalculate = document.getElementById('btnCalculate');
const resultsArea = document.getElementById('resultsArea');
const valSunrise = document.getElementById('valSunrise');
const valSunset = document.getElementById('valSunset');
const valDurationDay = document.getElementById('valDurationDay');
const valDurationNight = document.getElementById('valDurationNight');
// Default: Hari ini
inputDate.valueAsDate = new Date();
// Fungsi Notifikasi Modern
const showToast = (message) => {
const toast = document.getElementById('toastMessage');
document.getElementById('toastText').textContent = message;
toast.classList.add('show');
// Hilangkan setelah 3 detik
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
};
/**
* Mesin Algoritma Astronomi (Trigonometri Bola)
* Menghasilkan waktu terbit, terbenam, durasi siang, dan malam.
*/
const calculateSolarTimes = (lat, lon, tz, dateStr) => {
const date = new Date(dateStr);
const rad = Math.PI / 180;
const deg = 180 / Math.PI;
// Julian Date
const jd = (date.getTime() / 86400000) - (date.getTimezoneOffset() / 1440) + 2440587.5;
const d = jd - 2451545.0;
// Anomali & Ecliptic
const g = (357.529 + 0.98560028 * d) % 360;
const q = (280.459 + 0.98564736 * d) % 360;
const L = (q + 1.915 * Math.sin(g * rad) + 0.020 * Math.sin(2 * g * rad)) % 360;
const e = 23.439 - 0.00000036 * d;
const decRad = Math.asin(Math.sin(e * rad) * Math.sin(L * rad));
// Equation of Time
let ra = Math.atan2(Math.cos(e * rad) * Math.sin(L * rad), Math.cos(L * rad)) * deg;
ra = ra < 0 ? ra + 360 : ra;
const eqt = (q / 15) - (ra / 15); // Jam
// Hour Angle (t)
const hRad = -0.8333 * rad; // Koreksi ufuk (refraksi + diameter matahari)
const latRad = lat * rad;
const cosT = (Math.sin(hRad) - Math.sin(latRad) * Math.sin(decRad)) / (Math.cos(latRad) * Math.cos(decRad));
// Kondisi Polar (Matahari tidak terbit / tidak terbenam)
if (cosT > 1) return { error: "Malam 24 Jam (Polar Night)" };
if (cosT < -1) return { error: "Siang 24 Jam (Midnight Sun)" };
const tDeg = Math.acos(cosT) * deg;
const tHour = tDeg / 15;
// Perhitungan Final
const transit = 12 + tz - (lon / 15) - eqt;
const sunrise = transit - tHour;
const sunset = transit + tHour;
const durationDay = tHour * 2;
const durationNight = 24 - durationDay; // Sisa dari 24 jam
return { sunrise, sunset, durationDay, durationNight };
};
// Helper: Format ke HH:MM
const formatHourTime = (decimalHours) => {
let h = (decimalHours + 24) % 24;
let hrs = Math.floor(h);
let mins = Math.round((h - hrs) * 60);
if (mins === 60) { hrs += 1; mins = 0; }
if (hrs === 24) hrs = 0;
return `${String(hrs).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;
};
// Helper: Format ke "X Jam Y Menit"
const formatDuration = (decimalHours) => {
let hrs = Math.floor(decimalHours);
let mins = Math.round((decimalHours - hrs) * 60);
if (mins === 60) { hrs += 1; mins = 0; }
return `${hrs} Jam ${mins} Menit`;
};
// Eksekutor Klik Tombol
btnCalculate.addEventListener('click', () => {
const lat = parseFloat(inputLat.value);
const lon = parseFloat(inputLon.value);
const tz = parseFloat(inputTz.value);
const date = inputDate.value;
// Validasi Basic
if (!date || isNaN(lat) || isNaN(lon) || isNaN(tz)) {
showToast('Harap isi semua kolom dengan data yang valid.');
return;
}
if (lat < -90 || lat > 90) {
showToast('Garis Lintang (Lat) harus antara -90 dan 90.');
return;
}
if (lon < -180 || lon > 180) {
showToast('Garis Bujur (Lon) harus antara -180 dan 180.');
return;
}
// Efek visual tombol loading
const originalText = btnCalculate.innerHTML;
btnCalculate.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Memproses...';
setTimeout(() => {
const result = calculateSolarTimes(lat, lon, tz, date);
btnCalculate.innerHTML = originalText; // Kembalikan teks
if (result.error) {
showToast(result.error);
resultsArea.classList.remove('show');
return;
}
// Tampilkan Data ke UI
valSunrise.textContent = formatHourTime(result.sunrise);
valSunset.textContent = formatHourTime(result.sunset);
valDurationDay.textContent = formatDuration(result.durationDay);
valDurationNight.textContent = formatDuration(result.durationNight);
resultsArea.classList.add('show');
}, 300); // Simulasi delay animasi
});
});
</script>
</body>
</html>