';
}).join('');
}
// ═══════════════════════════════════════════
// SETTINGS
// ═══════════════════════════════════════════
function openSettings() { goTo('settings'); }
function renderSettings() {
var vl = document.getElementById('app-version-label');
if (vl) vl.textContent = 'v' + APP_VERSION + ' · ' + APP_BUILD;
updateNotifToggles();
initCompactMode();
initAccentColor();
const profile = DB.get('profile') || {};
updateGroqStatus();
const nameEl = document.getElementById('cfg-name');
const roleEl = document.getElementById('cfg-role');
if (nameEl) nameEl.value = profile.name || '';
if (roleEl) roleEl.value = profile.role || '';
const emailEl = document.getElementById('cfg-email-lbl');
if (emailEl && window._fbUser) emailEl.textContent = window._fbUser.email || '—';
updateThemeBtns();
updateLangBtns();
updateCatColorInputs();
gcalUpdateUI();
}
function saveProfile() {
const name = document.getElementById('cfg-name').value.trim();
const role = document.getElementById('cfg-role').value.trim();
DB.set('profile', { name, role });
const sb = document.getElementById('sb-user');
if (sb) sb.textContent = name || (window._fbUser ? window._fbUser.displayName : '—');
toast(t('profile_saved'));
}
function exportData() {
const data = { tasks: getTasks(), routines: getRoutines(), projects: getProjects(), library: getLib(), exported: new Date().toISOString() };
const blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'fastfluent-backup-'+dk(today0())+'.json';
a.click();
toast(t('exported'));
}
function clearAllData() {
if (!confirm(t('confirm_clear'))) return;
['tasks','routines','projects','library','profile'].forEach(k => localStorage.removeItem('ffos_'+k));
render();
toast(t('data_cleared'));
}
// ═══════════════════════════════════════════
// THEME
// ═══════════════════════════════════════════
function setTheme(t) {
document.documentElement.setAttribute('data-theme', t === 'dark' ? 'dark' : '');
localStorage.setItem('ffos_theme', t);
updateThemeBtns();
toast(t === 'dark' ? '🌙 Tema escuro' : '☀️ Tema claro');
}
function toggleTheme() { setTheme((localStorage.getItem('ffos_theme')||'light') === 'dark' ? 'light' : 'dark'); }
function updateThemeBtns() {
const t = localStorage.getItem('ffos_theme') || 'light';
const l = document.getElementById('btn-theme-light');
const d = document.getElementById('btn-theme-dark');
if (l) l.className = 'settings-btn'+(t==='light'?' active':'');
if (d) d.className = 'settings-btn'+(t==='dark'?' active':'');
const btn = document.getElementById('theme-btn');
if (btn) btn.textContent = t === 'dark' ? '☀' : '◐';
}
// ═══════════════════════════════════════════
// LANG
// ═══════════════════════════════════════════
let lang = localStorage.getItem('ffos_lang') || 'pt';
// ── TRANSLATIONS ──
var TX = {
pt: {
nav_today:'Hoje', nav_tasks:'Tarefas', nav_routines:'Rotinas',
nav_projects:'Projetos', nav_lib:'Biblioteca', nav_settings:'Configurações',
sb_capture:'CAPTURAR', sb_voice:'VOZ RÁPIDA', sb_search:'PESQUISAR',
sb_ask:'PERGUNTAR', sb_logout:'SAIR',
today_top3:'TOP 3 DO DIA', today_upnext:'EM SEGUIDA', today_viewall:'Ver tudo →',
today_alltasks:'TODAS ABERTAS', today_addtask:'+ Nova tarefa',
today_slipping:'ESCORREGANDO', today_routines:'ROTINAS',
today_resurf:'RELEMBRAR', today_review:'REVISAR',
today_no_events:'Nenhum evento hoje.',
today_no_tasks:'Nenhuma tarefa para hoje.',
today_no_slip:'Nenhuma tarefa esquecida.',
today_no_routine:'Nenhuma rotina cadastrada.',
today_no_review:'Nada para revisar.',
today_top3_empty:'⭐ Marque uma tarefa com estrela para definir o top 3.',
today_vaga:'(vaga aberta)',
today_focus:'⊙ Foco', today_focus_exit:'✕ Sair do foco',
tasks_title:'Tarefas', tasks_new:'+ Nova tarefa',
tasks_all:'Todas', tasks_open:'Abertas', tasks_done:'Concluídas',
tasks_overdue:'Atrasadas', tasks_none:'Nenhuma tarefa.',
routines_title:'Rotinas', routines_new:'+ Nova rotina',
routines_none:'Nenhuma rotina.',
period_manha:'☀️ Manhã', period_tarde:'🌤 Tarde', period_noite:'🌙 Noite',
projects_title:'Projetos', projects_new:'+ Novo projeto', projects_none:'Nenhum projeto.',
lib_title:'Biblioteca', lib_add:'+ Adicionar',
lib_notes:'Notas', lib_quotes:'Citações', lib_books:'Livros', lib_none:'Nenhum item.',
settings_title:'Configurações',
cfg_profile:'PERFIL', cfg_name:'Nome', cfg_role:'Cargo', cfg_account:'Conta Google',
cfg_logout:'Sair', cfg_appearance:'APARÊNCIA', cfg_theme:'Tema',
cfg_theme_light:'☀️ Claro', cfg_theme_dark:'🌙 Escuro',
cfg_colors:'Cores de categoria', cfg_lang:'IDIOMA', cfg_lang_label:'Idioma do app',
cfg_gcal:'GOOGLE CALENDAR', cfg_gcal_sub:'Eventos aparecem em "Em Seguida"',
cfg_gcal_connect:'🔗 Conectar', cfg_gcal_disconnect:'✕ Desconectar',
cfg_gcal_on:'✓ Conectado', cfg_gcal_off:'Não conectado',
cfg_data:'DADOS', cfg_export:'Exportar', cfg_export_sub:'Backup JSON de todos os dados',
cfg_clear:'Limpar tudo', cfg_clear_sub:'Remove todos os dados permanentemente',
modal_task_new:'Nova Tarefa', modal_task_edit:'Editar Tarefa',
modal_task_name:'Nome', modal_task_date:'Data', modal_task_time:'Horário',
modal_task_cat:'Categoria', modal_task_notes:'Notas', modal_task_ph:'O que precisa ser feito?',
modal_task_notes_ph:'Notas adicionais...',
modal_task_del:'Excluir', modal_cancel:'Cancelar', modal_save:'Salvar',
cat_none:'— Sem categoria —', cat_aula:'🟣 Aulas', cat_admin:'🔵 Admin',
cat_cobranca:'🟡 Cobranças', cat_pessoal:'🩷 Pessoal',
modal_routine_new:'Nova Rotina', modal_routine_edit:'Editar Rotina',
modal_routine_name:'Nome', modal_routine_period:'Período', modal_routine_goal:'Meta de dias (streak)',
modal_proj_new:'Novo Projeto', modal_proj_edit:'Editar Projeto',
modal_proj_name:'Nome', modal_proj_desc:'Descrição', modal_proj_deadline:'Prazo',
modal_proj_status:'Status', status_ativo:'Ativo', status_pausado:'Pausado', status_concluido:'Concluído',
modal_lib_new:'Adicionar', modal_lib_edit:'Editar',
modal_lib_title:'Título', modal_lib_body:'Conteúdo', modal_lib_tags:'Tags',
modal_lib_review:'Marcar para revisão', modal_lib_yes:'Sim', modal_lib_no:'Não',
ask_placeholder:'Pergunte sobre suas tarefas...',
ask_welcome:'Olá! Posso responder perguntas sobre suas tarefas e rotinas.',
search_placeholder:'Pesquisar tarefas, rotinas, projetos...',
search_none:'Nenhum resultado.',
gcal_connect_prompt:'Conectar Google Calendar para ver eventos',
streak_days:'dias · meta:',
reviewed:'Marcado como revisado!',
saved:'Salvo!', deleted:'Excluído!',
confirm_delete_task:'Excluir esta tarefa?',
confirm_delete_routine:'Excluir esta rotina?',
confirm_delete_project:'Excluir este projeto?',
confirm_delete_lib:'Excluir?',
confirm_clear:'Tem certeza? Isso apaga TODOS os dados permanentemente.',
exported:'Exportado!', data_cleared:'Dados limpos.',
profile_saved:'Perfil salvo!',
mic_listening:'🔴 Ouvindo...',
mic_no_browser:'Reconhecimento de voz não disponível. Use o Chrome.',
mic_no_permission:'❌ Permita o acesso ao microfone nas configurações do Chrome.',
days:['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'],
months:['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
short_days:['Dom','Seg','Ter','Qua','Qui','Sex','Sab']
},
en: {
nav_today:'Today', nav_tasks:'Tasks', nav_routines:'Routines',
nav_projects:'Projects', nav_lib:'Library', nav_settings:'Settings',
sb_capture:'CAPTURE', sb_voice:'QUICK VOICE', sb_search:'SEARCH',
sb_ask:'ASK', sb_logout:'SIGN OUT',
today_top3:'TOP 3 TODAY', today_upnext:'UP NEXT', today_viewall:'View all →',
today_alltasks:'ALL OPEN', today_addtask:'+ New task',
today_slipping:'SLIPPING', today_routines:'ROUTINES',
today_resurf:'RESURFACING', today_review:'NEEDS REVIEW',
today_no_events:'No events today.',
today_no_tasks:t('today_no_tasks'),
today_no_slip:t('today_no_slip'),
today_no_routine:t('today_no_routine'),
today_no_review:t('today_no_review'),
today_vaga:'(open slot)',
today_top3_empty:'⭐ Star a task below to set your top 3.',
today_vaga:t('today_vaga'),
today_focus:'⊙ Focus', today_focus_exit:'✕ Exit focus',
tasks_title:'Tasks', tasks_new:'+ New task',
tasks_all:'All', tasks_open:'Open', tasks_done:'Done',
tasks_overdue:'Overdue', tasks_none:t('tasks_none'),
routines_title:'Routines', routines_new:'+ New routine',
routines_none:t('routines_none'),
period_manha:'☀️ Morning', period_tarde:'🌤 Afternoon', period_noite:'🌙 Evening',
projects_title:'Projects', projects_new:'+ New project', projects_none:t('projects_none'),
lib_title:'Library', lib_add:'+ Add',
lib_notes:'Notes', lib_quotes:'Quotes', lib_books:'Books', lib_none:t('lib_none'),
settings_title:'Settings',
cfg_profile:'PROFILE', cfg_name:'Name', cfg_role:'Role', cfg_account:'Google Account',
cfg_logout:'Sign out', cfg_appearance:'APPEARANCE', cfg_theme:'Theme',
cfg_theme_light:'☀️ Light', cfg_theme_dark:'🌙 Dark',
cfg_colors:'Category colors', cfg_lang:'LANGUAGE', cfg_lang_label:'App language',
cfg_gcal:'GOOGLE CALENDAR', cfg_gcal_sub:'Events appear in "Up Next"',
cfg_gcal_connect:'🔗 Connect', cfg_gcal_disconnect:'✕ Disconnect',
cfg_gcal_on:'✓ Connected', cfg_gcal_off:'Not connected',
cfg_data:'DATA', cfg_export:'Export', cfg_export_sub:'JSON backup of all data',
cfg_clear:'Clear all', cfg_clear_sub:'Permanently removes all data',
modal_task_new:'New Task', modal_task_edit:'Edit Task',
modal_task_name:'Name', modal_task_date:'Date', modal_task_time:'Time',
modal_task_cat:'Category', modal_task_notes:'Notes', modal_task_ph:'What needs to be done?',
modal_task_notes_ph:'Additional notes...',
modal_task_del:'Delete', modal_cancel:'Cancel', modal_save:'Save',
cat_none:'— No category —', cat_aula:'🟣 Classes', cat_admin:'🔵 Admin',
cat_cobranca:'🟡 Billing', cat_pessoal:'🩷 Personal',
modal_routine_new:'New Routine', modal_routine_edit:'Edit Routine',
modal_routine_name:'Name', modal_routine_period:'Period', modal_routine_goal:'Streak goal (days)',
modal_proj_new:'New Project', modal_proj_edit:'Edit Project',
modal_proj_name:'Name', modal_proj_desc:'Description', modal_proj_deadline:'Deadline',
modal_proj_status:'Status', status_ativo:'Active', status_pausado:'Paused', status_concluido:'Completed',
modal_lib_new:'Add', modal_lib_edit:'Edit',
modal_lib_title:'Title', modal_lib_body:'Content', modal_lib_tags:'Tags',
modal_lib_review:'Flag for review', modal_lib_yes:'Yes', modal_lib_no:'No',
ask_placeholder:'Ask about your tasks...',
ask_welcome:'Hi! I can answer questions about your tasks and routines.',
search_placeholder:'Search tasks, routines, projects...',
search_none:'No results.',
gcal_connect_prompt:'Connect Google Calendar to see events',
streak_days:'days · goal:',
reviewed:'Marked as reviewed!',
saved:'Saved!', deleted:'Deleted!',
confirm_delete_task:'Delete this task?',
confirm_delete_routine:'Delete this routine?',
confirm_delete_project:'Delete this project?',
confirm_delete_lib:'Delete?',
confirm_clear:'Are you sure? This permanently deletes ALL data.',
exported:'Exported!', data_cleared:'Data cleared.',
profile_saved:'Profile saved!',
mic_listening:'🔴 Listening...',
mic_no_browser:'Voice recognition not available. Use Chrome.',
mic_no_permission:'❌ Please allow microphone access in Chrome settings.',
days:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
months:['January','February','March','April','May','June','July','August','September','October','November','December'],
short_days:['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
}
};
function t(key) {
try {
if (!window.TX) return key;
var l = lang || 'pt';
return (TX[l] && TX[l][key]) || (TX['pt'] && TX['pt'][key]) || key;
} catch(e) { return key; }
}
function setLang(l) {
lang = l;
localStorage.setItem('ffos_lang', l);
updateLangBtns();
applyTranslations();
render();
}
function applyTranslations() {
// Sidebar nav
var navItems = document.querySelectorAll('.nav-item');
var navKeys = ['nav_today','nav_tasks','nav_routines','nav_projects','nav_lib','nav_settings'];
navItems.forEach(function(el, i) {
var badge = el.querySelector('.nav-badge');
el.textContent = t(navKeys[i]);
if (badge) el.appendChild(badge);
});
// Sidebar footer
var footBtns = document.querySelectorAll('.sb-foot-btn span:first-child');
var footKeys = ['sb_capture','sb_voice','sb_search','sb_ask'];
footBtns.forEach(function(el, i) {
if (footKeys[i] && !el.closest('.danger')) el.textContent = t(footKeys[i]);
});
// Search placeholder
var si = document.getElementById('search-input');
if (si) si.placeholder = t('search_placeholder');
// Ask placeholder
var ai = document.getElementById('ask-input');
if (ai) ai.placeholder = t('ask_placeholder');
// Task modal fields
setElText('mo-task-name-lbl', t('modal_task_name'));
setElText('mo-task-date-lbl', t('modal_task_date'));
setElText('mo-task-time-lbl', t('modal_task_time'));
setElText('mo-task-cat-lbl', t('modal_task_cat'));
setElText('mo-task-notes-lbl', t('modal_task_notes'));
setElPlaceholder('t-name', t('modal_task_ph'));
setElPlaceholder('t-notes', t('modal_task_notes_ph'));
// Category options
var catEl = document.getElementById('t-cat');
if (catEl) catEl.innerHTML = ''
+''
+''
+''
+'';
}
function setElText(id, val) { var el=document.getElementById(id); if(el) el.textContent=val; }
function setElPlaceholder(id, val) { var el=document.getElementById(id); if(el) el.placeholder=val; }
function updateLangBtns() {
const pt = document.getElementById('btn-lang-pt');
const en = document.getElementById('btn-lang-en');
if (pt) pt.className = 'settings-btn'+(lang==='pt'?' active':'');
if (en) en.className = 'settings-btn'+(lang==='en'?' active':'');
}
// ═══════════════════════════════════════════
// CATEGORY COLORS
// ═══════════════════════════════════════════
function setCatColor(cat, color) {
document.documentElement.style.setProperty('--cat-'+cat, color);
const saved = JSON.parse(localStorage.getItem('ffos_cat_colors')||'{}');
saved[cat] = color;
localStorage.setItem('ffos_cat_colors', JSON.stringify(saved));
}
function applyCatColors() {
const saved = JSON.parse(localStorage.getItem('ffos_cat_colors')||'{}');
Object.entries(saved).forEach(([k,v]) => document.documentElement.style.setProperty('--cat-'+k, v));
}
function updateCatColorInputs() {
const saved = JSON.parse(localStorage.getItem('ffos_cat_colors')||'{}');
const defaults = { aula:'#7c3aed', admin:'#1d4ed8', cobranca:'#b45309', pessoal:'#be185d' };
Object.entries(defaults).forEach(([k,v]) => {
const el = document.getElementById('color-'+k);
if (el) el.value = saved[k] || v;
});
}
// ═══════════════════════════════════════════
// FOCUS MODE
// ═══════════════════════════════════════════
let _focus = false;
function toggleFocus() {
_focus = !_focus;
document.body.classList.toggle('focus', _focus);
const btn = document.getElementById('focus-btn');
if (btn) btn.textContent = _focus ? '✕ Sair do foco' : '⊙ Foco';
}
// ═══════════════════════════════════════════
// ASK
// ═══════════════════════════════════════════
var _askMsgs = [];
function openAsk() {
openMo('mo-ask');
if (!_askMsgs.length) {
_askMsgs = [{role:'assistant',text:t('ask_welcome')}];
renderAskMsgs();
}
setTimeout(function(){ var el=document.getElementById('ask-input'); if(el){el.value='';el.focus();} }, 100);
}
function renderAskMsgs() {
var el = document.getElementById('ask-msgs');
if (!el) return;
el.innerHTML = _askMsgs.map(function(m) {
return '
'+m.text+'
';
}).join('');
el.scrollTop = el.scrollHeight;
}
function sendAsk() {
var inp = document.getElementById('ask-input');
var q = inp ? inp.value.trim() : '';
if (!q) return;
if (inp) inp.value = '';
_askMsgs.push({role:'user', text:q});
_askMsgs.push({role:'assistant', text:'Pensando...'});
renderAskMsgs();
var todayStr = dk(today0());
var tasks = getTasks();
var open = tasks.filter(function(t){return !t.done;}).map(function(t){return t.name;}).join(', ');
var overdue = tasks.filter(function(t){return !t.done&&t.date&&t.date]+>/g,'')};})
)
})
}).then(function(r){return r.json();}).then(function(d){
var txt = d.choices&&d.choices[0] ? d.choices[0].message.content : 'Sem resposta.';
if(d.error) txt = 'Erro: '+(d.error.message||'tente novamente.');
_askMsgs[_askMsgs.length-1] = {role:'assistant', text: txt};
renderAskMsgs();
}).catch(function(){
_askMsgs[_askMsgs.length-1] = {role:'assistant', text:'Erro ao conectar.'};
renderAskMsgs();
});
}
// ═══════════════════════════════════════════
// MOBILE NAV
// ═══════════════════════════════════════════
function toggleSidebar() {
var sb = document.getElementById('sidebar');
var ov = document.getElementById('sidebar-overlay');
if (!sb) return;
var isOpen = sb.classList.contains('open');
sb.classList.toggle('open', !isOpen);
if (ov) ov.classList.toggle('show', !isOpen);
}
function closeSidebar() {
var sb = document.getElementById('sidebar');
var ov = document.getElementById('sidebar-overlay');
if (sb) sb.classList.remove('open');
if (ov) ov.classList.remove('show');
}
function toggleMobMore() {
var menu = document.getElementById('mob-more-menu');
if (menu) menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
}
function closeMobMore() {
var menu = document.getElementById('mob-more-menu');
if (menu) menu.style.display = 'none';
}
function updateMobNav(page) {
var pages = ['today','tasks','routines','projects'];
pages.forEach(function(p) {
var btn = document.getElementById('mob-btn-'+p);
if (btn) btn.classList.toggle('active', p === page);
});
var searchBtn = document.getElementById('mob-btn-search');
if (searchBtn) searchBtn.classList.remove('active');
}
// Render mobile sections (right panel shown inline on mobile)
function renderMobileSections() {
var todayStr = dk(today0());
// Slipping
var slipMob = document.getElementById('slipping-list-mob');
if (slipMob) {
var slipping = getTasks().filter(function(task) {
if (task.done || !task.updated) return false;
return Math.floor((new Date(todayStr) - new Date(task.updated)) / 86400000) >= 3;
}).slice(0,5);
if (!slipping.length) {
slipMob.innerHTML = '
'+t('today_no_slip')+'
';
} else {
slipMob.innerHTML = slipping.map(function(task) {
var days = Math.floor((new Date(todayStr) - new Date(task.updated)) / 86400000);
return '
'+escHtml(task.name.substring(0,35))+'
'+days+'d
';
}).join('');
}
}
// Routines
var rtMob = document.getElementById('routines-today-mob');
if (rtMob) {
var routines = getRoutines();
if (!routines.length) {
rtMob.innerHTML = '
'+t('today_no_routine')+'
';
} else {
var periods = [{key:'manha',label:t('period_manha')},{key:'tarde',label:t('period_tarde')},{key:'noite',label:t('period_noite')}];
var html2 = '';
periods.forEach(function(p) {
var pr = routines.filter(function(r) { return r.period === p.key; });
if (!pr.length) return;
html2 += '
'+p.label+'
';
pr.forEach(function(r) {
var done = (r.completions||[]).includes(todayStr);
var cc = done ? 'done' : '';
html2 += '
'
+ ''
+ '
'+escHtml(r.name)+'
'
+ '
'+streak7(r,todayStr)+'
'
+ '
';
});
});
rtMob.innerHTML = html2;
}
}
// Resurfacing
var resurf = getLib().filter(function(l) { return l.body && l.body.length > 20; });
var resurfMob = document.getElementById('resurf-content-mob');
var resurfSec = document.getElementById('resurf-sec-mob');
if (resurfMob) {
if (!resurf.length) {
if (resurfSec) resurfSec.style.display = 'none';
} else {
if (resurfSec) resurfSec.style.display = 'block';
var item = resurf[Math.floor(new Date().getDate() % resurf.length)];
resurfMob.innerHTML = '
"'+escHtml(item.body.substring(0,200))+'"
'
+ '
— '+(item.title||'Biblioteca')+'
';
}
}
// Review
var reviewMob = document.getElementById('review-list-mob');
var reviewCntMob = document.getElementById('review-count-mob');
if (reviewMob) {
var reviews = getLib().filter(function(l) { return l.review; });
if (reviewCntMob) reviewCntMob.textContent = reviews.length ? '· '+reviews.length : '';
if (!reviews.length) {
reviewMob.innerHTML = '
';
}
// ═══════════════════════════════════════════
// GROQ ONBOARDING BANNER
// ═══════════════════════════════════════════
function checkGroqOnboarding() {
var key = localStorage.getItem('ffos_groq_key');
var dismissed = localStorage.getItem('ffos_groq_dismissed');
var banner = document.getElementById('groq-banner');
if (!key && !dismissed && banner) {
setTimeout(function() { banner.style.display = 'block'; }, 3000);
}
}
// ═══════════════════════════════════════════
// TASK COMMENTS
// ═══════════════════════════════════════════
var _editComments = [];
function addTaskComment() {
var inp = document.getElementById('t-comment-input');
var text = inp ? inp.value.trim() : '';
if (!text) return;
_editComments.push({
id: uid(),
text: text,
date: dk(today0()),
time: new Date().toLocaleTimeString('pt-BR', {hour:'2-digit', minute:'2-digit'})
});
if (inp) inp.value = '';
renderCommentsModal();
}
function renderCommentsModal() {
var el = document.getElementById('t-comments-list');
if (!el) return;
if (!_editComments.length) { el.innerHTML = ''; return; }
el.innerHTML = _editComments.map(function(c) {
return '
'
+ '
' + c.date + ' ' + c.time + '
'
+ escHtml(c.text)
+ '
';
}).join('');
}
// ═══════════════════════════════════════════
// SWIPE BETWEEN PAGES (mobile)
// ═══════════════════════════════════════════
var _swipePageStart = 0;
var _swipePageActive = false;
var PAGE_ORDER = ['today', 'tasks', 'routines', 'projects', 'library'];
function initPageSwipe() {
if (window.innerWidth > 768) return;
var main = document.getElementById('main');
if (!main || main._pageSwipe) return;
main._pageSwipe = true;
main.addEventListener('touchstart', function(e) {
_swipePageStart = e.touches[0].clientX;
_swipePageActive = true;
}, {passive: true});
main.addEventListener('touchend', function(e) {
if (!_swipePageActive) return;
_swipePageActive = false;
var dx = e.changedTouches[0].clientX - _swipePageStart;
if (Math.abs(dx) < 60) return; // too small
var idx = PAGE_ORDER.indexOf(curPage);
if (idx < 0) return;
if (dx < -60 && idx < PAGE_ORDER.length - 1) {
goTo(PAGE_ORDER[idx + 1]);
} else if (dx > 60 && idx > 0) {
goTo(PAGE_ORDER[idx - 1]);
}
}, {passive: true});
}
// ═══════════════════════════════════════════
// TASK SEARCH (in notes and tags)
// ═══════════════════════════════════════════
function getTaskSearchQuery() {
var el = document.getElementById('tasks-search');
return el ? el.value.trim().toLowerCase() : '';
}
// ═══════════════════════════════════════════
// CAPTURE REPROCESS
// ═══════════════════════════════════════════
function reprocessCapture(idx) {
var captures = JSON.parse(localStorage.getItem('ffos_captures') || '[]');
var cap = captures[idx];
if (!cap) return;
openVoiceDiary();
setTimeout(function() {
_vdTranscript = cap.text;
var transcript = document.getElementById('vd-transcript');
if (transcript) { transcript.textContent = cap.text; transcript.style.display = 'block'; }
var label = document.getElementById('vd-state-label');
if (label) label.textContent = 'Reprocessando...';
processVoiceWithAI(cap.text);
}, 300);
}
// ═══════════════════════════════════════════
// DASHBOARD
// ═══════════════════════════════════════════
function renderDashboard() {
var tasks = getTasks();
var todayStr = dk(today0());
// Done today
var doneToday = tasks.filter(function(t){ return t.done && t.updated === todayStr; }).length;
var el = document.getElementById('dash-done-today');
if (el) el.textContent = doneToday;
// Open tasks
var open = tasks.filter(function(t){ return !t.done; }).length;
var el2 = document.getElementById('dash-open');
if (el2) el2.textContent = open;
// Weekly chart
renderWeeklyChart(tasks);
// Streaks
renderDashStreaks();
// Voice captures
renderDashCaptures();
}
function renderWeeklyChart(tasks) {
var el = document.getElementById('dash-chart');
if (!el) return;
var days = [];
var shortDays = TX[lang] ? TX[lang].short_days : ['Dom','Seg','Ter','Qua','Qui','Sex','Sab'];
for (var i = 6; i >= 0; i--) {
var d = new Date(today0());
d.setDate(d.getDate() - i);
days.push({ str: dk(d), label: shortDays[d.getDay()], isToday: i === 0 });
}
var max = 1;
var counts = days.map(function(day) {
var count = tasks.filter(function(t){ return t.done && t.updated === day.str; }).length;
if (count > max) max = count;
return count;
});
el.innerHTML = days.map(function(day, i) {
var count = counts[i];
var pct = Math.max(4, Math.round((count / max) * 100));
var barClass = 'chart-bar' + (day.isToday ? ' today' : '') + (count === 0 ? ' empty' : '');
return '
'
+ '
' + (count || '') + '
'
+ ''
+ '
' + day.label + '
'
+ '
';
}).join('');
}
function renderDashStreaks() {
var el = document.getElementById('dash-streaks');
if (!el) return;
var routines = getRoutines();
if (!routines.length) {
el.innerHTML = '
';
}).join('');
}
function renderDashCaptures() {
var el = document.getElementById('dash-captures');
if (!el) return;
var captures = JSON.parse(localStorage.getItem('ffos_captures') || '[]');
if (!captures.length) {
el.innerHTML = '
';
}).join('');
}
openMo('mo-notif');
}
function clearNotifications() {
_notifications = [];
localStorage.removeItem('ffos_notifications');
updateBellBadge();
closeMo('mo-notif');
toast('Notificações limpas.');
}
// Init notifications from storage
function initNotifications() {
_notifications = JSON.parse(localStorage.getItem('ffos_notifications') || '[]');
updateBellBadge();
}
// Override showPushNotification to also add to notification center
var _origShowPush = null;
function initNotifOverride() {
_origShowPush = showPushNotification;
showPushNotification = function(title, body) {
addNotification('🔔', title, body);
if (Notification.permission === 'granted') {
try { new Notification(title, {body:body, icon:'/icon-192.png'}); } catch(e) {
showPushBanner(title + ' — ' + body);
}
} else {
showPushBanner(title + ' — ' + body);
}
};
}
// ═══════════════════════════════════════════
// CREATE GOOGLE CALENDAR EVENT
// ═══════════════════════════════════════════
function openCreateEvent() {
var el = document.getElementById('ev-date');
if (el) el.value = dk(today0());
var s = document.getElementById('ev-start');
if (s) s.value = new Date().toTimeString().substring(0,5);
var e2 = document.getElementById('ev-end');
if (e2) {
var d = new Date(); d.setHours(d.getHours()+1);
e2.value = d.toTimeString().substring(0,5);
}
openMo('mo-create-event');
}
function createCalendarEvent() {
var token = localStorage.getItem('ffos_gcal_token');
if (!token) { toast('Conecte o Google Calendar primeiro.'); return; }
var title = document.getElementById('ev-title').value.trim();
if (!title) { toast('Digite um título para o evento.'); return; }
var date = document.getElementById('ev-date').value;
var start = document.getElementById('ev-start').value;
var end = document.getElementById('ev-end').value;
var desc = document.getElementById('ev-desc').value;
if (!date || !start || !end) { toast('Preencha data e horários.'); return; }
var body = {
summary: title,
description: desc,
start: { dateTime: date+'T'+start+':00', timeZone: 'America/Sao_Paulo' },
end: { dateTime: date+'T'+end+':00', timeZone: 'America/Sao_Paulo' }
};
fetch('https://www.googleapis.com/calendar/v3/calendars/primary/events', {
method: 'POST',
headers: {
'Authorization': 'Bearer '+token,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
.then(function(r){ return r.json(); })
.then(function(d){
if (d.error) {
toast('Erro: '+d.error.message);
} else {
closeMo('mo-create-event');
document.getElementById('ev-title').value = '';
document.getElementById('ev-desc').value = '';
toast('✓ Evento criado no Calendar!');
gcalFetch();
}
})
.catch(function(){ toast('Erro ao criar evento.'); });
}
// ═══════════════════════════════════════════
// CALENDAR EVENT COLORS BY TYPE
// ═══════════════════════════════════════════
function getEventColor(ev) {
var title = (ev.summary || '').toLowerCase();
if (/aula|aluno|turma|inglês|espanhol|lesson|class/.test(title)) return '#7c3aed';
if (/reunião|meeting|call|zoom|teams/.test(title)) return '#1d4ed8';
if (/almoço|jantar|café|lunch|dinner/.test(title)) return '#0f9d58';
if (/médico|saúde|academia|health|doctor/.test(title)) return '#db4437';
if (/pagamento|cobrança|financeiro|boleto/.test(title)) return '#d97706';
return '#4285f4';
}
// ═══════════════════════════════════════════
// NOTIFICATION SETTINGS
// ═══════════════════════════════════════════
var _notifSettings = {};
function loadNotifSettings() {
_notifSettings = JSON.parse(localStorage.getItem('ffos_notif_settings') || '{}');
var defaults = { morning:'07:00', afternoon:'13:00', evening:'20:00',
morning_on:true, afternoon_on:true, evening_on:true, calendar_on:true };
Object.keys(defaults).forEach(function(k){ if(_notifSettings[k]===undefined) _notifSettings[k]=defaults[k]; });
}
function saveNotifTimes() {
['morning','afternoon','evening'].forEach(function(p){
var el = document.getElementById('notif-'+p);
if (el) _notifSettings[p] = el.value;
});
localStorage.setItem('ffos_notif_settings', JSON.stringify(_notifSettings));
toast('Horários salvos!');
}
function toggleNotif(key) {
loadNotifSettings();
_notifSettings[key+'_on'] = !_notifSettings[key+'_on'];
localStorage.setItem('ffos_notif_settings', JSON.stringify(_notifSettings));
updateNotifToggles();
}
function updateNotifToggles() {
loadNotifSettings();
['morning','afternoon','evening','calendar'].forEach(function(k){
var el = document.getElementById('toggle-'+k);
if (el) el.classList.toggle('on', !!_notifSettings[k+'_on']);
var inp = document.getElementById('notif-'+k);
if (inp && _notifSettings[k]) inp.value = _notifSettings[k];
});
}
// Check for calendar event reminders (15 min before)
function checkCalendarReminders() {
if (!_notifSettings.calendar_on) return;
if (!_gcalEvents || !_gcalEvents.length) return;
var now = new Date();
var soon = new Date(now.getTime() + 15*60*1000);
_gcalEvents.forEach(function(ev){
if (!ev.start || !ev.start.dateTime) return;
var evTime = new Date(ev.start.dateTime);
if (evTime > now && evTime <= soon) {
var key = 'reminded_'+ev.id;
if (!sessionStorage.getItem(key)) {
sessionStorage.setItem(key, '1');
showPushNotification('📅 Em 15 min: '+ev.summary, new Date(ev.start.dateTime).toLocaleTimeString('pt-BR',{hour:'2-digit',minute:'2-digit'}));
}
}
});
}
// ═══════════════════════════════════════════
// COMPACT MODE
// ═══════════════════════════════════════════
function toggleCompact() {
var isCompact = document.body.classList.toggle('compact');
localStorage.setItem('ffos_compact', isCompact ? '1' : '0');
var el = document.getElementById('toggle-compact');
if (el) el.classList.toggle('on', isCompact);
}
function initCompactMode() {
var isCompact = localStorage.getItem('ffos_compact') === '1';
if (isCompact) document.body.classList.add('compact');
var el = document.getElementById('toggle-compact');
if (el) el.classList.toggle('on', isCompact);
}
// ═══════════════════════════════════════════
// CUSTOM ACCENT COLOR
// ═══════════════════════════════════════════
function setAccentColor(color) {
document.documentElement.style.setProperty('--accent', color);
// Update sidebar-bg contrast
var isDark = isColorDark(color);
document.documentElement.style.setProperty('--sidebar-bg-contrast', isDark ? '#fff' : '#000');
localStorage.setItem('ffos_accent', color);
updateAccentSwatches(color);
}
function isColorDark(hex) {
var r=parseInt(hex.slice(1,3),16), g=parseInt(hex.slice(3,5),16), b=parseInt(hex.slice(5,7),16);
return (r*299+g*587+b*114)/1000 < 128;
}
function updateAccentSwatches(color) {
document.querySelectorAll('.accent-swatch').forEach(function(s){
s.style.border = '2px solid '+(s.getAttribute('onclick').includes(color) ? color : 'transparent');
});
var ci = document.getElementById('custom-accent');
if (ci) ci.value = color;
}
function initAccentColor() {
var saved = localStorage.getItem('ffos_accent');
if (saved) { setAccentColor(saved); }
}
// ═══════════════════════════════════════════
// AI SUGGESTIONS
// ═══════════════════════════════════════════
function suggestTop3WithAI() {
var groqKey = localStorage.getItem('ffos_groq_key');
if (!groqKey) return;
var tasks = getTasks().filter(function(t){ return !t.done; }).slice(0, 20);
var calToday = (_gcalEvents||[]).map(function(ev){
return (ev.start&&ev.start.dateTime ? new Date(ev.start.dateTime).toLocaleTimeString('pt-BR',{hour:'2-digit',minute:'2-digit'}) : '') + ' ' + ev.summary;
}).join('; ');
var prompt = 'Você é um assistente de produtividade. Com base nas tarefas abertas e agenda de hoje, sugira as 3 tarefas mais importantes para o dia. '
+ 'Tarefas abertas: ' + tasks.map(function(t){return t.name+(t.date?' ('+t.date+')':'');}).join(', ') + '. '
+ 'Agenda de hoje: ' + (calToday||'nenhum evento') + '. '
+ 'Responda SOMENTE em JSON: {"top3":["tarefa1","tarefa2","tarefa3"],"motivo":"explicação breve"}';
fetch('https://api.groq.com/openai/v1/chat/completions', {
method:'POST',
headers:{'Content-Type':'application/json','Authorization':'Bearer '+groqKey},
body:JSON.stringify({model:'llama-3.3-70b-versatile',max_tokens:200,temperature:0.3,
messages:[{role:'user',content:prompt}]})
}).then(function(r){return r.json();})
.then(function(d){
var text = d.choices&&d.choices[0]?d.choices[0].message.content:'';
try {
var clean = text.replace(/```json|```/g,'').trim();
var parsed = JSON.parse(clean);
showTop3Suggestions(parsed);
} catch(e){}
}).catch(function(){});
}
function showTop3Suggestions(data) {
var el = document.getElementById('ai-top3-suggestions');
if (!el || !data.top3) return;
el.innerHTML = '
💡 IA sugere para hoje:
'
+ data.top3.map(function(s,i){
return '
'
+ ''+(i===0?'1️⃣':i===1?'2️⃣':'3️⃣')+''
+ '
'+escHtml(s)+'
';
}).join('')
+ (data.motivo?'
'+escHtml(data.motivo)+'
':'');
el.style.display = 'block';
}
function useAISuggestion(name) {
var tasks = getTasks();
var match = tasks.find(function(t){ return t.name.toLowerCase().includes(name.toLowerCase()) && !t.done; });
if (match) {
match.starred = true;
setTasks(tasks);
render();
toast('⭐ Adicionada ao Top 3!');
} else {
openAddTask();
setTimeout(function(){
var el = document.getElementById('t-name');
if (el) el.value = name;
}, 100);
}
var sugEl = document.getElementById('ai-top3-suggestions');
if (sugEl) sugEl.style.display = 'none';
}
// End of day summary
function requestDaySummary() {
var groqKey = localStorage.getItem('ffos_groq_key');
if (!groqKey) { toast('Configure a Groq API Key para usar a IA.'); return; }
var tasks = getTasks();
var todayStr = dk(today0());
var done = tasks.filter(function(t){ return t.done && t.updated===todayStr; });
var pending = tasks.filter(function(t){ return !t.done && t.date===todayStr; });
var overdue = tasks.filter(function(t){ return !t.done && t.date && t.date < todayStr; });
var prompt = 'Faça um resumo do dia de forma encorajadora e concisa (máximo 3 linhas). '
+ 'Concluídas hoje: ' + done.map(function(t){return t.name;}).join(', ') + '. '
+ 'Pendentes do dia: ' + pending.map(function(t){return t.name;}).join(', ') + '. '
+ 'Atrasadas: ' + overdue.map(function(t){return t.name;}).join(', ') + '. '
+ 'Responda em português.';
fetch('https://api.groq.com/openai/v1/chat/completions', {
method:'POST',
headers:{'Content-Type':'application/json','Authorization':'Bearer '+groqKey},
body:JSON.stringify({model:'llama-3.3-70b-versatile',max_tokens:150,
messages:[{role:'user',content:prompt}]})
}).then(function(r){return r.json();})
.then(function(d){
var text = d.choices&&d.choices[0]?d.choices[0].message.content:'';
if (text) showDaySummary(text);
}).catch(function(){});
}
function showDaySummary(text) {
var el = document.getElementById('day-summary-content');
if (el) {
el.textContent = text;
document.getElementById('day-summary-box').style.display = 'block';
} else {
toast(text.substring(0,80)+'...');
}
}
// Suggest recurrence for repeating tasks
function detectRecurringPatterns() {
var tasks = getTasks();
var nameCount = {};
tasks.filter(function(t){ return t.done; }).forEach(function(t){
var key = t.name.toLowerCase().trim();
if (!nameCount[key]) nameCount[key] = [];
nameCount[key].push(t.date);
});
var suggestions = [];
Object.keys(nameCount).forEach(function(name){
if (nameCount[name].length >= 3 && !tasks.find(function(t){ return t.name.toLowerCase()===name && t.recur; })) {
suggestions.push(name);
}
});
if (suggestions.length > 0) {
var existing = getTasks().filter(function(t){ return !t.done && !t.recur; });
var toSuggest = existing.filter(function(t){ return suggestions.includes(t.name.toLowerCase().trim()); });
toSuggest.slice(0,3).forEach(function(t){
addNotification('↻', 'Tarefa repetida detectada', '"'+t.name+'" — deseja adicionar recorrência?');
});
}
}
// ═══════════════════════════════════════════
// GLOBAL SEARCH (⌘K)
// ═══════════════════════════════════════════
var _gsSelected = -1;
var _gsResults = [];
function openGlobalSearch() {
var overlay = document.getElementById('global-search-overlay');
if (overlay) overlay.classList.add('open');
setTimeout(function() {
var inp = document.getElementById('global-search-input');
if (inp) { inp.value = ''; inp.focus(); }
}, 50);
runGlobalSearch('');
}
function closeGlobalSearch() {
var overlay = document.getElementById('global-search-overlay');
if (overlay) overlay.classList.remove('open');
}
function runGlobalSearch(q) {
q = (q||'').toLowerCase().trim();
var el = document.getElementById('global-search-results');
if (!el) return;
_gsResults = [];
_gsSelected = -1;
var tasks = getTasks().filter(function(t) {
return !q || (t.name||'').toLowerCase().includes(q)
|| (t.notes||'').toLowerCase().includes(q)
|| (t.tags||[]).some(function(tg){return tg.toLowerCase().includes(q);});
});
var projects = getProjects().filter(function(p) {
return !q || (p.name||'').toLowerCase().includes(q) || (p.desc||'').toLowerCase().includes(q);
});
var library = getLib().filter(function(l) {
return !q || (l.title||'').toLowerCase().includes(q) || (l.body||'').toLowerCase().includes(q);
});
if (!tasks.length && !projects.length && !library.length) {
el.innerHTML = '
'+(q?'Nenhum resultado para "'+escHtml(q)+'"':'Digite para pesquisar em tudo')+'
';
return;
}
var html2 = '';
if (tasks.length) {
html2 += '
Tarefas
';
tasks.slice(0,6).forEach(function(t) {
var idx = _gsResults.length;
_gsResults.push({type:'task', id:t.id});
var catColor = getCatColor(t.cat);
html2 += '
';
if (!t.done && t.status!=='progress') {
card += '';
}
if (t.status==='progress') {
card += '';
}
if (!t.done) {
card += '';
}
card += '
';
return card;
}
var kTodo = document.getElementById('k-todo');
var kProg = document.getElementById('k-progress');
var kDone = document.getElementById('k-done');
if (kTodo) { kTodo.innerHTML = todo.map(cardHtml).join(''); document.getElementById('k-todo-count').textContent = todo.length; }
if (kProg) { kProg.innerHTML = progress.map(cardHtml).join(''); document.getElementById('k-progress-count').textContent = progress.length; }
if (kDone) { kDone.innerHTML = done.map(cardHtml).join(''); document.getElementById('k-done-count').textContent = done.length; }
}
function moveKanban(taskId, dest) {
var tasks = getTasks();
var t = tasks.find(function(x){return x.id===taskId;});
if (!t) return;
if (dest==='done') { t.done=true; t.updated=dk(today0()); delete t.status; }
else if (dest==='progress') { t.done=false; t.status='progress'; }
else { t.done=false; delete t.status; }
setTasks(tasks);
renderKanban();
render();
}
// ═══════════════════════════════════════════
// SORT
// ═══════════════════════════════════════════
var _taskSort = 'date';
function setSort(s, btn) {
_taskSort = s;
document.querySelectorAll('.sort-btn').forEach(function(b){ b.classList.remove('active'); });
if (btn) btn.classList.add('active');
renderTasks();
}
function applySort(tasks) {
var sorted = tasks.slice();
if (_taskSort === 'date') {
sorted.sort(function(a,b){
if(a.date&&b.date) return a.date>b.date?1:-1;
if(a.date) return -1; if(b.date) return 1;
return a.name>b.name?1:-1;
});
} else if (_taskSort === 'priority') {
sorted.sort(function(a,b){ return (b.starred?1:0)-(a.starred?1:0); });
} else if (_taskSort === 'cat') {
sorted.sort(function(a,b){ return (a.cat||'zzz')>(b.cat||'zzz')?1:-1; });
} else if (_taskSort === 'name') {
sorted.sort(function(a,b){ return a.name>b.name?1:-1; });
}
return sorted;
}
// ═══════════════════════════════════════════
// DASHBOARD
// ═══════════════════════════════════════════
function renderDashboard() {
var tasks = getTasks();
var todayStr = dk(today0());
// Done today
var doneToday = tasks.filter(function(t){ return t.done && t.updated===todayStr; }).length;
var el = document.getElementById('dash-done-today');
if (el) el.textContent = doneToday;
// Open
var open = tasks.filter(function(t){ return !t.done; }).length;
var el2 = document.getElementById('dash-open');
if (el2) el2.textContent = open;
// Weekly goal
var goal = parseInt(localStorage.getItem('ffos_dash_goal')||'20');
var inp = document.getElementById('dash-goal-input');
if (inp) inp.value = goal;
var week = getWeekRange ? getWeekRange() : {start:todayStr,end:todayStr};
var doneWeek = tasks.filter(function(t){ return t.done && t.updated>=week.start && t.updated<=week.end; }).length;
var pct = Math.min(100, Math.round(doneWeek/goal*100));
var bar = document.getElementById('dash-goal-bar');
var lbl = document.getElementById('dash-goal-label');
var pctEl = document.getElementById('dash-goal-pct');
if (bar) bar.style.width = pct+'%';
if (lbl) lbl.textContent = doneWeek+' de '+goal;
if (pctEl) pctEl.textContent = pct+'%';
// Weekly chart
renderWeeklyChart(tasks);
// By category
renderCatChart(tasks);
// Streaks
renderDashStreaks();
}
function saveDashGoal(val) {
localStorage.setItem('ffos_dash_goal', val);
renderDashboard();
}
function renderWeeklyChart(tasks) {
var el = document.getElementById('dash-chart');
if (!el) return;
var shortDays = ['Dom','Seg','Ter','Qua','Qui','Sex','Sab'];
var days = [];
for (var i=6;i>=0;i--) {
var d=new Date(today0()); d.setDate(d.getDate()-i);
days.push({str:dk(d),label:shortDays[d.getDay()],isToday:i===0});
}
var max=1;
var counts=days.map(function(day){
var c=tasks.filter(function(t){return t.done&&t.updated===day.str;}).length;
if(c>max)max=c; return c;
});
el.innerHTML=days.map(function(day,i){
var c=counts[i];
var pct=Math.max(4,Math.round(c/max*100));
var color=day.isToday?'var(--blue)':'var(--accent)';
return '
'
+'
'+(c||'')+'
'
+''
+'
'+day.label+'
'
+'
';
}).join('');
}
function renderCatChart(tasks) {
var el = document.getElementById('dash-by-cat');
if (!el) return;
var cats = ['aula','admin','cobranca','pessoal'];
var catNames = {aula:'Aulas',admin:'Admin',cobranca:'Cobranças',pessoal:'Pessoal'};
var total = tasks.filter(function(t){return t.done;}).length || 1;
var rows = cats.map(function(cat){
var count = tasks.filter(function(t){return t.done && t.cat===cat;}).length;
var pct = Math.round(count/total*100);
var color = getCatColor(cat);
return {name:catNames[cat],count:count,pct:pct,color:color};
}).filter(function(r){return r.count>0;});
if (!rows.length) { el.innerHTML='
';
}).join('');
}
function renderDashStreaks() {
var el = document.getElementById('dash-streaks');
if (!el) return;
var routines = getRoutines();
if (!routines.length) { el.innerHTML='
Nenhuma rotina cadastrada.
'; return; }
el.innerHTML = routines.map(function(r){
var streak = getStreak ? getStreak(r) : 0;
var fire = streak>=7?'🔥':streak>=3?'⚡':'·';
return '
'+ri(yi);body.innerHTML=h;}
function actItemClick(type,id){closeActivityPanel();setTimeout(()=>{if(type==='task'){goTo('tasks');setTimeout(()=>openEditTask(id),150);}else if(type==='lib'){goTo('library');setTimeout(()=>openEditLib(id),150);}else if(type==='routine'){goTo('routines');}else if(type==='project'){goTo('projects');setTimeout(()=>openEditProject(id),150);}},200);}
function checkActivityBell(){const todayStr=dk(today0());const yd=new Date(today0());yd.setDate(yd.getDate()-1);const ydStr=dk(yd);let hasNew=false;getTasks().forEach(t=>{if((t.updated||t.date||'')>=ydStr)hasNew=true;});getLib().forEach(l=>{if((l.updated||l.date||'')>=ydStr)hasNew=true;});const dot=document.getElementById('bell-dot');if(dot&&hasNew)dot.classList.add('show');}
function toggleFabMenu(){_fabOpen?closeFabMenu():openFabMenu();}
function openFabMenu(){_fabOpen=true;document.getElementById('fab-menu').classList.add('open');document.getElementById('fab-backdrop').classList.add('show');}
function closeFabMenu(){_fabOpen=false;const m=document.getElementById('fab-menu');const b=document.getElementById('fab-backdrop');if(m)m.classList.remove('open');if(b)b.classList.remove('show');}
function hideSplash(){const s=document.getElementById('splash-screen');if(s){s.style.opacity='0';s.style.transition='opacity .4s';setTimeout(()=>{if(s.parentNode)s.parentNode.removeChild(s);},400);}}
function startClock(){function tick(){const el=document.getElementById('today-clock');if(el)el.textContent=new Date().toLocaleTimeString('pt-BR',{hour:'2-digit',minute:'2-digit',second:'2-digit'});}tick();setInterval(tick,1000);}
function renderRoutineProgress(){const el=document.getElementById('routine-progress-wrap');if(!el)return;const routines=getRoutines();if(!routines.length){el.style.display='none';return;}const todayStr=dk(today0());const done=routines.filter(r=>localStorage.getItem('routine_'+r.id+'_'+todayStr)).length;const pct=Math.round(done/routines.length*100);el.innerHTML='
Rotinas de hoje'+done+'/'+routines.length+' ('+pct+'%)
';el.style.display='block';}
async function fetchWeatherInline(){const el=document.getElementById('today-weather-inline');if(!el||!navigator.geolocation)return;try{navigator.geolocation.getCurrentPosition(async pos=>{const{latitude:lat,longitude:lon}=pos.coords;const r=await fetch('https://api.open-meteo.com/v1/forecast?latitude='+lat+'&longitude='+lon+'¤t_weather=true');const d=await r.json();const w=d.current_weather;const icons={0:'☀️',1:'🌤',2:'⛅',3:'☁️',45:'🌫',51:'🌦',61:'🌧',71:'❄️',80:'🌦',95:'⛈'};el.innerHTML=''+(icons[w.weathercode]||'🌡')+''+Math.round(w.temperature)+'°C';});}catch(e){}}
function initOfflineIndicator(){const b=document.getElementById('offline-banner');if(!b)return;function u(){b.classList.toggle('show',!navigator.onLine);}window.addEventListener('online',u);window.addEventListener('offline',u);u();}
let _libSort='updated';
function setLibSort(s,btn){_libSort=s;document.querySelectorAll('.lib-sort-btn').forEach(b=>b.classList.remove('active'));if(btn)btn.classList.add('active');renderLibrary();}
function applyLibSort(items){const s=items.slice();if(_libSort==='updated')s.sort((a,b)=>(b.updated||b.date||'')>(a.updated||a.date||'')?1:-1);else if(_libSort==='created')s.sort((a,b)=>(b.date||'')>(a.date||'')?1:-1);else if(_libSort==='alpha')s.sort((a,b)=>(a.title||'')>(b.title||'')?1:-1);return s;}
function dueDateLabel(dateStr){if(!dateStr)return '';const today=dk(today0());if(dateStr'+days+'d atraso';}if(dateStr===today)return 'Hoje';const days=Math.floor((new Date(dateStr)-new Date(today))/86400000);if(days===1)return 'Amanhã';if(days<=3)return 'em '+days+'d';return ''+dateStr+'';}
let _noteSearchMatches=[],_noteSearchIdx=0;
function noteSearchOpen(){const bar=document.getElementById('note-search-bar');if(bar){bar.classList.add('open');document.getElementById('note-search-input').focus();}}
function noteSearchClose(){const bar=document.getElementById('note-search-bar');if(bar)bar.classList.remove('open');const c=document.getElementById('note-editor-content');if(c)c.innerHTML=c.innerHTML.replace(/(.*?)<\/mark>/g,'$1');const cnt=document.getElementById('note-search-count');if(cnt)cnt.textContent='';_noteSearchMatches=[];}
function noteSearchRun(q){const c=document.getElementById('note-editor-content');if(!c)return;c.innerHTML=c.innerHTML.replace(/(.*?)<\/mark>/g,'$1');if(!q||!q.trim())return;c.innerHTML=c.innerHTML.replace(new RegExp('('+q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+')','gi'),'$1');_noteSearchMatches=Array.from(c.querySelectorAll('mark.search-hl'));_noteSearchIdx=0;noteSearchHighlight();const cnt=document.getElementById('note-search-count');if(cnt)cnt.textContent=_noteSearchMatches.length?'1/'+_noteSearchMatches.length:'0';}
function noteSearchNav(dir){if(!_noteSearchMatches.length)return;_noteSearchMatches[_noteSearchIdx].classList.remove('current');_noteSearchIdx=(_noteSearchIdx+dir+_noteSearchMatches.length)%_noteSearchMatches.length;noteSearchHighlight();const cnt=document.getElementById('note-search-count');if(cnt)cnt.textContent=(_noteSearchIdx+1)+'/'+_noteSearchMatches.length;}
function noteSearchHighlight(){if(!_noteSearchMatches[_noteSearchIdx])return;_noteSearchMatches[_noteSearchIdx].classList.add('current');_noteSearchMatches[_noteSearchIdx].scrollIntoView({block:'center'});}
function gcalResync(){const icon=document.getElementById('gcal-resync-icon');const token=localStorage.getItem('ffos_gcal_token');if(!token){toast('Conecte o Google Calendar primeiro.');return;}if(icon){let spin=0;const si=setInterval(()=>{spin+=360;icon.style.transform='rotate('+spin+'deg)';},800);gcalFetch();setTimeout(()=>{clearInterval(si);icon.style.transform='rotate(0deg)';toast('📅 Agenda atualizada!');},1500);}else{gcalFetch();toast('📅 Agenda atualizada!');}}
setInterval(()=>{if(localStorage.getItem('ffos_gcal_token'))gcalFetch();},5*60*1000);
function gcalSilentRefresh(){if(!window._auth||!window._auth.currentUser){gcalConnect();return;}var provider=new firebase.auth.GoogleAuthProvider();provider.addScope('https://www.googleapis.com/auth/calendar.readonly');window._auth.signInWithPopup(provider).then(function(result){var token=result.credential?result.credential.accessToken:null;if(token){localStorage.setItem('ffos_gcal_token',token);localStorage.setItem('ffos_gcal_expiry',Date.now()+55*60*1000);localStorage.setItem('ffos_gcal_connected','1');setTimeout(gcalFetch,500);}}).catch(function(){localStorage.removeItem('ffos_gcal_token');localStorage.setItem('ffos_gcal_connected','0');gcalUpdateUI();renderGCalPrompt();});}
setInterval(function(){var expiry=parseInt(localStorage.getItem('ffos_gcal_expiry')||'0');var token=localStorage.getItem('ffos_gcal_token');if(token&&expiry&&Date.now()>expiry)gcalSilentRefresh();},10*60*1000);
function openVoiceAssistant(){const ov=document.getElementById('voice-assistant-overlay');if(ov)ov.classList.add('open');vaClear();}
function closeVoiceAssistant(){const ov=document.getElementById('voice-assistant-overlay');if(ov)ov.classList.remove('open');vaStopMic();vaStopSpeaking();}
function openVoiceDiary(){openVoiceAssistant();}
function vaStatus(text,cls){const el=document.getElementById('va-status');if(!el)return;el.textContent=text;el.className='va-status'+(cls?' '+cls:'');}
function vaWave(on){const wf=document.getElementById('va-waveform');if(wf)wf.classList.toggle('listening',on);const btn=document.getElementById('va-mic-btn');if(btn){btn.classList.remove('listening','speaking');if(on)btn.classList.add('listening');}}
function vaClear(){const t=document.getElementById('va-transcript');const r=document.getElementById('va-response');const sb=document.getElementById('va-speak-btn');const ab=document.getElementById('va-response-actions');if(t)t.classList.remove('show');if(r)r.classList.remove('show');if(sb)sb.style.display='none';if(ab)ab.innerHTML='';vaStatus('Toque no microfone para falar','');vaStopSpeaking();const btn=document.getElementById('va-mic-btn');if(btn)btn.classList.remove('listening','speaking');vaWave(false);}
function vaToggleMic(){_vaRecording?vaStopMic():vaStartMic();}
async function vaStartMic(){const key=localStorage.getItem('ffos_groq_key');if(!key){toast('Configure a Groq API Key em Configurações.');return;}try{const stream=await navigator.mediaDevices.getUserMedia({audio:true});_vaChunks=[];_vaMediaRec=new MediaRecorder(stream,{mimeType:'audio/webm'});_vaMediaRec.ondataavailable=e=>{if(e.data.size>0)_vaChunks.push(e.data);};_vaMediaRec.onstop=async()=>{stream.getTracks().forEach(t=>t.stop());await vaProcessAudio(new Blob(_vaChunks,{type:'audio/webm'}));};_vaMediaRec.start();_vaRecording=true;vaWave(true);vaStatus('Ouvindo... toque para parar','listening');setTimeout(()=>{if(_vaRecording)vaStopMic();},30000);}catch(err){toast('Microfone não disponível.');}}
function vaStopMic(){if(_vaMediaRec&&_vaRecording){_vaMediaRec.stop();_vaRecording=false;vaWave(false);vaStatus('Processando...','thinking');}}
async function vaProcessAudio(blob){const key=localStorage.getItem('ffos_groq_key');vaStatus('Transcrevendo...','thinking');try{const fd=new FormData();fd.append('file',blob,'audio.webm');fd.append('model','whisper-large-v3');fd.append('language','pt');const tr=await fetch('https://api.groq.com/openai/v1/audio/transcriptions',{method:'POST',headers:{'Authorization':'Bearer '+key},body:fd});const td=await tr.json();const text=td.text||'';if(!text.trim()){vaStatus('Não entendi. Tente novamente.','');return;}const tEl=document.getElementById('va-transcript');const tTxt=document.getElementById('va-transcript-text');if(tEl)tEl.classList.add('show');if(tTxt)tTxt.textContent=text;vaStatus('Pensando...','thinking');await vaAskGroq(text,key);}catch(err){vaStatus('Erro ao processar.','');console.error(err);}}
async function vaAskGroq(question,key){const todayStr=dk(today0());const cal=(_gcalEvents||[]).filter(ev=>dk(new Date(ev.start.dateTime||ev.start.date))===todayStr).map(ev=>(ev.start.dateTime?new Date(ev.start.dateTime).toLocaleTimeString('pt-BR',{hour:'2-digit',minute:'2-digit'}):'Dia todo')+' - '+(ev.summary||'')).join('; ');const tasks=getTasks().filter(t=>!t.done).slice(0,20).map(t=>t.name+(t.date?' ['+t.date+']':'')).join('; ');const routines=getRoutines().map(r=>r.name).join(', ');const projects=getProjects().filter(p=>p.status==='ativo').map(p=>p.name).join(', ');const sys='Você é o assistente pessoal de voz de Tiago, dono da Fast Fluent Idiomas no Rio de Janeiro. Hoje é '+todayStr+'. Agenda: '+(cal||'nenhum evento')+'. Tarefas: '+(tasks||'nenhuma')+'. Rotinas: '+(routines||'nenhuma')+'. Projetos: '+(projects||'nenhum')+'. Responda em português, de forma natural e concisa. Máximo 2 parágrafos.';try{const res=await fetch('https://api.groq.com/openai/v1/chat/completions',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+key},body:JSON.stringify({model:'llama-3.3-70b-versatile',max_tokens:250,messages:[{role:'system',content:sys},{role:'user',content:question}]})});const data=await res.json();const answer=data.choices&&data.choices[0]?data.choices[0].message.content:'';if(!answer){vaStatus('Sem resposta.','');return;}const rEl=document.getElementById('va-response');const rTxt=document.getElementById('va-response-text');const sb=document.getElementById('va-speak-btn');if(rEl)rEl.classList.add('show');if(rTxt)rTxt.textContent=answer;if(sb)sb.style.display='flex';vaCheckActions(question);vaStatus('Respondendo...','speaking');const btn=document.getElementById('va-mic-btn');if(btn){btn.classList.remove('listening');btn.classList.add('speaking');}await vaSpeak(answer);vaStatus('Toque para continuar','');if(btn)btn.classList.remove('speaking');}catch(err){vaStatus('Erro ao conectar com a IA.','');console.error(err);}}
function vaSpeak(text){return new Promise(resolve=>{const synth=window.speechSynthesis;if(!synth||!text){resolve();return;}synth.cancel();const utt=new SpeechSynthesisUtterance(text);utt.lang='pt-BR';utt.rate=1.05;utt.pitch=1;const v=synth.getVoices().find(v=>v.lang.startsWith('pt'))||null;if(v)utt.voice=v;utt.onend=resolve;utt.onerror=resolve;synth.speak(utt);});}
function vaStopSpeaking(){if(window.speechSynthesis)window.speechSynthesis.cancel();_vaSpeaking=false;}
function vaCheckActions(question){const actEl=document.getElementById('va-response-actions');if(!actEl)return;const q=question.toLowerCase();let btns='';if(/adiciona|cria|criar|lembra|tarefa/i.test(q))btns+='';if(/nota|anota|salva|escreve/i.test(q))btns+='';actEl.innerHTML=btns;}
function vaCreateTask(){const tTxt=document.getElementById('va-transcript-text');closeVoiceAssistant();setTimeout(()=>{openAddTask();setTimeout(()=>{const n=document.getElementById('t-name');if(n&&tTxt)n.value=tTxt.textContent;},150);},300);}
function vaCreateNote(){const rTxt=document.getElementById('va-response-text');closeVoiceAssistant();setTimeout(()=>{openNoteEditor(null,'nota');setTimeout(()=>{const c=document.getElementById('note-editor-content');if(c&&rTxt)c.innerHTML='
'+escHtml(rTxt.textContent)+'
';},200);},300);}
let _dpField=null,_dpYear=0,_dpMonth=0;
const _dpMonths=['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'];
function dpOpen(fieldId,triggerEl){_dpField=fieldId;const popup=document.getElementById('dp-popup');const backdrop=document.getElementById('dp-backdrop');if(!popup)return;const input=document.getElementById(fieldId);const val=input?input.value:'';const now=val?new Date(val+'T12:00:00'):new Date();_dpYear=now.getFullYear();_dpMonth=now.getMonth();const rect=triggerEl.getBoundingClientRect();const spaceBelow=window.innerHeight-rect.bottom;popup.style.display='block';popup.style.left=Math.min(rect.left,window.innerWidth-280)+'px';if(spaceBelow>300){popup.style.top=(rect.bottom+6)+'px';popup.style.bottom='auto';}else{popup.style.bottom=(window.innerHeight-rect.top+6)+'px';popup.style.top='auto';}if(backdrop)backdrop.style.display='block';triggerEl.classList.add('open');dpRender();}
function dpClose(){const popup=document.getElementById('dp-popup');const backdrop=document.getElementById('dp-backdrop');if(popup)popup.style.display='none';if(backdrop)backdrop.style.display='none';document.querySelectorAll('.dp-input.open').forEach(el=>el.classList.remove('open'));}
function dpRender(){const label=document.getElementById('dp-month-label');const daysEl=document.getElementById('dp-days');if(!label||!daysEl)return;label.textContent=_dpMonths[_dpMonth]+' '+_dpYear;const todayStr=dk(today0());const curVal=_dpField?(document.getElementById(_dpField)||{}).value||'':'';const firstDay=new Date(_dpYear,_dpMonth,1).getDay();const daysInMonth=new Date(_dpYear,_dpMonth+1,0).getDate();let h='';for(let i=0;i
';for(let d=1;d<=daysInMonth;d++){const ds=_dpYear+'-'+String(_dpMonth+1).padStart(2,'0')+'-'+String(d).padStart(2,'0');let cls='dp-day';if(ds===todayStr)cls+=' dp-day-today';if(ds===curVal)cls+=' dp-day-selected';h+='';}daysEl.innerHTML=h;}
function dpPrevMonth(){_dpMonth--;if(_dpMonth<0){_dpMonth=11;_dpYear--;}dpRender();}
function dpNextMonth(){_dpMonth++;if(_dpMonth>11){_dpMonth=0;_dpYear++;}dpRender();}
function dpSelect(dateStr){if(!_dpField)return;const input=document.getElementById(_dpField);if(input)input.value=dateStr;const display=document.getElementById(_dpField+'-display');if(display){const d=new Date(dateStr+'T12:00:00');display.textContent=d.toLocaleDateString('pt-BR',{day:'2-digit',month:'short',year:'numeric'});display.style.color='var(--text)';}dpClose();}
function dpSelectToday(){dpSelect(dk(today0()));}
function dpClear(){dpClearField(_dpField);dpClose();}
function dpClearField(fieldId){const input=document.getElementById(fieldId);if(input)input.value='';const display=document.getElementById(fieldId+'-display');if(display){display.textContent='Selecionar data';display.style.color='var(--text3)';}}
'va-response-actions');if(!actEl)return;const q=question.toLowerCase();let btns='';if(/adiciona|cria|criar|lembra|tarefa/i.test(q))btns+='';if(/nota|anota|salva|escreve/i.test(q))btns+='';actEl.innerHTML=btns;}
function vaCreateTask(){const tTxt=document.getElementById('va-transcript-text');closeVoiceAssistant();setTimeout(()=>{openAddTask();setTimeout(()=>{const n=document.getElementById('t-name');if(n&&tTxt)n.value=tTxt.textContent;},150);},300);}
function vaCreateNote(){const rTxt=document.getElementById('va-response-text');closeVoiceAssistant();setTimeout(()=>{openNoteEditor(null,'nota');setTimeout(()=>{const c=document.getElementById('note-editor-content');if(c&&rTxt)c.innerHTML='
'+escHtml(rTxt.textContent)+'
';},200);},300);}
let _dpField=null,_dpYear=0,_dpMonth=0;
const _dpMonths=['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'];
function dpOpen(fieldId,triggerEl){_dpField=fieldId;const popup=document.getElementById('dp-popup');const backdrop=document.getElementById('dp-backdrop');if(!popup)return;const input=document.getElementById(fieldId);const val=input?input.value:'';const now=val?new Date(val+'T12:00:00'):new Date();_dpYear=now.getFullYear();_dpMonth=now.getMonth();const rect=triggerEl.getBoundingClientRect();const spaceBelow=window.innerHeight-rect.bottom;popup.style.display='block';popup.style.left=Math.min(rect.left,window.innerWidth-280)+'px';if(spaceBelow>300){popup.style.top=(rect.bottom+6)+'px';popup.style.bottom='auto';}else{popup.style.bottom=(window.innerHeight-rect.top+6)+'px';popup.style.top='auto';}if(backdrop)backdrop.style.display='block';triggerEl.classList.add('open');dpRender();}
function dpClose(){const popup=document.getElementById('dp-popup');const backdrop=document.getElementById('dp-backdrop');if(popup)popup.style.display='none';if(backdrop)backdrop.style.display='none';document.querySelectorAll('.dp-input.open').forEach(el=>el.classList.remove('open'));}
function dpRender(){const label=document.getElementById('dp-month-label');const daysEl=document.getElementById('dp-days');if(!label||!daysEl)return;label.textContent=_dpMonths[_dpMonth]+' '+_dpYear;const todayStr=dk(today0());const curVal=_dpField?(document.getElementById(_dpField)||{}).value||'':'';const firstDay=new Date(_dpYear,_dpMonth,1).getDay();const daysInMonth=new Date(_dpYear,_dpMonth+1,0).getDate();let h='';for(let i=0;i
';for(let d=1;d<=daysInMonth;d++){const ds=_dpYear+'-'+String(_dpMonth+1).padStart(2,'0')+'-'+String(d).padStart(2,'0');let cls='dp-day';if(ds===todayStr)cls+=' dp-day-today';if(ds===curVal)cls+=' dp-day-selected';h+='';}daysEl.innerHTML=h;}
function dpPrevMonth(){_dpMonth--;if(_dpMonth<0){_dpMonth=11;_dpYear--;}dpRender();}
function dpNextMonth(){_dpMonth++;if(_dpMonth>11){_dpMonth=0;_dpYear++;}dpRender();}
function dpSelect(dateStr){if(!_dpField)return;const input=document.getElementById(_dpField);if(input)input.value=dateStr;const display=document.getElementById(_dpField+'-display');if(display){const d=new Date(dateStr+'T12:00:00');display.textContent=d.toLocaleDateString('pt-BR',{day:'2-digit',month:'short',year:'numeric'});display.style.color='var(--text)';}dpClose();}
function dpSelectToday(){dpSelect(dk(today0()));}
function dpClear(){dpClearField(_dpField);dpClose();}
function dpClearField(fieldId){const input=document.getElementById(fieldId);if(input)input.value='';const display=document.getElementById(fieldId+'-display');if(display){display.textContent='Selecionar data';display.style.color='var(--text3)';}}