diff --git a/public/locales/de.json b/public/locales/de.json
index 3065bef..7df2190 100644
--- a/public/locales/de.json
+++ b/public/locales/de.json
@@ -21,7 +21,7 @@
"errorOccurred": "Etwas ist schiefgelaufen.",
"unexpectedError": "Ein unerwarteter Fehler ist aufgetreten.",
"errorGeneric": "Ein Fehler ist aufgetreten.",
- "updateAvailable": "Update verfügbar — Seite neu laden für die neueste Version.",
+ "updateAvailable": "Update verfügbar - Seite neu laden für die neueste Version.",
"titleRequired": "Titel ist erforderlich",
"nameRequired": "Name ist erforderlich",
"contentRequired": "Inhalt ist erforderlich",
@@ -82,7 +82,7 @@
"title": "Aufgaben",
"newTask": "Neue Aufgabe",
"editTask": "Aufgabe bearbeiten",
- "emptyTitle": "Keine Aufgaben — alles erledigt?",
+ "emptyTitle": "Keine Aufgaben - alles erledigt?",
"emptyDescription": "Neue Aufgaben über den + Button erstellen.",
"titleLabel": "Titel *",
"titlePlaceholder": "Was muss erledigt werden?",
@@ -93,7 +93,7 @@
"dueDateLabel": "Fälligkeit",
"dueTimeLabel": "Uhrzeit",
"assignedLabel": "Zugewiesen an",
- "assignedNobody": "— Niemand —",
+ "assignedNobody": "- Niemand -",
"statusLabel": "Status",
"priorityUrgent": "Dringend",
"priorityHigh": "Hoch",
@@ -259,7 +259,7 @@
"locationLabel": "Ort",
"locationPlaceholder": "Optional",
"assignedLabel": "Zugewiesen an",
- "assignedNobody": "— Niemand —",
+ "assignedNobody": "- Niemand -",
"colorLabel": "Farbe",
"descriptionLabel": "Beschreibung",
"descriptionPlaceholder": "Optional…",
@@ -428,7 +428,7 @@
"savedToast": "Eintrag gespeichert",
"deletedToast": "Eintrag gelöscht",
"loadError": "Budget konnte nicht geladen werden.",
- "trendNeutral": "— wie {{month}}",
+ "trendNeutral": "- wie {{month}}",
"validAmountRequired": "Gültigen Betrag eingeben",
"dateRequired": "Datum ist erforderlich",
"catFood": "Lebensmittel",
diff --git a/public/locales/en.json b/public/locales/en.json
index 9ab35e8..f448e81 100644
--- a/public/locales/en.json
+++ b/public/locales/en.json
@@ -21,7 +21,7 @@
"errorOccurred": "Something went wrong.",
"unexpectedError": "An unexpected error occurred.",
"errorGeneric": "An error occurred.",
- "updateAvailable": "Update available — reload the page to get the latest version.",
+ "updateAvailable": "Update available - reload the page to get the latest version.",
"titleRequired": "Title is required",
"nameRequired": "Name is required",
"contentRequired": "Content is required",
@@ -82,7 +82,7 @@
"title": "Tasks",
"newTask": "New Task",
"editTask": "Edit Task",
- "emptyTitle": "No tasks — all done?",
+ "emptyTitle": "No tasks - all done?",
"emptyDescription": "Create new tasks with the + button.",
"titleLabel": "Title *",
"titlePlaceholder": "What needs to be done?",
@@ -93,7 +93,7 @@
"dueDateLabel": "Due date",
"dueTimeLabel": "Time",
"assignedLabel": "Assigned to",
- "assignedNobody": "— Nobody —",
+ "assignedNobody": "- Nobody -",
"statusLabel": "Status",
"priorityUrgent": "Urgent",
"priorityHigh": "High",
@@ -259,7 +259,7 @@
"locationLabel": "Location",
"locationPlaceholder": "Optional",
"assignedLabel": "Assigned to",
- "assignedNobody": "— Nobody —",
+ "assignedNobody": "- Nobody -",
"colorLabel": "Color",
"descriptionLabel": "Description",
"descriptionPlaceholder": "Optional…",
@@ -428,7 +428,7 @@
"savedToast": "Entry saved",
"deletedToast": "Entry deleted",
"loadError": "Budget could not be loaded.",
- "trendNeutral": "— same as {{month}}",
+ "trendNeutral": "- same as {{month}}",
"validAmountRequired": "Please enter a valid amount",
"dateRequired": "Date is required",
"catFood": "Groceries",
diff --git a/public/locales/it.json b/public/locales/it.json
index 3078ab0..9a1f693 100644
--- a/public/locales/it.json
+++ b/public/locales/it.json
@@ -21,7 +21,7 @@
"errorOccurred": "Si è verificato un errore.",
"unexpectedError": "Si è verificato un errore imprevisto.",
"errorGeneric": "Si è verificato un errore.",
- "updateAvailable": "Aggiornamento disponibile — ricarica la pagina per ottenere l'ultima versione.",
+ "updateAvailable": "Aggiornamento disponibile - ricarica la pagina per ottenere l'ultima versione.",
"titleRequired": "Il titolo è obbligatorio",
"nameRequired": "Il nome è obbligatorio",
"contentRequired": "Il contenuto è obbligatorio",
@@ -82,7 +82,7 @@
"title": "Compiti",
"newTask": "Nuovo compito",
"editTask": "Modifica compito",
- "emptyTitle": "Nessun compito — tutto fatto?",
+ "emptyTitle": "Nessun compito - tutto fatto?",
"emptyDescription": "Crea nuovi compiti con il pulsante +.",
"titleLabel": "Titolo *",
"titlePlaceholder": "Cosa bisogna fare?",
@@ -93,7 +93,7 @@
"dueDateLabel": "Data di scadenza",
"dueTimeLabel": "Ora",
"assignedLabel": "Assegnato a",
- "assignedNobody": "— Nessuno —",
+ "assignedNobody": "- Nessuno -",
"statusLabel": "Stato",
"priorityUrgent": "Urgente",
"priorityHigh": "Alta",
@@ -259,7 +259,7 @@
"locationLabel": "Luogo",
"locationPlaceholder": "Opzionale",
"assignedLabel": "Assegnato a",
- "assignedNobody": "— Nessuno —",
+ "assignedNobody": "- Nessuno -",
"colorLabel": "Colore",
"descriptionLabel": "Descrizione",
"descriptionPlaceholder": "Opzionale…",
@@ -428,7 +428,7 @@
"savedToast": "Voce salvata",
"deletedToast": "Voce eliminata",
"loadError": "Impossibile caricare il bilancio.",
- "trendNeutral": "— come {{month}}",
+ "trendNeutral": "- come {{month}}",
"validAmountRequired": "Inserisci un importo valido",
"dateRequired": "La data è obbligatoria",
"catFood": "Spesa alimentare",
diff --git a/public/pages/dashboard.js b/public/pages/dashboard.js
index 2d58090..2966e61 100644
--- a/public/pages/dashboard.js
+++ b/public/pages/dashboard.js
@@ -7,7 +7,7 @@
import { api } from '/api.js';
import { t, formatDate, formatTime, getLocale } from '/i18n.js';
-// Hält den AbortController des aktuellen FAB-Listeners — wird bei jedem render() erneuert.
+// Hält den AbortController des aktuellen FAB-Listeners - wird bei jedem render() erneuert.
let _fabController = null;
// --------------------------------------------------------
@@ -225,7 +225,7 @@ function renderTodayMeals(meals) {
${mealLabels[type]}
-
${meal ? meal.title : '—'}
+
${meal ? meal.title : '-'}
`;
}).join('');
@@ -487,7 +487,7 @@ export async function render(container, { user }) {
refreshBtn.addEventListener('click', doWeatherRefresh, { signal: _fabController.signal });
- // 30-Minuten Auto-Refresh — abortiert wenn Seite verlassen wird
+ // 30-Minuten Auto-Refresh - abortiert wenn Seite verlassen wird
const timerId = setInterval(doWeatherRefresh, 30 * 60 * 1000);
_fabController.signal.addEventListener('abort', () => clearInterval(timerId));
}
diff --git a/public/pages/meals.js b/public/pages/meals.js
index 6d3ec1e..1e7e14f 100644
--- a/public/pages/meals.js
+++ b/public/pages/meals.js
@@ -350,7 +350,7 @@ function wireDragDrop(grid) {
async function onUp(ev) {
if (!dragging) return;
const { mealId, sourceDate, sourceType, slot: sourceSlot } = dragging;
- cleanup(); // setzt dragging = null — Werte daher vorher destrukturieren
+ cleanup(); // setzt dragging = null - Werte daher vorher destrukturieren
if (ghost) ghost.style.display = 'none';
const el = document.elementFromPoint(ev.clientX, ev.clientY);
diff --git a/public/pages/shopping.js b/public/pages/shopping.js
index b6991dc..4d62f6e 100644
--- a/public/pages/shopping.js
+++ b/public/pages/shopping.js
@@ -13,9 +13,9 @@ import { t } from '/i18n.js';
// --------------------------------------------------------
// Swipe-Gesten Konstanten (identisch zu tasks.js)
-const SWIPE_THRESHOLD = 80; // px — Mindestweg für Aktion
-const SWIPE_MAX_VERT = 12; // px — vertikaler Toleranzbereich
-const SWIPE_LOCK_VERT = 30; // px — ab diesem Weg gilt es als Scroll
+const SWIPE_THRESHOLD = 80; // px - Mindestweg für Aktion
+const SWIPE_MAX_VERT = 12; // px - vertikaler Toleranzbereich
+const SWIPE_LOCK_VERT = 30; // px - ab diesem Weg gilt es als Scroll
const ITEM_CATEGORIES = [
'Obst & Gemüse', 'Backwaren', 'Milchprodukte', 'Fleisch & Fisch',
diff --git a/public/pages/tasks.js b/public/pages/tasks.js
index 4621be7..b305a6e 100644
--- a/public/pages/tasks.js
+++ b/public/pages/tasks.js
@@ -717,9 +717,9 @@ function updateOverdueBadge() {
// Swipe-Gesten (Mobil: links = erledigt, rechts = bearbeiten)
// --------------------------------------------------------
-const SWIPE_THRESHOLD = 80; // px — Mindestweg für Aktion
-const SWIPE_MAX_VERT = 12; // px — vertikaler Bewegungs-Toleranzbereich (darunter: kein Scroll-Abbruch)
-const SWIPE_LOCK_VERT = 30; // px — ab diesem Weg gilt es als Scroll (Swipe abgebrochen)
+const SWIPE_THRESHOLD = 80; // px - Mindestweg für Aktion
+const SWIPE_MAX_VERT = 12; // px - vertikaler Bewegungs-Toleranzbereich (darunter: kein Scroll-Abbruch)
+const SWIPE_LOCK_VERT = 30; // px - ab diesem Weg gilt es als Scroll (Swipe abgebrochen)
function wireSwipeGestures(container) {
const listEl = container.querySelector('#task-list');
diff --git a/public/robots.txt b/public/robots.txt
index c68da96..490797d 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,4 +1,4 @@
-# Oikos Familienplaner — self-hosted, private
+# Oikos Familienplaner - self-hosted, private
# Keine öffentlichen Inhalte zum Indexieren
User-agent: *
diff --git a/public/router.js b/public/router.js
index 5f21124..92fbd1c 100644
--- a/public/router.js
+++ b/public/router.js
@@ -34,7 +34,7 @@ const isStandalone = window.matchMedia('(display-mode: standalone)').matches
/**
* Setzt die theme-color Meta-Tags (Light + Dark Variante).
* @param {string} lightColor
- * @param {string} [darkColor] — Falls nicht angegeben, wird lightColor für beide gesetzt
+ * @param {string} [darkColor] - Falls nicht angegeben, wird lightColor für beide gesetzt
*/
function setThemeColor(lightColor, darkColor) {
if (!isStandalone) return;
@@ -116,7 +116,7 @@ async function navigate(path, userOrPushState = true, pushState = true) {
pushState = userOrPushState;
}
- // Alten Pfad merken, bevor currentPath aktualisiert wird — für Richtungsberechnung
+ // Alten Pfad merken, bevor currentPath aktualisiert wird - für Richtungsberechnung
const previousPath = currentPath;
currentPath = path;
@@ -176,7 +176,7 @@ async function renderPage(route, previousPath = null) {
throw new Error(`Seite ${route.page} exportiert keine render()-Funktion.`);
}
- // App-Shell einmalig aufbauen BEVOR render() aufgerufen wird —
+ // App-Shell einmalig aufbauen BEVOR render() aufgerufen wird -
// page-content muss im DOM existieren damit document.getElementById()
// in Seiten-Modulen funktioniert.
if (!document.querySelector('.nav-bottom') && currentUser) {
@@ -446,7 +446,7 @@ window.addEventListener('locale-changed', () => {
// --------------------------------------------------------
// Virtuelle Tastatur: FAB ausblenden wenn Keyboard offen
-// Erkennung via visualViewport — Höhe < 75% des Fensters = Keyboard aktiv.
+// Erkennung via visualViewport - Höhe < 75% des Fensters = Keyboard aktiv.
// Nur auf Mobilgeräten relevant (< 1024px), Desktop hat keine virtuelle Tastatur.
// --------------------------------------------------------
if (window.visualViewport) {
diff --git a/public/styles/dashboard.css b/public/styles/dashboard.css
index c13f337..782a254 100644
--- a/public/styles/dashboard.css
+++ b/public/styles/dashboard.css
@@ -1,6 +1,6 @@
/**
* Modul: Dashboard
- * Zweck: Styles für das Dashboard — Begrüßung, Widget-Grid, alle Widget-Typen, FAB-Speed-Dial
+ * Zweck: Styles für das Dashboard - Begrüßung, Widget-Grid, alle Widget-Typen, FAB-Speed-Dial
* Abhängigkeiten: tokens.css, layout.css
*/
@@ -230,7 +230,7 @@
margin-bottom: var(--space-1);
}
-/* Widget hover lift (desktop) — dezent, max 1px */
+/* Widget hover lift (desktop) - dezent, max 1px */
@media (min-width: 1024px) {
.widget {
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
diff --git a/public/styles/layout.css b/public/styles/layout.css
index a5f047a..d85a0aa 100644
--- a/public/styles/layout.css
+++ b/public/styles/layout.css
@@ -208,7 +208,7 @@
}
/* --------------------------------------------------------
- * Page FAB — Schwebender Erstellen-Button (alle Breakpoints)
+ * Page FAB - Schwebender Erstellen-Button (alle Breakpoints)
*
* Einheitlicher runder Plus-Button auf allen Unterseiten.
* Mobile: über der Bottom-Nav. Desktop: unten rechts im Content.
@@ -252,7 +252,7 @@
}
}
-/* Toolbar-"Neu"-Buttons überall verstecken — FAB übernimmt */
+/* Toolbar-"Neu"-Buttons überall verstecken - FAB übernimmt */
#btn-new-task,
#notes-add-btn,
#contacts-add-btn,
@@ -262,7 +262,7 @@
}
/* ================================================================
- * Sidebar Navigation — Desktop (≥ 1024px)
+ * Sidebar Navigation - Desktop (≥ 1024px)
*
* Design: Flach, kein Neumorphismus. Dezenter Seitenrand.
* Aktiver State: Hintergrund-Highlight + Akzentstreifen links.
@@ -413,7 +413,7 @@
}
/* ================================================================
- * Sidebar Expanded (≥ 1280px) — Labels sichtbar
+ * Sidebar Expanded (≥ 1280px) - Labels sichtbar
* ================================================================ */
@media (min-width: 1280px) {
:root {
@@ -515,11 +515,11 @@
* - Hover auf Desktop: leichter Lift (1px)
*
* Varianten:
- * .card — Basis (kein Padding)
- * .card--padded — Mit Padding
- * .card--compact — Enges Padding (12px)
- * .card--flat — Kein Shadow, nur Border
- * .card--interactive — Hover-Lift + Cursor
+ * .card - Basis (kein Padding)
+ * .card--padded - Mit Padding
+ * .card--compact - Enges Padding (12px)
+ * .card--flat - Kein Shadow, nur Border
+ * .card--interactive - Hover-Lift + Cursor
* -------------------------------------------------------- */
.card {
background-color: var(--color-surface);
@@ -1048,15 +1048,15 @@
* Wiederverwendbare Content-Area-Patterns für Desktop.
* Mobile: immer single-column (Stacking).
*
- * .layout-master-detail — Liste links, Detail rechts (Aufgaben, Einkauf)
- * .layout-content-aside — Hauptinhalt + schmale Seitenleiste (Kalender)
- * .layout-center — Zentrierter schmaler Content (Settings, Login)
- * .layout-wide — Volle Breite mit max-width (Dashboard)
+ * .layout-master-detail - Liste links, Detail rechts (Aufgaben, Einkauf)
+ * .layout-content-aside - Hauptinhalt + schmale Seitenleiste (Kalender)
+ * .layout-center - Zentrierter schmaler Content (Settings, Login)
+ * .layout-wide - Volle Breite mit max-width (Dashboard)
* -------------------------------------------------------- */
/* ── Master-Detail ──
* Mobile: gestapelt (Detail wird programmatisch ein-/ausgeblendet).
- * Desktop: 2 Spalten — Liste ~40%, Detail ~60%.
+ * Desktop: 2 Spalten - Liste ~40%, Detail ~60%.
*/
.layout-master-detail {
display: flex;
@@ -1344,7 +1344,7 @@
}
/* --------------------------------------------------------
- * Swipe-Wrapper — Gemeinsame Basis (Tasks + Shopping)
+ * Swipe-Wrapper - Gemeinsame Basis (Tasks + Shopping)
* Modul-spezifische Styles (.swipe-reveal--edit, .swipe-reveal--delete,
* .swipe-row .task-card, .swipe-row .shopping-item) liegen in den Modul-CSS.
* -------------------------------------------------------- */
diff --git a/public/styles/meals.css b/public/styles/meals.css
index 5ff0f3a..93503ce 100644
--- a/public/styles/meals.css
+++ b/public/styles/meals.css
@@ -151,7 +151,7 @@
text-align: left;
}
-/* Slot-Typ-Farben — zentrale Tokens aus tokens.css */
+/* Slot-Typ-Farben - zentrale Tokens aus tokens.css */
.meal-slot[data-type="breakfast"] .meal-slot__type-label { color: var(--meal-breakfast); }
.meal-slot[data-type="lunch"] .meal-slot__type-label { color: var(--meal-lunch); }
.meal-slot[data-type="dinner"] .meal-slot__type-label { color: var(--meal-dinner); }
diff --git a/public/styles/pwa.css b/public/styles/pwa.css
index a0cca3d..32ec09d 100644
--- a/public/styles/pwa.css
+++ b/public/styles/pwa.css
@@ -31,7 +31,7 @@ body {
}
/* Touch-Targets werden über tokens.css (--target-sm/md/lg) und
- * komponentenspezifische Styles gehandhabt — siehe Redesign Phase E.
+ * komponentenspezifische Styles gehandhabt - siehe Redesign Phase E.
* Keine globale min-size-Regel hier, da sie mit dem bestehenden
* Touch-Target-System kollidiert (::before-Expansion auf kleinen Elementen). */
diff --git a/public/styles/settings.css b/public/styles/settings.css
index 4a77a03..3340f1b 100644
--- a/public/styles/settings.css
+++ b/public/styles/settings.css
@@ -10,7 +10,7 @@
.settings-page { --module-accent: var(--module-settings); }
/* --------------------------------------------------------
- Seiten-Layout — nutzt layout-center (max 720px)
+ Seiten-Layout - nutzt layout-center (max 720px)
-------------------------------------------------------- */
.settings-page {
diff --git a/public/styles/shopping.css b/public/styles/shopping.css
index 20a4e04..dff8eec 100644
--- a/public/styles/shopping.css
+++ b/public/styles/shopping.css
@@ -419,7 +419,7 @@
.item-delete:hover { color: var(--color-danger); }
/* --------------------------------------------------------
- * Swipe-Wrapper — Shopping-spezifische Styles
+ * Swipe-Wrapper - Shopping-spezifische Styles
* -------------------------------------------------------- */
/* Kein Margin mehr am shopping-item selbst (übernimmt swipe-row) */
@@ -444,7 +444,7 @@
box-shadow: var(--shadow-lg);
}
-/* × Löschen-Button auf Mobile ausblenden — Swipe übernimmt */
+/* × Löschen-Button auf Mobile ausblenden - Swipe übernimmt */
@media (max-width: 1023px) {
.item-delete {
display: none;
diff --git a/public/styles/tasks.css b/public/styles/tasks.css
index c08fdac..0b9d01a 100644
--- a/public/styles/tasks.css
+++ b/public/styles/tasks.css
@@ -169,7 +169,7 @@
}
/* --------------------------------------------------------
- * Swipe-Wrapper — Task-spezifische Styles
+ * Swipe-Wrapper - Task-spezifische Styles
* Basis-Styles (.swipe-row, .swipe-reveal, .swipe-reveal--done)
* liegen in layout.css
* -------------------------------------------------------- */
diff --git a/public/styles/tokens.css b/public/styles/tokens.css
index dd2f5ce..d5b1950 100644
--- a/public/styles/tokens.css
+++ b/public/styles/tokens.css
@@ -23,7 +23,7 @@
:root {
/* --------------------------------------------------------
- * 1. Farben — Neutral-Skala
+ * 1. Farben - Neutral-Skala
* Leicht warmgetönt (kein reines Grau) für einladende Atmosphäre.
* Benannt als --neutral-{stufe} für direkte Nutzung,
* plus semantische Aliase (--color-bg, --color-surface etc.)
@@ -56,7 +56,7 @@
--color-text-disabled: var(--neutral-300);
/* --------------------------------------------------------
- * 2. Farben — Akzent (konfigurierbar)
+ * 2. Farben - Akzent (konfigurierbar)
* Wärmerer Blauton statt reinem Corporate-Blau.
* -------------------------------------------------------- */
--color-accent: #2563EB;
@@ -68,7 +68,7 @@
--color-btn-primary-hover: #1E429A;
/* --------------------------------------------------------
- * 3. Farben — Semantisch
+ * 3. Farben - Semantisch
* -------------------------------------------------------- */
--color-success: #15803D;
--color-success-hover: #166534;
@@ -84,22 +84,22 @@
--color-info-light: #DDF4FF;
/* --------------------------------------------------------
- * 4. Farben — Modul-Akzente
+ * 4. Farben - Modul-Akzente
* Jedes Modul hat eine eigene dezente Akzentfarbe.
* Einsatz in Modul-Headern, Icons, aktiven States.
* -------------------------------------------------------- */
- --module-dashboard: #2563EB; /* Blau — Übersicht, neutral */
- --module-tasks: #15803D; /* Grün — Erledigung, Fortschritt */
- --module-calendar: #8250DF; /* Violett — Termine, Zeit */
- --module-meals: #B45309; /* Orange — Essen, Wärme */
- --module-shopping: #D4511E; /* Rot-Orange — Einkaufen, Aktion */
- --module-notes: #BF8700; /* Gold — Notizen, Pinnwand */
- --module-contacts: #0969DA; /* Kräftiges Blau — Kontakte */
- --module-budget: #1A7F5A; /* Teal — Finanzen, Stabilität */
- --module-settings: #6E7781; /* Grau — Konfiguration */
+ --module-dashboard: #2563EB; /* Blau - Übersicht, neutral */
+ --module-tasks: #15803D; /* Grün - Erledigung, Fortschritt */
+ --module-calendar: #8250DF; /* Violett - Termine, Zeit */
+ --module-meals: #B45309; /* Orange - Essen, Wärme */
+ --module-shopping: #D4511E; /* Rot-Orange - Einkaufen, Aktion */
+ --module-notes: #BF8700; /* Gold - Notizen, Pinnwand */
+ --module-contacts: #0969DA; /* Kräftiges Blau - Kontakte */
+ --module-budget: #1A7F5A; /* Teal - Finanzen, Stabilität */
+ --module-settings: #6E7781; /* Grau - Konfiguration */
/* --------------------------------------------------------
- * 5. Farben — Mahlzeit-Typen
+ * 5. Farben - Mahlzeit-Typen
* Zentrale Tokens statt Hardcoding in meals.css
* -------------------------------------------------------- */
--meal-breakfast: #B45309;
@@ -112,7 +112,7 @@
--meal-snack-light: #FFECE3;
/* --------------------------------------------------------
- * 6. Farben — Prioritäten
+ * 6. Farben - Prioritäten
* -------------------------------------------------------- */
--color-priority-low: var(--neutral-500);
--color-priority-medium: #B45309;
@@ -159,15 +159,15 @@
--font-mono: 'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', monospace;
/* Size-Skala */
- --text-xs: 0.75rem; /* 12px — Minimum, Captions, Badges, Nav-Labels */
- --text-sm: 0.8125rem; /* 13px — Small/Secondary */
- --text-base: 0.875rem; /* 14px — Body (Desktop), kompakter */
- --text-md: 1rem; /* 16px — Body (Mobile), Inputs */
- --text-lg: 1.125rem; /* 18px — Section-Title */
- --text-xl: 1.25rem; /* 20px — Subtitle */
- --text-2xl: 1.5rem; /* 24px — Page-Title */
- --text-3xl: 1.875rem; /* 30px — Page-Title Desktop */
- --text-4xl: 2.25rem; /* 36px — Hero/Greeting */
+ --text-xs: 0.75rem; /* 12px - Minimum, Captions, Badges, Nav-Labels */
+ --text-sm: 0.8125rem; /* 13px - Small/Secondary */
+ --text-base: 0.875rem; /* 14px - Body (Desktop), kompakter */
+ --text-md: 1rem; /* 16px - Body (Mobile), Inputs */
+ --text-lg: 1.125rem; /* 18px - Section-Title */
+ --text-xl: 1.25rem; /* 20px - Subtitle */
+ --text-2xl: 1.5rem; /* 24px - Page-Title */
+ --text-3xl: 1.875rem; /* 30px - Page-Title Desktop */
+ --text-4xl: 2.25rem; /* 36px - Hero/Greeting */
/* Line-Heights */
--line-height-tight: 1.25;
@@ -182,7 +182,7 @@
--font-weight-bold: 700;
/* --------------------------------------------------------
- * 11. Abstände — 4px-Raster
+ * 11. Abstände - 4px-Raster
* -------------------------------------------------------- */
--space-0: 0px;
--space-px: 1px;
@@ -216,7 +216,7 @@
/* --------------------------------------------------------
* 13. Sidebar
- * Reduzierter Neumorphismus — subtilere Schatten.
+ * Reduzierter Neumorphismus - subtilere Schatten.
* -------------------------------------------------------- */
--sidebar-bg: var(--neutral-100);
--sidebar-shadow-light: rgba(255, 255, 255, 0.6);
@@ -280,7 +280,7 @@
--sidebar-shadow-light: rgba(255, 255, 255, 0.04);
--sidebar-shadow-dark: rgba(0, 0, 0, 0.4);
- /* Akzent — Dark Mode */
+ /* Akzent - Dark Mode */
--color-accent: #60A5FA;
--color-accent-hover: #3B82F6;
--color-accent-active: #2563EB;
@@ -289,7 +289,7 @@
--color-btn-primary: #3B82F6;
--color-btn-primary-hover: #2563EB;
- /* Semantische Farben — Dark Mode */
+ /* Semantische Farben - Dark Mode */
--color-success: #4ADE80;
--color-warning: #F59E0B;
--color-danger: #FCA5A5;
@@ -300,7 +300,7 @@
--color-danger-light: #3D1C1A;
--color-info-light: #1A2D40;
- /* Modul-Akzente — Dark Mode */
+ /* Modul-Akzente - Dark Mode */
--module-dashboard: #60A5FA;
--module-tasks: #4ADE80;
--module-calendar: #A78BFA;
@@ -311,7 +311,7 @@
--module-budget: #34D399;
--module-settings: #94A3B8;
- /* Mahlzeit-Typ — Dark Mode */
+ /* Mahlzeit-Typ - Dark Mode */
--meal-breakfast: #F59E0B;
--meal-dinner: #60A5FA;
@@ -362,7 +362,7 @@
--sidebar-shadow-light: rgba(255, 255, 255, 0.04);
--sidebar-shadow-dark: rgba(0, 0, 0, 0.4);
- /* Akzent — Dark Mode */
+ /* Akzent - Dark Mode */
--color-accent: #60A5FA;
--color-accent-hover: #3B82F6;
--color-accent-active: #2563EB;
@@ -371,7 +371,7 @@
--color-btn-primary: #3B82F6;
--color-btn-primary-hover: #2563EB;
- /* Semantische Farben — Dark Mode */
+ /* Semantische Farben - Dark Mode */
--color-success: #4ADE80;
--color-warning: #F59E0B;
--color-danger: #FCA5A5;
@@ -382,7 +382,7 @@
--color-danger-light: #3D1C1A;
--color-info-light: #1A2D40;
- /* Modul-Akzente — Dark Mode */
+ /* Modul-Akzente - Dark Mode */
--module-dashboard: #60A5FA;
--module-tasks: #4ADE80;
--module-calendar: #A78BFA;
@@ -393,7 +393,7 @@
--module-budget: #34D399;
--module-settings: #94A3B8;
- /* Mahlzeit-Typ — Dark Mode */
+ /* Mahlzeit-Typ - Dark Mode */
--meal-breakfast: #F59E0B;
--meal-dinner: #60A5FA;
diff --git a/public/sw.js b/public/sw.js
index 2cc8fd9..f00ca31 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -111,7 +111,7 @@ self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
- // API: immer Netzwerk — niemals Nutzerdaten cachen
+ // API: immer Netzwerk - niemals Nutzerdaten cachen
if (url.pathname.startsWith('/api/')) return;
// Nur GET cachen
@@ -123,7 +123,7 @@ self.addEventListener('fetch', (event) => {
return;
}
- // Bilder + Fonts: Cache-First, langer TTL — nur Same-Origin
+ // Bilder + Fonts: Cache-First, langer TTL - nur Same-Origin
// Cross-Origin-Assets (z.B. Wetter-Icons von openweathermap.org) nicht
// abfangen: opaque Responses führen im PWA-Modus zu Darstellungsfehlern.
if (isAsset(url.pathname) && url.origin === self.location.origin) {
diff --git a/public/utils/ux.js b/public/utils/ux.js
index bd0c704..5354ae4 100644
--- a/public/utils/ux.js
+++ b/public/utils/ux.js
@@ -10,9 +10,9 @@
*
* @param {NodeList|Element[]} elements
* @param {Object} [opts]
- * @param {number} [opts.delay=30] — ms zwischen jedem Element
- * @param {number} [opts.duration=180] — ms pro Element
- * @param {number} [opts.max=5] — Maximale Anzahl gestaffelter Elemente
+ * @param {number} [opts.delay=30] - ms zwischen jedem Element
+ * @param {number} [opts.duration=180] - ms pro Element
+ * @param {number} [opts.max=5] - Maximale Anzahl gestaffelter Elemente
*/
export function stagger(elements, { delay = 30, duration = 180, max = 5 } = {}) {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
@@ -33,7 +33,7 @@ export function stagger(elements, { delay = 30, duration = 180, max = 5 } = {})
* Vibrationsmuster abspielen, wenn die API verfügbar ist und
* keine reduzierte Bewegung gewünscht wird.
*
- * @param {number|number[]} pattern — ms oder [an, aus, an, ...]-Array
+ * @param {number|number[]} pattern - ms oder [an, aus, an, ...]-Array
*/
export function vibrate(pattern) {
if (!navigator.vibrate) return;
diff --git a/scripts/generate-icons.js b/scripts/generate-icons.js
index 3f1dd77..32d9439 100644
--- a/scripts/generate-icons.js
+++ b/scripts/generate-icons.js
@@ -53,7 +53,7 @@ function createAppleTouchSvg() {
return createLogoSvg(180);
}
-/** Favicon (32x32): simplified — just gradient background with house */
+/** Favicon (32x32): simplified - just gradient background with house */
function createFaviconSvg() {
return createLogoSvg(32);
}
diff --git a/scripts/seed-demo.js b/scripts/seed-demo.js
index ee59e46..073b3c0 100644
--- a/scripts/seed-demo.js
+++ b/scripts/seed-demo.js
@@ -1,5 +1,5 @@
/**
- * Demo Seed Script — Oikos
+ * Demo Seed Script - Oikos
* Fills the database with realistic English demo content for screenshots/mockups.
* Usage: node scripts/seed-demo.js [--db /path/to/oikos.db]
*
@@ -96,18 +96,18 @@ const insertTask = db.prepare(`
[
['Book dentist appointment', 'Annual check-up for the whole family', 'health', 'high', 'open', daysFromNow(3), alexId, alexId],
- ['Pay electricity bill', 'Due end of month — online banking', 'finance', 'urgent', 'open', daysFromNow(2), alexId, alexId],
+ ['Pay electricity bill', 'Due end of month - online banking', 'finance', 'urgent', 'open', daysFromNow(2), alexId, alexId],
['Renew car insurance', 'Compare quotes on check24.de first', 'finance', 'high', 'open', daysFromNow(10), alexId, alexId],
['Fix leaking bathroom faucet', 'Replace washer, tools in basement', 'home', 'medium', 'open', daysFromNow(7), samId, alexId],
- ['Order birthday cake', "Emma's 8th birthday — chocolate cake", 'family', 'high', 'open', daysFromNow(5), samId, samId ],
+ ['Order birthday cake', "Emma's 8th birthday - chocolate cake", 'family', 'high', 'open', daysFromNow(5), samId, samId ],
['Clean out garage', 'Donate old stuff to charity', 'home', 'low', 'open', daysFromNow(14), alexId, alexId],
['Sign school permission slip', 'Field trip to the science museum', 'school', 'urgent', 'open', daysFromNow(1), samId, samId ],
['Renew library cards', 'All three cards expired last month', 'admin', 'low', 'open', daysFromNow(20), alexId, alexId],
- ['Plan summer holiday', 'Italy or Croatia — check flights', 'family', 'medium', 'open', daysFromNow(30), alexId, alexId],
+ ['Plan summer holiday', 'Italy or Croatia - check flights', 'family', 'medium', 'open', daysFromNow(30), alexId, alexId],
['Tax return 2025', 'Documents ready in the folder', 'finance', 'high', 'open', daysFromNow(18), alexId, alexId],
['Grocery run', 'See shopping list for details', 'home', 'medium', 'done', daysFromNow(-1), samId, samId ],
['Call insurance about claim', 'Reference: CLM-2025-0492', 'finance', 'high', 'done', daysFromNow(-3), alexId, alexId],
- ['Oil change — VW Golf', 'Every 15 000 km / 12 months', 'home', 'medium', 'open', daysFromNow(6), alexId, alexId],
+ ['Oil change - VW Golf', 'Every 15 000 km / 12 months', 'home', 'medium', 'open', daysFromNow(6), alexId, alexId],
['Buy birthday gift for Mum', 'Amazon wishlist or book voucher', 'family', 'medium', 'open', daysFromNow(8), samId, samId ],
['Update home inventory', 'For insurance purposes', 'admin', 'low', 'open', daysFromNow(25), alexId, alexId],
].forEach(row => insertTask.run(...row));
@@ -122,20 +122,20 @@ const insertEvent = db.prepare(`
[
["Emma's Birthday Party", 'Bouncy castle & cake at home', daysFromNow(5) + 'T14:00', daysFromNow(5) + 'T17:00', 0, 'Home', '#F59E0B', samId, samId ],
- ['Dentist — Family', 'Dr. Müller, bring insurance cards', daysFromNow(3) + 'T10:00', daysFromNow(3) + 'T11:30', 0, 'Dental Practice Müller', '#EF4444', alexId, alexId],
+ ['Dentist - Family', 'Dr. Müller, bring insurance cards', daysFromNow(3) + 'T10:00', daysFromNow(3) + 'T11:30', 0, 'Dental Practice Müller', '#EF4444', alexId, alexId],
['Parent-Teacher Evening', 'Room 12, bring report card', daysFromNow(9) + 'T18:30', daysFromNow(9) + 'T20:00', 0, 'Westpark Primary School', '#8B5CF6', samId, samId ],
- ['Science Museum Field Trip', 'Emma — permission slip signed', daysFromNow(1) + 'T08:30', daysFromNow(1) + 'T15:00', 0, 'Natural History Museum', '#06B6D4', samId, samId ],
- ['Family BBQ — Mum & Dad', 'Bring potato salad', daysFromNow(12) + 'T13:00', daysFromNow(12) + 'T19:00', 0, "Grandma's Garden", '#F59E0B', alexId, alexId],
+ ['Science Museum Field Trip', 'Emma - permission slip signed', daysFromNow(1) + 'T08:30', daysFromNow(1) + 'T15:00', 0, 'Natural History Museum', '#06B6D4', samId, samId ],
+ ['Family BBQ - Mum & Dad', 'Bring potato salad', daysFromNow(12) + 'T13:00', daysFromNow(12) + 'T19:00', 0, "Grandma's Garden", '#F59E0B', alexId, alexId],
['Car Service Appointment', 'VW Golf, oil change + tyre check', daysFromNow(6) + 'T09:00', daysFromNow(6) + 'T10:30', 0, 'AutoHaus König', '#6B7280', alexId, alexId],
- ['Yoga Class', 'Weekly — bring mat', daysFromNow(2) + 'T19:00', daysFromNow(2) + 'T20:00', 0, 'FitLife Studio', '#10B981', samId, samId ],
- ['Yoga Class', 'Weekly — bring mat', daysFromNow(9) + 'T19:00', daysFromNow(9) + 'T20:00', 0, 'FitLife Studio', '#10B981', samId, samId ],
+ ['Yoga Class', 'Weekly - bring mat', daysFromNow(2) + 'T19:00', daysFromNow(2) + 'T20:00', 0, 'FitLife Studio', '#10B981', samId, samId ],
+ ['Yoga Class', 'Weekly - bring mat', daysFromNow(9) + 'T19:00', daysFromNow(9) + 'T20:00', 0, 'FitLife Studio', '#10B981', samId, samId ],
['Mum\'s Birthday', '', daysFromNow(8) + 'T00:00', daysFromNow(8) + 'T00:00', 1, '', '#EC4899', alexId, alexId],
- ['Company All-Hands', 'Q2 results + roadmap presentation', daysFromNow(4) + 'T10:00', daysFromNow(4) + 'T12:00', 0, 'Office — Conference Room B','#2563EB', alexId, alexId],
- ['Football Training — Leo', 'Boots & water bottle', daysFromNow(2) + 'T17:00', daysFromNow(2) + 'T18:30', 0, 'Sports Ground West', '#F97316', samId, samId ],
- ['Football Training — Leo', 'Boots & water bottle', daysFromNow(7) + 'T17:00', daysFromNow(7) + 'T18:30', 0, 'Sports Ground West', '#F97316', samId, samId ],
- ['Holiday Planning Evening', 'Italy vs Croatia — laptops out', daysFromNow(3) + 'T21:00', daysFromNow(3) + 'T22:00', 0, 'Home', '#14B8A6', alexId, samId ],
- ['GP Appointment — Alex', 'Annual health check', daysFromNow(15) + 'T11:00', daysFromNow(15) + 'T11:30', 0, 'Dr. Weber — City Practice', '#EF4444', alexId, alexId],
- ['Weekend City Break', 'Hotel booked — just pack bags!', daysFromNow(20) + 'T00:00', daysFromNow(22) + 'T00:00', 1, 'Amsterdam', '#0EA5E9', alexId, alexId],
+ ['Company All-Hands', 'Q2 results + roadmap presentation', daysFromNow(4) + 'T10:00', daysFromNow(4) + 'T12:00', 0, 'Office - Conference Room B','#2563EB', alexId, alexId],
+ ['Football Training - Leo', 'Boots & water bottle', daysFromNow(2) + 'T17:00', daysFromNow(2) + 'T18:30', 0, 'Sports Ground West', '#F97316', samId, samId ],
+ ['Football Training - Leo', 'Boots & water bottle', daysFromNow(7) + 'T17:00', daysFromNow(7) + 'T18:30', 0, 'Sports Ground West', '#F97316', samId, samId ],
+ ['Holiday Planning Evening', 'Italy vs Croatia - laptops out', daysFromNow(3) + 'T21:00', daysFromNow(3) + 'T22:00', 0, 'Home', '#14B8A6', alexId, samId ],
+ ['GP Appointment - Alex', 'Annual health check', daysFromNow(15) + 'T11:00', daysFromNow(15) + 'T11:30', 0, 'Dr. Weber - City Practice', '#EF4444', alexId, alexId],
+ ['Weekend City Break', 'Hotel booked - just pack bags!', daysFromNow(20) + 'T00:00', daysFromNow(22) + 'T00:00', 1, 'Amsterdam', '#0EA5E9', alexId, alexId],
].forEach(row => insertEvent.run(...row));
// ── Meals ────────────────────────────────────────────────────────────────────
@@ -191,18 +191,18 @@ const insertContact = db.prepare(`
`);
[
- ['Dr. Anna Weber', 'medical', '+49 231 445 2210', 'praxis@dr-weber.de', 'Bürgerstraße 12, Dortmund', 'GP — appointments Mon–Thu'],
+ ['Dr. Anna Weber', 'medical', '+49 231 445 2210', 'praxis@dr-weber.de', 'Bürgerstraße 12, Dortmund', 'GP - appointments Mon–Thu'],
['Dr. Thomas Müller', 'medical', '+49 231 887 0034', 'info@zahnarzt-mueller.de', 'Hansastraße 55, Dortmund', 'Family dentist'],
['Grandma & Grandpa Johnson', 'family','+49 2304 78 221', 'oma.johnson@gmail.com', 'Ahornweg 4, Castrop-Rauxel', "Emma & Leo's grandparents"],
- ['Westpark Primary School','school', '+49 231 556 8810', 'office@westpark-grundschule.de', 'Westparkstraße 20, Dortmund', "Emma's school — Mrs Bauer is class teacher"],
- ['AutoHaus König', 'services', '+49 231 997 1100', 'service@autohaus-koenig.de','Industriestraße 88, Dortmund', 'VW service partner — Ref: Golf TDI 2021'],
- ['FitLife Studio', 'services', '+49 231 340 5060', 'hello@fitlife-dortmund.de', 'Rheinlanddamm 14, Dortmund', "Sam's yoga — Tuesdays 19:00"],
- ['Uncle Mike Johnson', 'family', '+49 172 3340 551', 'mike.j@outlook.com', '', 'Alex\'s brother — lives in Hamburg'],
+ ['Westpark Primary School','school', '+49 231 556 8810', 'office@westpark-grundschule.de', 'Westparkstraße 20, Dortmund', "Emma's school - Mrs Bauer is class teacher"],
+ ['AutoHaus König', 'services', '+49 231 997 1100', 'service@autohaus-koenig.de','Industriestraße 88, Dortmund', 'VW service partner - Ref: Golf TDI 2021'],
+ ['FitLife Studio', 'services', '+49 231 340 5060', 'hello@fitlife-dortmund.de', 'Rheinlanddamm 14, Dortmund', "Sam's yoga - Tuesdays 19:00"],
+ ['Uncle Mike Johnson', 'family', '+49 172 3340 551', 'mike.j@outlook.com', '', 'Alex\'s brother - lives in Hamburg'],
['Aunt Claire Becker', 'family', '+49 151 2234 8876','claire.becker@web.de', 'Fichtenweg 7, Bochum', 'Sam\'s sister'],
['Leo\'s Football Coach', 'school', '+49 176 5512 4490','trainer@svwest-dortmund.de','Sportplatz West, Dortmund', 'Training Tues & Sat 17:00'],
- ['City Library', 'services', '+49 231 502 6600', 'stadtbibliothek@dortmund.de','Königswall 18, Dortmund', 'Family cards — renew every 2 years'],
- ['Landlord — Mr Groß', 'services', '+49 231 112 7743', 'vermieter.gross@gmail.com', '', 'Emergency maintenance: same number'],
- ['Emma\'s Best Friend Lena','family', '+49 231 774 3309', '', '', "Lena Braun — mum is Katrin +49 231 774 3308"],
+ ['City Library', 'services', '+49 231 502 6600', 'stadtbibliothek@dortmund.de','Königswall 18, Dortmund', 'Family cards - renew every 2 years'],
+ ['Landlord - Mr Groß', 'services', '+49 231 112 7743', 'vermieter.gross@gmail.com', '', 'Emergency maintenance: same number'],
+ ['Emma\'s Best Friend Lena','family', '+49 231 774 3309', '', '', "Lena Braun - mum is Katrin +49 231 774 3308"],
].forEach(row => insertContact.run(...row));
// ── Budget ───────────────────────────────────────────────────────────────────
@@ -215,41 +215,41 @@ const insertBudget = db.prepare(`
[
// Income
- ['Alex — Monthly Salary', 3850.00, 'income', thisMonthDate(1), 1, alexId],
- ['Sam — Part-time Work', 1200.00, 'income', thisMonthDate(1), 1, alexId],
+ ['Alex - Monthly Salary', 3850.00, 'income', thisMonthDate(1), 1, alexId],
+ ['Sam - Part-time Work', 1200.00, 'income', thisMonthDate(1), 1, alexId],
['Child Benefit (Kindergeld)', 250.00, 'income', thisMonthDate(5), 1, alexId],
// Fixed expenses
['Rent', -1450.00, 'housing', thisMonthDate(1), 1, alexId],
- ['Car Insurance — VW Golf', -89.50, 'transport', thisMonthDate(1), 1, alexId],
+ ['Car Insurance - VW Golf', -89.50, 'transport', thisMonthDate(1), 1, alexId],
['Health Insurance', -310.00, 'insurance', thisMonthDate(1), 1, alexId],
['Internet & Phone Bundle', -49.99, 'utilities', thisMonthDate(5), 1, alexId],
['Electricity Bill', -78.00, 'utilities', thisMonthDate(15), 1, alexId],
['Netflix', -17.99, 'leisure', thisMonthDate(10), 1, alexId],
['Spotify Family', -16.99, 'leisure', thisMonthDate(10), 1, alexId],
- ['Gym — FitLife Monthly', -39.00, 'health', thisMonthDate(1), 1, alexId],
+ ['Gym - FitLife Monthly', -39.00, 'health', thisMonthDate(1), 1, alexId],
// Variable this month
- ['Weekly Groceries — Wk 1', -142.30, 'food', thisMonthDate(4), 0, samId ],
- ['Weekly Groceries — Wk 2', -118.75, 'food', thisMonthDate(11), 0, samId ],
- ['Weekly Groceries — Wk 3', -134.20, 'food', thisMonthDate(18), 0, samId ],
+ ['Weekly Groceries - Wk 1', -142.30, 'food', thisMonthDate(4), 0, samId ],
+ ['Weekly Groceries - Wk 2', -118.75, 'food', thisMonthDate(11), 0, samId ],
+ ['Weekly Groceries - Wk 3', -134.20, 'food', thisMonthDate(18), 0, samId ],
['School Trip Payment', -25.00, 'school', thisMonthDate(3), 0, samId ],
- ['Birthday Gift — Mum', -60.00, 'family', thisMonthDate(7), 0, alexId],
- ['Restaurant — Date Night', -87.50, 'leisure', thisMonthDate(9), 0, alexId],
- ['Fuel — VW Golf', -68.00, 'transport', thisMonthDate(6), 0, alexId],
+ ['Birthday Gift - Mum', -60.00, 'family', thisMonthDate(7), 0, alexId],
+ ['Restaurant - Date Night', -87.50, 'leisure', thisMonthDate(9), 0, alexId],
+ ['Fuel - VW Golf', -68.00, 'transport', thisMonthDate(6), 0, alexId],
['Pharmacy', -22.40, 'health', thisMonthDate(8), 0, samId ],
['Leo\'s Football Boots', -54.99, 'school', thisMonthDate(12), 0, samId ],
- ['Home Improvement — Tools', -43.00, 'home', thisMonthDate(14), 0, alexId],
- ['Clothing — Emma', -38.50, 'clothing', thisMonthDate(16), 0, samId ],
+ ['Home Improvement - Tools', -43.00, 'home', thisMonthDate(14), 0, alexId],
+ ['Clothing - Emma', -38.50, 'clothing', thisMonthDate(16), 0, samId ],
['Weekend Trip Deposit', -200.00, 'leisure', thisMonthDate(19), 0, alexId],
// Last month (for trend comparison)
- ['Alex — Monthly Salary', 3850.00, 'income', lastMonthDate(1), 0, alexId],
- ['Sam — Part-time Work', 1200.00, 'income', lastMonthDate(1), 0, alexId],
+ ['Alex - Monthly Salary', 3850.00, 'income', lastMonthDate(1), 0, alexId],
+ ['Sam - Part-time Work', 1200.00, 'income', lastMonthDate(1), 0, alexId],
['Rent', -1450.00, 'housing', lastMonthDate(1), 0, alexId],
['Weekly Groceries', -489.00, 'food', lastMonthDate(10), 0, samId ],
['Electricity Bill', -82.00, 'utilities', lastMonthDate(15), 0, alexId],
- ['Fuel — VW Golf', -71.00, 'transport', lastMonthDate(8), 0, alexId],
+ ['Fuel - VW Golf', -71.00, 'transport', lastMonthDate(8), 0, alexId],
].forEach(row => insertBudget.run(...row));
// ── Notes ────────────────────────────────────────────────────────────────────
@@ -262,7 +262,7 @@ const insertNote = db.prepare(`
[
['Holiday Checklist 🌍',
- 'Passports (exp. 2028)\nTravel insurance — check!\nEuro cash — €300\nBook airport parking\nAsk Mike to water plants\nPack sunscreen SPF 50',
+ 'Passports (exp. 2028)\nTravel insurance - check!\nEuro cash - €300\nBook airport parking\nAsk Mike to water plants\nPack sunscreen SPF 50',
'#0EA5E9', 1, alexId],
['WiFi & Smart Home',
@@ -270,23 +270,23 @@ const insertNote = db.prepare(`
'#F59E0B', 1, alexId],
["Emma's School Info",
- "Class: 3b — Mrs Bauer\nSchool starts: 08:10\nCollection: 13:30 (Tue/Thu 15:00)\nAllergy: mild lactose intolerance\nBest friends: Lena, Sophie, Tim",
+ "Class: 3b - Mrs Bauer\nSchool starts: 08:10\nCollection: 13:30 (Tue/Thu 15:00)\nAllergy: mild lactose intolerance\nBest friends: Lena, Sophie, Tim",
'#EC4899', 1, samId],
['Leo\'s Activities',
- 'Football: Tues & Sat 17:00 — SV West\nSwimming: Fri 16:00 — Westbad\nNeeds: boots size 35, goggles\nCoach: Herr Krüger +49 176 5512 4490',
+ 'Football: Tues & Sat 17:00 - SV West\nSwimming: Fri 16:00 - Westbad\nNeeds: boots size 35, goggles\nCoach: Herr Krüger +49 176 5512 4490',
'#F97316', 1, samId],
['Emergency Numbers',
'Police: 110\nFire / Ambulance: 112\nPoison Control: 0800 192 11 10\nLocal GP out-of-hours: 116 117\nNearest A&E: Klinikum Dortmund',
'#EF4444', 1, alexId],
- ['Car — Important Dates',
+ ['Car - Important Dates',
'Next service: June 2025 (60,000 km)\nTÜV due: September 2025\nWinter tyres: stored at AutoHaus König\nInsurance renewal: October 2025',
'#6B7280', 0, alexId],
['Book Recommendations',
- 'Currently reading: "Atomic Habits" — James Clear\nWishlist:\n• The Thursday Murder Club\n• Lessons in Chemistry\n• Tomorrow, and Tomorrow, and Tomorrow',
+ 'Currently reading: "Atomic Habits" - James Clear\nWishlist:\n• The Thursday Murder Club\n• Lessons in Chemistry\n• Tomorrow, and Tomorrow, and Tomorrow',
'#8B5CF6', 0, samId],
['Garden To-Do',
@@ -325,7 +325,7 @@ const insertItem = db.prepare(`
['Bananas', '6', 'fruit', 0],
['Blueberries', '125 g', 'fruit', 0],
['Lemons', '4', 'fruit', 0],
- ['Pasta — spaghetti', '500 g', 'pantry', 0],
+ ['Pasta - spaghetti', '500 g', 'pantry', 0],
['Basmati rice', '1 kg', 'pantry', 0],
['Olive oil', '500 ml', 'pantry', 0],
['Tomato passata', '2 × 500 g','pantry', 0],
diff --git a/server/auth.js b/server/auth.js
index 68fe22c..2765a43 100644
--- a/server/auth.js
+++ b/server/auth.js
@@ -17,7 +17,7 @@ const router = express.Router();
// --------------------------------------------------------
// Session-Store (better-sqlite3, gleiche DB-Instanz wie App)
-// Eigene Implementierung — kein connect-sqlite3 (nutzt sqlite3-Bindings,
+// Eigene Implementierung - kein connect-sqlite3 (nutzt sqlite3-Bindings,
// die separat kompiliert werden müssten und die Fehlerquelle waren).
// --------------------------------------------------------
class BetterSQLiteStore extends session.Store {
diff --git a/server/db-schema-test.js b/server/db-schema-test.js
index 2350ef2..a55f5a0 100644
--- a/server/db-schema-test.js
+++ b/server/db-schema-test.js
@@ -1,7 +1,7 @@
/**
* Modul: DB-Schema-Export für Tests
* Zweck: SQL-Strings aus MIGRATIONS für node:sqlite-Tests exportieren.
- * Nur für Testzwecke — db.js nutzt die MIGRATIONS direkt intern.
+ * Nur für Testzwecke - db.js nutzt die MIGRATIONS direkt intern.
* Abhängigkeiten: keine
*/
diff --git a/server/db.js b/server/db.js
index a6f65ac..d803e73 100644
--- a/server/db.js
+++ b/server/db.js
@@ -60,7 +60,7 @@ function init() {
/**
* Alle Migrationen in aufsteigender Reihenfolge.
- * Neue Migrations am Ende anhängen — niemals bestehende ändern.
+ * Neue Migrations am Ende anhängen - niemals bestehende ändern.
*/
const MIGRATIONS = [
{
@@ -352,7 +352,7 @@ function currentVersion() {
* @returns {import('better-sqlite3').Database}
*/
function get() {
- if (!db) throw new Error('[DB] Nicht initialisiert — init() zuerst aufrufen.');
+ if (!db) throw new Error('[DB] Nicht initialisiert - init() zuerst aufrufen.');
return db;
}
diff --git a/server/index.js b/server/index.js
index 72e96cb..ed3d463 100644
--- a/server/index.js
+++ b/server/index.js
@@ -94,7 +94,7 @@ app.use('/api/', (req, res, next) => {
});
// --------------------------------------------------------
-// Statische Dateien (Frontend) — differenzierte Caching-Strategie
+// Statische Dateien (Frontend) - differenzierte Caching-Strategie
//
// HTML + JS + CSS: no-cache (Browser revalidiert via ETag/304, kein stale Content
// nach Deployment). Bei unverändertem File → 304 Not Modified ohne Übertragung.
@@ -113,7 +113,7 @@ app.use(express.static(path.join(__dirname, '..', 'public'), {
} else if (['.png', '.jpg', '.jpeg', '.ico', '.svg', '.webp', '.woff2', '.woff'].includes(ext)) {
res.setHeader('Cache-Control', 'public, max-age=2592000, immutable'); // 30 Tage
} else {
- // HTML, JS, CSS, JSON, manifest, sw — immer revalidieren
+ // HTML, JS, CSS, JSON, manifest, sw - immer revalidieren
res.setHeader('Cache-Control', 'no-cache, must-revalidate');
}
// manifest.json: korrekter MIME-Type für PWA-Erkennung durch Chrome/Android
diff --git a/server/routes/budget.js b/server/routes/budget.js
index a31a3af..fbf2832 100644
--- a/server/routes/budget.js
+++ b/server/routes/budget.js
@@ -17,7 +17,7 @@ const { str, oneOf, date, num, rrule, collectErrors, MAX_TITLE, MONTH_RE } = req
/**
* Erstellt fehlende Instanzen wiederkehrender Budget-Einträge für den angefragten Monat.
- * Läuft idempotent — bereits vorhandene oder explizit übersprungene Instanzen werden ignoriert.
+ * Läuft idempotent - bereits vorhandene oder explizit übersprungene Instanzen werden ignoriert.
* @param {import('better-sqlite3').Database} database
* @param {string} month YYYY-MM
*/
diff --git a/server/routes/calendar.js b/server/routes/calendar.js
index ef03359..a8e41e3 100644
--- a/server/routes/calendar.js
+++ b/server/routes/calendar.js
@@ -228,7 +228,7 @@ router.get('/google/callback', async (req, res) => {
await googleCalendar.handleCallback(code);
- // Initialen Sync im Hintergrund starten (kein await — Redirect soll sofort erfolgen)
+ // Initialen Sync im Hintergrund starten (kein await - Redirect soll sofort erfolgen)
googleCalendar.sync().catch((e) => console.error('[Google] Initialer Sync fehlgeschlagen:', e.message));
res.redirect('/settings?sync_ok=google');
diff --git a/server/routes/dashboard.js b/server/routes/dashboard.js
index 9a15c58..8677058 100644
--- a/server/routes/dashboard.js
+++ b/server/routes/dashboard.js
@@ -1,6 +1,6 @@
/**
* Modul: Dashboard
- * Zweck: Aggregierter Endpoint — liefert Daten aller Dashboard-Widgets in einem Request
+ * Zweck: Aggregierter Endpoint - liefert Daten aller Dashboard-Widgets in einem Request
* Abhängigkeiten: express, server/db.js
*/
@@ -13,7 +13,7 @@ const db = require('../db');
/**
* GET /api/v1/dashboard
* Liefert aggregierte Daten für alle Dashboard-Widgets.
- * Jedes Widget-Objekt hat ein eigenes `error`-Feld falls die Abfrage fehlschlägt —
+ * Jedes Widget-Objekt hat ein eigenes `error`-Feld falls die Abfrage fehlschlägt -
* so bricht ein fehlerhaftes Widget nicht das gesamte Dashboard.
*
* Response: {
diff --git a/server/routes/meals.js b/server/routes/meals.js
index e415a7b..f1e9ef8 100644
--- a/server/routes/meals.js
+++ b/server/routes/meals.js
@@ -40,7 +40,7 @@ function weekEnd(dateStr) {
}
// --------------------------------------------------------
-// Routen — Mahlzeiten-Vorschläge (vor dynamischen Routen!)
+// Routen - Mahlzeiten-Vorschläge (vor dynamischen Routen!)
// --------------------------------------------------------
/**
@@ -70,7 +70,7 @@ router.get('/suggestions', (req, res) => {
});
// --------------------------------------------------------
-// Routen — Wochenübersicht
+// Routen - Wochenübersicht
// --------------------------------------------------------
/**
@@ -137,7 +137,7 @@ router.get('/', (req, res) => {
});
// --------------------------------------------------------
-// CRUD — Mahlzeiten
+// CRUD - Mahlzeiten
// --------------------------------------------------------
/**
@@ -266,7 +266,7 @@ router.delete('/:id', (req, res) => {
});
// --------------------------------------------------------
-// CRUD — Zutaten
+// CRUD - Zutaten
// --------------------------------------------------------
/**
diff --git a/server/routes/weather.js b/server/routes/weather.js
index 152d232..5bdd08e 100644
--- a/server/routes/weather.js
+++ b/server/routes/weather.js
@@ -94,7 +94,7 @@ router.get('/', async (req, res) => {
// --------------------------------------------------------
// GET /api/v1/weather/icon/:code
-// Proxy für OpenWeatherMap-Icons — vermeidet externe Bild-Requests
+// Proxy für OpenWeatherMap-Icons - vermeidet externe Bild-Requests
// im PWA-Standalone-Modus (CORS/CSP-Probleme auf Android Chrome).
// Erlaubte Codes: 2–4 alphanumerische Zeichen (z.B. "01d", "10n").
// Response: PNG-Bild mit 24h-Cache
diff --git a/server/services/apple-calendar.js b/server/services/apple-calendar.js
index d021d6c..45f6bf2 100644
--- a/server/services/apple-calendar.js
+++ b/server/services/apple-calendar.js
@@ -1,15 +1,15 @@
/**
* Modul: Apple Calendar Sync (CalDAV)
* Zweck: Bidirektionaler Sync mit iCloud Calendar via CalDAV-Protokoll
- * Abhängigkeiten: tsdav (ESM — dynamisch importiert), server/db.js
+ * Abhängigkeiten: tsdav (ESM - dynamisch importiert), server/db.js
*
* Konfiguration (.env):
- * APPLE_CALDAV_URL — z.B. https://caldav.icloud.com
- * APPLE_USERNAME — Apple-ID E-Mail
- * APPLE_APP_SPECIFIC_PASSWORD — App-spezifisches Passwort aus appleid.apple.com
+ * APPLE_CALDAV_URL - z.B. https://caldav.icloud.com
+ * APPLE_USERNAME - Apple-ID E-Mail
+ * APPLE_APP_SPECIFIC_PASSWORD - App-spezifisches Passwort aus appleid.apple.com
*
* sync_config-Schlüssel:
- * apple_last_sync — ISO-8601-Timestamp des letzten Syncs
+ * apple_last_sync - ISO-8601-Timestamp des letzten Syncs
*/
'use strict';
@@ -134,7 +134,7 @@ function parseICS(ics) {
const location = get('LOCATION') || null;
const rrule = get('RRULE') ? `RRULE:${get('RRULE')}` : null;
- // DTSTART — mit optionalem TZID oder VALUE=DATE
+ // DTSTART - mit optionalem TZID oder VALUE=DATE
const dtStartRaw = (() => {
const m = /^DTSTART(?:;[^:]*)?:(.*)$/im.exec(block);
return m ? m[1].trim() : null;
@@ -148,7 +148,7 @@ function parseICS(ics) {
const dtstart = dtStartRaw ? formatICSDate(dtStartRaw, allDay) : null;
let dtend = dtEndRaw ? formatICSDate(dtEndRaw, allDay) : null;
- // RFC 5545: DTEND for VALUE=DATE is exclusive — subtract one day
+ // RFC 5545: DTEND for VALUE=DATE is exclusive - subtract one day
if (allDay && dtend) {
const d = new Date(dtend + 'T00:00:00');
d.setDate(d.getDate() - 1);
@@ -215,7 +215,7 @@ function applyDuration(dtstart, dur, allDay) {
base.setHours(base.getHours() + hours, base.getMinutes() + mins, base.getSeconds() + secs);
if (allDay) {
- // Duration end is exclusive for DATE values — subtract one day for inclusive storage
+ // Duration end is exclusive for DATE values - subtract one day for inclusive storage
base.setDate(base.getDate() - 1);
return `${base.getFullYear()}-${String(base.getMonth() + 1).padStart(2, '0')}-${String(base.getDate()).padStart(2, '0')}`;
}
@@ -247,7 +247,7 @@ function buildICS(event) {
if (event.all_day) {
const startDate = event.start_datetime.slice(0, 10).replace(/-/g, '');
- // RFC 5545: DTEND for VALUE=DATE is exclusive — add one day
+ // RFC 5545: DTEND for VALUE=DATE is exclusive - add one day
const endSrc = (event.end_datetime || event.start_datetime).slice(0, 10);
const endD = new Date(endSrc + 'T00:00:00');
endD.setDate(endD.getDate() + 1);
@@ -288,7 +288,7 @@ async function sync() {
throw new Error('[Apple] Keine Credentials konfiguriert (weder in DB noch in .env).');
}
- // tsdav ist ESM-only — dynamischer Import aus CommonJS
+ // tsdav ist ESM-only - dynamischer Import aus CommonJS
const { createDAVClient } = await import('tsdav');
const client = await createDAVClient({
@@ -307,7 +307,7 @@ async function sync() {
// created_by: ersten existierenden User verwenden (nicht hardcoded ID 1)
const owner = db.get().prepare('SELECT id FROM users ORDER BY id ASC LIMIT 1').get();
if (!owner) {
- console.warn('[Apple] Kein User in der Datenbank — Sync übersprungen.');
+ console.warn('[Apple] Kein User in der Datenbank - Sync übersprungen.');
return;
}
const createdBy = owner.id;
@@ -397,7 +397,7 @@ async function sync() {
}
cfgSet('apple_last_sync', new Date().toISOString());
- console.log(`[Apple] Sync abgeschlossen — ${totalObjects} Objekte aus ${syncCalendars.length} Kalendern inbound, ${localEvents.length} lokal → iCloud.`);
+ console.log(`[Apple] Sync abgeschlossen - ${totalObjects} Objekte aus ${syncCalendars.length} Kalendern inbound, ${localEvents.length} lokal → iCloud.`);
}
module.exports = { sync, getStatus, saveCredentials, clearCredentials, testConnection };
diff --git a/server/services/google-calendar.js b/server/services/google-calendar.js
index 20dbe97..81cbb0c 100644
--- a/server/services/google-calendar.js
+++ b/server/services/google-calendar.js
@@ -4,11 +4,11 @@
* Abhängigkeiten: googleapis, server/db.js
*
* sync_config-Schlüssel:
- * google_access_token — OAuth Access Token
- * google_refresh_token — OAuth Refresh Token (langlebig)
- * google_token_expiry — ISO-8601-Timestamp bis wann Access Token gültig ist
- * google_sync_token — Inkrementeller Sync-Token von Google (events.list)
- * google_last_sync — ISO-8601-Timestamp des letzten erfolgreichen Syncs
+ * google_access_token - OAuth Access Token
+ * google_refresh_token - OAuth Refresh Token (langlebig)
+ * google_token_expiry - ISO-8601-Timestamp bis wann Access Token gültig ist
+ * google_sync_token - Inkrementeller Sync-Token von Google (events.list)
+ * google_last_sync - ISO-8601-Timestamp des letzten erfolgreichen Syncs
*/
'use strict';
@@ -65,7 +65,7 @@ function loadAuthorizedClient() {
const refreshToken = cfgGet('google_refresh_token');
if (!accessToken || !refreshToken) {
- throw new Error('[Google] Nicht konfiguriert — zuerst OAuth durchführen.');
+ throw new Error('[Google] Nicht konfiguriert - zuerst OAuth durchführen.');
}
const client = createClient();
@@ -103,7 +103,7 @@ function getAuthUrl() {
/**
* OAuth-Callback: tauscht Code gegen Tokens, speichert in sync_config.
- * @param {string} code — Code aus dem OAuth-Callback-Query-Parameter
+ * @param {string} code - Code aus dem OAuth-Callback-Query-Parameter
*/
async function handleCallback(code) {
const client = createClient();
@@ -117,7 +117,7 @@ async function handleCallback(code) {
cfgSet('google_refresh_token', tokens.refresh_token);
if (tokens.expiry_date) cfgSet('google_token_expiry', String(tokens.expiry_date));
- console.log('[Google] OAuth erfolgreich — Tokens gespeichert.');
+ console.log('[Google] OAuth erfolgreich - Tokens gespeichert.');
}
/**
@@ -179,7 +179,7 @@ async function sync() {
} catch (err) {
if (err.code === 410) {
// syncToken abgelaufen → vollständiger Resync
- console.warn('[Google] syncToken ungültig — vollständiger Resync.');
+ console.warn('[Google] syncToken ungültig - vollständiger Resync.');
cfgDel('google_sync_token');
syncToken = null;
continue;
@@ -220,7 +220,7 @@ async function sync() {
}
cfgSet('google_last_sync', new Date().toISOString());
- console.log(`[Google] Sync abgeschlossen — ${localEvents.length} lokal → Google, Inbound via syncToken.`);
+ console.log(`[Google] Sync abgeschlossen - ${localEvents.length} lokal → Google, Inbound via syncToken.`);
}
// --------------------------------------------------------
diff --git a/setup.js b/setup.js
index 1911676..1645e9b 100644
--- a/setup.js
+++ b/setup.js
@@ -1,6 +1,6 @@
/**
* Modul: Setup-Script
- * Zweck: Erstmalige Einrichtung — ersten Admin-User anlegen.
+ * Zweck: Erstmalige Einrichtung - ersten Admin-User anlegen.
* Wird einmalig nach dem ersten Start ausgeführt: `node setup.js`
* Abhängigkeiten: server/db.js, bcrypt, dotenv
*/
diff --git a/test-browser-loader.mjs b/test-browser-loader.mjs
index c373a18..b3e1479 100644
--- a/test-browser-loader.mjs
+++ b/test-browser-loader.mjs
@@ -1,5 +1,5 @@
/**
- * test-browser-loader.mjs — Node.js Custom Loader für Tests
+ * test-browser-loader.mjs - Node.js Custom Loader für Tests
* Zweck: Browser-absolute Pfade (/foo.js) auf Stubs umleiten, damit
* Frontend-Module im Node-Test-Kontext importierbar sind.
* Verwendung: node --loader ./test-browser-loader.mjs test-xxx.js
diff --git a/test-calendar.js b/test-calendar.js
index f9a0b24..8041034 100644
--- a/test-calendar.js
+++ b/test-calendar.js
@@ -169,7 +169,7 @@ test('Kommende Termine (upcoming)', () => {
// Sortierung
// --------------------------------------------------------
test('Sortierung: ganztägig nach uhrzeit-basierten Terminen', () => {
- // Gleicher Tag: Ganztägig sollte nach hinten oder flexibel — hier: all_day DESC in der Abfrage
+ // Gleicher Tag: Ganztägig sollte nach hinten oder flexibel - hier: all_day DESC in der Abfrage
const events = db.prepare(`
SELECT * FROM calendar_events
WHERE DATE(start_datetime) = '2026-03-24'
diff --git a/test-db.js b/test-db.js
index e42d856..cd35edd 100644
--- a/test-db.js
+++ b/test-db.js
@@ -1,7 +1,7 @@
/**
* Modul: Datenbank-Test
* Zweck: Schema-Migration mit node:sqlite (built-in) validieren.
- * Kein Kompilieren nötig — läuft direkt mit Node 22+.
+ * Kein Kompilieren nötig - läuft direkt mit Node 22+.
* Testet SQL-Korrektheit, FK-Reihenfolge, Triggers, Indizes.
*
* Ausführen: node test-db.js
@@ -13,7 +13,7 @@ const { DatabaseSync } = require('node:sqlite');
// --------------------------------------------------------
// Migrations-SQL direkt aus db.js extrahieren
-// (Nur für Tests — in Produktion läuft db.js mit better-sqlite3)
+// (Nur für Tests - in Produktion läuft db.js mit better-sqlite3)
// --------------------------------------------------------
const { MIGRATIONS_SQL } = require('./server/db-schema-test');
diff --git a/test-meals.js b/test-meals.js
index 47e19f8..678d524 100644
--- a/test-meals.js
+++ b/test-meals.js
@@ -251,7 +251,7 @@ test('Zutaten → Einkaufsliste übertragen (INSERT + Flag setzen)', () => {
});
test('Zweiter Transfer überträgt nichts (alle bereits markiert)', () => {
- // Mahlzeit aus vorherigem Test — alle on_shopping_list = 1
+ // Mahlzeit aus vorherigem Test - alle on_shopping_list = 1
const suppe = db.prepare(`SELECT id FROM meals WHERE title = 'Suppe'`).get();
const open = db.prepare(`
SELECT * FROM meal_ingredients WHERE meal_id = ? AND on_shopping_list = 0
diff --git a/test-modal-utils.js b/test-modal-utils.js
index 79ab0b1..48c9b7d 100644
--- a/test-modal-utils.js
+++ b/test-modal-utils.js
@@ -1,7 +1,7 @@
/**
* Tests: Modal Utilities (wireBlurValidation, btnSuccess, btnError)
* Modul: /public/components/modal.js
- * Läuft im Node-Kontext — die Utility-Funktionen greifen ausschließlich
+ * Läuft im Node-Kontext - die Utility-Funktionen greifen ausschließlich
* über ihre Parameter auf DOM-Objekte zu, daher kein DOM-Polyfill nötig.
*/
import { test } from 'node:test';
diff --git a/test-notes-contacts-budget.js b/test-notes-contacts-budget.js
index 8c4e7bc..1a68a00 100644
--- a/test-notes-contacts-budget.js
+++ b/test-notes-contacts-budget.js
@@ -1,5 +1,5 @@
/**
- * Modul: Notes / Contacts / Budget — Tests
+ * Modul: Notes / Contacts / Budget - Tests
* Zweck: Validiert CRUD, Constraints, Filterabfragen, Aggregation für alle drei Module
* Ausführen: node --experimental-sqlite test-notes-contacts-budget.js
*/
diff --git a/test-shopping.js b/test-shopping.js
index 61ac2fd..4841f35 100644
--- a/test-shopping.js
+++ b/test-shopping.js
@@ -72,21 +72,21 @@ test('Liste umbenennen', () => {
// --------------------------------------------------------
// Artikel-CRUD
// --------------------------------------------------------
-test('Artikel hinzufügen — Obst & Gemüse', () => {
+test('Artikel hinzufügen - Obst & Gemüse', () => {
const r = db.prepare(`INSERT INTO shopping_items (list_id, name, quantity, category)
VALUES (?, 'Äpfel', '1 kg', 'Obst & Gemüse')`).run(listId);
itemId1 = r.lastInsertRowid;
assert(itemId1 > 0);
});
-test('Artikel hinzufügen — Milchprodukte', () => {
+test('Artikel hinzufügen - Milchprodukte', () => {
const r = db.prepare(`INSERT INTO shopping_items (list_id, name, quantity, category)
VALUES (?, 'Milch', '1 Liter', 'Milchprodukte')`).run(listId);
itemId2 = r.lastInsertRowid;
assert(itemId2 > 0);
});
-test('Artikel hinzufügen — Backwaren', () => {
+test('Artikel hinzufügen - Backwaren', () => {
const r = db.prepare(`INSERT INTO shopping_items (list_id, name, category)
VALUES (?, 'Brot', 'Backwaren')`).run(listId);
itemId3 = r.lastInsertRowid;
@@ -184,7 +184,7 @@ test('Autocomplete-Suggestions nach Prefix', () => {
assert(results[0].name === 'Joghurt', `Erwartet Joghurt, erhalten: ${results[0].name}`);
});
-test('Autocomplete — kein Match gibt leeres Array', () => {
+test('Autocomplete - kein Match gibt leeres Array', () => {
const results = db.prepare(`
SELECT DISTINCT name FROM shopping_items WHERE name LIKE ? COLLATE NOCASE
`).all('XXXXXXXX%');
diff --git a/test-ux-utils.js b/test-ux-utils.js
index 00cc991..6c4fb87 100644
--- a/test-ux-utils.js
+++ b/test-ux-utils.js
@@ -1,17 +1,17 @@
/**
* Tests: UX Utilities (stagger, vibrate)
- * Läuft im Node-Kontext — kein DOM verfügbar, daher nur Pure-Logic-Tests.
+ * Läuft im Node-Kontext - kein DOM verfügbar, daher nur Pure-Logic-Tests.
*/
import { test } from 'node:test';
import assert from 'node:assert/strict';
// Minimales Window/Navigator-Mock für Node
const { stagger, vibrate } = await (async () => {
- // stagger braucht window.matchMedia — wir mocken es
+ // stagger braucht window.matchMedia - wir mocken es
global.window = {
matchMedia: () => ({ matches: false }),
};
- // navigator ist in Node ein getter-only property — über defineProperty überschreiben
+ // navigator ist in Node ein getter-only property - über defineProperty überschreiben
Object.defineProperty(global, 'navigator', {
value: { vibrate: null },
writable: true,