script.js

Anonymous
javascript
02/28/2026 9:40 PM
21.6 KB
14
Indexable
/*
    Full app script: tasks, timer, planner, analytics, achievements, settings, UI interactions
*/

const DB = {
    tasks: JSON.parse(localStorage.getItem('ssc_tasks')||'[]'),
    xp: Number(localStorage.getItem('ssc_xp')||0),
    streak: Number(localStorage.getItem('ssc_streak')||0),
    focusMinutesToday: Number(localStorage.getItem('ssc_focus_today')||0),
    weeklyFocus: JSON.parse(localStorage.getItem('ssc_weekly')||'[]'),
    badges: JSON.parse(localStorage.getItem('ssc_badges')||'{}'),
    settings: JSON.parse(localStorage.getItem('ssc_settings')||JSON.stringify({theme:'dark',durations:{focus:25,short:5,long:15},autoSwitch:false,sound:true,name:'Student'})),
    timerState: JSON.parse(localStorage.getItem('ssc_timer')||'{}')
};

/* ---------- Persistence helpers ---------- */
function saveDB(){
    localStorage.setItem('ssc_tasks', JSON.stringify(DB.tasks));
    localStorage.setItem('ssc_xp', String(DB.xp));
    localStorage.setItem('ssc_streak', String(DB.streak));
    localStorage.setItem('ssc_focus_today', String(DB.focusMinutesToday));
    localStorage.setItem('ssc_weekly', JSON.stringify(DB.weeklyFocus));
    localStorage.setItem('ssc_badges', JSON.stringify(DB.badges));
    localStorage.setItem('ssc_settings', JSON.stringify(DB.settings));
    localStorage.setItem('ssc_timer', JSON.stringify(DB.timerState));
}

/* ---------- Navigation ---------- */
document.querySelectorAll('.nav-item').forEach(btn=>{
    btn.addEventListener('click', ()=>{
        document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active'));
        btn.classList.add('active');
        showSection(btn.dataset.section);
    });
});

function showSection(id){
    document.querySelectorAll('.app-section').forEach(s=>s.classList.remove('active'));
    const sec = document.getElementById(id);
    if(sec) sec.classList.add('active');
}

/* ---------- Greeting & Date ---------- */
function updateGreeting(){
    const name = DB.settings.name || 'Student';
    const h = new Date().getHours();
    const timeOfDay = h<12? 'Good morning': h<18? 'Good afternoon' : 'Good evening';
    document.getElementById('greeting').innerText = `${timeOfDay}, ${name}!`;
    document.getElementById('todayDate').innerText = new Date().toLocaleDateString();
}

/* ---------- Tasks ---------- */
function renderTasks(filter='all'){
    const list = document.getElementById('taskList');
    list.innerHTML='';
    const tasks = DB.tasks.filter(t=>{
        if(filter==='all') return true;
        if(filter==='active') return !t.completed;
        return t.completed;
    });
    if(tasks.length===0){document.getElementById('emptyTasks').style.display='block';} else {document.getElementById('emptyTasks').style.display='none'}
    tasks.forEach((t,i)=>{
        const li=document.createElement('li'); li.className='task-item'+(t.completed?' completed':'');
        li.innerHTML = `<div class="task-meta"><button class="toggle" data-i="${i}" aria-label="Toggle complete">${t.completed?'βœ…':'⬜'}</button><div class="title">${escapeHtml(t.title)}</div><div class="due">${t.due? 'Due '+t.due:''}</div></div><div><span class="task-priority ${t.priority}">${t.priority==='high'?'πŸ”΄':t.priority==='medium'?'🟑':'🟒'}</span> <button class="del" data-i="${i}" aria-label="Delete task">βœ•</button></div>`;
        list.appendChild(li);
        li.animate([{opacity:0, transform:'translateX(-12px)'},{opacity:1, transform:'none'}],{duration:350,easing:'ease'});
    });
    // listeners
    list.querySelectorAll('.del').forEach(btn=>btn.addEventListener('click', (e)=>{ const i=Number(e.currentTarget.dataset.i); deleteTask(i); }));
    list.querySelectorAll('.toggle').forEach(btn=>btn.addEventListener('click', (e)=>{ const i=Number(e.currentTarget.dataset.i); toggleTask(i); }));
}

function addTaskFromControls(){
    const title = document.getElementById('taskText').value.trim();
    const priority = document.getElementById('taskPriority').value;
    const due = document.getElementById('taskDue').value||null;
    if(!title) return toast('Please enter a task title');
    DB.tasks.unshift({title,priority,due,completed:false,created:Date.now()}); saveDB(); renderTasks(currentFilter);
}

function quickAdd(){
    const title = document.getElementById('quickTask').value.trim();
    const priority = document.getElementById('quickPriority').value;
    if(!title) return;
    DB.tasks.unshift({title,priority,due:null,completed:false,created:Date.now()}); saveDB(); renderTasks(currentFilter);
    document.getElementById('quickTask').value='';
}

function toggleTask(i){ if(!DB.tasks[i]) return; DB.tasks[i].completed=!DB.tasks[i].completed; saveDB(); renderTasks(currentFilter); checkBadges(); }
function deleteTask(i){ if(!DB.tasks[i]) return; const removed = DB.tasks.splice(i,1); saveDB(); renderTasks(currentFilter); animateDelete(); }

function animateDelete(){/* small visual scale */}

let currentFilter='all';
document.querySelectorAll('.filter').forEach(b=>b.addEventListener('click', e=>{ document.querySelectorAll('.filter').forEach(x=>x.classList.remove('active')); e.currentTarget.classList.add('active'); currentFilter=e.currentTarget.dataset.filter; renderTasks(currentFilter); }));

/* ---------- Timer (robust with Date.now) ---------- */
let timerHandle=null;
let running=false;
let sessionType='focus';
let sessionCount=0;
function setSessionType(t){ sessionType=t; document.querySelectorAll('.type').forEach(x=>x.classList.toggle('active', x.dataset.type===t)); }
document.querySelectorAll('.type').forEach(b=>b.addEventListener('click', e=>{ setSessionType(e.currentTarget.dataset.type); }));

function getDurationFor(type){ const s=DB.settings.durations; return (type==='focus'?s.focus:type==='short'?s.short:s.long)*60; }

function startTimer(){
    if(running) return pauseTimer();
    const now=Date.now();
    const duration = getDurationFor(sessionType);
    DB.timerState = DB.timerState||{};
    DB.timerState.endAt = now + (duration*1000);
    DB.timerState.type = sessionType;
    DB.timerState.running = true;
    saveDB();
    running=true; tick(); updateRing(); document.getElementById('startPause').innerText='Pause';
}

function pauseTimer(){ running=false; clearTimeout(timerHandle); DB.timerState.running=false; saveDB(); document.getElementById('startPause').innerText='Start'; }

function resetTimer(){ running=false; clearTimeout(timerHandle); DB.timerState={}; saveDB(); updateTimerDisplay( getDurationFor(sessionType) ); updateRing(0); document.getElementById('startPause').innerText='Start'; }

function tick(){ clearTimeout(timerHandle); if(!DB.timerState || !DB.timerState.endAt){ running=false; return; } const remainingMs = DB.timerState.endAt - Date.now(); if(remainingMs<=0){ onTimerComplete(); return; } updateTimerDisplay(Math.ceil(remainingMs/1000)); updateRing(remainingMs/getDurationFor(DB.timerState.type)/1000); timerHandle = setTimeout(tick, 500); }

function updateTimerDisplay(sec){ const el = document.getElementById('timerDisplay'); if(!el) return; const m=Math.floor(sec/60); const s=sec%60; el.innerText = `${m}:${s<10?'0':''}${s}`; }

function updateRing(progressRatio=1){ const circle = document.querySelector('.ring-progress'); const radius=50; const circumference = 2*Math.PI*radius; if(!circle) return; const offset = circumference * (1 - (progressRatio||1)); circle.style.strokeDasharray = String(circumference); circle.style.strokeDashoffset = String(offset); if(running) document.querySelector('.ring').classList.add('running'); else document.querySelector('.ring').classList.remove('running'); }

function onTimerComplete(){ running=false; clearTimeout(timerHandle); DB.timerState={}; saveDB(); playSound(); addFocusMinutes(getDurationFor(sessionType)/60); sessionCount+= (sessionType==='focus'?1:0); updateSessionDots(); toast('Session complete', {type:'success'}); if(DB.settings.autoSwitch && sessionType==='focus'){ sessionType='short'; setSessionType('short'); startTimer(); } else { document.getElementById('startPause').innerText='Start'; }
    checkBadges();
}

function playSound(){ if(DB.settings.sound){ const audio=new Audio(); audio.src='data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQAAAAA='; audio.play().catch(()=>{}); } }

document.getElementById('startPause').addEventListener('click', ()=>{ if(running) pauseTimer(); else startTimer(); });
document.getElementById('resetTimer').addEventListener('click', resetTimer);
document.getElementById('autoSwitch').addEventListener('change', e=>{ DB.settings.autoSwitch=e.target.checked; saveDB(); });

function updateSessionDots(){ const wrap=document.getElementById('sessionDots'); wrap.innerHTML=''; for(let i=0;i<4;i++){ const d=document.createElement('div'); d.className='dot'+(i<sessionCount?' active':''); wrap.appendChild(d); } }

/* ---------- Planner ---------- */
function generatePlan(){ const subj = document.getElementById('planSubject').value.trim(); const hrs = Number(document.getElementById('planHours').value)||1; if(!subj) return toast('Please enter a subject'); const steps = [];
    const blocks = Math.max(1, Math.ceil(hrs*60/25)); for(let i=1;i<=blocks;i++){ steps.push({title:`Focus Session ${i} β€” 25 min`,done:false}); steps.push({title:'Short Break β€” 5 min',done:false}); }
    const ol=document.getElementById('planSteps'); ol.innerHTML=''; steps.forEach((s,idx)=>{ const li=document.createElement('li'); li.className='plan-step'; li.innerHTML=`<input type="checkbox" data-idx="${idx}"> <div>${escapeHtml(s.title)}</div>`; ol.appendChild(li); li.querySelector('input').addEventListener('change', e=>{ s.done=e.target.checked; }); });
}

/* ---------- Quotes (fake AI) ---------- */
const QUOTES={productivity:[{q:'Don’t count the days β€” make the days count.',a:'Muhammad Ali'},{q:'Focus is a superpower.',a:'Unknown'}],mindset:[{q:'Small steps every day yield big results.',a:'Unknown'},{q:'Progress over perfection.',a:'Unknown'}],discipline:[{q:'Discipline is doing what needs to be done.',a:'Unknown'}]};
function shuffleQuote(){ const cat = document.getElementById('quoteCategory').value; const list=QUOTES[cat]||[]; const pick=list[Math.floor(Math.random()*list.length)]; if(!pick) return; const container=document.getElementById('quoteArea'); container.classList.add('flip'); setTimeout(()=>{ document.getElementById('quoteText').innerText = `"${pick.q}"`; document.getElementById('quoteAuthor').innerText = 'β€” '+pick.a; container.classList.remove('flip'); },300); }

/* ---------- Achievements & Badges ---------- */
const BADGE_DEFS = [{id:'first_task',label:'First Task'},{id:'3day',label:'3-Day Streak'},{id:'pomodoro_pro',label:'Pomodoro Pro'},{id:'century',label:'Century'},{id:'night_owl',label:'Night Owl'}];
function renderBadges(){ const grid=document.getElementById('badgesGrid'); grid.innerHTML=''; BADGE_DEFS.forEach(b=>{ const div=document.createElement('div'); div.className='badge'+(DB.badges[b.id]?'':' locked'); div.id='badge-'+b.id; div.innerHTML=`<div class="badge-icon">πŸ…</div><div class="badge-label">${b.label}</div>`; grid.appendChild(div); if(DB.badges[b.id]) div.style.animation='badgePop .45s ease'; }); }

function unlockBadge(id){ if(DB.badges[id]) return; DB.badges[id]=Date.now(); saveDB(); renderBadges(); toast(`Badge unlocked: ${id}`); }

function checkBadges(){ if(DB.tasks.length>0) unlockBadge('first_task'); if(DB.xp>=100) unlockBadge('century'); if(sessionCount>=10) unlockBadge('pomodoro_pro'); const hour = new Date().getHours(); if(hour>=22) unlockBadge('night_owl'); }

/* ---------- Analytics (weekly chart & heatmap) ---------- */
function renderWeekly(){ const wrap=document.getElementById('weeklyChart'); wrap.innerHTML=''; const week = DB.weeklyFocus.length?DB.weeklyFocus: [0,0,0,0,0,0,0]; week.forEach(v=>{ const b=document.createElement('div'); b.className='bar'; b.style.height = Math.min(100, v/2)+'%'; wrap.appendChild(b); }); document.getElementById('totalFocus').innerText = DB.weeklyFocus.reduce((a,b)=>a+b,0); }

function renderHeatmap(){ const wrap=document.getElementById('heatmap'); wrap.innerHTML=''; const last7 = DB.weeklyFocus.slice(-7); for(let i=0;i<7;i++){ const d=document.createElement('div'); d.className='heat-day'; const val = last7[i]||0; if(val>40) d.style.background= 'linear-gradient(90deg,var(--accent),var(--accent-2))'; else if(val>20) d.style.background='var(--accent)'; wrap.appendChild(d);} }

/* ---------- Settings ---------- */
document.getElementById('userName').addEventListener('input', e=>{ DB.settings.name = e.target.value; saveDB(); updateGreeting(); });
document.getElementById('themeSelect').addEventListener('change', e=>{ applyTheme(e.target.value); DB.settings.theme=e.target.value; saveDB(); });
function applyTheme(name){ if(name==='midnight'){ document.documentElement.style.setProperty('--accent','#8ab4ff'); } else if(name==='forest'){ document.documentElement.style.setProperty('--accent','#3bb77e'); } else { document.documentElement.style.setProperty('--accent','#4f8cff'); } }

document.getElementById('durFocus').addEventListener('change', e=>{ DB.settings.durations.focus = Number(e.target.value); saveDB(); });
document.getElementById('durShort').addEventListener('change', e=>{ DB.settings.durations.short = Number(e.target.value); saveDB(); });
document.getElementById('durLong').addEventListener('change', e=>{ DB.settings.durations.long = Number(e.target.value); saveDB(); });
document.getElementById('soundToggle').addEventListener('change', e=>{ DB.settings.sound = e.target.checked; saveDB(); });
document.getElementById('clearData').addEventListener('click', ()=>{ if(confirm('Clear all saved data?')){ localStorage.clear(); location.reload(); } });

/* ---------- Toasts ---------- */
function toast(msg, opts={}){ const c=document.getElementById('toastContainer'); const t=document.createElement('div'); t.className='toast'; t.innerText=msg; c.appendChild(t); setTimeout(()=>t.remove(), 3500); }

/* ---------- Keyboard shortcuts ---------- */
document.addEventListener('keydown', e=>{
    if(e.target.tagName==='INPUT' || e.target.tagName==='TEXTAREA') return;
    if(e.code==='Space'){ e.preventDefault(); running? pauseTimer(): startTimer(); }
    if(e.key.toLowerCase()==='n'){ document.getElementById('quickTask').focus(); }
    if(e.key.toLowerCase()==='m'){ shuffleQuote(); }
    if(e.key==='?'){ alert('Shortcuts: Space=start/pause, N=quick task, M=motivation'); }
});

/* ---------- Utilities ---------- */
function escapeHtml(text){ return String(text).replace(/[&<>\"]/g, c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]||c)); }

/* ---------- Bind UI controls ---------- */
document.getElementById('addTaskBtn').addEventListener('click', addTaskFromControls);
document.getElementById('quickAddBtn').addEventListener('click', quickAdd);
document.getElementById('genMotivation').addEventListener('click', shuffleQuote);
document.getElementById('shuffleQuote').addEventListener('click', shuffleQuote);
document.getElementById('generatePlanBtn').addEventListener('click', generatePlan);
document.getElementById('startFocusNow').addEventListener('click', ()=>{ setSessionType('focus'); startTimer(); });
document.getElementById('openTasks').addEventListener('click', ()=>{ document.querySelector('[data-section="tasks"]').click(); });

/* ---------- Init ---------- */
function init(){
    // fill initial UI values
    document.getElementById('userName').value = DB.settings.name || '';
    document.getElementById('durFocus').value = DB.settings.durations.focus;
    document.getElementById('durShort').value = DB.settings.durations.short;
    document.getElementById('durLong').value = DB.settings.durations.long;
    document.getElementById('autoSwitch').checked = DB.settings.autoSwitch;
    document.getElementById('soundToggle').checked = DB.settings.sound;
    document.getElementById('miniXP').innerText = DB.xp;
    document.getElementById('miniStreak').innerText = DB.streak;
    updateGreeting(); renderTasks(); renderBadges(); renderWeekly(); renderHeatmap(); updateSessionDots(); applyTheme(DB.settings.theme);
    // restore timer if running
    if(DB.timerState && DB.timerState.endAt && DB.timerState.endAt > Date.now()){ sessionType = DB.timerState.type || 'focus'; running = true; tick(); document.getElementById('startPause').innerText='Pause'; }
}

init();

Editor is loading...
Leave a Comment