diff --git a/src/lib/components/StreakCounter.svelte b/src/lib/components/StreakCounter.svelte
new file mode 100644
index 0000000..e0254bf
--- /dev/null
+++ b/src/lib/components/StreakCounter.svelte
@@ -0,0 +1,98 @@
+
+
+
+
+ {streak?.length ?? 0}
+ Tage
+
+
+
+
+
diff --git a/src/lib/stores/rosaryStreak.svelte.ts b/src/lib/stores/rosaryStreak.svelte.ts
new file mode 100644
index 0000000..8007911
--- /dev/null
+++ b/src/lib/stores/rosaryStreak.svelte.ts
@@ -0,0 +1,101 @@
+import { browser } from '$app/environment';
+
+const STORAGE_KEY = 'rosary_streak';
+
+interface StreakData {
+ length: number;
+ lastPrayed: string | null; // ISO date string (YYYY-MM-DD)
+}
+
+function getToday(): string {
+ return new Date().toISOString().split('T')[0];
+}
+
+function isYesterday(dateStr: string): boolean {
+ const yesterday = new Date();
+ yesterday.setDate(yesterday.getDate() - 1);
+ return dateStr === yesterday.toISOString().split('T')[0];
+}
+
+function loadFromStorage(): StreakData {
+ if (!browser) return { length: 0, lastPrayed: null };
+
+ try {
+ const stored = localStorage.getItem(STORAGE_KEY);
+ if (stored) {
+ return JSON.parse(stored);
+ }
+ } catch {
+ // Invalid data, return default
+ }
+ return { length: 0, lastPrayed: null };
+}
+
+function saveToStorage(data: StreakData): void {
+ if (!browser) return;
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
+}
+
+class RosaryStreakStore {
+ #length = $state(0);
+ #lastPrayed = $state(null);
+ #initialized = false;
+
+ constructor() {
+ if (browser) {
+ this.#init();
+ }
+ }
+
+ #init() {
+ if (this.#initialized) return;
+ const data = loadFromStorage();
+ this.#length = data.length;
+ this.#lastPrayed = data.lastPrayed;
+ this.#initialized = true;
+ }
+
+ get length() {
+ return this.#length;
+ }
+
+ get lastPrayed() {
+ return this.#lastPrayed;
+ }
+
+ get prayedToday(): boolean {
+ return this.#lastPrayed === getToday();
+ }
+
+ recordPrayer(): void {
+ const today = getToday();
+
+ // Already prayed today - no change
+ if (this.#lastPrayed === today) {
+ return;
+ }
+
+ // Determine new streak length
+ let newLength: number;
+ if (this.#lastPrayed && isYesterday(this.#lastPrayed)) {
+ // Continuing streak from yesterday
+ newLength = this.#length + 1;
+ } else {
+ // Starting new streak (either first time or gap > 1 day)
+ newLength = 1;
+ }
+
+ this.#length = newLength;
+ this.#lastPrayed = today;
+ saveToStorage({ length: newLength, lastPrayed: today });
+ }
+}
+
+let instance: RosaryStreakStore | null = null;
+
+export function getRosaryStreak(): RosaryStreakStore {
+ if (!instance) {
+ instance = new RosaryStreakStore();
+ }
+ return instance;
+}
diff --git a/src/routes/glaube/rosenkranz/+page.svelte b/src/routes/glaube/rosenkranz/+page.svelte
index a05af63..f2fa33d 100644
--- a/src/routes/glaube/rosenkranz/+page.svelte
+++ b/src/routes/glaube/rosenkranz/+page.svelte
@@ -16,6 +16,7 @@ import CounterButton from "$lib/components/CounterButton.svelte";
import BibleModal from "$lib/components/BibleModal.svelte";
import Toggle from "$lib/components/Toggle.svelte";
import LanguageToggle from "$lib/components/LanguageToggle.svelte";
+import StreakCounter from "$lib/components/StreakCounter.svelte";
let { data } = $props();
@@ -888,14 +889,29 @@ h1 {
margin-bottom: 2rem;
}
+/* Controls row: toggles + streak counter */
+.controls-row {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1.5rem;
+ margin: 0 auto 2rem auto;
+}
+
+@media (min-width: 700px) {
+ .controls-row {
+ flex-direction: row;
+ justify-content: center;
+ gap: 3rem;
+ }
+}
+
/* Toggle controls container */
.toggle-controls {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
- max-width: fit-content;
- margin: 0 auto 2rem auto;
}
/* Mystery selector grid */
@@ -1208,17 +1224,21 @@ l536 389l-209 -629zM1671 934l-370 267l150 436l-378 -271l-371 271q8 -34 15 -68q10
{/if}
-
-