diff --git a/apps/web/src/app/(portal)/page.tsx b/apps/web/src/app/(portal)/page.tsx index 7aed16b..7bf4340 100644 --- a/apps/web/src/app/(portal)/page.tsx +++ b/apps/web/src/app/(portal)/page.tsx @@ -48,6 +48,9 @@ interface ActiveModule { export default function DashboardPage() { const t = useTranslations('widgets'); const [catalogOpen, setCatalogOpen] = useState(false); + // Seitlicher Versatz der zentrierten Kacheln — die Zeile mit Begruessung + // und Befehlsleiste rueckt mit, damit sie buendig ueber den Kacheln steht. + const [gridInset, setGridInset] = useState(0); // Hintergrund je Benutzer (Design „Mosaik“, Prototyp mit localStorage). const userId = useAuthStore((s) => s.user?.id); const { background, choose: chooseBackground } = useDashboardBackground(userId); @@ -143,7 +146,10 @@ export default function DashboardPage() { links, Befehlsleiste rechts (Fluent „command bar“) statt des frueheren schwebenden Knopfs unten rechts. Mit Hintergrund liegen beide auf der durchscheinenden Kachelflaeche (.dashboard-chrome). */} -
+
0 ? { paddingInline: 12 + gridInset } : undefined} + >
@@ -198,6 +204,7 @@ export default function DashboardPage() { onRemoveWidget={removeWidget} onOpenCatalog={() => setCatalogOpen(true)} onQuickAdd={(type) => addWidget(type)} + onInsetChange={setGridInset} /> )} diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 2ed7303..8eb180b 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -433,6 +433,70 @@ input[type="checkbox"], input[type="radio"] { line-height: 1.25rem; } +/* Seitenleiste (Runde 3): stehen die Dashboard-Reiter unter „Dashboard“, + traegt nur der gewaehlte Reiter die Auswahl — der Obereintrag bleibt + neutral, sonst waere „Dashboard“ doppelt markiert. */ +li:has(> #sidebar-dashboard-slot > nav) > a { + background-color: transparent; + font-weight: 500; +} +li:has(> #sidebar-dashboard-slot > nav) > a:hover { + background-color: var(--sidebar-hover); +} +li:has(> #sidebar-dashboard-slot > nav) > a [data-testid="nav-selection-pill"] { + display: none; +} + +/* Runde 3 — mehr Leben in den Kacheln, ohne bunt zu werden. + Symbol-Chip: Flaeche und Symbol aus der persoenlichen Akzentfarbe. */ +.widget-icon-chip { + display: inline-flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + width: 1.625rem; + height: 1.625rem; + border-radius: 0.4375rem; + background-color: color-mix(in oklab, var(--primary) 24%, var(--card)); + color: color-mix(in oklab, var(--primary) 38%, oklch(0.27 0.02 90)); +} +.dark .widget-icon-chip { + background-color: color-mix(in oklab, var(--primary) 16%, var(--card)); + color: var(--primary); +} + +/* Karte hebt sich beim Zeigen leicht an (nur Maus, nur Ansicht). */ +@media (hover: hover) { + .widget-card { + transition: box-shadow 160ms ease-out, translate 160ms ease-out; + } + .widget-card:not(.widget-drag-handle):hover { + translate: 0 -1px; + box-shadow: 0 0 2px oklch(0 0 0 / 0.12), 0 6px 16px oklch(0 0 0 / 0.12); + } + .dark .widget-card:not(.widget-drag-handle):hover { + box-shadow: 0 0 0 1px oklch(1 0 0 / 0.08), 0 8px 20px oklch(0 0 0 / 0.45); + } +} + +/* Beim Laden blenden die Kacheln gestaffelt ein (je 40 ms, --enter-delay). */ +@media (prefers-reduced-motion: no-preference) { + .widget-card { + animation: widget-enter 320ms cubic-bezier(0.2, 0.7, 0.3, 1) both; + animation-delay: var(--enter-delay, 0ms); + } +} +@keyframes widget-enter { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: none; + } +} + /* Aktionen der Kachel erscheinen erst beim Zeigen oder mit Tastaturfokus — nur auf Geraeten mit Maus; auf Touch bleiben sie sichtbar. */ @media (hover: hover) { diff --git a/apps/web/src/components/dashboard/dashboard-background.tsx b/apps/web/src/components/dashboard/dashboard-background.tsx index c0c26d6..9801f94 100644 --- a/apps/web/src/components/dashboard/dashboard-background.tsx +++ b/apps/web/src/components/dashboard/dashboard-background.tsx @@ -209,8 +209,8 @@ export function BackgroundPicker({ value, onChange }: BackgroundPickerProps) { {option('none', t('none'), NO_BACKGROUND, { backgroundColor: 'var(--background)' }, , )} - {BACKGROUND_PRESETS.map((preset) => { - const css = dark ? preset.dark : preset.light; + {BACKGROUND_PRESETS.filter((preset) => !dark || preset.dark !== null).map((preset) => { + const css = (dark ? preset.dark : preset.light) ?? preset.light; return option( preset.id, t(`presets.${preset.labelKey}`), diff --git a/apps/web/src/components/dashboard/dashboard-greeting.tsx b/apps/web/src/components/dashboard/dashboard-greeting.tsx index 319f0a1..6332516 100644 --- a/apps/web/src/components/dashboard/dashboard-greeting.tsx +++ b/apps/web/src/components/dashboard/dashboard-greeting.tsx @@ -19,8 +19,8 @@ export function greetingName(displayName: string | null | undefined, username: s } /** - * Ruhige Begruessung ueber dem Raster (Design „Mosaik“, Runde 2): „Guten - * Morgen, Max“ plus Wochentag und Datum. Keine Kennzahlen. Die Uhrzeit wird + * Ruhige Begruessung ueber dem Raster (Design „Mosaik“, Runde 2/3): „Guten + * Morgen, Max“ plus Wochentag und Datum in einer Zeile. Keine Kennzahlen. Die Uhrzeit wird * erst im Browser gelesen (kein Unterschied zwischen Server und Browser) und * jede Minute aufgefrischt. */ @@ -36,7 +36,7 @@ export function DashboardGreeting() { return () => window.clearInterval(id); }, []); - if (!now) return