diff --git a/src/plays/pomodoro-timer/PomodoroTimer.tsx b/src/plays/pomodoro-timer/PomodoroTimer.tsx
new file mode 100644
index 000000000..07aaa231f
--- /dev/null
+++ b/src/plays/pomodoro-timer/PomodoroTimer.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import TimerDisplay from './components/TimerDisplay';
+import TimerControls from './components/TimerControls';
+import SessionSelector from './components/SessionSelector';
+
+import { usePomodoroTimer, SessionType } from './hooks/usePomodoroTimer';
+
+import './styles.css';
+
+function PomodoroTimer(): JSX.Element {
+ const { session, timeLeft, start, pause, reset, updateSessionTime, changeSession } =
+ usePomodoroTimer();
+
+ return (
+
+
+
+ {/* Title */}
+
Pomodoro Timer
+
+ {/* Session Selector */}
+
+
+ {/* Timer Display */}
+
+
+ {/* Controls */}
+
+
+
+
+ );
+}
+
+export default PomodoroTimer;
diff --git a/src/plays/pomodoro-timer/Readme.md b/src/plays/pomodoro-timer/Readme.md
new file mode 100644
index 000000000..6f43e0f2c
--- /dev/null
+++ b/src/plays/pomodoro-timer/Readme.md
@@ -0,0 +1,76 @@
+# Pomodoro Timer
+
+A customizable Pomodoro Timer built with React and TypeScript that helps users improve productivity using the Pomodoro Technique.
+
+Users can set focus, short break, and long break durations using an interactive scroll-based time picker. The timer supports start, pause, and reset controls, and automatically manages session timing.
+
+This play demonstrates clean React architecture, custom hooks, state management, and interactive UI design.
+
+---
+
+## Play Demographic
+
+- Language: TypeScript
+- Level: Intermediate
+
+---
+
+## Creator Information
+
+- User: deansereigns
+- GitHub: https://github.com/deansereigns
+
+---
+
+## Features
+
+- Focus, Short Break, and Long Break sessions
+- Scroll-based time selection (interactive wheel picker)
+- Start, Pause, and Reset controls
+- Automatic session handling
+- Clean and responsive UI
+- Built using React functional components and hooks
+
+---
+
+## React Concepts Used
+
+- Functional Components
+- useState for managing timer state
+- useEffect for timer interval handling
+- Custom Hook (usePomodoroTimer)
+- Component composition
+- Controlled components
+- TypeScript for type safety
+
+---
+
+## Implementation Details
+
+The timer logic is implemented using a custom hook (`usePomodoroTimer`) which manages:
+
+- session state
+- timer countdown
+- start, pause, reset logic
+- time updates from scroll picker
+
+The UI is broken into modular components:
+
+- TimerDisplay
+- DualTimePicker
+- SessionSelector
+- TimerControls
+
+This ensures clean separation of logic and presentation.
+
+---
+
+## Considerations
+
+- Timer updates immediately when time is changed
+- Scroll picker wraps values circularly
+- Timer stops correctly when paused or reset
+- Clean and reusable component structure
+
+---
+
diff --git a/src/plays/pomodoro-timer/components/DualTimePicker.tsx b/src/plays/pomodoro-timer/components/DualTimePicker.tsx
new file mode 100644
index 000000000..5891944b9
--- /dev/null
+++ b/src/plays/pomodoro-timer/components/DualTimePicker.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+
+interface Props {
+ minutes: number;
+ seconds: number;
+ onChange: (minutes: number, seconds: number) => void;
+}
+
+const DualTimePicker: React.FC = ({ minutes, seconds, onChange }) => {
+ const wrap = (value: number, max: number) => {
+ if (value < 0) return max;
+ if (value > max) return 0;
+
+ return value;
+ };
+
+ const changeMinutes = (delta: number) => {
+ const newMinutes = wrap(minutes + delta, 59);
+ onChange(newMinutes, seconds);
+ };
+
+ const changeSeconds = (delta: number) => {
+ const newSeconds = wrap(seconds + delta, 59);
+ onChange(minutes, newSeconds);
+ };
+
+ const Wheel = ({ value, onChangeFn }: { value: number; onChangeFn: (delta: number) => void }) => {
+ const prev = wrap(value - 1, 59);
+ const next = wrap(value + 1, 59);
+
+ return (
+
+
onChangeFn(-1)}>
+ {String(prev).padStart(2, '0')}
+
+
+
{String(value).padStart(2, '0')}
+
+
onChangeFn(1)}>
+ {String(next).padStart(2, '0')}
+
+
+ );
+ };
+
+ return (
+
+ );
+};
+
+export default DualTimePicker;
diff --git a/src/plays/pomodoro-timer/components/SessionSelector.tsx b/src/plays/pomodoro-timer/components/SessionSelector.tsx
new file mode 100644
index 000000000..45d34c292
--- /dev/null
+++ b/src/plays/pomodoro-timer/components/SessionSelector.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { SessionType } from '../hooks/usePomodoroTimer';
+
+interface Props {
+ current: SessionType;
+ onChange: (session: SessionType) => void;
+}
+
+const SessionSelector: React.FC = ({ current, onChange }) => {
+ const sessions: SessionType[] = ['focus', 'shortBreak', 'longBreak'];
+
+ const labels: Record = {
+ focus: 'Focus',
+ shortBreak: 'Short Break',
+ longBreak: 'Long Break'
+ };
+
+ return (
+
+ {sessions.map((session) => (
+
+ ))}
+
+ );
+};
+
+export default SessionSelector;
diff --git a/src/plays/pomodoro-timer/components/TimerControls.tsx b/src/plays/pomodoro-timer/components/TimerControls.tsx
new file mode 100644
index 000000000..3f77539d7
--- /dev/null
+++ b/src/plays/pomodoro-timer/components/TimerControls.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+interface Props {
+ onStart: () => void;
+ onPause: () => void;
+ onReset: () => void;
+}
+
+const TimerControls: React.FC = ({ onStart, onPause, onReset }) => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default TimerControls;
diff --git a/src/plays/pomodoro-timer/components/TimerDisplay.tsx b/src/plays/pomodoro-timer/components/TimerDisplay.tsx
new file mode 100644
index 000000000..51f058c6c
--- /dev/null
+++ b/src/plays/pomodoro-timer/components/TimerDisplay.tsx
@@ -0,0 +1,53 @@
+import React, { useEffect, useRef, useState } from 'react';
+
+import DualTimePicker from './DualTimePicker';
+
+interface Props {
+ session: string;
+ timeLeft: number;
+ onTimeChange: (seconds: number) => void;
+}
+
+const TimerDisplay: React.FC = ({ session, timeLeft, onTimeChange }) => {
+ const [editing, setEditing] = useState(false);
+
+ const containerRef = useRef(null);
+
+ const minutes = Math.floor(timeLeft / 60);
+
+ const seconds = timeLeft % 60;
+
+ const formatted = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
+ setEditing(false);
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, []);
+
+ const handleChange = (m: number, s: number) => {
+ onTimeChange(m * 60 + s);
+ };
+
+ return (
+
+
{session.toUpperCase()}
+
+ {!editing && (
+
setEditing(true)}>
+ {formatted}
+
+ )}
+
+ {editing &&
}
+
+ );
+};
+
+export default TimerDisplay;
diff --git a/src/plays/pomodoro-timer/hooks/usePomodoroTimer.ts b/src/plays/pomodoro-timer/hooks/usePomodoroTimer.ts
new file mode 100644
index 000000000..17efe888d
--- /dev/null
+++ b/src/plays/pomodoro-timer/hooks/usePomodoroTimer.ts
@@ -0,0 +1,87 @@
+import { useEffect, useRef, useState } from 'react';
+
+export type SessionType = 'focus' | 'shortBreak' | 'longBreak';
+
+const DEFAULT_TIMES = {
+ focus: 25 * 60,
+ shortBreak: 5 * 60,
+ longBreak: 15 * 60
+};
+
+export const usePomodoroTimer = () => {
+ const [session, setSession] = useState('focus');
+
+ const [timeLeft, setTimeLeft] = useState(DEFAULT_TIMES.focus);
+
+ const [isRunning, setIsRunning] = useState(false);
+
+ const intervalRef = useRef(null);
+
+ /* Timer logic */
+ useEffect(() => {
+ if (!isRunning) return;
+
+ intervalRef.current = setInterval(() => {
+ setTimeLeft((prev) => {
+ if (prev <= 1) {
+ switchSession();
+
+ return 0;
+ }
+
+ return prev - 1;
+ });
+ }, 1000);
+
+ return () => {
+ if (intervalRef.current) clearInterval(intervalRef.current);
+ };
+ }, [isRunning]);
+
+ /* Switch session automatically */
+ const switchSession = () => {
+ if (session === 'focus') {
+ changeSession('shortBreak');
+ } else {
+ changeSession('focus');
+ }
+ };
+
+ /* Manual session change */
+ const changeSession = (newSession: SessionType) => {
+ setSession(newSession);
+ setTimeLeft(DEFAULT_TIMES[newSession]);
+ setIsRunning(false);
+ };
+
+ /* Start */
+ const start = () => {
+ setIsRunning(true);
+ };
+
+ /* Pause */
+ const pause = () => {
+ setIsRunning(false);
+ };
+
+ /* Reset */
+ const reset = () => {
+ setIsRunning(false);
+ setTimeLeft(DEFAULT_TIMES[session]);
+ };
+
+ /* Update time from scroll wheel */
+ const updateSessionTime = (seconds: number) => {
+ setTimeLeft(seconds);
+ };
+
+ return {
+ session,
+ timeLeft,
+ start,
+ pause,
+ reset,
+ updateSessionTime,
+ changeSession
+ };
+};
diff --git a/src/plays/pomodoro-timer/styles.css b/src/plays/pomodoro-timer/styles.css
new file mode 100644
index 000000000..06d995220
--- /dev/null
+++ b/src/plays/pomodoro-timer/styles.css
@@ -0,0 +1,142 @@
+/* Main container */
+.play-details-body {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 70vh;
+}
+
+/* Card */
+.pomodoro-card {
+ background: #0f172a;
+ padding: 30px;
+ border-radius: 16px;
+ width: 340px;
+ text-align: center;
+ color: white;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
+}
+
+/* Title */
+.pomodoro-title {
+ font-size: 26px;
+ font-weight: bold;
+ margin-bottom: 20px;
+}
+
+/* Session selector */
+.session-selector {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 20px;
+}
+
+.session-btn {
+ flex: 1;
+ margin: 4px;
+ padding: 8px;
+ border-radius: 8px;
+ border: none;
+ background: #1e293b;
+ color: #94a3b8;
+ cursor: pointer;
+ transition: 0.2s;
+}
+
+.session-btn.active {
+ background: #3b82f6;
+ color: white;
+}
+
+/* Session label */
+.session-label {
+ font-size: 14px;
+ margin-bottom: 10px;
+ color: #94a3b8;
+}
+
+/* Timer display */
+.timer-display {
+ margin: 20px 0;
+}
+
+/* Big time text */
+.timer-time.big {
+ font-size: 52px;
+ font-weight: bold;
+ cursor: pointer;
+}
+
+/* Wheel container */
+.time-wheel-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+/* Dual wheel */
+.dual-wheel {
+ display: flex;
+ gap: 10px;
+}
+
+/* Wheel column */
+.wheel-column {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+/* Wheel item */
+.wheel-item {
+ font-size: 28px;
+ padding: 6px 0;
+ transition: 0.2s;
+}
+
+/* Center active item */
+.wheel-item.active {
+ font-size: 42px;
+ font-weight: bold;
+ color: white;
+}
+
+/* Top/bottom faded items */
+.wheel-item.faded {
+ font-size: 20px;
+ opacity: 0.35;
+ cursor: pointer;
+}
+
+/* Controls */
+.timer-controls {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 20px;
+}
+
+/* Buttons */
+.control-btn {
+ flex: 1;
+ margin: 5px;
+ padding: 10px;
+ border-radius: 8px;
+ border: none;
+ cursor: pointer;
+ font-weight: bold;
+}
+
+.control-btn.start {
+ background: #22c55e;
+ color: white;
+}
+
+.control-btn.pause {
+ background: #f59e0b;
+ color: white;
+}
+
+.control-btn.reset {
+ background: #ef4444;
+ color: white;
+}