<!DOCTYPE html>
<!--
https://m.facebook.com/luberth.dijkman/
https://codepen.io/ldijkman/pen/NPxPpZE
-->
<html lang="nl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>24Hr Day Time Circle Schedule</title>
<style>
body {
margin: 0;
background: #111827;
color: #eee;
font-family: sans-serif;
padding: 20px;
}
h1 {
font-size: 18px;
margin-bottom: 10px;
}
.circlewrap {
position: relative;
display: inline-block;
}
canvas {
background: #111;
border-radius: 50%;
}
.time {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 20px;
font-weight: bold;
color: #06b6d4;
text-align: center;
}
.dateInfo {
position: absolute;
top: 60%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 12px;
color: #fbbf24;
text-align: center;
line-height: 1.3;
}
.moon {
position: absolute;
top: 36%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 26px;
}
.moonText {
position: absolute;
top: 44%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 12px;
color: #fbbf24;
}
#sunTimes {
margin-top: 8px;
text-align: center;
font-size: 14px;
color: #fbbf24;
}
#geoMsg {
margin-top: 4px;
text-align: center;
font-size: 13px;
color: #ef4444;
}
form {
margin-top: 15px;
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
input[type='time'] {
padding: 6px;
border-radius: 6px;
border: none;
}
button {
padding: 6px 10px;
border-radius: 6px;
border: none;
cursor: pointer;
}
.addBtn {
flex-basis: 100%;
max-width: 150px;
margin-top: 5px;
background: #06b6d4;
color: #111;
}
ul {
list-style: none;
padding: 0;
margin-top: 15px;
}
li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 8px;
margin-bottom: 6px;
background: #1f2937;
border-radius: 6px;
flex-wrap: wrap;
gap: 6px;
}
.controls {
display: flex;
gap: 6px;
}
.btn {
padding: 4px 8px;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 12px;
}
.delBtn {
background: #ef4444;
color: white;
}
.editBtn {
background: #f59e0b;
color: white;
}
.toggleBtn {
background: #06b6d4;
color: #111;
}
</style>
</head>
<body>
<h1>24Hr Day Time Circle TimeSlots Timer</h1>
<div class="circlewrap">
<canvas id="dayCircle" width="320" height="320"></canvas>
<div class="moon" id="moonIcon">🌕</div>
<div class="moonText" id="moonText">Volle Maan</div>
<div class="time" id="centerTime">--:--</div>
<div class="dateInfo" id="dateInfo">--</div>
</div>
<div id="sunTimes">Zonsopgang: --:-- | Zonsondergang: --:--</div>
<div id="geoMsg"></div>
<form id="addForm">
<input type="time" id="startTime" required />
<input type="time" id="endTime" required />
<button type="submit" class="addBtn">Add TimeSlot</button>
</form>
<ul id="slotList"></ul>
<script>
const canvas = document.getElementById('dayCircle');
const ctx = canvas.getContext('2d');
const r = canvas.width / 2 - 30;
const cx = canvas.width / 2;
const cy = canvas.height / 2;
const STORAGE_KEY = 'dagcirkel_slots_v7';
let slots = [];
let sunrise = '07:00',
sunset = '19:00';
function toMin(t) {
const [h, m] = t.split(':').map(Number);
return h * 60 + m;
}
function toHHMM(mins) {
const h = String(Math.floor(mins / 60)).padStart(2, '0');
const m = String(mins % 60).padStart(2, '0');
return `${h}:${m}`;
}
function isInSlot(mins, slot) {
const s = toMin(slot.start),
e = toMin(slot.end);
return s < e ? mins >= s && mins < e : mins >= s || mins < e;
}
function toLocalHHMM(d) {
return (
String(d.getHours()).padStart(2, '0') +
':' +
String(d.getMinutes()).padStart(2, '0')
);
}
function getISOWeek(date) {
const tmp = new Date(date.getTime());
tmp.setHours(0, 0, 0, 0);
tmp.setDate(tmp.getDate() + 3 - ((tmp.getDay() + 6) % 7));
const week1 = new Date(tmp.getFullYear(), 0, 4);
return (
1 +
Math.round(
((tmp - week1) / 86400000 - 3 + ((week1.getDay() + 6) % 7)) / 7
)
);
}
function save() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(slots));
}
function load() {
const data = localStorage.getItem(STORAGE_KEY);
if (data) {
slots = JSON.parse(data);
} else {
slots = [
{ id: 1, start: '06:00', end: '08:00', enabled: true },
{ id: 2, start: '12:00', end: '13:30', enabled: true },
{ id: 3, start: '19:00', end: '22:00', enabled: true },
];
save();
}
}
function drawMarkers() {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
for (let hour = 0; hour < 24; hour++) {
const mins = hour * 60;
const angle = (mins / 1440) * 2 * Math.PI - Math.PI / 2;
const inner = hour % 6 === 0 ? r - 14 : r - 8;
const outer = r + 6;
const x1 = cx + Math.cos(angle) * inner,
y1 = cy + Math.sin(angle) * inner;
const x2 = cx + Math.cos(angle) * outer,
y2 = cy + Math.sin(angle) * outer;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = '#aaa';
ctx.lineWidth = hour % 6 === 0 ? 2 : 1;
ctx.stroke();
if (hour % 6 === 0) {
ctx.fillStyle = '#ccc';
ctx.font = '12px sans-serif';
const labelX = cx + Math.cos(angle) * (r + 20),
labelY = cy + Math.sin(angle) * (r + 20);
ctx.fillText(hour.toString(), labelX, labelY);
}
}
}
function drawDayNight() {
const srMin = toMin(sunrise),
ssMin = toMin(sunset);
const srAngle = (srMin / 1440) * 2 * Math.PI - Math.PI / 2;
const ssAngle = (ssMin / 1440) * 2 * Math.PI - Math.PI / 2;
ctx.beginPath();
ctx.arc(cx, cy, r - 30, 0, 2 * Math.PI);
ctx.strokeStyle = '#1e3a8a';
ctx.lineWidth = 12;
ctx.stroke();
ctx.beginPath();
if (srMin < ssMin) {
ctx.arc(cx, cy, r - 30, srAngle, ssAngle);
} else {
ctx.arc(cx, cy, r - 30, srAngle, 1.5 * Math.PI);
ctx.arc(cx, cy, r - 30, -Math.PI / 2, ssAngle);
}
ctx.strokeStyle = '#fbbf24';
ctx.lineWidth = 12;
ctx.stroke();
}
function drawCircularText(text, radius, centerAngle) {
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(centerAngle);
ctx.fillStyle = '#ccc';
ctx.font = '14px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const totalWidth = [...text].reduce(
(w, ch) => w + ctx.measureText(ch).width,
0
);
let currentAngle = -(totalWidth / radius) / 2;
[...text].forEach((char) => {
const w = ctx.measureText(char).width,
angle = w / radius;
ctx.rotate(currentAngle + angle / 2);
ctx.save();
ctx.translate(0, -radius);
ctx.fillText(char, 0, 0);
ctx.restore();
currentAngle = angle / 2;
});
ctx.restore();
}
function formatTimeDiff(mins) {
const h = Math.floor(mins / 60);
const m = mins % 60;
return (h > 0 ? h + 'h ' : '') + m + 'm';
}
// ✅ Fixed function: now shows ON/OFF and exact time
function getNextStateChange(now) {
const minsNow = now.getHours() * 60 + now.getMinutes();
let nextChange = null;
slots.forEach((slot) => {
if (!slot.enabled) return;
const s = toMin(slot.start),
e = toMin(slot.end);
const sDelta = (s - minsNow + 1440) % 1440;
const eDelta = (e - minsNow + 1440) % 1440;
if (nextChange === null || sDelta < nextChange.mins) {
nextChange = { mins: sDelta, type: 'ON', time: slot.start };
}
if (nextChange === null || eDelta < nextChange.mins) {
nextChange = { mins: eDelta, type: 'OFF', time: slot.end };
}
});
return nextChange
? `In ${formatTimeDiff(nextChange.mins)} → ${nextChange.type} at ${
nextChange.time
}`
: '--';
}
function drawCircle() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(cx, cy, r, 0, 2 * Math.PI);
ctx.strokeStyle = '#e0e0e0';
ctx.lineWidth = 24;
ctx.stroke();
drawDayNight();
const now = new Date();
const mins = now.getHours() * 60 + now.getMinutes();
let stateColor = 'grey';
let inAnySlot = false;
slots.forEach((slot) => {
if (!slot.enabled) return;
const sMin = toMin(slot.start),
eMin = toMin(slot.end);
const active = isInSlot(mins, slot);
if (active) inAnySlot = true;
const color = active ? 'limegreen' : 'red';
const startAngle = (sMin / 1440) * 2 * Math.PI - Math.PI / 2;
const endAngle = (eMin / 1440) * 2 * Math.PI - Math.PI / 2;
ctx.beginPath();
if (sMin < eMin) {
ctx.arc(cx, cy, r, startAngle, endAngle);
} else {
ctx.arc(cx, cy, r, startAngle, 1.5 * Math.PI);
ctx.arc(cx, cy, r, -Math.PI / 2, endAngle);
}
ctx.strokeStyle = color;
ctx.lineWidth = 24;
ctx.stroke();
});
drawMarkers();
if (slots.some((s) => s.enabled)) {
stateColor = inAnySlot ? 'limegreen' : 'red';
}
const angle = (mins / 1440) * 2 * Math.PI - Math.PI / 2;
const innerR = 40;
ctx.beginPath();
ctx.moveTo(
cx + Math.cos(angle) * innerR,
cy + Math.sin(angle) * innerR
);
ctx.lineTo(cx + Math.cos(angle) * r, cy + Math.sin(angle) * r);
ctx.strokeStyle = stateColor;
ctx.lineWidth = 4;
ctx.stroke();
document.getElementById('centerTime').textContent = toHHMM(mins);
const days = [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
];
const dayName = days[now.getDay()];
const options = { day: 'numeric', month: 'long', year: 'numeric' };
const dateStr = now.toLocaleDateString('nl-NL', options);
const weekNum = getISOWeek(now);
const nextChangeText = getNextStateChange(now);
document.getElementById(
'dateInfo'
).innerHTML = `${dayName}<br>${dateStr}<br>Week ${weekNum}<br>Next: ${nextChangeText}`;
document.getElementById(
'sunTimes'
).textContent = `SunRise: ${sunrise} | SunSet: ${sunset}`;
drawCircularText('Evening', r + 20, -Math.PI / 4);
drawCircularText('Night', r + 20, Math.PI / 4);
drawCircularText('Morning', r + 20, (3 * Math.PI) / 4);
drawCircularText('Afternoon', r + 20, (-3 * Math.PI) / 4);
}
function renderList() {
const ul = document.getElementById('slotList');
ul.innerHTML = '';
slots.forEach((slot) => {
const li = document.createElement('li');
const span = document.createElement('span');
span.textContent = `${slot.start} → ${slot.end}`;
span.style.color = slot.enabled ? '#eee' : '#666';
li.appendChild(span);
const ctr = document.createElement('div');
ctr.className = 'controls';
const tgl = document.createElement('button');
tgl.textContent = 'Toggle';
tgl.className = 'btn toggleBtn';
tgl.onclick = () => {
slot.enabled = !slot.enabled;
save();
renderList();
drawCircle();
};
const edit = document.createElement('button');
edit.textContent = 'Edit';
edit.className = 'btn editBtn';
edit.onclick = () => {
li.innerHTML = '';
const startInput = document.createElement('input');
startInput.type = 'time';
startInput.value = slot.start;
const endInput = document.createElement('input');
endInput.type = 'time';
endInput.value = slot.end;
const saveBtn = document.createElement('button');
saveBtn.textContent = 'Save';
saveBtn.className = 'btn toggleBtn';
saveBtn.onclick = () => {
slot.start = startInput.value;
slot.end = endInput.value;
save();
renderList();
drawCircle();
};
li.appendChild(startInput);
li.appendChild(endInput);
li.appendChild(saveBtn);
};
const del = document.createElement('button');
del.textContent = 'Delete';
del.className = 'btn delBtn';
del.onclick = () => {
slots = slots.filter((s) => s.id !== slot.id);
save();
renderList();
drawCircle();
};
ctr.appendChild(tgl);
ctr.appendChild(edit);
ctr.appendChild(del);
li.appendChild(ctr);
ul.appendChild(li);
});
}
document.getElementById('addForm').addEventListener('submit', (e) => {
e.preventDefault();
const st = document.getElementById('startTime').value;
const ed = document.getElementById('endTime').value;
if (!st || !ed) return;
slots.push({ id: Date.now(), start: st, end: ed, enabled: true });
save();
renderList();
drawCircle();
e.target.reset();
});
function fetchSunTimes(lat, lon) {
fetch(
`https://api.sunrise-sunset.org/json?lat=${lat}&lng=${lon}&formatted=0`
)
.then((res) => res.json())
.then((data) => {
if (data.status === 'OK') {
sunrise = toLocalHHMM(new Date(data.results.sunrise));
sunset = toLocalHHMM(new Date(data.results.sunset));
document.getElementById('geoMsg').textContent = '';
drawCircle();
}
})
.catch(() => {
document.getElementById('geoMsg').textContent =
'Kon sunrise/sunset niet ophalen.';
});
}
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(pos) => {
fetchSunTimes(pos.coords.latitude, pos.coords.longitude);
},
() => {
document.getElementById('geoMsg').textContent =
'Geen toegang tot locatie → standaardtijden gebruikt.';
}
);
} else {
document.getElementById('geoMsg').textContent =
'Geolocatie niet ondersteund door deze browser.';
}
function updateMoonPhase() {
const phases = [
'🌑',
'🌒',
'🌓',
'🌔',
'🌕',
'🌖',
'🌗',
'🌘',
];
const names = [
'Nieuwe Maan',
'Wassende Sikkel',
'Eerste Kwartier',
'Wassende Maan',
'Volle Maan',
'Afnemende Maan',
'Laatste Kwartier',
'Afnemende Sikkel',
];
const now = new Date();
const lp = new Date(Date.UTC(2000, 0, 6, 18, 14));
const diff = now - lp;
const days = diff / 1000 / 60 / 60 / 24;
const lunations = days / 29.53058867;
const index =
Math.floor((lunations - Math.floor(lunations)) * 8 + 0.5) % 8;
document.getElementById('moonIcon').textContent = phases[index];
document.getElementById('moonText').textContent = names[index];
}
load();
renderList();
drawCircle();
updateMoonPhase();
setInterval(() => {
drawCircle();
updateMoonPhase();
}, 15000);
</script>
</body>
</html>
/* Add your styles here */
// Add your code here