<!doctype html>
<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; }
.daysContainer { display:flex; gap:4px; margin:4px 0; }
.dayCheckbox { display:flex; flex-direction:column; align-items:center; }
.dayCheckbox input { margin:0; }
.dayCheckbox label { font-size:10px; color:#ccc; }
.slotInfo { display:flex; flex-direction:column; }
</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>
<div class="daysContainer">
<div class="dayCheckbox"><input type="checkbox" id="dayMon" checked><label>M</label></div>
<div class="dayCheckbox"><input type="checkbox" id="dayTue" checked><label>T</label></div>
<div class="dayCheckbox"><input type="checkbox" id="dayWed" checked><label>W</label></div>
<div class="dayCheckbox"><input type="checkbox" id="dayThu" checked><label>T</label></div>
<div class="dayCheckbox"><input type="checkbox" id="dayFri" checked><label>F</label></div>
<div class="dayCheckbox"><input type="checkbox" id="daySat" checked><label>S</label></div>
<div class="dayCheckbox"><input type="checkbox" id="daySun" checked><label>S</label></div>
</div>
<button type="submit" class="addBtn">Add TimeSlot</button>
</form>
<ul id="slotList"></ul>
<br><br><br>
now send it JSon to ESP8266 ESP32 <br>
230vac wall plug
<br><br>
<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_v8";
const dayLetters = ["M", "T", "W", "T", "F", "S", "S"];
const dayNames = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
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() {
slots.sort((a, b) => toMin(a.start) - toMin(b.start));
localStorage.setItem(STORAGE_KEY, JSON.stringify(slots));
}
function load(){
const data = localStorage.getItem(STORAGE_KEY);
if(data){
slots = JSON.parse(data);
// Ensure all slots have days array (for backward compatibility)
slots.forEach(slot => {
if (!slot.days) {
slot.days = [true, true, true, true, true, true, true]; // All days enabled by default
}
});
}
else{
slots = [
{id:1,start:"06:00",end:"08:00",enabled:true,days:[true,true,true,true,true,true,true]},
{id:2,start:"12:00",end:"13:30",enabled:true,days:[true,true,true,true,true,true,true]},
{id:3,start:"19:00",end:"22:00",enabled:true,days:[true,true,true,true,true,true,true]}
];
save();
}
}
function isSlotActiveToday(slot) {
if (!slot.enabled) return false;
const today = new Date().getDay();
// Convert Sunday (0) to index 6, Monday (1) to 0, etc.
const dayIndex = today === 0 ? 6 : today - 1;
return slot.days[dayIndex];
}
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 chars = [...text];
const totalWidth = chars.reduce((w, ch) => w + ctx.measureText(ch).width, 0);
let offset = -(totalWidth / radius) / 2;
chars.forEach(char => {
const w = ctx.measureText(char).width;
const angle = w / radius;
ctx.rotate(offset + angle/2);
ctx.save();
ctx.translate(0, -radius);
ctx.fillText(char, 0, 0);
ctx.restore();
offset = angle/2;
});
ctx.restore();
}
function formatTimeDiff(mins){
const h=Math.floor(mins/60); const m=mins%60;
return (h>0 ? h+"h ":"")+m+"m";
}
function getNextStateChange(now) {
const minsNow = now.getHours()*60 + now.getMinutes();
let nextChange = null;
slots.forEach(slot=>{
if(!slot.enabled) return;
const today = now.getDay();
const dayIndex = today === 0 ? 6 : today - 1;
const isActiveToday = slot.days[dayIndex];
const s=toMin(slot.start), e=toMin(slot.end);
const sDelta=(s-minsNow+1440)%1440;
const eDelta=(e-minsNow+1440)%1440;
if(isActiveToday) {
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(!isSlotActiveToday(slot)) 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=>isSlotActiveToday(s))){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.sort((a, b) => toMin(a.start) - toMin(b.start));
slots.forEach(slot=>{
const li=document.createElement("li");
const slotInfo = document.createElement("div");
slotInfo.className = "slotInfo";
const timeSpan=document.createElement("span");
timeSpan.textContent = `${slot.start} → ${slot.end}`;
timeSpan.style.color = slot.enabled ? "#eee" : "#666";
slotInfo.appendChild(timeSpan);
// Days checkboxes
const daysContainer = document.createElement("div");
daysContainer.className = "daysContainer";
dayLetters.forEach((letter, index) => {
const dayCheckbox = document.createElement("div");
dayCheckbox.className = "dayCheckbox";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = slot.days[index];
checkbox.disabled = !slot.enabled;
checkbox.onchange = () => {
slot.days[index] = checkbox.checked;
save();
drawCircle();
};
const label = document.createElement("label");
label.textContent = letter;
label.style.color = slot.enabled ? "#ccc" : "#666";
dayCheckbox.appendChild(checkbox);
dayCheckbox.appendChild(label);
daysContainer.appendChild(dayCheckbox);
});
slotInfo.appendChild(daysContainer);
li.appendChild(slotInfo);
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;
// Days checkboxes in edit mode
const editDaysContainer = document.createElement("div");
editDaysContainer.className = "daysContainer";
dayLetters.forEach((letter, index) => {
const dayCheckbox = document.createElement("div");
dayCheckbox.className = "dayCheckbox";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = slot.days[index];
const label = document.createElement("label");
label.textContent = letter;
dayCheckbox.appendChild(checkbox);
dayCheckbox.appendChild(label);
editDaysContainer.appendChild(dayCheckbox);
});
const saveBtn = document.createElement("button");
saveBtn.textContent = "Save"; saveBtn.className="btn toggleBtn";
saveBtn.onclick=()=>{
slot.start = startInput.value;
slot.end = endInput.value;
// Update days from edit checkboxes
const editCheckboxes = editDaysContainer.querySelectorAll("input[type='checkbox']");
editCheckboxes.forEach((checkbox, index) => {
slot.days[index] = checkbox.checked;
});
save(); renderList(); drawCircle();
};
li.appendChild(startInput);
li.appendChild(endInput);
li.appendChild(editDaysContainer);
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;
// Get selected days from form
const days = [
document.getElementById("dayMon").checked,
document.getElementById("dayTue").checked,
document.getElementById("dayWed").checked,
document.getElementById("dayThu").checked,
document.getElementById("dayFri").checked,
document.getElementById("daySat").checked,
document.getElementById("daySun").checked
];
slots.push({id:Date.now(),start:st,end:ed,enabled:true,days:days});
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