diff --git a/css/timer.css b/css/timer.css index 20da197..54f7e66 100644 --- a/css/timer.css +++ b/css/timer.css @@ -37,6 +37,7 @@ display: flex; justify-content: center; gap: 12px; + flex-wrap: wrap; margin-bottom: 16px; } @@ -63,6 +64,25 @@ cursor: not-allowed; } +#starterBtn { + padding: 6px 10px; + font-size: 0.85rem; + border: 1px dashed var(--muted); + background: transparent; + color: var(--muted); + transition: color var(--transition), border-color var(--transition), transform var(--transition); +} + +#starterBtn:hover:not(:disabled) { + color: var(--accent); + border-color: var(--accent); + transform: translateY(-1px); +} + +#starterBtn:active:not(:disabled) { + transform: translateY(0); +} + /* Sound notification control styles */ #soundSettings { margin-top: 12px; diff --git a/index.html b/index.html index 17de0ec..81c4c13 100644 --- a/index.html +++ b/index.html @@ -186,7 +186,8 @@

52/17 RuleFocus Time
52:00
- + + diff --git a/js/constants.js b/js/constants.js index 5df32e0..bdacfbd 100644 --- a/js/constants.js +++ b/js/constants.js @@ -5,6 +5,7 @@ export const TIMER_PRESETS = { default: { name: '52/17 (Recommended)', work: 52 * 60, break: 17 * 60 }, pomodoro: { name: '25/5 (Pomodoro)', work: 25 * 60, break: 5 * 60 }, deepWork: { name: '90/20 (Deep Work)', work: 90 * 60, break: 20 * 60 }, + starter: { name: '5/5 (Starter)', work: 5 * 60, break: 5 * 60 }, custom: { name: 'Custom', work: 25 * 60, break: 5 * 60 } // Default values for custom preset }; diff --git a/js/timer.js b/js/timer.js index 8d94f37..99a678b 100644 --- a/js/timer.js +++ b/js/timer.js @@ -10,7 +10,10 @@ let timerCore; // Timer elements let timerEl, progressEl; -let startBtn, pauseBtn, endBtn, resetBtn, addTimeBtn; +let startBtn, pauseBtn, endBtn, resetBtn, addTimeBtn, starterBtn; + +// Track preset to restore after starter sessions +let pendingPresetRestore = null; export function initTimer() { // Get DOM elements @@ -21,6 +24,7 @@ export function initTimer() { endBtn = document.getElementById('endBtn'); resetBtn = document.getElementById('resetBtn'); addTimeBtn = document.getElementById('addTimeBtn'); + starterBtn = document.getElementById('starterBtn'); // Show initial value while timer is initializing if (timerEl) { @@ -30,7 +34,39 @@ export function initTimer() { // Initialize timer core timerCore = new TimerCore({ // Set up callbacks - onSessionEnd: recordSession, + onSessionEnd: (session) => { + recordSession(session); + + // Skip break after a starter run and immediately restore the user's preset + if (timerCore?.state.currentPreset === 'starter' && pendingPresetRestore) { + const presetToRestore = pendingPresetRestore; + pendingPresetRestore = null; + + // Restore the preset so durations reset correctly + updateTimerPreset(presetToRestore); + + // Ensure we remain in focus mode and start fresh + timerCore.state.onBreak = false; + timerCore.state.remainingTime = timerCore.state.workDuration; + timerCore.updateBreakUI(); + timerCore.updateDisplay(); + + // Immediately begin the restored focus session + timerCore.start(); + return { skipBreak: true }; + } + + return null; + }, + onBreakEnd: () => { + if (pendingPresetRestore && timerCore) { + const presetToRestore = pendingPresetRestore; + pendingPresetRestore = null; + + updateTimerPreset(presetToRestore); + timerCore.start(); + } + }, getTodos: getTodos, // Add a custom UI update callback to handle the timer title updates updateUI: (state) => { @@ -54,8 +90,27 @@ export function initTimer() { endBtn: endBtn, resetBtn: resetBtn, addTimeBtn: addTimeBtn, + starterBtn: starterBtn, timerLabel: document.getElementById('timerLabel') }); + + // Handle quick starter session + starterBtn?.addEventListener('click', () => { + if (!timerCore || timerCore.state.isRunning) return; + + pendingPresetRestore = timerCore.state.currentPreset !== 'starter' + ? timerCore.state.currentPreset + : null; + + updateTimerPreset('starter'); + + // Ensure we start from focus mode with the new preset + timerCore.state.onBreak = false; + timerCore.state.remainingTime = timerCore.state.workDuration; + timerCore.updateBreakUI(); + timerCore.updateDisplay(); + timerCore.start(); + }); } // Update break auto-start preference @@ -142,6 +197,9 @@ function updateTimerTitle(presetKey) { case 'deepWork': titleElement.innerHTML = ' 90/20 Deep Work'; break; + case 'starter': + titleElement.innerHTML = ' 5/5 Starter'; + break; case 'custom': // Get the custom preset values to display in the title const workMinutes = Math.floor(TIMER_PRESETS.custom.work / 60); diff --git a/js/timerCore.js b/js/timerCore.js index bdf485c..55be2aa 100644 --- a/js/timerCore.js +++ b/js/timerCore.js @@ -192,6 +192,10 @@ export class TimerCore { this.state.isRunning = running; + if (this.elements.starterBtn) { + this.elements.starterBtn.disabled = running; + } + if (this.state.onBreak) { if (this.elements.startBtn) { this.elements.startBtn.disabled = running; @@ -221,7 +225,7 @@ export class TimerCore { } else { if (this.elements.startBtn) { this.elements.startBtn.disabled = running; - this.elements.startBtn.textContent = "Lock In"; + this.elements.startBtn.textContent = "Start"; } if (this.elements.pauseBtn) { @@ -395,14 +399,24 @@ export class TimerCore { this.endBreak(); } else { // Work session ended + // Mark timer as stopped before running callbacks or scheduling next steps + this.state.isRunning = false; + if (this.callbacks.onSessionEnd) { const sessionDuration = this.state.workDuration; - this.callbacks.onSessionEnd({ + const sessionResult = this.callbacks.onSessionEnd({ startTime: this.state.startTime, duration: sessionDuration, isBreak: false, todos: this.callbacks.getTodos ? this.callbacks.getTodos() : [] }); + + // Allow consumer to skip break flow after a session + if (sessionResult && sessionResult.skipBreak) { + this.updateControls(false); + this.saveState(); + return; + } } playSound(getEndSound()); this.startBreak();