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=>({'&':'&','<':'<','>':'>','"':'"'}[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