@@ -229,3 +229,162 @@ function printConsoleArt() {
229229
230230 window . initTabs = initTabs ;
231231} ) ( ) ;
232+
233+ ( function ( ) {
234+ var pomodoro = {
235+ mode : 'focus' ,
236+ durations : { focus : 25 * 60 , short : 5 * 60 , long : 15 * 60 } ,
237+ remaining : 25 * 60 ,
238+ interval : null ,
239+ running : false
240+ } ;
241+
242+ function formatTime ( sec ) {
243+ sec = Math . max ( 0 , Math . floor ( sec ) ) ;
244+ var m = Math . floor ( sec / 60 ) ;
245+ var s = sec % 60 ;
246+ return ( m < 10 ?"" + m :m ) + ":" + ( s < 10 ?"0" + s :s ) ;
247+ }
248+
249+ function qs ( id ) { return document . getElementById ( id ) ; }
250+
251+ function updateNavbarTimer ( ) {
252+ var badge = document . getElementById ( 'focus-timer' ) ;
253+ if ( ! badge ) return ;
254+ if ( pomodoro . running ) {
255+ badge . textContent = formatTime ( pomodoro . remaining ) ;
256+ badge . classList . remove ( 'hidden' ) ;
257+ } else {
258+ badge . classList . add ( 'hidden' ) ;
259+ }
260+ }
261+
262+ function updateDisplay ( ) {
263+ var el = qs ( 'pomodoro-timer' ) ;
264+ if ( el ) el . textContent = formatTime ( pomodoro . remaining ) ;
265+ updateNavbarTimer ( ) ;
266+ }
267+
268+ function pomodoroEditTimer ( ) {
269+ var el = qs ( 'pomodoro-timer' ) ;
270+ if ( ! el || el . dataset . editing === '1' ) return ;
271+ el . dataset . editing = '1' ;
272+ var origSecs = pomodoro . durations [ pomodoro . mode ] || pomodoro . remaining ;
273+ var startVal = formatTime ( origSecs ) ;
274+ var input = document . createElement ( 'input' ) ;
275+ input . type = 'text' ;
276+ input . value = startVal ;
277+ input . style . width = '140px' ;
278+ input . style . fontSize = 'inherit' ;
279+ input . style . fontWeight = 'inherit' ;
280+ input . style . textAlign = 'center' ;
281+ var commit = function ( save ) {
282+ delete el . dataset . editing ;
283+ var newSecs = origSecs ;
284+ if ( save ) {
285+ var v = ( input . value || '' ) . trim ( ) ;
286+ var mins = null , secs = 0 ;
287+ if ( / ^ \d + : \d { 1 , 2 } $ / . test ( v ) ) {
288+ var parts = v . split ( ':' ) ;
289+ mins = parseInt ( parts [ 0 ] , 10 ) ;
290+ secs = parseInt ( parts [ 1 ] , 10 ) ;
291+ if ( ! isFinite ( mins ) || ! isFinite ( secs ) ) mins = null ;
292+ } else if ( / ^ \d + $ / . test ( v ) ) {
293+ mins = parseInt ( v , 10 ) ;
294+ }
295+ if ( mins != null ) {
296+ if ( mins < 1 ) mins = 1 ; if ( mins > 180 ) mins = 180 ;
297+ if ( secs < 0 ) secs = 0 ; if ( secs > 59 ) secs = 59 ;
298+ newSecs = ( mins * 60 ) + secs ;
299+ }
300+ }
301+ el . replaceChild ( span , input ) ;
302+ pomodoro . durations [ pomodoro . mode ] = newSecs ;
303+ if ( ! pomodoro . running ) {
304+ pomodoro . remaining = newSecs ;
305+ }
306+ updateDisplay ( ) ;
307+ } ;
308+ var span = document . createElement ( 'span' ) ;
309+ span . textContent = el . textContent ;
310+ el . textContent = '' ;
311+ el . appendChild ( input ) ;
312+ input . focus ( ) ;
313+ input . select ( ) ;
314+ input . addEventListener ( 'keydown' , function ( e ) {
315+ if ( e . key === 'Enter' ) commit ( true ) ;
316+ if ( e . key === 'Escape' ) commit ( false ) ;
317+ } ) ;
318+ input . addEventListener ( 'blur' , function ( ) { commit ( true ) ; } ) ;
319+ }
320+
321+ function highlightMode ( ) {
322+ var buttons = document . querySelectorAll ( '.pomodoro-mode' ) ;
323+ buttons . forEach ( function ( b ) {
324+ if ( b . getAttribute ( 'data-mode' ) === pomodoro . mode ) b . classList . add ( 'active' ) ;
325+ else b . classList . remove ( 'active' ) ;
326+ } ) ;
327+ }
328+
329+ function tick ( ) {
330+ pomodoro . remaining -= 1 ;
331+ updateDisplay ( ) ;
332+ if ( pomodoro . remaining <= 0 ) {
333+ clearInterval ( pomodoro . interval ) ; pomodoro . interval = null ; pomodoro . running = false ;
334+ updateNavbarTimer ( ) ;
335+ try { if ( window . Notification && Notification . permission === 'granted' ) new Notification ( 'Time\'s up!' ) ; } catch ( e ) { }
336+ if ( document . hidden && typeof document . title === 'string' ) { var orig = document . title ; document . title = '⏰ Time\'s up!' ; setTimeout ( function ( ) { document . title = orig ; } , 4000 ) ; }
337+ alert ( "Time's up!" ) ;
338+ }
339+ }
340+
341+ function setMode ( mode ) {
342+ pomodoro . mode = mode ;
343+ pomodoro . remaining = pomodoro . durations [ mode ] ;
344+ updateDisplay ( ) ;
345+ highlightMode ( ) ;
346+ }
347+
348+ function start ( ) {
349+ if ( pomodoro . running ) return ;
350+ pomodoro . running = true ;
351+ updateNavbarTimer ( ) ;
352+ if ( ! pomodoro . interval ) pomodoro . interval = setInterval ( tick , 1000 ) ;
353+ }
354+
355+ function pause ( ) {
356+ pomodoro . running = false ;
357+ if ( pomodoro . interval ) { clearInterval ( pomodoro . interval ) ; pomodoro . interval = null ; }
358+ updateNavbarTimer ( ) ;
359+ }
360+
361+ function reset ( ) {
362+ pause ( ) ;
363+ pomodoro . remaining = pomodoro . durations [ pomodoro . mode ] ;
364+ updateDisplay ( ) ;
365+ updateNavbarTimer ( ) ;
366+ }
367+
368+ function toggle ( show ) {
369+ var modal = qs ( 'pomodoro-modal' ) ;
370+ if ( ! modal ) return ;
371+ var shouldShow = typeof show === 'boolean' ? show : modal . classList . contains ( 'hidden' ) ;
372+ if ( shouldShow ) {
373+ modal . classList . remove ( 'hidden' ) ;
374+ modal . setAttribute ( 'aria-hidden' , 'false' ) ;
375+ highlightMode ( ) ;
376+ updateDisplay ( ) ;
377+ try { if ( window . Notification && Notification . permission === 'default' ) Notification . requestPermission ( ) ; } catch ( e ) { }
378+ } else {
379+ modal . classList . add ( 'hidden' ) ;
380+ modal . setAttribute ( 'aria-hidden' , 'true' ) ;
381+ }
382+ }
383+
384+ window . togglePomodoro = toggle ;
385+ window . pomodoroSwitch = setMode ;
386+ window . pomodoroStart = start ;
387+ window . pomodoroPause = pause ;
388+ window . pomodoroReset = reset ;
389+ window . pomodoroEditTimer = pomodoroEditTimer ;
390+ } ) ( ) ;
0 commit comments