diff --git a/.gitignore b/.gitignore
index 8450615..660e40e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,5 @@ azure_sdk_key.txt
log.txt
+/web/*
+*.mov
\ No newline at end of file
diff --git a/README.md b/README.md
index 1e71e9f..3c598da 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,13 @@
-
-
-
-
Psycho Timer: A Lightweight Text-to-Speech Timer Application
-
Experience full interactivity in less than 25 KB!
+
+
+
PsychoTimer
+
The Lightest Text-to-Speech Timer App
+
Efficiency and simplicity, reimagined.
+> **UPDATE**: PsychoTimer now supports web browsers!
+> Check out the [dorpascal.com/PsychoTimer](https://dorpascal.com/PsychoTimer/) for a quick and easy way to manage your time.
+
Psycho Timer is a Python program designed specifically for candidates preparing for the Psychometric Entrance Test (PET). It aids in effective time management during the test by setting individual timers for each section of the exam, providing audible instructions and alerts, allow tests modifications and more - all combined with a simple user interface.
---
diff --git a/docs/banner.png b/docs/banner.png
new file mode 100644
index 0000000..cae3b09
Binary files /dev/null and b/docs/banner.png differ
diff --git a/docs/config.ini b/docs/config.ini
new file mode 100644
index 0000000..9785cfb
--- /dev/null
+++ b/docs/config.ini
@@ -0,0 +1,3 @@
+[Timer]
+warning_time = 300
+chapter_time = 1200
diff --git a/docs/favicon.ico b/docs/favicon.ico
new file mode 100644
index 0000000..2ca5f35
Binary files /dev/null and b/docs/favicon.ico differ
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..030ac9c
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,640 @@
+
+
+
+
+
+
+
+
+
Psycho Timer: A Lightweight Text-to-Speech Timer Application by Dor
+
+
+
+
+
+
+
+
+
+
+
+
+
PsychoTimer 2.0 Online
+
Text-to-Speech. So light. So fast. So easy.
+
20:00
+
+
Start
+
Reset
+
Quit
+
Next Chapter
+
Settings
+
+
+
+
+
+
+ 🌙
+
+
+
+
+
+
+
+
+
×
+
+ Number of Chapters:
+
+
+
+
Save
+
+
+
+
+
+
+
diff --git a/docs/main.py b/docs/main.py
new file mode 100644
index 0000000..f38eaad
--- /dev/null
+++ b/docs/main.py
@@ -0,0 +1,249 @@
+import asyncio
+import pygame
+import configparser
+import time
+
+# Initialize Pygame
+pygame.init()
+
+# Constants and Configuration
+WIDTH, HEIGHT = 800, 600
+RED = (255, 0, 0)
+DARK_MODE_BG = (20, 20, 20)
+LIGHT_MODE_BG = (240, 240, 240)
+DARK_MODE_TEXT = (240, 240, 240)
+LIGHT_MODE_TEXT = (20, 20, 20)
+
+# Load configuration
+config = configparser.ConfigParser()
+config.read('config.ini')
+pomodoro_time = config.getint('Timer', 'pomodoro_time', fallback=1500) # 25 minutes
+short_break_time = config.getint('Timer', 'short_break_time', fallback=300) # 5 minutes
+long_break_time = config.getint('Timer', 'long_break_time', fallback=900) # 15 minutes
+pomodoros_until_long_break = config.getint('Timer', 'pomodoros_until_long_break', fallback=4)
+
+# Themes and Fonts
+THEMES = {
+ "light": {
+ "bg_color": LIGHT_MODE_BG,
+ "text_color": LIGHT_MODE_TEXT,
+ "button_color": (0, 122, 255),
+ "button_hover_color": (0, 101, 210),
+ "button_text_color": (255, 255, 255),
+ "highlight_color": (255, 87, 34),
+ "progress_bar_color": (0, 122, 255),
+ "overlay_color": (255, 255, 255, 100)
+ },
+ "dark": {
+ "bg_color": DARK_MODE_BG,
+ "text_color": DARK_MODE_TEXT,
+ "button_color": (0, 122, 255),
+ "button_hover_color": (0, 101, 210),
+ "button_text_color": (255, 255, 255),
+ "highlight_color": (255, 87, 34),
+ "progress_bar_color": (0, 122, 255),
+ "overlay_color": (0, 0, 0, 100)
+ }
+}
+
+FONT = pygame.font.Font(None, 80)
+SUB_FONT = pygame.font.Font(None, 40)
+
+# Globals
+remaining_time = pomodoro_time
+pomodoros_completed = 0
+session_type = "Pomodoro" # Can be "Pomodoro", "Short Break", or "Long Break"
+running = False
+paused = False
+current_theme = "light"
+last_update_time = time.time()
+
+# Classes
+class Button:
+ def __init__(self, x, y, w, h, color, hover_color, text, text_color):
+ self.rect = pygame.Rect(x, y, w, h)
+ self.color = color
+ self.hover_color = hover_color
+ self.text = text
+ self.text_color = text_color
+ self.is_hovered = False
+
+ def draw(self, screen):
+ color = self.hover_color if self.is_hovered else self.color
+ pygame.draw.rect(screen, color, self.rect, border_radius=10)
+ text_surface = SUB_FONT.render(self.text, True, self.text_color)
+ text_rect = text_surface.get_rect(center=self.rect.center)
+ screen.blit(text_surface, text_rect)
+
+ def set_hovered(self, pos):
+ self.is_hovered = self.rect.collidepoint(pos)
+
+ def is_clicked(self, pos):
+ return self.rect.collidepoint(pos)
+
+class ProgressBar:
+ def __init__(self, x, y, w, h, color, max_value):
+ self.rect = pygame.Rect(x, y, w, h)
+ self.color = color
+ self.max_value = max_value
+ self.current_value = 0
+
+ def draw(self, screen):
+ progress_width = int(self.rect.width * self.current_value / self.max_value)
+ progress_rect = pygame.Rect(self.rect.x, self.rect.y, progress_width, self.rect.height)
+ pygame.draw.rect(screen, self.color, progress_rect, border_radius=10)
+
+ def update(self, value):
+ self.current_value = value
+
+class TimerApp:
+ def __init__(self):
+ self.screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.DOUBLEBUF | pygame.HWSURFACE)
+ pygame.display.set_caption("Designer Pomodoro Timer")
+ self.theme = THEMES[current_theme]
+ self.buttons = []
+ self.progress_bar = ProgressBar(50, 500, 700, 20, self.theme["progress_bar_color"], pomodoro_time)
+ self.create_buttons()
+ self.last_update_time = time.time()
+
+ def create_buttons(self):
+ self.buttons.append(Button(50, 550, 100, 40, self.theme["button_color"], self.theme["button_hover_color"], "Start", self.theme["button_text_color"]))
+ self.buttons.append(Button(200, 550, 100, 40, self.theme["button_color"], self.theme["button_hover_color"], "Pause", self.theme["button_text_color"]))
+ self.buttons.append(Button(350, 550, 100, 40, self.theme["button_color"], self.theme["button_hover_color"], "Stop", self.theme["button_text_color"]))
+
+ def draw_background(self):
+ self.screen.fill(self.theme["bg_color"])
+ overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
+ overlay.fill(self.theme["overlay_color"])
+ self.screen.blit(overlay, (0, 0))
+
+ def update_timer_display(self):
+ global remaining_time, session_type, pomodoros_completed
+ mins, secs = divmod(remaining_time, 60)
+ timer_str = f"{mins:02d}:{secs:02d}"
+
+ self.draw_background()
+
+ timer_text = FONT.render(timer_str, True, self.theme["text_color"])
+ session_text = SUB_FONT.render(session_type, True, self.theme["text_color"])
+ pomo_text = SUB_FONT.render(f"Pomodoros: {pomodoros_completed}", True, self.theme["text_color"])
+
+ self.screen.blit(timer_text, (self.screen.get_width() // 2 - timer_text.get_width() // 2, 100))
+ self.screen.blit(session_text, (self.screen.get_width() // 2 - session_text.get_width() // 2, 250))
+ self.screen.blit(pomo_text, (self.screen.get_width() // 2 - pomo_text.get_width() // 2, 300))
+
+ self.progress_bar.draw(self.screen)
+
+ for button in self.buttons:
+ button.draw(self.screen)
+
+ pygame.display.flip()
+
+ def display_message(self, message, color):
+ self.draw_background()
+ text = SUB_FONT.render(message, True, color)
+ self.screen.blit(text, (self.screen.get_width() // 2 - text.get_width() // 2, self.screen.get_height() // 2 - text.get_height() // 2))
+ pygame.display.flip()
+
+ def handle_events(self):
+ global remaining_time, session_type, pomodoros_completed, running, paused, current_theme
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ pygame.quit()
+ return False
+ elif event.type == pygame.KEYDOWN:
+ if event.key == pygame.K_p:
+ paused = not paused
+ elif event.key == pygame.K_n:
+ remaining_time = 0
+ elif event.key == pygame.K_q:
+ pygame.quit()
+ return False
+ elif event.key == pygame.K_t:
+ # Cycle through themes
+ themes = list(THEMES.keys())
+ current_index = themes.index(current_theme)
+ current_theme = themes[(current_index + 1) % len(themes)]
+ self.theme = THEMES[current_theme]
+ self.create_buttons()
+ elif event.type == pygame.MOUSEMOTION:
+ for button in self.buttons:
+ button.set_hovered(event.pos)
+ elif event.type == pygame.MOUSEBUTTONDOWN:
+ for button in self.buttons:
+ if button.is_clicked(event.pos):
+ if button.text == "Start":
+ running = True
+ paused = False
+ elif button.text == "Pause":
+ paused = not paused
+ elif button.text == "Stop":
+ running = False
+ remaining_time = pomodoro_time
+ return True
+
+ async def menu(self):
+ self.display_message("Press S to Start or Q to Quit", self.theme["text_color"])
+ while True:
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ pygame.quit()
+ return False
+ elif event.type == pygame.KEYDOWN:
+ if event.key == pygame.K_q:
+ pygame.quit()
+ return False
+ elif event.key == pygame.K_s:
+ return True
+ await asyncio.sleep(0)
+
+ async def run(self):
+ global remaining_time, pomodoros_completed, session_type, running, paused, current_theme
+
+ if not await self.menu():
+ return
+
+ running = True
+ paused = False
+
+ while True:
+ current_time = time.time()
+ if running and not paused:
+ remaining_time -= int(current_time - self.last_update_time)
+ self.last_update_time = current_time
+
+ if remaining_time <= 0:
+ if session_type == "Pomodoro":
+ pomodoros_completed += 1
+ if pomodoros_completed % pomodoros_until_long_break == 0:
+ session_type = "Long Break"
+ remaining_time = long_break_time
+ else:
+ session_type = "Short Break"
+ remaining_time = short_break_time
+ elif session_type == "Short Break":
+ session_type = "Pomodoro"
+ remaining_time = pomodoro_time
+ elif session_type == "Long Break":
+ session_type = "Pomodoro"
+ remaining_time = pomodoro_time
+
+ self.display_message(f"{session_type} time!", RED)
+ await asyncio.sleep(2)
+
+ # Update progress bar
+ self.progress_bar.update(pomodoro_time - remaining_time)
+
+ # Update display
+ self.update_timer_display()
+
+ # Handle events
+ if not self.handle_events():
+ return
+
+ await asyncio.sleep(1)
+
+# Run the timer application
+if __name__ == "__main__":
+ timer_app = TimerApp()
+ asyncio.run(timer_app.run())
diff --git a/docs/small-banner.png b/docs/small-banner.png
new file mode 100644
index 0000000..bde9b70
Binary files /dev/null and b/docs/small-banner.png differ
diff --git a/docs/wide-banner.png b/docs/wide-banner.png
new file mode 100644
index 0000000..04885ba
Binary files /dev/null and b/docs/wide-banner.png differ
diff --git a/online-demo.gif b/online-demo.gif
new file mode 100644
index 0000000..39104fa
Binary files /dev/null and b/online-demo.gif differ