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:
+13
@@ -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/
|
||||||
|
|||||||
@@ -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
@@ -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>
|
|
||||||
Reference in New Issue
Block a user