Skip to content

Commit bd4803f

Browse files
committed
Add pomodoro
1 parent 758f931 commit bd4803f

3 files changed

Lines changed: 340 additions & 0 deletions

File tree

src/css/style.css

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,136 @@ body {
6161
border-radius: 10px;
6262
}
6363

64+
.pomodoro-modal {
65+
position: fixed;
66+
inset: 0;
67+
z-index: 10000;
68+
}
69+
70+
.pomodoro-modal.hidden {
71+
display: none !important;
72+
}
73+
74+
.pomodoro-backdrop {
75+
position: absolute;
76+
inset: 0;
77+
background: rgba(0,0,0,0.4);
78+
}
79+
80+
.pomodoro-panel {
81+
position: absolute;
82+
top: 50%;
83+
left: 50%;
84+
transform: translate(-50%, -50%);
85+
width: 360px;
86+
max-width: calc(100% - 32px);
87+
background: var(--textarea-background-color);
88+
border: 1px solid rgba(0,0,0,0.08);
89+
border-radius: 10px;
90+
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
91+
overflow: hidden;
92+
}
93+
94+
.pomodoro-header {
95+
display: flex;
96+
align-items: center;
97+
justify-content: space-between;
98+
padding: 12px 14px;
99+
background: var(--background-color);
100+
}
101+
102+
.pomodoro-title {
103+
font-weight: bold;
104+
}
105+
106+
.pomodoro-close {
107+
appearance: none;
108+
background: none;
109+
border: none;
110+
font-size: 20px;
111+
line-height: 1;
112+
cursor: pointer;
113+
}
114+
115+
.pomodoro-modes {
116+
display: flex;
117+
gap: 6px;
118+
padding: 10px 12px;
119+
}
120+
121+
.pomodoro-mode {
122+
flex: 1;
123+
padding: 8px 10px;
124+
border-radius: 8px;
125+
background: #f1eebd;
126+
border: 1px solid rgba(0,0,0,0.06);
127+
cursor: pointer;
128+
}
129+
130+
.pomodoro-mode.active {
131+
background: #eee8aa;
132+
border-color: rgba(0,0,0,0.15);
133+
font-weight: bold;
134+
}
135+
136+
.pomodoro-timer {
137+
font-size: 56px;
138+
font-weight: 700;
139+
text-align: center;
140+
padding: 18px 0 6px;
141+
color: #222;
142+
}
143+
144+
.pomodoro-controls {
145+
display: flex;
146+
gap: 8px;
147+
padding: 12px;
148+
}
149+
150+
.pomodoro-btn {
151+
flex: 1;
152+
padding: 10px 12px;
153+
border-radius: 8px;
154+
background: #f6f4cc;
155+
border: 1px solid rgba(0,0,0,0.08);
156+
cursor: pointer;
157+
}
158+
159+
.pomodoro-btn.primary {
160+
background: #cec98d;
161+
border-color: rgba(0,0,0,0.15);
162+
font-weight: bold;
163+
}
164+
165+
.dark-mode-topbar ~ .pomodoro-modal .pomodoro-panel {
166+
background: #1e1e1e;
167+
border-color: #333;
168+
}
169+
.dark-mode-topbar ~ .pomodoro-modal .pomodoro-header {
170+
background: #222;
171+
}
172+
.dark-mode-topbar ~ .pomodoro-modal .pomodoro-timer,
173+
.dark-mode-topbar ~ .pomodoro-modal .pomodoro-title {
174+
color: #ece5e5cc;
175+
}
176+
.dark-mode-topbar ~ .pomodoro-modal .pomodoro-mode {
177+
background: #222;
178+
color: #ece5e5cc;
179+
border-color: #333;
180+
}
181+
.dark-mode-topbar ~ .pomodoro-modal .pomodoro-mode.active {
182+
background: #121212;
183+
border-color: #444;
184+
}
185+
.dark-mode-topbar ~ .pomodoro-modal .pomodoro-btn {
186+
background: #222;
187+
color: #ece5e5cc;
188+
border-color: #333;
189+
}
190+
.dark-mode-topbar ~ .pomodoro-modal .pomodoro-btn.primary {
191+
background: #121212;
192+
border-color: #444;
193+
}
64194
.tab {
65195
width: 200px;
66196
min-width: 200px;
@@ -184,6 +314,27 @@ body {
184314
width: 28px;
185315
}
186316

317+
.focus-timer {
318+
display: inline-block;
319+
margin-left: 6px;
320+
font-size: 12px;
321+
color: #444;
322+
min-width: 44px;
323+
text-align: left;
324+
font-weight: bold;
325+
}
326+
327+
.dark-mode-navbar .focus-timer {
328+
color: #ece5e5cc;
329+
}
330+
331+
.focus-group {
332+
display: inline-flex;
333+
align-items: center;
334+
gap: 4px;
335+
float: right;
336+
}
337+
187338
.tools {
188339
margin-right: 3rem;
189340
display: flex;

src/index.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@
157157
<line x1="12" y1="3" x2="12" y2="15"></line>
158158
</svg>
159159
</label>
160+
<div class="focus-group">
161+
<button id="focus" class="nav-button tools" onclick="togglePomodoro()" title="Focus (Pomodoro)">
162+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-7 h-7 p-1.5 relative z-10 transition-colors ease-out text-gray-500">
163+
<circle cx="12" cy="12" r="9"></circle>
164+
<polyline points="12 7 12 12 15 15"></polyline>
165+
</svg>
166+
</button>
167+
<span id="focus-timer" class="focus-timer hidden">25:00</span>
168+
</div>
160169
<a id="toolkit" class="nav-button tools" href="https://shashi.dev/toolkit" target="_blank">
161170
<span>🛠️</span>
162171
</a>
@@ -172,6 +181,27 @@
172181
<div id="editor" class="editor">
173182
</div>
174183
</grammarly-editor-plugin>
184+
185+
<div id="pomodoro-modal" class="pomodoro-modal hidden" aria-hidden="true">
186+
<div class="pomodoro-backdrop" onclick="togglePomodoro(false)"></div>
187+
<div class="pomodoro-panel" role="dialog" aria-modal="true" aria-labelledby="pomodoro-title">
188+
<div class="pomodoro-header">
189+
<div class="pomodoro-title" id="pomodoro-title">Focus</div>
190+
<button class="pomodoro-close" onclick="togglePomodoro(false)" title="Close">×</button>
191+
</div>
192+
<div class="pomodoro-modes">
193+
<button class="pomodoro-mode" data-mode="focus" onclick="pomodoroSwitch('focus')">Focus</button>
194+
<button class="pomodoro-mode" data-mode="short" onclick="pomodoroSwitch('short')">Short Break</button>
195+
<button class="pomodoro-mode" data-mode="long" onclick="pomodoroSwitch('long')">Long Break</button>
196+
</div>
197+
<div class="pomodoro-timer" id="pomodoro-timer" onclick="pomodoroEditTimer()">25:00</div>
198+
<div class="pomodoro-controls">
199+
<button id="pomodoro-start" class="pomodoro-btn primary" onclick="pomodoroStart()">Start</button>
200+
<button id="pomodoro-pause" class="pomodoro-btn" onclick="pomodoroPause()">Pause</button>
201+
<button class="pomodoro-btn" onclick="pomodoroReset()">Reset</button>
202+
</div>
203+
</div>
204+
</div>
175205
</div>
176206
</body>
177207
<script src="https://cdn.jsdelivr.net/npm/@grammarly/editor-sdk?clientId=client_NCzASpZ7aSVx5Fm7GLAS7s"></script>

src/js/main.js

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)