This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
/* FluxTimer – Zeitschaltkreis-Optik, rein clientseitig. */
|
||||
.ft-bell { position:relative; }
|
||||
.ft-bell-dot { position:absolute; right:5px; top:5px; width:7px; height:7px; border-radius:50%; background:var(--muted); border:1px solid var(--surface); }
|
||||
.ft-bell.ft-active .ft-bell-dot { background:var(--fluxtimer-primary); box-shadow:0 0 8px var(--fluxtimer-primary); }
|
||||
.ft-popover { position:fixed; z-index:1600; width:min(340px,calc(100vw - 24px)); padding:1rem; border:1px solid color-mix(in srgb,var(--fluxtimer-secondary) 55%,var(--border)); border-radius:16px; background:var(--panel); box-shadow:0 18px 55px rgba(0,0,0,.38),0 0 24px color-mix(in srgb,var(--fluxtimer-secondary) 22%,transparent); display:none; }
|
||||
.ft-popover.open { display:block; animation:ft-in .18s ease-out; }
|
||||
.ft-popover-head,.ft-popover-actions,.ft-settings-head,.ft-settings-actions,.ft-options { display:flex; align-items:center; gap:.7rem; }
|
||||
.ft-popover-head { justify-content:space-between; margin-bottom:.75rem; }
|
||||
.ft-popover-head b { color:var(--heading); letter-spacing:.08em; }
|
||||
.ft-close { border:0; background:none; color:var(--muted); cursor:pointer; font-size:1.25rem; }
|
||||
.ft-circuit { display:grid; gap:.55rem; padding:.8rem; border-radius:12px; background:var(--fluxtimer-display); border:1px solid color-mix(in srgb,var(--fluxtimer-primary) 45%,var(--border)); box-shadow:inset 0 0 22px rgba(0,0,0,.28); }
|
||||
.ft-row { display:grid; grid-template-columns:1fr auto; align-items:center; gap:.7rem; }
|
||||
.ft-row span { font-size:.65rem; letter-spacing:.12em; color:var(--muted); }
|
||||
.ft-row strong,.ft-preview strong { color:var(--fluxtimer-segment); font-family:"Courier New",monospace; font-variant-numeric:tabular-nums; letter-spacing:.08em; text-shadow:0 0 9px color-mix(in srgb,var(--fluxtimer-segment) 58%,transparent); }
|
||||
.ft-row.primary strong { font-size:1.3rem; }
|
||||
.ft-popover-status { color:var(--muted); font-size:.8rem; margin:.7rem 0; }
|
||||
.ft-popover-actions { justify-content:space-between; flex-wrap:wrap; }
|
||||
.ft-mini-toggle { border:1px solid var(--border-accent); background:var(--surface-2); color:var(--text); border-radius:9px; padding:.45rem .7rem; cursor:pointer; font-weight:700; }
|
||||
.ft-settings-head { justify-content:space-between; align-items:flex-start; margin-bottom:1rem; }
|
||||
.ft-settings-head p { color:var(--muted); font-size:.88rem; margin-top:.25rem; }
|
||||
.ft-settings-grid { display:grid; grid-template-columns:1fr 220px; gap:1rem; align-items:stretch; }
|
||||
.ft-preview { display:flex; flex-direction:column; justify-content:center; align-items:center; padding:.8rem; border-radius:12px; background:var(--fluxtimer-display); border:1px solid color-mix(in srgb,var(--fluxtimer-primary) 42%,var(--border)); }
|
||||
.ft-preview span { color:var(--muted); font-size:.65rem; letter-spacing:.13em; }
|
||||
.ft-preview strong { font-size:1.55rem; margin-top:.25rem; }
|
||||
.ft-switch { display:inline-flex; align-items:center; gap:.5rem; cursor:pointer; color:var(--text); }
|
||||
.ft-switch input { position:absolute; opacity:0; }
|
||||
.ft-switch span { width:44px; height:24px; border-radius:99px; background:var(--surface-2); border:1px solid var(--border); position:relative; transition:.2s; }
|
||||
.ft-switch span::after { content:""; position:absolute; width:18px; height:18px; left:2px; top:2px; border-radius:50%; background:var(--muted); transition:.2s; }
|
||||
.ft-switch input:checked + span { background:color-mix(in srgb,var(--fluxtimer-secondary) 34%,var(--surface)); border-color:var(--fluxtimer-secondary); }
|
||||
.ft-switch input:checked + span::after { transform:translateX(20px); background:var(--fluxtimer-secondary); box-shadow:0 0 8px var(--fluxtimer-secondary); }
|
||||
.ft-reminders { display:flex; flex-wrap:wrap; gap:.2rem 1rem; border:0; margin:.3rem 0 .8rem; }
|
||||
.ft-reminders legend { width:100%; margin-bottom:.25rem; }
|
||||
.ft-options { flex-wrap:wrap; margin:.25rem 0 .8rem; }
|
||||
.ft-settings-actions { justify-content:space-between; flex-wrap:wrap; }
|
||||
.ft-overlay { position:fixed; inset:0; z-index:1900; display:none; place-items:center; padding:1rem; background:rgba(5,3,2,.76); backdrop-filter:blur(7px); }
|
||||
.ft-overlay.open { display:grid; }
|
||||
.ft-overlay::before { content:""; position:absolute; inset:0; pointer-events:none; background:linear-gradient(115deg,transparent 35%,rgba(255,255,255,.7) 49%,transparent 55%); transform:translateX(-120%); }
|
||||
.ft-overlay.open::before { animation:ft-flash .7s ease-out; }
|
||||
.ft-modal { position:relative; width:min(560px,100%); text-align:center; padding:1.5rem; border-radius:20px; background:var(--panel); border:1px solid var(--fluxtimer-secondary); box-shadow:0 0 50px color-mix(in srgb,var(--fluxtimer-secondary) 35%,transparent); }
|
||||
.ft-modal .ft-kicker { color:var(--fluxtimer-secondary); text-transform:uppercase; letter-spacing:.18em; font-size:.72rem; font-weight:800; }
|
||||
.ft-modal h2 { color:var(--heading); font-size:1.7rem; margin:.7rem 0 .4rem; }
|
||||
.ft-modal p { color:var(--muted); line-height:1.55; }
|
||||
.ft-modal-actions { display:flex; justify-content:center; flex-wrap:wrap; gap:.7rem; margin-top:1.2rem; }
|
||||
@keyframes ft-in { from{opacity:0;transform:translateY(-6px)} to{opacity:1;transform:none} }
|
||||
@keyframes ft-flash { to{transform:translateX(120%)} }
|
||||
@media (max-width:620px){ .ft-settings-grid{grid-template-columns:1fr}.ft-settings-head{align-items:stretch;flex-direction:column}.ft-popover{left:12px!important;right:12px!important;top:64px!important;width:auto} }
|
||||
@media (prefers-reduced-motion:reduce){ .ft-popover.open,.ft-overlay.open::before{animation:none} }
|
||||
@@ -14,6 +14,12 @@
|
||||
--secondary:#2d9cce; --secondary-2:#5cc4e8;
|
||||
--text:#ece3d2; --muted:#8c8170; --heading:#ffffff;
|
||||
--success:#2d9cce; --danger:#ff4d6d;
|
||||
--esc-accent:#f0a81a; --esc-glow:rgba(240,168,26,.48);
|
||||
--bh-accent:#d94b64; --bh-glow:rgba(217,75,100,.42);
|
||||
--sc-accent:#38b9d6; --sc-glow:rgba(56,185,214,.40);
|
||||
--nw-accent:#4f8ff7; --nw-glow:rgba(79,143,247,.42);
|
||||
--fluxtimer-primary:#ff5a5f; --fluxtimer-secondary:#f0a81a;
|
||||
--fluxtimer-display:#170704; --fluxtimer-segment:#ff765f;
|
||||
--glow-s:rgba(240,168,26,.55); --glow-w:rgba(240,168,26,.16);
|
||||
--g-soft:0 0 18px var(--glow-w);
|
||||
}
|
||||
@@ -25,6 +31,12 @@
|
||||
--secondary:#1f86b8; --secondary-2:#2d9cce;
|
||||
--text:#33271c; --muted:#8a7d6c; --heading:#241a12;
|
||||
--success:#1f86b8; --danger:#be123c;
|
||||
--esc-accent:#c96a12; --esc-glow:rgba(201,106,18,.30);
|
||||
--bh-accent:#b42342; --bh-glow:rgba(180,35,66,.26);
|
||||
--sc-accent:#147f9e; --sc-glow:rgba(20,127,158,.26);
|
||||
--nw-accent:#2868c7; --nw-glow:rgba(40,104,199,.26);
|
||||
--fluxtimer-primary:#b42342; --fluxtimer-secondary:#c96a12;
|
||||
--fluxtimer-display:#fff5ec; --fluxtimer-segment:#a93131;
|
||||
--glow-s:rgba(214,81,22,.40); --glow-w:rgba(214,81,22,.12);
|
||||
--g-soft:0 6px 18px var(--glow-w);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/* 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='<div class="ft-popover-head"><b>FLUXTIMER</b><button class="ft-close" type="button" data-ft-close aria-label="Schließen">×</button></div><div class="ft-circuit"><div class="ft-row primary"><span>NÄCHSTE PAUSE</span><strong data-ft-countdown>--:--:--</strong></div><div class="ft-row"><span>JETZT</span><strong data-ft-now>--:--:--</strong></div><div class="ft-row"><span>LETZTE PAUSE</span><strong data-ft-last>--:--:--</strong></div></div><p class="ft-popover-status" data-ft-status></p><div class="ft-popover-actions"><button class="ft-mini-toggle" type="button" data-ft-quick-toggle></button><a class="mf-btn outline" href="/profile/settings">Einstellungen</a></div>';
|
||||
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='<div class="ft-modal"><span class="ft-kicker">ZEITSCHALTKREISE AKTIV</span><h2 id="ft-title">Zeit für eine Pause</h2><p data-ft-message></p><div class="ft-modal-actions"><button class="mf-btn outline" type="button" data-ft-snooze>+5 Minuten</button><button class="mf-btn" type="button" data-ft-done>Pause erledigt</button></div></div>';
|
||||
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();
|
||||
})();
|
||||
@@ -8,6 +8,23 @@
|
||||
|
||||
apply(read()); // früh -> kein Aufblitzen
|
||||
|
||||
// Globale, rein clientseitige Komfortfunktionen. FluxTimer bleibt auf öffentlichen
|
||||
// Seiten inaktiv, läuft bei aktivierter Einstellung aber über Seitennavigationen weiter.
|
||||
if (!document.querySelector('link[data-fluxtimer]')) {
|
||||
var timerCss = document.createElement('link');
|
||||
timerCss.rel = 'stylesheet';
|
||||
timerCss.href = '/css/fluxtimer.css?v=1';
|
||||
timerCss.setAttribute('data-fluxtimer', '');
|
||||
document.head.appendChild(timerCss);
|
||||
}
|
||||
if (!document.querySelector('script[data-fluxtimer]')) {
|
||||
var timerScript = document.createElement('script');
|
||||
timerScript.src = '/js/fluxtimer.js?v=1';
|
||||
timerScript.defer = true;
|
||||
timerScript.setAttribute('data-fluxtimer', '');
|
||||
document.head.appendChild(timerScript);
|
||||
}
|
||||
|
||||
var SUN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>';
|
||||
var MOON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>';
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<svg viewBox="0 0 200 50" fill="none" stroke="var(--accent)" stroke-width="2"><path d="M2 26h40l8-18 10 34 9-26 7 14h26l6-12 9 22 7-24h59"/></svg>
|
||||
</div>
|
||||
<div class="mf-tools">
|
||||
<button class="mf-iconbtn" aria-label="Benachrichtigungen"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></button>
|
||||
<button class="mf-iconbtn ft-bell" type="button" data-fluxtimer-toggle aria-label="FluxTimer öffnen" aria-expanded="false"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg><span class="ft-bell-dot" aria-hidden="true"></span></button>
|
||||
<div class="mf-search"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4-4"/></svg><input placeholder="Suche..."></div>
|
||||
<button class="mf-theme"></button>
|
||||
<form th:action="@{/logout}" method="post" style="display:inline">
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
|
||||
<h1>Datenschutzerklärung</h1>
|
||||
<p class="stand">Stand: Juni 2026</p>
|
||||
<p class="stand">Stand: Juli 2026</p>
|
||||
|
||||
<div class="dev-note">
|
||||
Hinweis: Mindflux befindet sich in aktiver Entwicklung und ist derzeit nicht für die öffentliche
|
||||
@@ -87,6 +87,17 @@
|
||||
im Rahmen von Tests zurückgesetzt werden.
|
||||
</p>
|
||||
|
||||
<h2>6a. Lokale Einstellungen und FluxTimer</h2>
|
||||
<p>
|
||||
Theme-Auswahl und FluxTimer-Einstellungen werden ausschließlich im lokalen Browserspeicher
|
||||
(<code>localStorage</code>) des verwendeten Geräts abgelegt. Dazu gehören Aktivierungsstatus,
|
||||
Intervall, ausgewählte Erinnerungsarten, optionaler eigener Erinnerungstext sowie der nächste
|
||||
und letzte Auslösezeitpunkt. Diese Angaben werden nicht an den Mindflux-Server übertragen und
|
||||
können durch Löschen der Website-Daten im Browser entfernt werden. Browser-Benachrichtigungen
|
||||
werden nur nach ausdrücklicher Freigabe lokal erzeugt; es gibt keinen Web-Push- oder externen
|
||||
Benachrichtigungsdienst.
|
||||
</p>
|
||||
|
||||
<h2>7. Empfänger und Hosting</h2>
|
||||
<p>
|
||||
Die Daten werden auf einer vom Anbieter betriebenen Server-Infrastruktur verarbeitet und nicht zu
|
||||
|
||||
@@ -52,6 +52,50 @@
|
||||
<small class="mf-hint">Die Auswahl gilt sofort und wird auf diesem Gerät gespeichert.</small>
|
||||
</section>
|
||||
|
||||
<!-- FluxTimer: vollständig lokal im Browser, keine Serverdaten -->
|
||||
<section class="mf-panel ft-settings" style="margin-bottom:1.2rem;" data-fluxtimer-settings>
|
||||
<div class="ft-settings-head">
|
||||
<div>
|
||||
<h2 class="mf-section-title">⏱ FluxTimer</h2>
|
||||
<p>Erinnert dich während des Lernens ans Aufstehen, Trinken oder Durchatmen.</p>
|
||||
</div>
|
||||
<label class="ft-switch">
|
||||
<input id="ft-enabled" type="checkbox" data-ft-field="enabled">
|
||||
<span aria-hidden="true"></span><b>Aktiv</b>
|
||||
</label>
|
||||
</div>
|
||||
<div class="ft-settings-grid">
|
||||
<div class="mf-field">
|
||||
<label class="mf-label" for="ft-interval">Intervall in Minuten</label>
|
||||
<input class="mf-input" id="ft-interval" type="number" min="5" max="240" step="1" value="45" data-ft-field="intervalMinutes">
|
||||
<small class="mf-hint">Zwischen 5 und 240 Minuten.</small>
|
||||
</div>
|
||||
<div class="ft-preview" aria-live="polite">
|
||||
<span>NÄCHSTE PAUSE</span>
|
||||
<strong data-ft-countdown>--:--:--</strong>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="ft-reminders">
|
||||
<legend class="mf-label">Erinnerungen rotieren</legend>
|
||||
<label class="mf-check"><input type="checkbox" value="aufstehen" data-ft-reminder> Aufstehen 🧍</label>
|
||||
<label class="mf-check"><input type="checkbox" value="trinken" data-ft-reminder> Trinken 💧</label>
|
||||
<label class="mf-check"><input type="checkbox" value="essen" data-ft-reminder> Essen 🍎</label>
|
||||
<label class="mf-check"><input type="checkbox" value="augenpause" data-ft-reminder> Augenpause 👀</label>
|
||||
</fieldset>
|
||||
<div class="mf-field">
|
||||
<label class="mf-label" for="ft-custom">Eigener Erinnerungstext</label>
|
||||
<input class="mf-input" id="ft-custom" maxlength="120" placeholder="Zum Beispiel: Schultern lockern" data-ft-field="customText">
|
||||
</div>
|
||||
<div class="ft-options">
|
||||
<label class="mf-check"><input type="checkbox" data-ft-field="notify"> Browser-Benachrichtigung</label>
|
||||
<label class="mf-check"><input type="checkbox" data-ft-field="sound"> Dezenter Signalton</label>
|
||||
</div>
|
||||
<div class="ft-settings-actions">
|
||||
<button class="mf-btn outline" type="button" data-ft-test>Probelauf</button>
|
||||
<small class="mf-hint" data-ft-save-status>Wird nur auf diesem Gerät gespeichert.</small>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Passwort ändern -->
|
||||
<form th:action="@{/profile/change-password}" method="post" class="mf-panel" style="margin-bottom:1.2rem;">
|
||||
<h2 class="mf-section-title">🔒 Passwort ändern</h2>
|
||||
|
||||
Reference in New Issue
Block a user