first commit

This commit is contained in:
Patsy 2026-08-07 16:40:47 +02:00
parent 5e6763aa83
commit 4a1418a16a
4 changed files with 1236 additions and 0 deletions

44
claude.md Normal file
View file

@ -0,0 +1,44 @@
### ✅ **Prompt für eine Coding KI (z. B. ChatGPT, Codex, GitHub Copilot, etc.)**
> "Erstelle eine **HTML-Datei**, die eine **ADHS-freundliche, visuell ansprechende und strukturierte Aufgabenliste** darstellt. Die Liste sollte folgende Merkmale aufweisen:
>
> 1. **Klarer visueller Aufbau**: Einfache, klare Liste mit Nummerierung oder Symbolen (z. B. ✅, ⚠️, 🎯).
>
> 2. **Farbliche Unterstützung**: Nutze **pastellige oder beruhigende Farben** (z. B. Lavendel, Blau, Gelb, Lila) für die verschiedenen Status (erledigt, in Bearbeitung, nicht begonnen).
>
> 3. **Interaktivität**: Mögliche Option, den Status der Aufgaben zu aktualisieren (z. B. von „in Bearbeitung“ zu „erledigt“).
>
> 4. **Flexibilität**: Mögliche Option, Aufgaben zu hinzufügen, zu löschen oder zu verschieben.
>
> 5. **Erinnerungen**: Ein **Timer oder Erinnerung** für jede Aufgabe.
>
> 6. **Zielorientierte Darstellung**: Zeige den Fortschritt in Form eines **Kreises oder Balkens**, um den Überblick zu behalten.
>
> 7. **Dark Mode**: Option, den Hintergrund auf **Dark Mode** zu wechseln, um Stress zu reduzieren.
>
> 8. **Responsive Design**: Die Liste sollte auf allen Bildschirmgrößen gut funktionieren.
>
> 9. **Farbpalette**: Verwende die folgende Farbpalette:
> * **Lavendel** (hauptsächlich für wichtige Aufgaben)
>
> * **Blau** (für erledigte Aufgaben)
>
> * **Gelb** (für dringende Aufgaben)
>
> * **Weiß oder Beige** (Hintergrundfarbe)
>
> * **Orange oder Lila** (für Aufgaben, die noch nicht begonnen wurden)
>
>
> Erstelle die HTML-Datei mit **CSS** und optional **JavaScript**, um interaktive Funktionen zu ermöglichen. Die Liste sollte sich automatisch aktualisieren, sobald eine Aufgabe erledigt wird. Mögliche Optionen:
>
> * **Kreis-Modell**
>
> * **Balken-Modell**
>
> * **Karteähnliche Struktur** (visuelle Karte)
>
> * **Farbe für Priorisierung** (z. B. Rot = dringend, Gelb = wichtig, Grün = erledigt)
>
>
> Erstelle eine **visuelle Karte** mit Farben und Symbolen, die die Aufgaben darstellen. Die Aufgabenliste sollte sich **im Laufe der Zeit aktualisieren** und sich anpassen, sobald neue Aufgaben hinzugefügt oder bestehende bearbeitet werden."

990
index.html Normal file
View file

@ -0,0 +1,990 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Steady — a calmer task list</title>
<style>
:root{
--bg:#F6F1E7;
--surface:#FFFFFF;
--surface-2:#EFE7D8;
--ink:#2B2733;
--ink-muted:#746C82;
--border:#E4D9C4;
--accent:#7C6FE0;
--accent-ink:#FFFFFF;
--focus-ring:#7C6FE0;
--not-started-bg:#F3A65B; --not-started-ink:#5C3306;
--progress-bg:#7FC8B8; --progress-ink:#0F3A31;
--done-bg:#8FC1E8; --done-ink:#16374D;
--urgent-bg:#F6CC4D; --urgent-ink:#5B4300;
--important-bg:#B9A6EE; --important-ink:#35235E;
--overdue-bg:#EFA396; --overdue-ink:#5C1C10;
--celebrate:#7FCB93;
--shadow: 0 1px 2px rgba(43,39,51,0.06), 0 4px 14px rgba(43,39,51,0.06);
}
@media (prefers-color-scheme: dark){
:root{
--bg:#1B1720; --surface:#241F2C; --surface-2:#2E2736;
--ink:#EDE7F5; --ink-muted:#A79DBA; --border:#3A3247;
--accent:#A79BFF; --accent-ink:#1B1720; --focus-ring:#A79BFF;
--not-started-bg:#7A4C22; --not-started-ink:#FBE3C7;
--progress-bg:#316B5F; --progress-ink:#D8F5EE;
--done-bg:#2F5C82; --done-ink:#DCEEFB;
--urgent-bg:#8A6E1E; --urgent-ink:#FBF0CE;
--important-bg:#5C4A99; --important-ink:#EDE6FB;
--overdue-bg:#8A3B30; --overdue-ink:#FBE1DC;
--celebrate:#4FAE72;
--shadow: 0 1px 2px rgba(0,0,0,0.3), 0 4px 18px rgba(0,0,0,0.35);
}
}
:root[data-theme="dark"]{
--bg:#1B1720; --surface:#241F2C; --surface-2:#2E2736;
--ink:#EDE7F5; --ink-muted:#A79DBA; --border:#3A3247;
--accent:#A79BFF; --accent-ink:#1B1720; --focus-ring:#A79BFF;
--not-started-bg:#7A4C22; --not-started-ink:#FBE3C7;
--progress-bg:#316B5F; --progress-ink:#D8F5EE;
--done-bg:#2F5C82; --done-ink:#DCEEFB;
--urgent-bg:#8A6E1E; --urgent-ink:#FBF0CE;
--important-bg:#5C4A99; --important-ink:#EDE6FB;
--overdue-bg:#8A3B30; --overdue-ink:#FBE1DC;
--celebrate:#4FAE72;
--shadow: 0 1px 2px rgba(0,0,0,0.3), 0 4px 18px rgba(0,0,0,0.35);
}
:root[data-theme="light"]{
--bg:#F6F1E7; --surface:#FFFFFF; --surface-2:#EFE7D8;
--ink:#2B2733; --ink-muted:#746C82; --border:#E4D9C4;
--accent:#7C6FE0; --accent-ink:#FFFFFF; --focus-ring:#7C6FE0;
--not-started-bg:#F3A65B; --not-started-ink:#5C3306;
--progress-bg:#7FC8B8; --progress-ink:#0F3A31;
--done-bg:#8FC1E8; --done-ink:#16374D;
--urgent-bg:#F6CC4D; --urgent-ink:#5B4300;
--important-bg:#B9A6EE; --important-ink:#35235E;
--overdue-bg:#EFA396; --overdue-ink:#5C1C10;
--celebrate:#7FCB93;
--shadow: 0 1px 2px rgba(43,39,51,0.06), 0 4px 14px rgba(43,39,51,0.06);
}
*{box-sizing:border-box;}
html,body{margin:0;padding:0;}
body{
background:var(--bg); color:var(--ink);
font-family: ui-rounded, "SF Pro Rounded", -apple-system, "Segoe UI", system-ui, Roboto, sans-serif;
font-size:16px; line-height:1.5;
-webkit-font-smoothing:antialiased;
min-height:100vh;
}
body.dyslexia-mode{
font-family: Verdana, Tahoma, "Trebuchet MS", Arial, sans-serif;
letter-spacing: 0.02em; word-spacing: 0.08em; line-height: 1.7;
}
body.dyslexia-mode .task-text{ letter-spacing: 0.02em; word-spacing: 0.08em; }
body.dyslexia-mode .brand h1{ letter-spacing: 0.01em; }
::selection{ background: var(--important-bg); color: var(--important-ink); }
button, input{ font-family:inherit; }
button{ cursor:pointer; }
:focus-visible{ outline: 3px solid var(--focus-ring); outline-offset: 2px; border-radius: 6px; }
.num{ font-variant-numeric: tabular-nums; }
.shell{ max-width: 720px; margin: 0 auto; padding: 20px 16px 96px; }
/* ---------- Header ---------- */
header.top{ display:flex; align-items:flex-start; justify-content:space-between; gap:16px; margin-bottom: 18px; }
.brand h1{ font-size: 1.5rem; margin:0 0 2px; letter-spacing:-0.01em; font-weight:800; }
.brand p{ margin:0; color:var(--ink-muted); font-size:0.92rem; }
.top-actions{ display:flex; gap:8px; align-items:center; }
.icon-btn{
width:40px;height:40px; border-radius:12px; border:1px solid var(--border);
background:var(--surface); font-size:1.1rem; display:flex; align-items:center; justify-content:center;
box-shadow: var(--shadow);
}
.icon-btn:hover{ background:var(--surface-2); }
/* ---------- Progress ring ---------- */
.progress-card{
display:flex; align-items:center; gap:16px;
background:var(--surface); border:1px solid var(--border); border-radius:18px;
padding:14px 18px; margin-bottom:16px; box-shadow: var(--shadow);
}
.ring{ flex-shrink:0; }
.ring text{ fill:var(--ink); font-weight:700; font-size:15px; font-variant-numeric: tabular-nums; }
.ring .track{ stroke:var(--surface-2); }
.ring .fill{ stroke:var(--done-bg); transition: stroke-dashoffset .5s ease; }
.progress-copy strong{ display:block; font-size:1rem; }
.progress-copy span{ color:var(--ink-muted); font-size:0.88rem; }
/* ---------- Quick add ---------- */
.quickadd{
position: sticky; top: 12px; z-index: 5;
display:flex; gap:8px; background:var(--surface); border:1px solid var(--border);
border-radius:16px; padding:8px; margin-bottom:14px; box-shadow: var(--shadow);
}
.quickadd input[type=text]{
flex:1; border:none; background:transparent; color:var(--ink);
font-size:1rem; padding:8px 10px; min-width:0;
}
.quickadd input[type=text]::placeholder{ color:var(--ink-muted); }
.quickadd button{ border:none; border-radius:12px; }
.btn-mic{ background:var(--surface-2); color:var(--ink); width:42px; }
.btn-mic.listening{ background:var(--urgent-bg); color:var(--urgent-ink); animation: pulse 1s infinite; }
.btn-add{ background:var(--accent); color:var(--accent-ink); font-weight:700; padding:0 16px; white-space:nowrap; }
@keyframes pulse{ 0%,100%{ transform:scale(1);} 50%{ transform:scale(1.08);} }
@media (prefers-reduced-motion: reduce){ .btn-mic.listening{ animation:none; } }
/* ---------- Filter row ---------- */
.toolbar{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:16px; align-items:center; }
.chip{
border:1px solid var(--border); background:var(--surface); color:var(--ink-muted);
border-radius:999px; padding:6px 12px; font-size:0.85rem; font-weight:600;
}
.chip.active{ background:var(--ink); color:var(--bg); border-color:var(--ink); }
.toolbar .spacer{ flex:1; }
.btn-focus{
border:1px solid var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent);
color:var(--accent); border-radius:999px; padding:6px 14px; font-size:0.85rem; font-weight:700;
}
:root[data-theme="dark"] .btn-focus, .btn-focus{ color:var(--ink); }
/* ---------- Task list ---------- */
.list{ display:flex; flex-direction:column; gap:10px; }
.empty{ text-align:center; color:var(--ink-muted); padding:40px 16px; }
.empty .big{ font-size:2rem; margin-bottom:6px; }
.card{
background:var(--surface); border:1px solid var(--border); border-radius:16px;
padding:12px 14px; box-shadow: var(--shadow); position:relative;
}
.card.overdue{ box-shadow: 0 0 0 2px var(--overdue-bg), var(--shadow); }
.card.status-not-started{ border-left:5px solid var(--not-started-bg); }
.card.status-in-progress{ border-left:5px solid var(--progress-bg); }
.card.status-done{ border-left:5px solid var(--done-bg); opacity:0.72; }
.card.status-done .task-text{ text-decoration: line-through; text-decoration-color: var(--ink-muted); }
.card-top{ display:flex; align-items:flex-start; gap:10px; }
.rank{ color:var(--ink-muted); font-size:0.75rem; padding-top:6px; width:20px; flex-shrink:0; text-align:right; }
.status-btn{
flex-shrink:0; width:34px; height:34px; border-radius:50%; border:2px solid var(--border);
background:var(--surface); font-size:1rem; display:flex; align-items:center; justify-content:center;
}
.status-btn.status-not-started{ border-color: var(--not-started-bg); }
.status-btn.status-in-progress{ border-color: var(--progress-bg); background: var(--progress-bg); }
.status-btn.status-done{ border-color: var(--done-bg); background: var(--done-bg); }
.task-main{ flex:1; min-width:0; }
.task-text{
border:none; background:transparent; color:var(--ink); font-size:1.02rem; width:100%;
padding:4px 0; font-weight:600; overflow:hidden; text-overflow:ellipsis;
}
.meta-row{ display:flex; gap:6px; flex-wrap:wrap; margin-top:4px; align-items:center; }
.badge{ font-size:0.76rem; font-weight:700; border-radius:999px; padding:3px 9px; display:inline-flex; align-items:center; gap:4px; border:none; }
.badge.urgent{ background:var(--urgent-bg); color:var(--urgent-ink); }
.badge.important{ background:var(--important-bg); color:var(--important-ink); }
.badge.toggle{ background:var(--surface-2); color:var(--ink-muted); }
.badge.overdue-badge{ background:var(--overdue-bg); color:var(--overdue-ink); }
.badge.due{ background:var(--surface-2); color:var(--ink-muted); }
.badge.sub-count{ background:var(--surface-2); color:var(--ink-muted); }
.card-actions{ display:flex; gap:4px; flex-shrink:0; }
.mini-btn{
width:30px;height:30px; border-radius:9px; border:1px solid var(--border); background:var(--surface);
font-size:0.9rem; display:flex; align-items:center; justify-content:center; color:var(--ink-muted);
}
.mini-btn:hover{ background:var(--surface-2); color:var(--ink); }
.mini-btn.danger:hover{ background:var(--overdue-bg); color:var(--overdue-ink); }
.subtasks{ margin:10px 0 2px 44px; display:flex; flex-direction:column; gap:6px; }
.subtask-row{ display:flex; align-items:center; gap:8px; font-size:0.92rem; }
.subtask-row input[type=checkbox]{ width:16px; height:16px; accent-color: var(--accent); }
.subtask-row.done span{ text-decoration:line-through; color:var(--ink-muted); }
.subtask-add{ display:flex; gap:6px; margin:6px 0 2px 44px; }
.subtask-add input{ flex:1; border:1px solid var(--border); background:var(--surface-2); color:var(--ink); border-radius:8px; padding:5px 9px; font-size:0.88rem; }
.subtask-add button{ border:1px solid var(--border); background:var(--surface-2); border-radius:8px; padding:5px 10px; font-size:0.85rem; color:var(--ink-muted); }
.due-row{ margin:8px 0 0 44px; display:flex; gap:6px; align-items:center; }
.due-row input[type=datetime-local]{
border:1px solid var(--border); background:var(--surface-2); color:var(--ink); border-radius:8px;
padding:5px 8px; font-size:0.85rem;
}
/* ---------- Snoozed ---------- */
.snoozed{ margin-top:20px; }
.snoozed summary{ cursor:pointer; color:var(--ink-muted); font-weight:700; font-size:0.9rem; padding:6px 2px; }
.snoozed .list{ margin-top:10px; }
.snoozed .card{ opacity:0.8; }
/* ---------- Focus mode ---------- */
.focus-overlay{
display:flex; flex-direction:column; align-items:center; justify-content:center;
text-align:center; gap:18px; padding: 60px 16px; min-height: 50vh;
}
.focus-overlay .label{ color:var(--ink-muted); font-weight:700; letter-spacing:0.04em; text-transform:uppercase; font-size:0.78rem; }
.focus-overlay h2{ font-size:1.7rem; margin:0; max-width:520px; }
.focus-badges{ display:flex; gap:6px; }
.focus-actions{ display:flex; gap:10px; margin-top:8px; flex-wrap:wrap; justify-content:center; }
.btn-big{ border-radius:14px; padding:12px 22px; font-weight:700; font-size:1rem; border:1px solid var(--border); background:var(--surface); color:var(--ink); }
.btn-big.primary{ background:var(--accent); color:var(--accent-ink); border:none; }
.btn-big.ghost{ background:transparent; }
/* ---------- Settings drawer ---------- */
.drawer-backdrop{ position:fixed; inset:0; background:rgba(0,0,0,0.35); opacity:0; pointer-events:none; transition:opacity .2s; z-index:20; }
.drawer-backdrop.open{ opacity:1; pointer-events:auto; }
.drawer{
position:fixed; top:0; right:0; height:100%; width:min(340px,88vw); background:var(--surface);
border-left:1px solid var(--border); transform:translateX(100%); transition:transform .25s ease;
z-index:21; padding:20px; overflow-y:auto;
}
.drawer.open{ transform:translateX(0); }
.drawer h3{ margin:0 0 14px; }
.drawer .row{ display:flex; align-items:center; justify-content:space-between; padding:12px 0; border-bottom:1px solid var(--border); gap:10px; }
.seg{ display:flex; border:1px solid var(--border); border-radius:10px; overflow:hidden; }
.seg button{ border:none; background:var(--surface); color:var(--ink-muted); padding:6px 10px; font-size:0.82rem; font-weight:600; }
.seg button.active{ background:var(--accent); color:var(--accent-ink); }
.switch{ position:relative; width:44px; height:26px; }
.switch input{ opacity:0; width:0; height:0; }
.slider{ position:absolute; inset:0; background:var(--surface-2); border-radius:999px; transition:.2s; }
.slider::before{ content:""; position:absolute; width:20px; height:20px; left:3px; top:3px; background:var(--surface); border-radius:50%; transition:.2s; box-shadow:var(--shadow); }
.switch input:checked + .slider{ background:var(--accent); }
.switch input:checked + .slider::before{ transform:translateX(18px); }
.drawer .hint{ color:var(--ink-muted); font-size:0.8rem; margin-top:4px; }
.drawer .link-btn{ background:none; border:none; color:var(--accent); font-weight:700; padding:0; font-size:0.9rem; }
/* ---------- Toasts ---------- */
.toasts{ position:fixed; bottom:16px; left:50%; transform:translateX(-50%); display:flex; flex-direction:column; gap:8px; z-index:30; width:min(420px,92vw); }
.toast{
background:var(--ink); color:var(--bg); border-radius:12px; padding:10px 14px; font-size:0.9rem;
display:flex; align-items:center; justify-content:space-between; gap:12px; box-shadow:var(--shadow);
animation: toastin .18s ease;
}
.toast button{ background:none; border:none; color:var(--celebrate); font-weight:700; flex-shrink:0; }
@keyframes toastin{ from{ opacity:0; transform:translateY(6px);} to{opacity:1; transform:translateY(0);} }
#confetti-canvas{ position:fixed; inset:0; pointer-events:none; z-index:40; }
@media (max-width: 480px){
.brand h1{ font-size:1.3rem; }
.progress-card{ padding:12px; }
}
</style>
</head>
<body>
<div class="shell">
<header class="top">
<div class="brand">
<h1>🌿 Steady</h1>
<p>One calm list. Nothing lost, nothing rushed.</p>
</div>
<div class="top-actions">
<button class="icon-btn" id="themeToggle" aria-label="Toggle dark mode" title="Toggle dark mode">🌙</button>
<button class="icon-btn" id="settingsToggle" aria-label="Open settings" title="Settings">⚙️</button>
</div>
</header>
<div class="progress-card">
<svg class="ring" width="64" height="64" viewBox="0 0 64 64">
<circle class="track" cx="32" cy="32" r="26" fill="none" stroke-width="8"></circle>
<circle class="fill" id="ringFill" cx="32" cy="32" r="26" fill="none" stroke-width="8"
stroke-linecap="round" transform="rotate(-90 32 32)"></circle>
<text id="ringText" x="32" y="37" text-anchor="middle">0/0</text>
</svg>
<div class="progress-copy">
<strong id="progressHeadline">Add your first task 🎯</strong>
<span id="progressSub">Small steps still count.</span>
</div>
</div>
<form class="quickadd" id="quickAddForm">
<button type="button" class="btn-mic" id="micBtn" aria-label="Add task by voice" title="Add by voice">🎤</button>
<input type="text" id="quickAddInput" placeholder="What's next? Type it before it slips away…" autocomplete="off" />
<button type="submit" class="btn-add">Add</button>
</form>
<div class="toolbar" id="toolbar">
<button class="chip active" data-filter="all">All</button>
<button class="chip" data-filter="not-started">Not started</button>
<button class="chip" data-filter="in-progress">In progress</button>
<button class="chip" data-filter="done">Done</button>
<button class="chip" data-filter="urgent">⚠️ Urgent</button>
<button class="chip" data-filter="important">🎯 Important</button>
<span class="spacer"></span>
<button class="btn-focus" id="focusToggle">🔭 Focus mode</button>
</div>
<div id="mainList"></div>
<details class="snoozed" id="snoozedSection">
<summary id="snoozedSummary">🌙 Later (0)</summary>
<div class="list" id="snoozedList"></div>
</details>
</div>
<div class="drawer-backdrop" id="drawerBackdrop"></div>
<aside class="drawer" id="drawer" aria-label="Settings">
<h3>Settings</h3>
<div class="row">
<div>
<div>Appearance</div>
<div class="hint">System follows your device.</div>
</div>
<div class="seg" id="themeSeg">
<button data-theme="system">Auto</button>
<button data-theme="light">Light</button>
<button data-theme="dark">Dark</button>
</div>
</div>
<div class="row">
<div>
<div>Completion chime</div>
<div class="hint">Soft sound when you finish a task.</div>
</div>
<label class="switch"><input type="checkbox" id="soundToggle" checked><span class="slider"></span></label>
</div>
<div class="row">
<div>
<div>Dyslexia-friendly font</div>
<div class="hint">Wider letter spacing and an easier-to-scan typeface.</div>
</div>
<label class="switch"><input type="checkbox" id="dyslexiaToggle"><span class="slider"></span></label>
</div>
<div class="row">
<div>
<div>Browser reminders</div>
<div class="hint" id="notifStatus">Not enabled yet.</div>
</div>
<button class="link-btn" id="enableNotifs">Enable</button>
</div>
<div class="row">
<div>
<div>Clear completed</div>
<div class="hint">Removes done tasks (undoable for a few seconds).</div>
</div>
<button class="link-btn" id="clearDone">Clear</button>
</div>
<div class="row">
<div>
<div>Backup your list</div>
<div class="hint">Download everything as a JSON file.</div>
</div>
<button class="link-btn" id="exportBtn">Export</button>
</div>
<div class="row" style="border-bottom:none;">
<div>
<div>Restore / move to another device</div>
<div class="hint">Load a backup JSON file exported from Steady.</div>
</div>
<button class="link-btn" id="importBtn">Import</button>
<input type="file" id="importFile" accept="application/json,.json" style="display:none">
</div>
</aside>
<canvas id="confetti-canvas"></canvas>
<div class="toasts" id="toasts"></div>
<script>
(function(){
"use strict";
/* ---------------- state & persistence ---------------- */
const STORAGE_KEY = "steady_tasks_v1";
const SETTINGS_KEY = "steady_settings_v1";
const reduceMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
let tasks = load(STORAGE_KEY, []);
let settings = Object.assign({ theme:"system", sound:true, notifications:false, dyslexia:false }, load(SETTINGS_KEY, {}));
let filter = "all";
let focusMode = false;
let focusTaskId = null;
function load(key, fallback){
try { const v = JSON.parse(localStorage.getItem(key)); return v == null ? fallback : v; }
catch(e){ return fallback; }
}
function persistTasks(){ localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks)); }
function persistSettings(){ localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); }
function uid(){ return (crypto.randomUUID ? crypto.randomUUID() : "id-" + Date.now() + "-" + Math.random().toString(16).slice(2)); }
function esc(s){ return (s||"").replace(/[&<>"']/g, c => ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;" }[c])); }
/* ---------------- theme ---------------- */
function applyTheme(){
const root = document.documentElement;
if(settings.theme === "light") root.setAttribute("data-theme","light");
else if(settings.theme === "dark") root.setAttribute("data-theme","dark");
else root.removeAttribute("data-theme");
document.getElementById("themeToggle").textContent =
(settings.theme === "dark" || (settings.theme==="system" && window.matchMedia("(prefers-color-scheme: dark)").matches)) ? "☀️" : "🌙";
document.querySelectorAll("#themeSeg button").forEach(b=>{
b.classList.toggle("active", b.dataset.theme === settings.theme);
});
}
document.getElementById("themeToggle").addEventListener("click", ()=>{
const dark = settings.theme === "dark" || (settings.theme==="system" && window.matchMedia("(prefers-color-scheme: dark)").matches);
settings.theme = dark ? "light" : "dark";
persistSettings(); applyTheme();
});
document.getElementById("themeSeg").addEventListener("click", (e)=>{
const b = e.target.closest("button"); if(!b) return;
settings.theme = b.dataset.theme; persistSettings(); applyTheme();
});
/* ---------------- dyslexia-friendly font ---------------- */
function applyDyslexiaMode(){
document.body.classList.toggle("dyslexia-mode", !!settings.dyslexia);
document.getElementById("dyslexiaToggle").checked = !!settings.dyslexia;
}
document.getElementById("dyslexiaToggle").addEventListener("change", e=>{
settings.dyslexia = e.target.checked; persistSettings(); applyDyslexiaMode();
});
/* ---------------- settings drawer ---------------- */
const drawer = document.getElementById("drawer");
const backdrop = document.getElementById("drawerBackdrop");
function openDrawer(){ drawer.classList.add("open"); backdrop.classList.add("open"); }
function closeDrawer(){ drawer.classList.remove("open"); backdrop.classList.remove("open"); }
document.getElementById("settingsToggle").addEventListener("click", openDrawer);
backdrop.addEventListener("click", closeDrawer);
document.getElementById("soundToggle").checked = settings.sound;
document.getElementById("soundToggle").addEventListener("change", e=>{
settings.sound = e.target.checked; persistSettings();
});
function refreshNotifStatus(){
const el = document.getElementById("notifStatus");
const btn = document.getElementById("enableNotifs");
if(!("Notification" in window)){ el.textContent = "Not supported in this browser."; btn.style.display="none"; return; }
if(Notification.permission === "granted"){ el.textContent = "Enabled — you'll get a real popup."; btn.style.display="none"; settings.notifications = true; }
else if(Notification.permission === "denied"){ el.textContent = "Blocked by browser. In-app reminders still work."; btn.style.display="none"; }
else { el.textContent = "In-app reminders only, for now."; btn.style.display="inline"; }
persistSettings();
}
document.getElementById("enableNotifs").addEventListener("click", ()=>{
if(!("Notification" in window)) return;
Notification.requestPermission().then(refreshNotifStatus);
});
document.getElementById("clearDone").addEventListener("click", ()=>{
const removed = tasks.filter(t=>t.status==="done");
if(!removed.length){ toast("Nothing to clear."); return; }
tasks = tasks.filter(t=>t.status!=="done");
persistTasks(); render();
const removedIds = new Set(removed.map(t=>t.id));
toast(`Cleared ${removed.length} completed task${removed.length>1?"s":""}.`, "Undo", ()=>{
tasks = tasks.concat(removed);
persistTasks(); render();
});
});
document.getElementById("exportBtn").addEventListener("click", ()=>{
const blob = new Blob([JSON.stringify(tasks, null, 2)], {type:"application/json"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url; a.download = "steady-backup-" + new Date().toISOString().slice(0,10) + ".json";
document.body.appendChild(a); a.click(); a.remove();
URL.revokeObjectURL(url);
toast("Backup downloaded.");
});
document.getElementById("importBtn").addEventListener("click", ()=>{
document.getElementById("importFile").click();
});
document.getElementById("importFile").addEventListener("change", (e)=>{
const file = e.target.files[0];
e.target.value = ""; // allow re-selecting the same file later
if(!file) return;
const reader = new FileReader();
reader.onload = () => {
let incoming;
try { incoming = JSON.parse(reader.result); }
catch(err){ toast("That file isn't valid Steady backup JSON."); return; }
if(!Array.isArray(incoming)){ toast("That file isn't valid Steady backup JSON."); return; }
// normalize + re-key everything with fresh ids so imported tasks
// never collide with (or overwrite) anything already on this device
const imported = incoming.filter(t => t && typeof t.text === "string").map(t => ({
id: uid(),
text: t.text,
status: STATUS_ORDER.includes(t.status) ? t.status : "not-started",
urgent: !!t.urgent,
important: !!t.important,
subtasks: Array.isArray(t.subtasks) ? t.subtasks.filter(s=>s && typeof s.text==="string").map(s=>({ id: uid(), text: s.text, done: !!s.done })) : [],
due: t.due || null,
notified: !!t.notified,
hiddenUntil: t.hiddenUntil || null,
createdAt: typeof t.createdAt === "number" ? t.createdAt : Date.now()
}));
if(!imported.length){ toast("No tasks found in that file."); return; }
tasks = tasks.concat(imported);
persistTasks(); render();
toast(`Imported ${imported.length} task${imported.length>1?"s":""}.`);
};
reader.onerror = () => toast("Couldn't read that file.");
reader.readAsText(file);
});
/* ---------------- toasts ---------------- */
function toast(message, actionLabel, actionFn){
const wrap = document.getElementById("toasts");
const el = document.createElement("div");
el.className = "toast";
el.innerHTML = `<span>${esc(message)}</span>`;
if(actionLabel){
const b = document.createElement("button");
b.textContent = actionLabel;
b.addEventListener("click", ()=>{ actionFn && actionFn(); el.remove(); });
el.appendChild(b);
}
wrap.appendChild(el);
setTimeout(()=>{ el.remove(); }, actionLabel ? 6000 : 3500);
}
/* ---------------- data helpers ---------------- */
const STATUS_ORDER = ["not-started","in-progress","done"];
const STATUS_ICON = { "not-started":"○", "in-progress":"◐", "done":"✅" };
function isHidden(t){ return t.hiddenUntil && new Date(t.hiddenUntil) > new Date(); }
function isOverdue(t){ return t.due && t.status!=="done" && new Date(t.due) < new Date(); }
function unSnoozeExpired(){
let changed = false;
tasks.forEach(t=>{ if(t.hiddenUntil && new Date(t.hiddenUntil) <= new Date()){ t.hiddenUntil = null; changed = true; } });
if(changed) persistTasks();
}
function addTask(text){
const t = text.trim();
if(!t) return;
tasks.push({
id: uid(), text: t, status:"not-started",
urgent:false, important:false,
subtasks:[], due:null, notified:false, hiddenUntil:null,
createdAt: Date.now()
});
persistTasks(); render();
}
function cycleStatus(task){
const idx = STATUS_ORDER.indexOf(task.status);
const next = STATUS_ORDER[(idx+1) % STATUS_ORDER.length];
setStatus(task, next);
}
function setStatus(task, next){
const wasDone = task.status === "done";
task.status = next;
persistTasks();
if(!wasDone && next === "done") celebrate();
render();
}
function toggleSubtask(task, sub){
sub.done = !sub.done;
if(task.subtasks.length && task.subtasks.every(s=>s.done)){
if(task.status !== "done"){ task.status = "done"; celebrate(); }
} else if(task.subtasks.some(s=>s.done) && task.status === "not-started"){
task.status = "in-progress";
}
persistTasks(); render();
}
function addSubtask(task, text){
const t = text.trim(); if(!t) return;
task.subtasks.push({ id:uid(), text:t, done:false });
persistTasks(); render();
}
function snoozeTask(task){
const tmr = new Date(); tmr.setDate(tmr.getDate()+1); tmr.setHours(0,0,0,0);
task.hiddenUntil = tmr.toISOString();
persistTasks(); render();
toast("Moved to Later — it'll come back tomorrow.", "Undo", ()=>{ task.hiddenUntil = null; persistTasks(); render(); });
}
function deleteTask(task){
tasks = tasks.filter(x=>x.id!==task.id);
persistTasks(); render();
toast("Task deleted.", "Undo", ()=>{ tasks.push(task); persistTasks(); render(); });
}
function fmtCountdown(dueIso){
const diff = new Date(dueIso) - new Date();
if(diff <= 0) return "Overdue";
const mins = Math.round(diff/60000);
if(mins < 60) return `in ${mins}m`;
const hours = Math.floor(mins/60);
if(hours < 24) return `in ${hours}h ${mins%60}m`;
const days = Math.floor(hours/24);
return `in ${days}d`;
}
/* ---------------- rendering ---------------- */
function pickFocusTask(){
const pool = tasks.filter(t=> !isHidden(t) && t.status!=="done");
if(!pool.length) return null;
pool.sort((a,b)=>{
const score = t => (t.urgent?2:0) + (t.important?1:0);
if(score(b) !== score(a)) return score(b)-score(a);
if(a.due && b.due) return new Date(a.due)-new Date(b.due);
if(a.due) return -1;
if(b.due) return 1;
return a.createdAt - b.createdAt;
});
return pool[0];
}
function taskCardHTML(task, index, opts){
opts = opts || {};
const overdue = isOverdue(task);
const subDone = task.subtasks.filter(s=>s.done).length;
const badges = [];
if(task.urgent) badges.push(`<button class="badge urgent" data-action="toggle-urgent" data-id="${task.id}">⚠️ Urgent</button>`);
else badges.push(`<button class="badge toggle" data-action="toggle-urgent" data-id="${task.id}">+ Urgent</button>`);
if(task.important) badges.push(`<button class="badge important" data-action="toggle-important" data-id="${task.id}">🎯 Important</button>`);
else badges.push(`<button class="badge toggle" data-action="toggle-important" data-id="${task.id}">+ Important</button>`);
if(task.subtasks.length) badges.push(`<span class="badge sub-count">${subDone}/${task.subtasks.length} steps</span>`);
if(overdue) badges.push(`<span class="badge overdue-badge">Overdue</span>`);
else if(task.due) badges.push(`<span class="badge due">⏰ ${fmtCountdown(task.due)}</span>`);
const subtaskRows = task.subtasks.map(s=>`
<div class="subtask-row ${s.done?"done":""}">
<input type="checkbox" ${s.done?"checked":""} data-action="toggle-subtask" data-id="${task.id}" data-sub="${s.id}">
<span>${esc(s.text)}</span>
</div>`).join("");
return `
<div class="card status-${task.status} ${overdue?"overdue":""}" data-id="${task.id}">
<div class="card-top">
<div class="rank num">${String(index+1).padStart(2,"0")}</div>
<button class="status-btn status-${task.status}" data-action="cycle-status" data-id="${task.id}" title="Cycle status" aria-label="Task status: ${task.status}">${STATUS_ICON[task.status]}</button>
<div class="task-main">
<input class="task-text" data-action="edit-text" data-id="${task.id}" value="${esc(task.text)}" ${task.status==="done"?"style='text-decoration:line-through'":""}>
<div class="meta-row">${badges.join("")}</div>
</div>
<div class="card-actions">
${opts.hideSnooze?"":`<button class="mini-btn" data-action="snooze" data-id="${task.id}" title="Not today">😴</button>`}
<button class="mini-btn" data-action="set-due" data-id="${task.id}" title="Set reminder"></button>
<button class="mini-btn danger" data-action="delete" data-id="${task.id}" title="Delete">🗑️</button>
</div>
</div>
${subtaskRows ? `<div class="subtasks">${subtaskRows}</div>` : ""}
<div class="subtask-add">
<input type="text" placeholder="Add a small step…" data-action="subtask-input" data-id="${task.id}">
<button data-action="subtask-add" data-id="${task.id}">Add step</button>
</div>
<div class="due-row" data-due-row="${task.id}" style="display:${opts.showDueEditor?"flex":"none"}">
<input type="datetime-local" data-action="due-input" data-id="${task.id}" value="${task.due ? task.due.slice(0,16) : ""}">
<button class="mini-btn" data-action="due-save" data-id="${task.id}">Save</button>
<button class="mini-btn" data-action="due-clear" data-id="${task.id}">Clear</button>
</div>
</div>`;
}
let openDueEditorId = null;
function render(){
unSnoozeExpired();
// progress ring — over all active (non-hidden) tasks
const active = tasks.filter(t=>!isHidden(t));
const done = active.filter(t=>t.status==="done").length;
const total = active.length;
const pct = total ? done/total : 0;
const r = 26, C = 2*Math.PI*r;
const fill = document.getElementById("ringFill");
fill.style.strokeDasharray = `${C}`;
fill.style.strokeDashoffset = `${C * (1-pct)}`;
document.getElementById("ringText").textContent = `${done}/${total}`;
const headline = document.getElementById("progressHeadline");
const sub = document.getElementById("progressSub");
if(total === 0){ headline.textContent = "Add your first task 🎯"; sub.textContent = "Small steps still count."; }
else if(done === total){ headline.textContent = "All done! 🎉"; sub.textContent = "Nothing pending right now."; }
else { headline.textContent = `${done} of ${total} done`; sub.textContent = "You're moving — that's what counts."; }
// snoozed section
const snoozed = tasks.filter(isHidden);
document.getElementById("snoozedSummary").textContent = `🌙 Later (${snoozed.length})`;
document.getElementById("snoozedList").innerHTML = snoozed.map((t,i)=>{
const html = taskCardHTML(t, i, {hideSnooze:true});
return html.replace('data-action="snooze"','').concat("");
}).join("") || `<p class="hint" style="color:var(--ink-muted); padding:6px 2px;">Nothing snoozed. Tasks you push to tomorrow show up here.</p>`;
// add an "unsnooze now" affordance
document.getElementById("snoozedList").querySelectorAll(".card").forEach(card=>{
const id = card.dataset.id;
const actions = card.querySelector(".card-actions");
const btn = document.createElement("button");
btn.className = "mini-btn"; btn.title = "Bring back now"; btn.textContent = "↩️";
btn.dataset.action = "unsnooze"; btn.dataset.id = id;
actions.insertBefore(btn, actions.firstChild);
});
const mainList = document.getElementById("mainList");
if(focusMode){
const ft = tasks.find(t=>t.id===focusTaskId) || pickFocusTask();
focusTaskId = ft ? ft.id : null;
if(!ft){
mainList.innerHTML = `<div class="focus-overlay">
<div class="label">Focus mode</div>
<div style="font-size:2.4rem;">🎉</div>
<h2>Nothing left to focus on. Nicely done.</h2>
<div class="focus-actions"><button class="btn-big" id="exitFocus">Show all tasks</button></div>
</div>`;
} else {
mainList.innerHTML = `<div class="focus-overlay">
<div class="label">Focus mode · just this one</div>
<h2>${esc(ft.text)}</h2>
<div class="focus-badges">
${ft.urgent?'<span class="badge urgent">⚠️ Urgent</span>':""}
${ft.important?'<span class="badge important">🎯 Important</span>':""}
${ft.due?`<span class="badge due">⏰ ${fmtCountdown(ft.due)}</span>`:""}
</div>
${ft.subtasks.length?`<div class="subtasks" style="margin-left:0;text-align:left;">${ft.subtasks.map(s=>`
<div class="subtask-row ${s.done?"done":""}">
<input type="checkbox" ${s.done?"checked":""} data-action="toggle-subtask" data-id="${ft.id}" data-sub="${s.id}">
<span>${esc(s.text)}</span>
</div>`).join("")}</div>`:""}
<div class="focus-actions">
<button class="btn-big primary" data-action="cycle-status" data-id="${ft.id}">${ft.status==="done"?"Done ✅":"Mark progress →"}</button>
<button class="btn-big" data-action="snooze" data-id="${ft.id}">😴 Not today</button>
<button class="btn-big ghost" id="exitFocus">Show all tasks</button>
</div>
</div>`;
}
return;
}
let visible = tasks.filter(t=>!isHidden(t));
if(filter==="urgent") visible = visible.filter(t=>t.urgent);
else if(filter==="important") visible = visible.filter(t=>t.important);
else if(filter!=="all") visible = visible.filter(t=>t.status===filter);
if(!visible.length){
mainList.innerHTML = `<div class="empty"><div class="big">🌤️</div>${tasks.length? "Nothing matches this filter." : "Your list is empty — add something small to start."}</div>`;
} else {
mainList.innerHTML = `<div class="list">${visible.map((t)=>{
const idx = tasks.indexOf(t);
return taskCardHTML(t, idx, { showDueEditor: t.id === openDueEditorId });
}).join("")}</div>`;
}
document.querySelectorAll(".chip[data-filter]").forEach(c=>{
c.classList.toggle("active", c.dataset.filter === filter);
});
}
/* ---------------- event delegation ---------------- */
document.addEventListener("click", (e)=>{
const exitBtn = e.target.closest("#exitFocus");
if(exitBtn){ focusMode = false; render(); return; }
const el = e.target.closest("[data-action]");
if(!el) return;
const id = el.dataset.id;
const task = tasks.find(t=>t.id===id);
switch(el.dataset.action){
case "cycle-status": if(task) cycleStatus(task); break;
case "toggle-urgent": if(task){ task.urgent = !task.urgent; persistTasks(); render(); } break;
case "toggle-important": if(task){ task.important = !task.important; persistTasks(); render(); } break;
case "snooze": if(task) snoozeTask(task); break;
case "unsnooze": if(task){ task.hiddenUntil = null; persistTasks(); render(); } break;
case "delete": if(task) deleteTask(task); break;
case "set-due": openDueEditorId = (openDueEditorId===id ? null : id); render(); break;
case "due-save": {
if(task){
const input = document.querySelector(`[data-due-row="${id}"] input[data-action="due-input"]`);
if(input && input.value){ task.due = new Date(input.value).toISOString(); task.notified = false; persistTasks(); toast("Reminder set."); }
}
openDueEditorId = null; render();
break;
}
case "due-clear": if(task){ task.due = null; task.notified = false; persistTasks(); } openDueEditorId = null; render(); break;
case "subtask-add": {
const input = document.querySelector(`input[data-action="subtask-input"][data-id="${id}"]`);
if(task && input && input.value.trim()){ addSubtask(task, input.value); }
break;
}
case "toggle-subtask": break; // handled via change event below
}
});
document.addEventListener("change", (e)=>{
const el = e.target.closest("[data-action='toggle-subtask']");
if(el){
const task = tasks.find(t=>t.id===el.dataset.id);
const sub = task && task.subtasks.find(s=>s.id===el.dataset.sub);
if(task && sub) toggleSubtask(task, sub);
}
});
document.addEventListener("keydown", (e)=>{
if(e.key === "Enter" && e.target.matches('input[data-action="subtask-input"]')){
e.preventDefault();
const id = e.target.dataset.id;
const task = tasks.find(t=>t.id===id);
if(task && e.target.value.trim()) addSubtask(task, e.target.value);
}
});
document.addEventListener("blur", (e)=>{
if(e.target && e.target.matches('input[data-action="edit-text"]')){
const task = tasks.find(t=>t.id===e.target.dataset.id);
if(task && e.target.value.trim()){ task.text = e.target.value.trim(); persistTasks(); }
else if(task){ e.target.value = task.text; }
}
}, true);
document.getElementById("toolbar").addEventListener("click", (e)=>{
const chip = e.target.closest(".chip[data-filter]");
if(chip){ filter = chip.dataset.filter; render(); }
});
document.getElementById("focusToggle").addEventListener("click", ()=>{
focusMode = !focusMode;
if(focusMode) focusTaskId = pickFocusTask() ? pickFocusTask().id : null;
render();
});
/* ---------------- quick add + voice ---------------- */
const quickAddForm = document.getElementById("quickAddForm");
const quickAddInput = document.getElementById("quickAddInput");
quickAddForm.addEventListener("submit", (e)=>{
e.preventDefault();
addTask(quickAddInput.value);
quickAddInput.value = "";
quickAddInput.focus();
});
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const micBtn = document.getElementById("micBtn");
if(!SpeechRecognition){
micBtn.disabled = true;
micBtn.title = "Voice input isn't supported in this browser";
micBtn.style.opacity = 0.4;
} else {
let recognizing = false;
const rec = new SpeechRecognition();
rec.lang = "en-US"; rec.interimResults = false; rec.maxAlternatives = 1;
rec.addEventListener("result", (e)=>{
const transcript = e.results[0][0].transcript;
quickAddInput.value = transcript;
quickAddInput.focus();
});
rec.addEventListener("end", ()=>{ recognizing = false; micBtn.classList.remove("listening"); });
rec.addEventListener("error", ()=>{ recognizing = false; micBtn.classList.remove("listening"); toast("Didn't catch that — try typing it."); });
micBtn.addEventListener("click", ()=>{
if(recognizing) return;
try{ rec.start(); recognizing = true; micBtn.classList.add("listening"); }
catch(err){ /* already started elsewhere */ }
});
}
/* ---------------- celebration: confetti + chime ---------------- */
function celebrate(){
playChime();
if(reduceMotion) return;
burstConfetti();
}
function playChime(){
if(!settings.sound) return;
try{
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const notes = [880, 1318.5];
notes.forEach((freq, i)=>{
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = "sine"; osc.frequency.value = freq;
const start = ctx.currentTime + i*0.11;
gain.gain.setValueAtTime(0.0001, start);
gain.gain.exponentialRampToValueAtTime(0.18, start + 0.02);
gain.gain.exponentialRampToValueAtTime(0.0001, start + 0.35);
osc.connect(gain); gain.connect(ctx.destination);
osc.start(start); osc.stop(start + 0.4);
});
setTimeout(()=>ctx.close(), 800);
} catch(e){ /* audio unavailable, ignore */ }
}
const confettiColors = ["#F3A65B","#7FC8B8","#8FC1E8","#F6CC4D","#B9A6EE","#7FCB93"];
function burstConfetti(){
const canvas = document.getElementById("confetti-canvas");
const dpr = window.devicePixelRatio || 1;
canvas.width = innerWidth*dpr; canvas.height = innerHeight*dpr;
canvas.style.width = innerWidth+"px"; canvas.style.height = innerHeight+"px";
const ctx = canvas.getContext("2d"); ctx.scale(dpr,dpr);
const originX = innerWidth/2, originY = innerHeight*0.3;
const particles = Array.from({length:36}, ()=>({
x: originX, y: originY,
vx: (Math.random()-0.5)*9, vy: Math.random()*-7-2,
size: Math.random()*5+4, color: confettiColors[Math.floor(Math.random()*confettiColors.length)],
rot: Math.random()*Math.PI, vr: (Math.random()-0.5)*0.3, life: 1
}));
let running = true;
function frame(){
if(!running) return;
ctx.clearRect(0,0,innerWidth,innerHeight);
let alive = false;
particles.forEach(p=>{
p.vy += 0.28; p.x += p.vx; p.y += p.vy; p.rot += p.vr; p.life -= 0.014;
if(p.life > 0){
alive = true;
ctx.save();
ctx.globalAlpha = Math.max(p.life,0);
ctx.translate(p.x,p.y); ctx.rotate(p.rot);
ctx.fillStyle = p.color;
ctx.fillRect(-p.size/2,-p.size/2,p.size,p.size);
ctx.restore();
}
});
if(alive) requestAnimationFrame(frame);
else { running=false; ctx.clearRect(0,0,innerWidth,innerHeight); }
}
requestAnimationFrame(frame);
}
/* ---------------- reminders (in-app + browser notifications) ---------------- */
function checkReminders(){
const now = new Date();
let changed = false;
tasks.forEach(t=>{
if(t.due && !t.notified && t.status!=="done" && new Date(t.due) <= now){
t.notified = true; changed = true;
toast(`⏰ Reminder: ${t.text}`);
if("Notification" in window && Notification.permission === "granted"){
try{ new Notification("⏰ " + t.text, { body: "It's time.", tag: t.id }); } catch(e){}
}
}
});
if(changed) persistTasks();
render(); // refresh overdue styling / countdowns
}
setInterval(checkReminders, 20000);
/* ---------------- init ---------------- */
applyTheme();
applyDyslexiaMode();
refreshNotifStatus();
render();
if(window.matchMedia){
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", ()=>{ if(settings.theme==="system") applyTheme(); });
}
})();
</script>
</body>
</html>

View file

@ -0,0 +1,80 @@
[
{
"id": "faaea9af-7e2f-4ae7-b1df-d846e46ed15e",
"text": "test 1",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": "2026-08-07T22:00:00.000Z",
"createdAt": 1786112677570
},
{
"id": "11313d19-e4e5-4cdb-8120-38d9a7bb1050",
"text": "test 2",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": "2026-08-08T14:25:00.000Z",
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112696102
},
{
"id": "4f815881-9c31-4895-82c6-3aecf505218b",
"text": "test3",
"status": "not-started",
"urgent": false,
"important": true,
"subtasks": [
{
"id": "9fda4902-9079-4525-a74e-a4dae3c5d0fa",
"text": "One small step for man",
"done": false
}
],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112716480
},
{
"id": "9cb9a9ba-96d3-4c17-8ddd-5b734a8d2efa",
"text": "test 4",
"status": "not-started",
"urgent": true,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112740415
},
{
"id": "c9678a33-ad94-4ea8-ab1b-f2fb0f2656cf",
"text": "test 5",
"status": "in-progress",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112752591
},
{
"id": "20ac29ae-ff76-46d7-8503-89620140f68b",
"text": "test 6",
"status": "done",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112764729
}
]

122
upload.json Normal file
View file

@ -0,0 +1,122 @@
[
{
"id": "1",
"text": "Fenster aufmachen",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112740415
},
{
"id": "2",
"text": "Schlüssel holen",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112752591
},
{
"id": "3",
"text": "Wäsche sammeln",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112764729
},
{
"id": "4",
"text": "Aufbietungsgeschichte recherchieren",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112776857
},
{
"id": "5",
"text": "Resultat von Aufbietungsgeschichte an Florian schicken",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112788985
},
{
"id": "6",
"text": "Anrufen bei der Ergotherapie",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112801113
},
{
"id": "7",
"text": "EMAIL?",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112813241
},
{
"id": "8",
"text": "Fliegending holen kombi mit Fenster",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112825369
},
{
"id": "9",
"text": "Hammer mitnehmen",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112837497
},
{
"id": "10",
"text": "Sachen für die Vor Ort Arbeit vorbereiten",
"status": "not-started",
"urgent": false,
"important": false,
"subtasks": [],
"due": null,
"notified": false,
"hiddenUntil": null,
"createdAt": 1786112849625
}
]