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:
@@ -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)
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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…">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
# Oikos Familienplaner — self-hosted, private
|
||||
# Oikos Familienplaner - self-hosted, private
|
||||
# Keine öffentlichen Inhalte zum Indexieren
|
||||
|
||||
User-agent: *
|
||||
|
||||
+4
-4
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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.
|
||||
* -------------------------------------------------------- */
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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). */
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user