style: replace em dashes with hyphens throughout codebase

Replace all — with - in all source files (JS, CSS, HTML, JSON,
Markdown) for consistency and readability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ulas
2026-04-03 17:04:39 +02:00
parent 6046cac7a8
commit 1122bd269b
56 changed files with 256 additions and 256 deletions
+6 -6
View File
@@ -138,7 +138,7 @@ function _wireSheetSwipe(panel) {
}
// --------------------------------------------------------
// _doClose gemeinsame Cleanup-Logik
// _doClose - gemeinsame Cleanup-Logik
// --------------------------------------------------------
function _doClose() {
@@ -169,12 +169,12 @@ function _doClose() {
* Öffnet ein Modal mit dem Shared-System.
*
* @param {Object} opts
* @param {string} opts.title Titel im Modal-Header
* @param {string} opts.content HTML-String für den Modal-Body
* @param {Function} [opts.onSave] Callback, wird nach Einfügen in DOM aufgerufen
* @param {string} opts.title - Titel im Modal-Header
* @param {string} opts.content - HTML-String für den Modal-Body
* @param {Function} [opts.onSave] - Callback, wird nach Einfügen in DOM aufgerufen
* (zum Binden von Form-Events)
* @param {Function} [opts.onDelete] Falls vorhanden, wird ein Löschen-Button eingebaut
* @param {string} [opts.size='md'] 'sm' | 'md' | 'lg'
* @param {Function} [opts.onDelete] - Falls vorhanden, wird ein Löschen-Button eingebaut
* @param {string} [opts.size='md'] - 'sm' | 'md' | 'lg'
*/
export function openModal({ title, content, onSave, onDelete, size = 'md' } = {}) {
// Vorheriges Modal schließen (kein Stacking)
+1 -1
View File
@@ -27,7 +27,7 @@ class OikosInstallPrompt extends HTMLElement {
}
connectedCallback() {
// Bereits im Standalone-Modus nichts anzeigen
// Bereits im Standalone-Modus - nichts anzeigen
if (
window.matchMedia('(display-mode: standalone)').matches ||
navigator.standalone === true
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* oikos-locale-picker Sprachauswahl-Web-Component
* oikos-locale-picker - Sprachauswahl-Web-Component
* Zeigt ein <select>-Dropdown für System/Deutsch/English.
* Bei Auswahl: setLocale() oder localStorage-Eintrag löschen (System).
* Dependencies: i18n.js
+3 -3
View File
@@ -1,5 +1,5 @@
/**
* i18n Internationalisierung / Übersetzungsmodul
* i18n - Internationalisierung / Übersetzungsmodul
* Bietet t(), initI18n(), setLocale(), getLocale(), getSupportedLocales(),
* formatDate(), formatTime() für die gesamte App.
* Dependencies: none (vanilla JS, Fetch API, Intl API)
@@ -33,7 +33,7 @@ async function loadLocale(locale) {
return resp.json();
}
/** Initialisierung einmal beim App-Start aufrufen */
/** Initialisierung - einmal beim App-Start aufrufen */
export async function initI18n() {
currentLocale = resolveLocale();
fallbackTranslations = await loadLocale(DEFAULT_LOCALE);
@@ -50,7 +50,7 @@ export async function initI18n() {
document.documentElement.lang = currentLocale;
}
/** Sprache wechseln löst 'locale-changed' Event aus */
/** Sprache wechseln - löst 'locale-changed' Event aus */
export async function setLocale(locale) {
if (!SUPPORTED_LOCALES.includes(locale)) return;
localStorage.setItem(STORAGE_KEY, locale);
+2 -2
View File
@@ -14,7 +14,7 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Oikos" />
<meta name="description" content="Oikos Familienplaner" />
<meta name="description" content="Oikos - Familienplaner" />
<title>Oikos</title>
<!-- PWA -->
@@ -59,7 +59,7 @@
<script src="/lucide.min.js"></script>
</head>
<body>
<!-- App-Shell wird durch JavaScript gefüllt -->
<!-- App-Shell - wird durch JavaScript gefüllt -->
<div id="app" class="app-shell">
<!-- Skeleton-Loading während Initialisierung -->
<div id="app-loading" class="app-loading" aria-live="polite" aria-label="Lade Oikos…">
+5 -5
View File
@@ -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",
+5 -5
View File
@@ -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",
+5 -5
View File
@@ -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",
+3 -3
View File
@@ -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) {
<div class="meal-slot ${meal ? 'meal-slot--filled' : ''}" data-route="/meals" role="button" tabindex="0">
<i data-lucide="${MEAL_ICONS[type]}" class="meal-slot__icon" aria-hidden="true"></i>
<div class="meal-slot__type">${mealLabels[type]}</div>
<div class="meal-slot__title">${meal ? meal.title : ''}</div>
<div class="meal-slot__title">${meal ? meal.title : '-'}</div>
</div>
`;
}).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));
}
+1 -1
View File
@@ -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);
+3 -3
View File
@@ -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',
+3 -3
View File
@@ -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');
+1 -1
View File
@@ -1,4 +1,4 @@
# Oikos Familienplaner self-hosted, private
# Oikos Familienplaner - self-hosted, private
# Keine öffentlichen Inhalte zum Indexieren
User-agent: *
+4 -4
View File
@@ -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) {
+2 -2
View File
@@ -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);
+15 -15
View File
@@ -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.
* -------------------------------------------------------- */
+1 -1
View File
@@ -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); }
+1 -1
View File
@@ -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). */
+1 -1
View File
@@ -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 {
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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
* -------------------------------------------------------- */
+34 -34
View File
@@ -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;
+2 -2
View File
@@ -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) {
+4 -4
View File
@@ -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;