TimerFlow

TimerFlow - Timer · Pomodoro · Stopwatch

min
Ready
00:00
History
No records
/* i18n engine */ const i18n={locale:navigator.language.startsWith('zh')?'zh':'en',data:{zh:{appTitle:'TimerFlow',appSubtitle:'在线计时器 · 番茄钟 · Stopwatch · 全屏专注',mode_countdown:'倒计时',mode_pomodoro:'番茄钟',mode_stopwatch:'秒表',preset_h:'h',preset_min:'min',custom_min:'分',custom_sec:'秒',custom_set:'设置',phase_ready:'准备就绪',phase_running:'倒计时中',phase_work:'工作',phase_break:'休息',phase_stopwatch:'秒表运行中',start:'开始',pause:'暂停',resume:'继续',reset:'重置',fullscreen:'⛶ 全屏(空格键)',exitFullscreen:'⛶ 退出全屏',historyTitle:'计时历史',clearHistory:'清空',historyEmpty:'暂无记录',historyCleared:'历史已清空',timeSet:'已设置为',pomo_workDone:'🍅 工作完成,开始休息!',pomo_breakDone:'☕ 休息结束,开始工作!',countdownDone:'⏰ 倒计时结束!',resetDone:'已重置',stopRunning:'请先停止当前计时',invalidTime:'请输入有效时间'},en:{appTitle:'TimerFlow',appSubtitle:'Online Timer · Pomodoro · Stopwatch · Fullscreen Focus',mode_countdown:'Countdown',mode_pomodoro:'Pomodoro',mode_stopwatch:'Stopwatch',preset_h:'h',preset_min:'min',custom_min:'min',custom_sec:'sec',custom_set:'Set',phase_ready:'Ready',phase_running:'Countdown',phase_work:'Work',phase_break:'Break',phase_stopwatch:'Stopwatch',start:'Start',pause:'Pause',resume:'Resume',reset:'Reset',fullscreen:'⛶ Fullscreen (Space)',exitFullscreen:'⛶ Exit Fullscreen',historyTitle:'History',clearHistory:'Clear',historyEmpty:'No records',historyCleared:'History cleared',timeSet:'Set to',pomo_workDone:'🍅 Work done, take a break!',pomo_breakDone:'☕ Break over, back to work!',countdownDone:'⏰ Time is up!',resetDone:'Reset',stopRunning:'Please stop the timer first',invalidTime:'Please enter a valid time'}},t(key){return this.data[this.locale][key]||key},setLocale(l){this.locale=l;applyI18n();}}; window.i18n=i18n; /* apply i18n to static DOM */ function applyI18n(){$('h1').textContent=i18n.t('appTitle');document.querySelector('.header p').textContent=i18n.t('appSubtitle');document.querySelectorAll('.mode-btn').forEach(b=>{const m=b.dataset.mode;b.textContent=i18n.t('mode_'+m);});$('customMin').nextElementSibling.textContent=i18n.t('custom_min');$('customSec').nextElementSibling.textContent=i18n.t('custom_sec');$('customSetBtn').textContent=i18n.t('custom_set');$('timerPhase').textContent=i18n.t('phase_ready');$('startBtn').textContent=i18n.t('start');$('pauseBtn').textContent=i18n.t('pause');$('resetBtn').textContent=i18n.t('reset');$('fullscreenBtn').textContent=i18n.t('fullscreen');document.querySelector('#historyPanel .section-title span').textContent=i18n.t('historyTitle');$('clearHistoryBtn').textContent=i18n.t('clearHistory');if($('historyList').children.length===1&&$('historyList').children[0].textContent.includes(i18n.locale==='zh'?'暂无记录':'No records')){$('historyList').innerHTML='
'+i18n.t('historyEmpty')+'
';}document.title=i18n.t('appTitle');} /* language switch */ function addLangSwitch(){const header=document.querySelector('.header');if(header){const btn=document.createElement('button');btn.className='btn btn-sm btn-outline';btn.style.cssText='position:absolute;top:16px;right:16px;z-index:10';btn.textContent=i18n.locale==='zh'?'EN':'中文';btn.onclick=function(){i18n.setLocale(i18n.locale==='zh'?'en':'zh');this.textContent=i18n.locale==='zh'?'EN':'中文';};header.style.position='relative';header.appendChild(btn);}} /* wrap existing startTimer to add i18n for resume label */ const origStartTimer=startTimer;startTimer=function(){origStartTimer.apply(this,arguments);if(state.isPaused||(state.mode==='stopwatch'&&state.stopwatchTime>0&&!state.isRunning)){$('startBtn').textContent=i18n.t('resume');}else{$('startBtn').textContent=i18n.t('start');}}; /* wrap showToast to use i18n keys if possible */ const origShowToast=showToast;showToast=function(msg){const mapped={[i18n.t('historyCleared')]:'historyCleared',[i18n.t('resetDone')]:'resetDone',[i18n.t('invalidTime')]:'invalidTime',[i18n.t('pomo_workDone')]:'pomo_workDone',[i18n.t('pomo_breakDone')]:'pomo_breakDone',[i18n.t('countdownDone')]:'countdownDone',[i18n.t('stopRunning')]:'stopRunning'};const key=mapped[msg];const tMsg=key?i18n.t(key):msg;origShowToast(tMsg);}; /* global error handler */ window.onerror=function(msg,url,line,column,error){console.error('Global error:',msg,error);showToast('Script error');}; window.addEventListener('unhandledrejection',function(e){console.error('Unhandled rejection:',e.reason);showToast('Async error');}); /* safe DOM getter with null check */ const safeGet=function(id){const el=document.getElementById(id);if(!el)console.warn('Element #'+id+' not found');return el;}; /* patch existing $ calls (already used) - we keep original but add warnings */ const orig$=window.$;window.$=function(id){const el=document.getElementById(id);if(!el)console.warn('Element #'+id+' not found');return el;}; /* init */ document.addEventListener('DOMContentLoaded',function(){addLangSwitch();applyI18n();}); (function(){if(typeof i18n==='undefined')return;var origApp=i18n.setLocale;i18n.setLocale=function(l){origApp(l);if(typeof renderHistory==='function')renderHistory();};var origAI=window.applyI18n;window.applyI18n=function(){try{var h1=document.querySelector('h1');if(h1)h1.textContent=i18n.t('appTitle');var sub=document.querySelector('.header p');if(sub)sub.textContent=i18n.t('appSubtitle');document.querySelectorAll('.mode-btn').forEach(function(b){var m=b.dataset.mode;b.textContent=i18n.t('mode_'+m);});var cMin=$('customMin');if(cMin&&cMin.nextElementSibling)cMin.nextElementSibling.textContent=i18n.t('custom_min');var cSec=$('customSec');if(cSec&&cSec.nextElementSibling)cSec.nextElementSibling.textContent=i18n.t('custom_sec');var cSet=$('customSetBtn');if(cSet)cSet.textContent=i18n.t('custom_set');var phase=$('timerPhase');if(phase)phase.textContent=i18n.t('phase_ready');var sBtn=$('startBtn');if(sBtn)sBtn.textContent=i18n.t('start');var pBtn=$('pauseBtn');if(pBtn)pBtn.textContent=i18n.t('pause');var rBtn=$('resetBtn');if(rBtn)rBtn.textContent=i18n.t('reset');var fBtn=$('fullscreenBtn');if(fBtn)fBtn.textContent=i18n.t('fullscreen');var ht=document.querySelector('#historyPanel .section-title span');if(ht)ht.textContent=i18n.t('historyTitle');var clr=$('clearHistoryBtn');if(clr)clr.textContent=i18n.t('clearHistory');document.title=i18n.t('appTitle');if(typeof renderHistory==='function')renderHistory();}catch(e){console.warn('applyI18n fix:',e);}};var origRH=window.renderHistory;window.renderHistory=function(){var h=getHistory();var list=$('historyList');if(!list)return;if(h.length===0){list.innerHTML='
'+i18n.t('historyEmpty')+'
';return;}list.innerHTML=h.slice(0,20).map(function(e){var d=new Date(e.time);var ts=d.getHours().toString().padStart(2,'0')+':'+d.getMinutes().toString().padStart(2,'0');return '
'+e.label+' — '+e.duration+''+e.mode+' · '+ts+'
';}).join('');};var origUB=window.updateButtons;window.updateButtons=function(){var s=$('startBtn'),p=$('pauseBtn');if(!s||!p)return;s.style.display=(state.isRunning&&!state.isPaused)?'none':'inline-flex';p.style.display=(state.isRunning&&!state.isPaused)?'inline-flex':'none';if(state.isPaused){s.textContent=i18n.t('resume');s.style.display='inline-flex';}else if(state.mode==='stopwatch'&&state.stopwatchTime>0&&!state.isRunning){s.textContent=i18n.t('resume');}else{s.textContent=i18n.t('start');}};if(typeof addLangSwitch==='function'&&!document.querySelector('.lang-switch')){addLangSwitch();};if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',function(){applyI18n();});else applyI18n();})(); (function(){ window.addEventListener('error',function(e){console.error('[TimerFlow] Caught error:',e.message)}); window.addEventListener('unhandledrejection',function(e){console.error('[TimerFlow] Unhandled Promise rejection:',e.reason)}); var navLink=document.querySelector('link[href="../shared/nav-bar.css"]'); if(navLink&&!navLink.sheet){console.warn('[TimerFlow] nav-bar.css may not have loaded, applying fallback styles'); var fs=document.createElement('style'); fs.textContent='/* nav-bar fallback */'; document.head.appendChild(fs)} if(typeof window.NavBar==='undefined'&&!document.querySelector('script[src*="nav-bar.js"]')?.dataset?.loaded){console.warn('[TimerFlow] nav-bar.js may not have loaded')} var origGid=document.getElementById.bind(document); window.$=function(id){var el=origGid(id); if(!el){console.warn('[TimerFlow] Element #'+id+' not found, creating placeholder'); el=document.createElement('div'); el.id=id; el.style.display='none'; document.body.appendChild(el)} return el}; function ensureSibling(inputId){var inp=document.getElementById(inputId); if(inp&&!inp.nextElementSibling){var sp=document.createElement('span'); sp.textContent='min'; inp.parentNode.insertBefore(sp,inp.nextSibling)}} ensureSibling('customMin'); ensureSibling('customSec'); if(window.i18n&&window.i18n.data){var zd=window.i18n.data.zh, en=window.i18n.data.en; if(!zd.countdown_complete){zd.countdown_complete='⏰ 倒计时结束!'; en.countdown_complete='⏰ Time is up!'} if(!zd.pomo_work_complete){zd.pomo_work_complete='🍅 工作完成!'; en.pomo_work_complete='🍅 Work session complete!'} if(!zd.pomo_break_complete){zd.pomo_break_complete='☕ 休息结束!'; en.pomo_break_complete='☕ Break over!'} if(!zd.history_cleared){zd.history_cleared='历史已清空'; en.history_cleared='History cleared'}} document.documentElement.setAttribute('data-timerflow-ready','true') })() (function(){if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',fixCustomSetBtn)}else{fixCustomSetBtn()}function fixCustomSetBtn(){var btn=document.getElementById('customSetBtn');if(!btn)return;var newBtn=document.createElement('button');newBtn.id='customSetBtn';newBtn.className=btn.className;newBtn.textContent=btn.textContent;btn.parentNode.insertBefore(newBtn,btn);btn.parentNode.removeChild(btn);newBtn.addEventListener('click',function(){if(state.isRunning){showToast('Please stop the timer first');return}var min=parseInt(document.getElementById('customMin').value);var sec=parseInt(document.getElementById('customSec').value);if(isNaN(min))min=0;if(isNaN(sec))sec=0;var total=min*60+sec;if(total<1||total>99999){showToast('Please enter a valid time (1-99999 seconds)');return}state.totalSeconds=total;state.remaining=total;updateDisplay();renderPresets();showToast('Set to '+min+'min '+sec+'s')})}})();