Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions css/timer.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
display: flex;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 16px;
}

Expand All @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ <h2><i class="fas fa-stopwatch"></i> 52/17 Rule<span class="tooltip"
<div id="timerLabel">Focus Time</div>
<div id="timer">52:00</div>
<div class="controls">
<button id="startBtn">Lock In</button>
<button id="starterBtn" class="secondary">Start with 5</button>
<button id="startBtn">Start</button>
<button id="pauseBtn">Pause</button>
<button id="addTimeBtn" title="Add 5 minutes to current session">+5 min</button>
<button id="endBtn">End</button>
Expand Down
1 change: 1 addition & 0 deletions js/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
62 changes: 60 additions & 2 deletions js/timer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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) => {
Expand All @@ -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
Expand Down Expand Up @@ -142,6 +197,9 @@ function updateTimerTitle(presetKey) {
case 'deepWork':
titleElement.innerHTML = '<i class="fas fa-stopwatch"></i> 90/20 Deep Work<span class="tooltip" title="Extended focus time for complex tasks requiring deep concentration"><i class="fas fa-info-circle"></i></span>';
break;
case 'starter':
titleElement.innerHTML = '<i class="fas fa-stopwatch"></i> 5/5 Starter<span class="tooltip" title="Kick off with a quick 5-minute focus and short reset"><i class="fas fa-info-circle"></i></span>';
break;
case 'custom':
// Get the custom preset values to display in the title
const workMinutes = Math.floor(TIMER_PRESETS.custom.work / 60);
Expand Down
18 changes: 16 additions & 2 deletions js/timerCore.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down