diff --git a/DOXA_ROADMAP.md b/DOXA_ROADMAP.md
index 4857c98..d7eae1e 100644
--- a/DOXA_ROADMAP.md
+++ b/DOXA_ROADMAP.md
@@ -103,31 +103,9 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
---
-### 5. ✅ DOXA Habit Tracker 🎯
-**Popis**: Tracker návyků pro terminál
-
-- **Priorita**: Střední
-- **Integrace**: Motivace a sledování osobního růstu
-- **Cílová platforma**: Linux, FreeBSD
-- **Technologie**: Go, Bubble Tea
-
-**Plánované funkce**:
-- [ ] Vytváření návyků s cíli (denní, týdenní)
-- [ ] Denní sledování splnění
-- [ ] Statistiky a grafy (ASCII)
-- [ ] Motivující zprávy a upozornění
-- [ ] Export dat do CSV
-- [ ] Přehled streaku (počet po sobě jdoucích dnů)
-- [ ] Dvoujazyčné rozhraní (CZ/EN)
-
-**Odhadovaná složitost**: Střední
-**Odhadovaný čas**: 2 týdny
-
----
-
## 🌟 Bonusové nápady
-### 6. DOXA Finance 💰
+### 5. DOXA Finance 💰
**Popis**: Správa výdajů a příjmů
- **Funkce**: Evidence transakcí, kategorie, statistiky, rozpočty
@@ -137,7 +115,7 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
---
-### 7. DOXA Bookmarks 🔖
+### 6. DOXA Bookmarks 🔖
**Popis**: Správce záložek z příkazové řádky
- **Funkce**: Ukládání URL, tagování, vyhledávání, otevírání v prohlížeči
@@ -147,17 +125,7 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
---
-### 8. DOXA Timer ⏱️
-**Popis**: Pomodoro timer pro terminál
-
-- **Funkce**: Nastavení časových intervalů, sledování produktivity, statistiky
-- **Technologie**: Go, Bubble Tea
-- **Složitost**: Nízká
-- **Integrace**: Spuštění společně s doxa-todo
-
----
-
-### 9. DOXA Sync 🔄
+### 7. DOXA Sync 🔄
**Popis**: Lokální synchronizace mezi zařízeními
- **Funkce**: Synchronizace dat přes SSH/SCP, konflikt resolution
@@ -167,7 +135,7 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
---
-### 10. DOXA Backup 💾
+### 8. DOXA Backup 💾
**Popis**: Nástroj pro zálohování DOXA dat
- **Funkce**: Automatické zálohování, obnovení, verze, komprese
@@ -180,17 +148,15 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
## 📊 Prioritizace
| Pořadí | Aplikace | Priorita | Složitost | Čas | Integrace s existujícími |
-|--------|----------|----------|-----------|------|--------------------------|
+|--------|----------|----------|-----------|------|-----------------------------|
| 1 | DOXA Calendar | Vysoká | Střední | 2-3 týdny | ✅ Vysoká (doxa-todo) |
| 2 | DOXA Contacts | Vysoká | Nízká | 1-2 týdny | ✅ Střední |
| 3 | DOXA Password | Vysoká | Vysoká | 3-4 týdny | ✅ Střední (doxa-secret) |
| 4 | DOXA Journal | Střední | Nízká | 1-2 týdny | ⚠️ Nízká |
-| 5 | DOXA Habit Tracker | Střední | Střední | 2 týdny | ⚠️ Nízká |
-| 6 | DOXA Finance | Nízká | Vysoká | 3-4 týdny | ❌ Žádná |
-| 7 | DOXA Bookmarks | Nízká | Nízká | 1 týden | ❌ Žádná |
-| 8 | DOXA Timer | Nízká | Nízká | 1 týden | ✅ Střední (doxa-todo) |
-| 9 | DOXA Sync | Nízká | Vysoká | 4+ týdny | ✅ Vysoká (všechny) |
-| 10 | DOXA Backup | Nízká | Střední | 2-3 týdny | ✅ Vysoká (všechny) |
+| 5 | DOXA Finance | Nízká | Vysoká | 3-4 týdny | ❌ Žádná |
+| 6 | DOXA Bookmarks | Nízká | Nízká | 1 týden | ❌ Žádná |
+| 7 | DOXA Sync | Nízká | Vysoká | 4+ týdny | ✅ Vysoká (všechny) |
+| 8 | DOXA Backup | Nízká | Střední | 2-3 týdny | ✅ Vysoká (všechny) |
---
@@ -204,13 +170,9 @@ Tento dokument obsahuje návrhy na rozšíření kolekce DOXA aplikací. Všechn
3. **DOXA Password** - Bezpečný správce hesel
4. **DOXA Journal** - Osobní deník
-### Fáze 3: Pokročilé funkce (3-4 měsíce)
-5. **DOXA Habit Tracker** - Sledování návyků
-6. **DOXA Timer** - Pomodoro timer
-
-### Fáze 4: Systémové nástroje (4+ měsíce)
-7. **DOXA Sync** - Synchronizace mezi zařízeními
-8. **DOXA Backup** - Zálohovací nástroj
+### Fáze 3: Systémové nástroje (3+ měsíce)
+5. **DOXA Sync** - Synchronizace mezi zařízeními
+6. **DOXA Backup** - Zálohovací nástroj
---
@@ -232,12 +194,13 @@ Všechny nové aplikace by měly:
- [x] DOXA Notes - ✅ Kompletní
- [x] DOXA CLI - ✅ Kompletní
- [x] DOXA Secret - ✅ Kompletní
-- [x] DOXA TODO - ✅ Kompletní (nově přidáno označování splněných úkolů)
+- [x] DOXA TODO - ✅ Kompletní
+- [x] DOXA Timer - ✅ Kompletní (nově přidáno)
+- [x] DOXA Habit - ✅ Kompletní (nově přidáno)
- [ ] DOXA Calendar - ⏳ Plánováno
- [ ] DOXA Contacts - ⏳ Plánováno
- [ ] DOXA Password - ⏳ Plánováno
- [ ] DOXA Journal - ⏳ Plánováno
-- [ ] DOXA Habit Tracker - ⏳ Plánováno
---
@@ -247,4 +210,4 @@ Pokud máte nápady na další aplikace nebo chcete upřednostnit některý z n
---
-*Poslední aktualizace: 9. srpna 2026*
+*Poslední aktualizace: 16. srpna 2026*
diff --git a/README.md b/README.md
index 76f1fc1..132464c 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
-
+
@@ -16,7 +16,7 @@
## 🌍 DOXA
-**DOXA** is a collection of three minimalist, locally-focused applications for note management, message encryption, and command-line text processing. All projects share a common philosophy: **complete independence, privacy, and control over your data**. No cloud services, no tracking, no external dependencies. Your data belongs only to you.
+**DOXA** is a collection of six minimalist, locally-focused applications for note management, message encryption, task management, Pomodoro productivity and habit tracking. All projects share a common philosophy: **complete independence, privacy, and control over your data**. No cloud services, no tracking, no external dependencies. Your data belongs only to you.
---
@@ -101,20 +101,66 @@
---
+### 5. ⏱️ [DOXA Timer](doxa-timer/)
+
+**Pomodoro timer for the terminal with productivity statistics**
+
+- **Description**: A minimalist Pomodoro timer for the terminal. Helps you focus using the Pomodoro technique — alternating work sessions with short and long breaks. Tracks your daily and total completed Pomodoros.
+- **Philosophy**: Local-first productivity. No cloud apps, no subscriptions — just clean focus. Data stored in `~/.doxa-timer/`.
+- **Technologies**: Go (Golang), Bubble Tea (TUI framework), Lip Gloss (styling).
+- **Platforms**: Linux, FreeBSD.
+- **Features**:
+ - Pomodoro technique: work session (25 min), short break (5 min), long break (15 min).
+ - Live countdown timer with ASCII progress bar.
+ - Configurable session lengths.
+ - Daily and total Pomodoro statistics.
+ - Bilingual interface (Czech/English) with `l` key toggle.
+ - Data stored in `~/.doxa-timer/doxa_timer.json`.
+
+📖 [Read detailed documentation](doxa-timer/README.md)
+
+---
+
+### 6. 🎯 [DOXA Habit](doxa-habit/)
+
+**Habit tracker for the terminal with streak tracking**
+
+- **Description**: A minimalist terminal application for building and tracking habits. Monitor your daily and weekly habits, track streaks and visualize progress with ASCII heatmaps.
+- **Philosophy**: Local-first personal growth. No gamification traps, no notification manipulation — just your data and your progress. Data stored in `~/.doxa-habit/`.
+- **Technologies**: Go (Golang), Bubble Tea (TUI framework), Lip Gloss (styling).
+- **Platforms**: Linux, FreeBSD.
+- **Features**:
+ - Daily and weekly habits with completion tracking.
+ - Streak counter (consecutive days completed).
+ - ASCII heatmap for the last 7 days.
+ - Success rate statistics (7/30 days).
+ - Export data to CSV format.
+ - Search habits by name.
+ - Bilingual interface (Czech/English) with `l` key toggle.
+ - Data stored in `~/.doxa-habit/doxa_habit.json`.
+
+📖 [Read detailed documentation](doxa-habit/README.md)
+
+---
+
## 🔗 Common Features of All DOXA Projects
-| Feature | DOXA Notes | DOXA CLI | DOXA Secret | DOXA TODO |
-|---------|------------|----------|--------------|-----------|
-| **Local Data Storage** | ✅ | ✅ | ✅ | ✅ |
-| **Open Format (JSON)** | ✅ | ✅ | ✅ | ✅ |
-| **No Cloud Dependencies** | ✅ | ✅ | ✅ | ✅ |
-| **Open Source (MIT)** | ✅ | ✅ | ✅ | ✅ |
-| **Minimalist Design** | ✅ | ✅ | ✅ | ✅ |
-| **Tag Support** | ✅ | ✅ | ❌ | ✅ |
-| **Search and Filtering** | ✅ | ✅ | ❌ | ✅ |
-| **Encryption** | ❌ | ❌ | ✅ | ❌ |
-| **Priority Levels** | ❌ | ❌ | ❌ | ✅ |
-| **Categories** | ❌ | ❌ | ❌ | ✅ |
+| Feature | DOXA Notes | DOXA CLI | DOXA Secret | DOXA TODO | DOXA Timer | DOXA Habit |
+|---------|------------|----------|-------------|-----------|------------|------------|
+| **Local Data Storage** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Open Format (JSON)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **No Cloud Dependencies** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Open Source (MIT)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Minimalist Design** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Bilingual (CZ/EN)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Tag Support** | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ |
+| **Search & Filtering** | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
+| **Encryption** | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
+| **Priority Levels** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
+| **Categories** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
+| **Pomodoro Timer** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
+| **Streak / Habit Stats** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
+| **Data Export** | ✅ JSON | ✅ TXT | ✅ TXT | ✅ TXT | ✅ JSON | ✅ CSV |
---
@@ -126,6 +172,8 @@ Each project has its own installation process. Detailed guides can be found in t
- [DOXA CLI – Installation](doxa-cli/README.md#installation)
- [DOXA Secret – Installation](doxa-secret/README_EN.md#installation)
- [DOXA TODO – Installation](doxa-todo/README.md#installation)
+- [DOXA Timer – Installation](doxa-timer/README.md#installation)
+- [DOXA Habit – Installation](doxa-habit/README.md#installation)
---
@@ -170,7 +218,7 @@ DOXA believes in:
## 🌍 DOXA
-**DOXA** je sbírka tří minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv a práci s textem z příkazové řádky. Všechny projekty sdílejí společnou filozofii: **úplná nezávislost, soukromí a kontrola nad vašimi daty**. Žádné cloudové služby, žádné sledování, žádné externí závislosti. Vaše data patří pouze vám.
+**DOXA** je sbírka šesti minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv, správu úkolů, Pomodoro produktivitu a sledování návyků. Všechny projekty sdílejí společnou filozofii: **úplná nezávislost, soukromí a kontrola nad vašimi daty**. Žádné cloudové služby, žádné sledování, žádné externí závislosti. Vaše data patří pouze vám.
---
@@ -255,20 +303,66 @@ DOXA believes in:
---
+### 5. ⏱️ [DOXA Timer](doxa-timer/)
+
+**Pomodoro timer pro terminál se statistikami produktivity**
+
+- **Popis**: Minimalistický Pomodoro timer pro terminál. Pomáhá vám se soustředit pomocí Pomodoro techniky — střídání pracovních sezení s krátkými a dlouhými přestávkami. Sleduje denní a celkový počet dokončených Pomodorů.
+- **Filozofie**: Lokální produktivita. Žádné cloudové aplikace, žádné předplatné — jen čistý fokus na vaši práci. Data uložena v `~/.doxa-timer/`.
+- **Technologie**: Go (Golang), Bubble Tea (TUI framework), Lip Gloss (stylování).
+- **Platformy**: Linux, FreeBSD.
+- **Vlastnosti**:
+ - Pomodoro technika: pracovní sezení (25 min), krátká přestávka (5 min), dlouhá přestávka (15 min).
+ - Live odpočítávání s ASCII progress barem.
+ - Konfigurovatelné délky sezení.
+ - Denní a celkové statistiky Pomodorů.
+ - Dvoujazyčné rozhraní (česky/anglicky) s přepínáním klávesou `l`.
+ - Data uložena v `~/.doxa-timer/doxa_timer.json`.
+
+📖 [Přečtěte si podrobnější dokumentaci](doxa-timer/README_CZ.md)
+
+---
+
+### 6. 🎯 [DOXA Habit](doxa-habit/)
+
+**Tracker návyků pro terminál se sledováním streaku**
+
+- **Popis**: Minimalistická terminálová aplikace pro budování a sledování návyků. Sledujte denní a týdenní návyky, streaky a vizualizujte pokrok pomocí ASCII heatmap.
+- **Filozofie**: Lokální osobní rozvoj. Žádné gamifikační pasti, žádná manipulace s notifikacemi — jen vaše data a váš pokrok. Data uložena v `~/.doxa-habit/`.
+- **Technologie**: Go (Golang), Bubble Tea (TUI framework), Lip Gloss (stylování).
+- **Platformy**: Linux, FreeBSD.
+- **Vlastnosti**:
+ - Denní a týdenní návyky se sledováním splnění.
+ - Streak čítač (po sobě jdoucí dny splnění).
+ - ASCII heatmap posledních 7 dní.
+ - Statistiky úspěšnosti (7/30 dní).
+ - Export dat do CSV formátu.
+ - Vyhledávání návyků dle názvu.
+ - Dvoujazyčné rozhraní (česky/anglicky) s přepínáním klávesou `l`.
+ - Data uložena v `~/.doxa-habit/doxa_habit.json`.
+
+📖 [Přečtěte si podrobnější dokumentaci](doxa-habit/README_CZ.md)
+
+---
+
## 🔗 Společné vlastnosti všech projektů DOXA
-| Vlastnost | DOXA Notes | DOXA CLI | DOXA Secret | DOXA TODO |
-|-----------|------------|----------|--------------|-----------|
-| **Lokální uložení dat** | ✅ | ✅ | ✅ | ✅ |
-| **Otevřený formát (JSON)** | ✅ | ✅ | ✅ | ✅ |
-| **Žádné cloudové závislosti** | ✅ | ✅ | ✅ | ✅ |
-| **Open Source (MIT)** | ✅ | ✅ | ✅ | ✅ |
-| **Minimalistický design** | ✅ | ✅ | ✅ | ✅ |
-| **Podpora tagů** | ✅ | ✅ | ❌ | ✅ |
-| **Vyhledávání a filtrování** | ✅ | ✅ | ❌ | ✅ |
-| **Šifrování** | ❌ | ❌ | ✅ | ❌ |
-| **Úrovně priority** | ❌ | ❌ | ❌ | ✅ |
-| **Kategorie** | ❌ | ❌ | ❌ | ✅ |
+| Vlastnost | DOXA Notes | DOXA CLI | DOXA Secret | DOXA TODO | DOXA Timer | DOXA Habit |
+|-----------|------------|----------|-------------|-----------|------------|------------|
+| **Lokální uložení dat** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Otevřený formát (JSON)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Žádné cloudové závislosti** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Open Source (MIT)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Minimalistický design** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Dvoujazyčné rozhraní** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Podpora tagů** | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ |
+| **Vyhledávání a filtrování** | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
+| **Šifrování** | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
+| **Úrovně priority** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
+| **Kategorie** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
+| **Pomodoro timer** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
+| **Streak / Statistiky návyků** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
+| **Export dat** | ✅ JSON | ✅ TXT | ✅ TXT | ✅ TXT | ✅ JSON | ✅ CSV |
---
@@ -280,6 +374,8 @@ Každý projekt má vlastní instalační postup. Podrobné návody naleznete v
- [DOXA CLI – Instalace](doxa-cli/README_CZ.md#instalace)
- [DOXA Secret – Instalace](doxa-secret/README.md#instalace)
- [DOXA TODO – Instalace](doxa-todo/README_CZ.md#instalace)
+- [DOXA Timer – Instalace](doxa-timer/README_CZ.md#instalace)
+- [DOXA Habit – Instalace](doxa-habit/README_CZ.md#instalace)
---
diff --git a/doxa-habit/LICENSE b/doxa-habit/LICENSE
new file mode 100644
index 0000000..b459e5f
--- /dev/null
+++ b/doxa-habit/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Kankys
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/doxa-habit/MANUAL.md b/doxa-habit/MANUAL.md
new file mode 100644
index 0000000..2e965de
--- /dev/null
+++ b/doxa-habit/MANUAL.md
@@ -0,0 +1,41 @@
+# DOXA Habit User Manual 🎯
+
+This manual describes how to navigate and manage habits in the DOXA Habit tracker.
+
+## Getting Started
+
+Start the application by running:
+```bash
+doxa-habit
+```
+Press `Enter` on the splash screen to enter the main interface.
+
+## Interface States
+
+1. **MAIN VIEW**: A list showing all your habits, their current streaks (e.g., `🔥3` for 3 consecutive days, or `❄️0`), and a mini calendar heatmap showing the last 7 days (`▓` for completed, `░` for missed).
+2. **DETAIL MODAL**: Displays description, frequency, stats for last 7/30 days, and a detailed heatmap.
+3. **STATS VIEW**: Table summarizing all your habits' statistics.
+4. **ADD / EDIT FORM**: Text inputs to add or modify habits.
+
+## Controls
+
+### Main Screen
+- `1`: Add a new habit.
+- `2`: Edit the selected habit.
+- `3`: Export completion history to `~/.doxa-habit/export/ Úplná nezávislost, soukromí a kontrola nad daty
- DOXA je sbírka čtyř minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv a práci s textem z příkazové řádky.
+ DOXA je sbírka šesti minimalistických, lokálně orientovaných aplikací pro správu poznámek, šifrování zpráv, správu úkolů, Pomodoro timer a sledování návyků.
Všechny projekty sdílejí společnou filozofii: úplná nezávislost, soukromí a kontrola nad vašimi daty.
Žádná cloudová služba, žádné sledování, žádné externí závislosti. Vaše data patří pouze vám.
Přečtěte si podrobnější dokumentaci Pomodoro timer pro terminál se statistikami produktivity Local-first produktivita. Žádné cloudové aplikace, žádné předplatné – jen čistý fokus na vaši práci. Data uložena v Funkce: Pomodoro technika (25/5/15 min) · Live countdown · Konfigurovatelné délky sessions · Statistiky pomodorů · Dvoujazyčné rozhraní (CZ/EN) Technologie: Go (Golang), Bubble Tea, Lip Gloss Platformy: Linux, FreeBSD Tracker návyků pro terminál se sledováním streaku Sledujte svůj osobní růst lokálně. Bez notifikačních manipulací, bez gamifikačního tlaku – jen vaše data a váš pokrok. Data uložena v Funkce: Denní/týdenní návyky · Streak tracker · ASCII heatmap · Statistiky úspěšnosti (7/30 dní) · Export do CSV · Dvoujazyčné rozhraní (CZ/EN) Technologie: Go (Golang), Bubble Tea, Lip Gloss Platformy: Linux, FreeBSD
@@ -130,10 +130,37 @@
+
+
+ ⏱️ DOXA Timer
+ ~/.doxa-timer/.
+
+
+ 🎯 DOXA Habit
+ ~/.doxa-habit/.
Společné vlastnosti všech projektů DOXA
+
+
@@ -142,6 +169,8 @@
@@ -151,6 +180,8 @@
DOXA CLI
DOXA Secret
DOXA TODO
+ DOXA Timer
+ DOXA Habit
✅
✅
✅
+ ✅
+ ✅
Otevřený formát (JSON)
@@ -158,6 +189,8 @@
✅
✅
✅
+ ✅
+ ✅
Žádné cloudové závislosti
@@ -165,6 +198,8 @@
✅
✅
✅
+ ✅
+ ✅
Open Source (MIT)
@@ -172,6 +207,8 @@
✅
✅
✅
+ ✅
+ ✅
+ Minimalistický design
@@ -179,6 +216,17 @@
✅
✅
✅
+ ✅
+ ✅
+
+
Dvoujazyčné rozhraní (CZ/EN)
+ ✅
+ ✅
+ ✅
+ ✅
+ ✅
+ ✅
Podpora tagů
@@ -186,6 +234,8 @@
✅
❌
✅
+ ❌
+ ❌
Vyhledávání a filtrování
@@ -193,6 +243,8 @@
✅
❌
✅
+ ❌
+ ✅
Šifrování
@@ -200,6 +252,8 @@
❌
✅
❌
+ ❌
+ ❌
Úrovně priority
@@ -207,26 +261,50 @@
❌
❌
✅
+ ❌
+ ❌
-
+ Kategorie
+ Pomodoro timer
+ ❌
+ ❌
+ ❌
+ ❌
+ ✅
+ ❌
+
+
+ Streak / Statistiky návyků
+ ❌
+ ❌
❌
❌
❌
✅
+
Export dat
+ ✅ JSON
+ ✅ TXT
+ ✅ TXT
+ ✅ TXT
+ ✅ JSON
+ ✅ CSV
+
Instalace
-
@@ -256,18 +334,16 @@
Hlavní návrhy
-
Bonusové nápady
@@ -296,7 +372,7 @@
diff --git a/doxa-timer/LICENSE b/doxa-timer/LICENSE
new file mode 100644
index 0000000..b459e5f
--- /dev/null
+++ b/doxa-timer/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Kankys
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/doxa-timer/MANUAL.md b/doxa-timer/MANUAL.md
new file mode 100644
index 0000000..3268034
--- /dev/null
+++ b/doxa-timer/MANUAL.md
@@ -0,0 +1,43 @@
+# DOXA Timer User Manual ⏱️
+
+This manual explains how to use the DOXA Timer application.
+
+## Getting Started
+
+Run the program with:
+```bash
+doxa-timer
+```
+
+When you first launch the app, you will see a startup screen. Press `Enter` to access the main interface.
+
+## Interface States
+
+1. **READY**: The timer is set and waiting for you to start.
+2. **FOCUS**: Work interval. Focus on your tasks.
+3. **SHORT BREAK / LONG BREAK**: Relax. The countdown signals the break duration.
+4. **SETTINGS**: Configuration menu to set durations.
+5. **STATS**: Historical data.
+
+## Controls
+
+### Main Screen
+- `Space`: Starts or pauses the countdown.
+- `r`: Resets the countdown back to the configured focus or break time.
+- `s`: Opens Settings.
+- `t`: Opens Stats.
+- `l`: Toggles UI translation (English/Czech).
+- `q` / `Esc`: Exits the application.
+
+### Settings Screen
+- `up`/`down` or `k`/`j`: Navigate between different settings options:
+ - Focus time
+ - Short break time
+ - Long break time
+ - Long break after X pomodoros
+- `+` / `-`: Increase or decrease the value of the active option.
+- `Enter`: Save settings and return to the main screen.
+- `q` / `Esc`: Go back to the main screen without saving changes.
+
+### Stats Screen
+- `q` / `Esc`: Return to the main screen.
diff --git a/doxa-timer/MANUAL_CZ.md b/doxa-timer/MANUAL_CZ.md
new file mode 100644
index 0000000..37ba8f9
--- /dev/null
+++ b/doxa-timer/MANUAL_CZ.md
@@ -0,0 +1,43 @@
+# Uživatelský manuál DOXA Timer ⏱️
+
+Tento manuál popisuje použití aplikace DOXA Timer.
+
+## Spuštění programu
+
+Spusťte aplikaci příkazem:
+```bash
+doxa-timer
+```
+
+Při prvním spuštění se zobrazí úvodní logo a text. Stiskněte `Enter` pro přechod do hlavního rozhraní.
+
+## Stavy aplikace
+
+1. **PŘIPRAVEN (READY)**: Časovač čeká na spuštění.
+2. **FOKUS (FOCUS)**: Pracovní interval. Soustřeďte se na práci.
+3. **KRÁTKÁ / DLOUHÁ PŘESTÁVKA**: Čas na relaxaci a odpočinek.
+4. **NASTAVENÍ (SETTINGS)**: Konfigurace délek časovačů.
+5. **STATISTIKY (STATS)**: Přehled o dokončených sezeních.
+
+## Ovládání
+
+### Hlavní obrazovka
+- `Mezerník (Space)`: Spustí nebo pozastaví aktuální odpočet.
+- `r`: Obnoví (resetuje) aktuální časovač zpět na plnou délku.
+- `s`: Přejde do Nastavení.
+- `t`: Přejde do Statistik.
+- `l`: Přepne jazyk mezi češtinou a angličtinou.
+- `q` / `Esc`: Ukončí program.
+
+### Obrazovka Nastavení
+- `šipka nahoru/dolů` nebo `k`/`j`: Pohyb mezi položkami nastavení:
+ - Délka fokusu
+ - Krátká přestávka
+ - Dlouhá přestávka
+ - Dlouhá přestávka po X pomodorech
+- `+` / `-`: Zvýší nebo sníží hodnotu zvolené položky.
+- `Enter`: Uloží konfiguraci a vrátí se na hlavní obrazovku.
+- `q` / `Esc`: Vrátí se na hlavní obrazovku bez uložení změn.
+
+### Obrazovka Statistiky
+- `q` / `Esc`: Vrátí se na hlavní obrazovku.
diff --git a/doxa-timer/README.md b/doxa-timer/README.md
new file mode 100644
index 0000000..8e78b1b
--- /dev/null
+++ b/doxa-timer/README.md
@@ -0,0 +1,32 @@
+# DOXA Timer ⏱️
+
+**Minimalist Pomodoro timer for the terminal with local statistics**
+
+DOXA Timer is a terminal-based Pomodoro app. It alternates focus work sessions with short and long breaks to optimize concentration, while tracking your sessions locally.
+
+## Features
+
+- **Pomodoro Technique**: Standard Focus (25m), Short Break (5m), and Long Break (15m).
+- **Live Countdown & Progress**: Interactive ASCII progress bar.
+- **Persistent Local Stats**: Tracking of today's and total Pomodoros.
+- **Customizable Intervals**: Adjust times inside the app settings.
+- **Sovereign & Offline**: Everything is saved locally at `~/.doxa-timer/doxa_timer.json`.
+- **Bilingual TUI**: Switch between English and Czech using the `l` key.
+
+## Installation
+
+Go to the `doxa-timer` folder and run the install script:
+
+```bash
+chmod +x install.sh
+./install.sh
+```
+
+## Hotkeys
+
+- `Space` - Start / Pause / Resume
+- `r` - Reset current timer
+- `s` - Open Settings (press `+`/`-` to adjust times, `Enter` to Save, `q` to Cancel)
+- `t` - Show Statistics
+- `l` - Toggle language (EN/CS)
+- `q` or `Esc` - Go back / Quit
diff --git a/doxa-timer/README_CZ.md b/doxa-timer/README_CZ.md
new file mode 100644
index 0000000..3a8ea86
--- /dev/null
+++ b/doxa-timer/README_CZ.md
@@ -0,0 +1,32 @@
+# DOXA Timer ⏱️
+
+**Minimalistický Pomodoro časovač do terminálu s lokální statistikou**
+
+DOXA Timer je terminálová Pomodoro aplikace. Střídá intervaly plného soustředění na práci s krátkými a dlouhými přestávkami pro zvýšení produktivity.
+
+## Vlastnosti
+
+- **Metoda Pomodoro**: Fokus (25 min), Krátká přestávka (5 min) a Dlouhá přestávka (15 min).
+- **Živý odpočet**: ASCII progress bar s procentuálním vyjádřením pokroku.
+- **Lokální statistika**: Počítadlo celkových a dnešních dokončených Pomodorů.
+- **Nastavitelnost**: Změna délek jednotlivých fází v menu Nastavení.
+- **Local-first**: Data se ukládají lokálně do `~/.doxa-timer/doxa_timer.json`.
+- **Dvojjazyčnost**: Přepínání češtiny a angličtiny pomocí klávesy `l`.
+
+## Instalace
+
+Přejděte do složky `doxa-timer` a spusťte instalační skript:
+
+```bash
+chmod +x install.sh
+./install.sh
+```
+
+## Klávesové zkratky
+
+- `Space` - Start / Pauza / Pokračovat
+- `r` - Reset aktuálního odpočtu
+- `s` - Otevřít Nastavení (klávesy `+`/`-` mění hodnotu, `Enter` uloží, `q` zruší změny)
+- `t` - Zobrazit Statistiky
+- `l` - Přepnutí jazyka (CZ/EN)
+- `q` nebo `Esc` - Zpět / Konec
diff --git a/doxa-timer/doxa-timer b/doxa-timer/doxa-timer
new file mode 100755
index 0000000..f460a8e
Binary files /dev/null and b/doxa-timer/doxa-timer differ
diff --git a/doxa-timer/go.mod b/doxa-timer/go.mod
new file mode 100644
index 0000000..a0e365b
--- /dev/null
+++ b/doxa-timer/go.mod
@@ -0,0 +1,31 @@
+module doxa-timer
+
+go 1.26.5
+
+require (
+ github.com/charmbracelet/bubbletea v1.3.10
+ github.com/charmbracelet/lipgloss v1.1.0
+)
+
+require (
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
+ github.com/charmbracelet/colorprofile v0.4.1 // indirect
+ github.com/charmbracelet/x/ansi v0.11.6 // indirect
+ github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
+ github.com/charmbracelet/x/term v0.2.2 // indirect
+ github.com/clipperhouse/displaywidth v0.9.0 // indirect
+ github.com/clipperhouse/stringish v0.1.1 // indirect
+ github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
+ github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-localereader v0.0.1 // indirect
+ github.com/mattn/go-runewidth v0.0.19 // indirect
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
+ github.com/muesli/cancelreader v0.2.2 // indirect
+ github.com/muesli/termenv v0.16.0 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
+ golang.org/x/sys v0.38.0 // indirect
+ golang.org/x/text v0.3.8 // indirect
+)
diff --git a/doxa-timer/go.sum b/doxa-timer/go.sum
new file mode 100644
index 0000000..b7000fb
--- /dev/null
+++ b/doxa-timer/go.sum
@@ -0,0 +1,48 @@
+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
+github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
+github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
+github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
+github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
+github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
+github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
+github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
+github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
+github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
+github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
+github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
+github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
+github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
+github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
+github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
+github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
+github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
+github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
+github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
+github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
+github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
+github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
+github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
+github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
+github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
+github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
+github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
+github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
diff --git a/doxa-timer/install.sh b/doxa-timer/install.sh
new file mode 100755
index 0000000..0f70e15
--- /dev/null
+++ b/doxa-timer/install.sh
@@ -0,0 +1,99 @@
+#!/bin/bash
+
+# DOXA-TIMER Installation Script
+
+# --- Nastavení ---
+
+APP_NAME="doxa-timer"
+INSTALL_DIR="$HOME/.local/bin"
+CONFIG_DIR="$HOME/.doxa-timer"
+CONFIG_FILE="doxa_timer.json"
+REPO_URL="https://codeberg.org/Kankys/doxa.git"
+
+# --- Funkce ---
+
+error_exit() {
+ echo -e "\033[1;31mERROR: $1\033[0m" >&2
+ exit 1
+}
+
+success_message() {
+ echo -e "\033[1;32mSUCCESS: $1\033[0m"
+}
+
+info_message() {
+ echo -e "\033[1;34mINFO: $1\033[0m"
+}
+
+# --- Hlavní část skriptu ---
+
+info_message "Kontrola Go instalace..."
+if ! command -v go &> /dev/null
+then
+ error_exit "Go není nainstalováno. Prosím, nainstalujte Go před spuštěním tohoto skriptu."
+fi
+success_message "Go je nainstalováno."
+
+info_message "Kontrola aktuálního adresáře..."
+if [ -f "main.go" ] && [ -f "go.mod" ] && grep -q "module doxa-timer" go.mod; then
+ info_message "Jste již v adresáři $APP_NAME, používám místní verzi."
+else
+ error_exit "Tento skript spouštějte z adresáře doxa-timer."
+fi
+
+info_message "Stažení závislostí projektu..."
+if ! go mod tidy
+then
+ error_exit "Nepodařilo se stáhnout závislosti Go."
+fi
+success_message "Závislosti projektu úspěšně staženy."
+
+info_message "Kompilace aplikace..."
+APP_BINARY="$APP_NAME"
+if ! go build -o "$APP_BINARY" .
+then
+ error_exit "Nepodařilo se zkompilovat aplikaci."
+fi
+success_message "Aplikace úspěšně zkompilována do './$APP_BINARY'."
+
+info_message "Instalace aplikace..."
+
+if ! mkdir -p "$INSTALL_DIR"
+then
+ error_exit "Nepodařilo se vytvořit cílový adresář '$INSTALL_DIR'."
+fi
+
+if ! cp "$APP_BINARY" "$INSTALL_DIR/"
+then
+ error_exit "Nepodařilo se zkopírovat binárku do '$INSTALL_DIR'."
+fi
+
+chmod +x "$INSTALL_DIR/$APP_NAME"
+success_message "Aplikace '$APP_NAME' úspěšně nainstalována do '$INSTALL_DIR'."
+
+info_message "Nastavení konfiguračního adresáře..."
+
+if ! mkdir -p "$CONFIG_DIR"
+then
+ error_exit "Nepodařilo se vytvořit konfigurační adresář '$CONFIG_DIR'."
+fi
+
+if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]
+then
+ echo "{}" > "$CONFIG_DIR/$CONFIG_FILE"
+ if [ $? -ne 0 ]
+ then
+ error_exit "Nepodařilo se vytvořit konfigurační soubor '$CONFIG_DIR/$CONFIG_FILE'."
+ fi
+ success_message "Konfigurační soubor '$CONFIG_DIR/$CONFIG_FILE' vytvořen."
+else
+ info_message "Konfigurační soubor '$CONFIG_DIR/$CONFIG_FILE' již existuje."
+fi
+
+info_message "Instalace dokončena."
+success_message "Nyní můžete spustit aplikaci '$APP_NAME' z jakéhokoli místa v terminálu."
+echo ""
+echo -e "\033[1;33mPRO TIP:\nPokud \`$APP_NAME\` nefunguje z jakéhokoli místa, přidejte '$INSTALL_DIR' do vašeho PATH.\nexport PATH=\$PATH:$INSTALL_DIR\033[0m"
+echo ""
+
+exit 0
diff --git a/doxa-timer/main.go b/doxa-timer/main.go
new file mode 100644
index 0000000..4422e42
--- /dev/null
+++ b/doxa-timer/main.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ tea "github.com/charmbracelet/bubbletea"
+)
+
+func main() {
+ m := NewModel()
+
+ p := tea.NewProgram(m)
+ if _, err := p.Run(); err != nil {
+ fmt.Printf("Chyba při spuštění programu: %v\n", err)
+ os.Exit(1)
+ }
+}
diff --git a/doxa-timer/storage.go b/doxa-timer/storage.go
new file mode 100644
index 0000000..327edc3
--- /dev/null
+++ b/doxa-timer/storage.go
@@ -0,0 +1,86 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+// TimerStats holds persistent statistics and settings
+type TimerStats struct {
+ TotalPomodoros int `json:"total_pomodoros"`
+ TodayPomodoros int `json:"today_pomodoros"`
+ LastDate string `json:"last_date"`
+ FocusDuration int `json:"focus_duration"` // minutes
+ ShortBreak int `json:"short_break"` // minutes
+ LongBreak int `json:"long_break"` // minutes
+ LongBreakAfter int `json:"long_break_after"` // after how many pomodoros
+}
+
+func getStoragePath() string {
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return "doxa_timer.json"
+ }
+ configDir := filepath.Join(home, ".doxa-timer")
+ if _, err := os.Stat(configDir); os.IsNotExist(err) {
+ os.MkdirAll(configDir, 0755)
+ }
+ return filepath.Join(configDir, "doxa_timer.json")
+}
+
+func loadStats() TimerStats {
+ path := getStoragePath()
+ file, err := os.ReadFile(path)
+ if err != nil {
+ return defaultStats()
+ }
+ var stats TimerStats
+ if err := json.Unmarshal(file, &stats); err != nil {
+ return defaultStats()
+ }
+ // Reset today's count if it's a new day
+ today := time.Now().Format("2006-01-02")
+ if stats.LastDate != today {
+ stats.TodayPomodoros = 0
+ stats.LastDate = today
+ }
+ // Ensure sane defaults if values are zero
+ if stats.FocusDuration == 0 {
+ stats.FocusDuration = 25
+ }
+ if stats.ShortBreak == 0 {
+ stats.ShortBreak = 5
+ }
+ if stats.LongBreak == 0 {
+ stats.LongBreak = 15
+ }
+ if stats.LongBreakAfter == 0 {
+ stats.LongBreakAfter = 4
+ }
+ return stats
+}
+
+func saveStats(stats TimerStats) {
+ path := getStoragePath()
+ data, err := json.MarshalIndent(stats, "", " ")
+ if err != nil {
+ fmt.Println("Chyba při ukládání dat:", err)
+ return
+ }
+ os.WriteFile(path, data, 0644)
+}
+
+func defaultStats() TimerStats {
+ return TimerStats{
+ TotalPomodoros: 0,
+ TodayPomodoros: 0,
+ LastDate: time.Now().Format("2006-01-02"),
+ FocusDuration: 25,
+ ShortBreak: 5,
+ LongBreak: 15,
+ LongBreakAfter: 4,
+ }
+}
diff --git a/doxa-timer/translations.go b/doxa-timer/translations.go
new file mode 100644
index 0000000..8a473d9
--- /dev/null
+++ b/doxa-timer/translations.go
@@ -0,0 +1,111 @@
+package main
+
+// Translation holds all UI strings for one language
+type Translation struct {
+ Title string
+ MenuStart string
+ MenuPause string
+ MenuReset string
+ MenuSettings string
+ MenuStats string
+ MenuLang string
+ MenuQuit string
+ StateIdle string
+ StateRunning string
+ StatePaused string
+ StateBreak string
+ StateLongBreak string
+ StateDone string
+ LabelFocus string
+ LabelShortBreak string
+ LabelLongBreak string
+ LabelLongAfter string
+ LabelTodayCount string
+ LabelTotalCount string
+ LabelNextBreak string
+ HelpIdle string
+ HelpRunning string
+ HelpSettings string
+ HelpStats string
+ SettingsTitle string
+ StatsTitle string
+ SessionComplete string
+ BreakComplete string
+ PomodorosToday string
+ PomodorosTotal string
+ NextLongBreakIn string
+ MinutesShort string
+}
+
+var Translations = map[string]Translation{
+ "cs": {
+ Title: "DOXA-TIMER 🦅",
+ MenuStart: "Space: Start",
+ MenuPause: "Space: Pauza",
+ MenuReset: "r: Reset",
+ MenuSettings: "s: Nastavení",
+ MenuStats: "t: Statistiky",
+ MenuLang: "l: EN",
+ MenuQuit: "q: Konec",
+ StateIdle: "PŘIPRAVEN",
+ StateRunning: "FOKUS",
+ StatePaused: "PAUZA",
+ StateBreak: "KRÁTKÁ PŘESTÁVKA",
+ StateLongBreak: "DLOUHÁ PŘESTÁVKA",
+ StateDone: "DOKONČENO!",
+ LabelFocus: "Fokus:",
+ LabelShortBreak: "Krátká přestávka:",
+ LabelLongBreak: "Dlouhá přestávka:",
+ LabelLongAfter: "Dlouhá přestávka po:",
+ LabelTodayCount: "Dnes:",
+ LabelTotalCount: "Celkem:",
+ LabelNextBreak: "Příští dlouhá přestávka za:",
+ HelpIdle: "Space: Spustit | r: Reset | s: Nastavení | t: Statistiky | l: EN | q: Konec",
+ HelpRunning: "Space: Pauza | r: Reset | q: Konec",
+ HelpSettings: "+/-: Změnit fokus | b/n: Změnit přestávky | Enter: Uložit | q: Zpět",
+ HelpStats: "q/Esc: Zpět",
+ SettingsTitle: "NASTAVENÍ ČASOVAČŮ",
+ StatsTitle: "STATISTIKY",
+ SessionComplete: "🎉 Sezení dokončeno! Čas na přestávku.",
+ BreakComplete: "⏰ Přestávka skončila! Čas na práci.",
+ PomodorosToday: "Dnes dokončeno:",
+ PomodorosTotal: "Celkem dokončeno:",
+ NextLongBreakIn: "Dlouhá přestávka za:",
+ MinutesShort: "min",
+ },
+ "en": {
+ Title: "DOXA-TIMER 🦅",
+ MenuStart: "Space: Start",
+ MenuPause: "Space: Pause",
+ MenuReset: "r: Reset",
+ MenuSettings: "s: Settings",
+ MenuStats: "t: Stats",
+ MenuLang: "l: CS",
+ MenuQuit: "q: Quit",
+ StateIdle: "READY",
+ StateRunning: "FOCUS",
+ StatePaused: "PAUSED",
+ StateBreak: "SHORT BREAK",
+ StateLongBreak: "LONG BREAK",
+ StateDone: "DONE!",
+ LabelFocus: "Focus:",
+ LabelShortBreak: "Short break:",
+ LabelLongBreak: "Long break:",
+ LabelLongAfter: "Long break after:",
+ LabelTodayCount: "Today:",
+ LabelTotalCount: "Total:",
+ LabelNextBreak: "Next long break in:",
+ HelpIdle: "Space: Start | r: Reset | s: Settings | t: Stats | l: CS | q: Quit",
+ HelpRunning: "Space: Pause | r: Reset | q: Quit",
+ HelpSettings: "+/-: Change focus | b/n: Change breaks | Enter: Save | q: Back",
+ HelpStats: "q/Esc: Back",
+ SettingsTitle: "TIMER SETTINGS",
+ StatsTitle: "STATISTICS",
+ SessionComplete: "🎉 Session complete! Time for a break.",
+ BreakComplete: "⏰ Break over! Time to focus.",
+ PomodorosToday: "Completed today:",
+ PomodorosTotal: "Completed total:",
+ NextLongBreakIn: "Long break in:",
+ MinutesShort: "min",
+ },
+}
diff --git a/doxa-timer/tui.go b/doxa-timer/tui.go
new file mode 100644
index 0000000..581746a
--- /dev/null
+++ b/doxa-timer/tui.go
@@ -0,0 +1,526 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+)
+
+// ─── Styles ──────────────────────────────────────────────────────────────────
+
+var (
+ appStyle = lipgloss.NewStyle().
+ Padding(1, 2).
+ Border(lipgloss.RoundedBorder()).
+ BorderForeground(lipgloss.Color("62"))
+
+ titleStyle = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("#FAFAFA")).
+ Background(lipgloss.Color("#7D56F4")).
+ Padding(0, 1).
+ MarginBottom(1)
+
+ statusStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("205"))
+
+ timerFocusStyle = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("#7D56F4"))
+
+ timerPauseStyle = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("205"))
+
+ timerBreakStyle = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("#2ECC71"))
+
+ timerDoneStyle = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("#F39C12"))
+
+ labelStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("241"))
+
+ valueStyle = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("#FAFAFA"))
+
+ settingActiveStyle = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("#7D56F4"))
+
+ helpStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("241")).
+ MarginTop(1)
+)
+
+// ─── State ────────────────────────────────────────────────────────────────────
+
+type AppState int
+
+const (
+ StateStartup AppState = iota
+ StateIdle
+ StateRunning
+ StatePaused
+ StateBreak
+ StateLongBreak
+ StateSettings
+ StateStats
+)
+
+// ─── Messages ─────────────────────────────────────────────────────────────────
+
+type tickMsg time.Time
+
+func tickCmd() tea.Cmd {
+ return tea.Tick(time.Second, func(t time.Time) tea.Msg {
+ return tickMsg(t)
+ })
+}
+
+// ─── Model ────────────────────────────────────────────────────────────────────
+
+type model struct {
+ state AppState
+ stats TimerStats
+ lang string
+ width int
+ height int
+ statusMsg string
+ // Timer countdown
+ remaining int // seconds remaining
+ totalSeconds int // total seconds for current session
+ // Settings edit fields (temporary values while editing)
+ editFocus int
+ editShort int
+ editLong int
+ editAfter int
+ settingCursor int // 0=focus,1=short,2=long,3=after
+}
+
+func NewModel() model {
+ stats := loadStats()
+ return model{
+ state: StateStartup,
+ stats: stats,
+ lang: "cs",
+ remaining: stats.FocusDuration * 60,
+ totalSeconds: stats.FocusDuration * 60,
+ editFocus: stats.FocusDuration,
+ editShort: stats.ShortBreak,
+ editLong: stats.LongBreak,
+ editAfter: stats.LongBreakAfter,
+ }
+}
+
+func (m model) Init() tea.Cmd {
+ return tea.EnterAltScreen
+}
+
+// ─── Update ───────────────────────────────────────────────────────────────────
+
+func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ tr := Translations[m.lang]
+
+ switch msg := msg.(type) {
+ case tea.WindowSizeMsg:
+ m.width = msg.Width
+ m.height = msg.Height
+
+ case tickMsg:
+ if m.state == StateRunning || m.state == StateBreak || m.state == StateLongBreak {
+ m.remaining--
+ if m.remaining <= 0 {
+ return m.handleTimerComplete()
+ }
+ return m, tickCmd()
+ }
+
+ case tea.KeyMsg:
+ switch msg.String() {
+ case "ctrl+c":
+ return m, tea.Quit
+
+ case "q", "esc":
+ switch m.state {
+ case StateStartup:
+ return m, tea.Quit
+ case StateIdle:
+ return m, tea.Quit
+ case StateRunning, StatePaused:
+ return m, tea.Quit
+ case StateSettings, StateStats:
+ m.state = StateIdle
+ m.statusMsg = ""
+ return m, nil
+ default:
+ return m, tea.Quit
+ }
+
+ case "l":
+ if m.state != StateSettings {
+ if m.lang == "cs" {
+ m.lang = "en"
+ } else {
+ m.lang = "cs"
+ }
+ }
+ return m, nil
+
+ case "enter":
+ switch m.state {
+ case StateStartup:
+ m.state = StateIdle
+ m.remaining = m.stats.FocusDuration * 60
+ m.totalSeconds = m.stats.FocusDuration * 60
+ return m, nil
+ case StateSettings:
+ // Save settings
+ m.stats.FocusDuration = m.editFocus
+ m.stats.ShortBreak = m.editShort
+ m.stats.LongBreak = m.editLong
+ m.stats.LongBreakAfter = m.editAfter
+ saveStats(m.stats)
+ m.state = StateIdle
+ m.remaining = m.stats.FocusDuration * 60
+ m.totalSeconds = m.stats.FocusDuration * 60
+ m.statusMsg = ""
+ return m, nil
+ }
+
+ case " ":
+ switch m.state {
+ case StateIdle:
+ m.state = StateRunning
+ m.statusMsg = ""
+ return m, tickCmd()
+ case StateRunning:
+ m.state = StatePaused
+ return m, nil
+ case StatePaused:
+ m.state = StateRunning
+ return m, tickCmd()
+ case StateBreak, StateLongBreak:
+ m.state = StateRunning
+ return m, tickCmd()
+ }
+
+ case "r":
+ if m.state == StateRunning || m.state == StatePaused || m.state == StateIdle {
+ m.state = StateIdle
+ m.remaining = m.stats.FocusDuration * 60
+ m.totalSeconds = m.stats.FocusDuration * 60
+ m.statusMsg = ""
+ return m, nil
+ }
+ if m.state == StateBreak || m.state == StateLongBreak {
+ m.state = StateIdle
+ m.remaining = m.stats.FocusDuration * 60
+ m.totalSeconds = m.stats.FocusDuration * 60
+ m.statusMsg = ""
+ return m, nil
+ }
+
+ case "s":
+ if m.state == StateIdle || m.state == StatePaused {
+ m.state = StateSettings
+ m.editFocus = m.stats.FocusDuration
+ m.editShort = m.stats.ShortBreak
+ m.editLong = m.stats.LongBreak
+ m.editAfter = m.stats.LongBreakAfter
+ m.settingCursor = 0
+ return m, nil
+ }
+
+ case "t":
+ if m.state == StateIdle || m.state == StatePaused {
+ m.state = StateStats
+ return m, nil
+ }
+
+ // Settings navigation
+ case "up", "k":
+ if m.state == StateSettings {
+ m.settingCursor--
+ if m.settingCursor < 0 {
+ m.settingCursor = 3
+ }
+ }
+ case "down", "j":
+ if m.state == StateSettings {
+ m.settingCursor++
+ if m.settingCursor > 3 {
+ m.settingCursor = 0
+ }
+ }
+ case "+", "=":
+ if m.state == StateSettings {
+ switch m.settingCursor {
+ case 0:
+ if m.editFocus < 60 { m.editFocus++ }
+ case 1:
+ if m.editShort < 30 { m.editShort++ }
+ case 2:
+ if m.editLong < 60 { m.editLong++ }
+ case 3:
+ if m.editAfter < 10 { m.editAfter++ }
+ }
+ }
+ case "-":
+ if m.state == StateSettings {
+ switch m.settingCursor {
+ case 0:
+ if m.editFocus > 1 { m.editFocus-- }
+ case 1:
+ if m.editShort > 1 { m.editShort-- }
+ case 2:
+ if m.editLong > 1 { m.editLong-- }
+ case 3:
+ if m.editAfter > 1 { m.editAfter-- }
+ }
+ }
+ }
+ _ = tr
+ }
+
+ return m, nil
+}
+
+func (m model) handleTimerComplete() (tea.Model, tea.Cmd) {
+ tr := Translations[m.lang]
+
+ if m.state == StateRunning {
+ // Pomodoro complete
+ m.stats.TotalPomodoros++
+ m.stats.TodayPomodoros++
+ m.stats.LastDate = time.Now().Format("2006-01-02")
+ saveStats(m.stats)
+ m.statusMsg = tr.SessionComplete
+
+ // Decide: long break or short break
+ if m.stats.TodayPomodoros%m.stats.LongBreakAfter == 0 {
+ m.state = StateLongBreak
+ m.remaining = m.stats.LongBreak * 60
+ m.totalSeconds = m.stats.LongBreak * 60
+ } else {
+ m.state = StateBreak
+ m.remaining = m.stats.ShortBreak * 60
+ m.totalSeconds = m.stats.ShortBreak * 60
+ }
+ return m, tickCmd()
+ }
+
+ // Break complete → go idle
+ m.statusMsg = tr.BreakComplete
+ m.state = StateIdle
+ m.remaining = m.stats.FocusDuration * 60
+ m.totalSeconds = m.stats.FocusDuration * 60
+ return m, nil
+}
+
+// ─── View ─────────────────────────────────────────────────────────────────────
+
+func (m model) View() string {
+ tr := Translations[m.lang]
+ var b strings.Builder
+
+ b.WriteString(titleStyle.Render(tr.Title) + "\n")
+
+ if m.statusMsg != "" {
+ b.WriteString(statusStyle.Render(m.statusMsg) + "\n\n")
+ }
+
+ switch m.state {
+ case StateStartup:
+ eagle := `
+ _
+ ( ` + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("`") + `
+ \ ` + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("\\") + `
+ / / _
+ __ / / ( \
+ ( "---" / / /
+ \ / / /
+ \ /-----' /
+ \ / /
+ \ / /
+ '-------'
+`
+ b.WriteString(lipgloss.Place(m.width-12, 14, lipgloss.Center, lipgloss.Center,
+ eagle+"\n"+
+ lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#7D56F4")).Render("DOXA-TIMER 🦅")+"\n\n"+
+ "Local-First Pomodoro Timer\n\n"+
+ "Enter: Start | q: Quit\n\n"+
+ "License: MIT | © 2026 Kankys"))
+
+ case StateIdle, StateRunning, StatePaused, StateBreak, StateLongBreak:
+ // State label
+ stateLabel := m.getStateLabel()
+ b.WriteString(lipgloss.NewStyle().Bold(true).Render(stateLabel) + "\n\n")
+
+ // Big timer
+ timerStr := formatDuration(m.remaining)
+ timerRendered := m.renderTimer(timerStr)
+ b.WriteString(lipgloss.PlaceHorizontal(m.width-10, lipgloss.Center, timerRendered) + "\n\n")
+
+ // Progress bar
+ progressBar := renderProgressBar(m.remaining, m.totalSeconds, 40)
+ b.WriteString(lipgloss.PlaceHorizontal(m.width-10, lipgloss.Center, progressBar) + "\n\n")
+
+ // Stats row
+ pomLine := fmt.Sprintf(" %s %s %s %s",
+ labelStyle.Render(tr.LabelTodayCount),
+ valueStyle.Render(fmt.Sprintf("%d", m.stats.TodayPomodoros)),
+ labelStyle.Render(tr.LabelTotalCount),
+ valueStyle.Render(fmt.Sprintf("%d", m.stats.TotalPomodoros)),
+ )
+ b.WriteString(pomLine + "\n\n")
+
+ // Next long break
+ remaining := m.stats.LongBreakAfter - (m.stats.TodayPomodoros % m.stats.LongBreakAfter)
+ if remaining == m.stats.LongBreakAfter {
+ remaining = 0
+ }
+ if remaining > 0 {
+ b.WriteString(labelStyle.Render(fmt.Sprintf(" %s %d pomodoro(s)", tr.LabelNextBreak, remaining)) + "\n\n")
+ }
+
+ // Help
+ helpText := tr.HelpIdle
+ if m.state == StateRunning {
+ helpText = tr.HelpRunning
+ } else if m.state == StatePaused {
+ helpText = tr.HelpIdle
+ }
+ b.WriteString(helpStyle.Render(helpText))
+
+ case StateSettings:
+ b.WriteString(lipgloss.NewStyle().Bold(true).Render(tr.SettingsTitle) + "\n\n")
+
+ settings := []struct {
+ label string
+ value int
+ unit string
+ }{
+ {tr.LabelFocus, m.editFocus, tr.MinutesShort},
+ {tr.LabelShortBreak, m.editShort, tr.MinutesShort},
+ {tr.LabelLongBreak, m.editLong, tr.MinutesShort},
+ {tr.LabelLongAfter, m.editAfter, "pomodorů"},
+ }
+ if m.lang == "en" {
+ settings[3].unit = "pomodoros"
+ }
+
+ for i, s := range settings {
+ cursor := " "
+ lStyle := labelStyle
+ vStyle := valueStyle
+ if i == m.settingCursor {
+ cursor = "> "
+ lStyle = settingActiveStyle
+ vStyle = settingActiveStyle
+ }
+ b.WriteString(fmt.Sprintf("%s%-22s %s %s\n",
+ cursor,
+ lStyle.Render(s.label),
+ vStyle.Render(fmt.Sprintf("%d", s.value)),
+ labelStyle.Render(s.unit),
+ ))
+ }
+ b.WriteString("\n" + helpStyle.Render(tr.HelpSettings))
+
+ case StateStats:
+ b.WriteString(lipgloss.NewStyle().Bold(true).Render(tr.StatsTitle) + "\n\n")
+ b.WriteString(fmt.Sprintf(" %-28s %s\n",
+ labelStyle.Render(tr.PomodorosToday),
+ valueStyle.Render(fmt.Sprintf("%d 🍅", m.stats.TodayPomodoros)),
+ ))
+ b.WriteString(fmt.Sprintf(" %-28s %s\n",
+ labelStyle.Render(tr.PomodorosTotal),
+ valueStyle.Render(fmt.Sprintf("%d 🍅", m.stats.TotalPomodoros)),
+ ))
+ b.WriteString(fmt.Sprintf("\n %-28s %d %s\n",
+ labelStyle.Render(tr.LabelFocus),
+ m.stats.FocusDuration,
+ tr.MinutesShort,
+ ))
+ b.WriteString(fmt.Sprintf(" %-28s %d %s\n",
+ labelStyle.Render(tr.LabelShortBreak),
+ m.stats.ShortBreak,
+ tr.MinutesShort,
+ ))
+ b.WriteString(fmt.Sprintf(" %-28s %d %s\n",
+ labelStyle.Render(tr.LabelLongBreak),
+ m.stats.LongBreak,
+ tr.MinutesShort,
+ ))
+ b.WriteString("\n" + helpStyle.Render(tr.HelpStats))
+ }
+
+ content := b.String()
+ if m.width > 0 && m.height > 0 {
+ return appStyle.Width(m.width - 6).Height(m.height - 4).Render(content)
+ }
+ return appStyle.Render(content)
+}
+
+func (m model) getStateLabel() string {
+ tr := Translations[m.lang]
+ switch m.state {
+ case StateIdle:
+ return tr.StateIdle
+ case StateRunning:
+ return tr.StateRunning
+ case StatePaused:
+ return tr.StatePaused
+ case StateBreak:
+ return tr.StateBreak
+ case StateLongBreak:
+ return tr.StateLongBreak
+ }
+ return ""
+}
+
+func (m model) renderTimer(s string) string {
+ large := lipgloss.NewStyle().Bold(true).PaddingLeft(1).PaddingRight(1)
+ switch m.state {
+ case StateRunning:
+ return timerFocusStyle.Copy().Inherit(large).Render(s)
+ case StatePaused:
+ return timerPauseStyle.Copy().Inherit(large).Render(s)
+ case StateBreak, StateLongBreak:
+ return timerBreakStyle.Copy().Inherit(large).Render(s)
+ default:
+ return timerPauseStyle.Copy().Inherit(large).Render(s)
+ }
+}
+
+// renderProgressBar renders an ASCII progress bar
+func renderProgressBar(remaining, total, width int) string {
+ if total == 0 {
+ return ""
+ }
+ elapsed := total - remaining
+ filled := int(float64(elapsed) / float64(total) * float64(width))
+ if filled > width {
+ filled = width
+ }
+ bar := "[" + strings.Repeat("█", filled) + strings.Repeat("░", width-filled) + "]"
+ pct := int(float64(elapsed) / float64(total) * 100)
+ return fmt.Sprintf("%s %3d%%", bar, pct)
+}
+
+// formatDuration formats seconds as MM:SS
+func formatDuration(secs int) string {
+ if secs < 0 {
+ secs = 0
+ }
+ m := secs / 60
+ s := secs % 60
+ return fmt.Sprintf("%02d:%02d", m, s)
+}