A premium Pomodoro timer built with Angular 18 — luxury-minimalist design, reactive state management, and offline-first PWA support.
Aurea Focus is a personal productivity application built to practice modern Angular patterns and to serve my own day-to-day focus needs. Every technical decision was made with production quality in mind: reactive state with BehaviorSubject streams, clean service-to-component data flow, zero backend dependency, and a fully installable PWA.
What it demonstrates
- Reactive state management: All state lives in injectable services exposed as
Observablestreams viaBehaviorSubject. Components subscribe, react, and clean up properly — no shared mutable globals. - Clean service architecture: Each concern is isolated —
TimerServiceowns the countdown,StatisticsServiceowns persistence and streak logic,ThemeServiceowns appearance,NotificationServiceowns alerts. Components are thin orchestrators. - Adaptive business logic:
StatisticsServiceapplies a time-of-day productivity weighting (morning sessions score higher), auto-resets daily counters at midnight, and maintains a cross-session streak vialocalStorage. - PWA-first: Service Worker enabled, offline-capable, installable on desktop and mobile with no friction.
- Luxury UI: Cormorant Garamond serif display font, gold accent system, conic-gradient progress ring, smooth cubic-bezier entrance animations, and automatic dark mode after 18:00.
Stack: Angular 18 · TypeScript 5.4 · RxJS 7.8 · SCSS · Angular PWA · Web Audio API
- Architecture
- Tech stack
- Project structure
- Data flow
- Timer logic
- Statistics & productivity scoring
- Theme system
- Notes system
- Notifications
- Design system
- PWA
- Configuration
- LocalStorage schema
- Running locally
- Browser compatibility
aurea-focus/
└── src/app/
├── components/ # Presentational UI components
│ ├── timer/ # Main countdown ring + controls
│ ├── stats/ # Productivity metrics panel
│ ├── notes/ # Session notes panel
│ └── settings/ # Configuration modal
├── services/ # State & business logic (singleton)
├── models/ # TypeScript interfaces & enums
└── app.* # Root component + bootstrap
State is never shared directly between components. Components communicate through services only — a deliberate choice to keep the dependency graph flat and testable.
Services (state layer)
│
├── BehaviorSubject<T> → holds the current value
├── Observable<T> → exposed to components (read-only view)
└── methods → the only way to mutate state
│
▼
Components (UI layer)
│
├── subscribe to observable streams
├── render current state
└── call service methods on user interaction
| Technology | Version | Role |
|---|---|---|
| Angular | 18 | SPA framework |
| TypeScript | 5.4 | Language |
| RxJS | 7.8 | Reactive streams / state management |
| SCSS | Latest | Styling (design tokens via CSS variables) |
| Angular PWA | n/a | Service Worker + manifest |
| Web Audio API | n/a | In-browser sound notifications (no assets needed) |
src/app/
├── components/
│ ├── timer/
│ │ ├── timer.component.ts # Mode switching, controls, completion handler
│ │ ├── timer.component.html # Mode selector, SVG ring, control buttons
│ │ └── timer.component.scss # Ring animation, responsive layout
│ ├── stats/
│ │ ├── stats.component.ts # Subscribes to StatisticsService
│ │ ├── stats.component.html # Four metric cards
│ │ └── stats.component.scss
│ ├── notes/
│ │ ├── notes.component.ts # Note CRUD, mode-aware tagging
│ │ ├── notes.component.html # Collapsible input + note list
│ │ └── notes.component.scss
│ └── settings/
│ ├── Settings.component.ts # Settings form, persists on save
│ ├── Settings.component.html # Modal overlay with form controls
│ └── Settings.component.scss
├── services/
│ ├── timer.service.ts # Countdown engine, mode management, settings persistence
│ ├── statistics.service.ts # Session tracking, streak, productivity score
│ ├── notes.service.ts # Notes CRUD + localStorage persistence
│ ├── theme.service.ts # Light/dark toggle + auto dark mode
│ └── notification.service.ts # Toast notifications + Web Audio beep
├── models/
│ ├── timer.model.ts # TimerMode enum, TimerSettings, TimerState
│ ├── stats.model.ts # Statistics, SessionRecord
│ └── note.model.ts # Note interface
├── app.ts # Root component, layout, settings modal trigger
├── app.html # Three-panel layout (stats | timer | notes)
├── app.config.ts # Angular providers (router, HTTP)
└── main.ts # Bootstrap entry point
TimerService (interval ticks)
│
├─→ timeLeft reaches 0
├─→ calls this.pause()
├─→ increments sessionsCompleted if mode === WORK
└─→ emits updated TimerState via BehaviorSubject
│
▼
TimerComponent (subscribed to timerState$)
│
├─→ detects wasRunning && !state.isRunning && timeLeft === 0
└─→ calls onTimerComplete()
│
├─→ NotificationService.showNotification(message)
├─→ NotificationService.playSound() (if soundEnabled)
└─→ StatisticsService.addSession(mode, duration, true)
│
├─→ updates todaySessions, totalTime, productivity
├─→ calls updateStreak()
└─→ persists to localStorage
│
▼
StatsComponent (subscribed to stats$)
└─→ re-renders metric cards
TimerService drives the countdown with an RxJS interval(1000) subscription. Key design decisions:
Mode switching is guarded: changeMode() is a no-op if the timer is running. The HTML template disables mode buttons accordingly ([disabled]="timerState.isRunning").
Settings apply immediately when the timer is stopped; a running timer ignores settings changes mid-session.
Auto-transition (autoStartBreaks setting): after each session, a 3-second delay fires autoTransition() which selects the next mode (long break every 4 sessions, short break otherwise) and calls start() automatically.
Progress is a derived value computed on demand: ((totalTime − timeLeft) / totalTime) × 100. The SVG ring consumes this as a CSS custom property (--progress).
Modes:
WORK → default 25 min
SHORT_BREAK → default 5 min (after sessions 1, 2, 3)
LONG_BREAK → default 15 min (after every 4th session)
StatisticsService tracks four metrics, all persisted in localStorage:
| Metric | Description |
|---|---|
todaySessions |
Work sessions completed today; resets at midnight |
totalTime |
Cumulative work minutes this week |
currentStreak |
Consecutive days with at least one session |
productivity |
Adaptive score (0–100), starts at 95 |
Productivity weighting: sessions completed between 09:00–12:00 add +2 points; sessions between 14:00–17:00 add +1 point. The score is capped at 100.
Streak logic: on each session save, the date is compared to lastSessionDate in localStorage. Yesterday → streak increments. Any earlier → streak resets to 1. checkDailyReset() polls every 60 seconds and zeroes todaySessions when the clock crosses midnight.
ThemeService manages a 'light' | 'dark' value written to document.documentElement as a data-theme attribute. SCSS reads design tokens from CSS variables keyed on this attribute.
Auto dark mode: on service construction, if the current hour is ≥ 18 or < 6 and no stored preference exists, dark mode is activated automatically.
Manual toggle: toggleTheme() flips the current value and persists it to localStorage under aureaFocusTheme, overriding the auto rule for the remainder of the session.
NotesService maintains an ordered list of Note objects (newest first). Each note captures:
interface Note {
id: number; // Date.now() at creation time — doubles as unique key
content: string;
timestamp: string; // Formatted locale string displayed in the UI
mode: string; // Timer mode active when the note was saved
}Notes are tagged with the active timer mode so you can see at a glance whether a note was written during a focus block or a break. All CRUD operations flush synchronously to localStorage.
Toast: NotificationService.showNotification(message) pushes a NotificationMessage to a BehaviorSubject. The root component subscribes and renders the toast overlay. It auto-dismisses after 3 seconds via setTimeout.
Audio alert: NotificationService.playSound() synthesises a short 800 Hz sine-wave beep using the Web Audio API — no audio file assets required. The oscillator ramps its gain from 0.3 to ~0 over 500 ms for a clean fade-out.
| Token | Light | Dark |
|---|---|---|
| Primary | #1a1a1a |
#f5f5f5 |
| Accent (gold) | #d4af37 |
#ffd700 |
| Background | #ffffff |
#1a1a1a |
| Surface | #f8f8f8 |
#2a2a2a |
- Display / timer: Cormorant Garamond (serif) — precision and luxury
- UI / labels: Montserrat (sans-serif) — legible at small sizes
| Animation | Element | Timing |
|---|---|---|
| Slide down | Header | 0.8s cubic-bezier |
| Fade in up | Timer panel | 1s cubic-bezier |
| Fade in left / right | Side panels | 1s cubic-bezier |
| Progress ring | Conic gradient | 1s linear |
| Logo rotation | App icon | 60s continuous |
Aurea Focus is configured as an installable Progressive Web App:
ngsw-config.jsondefines the Service Worker caching strategymanifest.webmanifestprovides app name, icons, andstandalonedisplay mode- All state is in
localStorage— the app is fully functional offline after the first load - Responsive layout works on mobile and desktop without a separate build target
Default settings (editable at runtime via the Settings modal):
| Setting | Default | Range |
|---|---|---|
| Work duration | 25 min | 1–60 min |
| Short break | 5 min | 1–30 min |
| Long break | 15 min | 1–60 min |
| Sound enabled | ✅ | Boolean |
| Auto-start breaks | ❌ | Boolean |
| Deep Focus Mode | ❌ | Boolean |
Settings are persisted immediately on save and take effect from the next session — a running timer is never interrupted by a settings change mid-countdown.
| Key | Type | Description |
|---|---|---|
aureaFocusSettings |
TimerSettings JSON |
Work/break durations and feature toggles |
aureaFocusStats |
Statistics JSON |
Session counts, streak, productivity score |
aureaFocusNotes |
Note[] JSON |
All saved session notes |
aureaFocusTheme |
'light' | 'dark' |
User theme preference |
lastSessionDate |
string (date) |
Reference date for streak calculation |
No backend, no account — all data is local and private.
Prerequisites: Node.js 18+ · npm 9+
# Clone the repository
git clone <repo-url>
cd aurea-focus
# Install dependencies
npm install
# Start the development server
npm start
# → http://localhost:4200
# Production build
npm run build
# → dist/aurea-focus/browser/No environment variables or external services required.
| Browser | Version | Status |
|---|---|---|
| Chrome | 90+ | ✅ Fully Supported |
| Firefox | 88+ | ✅ Fully Supported |
| Safari | 14+ | ✅ Fully Supported |
| Edge | 90+ | ✅ Fully Supported |
Potential improvements for learning:
- Unit tests (Jasmine/Karma)
- E2E tests (Cypress/Playwright)
- Service Worker for offline capability
- Web Push Notifications API
- i18n internationalization
- Data export/import (JSON)
- Cloud sync (Firebase/Supabase)
- WCAG 2.1 AA accessibility compliance
- Analytics integration
- Dark mode system preference detection
- Clean, elegant interface with golden accents
- Animated progress ring
- Real-time statistics
- Automatically activates 6 PM - 6 AM
- Warm gold highlights on dark background
- Reduced eye strain for night sessions
Created as a personal learning project to:
- Practice modern Angular development (v18+)
- Explore reactive programming with RxJS
- Build a production-quality PWA from scratch
- Solve my own concentration challenges at work
- Demonstrate clean architecture principles
Personal project — public — no restrictive license.