diff --git a/BRAIN.md b/BRAIN.md index 52bb27e..f55b90c 100644 --- a/BRAIN.md +++ b/BRAIN.md @@ -3,39 +3,41 @@ --- -## 🎹 1. Design-Tokens & Gemini-Dark-Theme (Copy & Paste) +## 🎹 1. Design-Tokens & Template-Aesthetic (Copy & Paste) -### Farbpalette (Strict Hex Values - Google Gemini Material 3 Dark) +### Farbpalette (Deep Dark Theme & Squircles) ```css -/* Backgrounds (Material 3 Dark Theme) */ ---bg-canvas: #131314; /* Google Gemini Canvas */ ---bg-surface: #1e1f20; /* Surface Container (Karten, Dialoge, Panels) */ ---bg-elevated: #282a2c; /* Elevated Container / Tonal Chips / Hover */ ---bg-field: #131314; /* Input Field Fill */ +/* Backgrounds (Canvas & Surfaces) */ +--bg-canvas: #16171a; /* Deep Charcoal Canvas */ +--bg-surface: #222428; /* Card & Capsule Surface */ +--bg-elevated: #282a2f; /* Elevated Container / Hover */ +--bg-field: #1a1b1e; /* Input Field Fill */ /* Borders & Dividers */ --border-subtle: rgba(255, 255, 255, 0.08); /* 1px Hairline Kante */ ---border-focus: rgba(168, 199, 250, 0.40); /* Gemini Primary Blue Hairline */ +--border-focus: rgba(29, 140, 248, 0.50); /* Electric Blue Hairline */ -/* Akzente & Status (Material 3 Soft Pastels - KEINE grellen AI-FarbverlĂ€ufe) */ ---color-primary: #a8c7fa; /* Google Gemini Primary Blue */ ---color-on-pri: #041e49; /* Text on Primary Container */ ---color-success: #a8dab5; /* Soft Green (Check / Kept) */ ---color-danger: #f28b82; /* Soft Coral Red (Discard / Delete) */ ---color-warning: #fdd663; /* Warm Amber */ +/* Akzente & Status */ +--color-primary: #1d8cf8; /* Electric Blue Primary */ +--color-on-pri: #ffffff; /* Text on Primary */ +--color-success: #22c55e; /* Vivid Green (Kept / Check / Done) */ +--color-danger: #ef4444; /* Vibrant Red (Discard / Delete) */ +--color-warning: #f97316; /* Vivid Orange (High Priority) */ +--color-purple: #a855f7; /* Accent Purple */ /* Typografie */ ---text-primary: #e3e3e3; /* High Emphasis */ ---text-secondary: #c4c7c5; /* Medium Emphasis */ ---text-muted: #8e918f; /* Low Emphasis / Subtle */ +--text-primary: #ffffff; /* High Emphasis */ +--text-secondary: #d1d5db; /* Medium Emphasis */ +--text-muted: #9ca3af; /* Low Emphasis / Subtle */ ``` ### UI-Regeln & Radien -* **Keine grellen FarbverlĂ€ufe:** Niemals Blau-zu-Lila-Gradienten wie billige AI-Templates. Strikte neutrale Anthrazit-FlĂ€chen mit sanften Pastell-Akzenten. -* **Keine grellen Glow-Schatten:** Keine starken farbigen Drop-Shadows oder Neon-Glows. Feine `border: 1px solid rgba(255,255,255,0.08)` Hairlines nutzen. -* **Buttons / Pills:** `rounded-full` (`border-radius: 9999px`) fĂŒr Filter, Actions und Navigation. -* **Karten:** `rounded-2xl` (`16px`) oder `rounded-3xl` (`24px`), Padding `p-5` bis `p-6`, Border `1px solid rgba(255, 255, 255, 0.08)`. -* **Input-Feld (Instant Capture):** Prompt-Box-Stil wie Google Gemini, Höhe `h-14` (56px) fĂŒr mobile Daumenbedienung, Autofokus bei Mount. +* **Keine grellen FarbverlĂ€ufe:** Niemals Blau-zu-Lila-Gradienten wie billige AI-Templates. Strikte neutrale Anthrazit-FlĂ€chen mit satten Einzelfarben (`#1d8cf8`, `#22c55e`, `#f97316`). +* **Squircles fĂŒr Icons:** `w-10 h-10` oder `w-9 h-9` mit `rounded-xl` in soliden Farben. +* **Pill-Buttons:** `rounded-full` (`border-radius: 9999px`) mit reinweißem Text auf `#1d8cf8`. +* **Karten:** `rounded-2xl` (`16px`) oder `rounded-3xl` (`24px`), `bg-[#222428]`, feine Hairline-Border `1px solid rgba(255, 255, 255, 0.08)`. +* **Fokus/Active Outline:** `border border-[#22c55e]/70` mit dezentem Glow fĂŒr aktive Elemente. +* **Floating Bottom Dock:** Feste Daumenleiste mit zentriertem runden `+` Button in `#1d8cf8`. * **Gesten-Physik (Framer Motion):** * Swipe-Schwellenwert: `dragConstraints={{ left: -150, right: 150 }}` * Spring-Config: `{ type: "spring", stiffness: 350, damping: 25 }` @@ -117,7 +119,7 @@ mindsift/ │ │ │ ├── workspace/ # Tiptap Block Editor │ │ │ └── lists/ # Realtime Shopping / Todo Lists │ │ ├── lib/ # WS Client, API Client, Sound/Haptics -│ │ └── index.css # Tailwind v4 & Gemini CSS Variablen +│ │ └── index.css # Tailwind v4 & Design-Tokens / CSS Variablen ├── .gitea/workflows/ # Gitea Actions CI/CD ├── Dockerfile # Multi-Stage Build (< 35 MB) ├── docker-compose.yml diff --git a/README.md b/README.md index 214090f..2e144cd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Backend](https://img.shields.io/badge/Backend-Go%201.24-00ADD8?logo=go&logoColor=white)](https://go.dev) [![Frontend](https://img.shields.io/badge/Frontend-React%2019%20%2F%20Vite-61DAFB?logo=react&logoColor=black)](https://react.dev) [![Database](https://img.shields.io/badge/Database-SQLite%20(WAL)%20%2F%20Postgres-003B57?logo=sqlite&logoColor=white)](https://sqlite.org) -[![Styling](https://img.shields.io/badge/Theme-Google%20Gemini%20Dark-8ab4f8)](#-designsystem--Ă€sthetik-google-gemini-dna) +[![Styling](https://img.shields.io/badge/Theme-Deep%20Dark-1d8cf8)](#-designsystem--Ă€sthetik) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) **MindSift** verbindet den schnellsten Brain-Dump der Welt mit der Eleganz eines modernen Workspaces, spielerischer Tinder-Style-Triage und kollaborativen Echtzeit-Listen fĂŒr Familien und Teams — 100 % Self-Hosted & Open Source. @@ -69,16 +69,19 @@ Wenn dir an der Supermarktkasse eine Idee kommt, du im Auto ĂŒber einen Geistesb --- -## 🎹 Designsystem & Ästhetik: Google Gemini DNA +## 🎹 Designsystem & Ästhetik -MindSift setzt konsequent auf die Ästhetik von Google Gemini – organisch, hochmodern und beruhigend: +MindSift setzt konsequent auf eine ultramoderne, dunkle Productivity-Suite-Ästhetik — fokussiert, augenschonend und schnell: -* **Dark Mode First (OLED Deep Obsidian):** - * Hintergrund primĂ€r: `#0b0f19` / `#131314` - * OberflĂ€chen/Karten: `#1e1f20` mit feinen `1px` Konturen (`rgba(255, 255, 255, 0.08)`) - * Akzente & Glows: Gemini Blue (`#8ab4f8`) und sanftes Violett (`#c58af9`) -* **Pill-Shapes & Soft Curves:** Stark abgerundete Eingabefelder (`rounded-full`), fließende Kapselbuttons und weiche Kanten. -* **ZurĂŒckhaltende Eleganz:** GroßzĂŒgiger Weißraum (*Spreading*), subtile Ambient-Glows bei Fokus und flĂŒssige 60fps-Gestenphysik fĂŒr die Karten-Triage. +* **Dark Mode First (Deep Charcoal Canvas):** + * Hintergrund primĂ€r: `#16171a` + * OberflĂ€chen/Karten: `#222428` mit feinen `1px` Konturen (`rgba(255, 255, 255, 0.08)`) + * Elevated & Hover: `#282a2f` / `#2b2d33` + * PrimĂ€r-Brand: Electric Blue (`#1d8cf8`) + * Status-Akzente: Vivid Green (`#22c55e`), Coral Red (`#ef4444`), Vivid Orange (`#f97316`) +* **Squircles & Solid Color Badges:** Quadratisch abgerundete Icons (`rounded-xl`) mit satten Einzelfarben ohne grelle AI-FarbverlĂ€ufe. +* **Pill-Buttons & Active Highlighting:** Filter-Chips (`All`, `Favorite`) und Action-Buttons in Pillenform (`rounded-full`); aktive Aufgaben erhalten eine scharfe grĂŒne Kontur (`border border-[#22c55e]/70`). +* **Floating Bottom Dock:** Zentrale Daumenleiste mit markantem blauem `+` Action-Button zum blitzschnellen Gedanken-Erfassen von ĂŒberall. * **Haptik & Touch-Optimierung:** Große Interaktionsradien (mind. 48px Touch-Targets), Vibrations-Feedback via Web Vibration API bei Karten-Swipes. --- @@ -123,7 +126,7 @@ Als Lead-Architektur wurde ein **Single-Binary / Single-Container** Paradigma ge | :--- | :--- | :--- | | **Backend** | **Go 1.24+ (Chi Router)** | Extrem geringer Speicherverbrauch (~15–25 MB RAM idle), Kaltstart in < 5 ms, ĂŒberlegene Concurrency fĂŒr WebSockets, erzeugt ein statisches Binary. | | **Frontend** | **React 19 + TypeScript + Vite** | Bietet mit **Framer Motion** die beste Gesten- und Physik-Engine fĂŒr flĂŒssige Tinder-Swipes und mit **Tiptap** den industrieweit fĂŒhrenden blockbasierten Rich-Text-Editor. | -| **Styling** | **Tailwind CSS v4** | Höchste Performance, null CSS-Runtime-Overhead, perfekte Abbildung des Gemini Dark-Themas mit Design-Tokens. | +| **Styling** | **Tailwind CSS v4** | Höchste Performance, null CSS-Runtime-Overhead, perfekte Abbildung des Deep Dark Themes mit Design-Tokens. | | **Realtime** | **Go WebSockets (`nhooyr/websocket`)** | Schlanke, native WebSocket-Rooms fĂŒr kollaborative Einkaufslisten mit Sub-10ms-Latenz und automatischem Reconnect. | | **Datenbank** | **SQLite (WAL-Modus) / sqlc** | StandardmĂ€ĂŸig Zero-Config mit Write-Ahead-Logging, ACID-sicher und als einzelne Datei einfachst per Backup sicherbar. Optional umschaltbar auf PostgreSQL per `DATABASE_URL`. | | **PWA / Offline** | **Workbox / IndexedDB** | App startet auch im Funkloch sofort; Gedanken werden lokal vorgehalten und beim nĂ€chsten Verbindungsaufbau transparent synchronisiert. | @@ -552,7 +555,7 @@ Phase 6: Hardening ◄──── Phase 5: Webhooks ◄──── Phase 4 - [x] Go Backend-GrundgerĂŒst (Chi Router, Konfigurations-Management, Healthcheck) - [x] SQLite Storage-Layer mit WAL-Modus und automatischer Migration - [x] JWT-Authentifizierung & User-Management -- [x] Mobile-First PWA Setup (Vite, React, Tailwind CSS, Gemini Dark Theme) +- [x] Mobile-First PWA Setup (Vite, React, Tailwind CSS, Deep Dark Theme) - [x] **Instant-Capture-Interface:** Autofokus-Eingabemaske, Sprachaufnahme (Web Speech API), Latenz < 300 ms ### Phase 2: Tinder-Style Triage & Task Engine (Sprint 2) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a5841e3..1db166a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -8,9 +8,14 @@ import { Brain, LogOut, User as UserIcon, + Plus, + Bell, + Shield, + ChevronRight, + Sparkles, } from 'lucide-react'; import { DashboardStats, SharedList, Thought, User } from './lib/types'; -import { api, clearToken, getToken } from './lib/api'; +import { api, clearToken, getToken, triggerHaptic } from './lib/api'; import { InstantCaptureBar } from './features/capture/InstantCaptureBar'; import { TriageDeck } from './features/triage/TriageDeck'; import { TasksView } from './features/triage/TasksView'; @@ -36,13 +41,14 @@ export const App: React.FC = () => { const [selectedDocId, setSelectedDocId] = useState(null); const [captureFocusCount, setCaptureFocusCount] = useState(0); - // Auth Modal State for switching or registering additional family members - const [showAuthModal, setShowAuthModal] = useState(false); + // Profile & Auth Modal State (aligned with Template Screen 2) + const [showProfileModal, setShowProfileModal] = useState(false); const [authMode, setAuthMode] = useState<'login' | 'register'>('login'); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [displayName, setDisplayName] = useState(''); const [authError, setAuthError] = useState(''); + const [notificationsEnabled, setNotificationsEnabled] = useState(true); const refreshAll = async () => { try { @@ -107,16 +113,24 @@ export const App: React.FC = () => { setActiveTab('workspace'); }; + const handleTriggerAddButton = () => { + triggerHaptic(20); + // Scroll smoothly to top and focus capture bar + window.scrollTo({ top: 0, behavior: 'smooth' }); + setCaptureFocusCount((c) => c + 1); + }; + const handleAuthSubmit = async (e: React.FormEvent) => { e.preventDefault(); setAuthError(''); + triggerHaptic(15); try { if (authMode === 'login') { await api.login(email, password); } else { await api.register(email, password, displayName); } - setShowAuthModal(false); + setShowProfileModal(false); setEmail(''); setPassword(''); setDisplayName(''); @@ -126,49 +140,62 @@ export const App: React.FC = () => { } }; + const handleLogout = async () => { + triggerHaptic(20); + clearToken(); + await api.quickStart(); + await refreshAll(); + setShowProfileModal(false); + }; + return ( -
- {/* Header Bar */} -
+
+ {/* Top Header Bar */} +
- {/* Brand Logo */} + {/* Logo & Headline */}
{ setActiveTab('triage'); - setCaptureFocusCount((c) => c + 1); + handleTriggerAddButton(); }} - className="flex items-center gap-2.5 cursor-pointer group select-none" + className="flex items-center gap-3 cursor-pointer group select-none" > -
- +
+
-
- +
+ MindSift - + Der Kopf frei in drei Sekunden
- {/* Right User Pill */} + {/* User Profile Pill matching Template Screen 2 */}
- {/* Main Content Container */} -
- {/* Global Instant Capture Section */} + {/* Main Container */} +
+ {/* Instant Capture Section */}
{ />
- {/* Google Material 3 Navigation Chips */} -
- {/* Account Switcher / Registration Modal */} - {showAuthModal && ( -
-
-
-

- {authMode === 'login' ? 'Konto wechseln' : 'Neues Familien-Konto anlegen'} -

+ {/* Floating Bottom Navigation Dock matching Template Screen 3 */} +
+
+ {/* Triage / Inbox */} + + + {/* Tasks */} + + + {/* Center Floating Action Button matching Template + Button */} +
+ + Erfassen +
+ + {/* Lists */} + + + {/* Workspace */} + +
+
+ + {/* Profile & Settings Modal styled identically to Template Screen 2 */} + {showProfileModal && ( +
+
+ {/* Screen 2: Top Profile Card */} +
+
+ {user?.displayName ? user.displayName.slice(0, 1).toUpperCase() : 'U'} +
+

+ {user?.displayName || 'Standard Benutzer'} +

+

+ {user?.email || 'lokal@mindsift.local'} +

+ + {/* Template Screen 2: Blue Pill Edit Button */}
- {user && ( -
-
- Aktiv: - {user.displayName} - ({user.email}) + {/* Template Screen 2: Capsule Settings Sections */} +
+ {/* Notification toggle capsule */} +
+
+
+ +
+ Benachrichtigungen
- )} -
- - + {/* Security info capsule */} +
+
+
+ +
+
+ Sicherheit + Lokale SQLite VerschlĂŒsselung +
+
+ +
-
+ {/* Auth Switcher Form if switching accounts */} + {authMode === 'register' && ( setDisplayName(e.target.value)} - placeholder="Anzeigename (z. B. Lisa)" - className="w-full h-10 px-4 rounded-full bg-[#131314] text-sm text-[#e3e3e3] placeholder-[#8e918f] border border-[#3c4043] focus:border-[#a8c7fa] focus:outline-none" + placeholder="Anzeigename" + className="w-full h-10 px-4 rounded-xl bg-[#16171a] text-xs text-white placeholder-zinc-500 border border-white/[0.08] focus:border-[#1d8cf8] focus:outline-none" required /> )} @@ -366,28 +506,47 @@ export const App: React.FC = () => { type="email" value={email} onChange={(e) => setEmail(e.target.value)} - placeholder="E-Mail-Adresse" - className="w-full h-10 px-4 rounded-full bg-[#131314] text-sm text-[#e3e3e3] placeholder-[#8e918f] border border-[#3c4043] focus:border-[#a8c7fa] focus:outline-none" + placeholder="E-Mail" + className="w-full h-10 px-4 rounded-xl bg-[#16171a] text-xs text-white placeholder-zinc-500 border border-white/[0.08] focus:border-[#1d8cf8] focus:outline-none" required /> setPassword(e.target.value)} - placeholder="Passwort (mind. 4 Zeichen)" - className="w-full h-10 px-4 rounded-full bg-[#131314] text-sm text-[#e3e3e3] placeholder-[#8e918f] border border-[#3c4043] focus:border-[#a8c7fa] focus:outline-none" + placeholder="Passwort" + className="w-full h-10 px-4 rounded-xl bg-[#16171a] text-xs text-white placeholder-zinc-500 border border-white/[0.08] focus:border-[#1d8cf8] focus:outline-none" required /> - {authError &&

{authError}

} + {authError &&

{authError}

}
+ + {/* Log Out Row matching Template Screen 2 */} +
+ + +
)} diff --git a/frontend/src/features/capture/InstantCaptureBar.tsx b/frontend/src/features/capture/InstantCaptureBar.tsx index e61874d..30dde31 100644 --- a/frontend/src/features/capture/InstantCaptureBar.tsx +++ b/frontend/src/features/capture/InstantCaptureBar.tsx @@ -133,15 +133,17 @@ export const InstantCaptureBar: React.FC = ({ return (
- {/* Google Gemini Style Input Capsule */} + {/* Capsule Input Bar */}
- +
+ +
= ({ ? 'Ich höre zu
' : 'Gedanke, Idee oder Zutat sofort festhalten
 (Taste C oder /)' } - className="w-full bg-transparent text-[#e3e3e3] placeholder-[#8e918f] text-sm sm:text-base focus:outline-none" + className="w-full bg-transparent text-white placeholder-zinc-500 text-sm sm:text-base focus:outline-none" autoComplete="off" /> @@ -165,8 +167,8 @@ export const InstantCaptureBar: React.FC = ({ title="PrioritĂ€t & FĂ€lligkeit" className={`h-8 px-2.5 rounded-full text-xs flex items-center gap-1.5 transition-colors ${ showOptions || priority !== 'normal' || dueDate - ? 'bg-[#282a2c] text-[#a8c7fa] border border-[#a8c7fa]/30' - : 'text-[#8e918f] hover:text-[#e3e3e3] hover:bg-[#282a2c]' + ? 'bg-[#1d8cf8]/20 text-[#1d8cf8] border border-[#1d8cf8]/40' + : 'text-zinc-400 hover:text-white hover:bg-[#2b2d33]' }`} > @@ -182,8 +184,8 @@ export const InstantCaptureBar: React.FC = ({ title="Spracheingabe" className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${ isListening - ? 'bg-[#004a77]/50 text-[#a8c7fa]' - : 'text-[#8e918f] hover:text-[#e3e3e3] hover:bg-[#282a2c]' + ? 'bg-[#1d8cf8]/20 text-[#1d8cf8]' + : 'text-zinc-400 hover:text-white hover:bg-[#2b2d33]' }`} > {isListening ? : } @@ -193,10 +195,10 @@ export const InstantCaptureBar: React.FC = ({
@@ -304,8 +304,8 @@ export const SharedListsView: React.FC = ({ onRefreshGloba
@@ -315,14 +315,14 @@ export const SharedListsView: React.FC = ({ onRefreshGloba
@@ -333,19 +333,19 @@ export const SharedListsView: React.FC = ({ onRefreshGloba {showNewSpaceModal && ( setNewSpaceName(e.target.value)} placeholder="Name des neuen Familien-/Team-Spaces (z. B. Urlaub 2026)
" - className="flex-1 h-10 px-4 rounded-full bg-[#131314] text-sm text-[#e3e3e3] placeholder-[#8e918f] border border-white/[0.08] focus:outline-none focus:border-[#a8c7fa] transition-colors" + className="flex-1 h-10 px-4 rounded-full bg-[#16171a] text-sm text-white placeholder-zinc-500 border border-white/[0.08] focus:outline-none focus:border-[#1d8cf8] transition-colors" autoFocus /> @@ -355,19 +355,19 @@ export const SharedListsView: React.FC = ({ onRefreshGloba {showJoinModal && ( setJoinCode(e.target.value)} placeholder="Invite-Code des Partners eingeben
" - className="flex-1 h-10 px-4 rounded-full bg-[#131314] text-sm text-[#e3e3e3] placeholder-[#8e918f] border border-white/[0.08] focus:outline-none focus:border-[#a8c7fa] uppercase transition-colors" + className="flex-1 h-10 px-4 rounded-full bg-[#16171a] text-sm text-white placeholder-zinc-500 border border-white/[0.08] focus:outline-none focus:border-[#1d8cf8] uppercase transition-colors" autoFocus /> @@ -383,15 +383,17 @@ export const SharedListsView: React.FC = ({ onRefreshGloba @@ -400,9 +402,9 @@ export const SharedListsView: React.FC = ({ onRefreshGloba
@@ -410,7 +412,7 @@ export const SharedListsView: React.FC = ({ onRefreshGloba {showNewListModal && (
{['🛒', 'đŸ”ïž', '🍕', '🎉', '🧰', '🎁'].map((ic) => ( @@ -420,8 +422,8 @@ export const SharedListsView: React.FC = ({ onRefreshGloba onClick={() => setNewListIcon(ic)} className={`w-9 h-9 rounded-full flex items-center justify-center transition-colors ${ newListIcon === ic - ? 'bg-[#a8c7fa]/20 border border-[#a8c7fa]/40 text-[#a8c7fa]' - : 'bg-[#131314] hover:bg-[#282a2c] border border-white/[0.08]' + ? 'bg-[#1d8cf8]/20 border border-[#1d8cf8] text-[#1d8cf8]' + : 'bg-[#16171a] hover:bg-[#2b2d33] border border-white/[0.06]' }`} > {ic} @@ -433,12 +435,12 @@ export const SharedListsView: React.FC = ({ onRefreshGloba value={newListTitle} onChange={(e) => setNewListTitle(e.target.value)} placeholder="Titel der Liste (z. B. Baumarkt, Drogerie, Camping)
" - className="flex-1 h-10 px-4 rounded-full bg-[#131314] text-sm text-[#e3e3e3] placeholder-[#8e918f] border border-white/[0.08] focus:outline-none focus:border-[#a8c7fa] transition-colors" + className="flex-1 h-10 px-4 rounded-full bg-[#16171a] text-sm text-white placeholder-zinc-500 border border-white/[0.08] focus:outline-none focus:border-[#1d8cf8] transition-colors" autoFocus /> @@ -447,7 +449,7 @@ export const SharedListsView: React.FC = ({ onRefreshGloba {/* Active Shared List Card */} {activeList ? ( -
+
{/* Quick-Add Pill Form */}
@@ -456,7 +458,7 @@ export const SharedListsView: React.FC = ({ onRefreshGloba value={newItemText} onChange={(e) => setNewItemText(e.target.value)} placeholder={`Eintrag zu "${activeList.title}" hinzufĂŒgen (z. B. Hafermilch)
`} - className="flex-1 h-11 px-5 rounded-full bg-[#131314] text-sm sm:text-base text-[#e3e3e3] placeholder-[#8e918f] border border-white/[0.08] focus:outline-none focus:border-[#a8c7fa] transition-colors" + className="flex-1 h-11 px-5 rounded-full bg-[#16171a] text-sm sm:text-base text-white placeholder-zinc-500 border border-white/[0.08] focus:outline-none focus:border-[#1d8cf8] transition-colors" />
= ({ onRefreshGloba value={newItemQty} onChange={(e) => setNewItemQty(e.target.value)} placeholder="Menge (z.B. 2x)" - className="w-32 h-11 px-4 rounded-full bg-[#131314] text-sm text-[#e3e3e3] placeholder-[#8e918f] border border-white/[0.08] focus:outline-none focus:border-[#a8c7fa] transition-colors" + className="w-32 h-11 px-4 rounded-full bg-[#16171a] text-sm text-white placeholder-zinc-500 border border-white/[0.08] focus:outline-none focus:border-[#1d8cf8] transition-colors" /> @@ -547,12 +549,12 @@ export const SharedListsView: React.FC = ({ onRefreshGloba {completedItems.length > 0 && (
- + Bereits im Einkaufswagen / Erledigt ({completedItems.length}) @@ -563,19 +565,19 @@ export const SharedListsView: React.FC = ({ onRefreshGloba
handleToggleItem(item)} - className="p-3 rounded-2xl bg-[#131314]/50 border border-white/[0.04] hover:bg-[#131314]/80 flex items-center justify-between gap-3 cursor-pointer transition-colors" + className="p-3 rounded-2xl bg-[#16171a]/50 border border-white/[0.04] hover:bg-[#16171a]/80 flex items-center justify-between gap-3 cursor-pointer transition-colors" >
- - + + {item.content} {item.quantity && ( - ({item.quantity}) + ({item.quantity}) )}
{item.completedByName && ( - ✓ {item.completedByName} + ✓ {item.completedByName} )}
))} diff --git a/frontend/src/features/settings/EcosystemView.tsx b/frontend/src/features/settings/EcosystemView.tsx index 0814448..985203d 100644 --- a/frontend/src/features/settings/EcosystemView.tsx +++ b/frontend/src/features/settings/EcosystemView.tsx @@ -94,16 +94,16 @@ export const EcosystemView: React.FC = ({ onIngested }) => { return (
{/* Section 1: API Keys for Smarthome & Automation */} -
+
-
- +
+
-

+

Webhooks & Ingestion API-SchlĂŒssel

-

+

Verbinde Home Assistant, Zigbee2MQTT, n8n oder eigene Cronjobs direkt mit deiner MindSift Inbox.

@@ -115,11 +115,11 @@ export const EcosystemView: React.FC = ({ onIngested }) => { value={newKeyName} onChange={(e) => setNewKeyName(e.target.value)} placeholder="Bezeichnung (z. B. Home Assistant, Proxmox Backup, Rauchmelder)
" - className="flex-1 h-11 px-4 rounded-full bg-[#131314] text-sm text-[#e3e3e3] placeholder-[#8e918f] border border-white/[0.08] focus:outline-none focus:border-[#a8c7fa] transition-colors" + className="flex-1 h-11 px-4 rounded-full bg-[#16171a] text-sm text-white placeholder-zinc-500 border border-white/[0.08] focus:outline-none focus:border-[#1d8cf8] transition-colors" /> @@ -180,16 +180,16 @@ export const EcosystemView: React.FC = ({ onIngested }) => {
{/* Section 2: Interactive Smarthome Webhook Simulator */} -
+
-
- +
+
-

+

Live Smarthome Webhook-Simulator

-

+

Teste den Webhook-Endpunkt direkt hier – das Event landet sofort als Karte im Triage-Deck.

@@ -208,7 +208,7 @@ export const EcosystemView: React.FC = ({ onIngested }) => { setSimTitle(preset.text); setSimPriority(preset.prio); }} - className="px-3.5 py-1.5 rounded-full bg-[#131314] hover:bg-[#282a2c] border border-white/[0.08] text-xs text-[#c4c7c5] hover:text-[#e3e3e3] transition-colors" + className="px-3.5 py-1.5 rounded-full bg-[#16171a] hover:bg-[#2b2d33] border border-white/[0.06] text-xs text-zinc-300 hover:text-white transition-colors" > {preset.label} @@ -220,12 +220,12 @@ export const EcosystemView: React.FC = ({ onIngested }) => { type="text" value={simTitle} onChange={(e) => setSimTitle(e.target.value)} - className="flex-1 h-11 px-4 rounded-full bg-[#131314] text-sm text-[#e3e3e3] border border-white/[0.08] focus:outline-none focus:border-[#a8c7fa] transition-colors" + className="flex-1 h-11 px-4 rounded-full bg-[#16171a] text-sm text-white placeholder-zinc-500 border border-white/[0.08] focus:outline-none focus:border-[#1d8cf8] transition-colors" />
{simStatus && ( -

{simStatus}

+

{simStatus}

)} {/* Code Example Snippets */}
-
- +
+ cURL / Home Assistant REST Command:
-
+          
 {`curl -X POST "${baseUrl}/api/v1/webhooks/ingest?token=${exampleToken}" \\
   -H "Content-Type: application/json" \\
   -d '{
@@ -255,16 +255,16 @@ export const EcosystemView: React.FC = ({ onIngested }) => {
       
{/* Section 3: Data Sovereignty & Export */} -
+
-
- +
+
-

+

100 % DatensouverÀnitÀt & Komplett-Export

-

+

Exportiere jederzeit alle Gedanken, Aufgaben, Dokumente und geteilten Listen als portables JSON-Archiv.

@@ -272,7 +272,7 @@ export const EcosystemView: React.FC = ({ onIngested }) => {
{loading ? ( -
Lade Aufgaben

+
Lade Aufgaben

) : filter === 'discarded' ? (
{discarded.length === 0 ? ( -
+
Keine verworfenen Gedanken im Papierkorb.
) : ( discarded.map((item) => (
-
-

{item.content}

+
+
+ +
+

{item.content}

@@ -178,92 +181,119 @@ export const TasksView: React.FC = ({ onRefreshStats, onOpenDocu ) : (
{(filter === 'open' ? openTasks : doneTasks).length === 0 ? ( -
+
{filter === 'open' - ? 'Keine offenen Aufgaben. Wische Gedanken in der Triage nach rechts, um Aufgaben anzulegen.' + ? 'Keine offenen Aufgaben vorhanden.' : 'Noch keine erledigten Aufgaben.'}
) : ( - (filter === 'open' ? openTasks : doneTasks).map((task) => ( -
-
- + (filter === 'open' ? openTasks : doneTasks).map((task, idx) => { + // Alternate squircle colors matching template: blue, green, purple, orange, violet + const squircleColors = [ + 'bg-[#1d8cf8]', // electric blue + 'bg-[#22c55e]', // vibrant green + 'bg-[#a855f7]', // purple + 'bg-[#f97316]', // orange + 'bg-[#8b5cf6]', // violet + ]; + const squircleBg = task.priority === 'urgent' + ? 'bg-[#ef4444]' + : task.isCompleted + ? 'bg-[#a855f7]' + : squircleColors[idx % squircleColors.length]; -
-

+

+ {/* Template Squircle Icon */} + -
- {task.priority === 'urgent' && ( - - Dringend - - )} - {task.priority === 'high' && ( - - Hoch - - )} - {task.tags?.map((tag) => ( - - #{tag} - - ))} +
+

+ {task.content} +

+ +
+ {task.priority === 'urgent' && ( + + Dringend + + )} + {task.priority === 'high' && ( + + Wichtig + + )} + {task.tags?.map((tag) => ( + + #{tag} + + ))} +
-
- {/* Action Controls */} -
-
- - handleDueDateChange(task, e.target.value)} - className="pl-8 pr-3 py-1 rounded-full bg-[#131314] text-[#e3e3e3] text-xs border border-white/[0.08] focus:outline-none focus:border-[#a8c7fa] transition-colors" - /> + {/* Action Controls */} +
+
+ + handleDueDateChange(task, e.target.value)} + className="pl-8 pr-3 py-1 rounded-full bg-[#16171a] text-white text-xs border border-white/[0.08] focus:outline-none focus:border-[#1d8cf8] transition-colors" + /> +
+ + + +
- - - -
-
- )) + ); + }) )}
)} diff --git a/frontend/src/features/triage/TriageDeck.tsx b/frontend/src/features/triage/TriageDeck.tsx index 295840f..45fa269 100644 --- a/frontend/src/features/triage/TriageDeck.tsx +++ b/frontend/src/features/triage/TriageDeck.tsx @@ -55,7 +55,7 @@ export const TriageDeck: React.FC = ({ const cardBorderColor = useTransform( x, [-140, 0, 140], - ['rgba(242, 139, 130, 0.6)', '#3c4043', 'rgba(168, 218, 181, 0.6)'] + ['rgba(239, 68, 68, 0.7)', 'rgba(255, 255, 255, 0.08)', 'rgba(34, 197, 94, 0.7)'] ); const handleAction = async ( @@ -98,7 +98,7 @@ export const TriageDeck: React.FC = ({ onDeckChange(); }; - // Keyboard shortcuts for desktop power users (BRAIN.md Section 5) + // Keyboard shortcuts for desktop power users useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { const target = e.target as HTMLElement; @@ -134,19 +134,19 @@ export const TriageDeck: React.FC = ({ switch (priority) { case 'urgent': return ( - + Dringend ); case 'high': return ( - - Hoch + + Wichtig ); default: return ( - + Gedanke ); @@ -156,14 +156,14 @@ export const TriageDeck: React.FC = ({ const sourceBadge = (source: string) => { if (source === 'voice') { return ( - + Diktat ); } if (source === 'webhook') { return ( - + Webhook ); @@ -173,19 +173,19 @@ export const TriageDeck: React.FC = ({ if (!activeCard) { return ( -
-
- +
+
+
-

Inbox Zero

-

+

Inbox Zero

+

Alle Gedanken sind gesichtet. Dein Kopf ist wieder frei.

@@ -193,9 +193,9 @@ export const TriageDeck: React.FC = ({ {lastTriaged && ( )} @@ -209,11 +209,11 @@ export const TriageDeck: React.FC = ({ {/* Top Deck Status & Undo Bar */}
- + {cards.length} {cards.length === 1 ? 'Gedanke' : 'Gedanken'} verbleibend - ‱ - + ‱ + H / L oder ← / →
@@ -221,9 +221,9 @@ export const TriageDeck: React.FC = ({ {lastTriaged && ( )} @@ -235,11 +235,11 @@ export const TriageDeck: React.FC = ({ {nextCard && (
{priorityBadge(nextCard.priority)}
-

{nextCard.content}

-
NĂ€chste Karte

+

{nextCard.content}

+
NĂ€chste Karte

)} @@ -262,19 +262,19 @@ export const TriageDeck: React.FC = ({ initial={{ scale: 0.96, opacity: 0, y: 12 }} animate={{ scale: 1, opacity: 1, y: 0 }} exit={{ scale: 0.92, opacity: 0 }} - className="relative w-full h-full rounded-3xl bg-[#1e1f20] border p-6 sm:p-7 shadow-card cursor-grab active:cursor-grabbing flex flex-col justify-between overflow-hidden" + className="relative w-full h-full rounded-3xl bg-[#222428] border p-6 sm:p-7 shadow-card cursor-grab active:cursor-grabbing flex flex-col justify-between overflow-hidden" > {/* Dynamic Swipe Overlays */} Behalten 👉 👈 Weg damit @@ -285,7 +285,7 @@ export const TriageDeck: React.FC = ({ {priorityBadge(activeCard.priority)} {sourceBadge(activeCard.source)}
- + {new Date(activeCard.createdAt).toLocaleString('de-DE', { day: '2-digit', @@ -298,7 +298,7 @@ export const TriageDeck: React.FC = ({ {/* Card Body */}
-

+

{activeCard.content}

@@ -310,18 +310,18 @@ export const TriageDeck: React.FC = ({ activeCard.tags.map((tag) => ( #{tag} )) ) : ( - Wische links oder rechts + Wische links oder rechts )}
{activeCard.dueDate && ( - + 📅 {activeCard.dueDate} )} @@ -332,9 +332,9 @@ export const TriageDeck: React.FC = ({ {/* Convert to Task Date Picker Popover */} {showDateModal && ( -
-
- +
+
+ FĂ€lligkeitsdatum fĂŒr Aufgabe:
@@ -342,11 +342,11 @@ export const TriageDeck: React.FC = ({ type="date" value={taskDueDate} onChange={(e) => setTaskDueDate(e.target.value)} - className="bg-[#131314] text-[#e3e3e3] px-3 py-1 rounded-full border border-[#3c4043] text-xs focus:outline-none" + className="bg-[#16171a] text-white px-3 py-1 rounded-full border border-white/[0.08] text-xs focus:outline-none focus:border-[#1d8cf8]" /> @@ -356,8 +356,8 @@ export const TriageDeck: React.FC = ({ {/* Convert to Shopping List Item Popover */} {showListSelector && ( -
-

+

+

In welche Liste verschieben?

@@ -368,26 +368,26 @@ export const TriageDeck: React.FC = ({ onClick={() => handleAction('keep', { convertTo: 'list_item', targetListId: lst.id }) } - className="px-3 py-1.5 rounded-full bg-[#282a2c] hover:bg-[#333537] border border-white/[0.06] text-xs text-[#e3e3e3] flex items-center gap-1.5 transition-colors" + className="px-3 py-1.5 rounded-full bg-[#2b2d33] hover:bg-[#32353b] border border-white/[0.06] text-xs text-zinc-200 flex items-center gap-1.5 transition-colors" > {lst.icon} {lst.title} )) ) : ( - Keine Listen vorhanden. + Keine Listen vorhanden. )}
)} - {/* Primary Triage Action Pills (Material 3 Tonal & Outlined) */} + {/* Primary Triage Action Pills */}
{/* Swipe Left: Discard */} {/* Convert to Workspace Document */} @@ -429,11 +429,11 @@ export const TriageDeck: React.FC = ({ title="Auf eine geteilte Liste setzen" className={`h-10 px-3.5 rounded-full border text-xs sm:text-sm flex items-center gap-1.5 transition-colors ${ showListSelector - ? 'bg-[#282a2c] text-[#a8dab5] border-[#a8dab5]/40 font-medium' - : 'bg-[#282a2c] hover:bg-[#333537] text-[#c4c7c5] hover:text-[#e3e3e3] border-white/[0.08]' + ? 'bg-[#22c55e]/20 text-[#22c55e] border-[#22c55e]/40 font-medium' + : 'bg-[#2b2d33] hover:bg-[#32353b] text-zinc-300 hover:text-white border-white/[0.06]' }`} > - + Auf Liste @@ -441,7 +441,7 @@ export const TriageDeck: React.FC = ({ ); })}
- {/* Right Notion-Flair Block Editor */} + {/* Right Block Editor */}
{selectedDoc ? ( -
+
{/* Document Top Action Bar */}
-
- +
+ {saveState === 'saving' ? 'Speichert
' : 'Automatisch gespeichert'} @@ -278,8 +278,8 @@ export const WorkspaceView: React.FC = ({ } className={`h-8 px-3 rounded-full text-xs flex items-center gap-1.5 transition-colors ${ selectedDoc.isPinned - ? 'bg-[#a8c7fa]/15 text-[#a8c7fa] border border-[#a8c7fa]/25' - : 'bg-[#282a2c] hover:bg-[#323639] border border-white/[0.08] text-[#c4c7c5] hover:text-[#e3e3e3]' + ? 'bg-[#1d8cf8]/20 text-[#1d8cf8] border border-[#1d8cf8]/40' + : 'bg-[#2b2d33] hover:bg-[#32353b] border border-white/[0.06] text-zinc-300 hover:text-white' }`} > @@ -288,15 +288,15 @@ export const WorkspaceView: React.FC = ({ @@ -308,14 +308,14 @@ export const WorkspaceView: React.FC = ({ {showIconPicker && ( -
+
{ICONS.map((ic) => ( @@ -337,7 +337,7 @@ export const WorkspaceView: React.FC = ({ value={selectedDoc.title} onChange={(e) => persistDocument({ ...selectedDoc, title: e.target.value })} placeholder="Dokument-Titel
" - className="w-full bg-transparent text-2xl sm:text-3xl font-semibold text-[#e3e3e3] focus:outline-none" + className="w-full bg-transparent text-2xl sm:text-3xl font-bold text-white focus:outline-none" />
@@ -353,7 +353,7 @@ export const WorkspaceView: React.FC = ({ value={block.content} onChange={(e) => updateBlock(block.id, { content: e.target.value })} placeholder="Überschrift 1
" - className="w-full bg-transparent text-2xl font-semibold text-[#e3e3e3] focus:outline-none py-1" + className="w-full bg-transparent text-2xl font-bold text-white focus:outline-none py-1" /> )} @@ -363,7 +363,7 @@ export const WorkspaceView: React.FC = ({ value={block.content} onChange={(e) => updateBlock(block.id, { content: e.target.value })} placeholder="Abschnitts-Überschrift
" - className="w-full bg-transparent text-lg font-medium text-[#a8c7fa] focus:outline-none pt-2 pb-1" + className="w-full bg-transparent text-lg font-semibold text-[#1d8cf8] focus:outline-none pt-2 pb-1" /> )} @@ -373,7 +373,7 @@ export const WorkspaceView: React.FC = ({ value={block.content} onChange={(e) => updateBlock(block.id, { content: e.target.value })} placeholder="Text schreiben
" - className="w-full bg-transparent text-sm sm:text-base text-[#e3e3e3] leading-relaxed focus:outline-none resize-none font-normal" + className="w-full bg-transparent text-sm sm:text-base text-zinc-200 leading-relaxed focus:outline-none resize-none font-normal" /> )} @@ -383,7 +383,7 @@ export const WorkspaceView: React.FC = ({ type="checkbox" checked={!!block.checked} onChange={(e) => updateBlock(block.id, { checked: e.target.checked })} - className="w-4 h-4 rounded accent-[#a8dab5] cursor-pointer" + className="w-4 h-4 rounded accent-[#22c55e] cursor-pointer" /> = ({ onChange={(e) => updateBlock(block.id, { content: e.target.value })} placeholder="To-Do Punkt
" className={`w-full bg-transparent text-sm sm:text-base focus:outline-none ${ - block.checked ? 'line-through text-[#8e918f]' : 'text-[#e3e3e3]' + block.checked ? 'line-through text-zinc-500' : 'text-white' }`} />
@@ -399,32 +399,32 @@ export const WorkspaceView: React.FC = ({ {block.type === 'bullet' && (
- + updateBlock(block.id, { content: e.target.value })} placeholder="Listenpunkt
" - className="w-full bg-transparent text-sm sm:text-base text-[#e3e3e3] focus:outline-none" + className="w-full bg-transparent text-sm sm:text-base text-zinc-200 focus:outline-none" />
)} {block.type === 'callout' && ( -
- +
+