/* FluxTimer – lokaler Pausen-Timer ohne Server- oder Drittdienst. */
(function () {
'use strict';
if (window.MindfluxTimer) return;
var KEY = 'fluxtimer';
var MIN_INTERVAL = 5;
var MAX_INTERVAL = 240;
var DEFAULTS = { enabled:false, intervalMinutes:45, reminders:['aufstehen','trinken','augenpause'], customText:'', sound:false, notify:false, nextFireAt:null, lastFiredAt:null, reminderIndex:0 };
var LABELS = {
aufstehen:'88 mph erreicht – kurz aufstehen, McFly!',
trinken:'Großer Scott! Zeit, etwas zu trinken.',
essen:'Dein Fluxkompensator braucht einen Snack.',
augenpause:'Blick in die Zukunft: Gönn deinen Augen eine Pause.'
};
var state = load();
var ringing = false;
var intervalId;
function number(value, fallback) { var n = Number(value); return Number.isFinite(n) ? n : fallback; }
function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); }
function normalize(raw) {
raw = raw && typeof raw === 'object' ? raw : {};
var reminders = Array.isArray(raw.reminders) ? raw.reminders.filter(function (x) { return Object.prototype.hasOwnProperty.call(LABELS, x); }) : DEFAULTS.reminders.slice();
return {
enabled: raw.enabled === true,
intervalMinutes: clamp(Math.round(number(raw.intervalMinutes, 45)), MIN_INTERVAL, MAX_INTERVAL),
reminders: reminders,
customText: typeof raw.customText === 'string' ? raw.customText.trim().slice(0,120) : '',
sound: raw.sound === true,
notify: raw.notify === true,
nextFireAt: Number.isFinite(Number(raw.nextFireAt)) ? Number(raw.nextFireAt) : null,
lastFiredAt: Number.isFinite(Number(raw.lastFiredAt)) ? Number(raw.lastFiredAt) : null,
reminderIndex: Math.max(0, Math.round(number(raw.reminderIndex, 0)))
};
}
function load() { try { return normalize(JSON.parse(localStorage.getItem(KEY) || '{}')); } catch (e) { return normalize({}); } }
function save() { try { localStorage.setItem(KEY, JSON.stringify(state)); } catch (e) {} syncUi(); }
function schedule(from) { state.nextFireAt = (from || Date.now()) + state.intervalMinutes * 60000; ringing = false; save(); }
function setEnabled(enabled) { state.enabled = !!enabled; if (state.enabled) schedule(Date.now()); else { state.nextFireAt = null; ringing = false; closeOverlay(); save(); } }
function remainingMs(now) { return state.enabled && state.nextFireAt ? Math.max(0, state.nextFireAt - (now || Date.now())) : 0; }
function pad(n) { return String(n).padStart(2,'0'); }
function formatDuration(ms) { var seconds=Math.max(0,Math.ceil(ms/1000)); return pad(Math.floor(seconds/3600))+':'+pad(Math.floor((seconds%3600)/60))+':'+pad(seconds%60); }
function formatClock(ts) { if (!ts) return '--:--:--'; return new Date(ts).toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit',second:'2-digit'}); }
function reminderText() {
var pool = state.reminders.slice(); if (state.customText) pool.push('custom');
if (!pool.length) return 'Zeit für eine kurze Pause.';
var key = pool[state.reminderIndex % pool.length];
state.reminderIndex = (state.reminderIndex + 1) % pool.length;
return key === 'custom' ? state.customText : LABELS[key];
}
function ensureUi() {
if (!document.querySelector('.ft-popover')) {
var pop=document.createElement('section'); pop.className='ft-popover'; pop.setAttribute('aria-label','FluxTimer');
pop.innerHTML='
FLUXTIMER
NÄCHSTE PAUSE--:--:--
JETZT--:--:--
LETZTE PAUSE--:--:--
';
document.body.appendChild(pop);
}
if (!document.querySelector('.ft-overlay')) {
var overlay=document.createElement('div'); overlay.className='ft-overlay'; overlay.setAttribute('role','dialog'); overlay.setAttribute('aria-modal','true'); overlay.setAttribute('aria-labelledby','ft-title');
overlay.innerHTML='ZEITSCHALTKREISE AKTIVZeit für eine Pause
';
document.body.appendChild(overlay);
}
}
function positionPopover() {
var trigger=document.querySelector('[data-fluxtimer-toggle]'), pop=document.querySelector('.ft-popover'); if (!trigger || !pop) return;
var r=trigger.getBoundingClientRect(); pop.style.top=(r.bottom+8)+'px'; pop.style.left=Math.max(12,Math.min(window.innerWidth-pop.offsetWidth-12,r.right-pop.offsetWidth))+'px';
}
function togglePopover() { var pop=document.querySelector('.ft-popover'); if (!pop) return; pop.classList.toggle('open'); var open=pop.classList.contains('open'); var trigger=document.querySelector('[data-fluxtimer-toggle]'); if(trigger) trigger.setAttribute('aria-expanded',String(open)); if(open){positionPopover();syncUi();} }
function closePopover() { var pop=document.querySelector('.ft-popover'); if(pop) pop.classList.remove('open'); var trigger=document.querySelector('[data-fluxtimer-toggle]'); if(trigger) trigger.setAttribute('aria-expanded','false'); }
function closeOverlay() { var overlay=document.querySelector('.ft-overlay'); if(overlay) overlay.classList.remove('open'); }
function playSound() { if (!state.sound) return; try { var C=window.AudioContext||window.webkitAudioContext; if(!C)return; var ctx=new C(),o=ctx.createOscillator(),g=ctx.createGain(); o.frequency.value=523.25; g.gain.setValueAtTime(.05,ctx.currentTime); g.gain.exponentialRampToValueAtTime(.001,ctx.currentTime+.35); o.connect(g);g.connect(ctx.destination);o.start();o.stop(ctx.currentTime+.35); } catch(e){} }
function notify(text) { if (!state.notify || !('Notification' in window) || Notification.permission!=='granted') return; try { new Notification('Mindflux FluxTimer',{body:text,tag:'mindflux-fluxtimer'}); } catch(e){} }
function fire(testOnly) { ensureUi(); if(ringing&&!testOnly)return; ringing=true; var text=reminderText(); state.lastFiredAt=Date.now(); save(); var msg=document.querySelector('[data-ft-message]'); if(msg)msg.textContent=text; document.querySelector('.ft-overlay').classList.add('open'); playSound(); notify(text); }
function done() { closeOverlay(); schedule(Date.now()); }
function snooze() { closeOverlay(); state.nextFireAt=Date.now()+5*60000; ringing=false; save(); }
function tick() { var now=Date.now(); if(state.enabled&&state.nextFireAt&&now>=state.nextFireAt)fire(false); syncUi(now); }
function syncUi(now) {
now=now||Date.now(); var countdown=state.enabled?formatDuration(remainingMs(now)):'PAUSIERT';
document.querySelectorAll('[data-ft-countdown]').forEach(function(el){el.textContent=countdown;});
document.querySelectorAll('[data-ft-now]').forEach(function(el){el.textContent=formatClock(now);});
document.querySelectorAll('[data-ft-last]').forEach(function(el){el.textContent=formatClock(state.lastFiredAt);});
document.querySelectorAll('[data-ft-status]').forEach(function(el){el.textContent=state.enabled?'Aktiv · '+state.intervalMinutes+' Minuten':'Der Timer ist deaktiviert.';});
document.querySelectorAll('[data-ft-quick-toggle]').forEach(function(el){el.textContent=state.enabled?'Timer pausieren':'Timer aktivieren';});
document.querySelectorAll('[data-fluxtimer-toggle]').forEach(function(el){el.classList.toggle('ft-active',state.enabled);});
var fields=document.querySelector('[data-fluxtimer-settings]'); if(fields){
var enabled=fields.querySelector('[data-ft-field="enabled"]'); if(enabled&&document.activeElement!==enabled)enabled.checked=state.enabled;
var interval=fields.querySelector('[data-ft-field="intervalMinutes"]'); if(interval&&document.activeElement!==interval)interval.value=state.intervalMinutes;
var custom=fields.querySelector('[data-ft-field="customText"]'); if(custom&&document.activeElement!==custom)custom.value=state.customText;
['sound','notify'].forEach(function(k){var el=fields.querySelector('[data-ft-field="'+k+'"]');if(el&&document.activeElement!==el)el.checked=state[k];});
fields.querySelectorAll('[data-ft-reminder]').forEach(function(el){if(document.activeElement!==el)el.checked=state.reminders.indexOf(el.value)>=0;});
}
}
function readSettings(container) {
var interval=container.querySelector('[data-ft-field="intervalMinutes"]'), previousInterval=state.intervalMinutes; state.intervalMinutes=clamp(Math.round(number(interval.value,45)),MIN_INTERVAL,MAX_INTERVAL); interval.value=state.intervalMinutes;
state.customText=container.querySelector('[data-ft-field="customText"]').value.trim().slice(0,120);
state.sound=container.querySelector('[data-ft-field="sound"]').checked;
state.notify=container.querySelector('[data-ft-field="notify"]').checked;
state.reminders=Array.prototype.map.call(container.querySelectorAll('[data-ft-reminder]:checked'),function(el){return el.value;});
if(state.enabled&&previousInterval!==state.intervalMinutes)schedule(Date.now());else save();
var status=container.querySelector('[data-ft-save-status]');if(status){status.textContent='Auf diesem Gerät gespeichert.';setTimeout(function(){status.textContent='Wird nur auf diesem Gerät gespeichert.';},1800);}
}
function init() {
if(location.pathname==='/'||location.pathname==='/login'||location.pathname==='/register')return;
ensureUi();
if(state.enabled&&!state.nextFireAt)schedule(Date.now());
document.addEventListener('click',function(e){
if(e.target.closest('[data-fluxtimer-toggle]')){togglePopover();return;}
if(e.target.closest('[data-ft-close]')){closePopover();return;}
if(e.target.closest('[data-ft-quick-toggle]')){setEnabled(!state.enabled);return;}
if(e.target.closest('[data-ft-done]')){done();return;}
if(e.target.closest('[data-ft-snooze]')){snooze();return;}
if(e.target.closest('[data-ft-test]')){fire(true);return;}
var pop=e.target.closest('.ft-popover'); if(!pop&&!e.target.closest('[data-fluxtimer-toggle]'))closePopover();
});
var settings=document.querySelector('[data-fluxtimer-settings]'); if(settings){
settings.addEventListener('change',function(e){
if(e.target.matches('[data-ft-field="enabled"]')){setEnabled(e.target.checked);return;}
if(e.target.matches('[data-ft-field="notify"]')&&e.target.checked&&'Notification' in window&&Notification.permission==='default') Notification.requestPermission().then(function(p){if(p!=='granted'){e.target.checked=false;state.notify=false;save();}});
readSettings(settings);
});
settings.addEventListener('input',function(e){if(e.target.matches('[data-ft-field="customText"],[data-ft-field="intervalMinutes"]'))readSettings(settings);});
}
window.addEventListener('storage',function(e){if(e.key===KEY){state=load();syncUi();}});
window.addEventListener('resize',positionPopover);
intervalId=window.setInterval(tick,1000); tick();
}
window.MindfluxTimer={getState:function(){return JSON.parse(JSON.stringify(state));},setEnabled:setEnabled,remainingMs:remainingMs,formatDuration:formatDuration,test:function(){fire(true);},reset:function(){state=normalize({});save();}};
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init);else init();
})();