/* myadhd.dev — client-side tools. No accounts, no network; everything in localStorage. */ (function () { 'use strict'; var $ = function (s, r) { return (r || document).querySelector(s); }; var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); }; function store(k, v) { if (v === undefined) { try { return JSON.parse(localStorage.getItem('myadhd.' + k)); } catch (e) { return null; } } localStorage.setItem('myadhd.' + k, JSON.stringify(v)); } function today() { return new Date().toISOString().slice(0, 10); } /* ---------- tabs ---------- */ function showTab(name) { $$('.tool').forEach(function (t) { t.hidden = (t.id !== name); }); $$('.tab').forEach(function (t) { t.classList.toggle('on', t.dataset.tab === name); }); if (!location.hash.slice(1)) {} // keep url in sync without loops try { history.replaceState(null, '', '#' + name); } catch (e) {} } $$('.tab').forEach(function (t) { t.addEventListener('click', function (e) { e.preventDefault(); showTab(t.dataset.tab); }); }); var initial = location.hash.slice(1); showTab(['breaker', 'timer', 'dopamine', 'now'].indexOf(initial) !== -1 ? initial : 'breaker'); /* ---------- task breaker ---------- */ var breakerKey = 'breaker'; function breakerTemplates(task) { return [ { t: 'Write down what “done” looks like for: ' + task + '.', mins: 2, done: false }, { t: 'Gather everything you need for “' + task + '” in one spot.', mins: 5, done: false }, { t: 'Do the ugliest possible first version of “' + task + '”. No polish allowed.', mins: 5, done: false }, { t: 'Set a 10-minute timer and start “' + task + '”. Starting is the whole job.', mins: 10, done: false }, { t: 'Momentum check: keep going, or park it with a note for next time.', mins: 2, done: false }, { t: 'Close the loop: tidy up and write down the very next step.', mins: 2, done: false } ]; } function breakerRender() { var b = store(breakerKey); $('#breaker-out').hidden = !b; if (!b) return; var ul = $('#breaker-steps'); ul.innerHTML = ''; b.steps.forEach(function (s, i) { var li = document.createElement('li'); li.className = s.done ? 'done' : ''; var tick = document.createElement('button'); tick.className = 'tick'; tick.type = 'button'; tick.setAttribute('aria-label', 'toggle done'); tick.addEventListener('click', function () { s.done = !s.done; store(breakerKey, b); breakerRender(); }); var txt = document.createElement('span'); txt.className = 'txt'; txt.contentEditable = 'true'; txt.textContent = s.t; txt.addEventListener('blur', function () { s.t = txt.textContent.trim(); store(breakerKey, b); }); var mins = document.createElement('span'); mins.className = 'mins'; mins.textContent = s.mins ? s.mins + ' min' : ''; li.appendChild(tick); li.appendChild(txt); li.appendChild(mins); ul.appendChild(li); }); var done = b.steps.filter(function (s) { return s.done; }).length; $('#breaker-progress').style.width = b.steps.length ? (100 * done / b.steps.length) + '%' : '0'; $('#breaker-count').textContent = done + ' of ' + b.steps.length + ' steps done' + (done === b.steps.length && b.steps.length ? ' — look at you. 🎉' : ''); } $('#breaker-form').addEventListener('submit', function (e) { e.preventDefault(); var task = $('#breaker-input').value.trim(); if (!task) return; store(breakerKey, { task: task, steps: breakerTemplates(task) }); breakerRender(); }); $('#breaker-add').addEventListener('click', function () { var b = store(breakerKey); if (!b) return; b.steps.push({ t: 'Next step…', mins: 0, done: false }); store(breakerKey, b); breakerRender(); }); $('#breaker-reset').addEventListener('click', function () { localStorage.removeItem('myadhd.' + breakerKey); $('#breaker-input').value = ''; breakerRender(); }); breakerRender(); /* ---------- focus timer ---------- */ var CIRC = 628.3; var timer = { total: 25 * 60, left: 25 * 60, running: false, phase: 'focus', iv: null }; function timerPaint() { var m = Math.floor(timer.left / 60), s = timer.left % 60; $('#timer-time').textContent = (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; $('#ring-fg').style.strokeDashoffset = CIRC * (1 - timer.left / timer.total); var ph = $('#timer-phase'); ph.textContent = timer.running ? (timer.phase === 'focus' ? 'focus — you got this' : 'break — actually rest') : ph.textContent; ph.className = 'timer-phase' + (timer.phase === 'break' ? ' break' : ''); } function beep() { try { var ctx = new (window.AudioContext || window.webkitAudioContext)(); [[660, 0], [880, 0.18]].forEach(function (n) { var o = ctx.createOscillator(), g = ctx.createGain(); o.frequency.value = n[0]; o.type = 'sine'; g.gain.setValueAtTime(0.001, ctx.currentTime + n[1]); g.gain.exponentialRampToValueAtTime(0.12, ctx.currentTime + n[1] + 0.02); g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + n[1] + 0.4); o.connect(g); g.connect(ctx.destination); o.start(ctx.currentTime + n[1]); o.stop(ctx.currentTime + n[1] + 0.45); }); } catch (e) {} } function timerStop() { clearInterval(timer.iv); timer.iv = null; timer.running = false; } $('#timer-start').addEventListener('click', function () { if (timer.running) { timerStop(); $('#timer-start').textContent = 'resume'; timerPaint(); return; } timer.running = true; $('#timer-start').textContent = 'pause'; timer.iv = setInterval(function () { timer.left--; if (timer.left <= 0) { timerStop(); beep(); if (timer.phase === 'focus') { store('sessions', (store('sessions') || 0) + 1); timer.phase = 'break'; $('#timer-note').textContent = 'Focus round done (' + store('sessions') + ' total). Stand up. Drink water. Actually take the break.'; } else { timer.phase = 'focus'; $('#timer-note').textContent = 'Break over. Pick a length and go again — or stop here, that is allowed too.'; } timer.total = timer.left = (timer.phase === 'break' ? 5 : 25) * 60; $('#timer-start').textContent = 'start'; } timerPaint(); }, 1000); timerPaint(); }); $('#timer-reset').addEventListener('click', function () { timerStop(); timer.left = timer.total; $('#timer-start').textContent = 'start'; timerPaint(); }); $$('.preset').forEach(function (p) { p.addEventListener('click', function () { timerStop(); timer.total = timer.left = (+p.dataset.min) * 60; timer.phase = p.classList.contains('break') ? 'break' : 'focus'; $('#timer-start').textContent = 'start'; timerPaint(); }); }); timerPaint(); /* ---------- dopamine menu ---------- */ var MENU = { 5: [ 'Put on one song and tidy one single surface.', 'Drink a big glass of water like it is a magic potion.', 'Step outside for two minutes and name five sounds you hear.', 'Stretch your arms and neck slowly — like a cat waking up.', 'Text someone one true compliment. No context, no apology.', 'Draw the nearest object as badly as you possibly can.', 'Open the window and take ten slow breaths.', 'Rearrange exactly three things on your desk.', 'Do ten squats or one ridiculous dance move.', 'Write down three things you can see right now that you like.' ], 15: [ 'Walk once around the block — phone stays home or in pocket.', 'Build the fanciest snack possible from what you already have.', 'Write a short thank-you note to your future self.', 'Doodle your current mood as a weather report.', 'Do a ten-minute follow-along stretch video.', 'Clean one drawer. One. Set a timer so it cannot grow.', 'Read five pages of any book you own.', 'Water (or rescue) one plant.', 'Have a shower, but treat it like a whole concert.', 'Learn three words in a language you do not speak.' ], 30: [ 'Cook something new with a video recipe playing.', 'Go for a 25-minute walk with one podcast episode.', 'Call someone you like. With your voice. Wild, we know.', 'Build something with zero rules: LEGO, blanket fort, collage.', 'Brain-dump everything in your head onto paper for ten minutes.', 'Deep-clean the one object that quietly bothers you most.', 'Follow a 30-minute workout video at whatever intensity you like.', 'Walk or bike to a spot nearby you have never actually been.', 'Pack a small comfort bag for your worst brain days.', 'Ten minutes journaling, ten minutes stretching, ten minutes staring.' ], 60: [ 'Long walk or bike ride somewhere with trees.', 'One-hour hobby sprint — make something terrible and be proud.', 'Bake something from scratch. Eat evidence.', 'One-room tidy: music loud, timer rules, no mercy.', 'Wandering trip (thrift store, library) with a $10 budget.', 'Digital reset: close twenty tabs, clear the inbox as far as you can.', 'Follow a full workout or yoga class at home.', 'Meal-prep one lunch so tomorrow-you says thanks.', 'Take a notebook to a café or library and people-watch or sketch.', 'Watch a documentary about something absurd and become an expert.' ] }; var lastPick = null; function menuPick(mins) { var pool = MENU[mins].filter(function (x) { return x !== lastPick; }); var item = pool[Math.floor(Math.random() * pool.length)]; lastPick = item; $('#menu-text').textContent = item; $('#menu-item').hidden = false; $('#menu-actions').hidden = false; } $$('.menu-pick').forEach(function (b) { b.addEventListener('click', function () { menuPick(+b.dataset.min); }); }); $('#menu-reroll').addEventListener('click', function () { var mins = $$('.menu-pick').filter(function (b) { return b.classList.contains('on'); })[0]; menuPick(mins ? +mins.dataset.min : 5); }); function paintStreak() { var s = store('streak'); $('#menu-streak').innerHTML = s && s.streak ? 'Streak: ' + s.streak + ' day' + (s.streak > 1 ? 's' : '') + ' of healthy dopamine. ' + (s.last === today() ? 'Today is already logged.' : '') : ''; } $('#menu-done').addEventListener('click', function () { var s = store('streak') || { last: '', streak: 0 }; if (s.last !== today()) { var y = new Date(Date.now() - 86400000).toISOString().slice(0, 10); s.streak = (s.last === y) ? s.streak + 1 : 1; s.last = today(); store('streak', s); } $('#menu-item').hidden = true; $('#menu-actions').hidden = true; paintStreak(); }); paintStreak(); /* ---------- one thing ---------- */ var nowSaved = store('now'); if (nowSaved && nowSaved.text) { $('#now-text').textContent = nowSaved.text; $('#now-form').hidden = true; $('#now-view').hidden = false; } $('#now-form').addEventListener('submit', function (e) { e.preventDefault(); var t = $('#now-input').value.trim(); if (!t) return; store('now', { text: t }); $('#now-text').textContent = t; $('#now-form').hidden = true; $('#now-view').hidden = false; }); $('#now-edit').addEventListener('click', function () { $('#now-view').hidden = true; $('#now-form').hidden = false; $('#now-input').value = ''; $('#now-input').focus(); }); $('#now-done').addEventListener('click', function () { localStorage.removeItem('myadhd.now'); $('#now-view').hidden = true; $('#now-form').hidden = false; $('#now-input').value = ''; $('#now-input').focus(); }); })();