docs: archive cleanup plan, update SPEC and README for v0.45–v0.47

- Archive docs/designs/2026-05-04-repo-cleanup-design.md to docs/archive/designs/
- Remove docs/designs/2026-05-04-settings-sidebar-demo.html (implemented)
- SPEC.md: add CardDAV Accounts and CardDAV Addressbook Selection tables
- SPEC.md: expand Contacts table with multi-value fields, CardDAV columns, and
  new contact_phones / contact_emails / contact_addresses sub-tables
- SPEC.md: add birthday reminder_offset columns (v0.46.0)
- SPEC.md: update External Calendars table (apple→caldav migration note)
- SPEC.md: update Tasks module (bulk actions, v0.42.0)
- SPEC.md: update Contacts module (CardDAV multi-account sync, multi-value fields)
- SPEC.md: update Birthdays module (flexible reminder offsets, v0.46.0)
- SPEC.md: update Settings module (Synchronization tab, module toggles, scheduled
  backups, CardDAV UI, correct tab count and names)
- README.md: update Birthdays row (flexible reminder offsets)
- README.md: update Backup row (automatic scheduled backups)
- .gitignore: exclude screenshot scripts, seed scripts, backup files,
  docs/github-pages/ staging folder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ulas Kalayci
2026-05-06 01:45:00 +02:00
parent 9acb10ab1d
commit d7cae21063
5 changed files with 111 additions and 360 deletions
+13
View File
@@ -52,3 +52,16 @@ docs/repo-audit-*.md
*.txt *.txt
!public/robots.txt !public/robots.txt
CLAUDE.md CLAUDE.md
# Screenshot generation scripts (dev tooling, not part of the app)
create-screenshots*.js
custom-screenshots.js
seed-demo-simple.js
seed-english-demo*.js
screenshots/
# Database backup files
*.db.backup-*
# Duplicate GitHub Pages staging folder
docs/github-pages/
+2 -2
View File
@@ -55,11 +55,11 @@ The goal is a single, private place for everything that keeps a household runnin
| **Documents** | Upload and manage family files (PDF, images, Office documents up to 5 MB). Grid/list view, drag-and-drop upload, 14 category tags (medical, school, identity, finance, and more), per-document visibility (family, selected members, private), archive and download. | | **Documents** | Upload and manage family files (PDF, images, Office documents up to 5 MB). Grid/list view, drag-and-drop upload, 14 category tags (medical, school, identity, finance, and more), per-document visibility (family, selected members, private), archive and download. |
| **Budget** | Track income and expenses with recurring entries, monthly trends, and CSV export. 35 predefined categories plus custom ones. Supports 15 currencies. Loans tab for instalment-based loan tracking with per-payment history and automatic paid-off detection. | | **Budget** | Track income and expenses with recurring entries, monthly trends, and CSV export. 35 predefined categories plus custom ones. Supports 15 currencies. Loans tab for instalment-based loan tracking with per-payment history and automatic paid-off detection. |
| **Notes & Contacts** | Colored sticky notes with Markdown support. Contact directory with multi-account CardDAV sync (Nextcloud, iCloud, Radicale, Baikal), multiple phones/emails/addresses per contact, and vCard import/export. | | **Notes & Contacts** | Colored sticky notes with Markdown support. Contact directory with multi-account CardDAV sync (Nextcloud, iCloud, Radicale, Baikal), multiple phones/emails/addresses per contact, and vCard import/export. |
| **Birthdays** | Birthday tracker with automatic annual calendar events, age display, profile photos, and 1-day-before reminders. | | **Birthdays** | Birthday tracker with automatic annual calendar events, age display, profile photos, and customizable reminder offsets (preset or fully custom interval). |
| **Reminders** | Time-based reminders on tasks and calendar events. In-app notification badge. | | **Reminders** | Time-based reminders on tasks and calendar events. In-app notification badge. |
| **Family** | Assign family roles, profile pictures, phone, email, and birthday per member. Family details are automatically synced to Contacts and Birthdays. | | **Family** | Assign family roles, profile pictures, phone, email, and birthday per member. Family details are automatically synced to Contacts and Birthdays. |
| **API Tokens** | Named Bearer / X-API-Key tokens for external integrations. SHA-256-hashed at rest, with optional expiry. OpenAPI 3.0 spec at `/api/v1/openapi.json`. | | **API Tokens** | Named Bearer / X-API-Key tokens for external integrations. SHA-256-hashed at rest, with optional expiry. OpenAPI 3.0 spec at `/api/v1/openapi.json`. |
| **Backup** | Admin-only database backup and restore via the Settings UI. Download a snapshot or restore from a file upload with an automatic pre-restore rollback copy. | | **Backup** | Admin-only database backup and restore via the Settings UI. Download a snapshot or restore from a file upload with an automatic pre-restore rollback copy. Automatic scheduled backups (configurable schedule, rotation, retention). |
## Design & Technology ## Design & Technology
+96 -9
View File
@@ -125,11 +125,11 @@ Reusable recipe cards that can be pre-filled into meal slots.
| target_caldav_calendar_url | TEXT | CalDAV calendar URL (for outbound sync), nullable | | target_caldav_calendar_url | TEXT | CalDAV calendar URL (for outbound sync), nullable |
### External Calendars ### External Calendars
Display metadata (name, color) for synced Google/Apple/CalDAV calendars. Populated automatically during sync. Display metadata (name, color) for synced Google/CalDAV calendars. Populated automatically during sync.
| Column | Type | Constraint | | Column | Type | Constraint |
|--------|------|-----------| |--------|------|-----------|
| source | TEXT | 'google', 'apple', or 'caldav', NOT NULL | | source | TEXT | 'google' or 'caldav', NOT NULL (legacy 'apple' entries migrated to 'caldav' in v0.44.0) |
| external_id | TEXT | Calendar ID from the provider, NOT NULL | | external_id | TEXT | Calendar ID from the provider, NOT NULL |
| name | TEXT | Display name from the provider, NOT NULL | | name | TEXT | Display name from the provider, NOT NULL |
| color | TEXT | Background color from the provider (HEX) | | color | TEXT | Background color from the provider (HEX) |
@@ -179,11 +179,88 @@ Index: CREATE INDEX idx_caldav_selection_enabled ON caldav_calendar_selection(ac
|--------|------|-----------| |--------|------|-----------|
| name | TEXT | NOT NULL | | name | TEXT | NOT NULL |
| category | TEXT | Doctor, School/Nursery, Authority, Insurance, Tradesperson, Emergency, Other | | category | TEXT | Doctor, School/Nursery, Authority, Insurance, Tradesperson, Emergency, Other |
| phone | TEXT | | | phone | TEXT | legacy single-value field |
| email | TEXT | | | email | TEXT | legacy single-value field |
| address | TEXT | | | address | TEXT | legacy single-value field |
| notes | TEXT | | | notes | TEXT | |
| organization | TEXT | nullable |
| job_title | TEXT | nullable |
| birthday | TEXT | DATE, nullable |
| website | TEXT | nullable |
| photo | TEXT | Base64 data URL, nullable |
| nickname | TEXT | nullable |
| family_user_id | INTEGER | FK → Users (CASCADE delete), UNIQUE (one linked user per contact), nullable | | family_user_id | INTEGER | FK → Users (CASCADE delete), UNIQUE (one linked user per contact), nullable |
| carddav_account_id | INTEGER | FK → CardDAV Accounts (SET NULL on delete), nullable |
| carddav_uid | TEXT | CardDAV UID from server, nullable |
| carddav_addressbook_url | TEXT | Source addressbook URL, nullable |
Index: UNIQUE on `(carddav_account_id, carddav_addressbook_url, carddav_uid)` WHERE `carddav_uid IS NOT NULL`
### Contact Phones
Multiple phone numbers per contact with label and primary flag.
| Column | Type | Constraint |
|--------|------|-----------|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT |
| contact_id | INTEGER | FK → Contacts (CASCADE delete), NOT NULL |
| label | TEXT | e.g. "mobile", "work", "home", nullable |
| value | TEXT | NOT NULL |
| is_primary | INTEGER | 0/1, default 0 |
### Contact Emails
Multiple email addresses per contact with label and primary flag.
| Column | Type | Constraint |
|--------|------|-----------|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT |
| contact_id | INTEGER | FK → Contacts (CASCADE delete), NOT NULL |
| label | TEXT | e.g. "work", "home", nullable |
| value | TEXT | NOT NULL |
| is_primary | INTEGER | 0/1, default 0 |
### Contact Addresses
Multiple addresses per contact with label and primary flag.
| Column | Type | Constraint |
|--------|------|-----------|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT |
| contact_id | INTEGER | FK → Contacts (CASCADE delete), NOT NULL |
| label | TEXT | e.g. "home", "work", nullable |
| street | TEXT | nullable |
| city | TEXT | nullable |
| state | TEXT | nullable |
| postal_code | TEXT | nullable |
| country | TEXT | nullable |
| is_primary | INTEGER | 0/1, default 0 |
### CardDAV Accounts
Multi-account CardDAV integration. Stores credentials for CardDAV servers (Nextcloud, iCloud, Radicale, Baikal, etc.).
| Column | Type | Constraint |
|--------|------|-----------|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT |
| name | TEXT | User-defined label (e.g. "My Nextcloud", "iCloud"), NOT NULL |
| carddav_url | TEXT | CardDAV server base URL, NOT NULL |
| username | TEXT | CardDAV username, NOT NULL |
| password | TEXT | CardDAV password (encrypted if DB_ENCRYPTION_KEY set), NOT NULL |
| created_at | TEXT | ISO 8601 |
| last_sync | TEXT | ISO 8601, nullable |
| UNIQUE | | (carddav_url, username) |
### CardDAV Addressbook Selection
Per-account addressbook enable/disable state for CardDAV accounts.
| Column | Type | Constraint |
|--------|------|-----------|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT |
| account_id | INTEGER | FK → CardDAV Accounts (CASCADE delete), NOT NULL |
| addressbook_url | TEXT | CardDAV addressbook URL from provider, NOT NULL |
| addressbook_name | TEXT | Display name from provider, NOT NULL |
| enabled | INTEGER | 0/1 (default 1), controls sync for this addressbook |
| created_at | TEXT | ISO 8601 |
| UNIQUE | | (account_id, addressbook_url) |
Index: CREATE INDEX idx_carddav_addressbook_account ON carddav_addressbook_selection(account_id, enabled)
### Budget Entries ### Budget Entries
| Column | Type | Constraint | | Column | Type | Constraint |
@@ -255,6 +332,9 @@ Birthday records with optional profile photo and automatic calendar event + remi
| calendar_event_id | INTEGER | FK → calendar_events (SET NULL on delete), nullable | | calendar_event_id | INTEGER | FK → calendar_events (SET NULL on delete), nullable |
| family_user_id | INTEGER | FK → Users (CASCADE delete), UNIQUE (one linked user per birthday), nullable | | family_user_id | INTEGER | FK → Users (CASCADE delete), UNIQUE (one linked user per birthday), nullable |
| created_by | INTEGER | FK → Users (CASCADE delete), NOT NULL | | created_by | INTEGER | FK → Users (CASCADE delete), NOT NULL |
| reminder_offset | TEXT | Preset offset key (e.g. "1d", "1w") or "custom"; empty/null = no reminder |
| reminder_custom_amount | INTEGER | Amount for custom offset, nullable |
| reminder_custom_unit | TEXT | Unit for custom offset: "minutes", "hours", "days", "weeks", nullable |
### API Tokens ### API Tokens
Named Bearer / X-API-Key tokens for non-interactive external integrations. Admin-only creation and revocation. Token values are SHA-256-hashed at rest; the plaintext is shown only once after creation. Named Bearer / X-API-Key tokens for non-interactive external integrations. Admin-only creation and revocation. Token values are SHA-256-hashed at rest; the plaintext is shown only once after creation.
@@ -382,6 +462,7 @@ Skeleton loading instead of spinners. Clicking any widget navigates to that modu
- Recurring: automatically create next instance on completion - Recurring: automatically create next instance on completion
- Archive: completed tasks can be archived (status = 'archived'); visible in a separate Archived filter - Archive: completed tasks can be archived (status = 'archived'); visible in a separate Archived filter
- Inline reminder presets: offset from due date/time — 15 min, 1 h, 1 d, 2 d, 1 w, 2 w, or fully custom offset - Inline reminder presets: offset from due date/time — 15 min, 1 h, 1 d, 2 d, 1 w, 2 w, or fully custom offset
- **Bulk actions (list view only):** select multiple tasks via checkboxes and apply batch operations (mark done, mark open, archive, delete); bulk select toggle in toolbar
- Mobile swipe: left = done, right = edit - Mobile swipe: left = done, right = edit
- Badge for overdue tasks - Badge for overdue tasks
@@ -449,11 +530,14 @@ Masonry grid with colored sticky notes.
### Contacts (`/contacts`) ### Contacts (`/contacts`)
- CRUD with category filter - CRUD with category filter
- **Multi-value fields:** multiple phones, emails, and addresses per contact, each with a label (mobile, work, home, etc.) and optional `isPrimary` flag
- **Additional fields:** organization, job_title, birthday, website, photo, nickname
- Phone: `tel:` link, email: `mailto:` link - Phone: `tel:` link, email: `mailto:` link
- Address: Maps link (Google/Apple via user agent) - Address: Maps link (Google/Apple via user agent)
- Real-time search filter - Real-time search filter
- vCard export: each contact downloadable as `.vcf` (`GET /api/v1/contacts/:id/vcard`) - vCard export: each contact downloadable as `.vcf` (`GET /api/v1/contacts/:id/vcard`)
- vCard import: upload file → client-side parser (FN, TEL, EMAIL, ADR, NOTE, CATEGORIES) → create contact - vCard import: upload file → client-side parser (FN, TEL, EMAIL, ADR, NOTE, CATEGORIES) → create contact
- **CardDAV multi-account sync:** connect multiple CardDAV servers (Nextcloud, iCloud, Radicale, Baikal); per-addressbook enable/disable via checkboxes; manual sync trigger; bidirectional sync. New API routes under `/api/v1/contacts/cardav/*`: create/delete accounts, test connections, discover/refresh addressbooks, toggle addressbook selection, sync contacts
### Documents (`/documents`) ### Documents (`/documents`)
@@ -483,12 +567,15 @@ User management and app configuration. Logged-in users only.
- **Profile:** change display name, avatar color, password - **Profile:** change display name, avatar color, password
- **User management (admin):** create new users, edit/delete existing users, assign roles (admin/member) - **User management (admin):** create new users, edit/delete existing users, assign roles (admin/member)
- **Calendar integration:** connect/disconnect Google Calendar OAuth, store Apple Calendar (CalDAV) credentials, configure sync interval; manage ICS URL subscriptions (add, delete, sync now, set color and visibility) - **Module toggles (admin, Settings → General):** individual modules (Tasks, Calendar, Shopping, Meals, Recipes, Birthdays, Notes, Contacts, Budget, Documents) can be disabled to hide them from navigation. Data is preserved and reappears when re-enabled. Dashboard and Settings remain essential and cannot be disabled. Stored as `disabled_modules` key in `sync_config`.
- **Synchronization tab:** unified tab for calendar and contact sync, replacing the old Calendar tab. Contains two sections:
- **Calendar Sync:** connect/disconnect Google Calendar (OAuth 2.0); manage multiple CalDAV accounts (iCloud, Nextcloud, Radicale, Baikal) with per-account calendar selection via checkboxes, two-way sync, and optional outbound event target; manage ICS URL subscriptions (add, delete, sync now, set color and visibility); configure sync interval
- **Contact Sync:** manage multiple CardDAV accounts (iCloud, Nextcloud, Radicale, Baikal); per-addressbook enable/disable; manual sync trigger; real-time status badges (success, error, syncing with animated spinner)
- **Weather:** configure OpenWeatherMap location - **Weather:** configure OpenWeatherMap location
- **Language:** System (follows `navigator.language`), German, English, Spanish, French, Italian, Swedish, Greek, Russian, Turkish, Chinese, Japanese, Arabic, Hindi, Portuguese - via `oikos-locale-picker` web component; switch without page reload - **Language:** System (follows `navigator.language`), German, English, Spanish, French, Italian, Swedish, Greek, Russian, Turkish, Chinese, Japanese, Arabic, Hindi, Portuguese - via `oikos-locale-picker` web component; switch without page reload
- **API Tokens (admin):** create named Bearer / X-API-Key tokens for external integrations; the full token value is shown only once immediately after creation; tokens can be revoked at any time; support optional expiry and track last-used timestamp - **API Tokens (admin):** create named Bearer / X-API-Key tokens for external integrations; the full token value is shown only once immediately after creation; tokens can be revoked at any time; support optional expiry and track last-used timestamp
- **Backup Management (admin):** download the current database as a file (`GET /api/v1/backup/database`) or restore from a backup file (`POST /api/v1/backup/restore`, drag-and-drop supported). Validates that the uploaded file is a valid Oikos database. A rollback copy is created automatically before restore. - **Backup Management (admin):** download the current database as a file (`GET /api/v1/backup/database`) or restore from a backup file (`POST /api/v1/backup/restore`, drag-and-drop supported). Validates that the uploaded file is a valid Oikos database. A rollback copy is created automatically before restore. **Automatic scheduled backups:** configurable via `.env` (`BACKUP_ENABLED`, `BACKUP_SCHEDULE`, `BACKUP_DIR`, `BACKUP_KEEP`); default 2 AM daily, keeps last 7 copies; Settings → Backup shows scheduler status, schedule, retention policy, last backup timestamp, and a manual trigger button.
- **Tab navigation:** Settings is organized in nine tabs (General, Meals, Budget, Shopping, Calendar, Family, API Tokens, Backup, Account). Admin-only tabs: Family, API Tokens, Backup. Sticky tab bar, active tab persists in sessionStorage, Calendar tab auto-activates after OAuth callbacks. - **Tab navigation:** Settings is organized in nine tabs (General, Meals, Budget, Shopping, Synchronization, Family, API Tokens, Backup, Account). Admin-only tabs: Family, API Tokens, Backup. Sticky tab bar, active tab persists in sessionStorage, Synchronization tab auto-activates after OAuth callbacks.
- **Family management (admin):** assign a `family_role` (Dad, Mom, Parent, Child, Grandparent, Relative, Other) to each user, and set per-member phone, email, and birthday — automatically synced to Contacts and Birthdays. Displayed in the family member list and profile views. - **Family management (admin):** assign a `family_role` (Dad, Mom, Parent, Child, Grandparent, Relative, Other) to each user, and set per-member phone, email, and birthday — automatically synced to Contacts and Birthdays. Displayed in the family member list and profile views.
- **Profile picture:** users can upload a personal avatar (PNG/JPEG/WebP/GIF, ≤ 5 MB), stored as a Base64 data URL in `avatar_data`. Displayed alongside display name across the app. - **Profile picture:** users can upload a personal avatar (PNG/JPEG/WebP/GIF, ≤ 5 MB), stored as a Base64 data URL in `avatar_data`. Displayed alongside display name across the app.
- **App info:** version, license - **App info:** version, license
@@ -517,7 +604,7 @@ Personal birthday tracker with automatic calendar integration.
- Profile photo upload (PNG/JPEG/WebP/GIF, ≤ 5 MB, stored as Base64 data URL) - Profile photo upload (PNG/JPEG/WebP/GIF, ≤ 5 MB, stored as Base64 data URL)
- **Upcoming view:** birthdays sorted by days until next occurrence; shows age when year is known - **Upcoming view:** birthdays sorted by days until next occurrence; shows age when year is known
- **Calendar integration:** creating or updating a birthday automatically creates/updates a recurring annual all-day calendar event (title: "🎂 {Name}"); deleting a birthday removes the linked event - **Calendar integration:** creating or updating a birthday automatically creates/updates a recurring annual all-day calendar event (title: "🎂 {Name}"); deleting a birthday removes the linked event
- **Automatic reminder:** a birthday reminder is synced 1 day before each occurrence (auto-dismissed when the birthday passes) - **Configurable reminder:** customizable reminder offset per birthday with preset options (none, at time, 15 min, 1 h, 1 d, 2 d, 1 w, 2 w) and a fully custom interval (amount + unit). Reminder time calculated from offset; auto-dismissed when the birthday passes
- Search filter by name - Search filter by name
- API: `GET /api/v1/birthdays`, `GET /api/v1/birthdays/upcoming`, `GET /api/v1/birthdays/:id`, `POST /api/v1/birthdays`, `PUT /api/v1/birthdays/:id`, `DELETE /api/v1/birthdays/:id` - API: `GET /api/v1/birthdays`, `GET /api/v1/birthdays/upcoming`, `GET /api/v1/birthdays/:id`, `POST /api/v1/birthdays`, `PUT /api/v1/birthdays/:id`, `DELETE /api/v1/birthdays/:id`
@@ -1,349 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings Sidebar Navigation - Demo</title>
<style>
:root {
--color-bg: #ffffff;
--color-surface: #fafafa;
--color-surface-2: #f5f5f5;
--color-border: #e5e5e5;
--color-text-primary: #171717;
--color-text-secondary: #737373;
--color-text-tertiary: #a3a3a3;
--color-accent: #4F46E5;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
--space-8: 32px;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
--text-xs: 11px;
--text-sm: 13px;
--text-base: 14px;
--text-lg: 16px;
--text-2xl: 24px;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--target-base: 36px;
--transition-fast: 0.15s ease;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #18181b;
--color-surface: #27272a;
--color-surface-2: #3f3f46;
--color-border: #3f3f46;
--color-text-primary: #fafafa;
--color-text-secondary: #a1a1aa;
--color-text-tertiary: #71717a;
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--color-bg);
color: var(--color-text-primary);
}
/* Layout */
.settings-layout {
display: grid;
grid-template-columns: 240px 1fr;
min-height: 100vh;
}
/* Sidebar */
.settings-sidebar {
background: var(--color-surface);
border-right: 1px solid var(--color-border);
padding: var(--space-6) 0;
overflow-y: auto;
}
.settings-sidebar-section {
margin-bottom: var(--space-6);
}
.settings-sidebar-section__header {
display: flex;
align-items: center;
gap: var(--space-2);
padding: 0 var(--space-4);
margin-bottom: var(--space-2);
color: var(--color-text-tertiary);
font-size: var(--text-xs);
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.settings-sidebar-section--active .settings-sidebar-section__header {
color: var(--color-accent);
}
.settings-sidebar-pages {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.settings-sidebar-page {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-4);
margin: 0 var(--space-2);
border: none;
border-radius: var(--radius-md);
background: transparent;
color: var(--color-text-secondary);
font-family: inherit;
font-size: var(--text-sm);
font-weight: var(--font-weight-medium);
text-align: left;
cursor: pointer;
transition: background-color var(--transition-fast), color var(--transition-fast);
min-height: var(--target-base);
}
.settings-sidebar-page:hover {
background: var(--color-surface-2);
color: var(--color-text-primary);
}
.settings-sidebar-page--active {
background: color-mix(in srgb, var(--color-accent) 12%, transparent);
color: var(--color-accent);
font-weight: var(--font-weight-semibold);
}
/* Content */
.settings-content {
padding: var(--space-6);
max-width: 900px;
}
.settings-breadcrumb {
margin-bottom: var(--space-6);
padding-bottom: var(--space-4);
border-bottom: 1px solid var(--color-border);
font-size: var(--text-sm);
color: var(--color-text-secondary);
}
.settings-breadcrumb__current {
color: var(--color-text-primary);
font-weight: var(--font-weight-semibold);
}
.settings-page-header__title {
font-size: var(--text-2xl);
font-weight: var(--font-weight-bold);
margin-bottom: var(--space-2);
}
.settings-page-header__description {
font-size: var(--text-base);
color: var(--color-text-secondary);
line-height: 1.6;
margin-bottom: var(--space-6);
}
.settings-card {
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: var(--space-4);
margin-bottom: var(--space-4);
border: 1px solid var(--color-border);
}
.settings-empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: var(--space-8);
text-align: center;
border: 2px dashed var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-surface-2);
}
.settings-empty-state__title {
font-size: var(--text-lg);
font-weight: var(--font-weight-semibold);
margin: var(--space-4) 0 var(--space-2);
}
.settings-empty-state__description {
color: var(--color-text-secondary);
margin-bottom: var(--space-4);
}
.btn {
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
border: none;
background: var(--color-accent);
color: white;
font-weight: var(--font-weight-medium);
cursor: pointer;
}
.btn:hover {
opacity: 0.9;
}
</style>
</head>
<body>
<div class="settings-layout">
<!-- Sidebar -->
<nav class="settings-sidebar">
<!-- Persönlich -->
<div class="settings-sidebar-section">
<div class="settings-sidebar-section__header">
<span>👤</span>
<span>Persönlich</span>
</div>
<div class="settings-sidebar-pages">
<button class="settings-sidebar-page">
<span>🔐</span>
<span>Konto</span>
</button>
</div>
</div>
<!-- Module -->
<div class="settings-sidebar-section">
<div class="settings-sidebar-section__header">
<span>🧩</span>
<span>Module</span>
</div>
<div class="settings-sidebar-pages">
<button class="settings-sidebar-page settings-sidebar-page--active">
<span>⚙️</span>
<span>Allgemein</span>
</button>
<button class="settings-sidebar-page">
<span>🍽️</span>
<span>Mahlzeiten</span>
</button>
<button class="settings-sidebar-page">
<span>💰</span>
<span>Budget</span>
</button>
<button class="settings-sidebar-page">
<span>🛒</span>
<span>Einkauf</span>
</button>
</div>
</div>
<!-- Synchronisation -->
<div class="settings-sidebar-section settings-sidebar-section--active">
<div class="settings-sidebar-section__header">
<span>🔄</span>
<span>Synchronisation</span>
</div>
<div class="settings-sidebar-pages">
<button class="settings-sidebar-page">
<span>📅</span>
<span>Kalender</span>
</button>
<button class="settings-sidebar-page">
<span>👥</span>
<span>Kontakte</span>
</button>
</div>
</div>
<!-- Administration -->
<div class="settings-sidebar-section">
<div class="settings-sidebar-section__header">
<span>🛡️</span>
<span>Administration</span>
</div>
<div class="settings-sidebar-pages">
<button class="settings-sidebar-page">
<span>👨‍👩‍👧‍👦</span>
<span>Familie</span>
</button>
<button class="settings-sidebar-page">
<span>🔑</span>
<span>API-Tokens</span>
</button>
<button class="settings-sidebar-page">
<span>💾</span>
<span>Backup</span>
</button>
</div>
</div>
</nav>
<!-- Content -->
<main class="settings-content">
<nav class="settings-breadcrumb">
Einstellungen Module <span class="settings-breadcrumb__current">Allgemein</span>
</nav>
<header class="settings-page-header">
<h1 class="settings-page-header__title">Allgemein</h1>
<p class="settings-page-header__description">
Grundlegende Einstellungen für Design, Sprache und Module.
</p>
</header>
<div class="settings-card">
<h3 style="margin-bottom: 12px;">Design</h3>
<p style="color: var(--color-text-secondary); font-size: var(--text-sm);">
Wähle zwischen hellem, dunklem oder System-Modus.
</p>
</div>
<div class="settings-card">
<h3 style="margin-bottom: 12px;">Sprache</h3>
<p style="color: var(--color-text-secondary); font-size: var(--text-sm);">
Ändere die Anzeigesprache der Anwendung.
</p>
</div>
<!-- Demo: Kontakte-Sync Page -->
<div style="display: none;">
<nav class="settings-breadcrumb">
Einstellungen Synchronisation <span class="settings-breadcrumb__current">Kontakte</span>
</nav>
<header class="settings-page-header">
<h1 class="settings-page-header__title">Kontakte-Synchronisation</h1>
<p class="settings-page-header__description">
Verbinde mehrere CardDAV-Konten und synchronisiere deine Kontakte mit iCloud, Nextcloud und anderen Diensten.
</p>
</header>
<div class="settings-empty-state">
<span style="font-size: 48px;">📇</span>
<h3 class="settings-empty-state__title">Noch keine CardDAV-Konten</h3>
<p class="settings-empty-state__description">
Füge dein erstes CardDAV-Konto hinzu, um Kontakte zu synchronisieren.
</p>
<button class="btn">+ CardDAV-Konto hinzufügen</button>
</div>
</div>
</main>
</div>
</body>
</html>