From e7348b0b68493ee046f8830c67b8567f75ef26db Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Sat, 22 Aug 2026 10:45:41 +0200 Subject: [PATCH] style(frontend): grundlegendes Redesign (Design-System, Farben, Typografie) Neues Design-System: warme Honig-/Wachs-Farbpalette (Amber-Primaer, Salbeigruen-Sekundaer) statt Standard-Graustufen, Space Grotesk fuer Ueberschriften, Inter fuer Fliesstext, IBM Plex Mono fuer Codes/IDs. Card-Komponente mit weicher Schattierung statt Ring, neuer Badge-Warning-Variante und statusTone()-Helper fuer semantische Status-Farben (Voelker/Honigchargen). Navigation umgebaut: aktiver Link-Zustand, Hexagon-Markenzeichen, "Mehr"-Dropdown fuer Admin-Links. Dashboard-Kacheln mit Icons/Farbakzenten, Login-Seite mit dezentem Waben-Hintergrundmuster als wiederkehrendes Signature-Element. --- .../src/app/(app)/apiaries/[id]/page.tsx | 2 +- .../src/app/(app)/apiaries/new/page.tsx | 2 +- .../src/app/(app)/apiaries/page.tsx | 4 +- .../src/app/(app)/colonies/[id]/page.tsx | 2 +- .../src/app/(app)/colonies/new/page.tsx | 2 +- .../src/app/(app)/colonies/page.tsx | 14 +- .../src/app/(app)/dashboard/page.tsx | 108 +++++++++---- .../src/app/(app)/extractions/new/page.tsx | 2 +- .../src/app/(app)/feedings/new/page.tsx | 2 +- .../src/app/(app)/harvests/[id]/page.tsx | 2 +- .../src/app/(app)/harvests/new/page.tsx | 2 +- .../[id]/add-measurement-dialog.tsx | 2 +- .../src/app/(app)/honey-batches/[id]/page.tsx | 2 +- .../create-honey-batch-dialog.tsx | 2 +- .../src/app/(app)/honey-batches/page.tsx | 12 +- .../src/app/(app)/inspections/new/page.tsx | 2 +- ApiCore-Frontend/src/app/(app)/layout.tsx | 113 +++++++------ .../src/app/(app)/queens/[id]/page.tsx | 2 +- .../src/app/(app)/queens/new/page.tsx | 2 +- .../src/app/(app)/queens/page.tsx | 4 +- .../src/app/(app)/search/page.tsx | 2 +- .../src/app/(app)/settings/audit-log/page.tsx | 4 +- .../settings/users/create-user-dialog.tsx | 2 +- .../src/app/(app)/settings/users/page.tsx | 4 +- .../storage/create-stock-item-dialog.tsx | 2 +- .../create-storage-container-dialog.tsx | 2 +- .../src/app/(app)/storage/page.tsx | 4 +- .../(app)/storage/record-movement-dialog.tsx | 2 +- .../src/app/(app)/tasks/new/page.tsx | 2 +- ApiCore-Frontend/src/app/(app)/tasks/page.tsx | 4 +- .../src/app/(app)/treatments/new/page.tsx | 2 +- .../src/app/(app)/varroa/new/page.tsx | 2 +- .../src/app/(auth)/login/page.tsx | 42 ++++- ApiCore-Frontend/src/app/globals.css | 151 ++++++++++-------- ApiCore-Frontend/src/app/layout.tsx | 19 ++- ApiCore-Frontend/src/components/ui/badge.tsx | 2 + ApiCore-Frontend/src/components/ui/card.tsx | 2 +- ApiCore-Frontend/src/lib/status-tone.ts | 20 +++ 38 files changed, 350 insertions(+), 201 deletions(-) create mode 100644 ApiCore-Frontend/src/lib/status-tone.ts diff --git a/ApiCore-Frontend/src/app/(app)/apiaries/[id]/page.tsx b/ApiCore-Frontend/src/app/(app)/apiaries/[id]/page.tsx index ef3462e..b736ee6 100644 --- a/ApiCore-Frontend/src/app/(app)/apiaries/[id]/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/apiaries/[id]/page.tsx @@ -19,7 +19,7 @@ export default function ApiaryDetailPage() { } if (isError || !apiary) { return ( -

+

Bienenstand konnte nicht geladen werden.

); diff --git a/ApiCore-Frontend/src/app/(app)/apiaries/new/page.tsx b/ApiCore-Frontend/src/app/(app)/apiaries/new/page.tsx index 4179de9..7b30977 100644 --- a/ApiCore-Frontend/src/app/(app)/apiaries/new/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/apiaries/new/page.tsx @@ -88,7 +88,7 @@ export default function NewApiaryPage() { /> {error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx b/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx index 0a9fe9e..c59ccbf 100644 --- a/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/apiaries/page.tsx @@ -36,7 +36,7 @@ export default function ApiariesPage() { return (
-

Bienenstände

+

Bienenstände

@@ -66,7 +66,7 @@ export default function ApiariesPage() { {isLoading ?

Lädt…

: null} {isError ? ( -

+

Bienenstände konnten nicht geladen werden.

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx index 79fd3f2..a517a91 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/page.tsx @@ -23,7 +23,7 @@ export default function ColonyDetailPage() { } if (isError || !colony) { return ( -

+

Volk konnte nicht geladen werden.

); diff --git a/ApiCore-Frontend/src/app/(app)/colonies/new/page.tsx b/ApiCore-Frontend/src/app/(app)/colonies/new/page.tsx index 735857c..e7105a2 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/new/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/new/page.tsx @@ -114,7 +114,7 @@ export default function NewColonyPage() {
{error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/colonies/page.tsx b/ApiCore-Frontend/src/app/(app)/colonies/page.tsx index f1d2e59..2f779c2 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/page.tsx @@ -6,6 +6,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { fetchColonies } from "@/lib/colonies/api"; import { ColonyStatus } from "@/lib/colonies/types"; import { exportColoniesCsv } from "@/lib/export/api"; +import { statusTone } from "@/lib/status-tone"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { @@ -48,7 +49,7 @@ export default function ColoniesPage() { return (
-

Völker

+

Völker

{error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/feedings/new/page.tsx b/ApiCore-Frontend/src/app/(app)/feedings/new/page.tsx index 5931828..5f42cd7 100644 --- a/ApiCore-Frontend/src/app/(app)/feedings/new/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/feedings/new/page.tsx @@ -183,7 +183,7 @@ export default function NewFeedingPage() { />
{error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/harvests/[id]/page.tsx b/ApiCore-Frontend/src/app/(app)/harvests/[id]/page.tsx index a281ce2..0063825 100644 --- a/ApiCore-Frontend/src/app/(app)/harvests/[id]/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/harvests/[id]/page.tsx @@ -19,7 +19,7 @@ export default function HarvestDetailPage() { } if (isError || !harvest) { return ( -

+

Ernte konnte nicht geladen werden.

); diff --git a/ApiCore-Frontend/src/app/(app)/harvests/new/page.tsx b/ApiCore-Frontend/src/app/(app)/harvests/new/page.tsx index 1b22be7..f4d2599 100644 --- a/ApiCore-Frontend/src/app/(app)/harvests/new/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/harvests/new/page.tsx @@ -176,7 +176,7 @@ export default function NewHarvestPage() { /> {error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/honey-batches/[id]/add-measurement-dialog.tsx b/ApiCore-Frontend/src/app/(app)/honey-batches/[id]/add-measurement-dialog.tsx index 10d6d3c..d8fd8cc 100644 --- a/ApiCore-Frontend/src/app/(app)/honey-batches/[id]/add-measurement-dialog.tsx +++ b/ApiCore-Frontend/src/app/(app)/honey-batches/[id]/add-measurement-dialog.tsx @@ -94,7 +94,7 @@ export function AddMeasurementDialog({ honeyBatchId }: { honeyBatchId: string }) /> {error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/honey-batches/[id]/page.tsx b/ApiCore-Frontend/src/app/(app)/honey-batches/[id]/page.tsx index aa6c9a7..2171026 100644 --- a/ApiCore-Frontend/src/app/(app)/honey-batches/[id]/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/honey-batches/[id]/page.tsx @@ -29,7 +29,7 @@ export default function HoneyBatchDetailPage() { } if (isError || !honeyBatch) { return ( -

+

Honigcharge konnte nicht geladen werden.

); diff --git a/ApiCore-Frontend/src/app/(app)/honey-batches/create-honey-batch-dialog.tsx b/ApiCore-Frontend/src/app/(app)/honey-batches/create-honey-batch-dialog.tsx index 454d0d9..bfdd8a9 100644 --- a/ApiCore-Frontend/src/app/(app)/honey-batches/create-honey-batch-dialog.tsx +++ b/ApiCore-Frontend/src/app/(app)/honey-batches/create-honey-batch-dialog.tsx @@ -122,7 +122,7 @@ export function CreateHoneyBatchDialog() { /> {error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx b/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx index 0670f57..15ebda7 100644 --- a/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/honey-batches/page.tsx @@ -6,6 +6,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { fetchHoneyBatches } from "@/lib/honey-batches/api"; import { HoneyBatchStatus } from "@/lib/honey-batches/types"; import { exportHoneyBatchesCsv } from "@/lib/export/api"; +import { statusTone } from "@/lib/status-tone"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -49,7 +50,7 @@ export default function HoneyBatchesPage() { return (
-

Honigchargen

+

Honigchargen

{error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/layout.tsx b/ApiCore-Frontend/src/app/(app)/layout.tsx index e24c72b..74c0c97 100644 --- a/ApiCore-Frontend/src/app/(app)/layout.tsx +++ b/ApiCore-Frontend/src/app/(app)/layout.tsx @@ -2,14 +2,53 @@ import { ReactNode, useEffect } from "react"; import Link from "next/link"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; +import { Hexagon, MoreHorizontal } from "lucide-react"; import { useAuth } from "@/lib/auth/auth-context"; import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { BottomNav } from "./bottom-nav"; import { QuickAddMenu } from "./quick-add-menu"; +const NAV_LINK_CLASS = + "shrink-0 rounded-md px-2.5 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary"; + +const PRIMARY_LINKS = [ + { label: "Dashboard", href: "/dashboard" }, + { label: "Bienenstände", href: "/apiaries" }, + { label: "Völker", href: "/colonies" }, + { label: "Königinnen", href: "/queens" }, + { label: "Aufgaben", href: "/tasks" }, + { label: "Honigchargen", href: "/honey-batches" }, + { label: "Lager", href: "/storage" }, + { label: "Suche", href: "/search" }, +]; + +const ADMIN_LINKS = [ + { label: "Benutzer", href: "/settings/users" }, + { label: "Audit-Log", href: "/settings/audit-log" }, +]; + +function NavLink({ href, label, pathname }: { href: string; label: string; pathname: string }) { + return ( + + {label} + + ); +} + export default function AppLayout({ children }: { children: ReactNode }) { const router = useRouter(); + const pathname = usePathname(); const { user, isLoading, logout } = useAuth(); useEffect(() => { @@ -24,53 +63,33 @@ export default function AppLayout({ children }: { children: ReactNode }) { return (
-
-
{error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/storage/page.tsx b/ApiCore-Frontend/src/app/(app)/storage/page.tsx index 2e922e0..10160c7 100644 --- a/ApiCore-Frontend/src/app/(app)/storage/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/storage/page.tsx @@ -29,7 +29,7 @@ export default function StoragePage() {
-

Lagerbehälter

+

Lagerbehälter

{containersLoading ?

Lädt…

: null} @@ -63,7 +63,7 @@ export default function StoragePage() {
-

Lagerartikel

+

Lagerartikel

{itemsLoading ?

Lädt…

: null} diff --git a/ApiCore-Frontend/src/app/(app)/storage/record-movement-dialog.tsx b/ApiCore-Frontend/src/app/(app)/storage/record-movement-dialog.tsx index 2fc18d1..0e27765 100644 --- a/ApiCore-Frontend/src/app/(app)/storage/record-movement-dialog.tsx +++ b/ApiCore-Frontend/src/app/(app)/storage/record-movement-dialog.tsx @@ -94,7 +94,7 @@ export function RecordMovementDialog({ stockItemId }: { stockItemId: string }) { />
{error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/tasks/new/page.tsx b/ApiCore-Frontend/src/app/(app)/tasks/new/page.tsx index 2af2d19..0ce88dc 100644 --- a/ApiCore-Frontend/src/app/(app)/tasks/new/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/tasks/new/page.tsx @@ -104,7 +104,7 @@ export default function NewTaskPage() { /> {error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/tasks/page.tsx b/ApiCore-Frontend/src/app/(app)/tasks/page.tsx index 8212bbd..fbb5f4b 100644 --- a/ApiCore-Frontend/src/app/(app)/tasks/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/tasks/page.tsx @@ -57,7 +57,7 @@ export default function TasksPage() { return (
-

Aufgaben

+

Aufgaben

@@ -110,7 +110,7 @@ export default function TasksPage() { {isLoading ?

Lädt…

: null} {isError ? ( -

+

Aufgaben konnten nicht geladen werden.

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/treatments/new/page.tsx b/ApiCore-Frontend/src/app/(app)/treatments/new/page.tsx index 059b425..0a4c29a 100644 --- a/ApiCore-Frontend/src/app/(app)/treatments/new/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/treatments/new/page.tsx @@ -153,7 +153,7 @@ export default function NewTreatmentPage() { />
{error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(app)/varroa/new/page.tsx b/ApiCore-Frontend/src/app/(app)/varroa/new/page.tsx index fb86236..3db9a45 100644 --- a/ApiCore-Frontend/src/app/(app)/varroa/new/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/varroa/new/page.tsx @@ -173,7 +173,7 @@ export default function NewVarroaMeasurementPage() { /> {error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/(auth)/login/page.tsx b/ApiCore-Frontend/src/app/(auth)/login/page.tsx index a46768b..5d172ce 100644 --- a/ApiCore-Frontend/src/app/(auth)/login/page.tsx +++ b/ApiCore-Frontend/src/app/(auth)/login/page.tsx @@ -2,6 +2,7 @@ import { FormEvent, useState } from "react"; import { useRouter } from "next/navigation"; +import { Hexagon } from "lucide-react"; import { useAuth } from "@/lib/auth/auth-context"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -14,6 +15,33 @@ import { CardTitle, } from "@/components/ui/card"; +function HoneycombBackdrop() { + return ( + + ); +} + export default function LoginPage() { const router = useRouter(); const { login } = useAuth(); @@ -37,11 +65,15 @@ export default function LoginPage() { } return ( -
- +
+ + - ApiCore Login - Digitale Imkereiverwaltung +
+ + ApiCore +
+ Digitale Imkereiverwaltung für Wabenhain
@@ -68,7 +100,7 @@ export default function LoginPage() { /> {error ? ( -

+

{error}

) : null} diff --git a/ApiCore-Frontend/src/app/globals.css b/ApiCore-Frontend/src/app/globals.css index c56032b..7df2050 100644 --- a/ApiCore-Frontend/src/app/globals.css +++ b/ApiCore-Frontend/src/app/globals.css @@ -7,9 +7,9 @@ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-sans); - --font-mono: var(--font-geist-mono); - --font-heading: var(--font-sans); + --font-sans: var(--font-body); + --font-mono: var(--font-data); + --font-heading: var(--font-heading); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); @@ -27,6 +27,10 @@ --color-input: var(--input); --color-border: var(--border); --color-destructive: var(--destructive); + --color-warning: var(--warning); + --color-warning-foreground: var(--warning-foreground); + --color-success: var(--success); + --color-success-foreground: var(--success-foreground); --color-accent-foreground: var(--accent-foreground); --color-accent: var(--accent); --color-muted-foreground: var(--muted-foreground); @@ -49,72 +53,80 @@ } :root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --radius: 0.625rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); + --background: oklch(0.98 0.012 85); + --foreground: oklch(0.24 0.025 55); + --card: oklch(0.995 0.006 85); + --card-foreground: oklch(0.24 0.025 55); + --popover: oklch(0.995 0.006 85); + --popover-foreground: oklch(0.24 0.025 55); + --primary: oklch(0.6 0.135 50); + --primary-foreground: oklch(0.98 0.02 85); + --secondary: oklch(0.46 0.06 145); + --secondary-foreground: oklch(0.98 0.015 100); + --muted: oklch(0.93 0.016 85); + --muted-foreground: oklch(0.5 0.03 65); + --accent: oklch(0.94 0.028 75); + --accent-foreground: oklch(0.28 0.04 50); + --destructive: oklch(0.5 0.16 30); + --border: oklch(0.88 0.02 78); + --input: oklch(0.88 0.02 78); + --ring: oklch(0.6 0.135 50); + --warning: oklch(0.68 0.14 75); + --warning-foreground: oklch(0.24 0.04 60); + --success: oklch(0.46 0.06 145); + --success-foreground: oklch(0.98 0.015 100); + --chart-1: oklch(0.6 0.135 50); + --chart-2: oklch(0.46 0.06 145); + --chart-3: oklch(0.68 0.14 75); + --chart-4: oklch(0.5 0.16 30); + --chart-5: oklch(0.55 0.05 250); + --radius: 0.75rem; + --sidebar: oklch(0.98 0.012 85); + --sidebar-foreground: oklch(0.24 0.025 55); + --sidebar-primary: oklch(0.6 0.135 50); + --sidebar-primary-foreground: oklch(0.98 0.02 85); + --sidebar-accent: oklch(0.94 0.028 75); + --sidebar-accent-foreground: oklch(0.28 0.04 50); + --sidebar-border: oklch(0.88 0.02 78); + --sidebar-ring: oklch(0.6 0.135 50); } .dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); + --background: oklch(0.2 0.018 55); + --foreground: oklch(0.94 0.02 80); + --card: oklch(0.25 0.02 55); + --card-foreground: oklch(0.94 0.02 80); + --popover: oklch(0.25 0.02 55); + --popover-foreground: oklch(0.94 0.02 80); + --primary: oklch(0.72 0.13 55); + --primary-foreground: oklch(0.2 0.02 55); + --secondary: oklch(0.62 0.075 145); + --secondary-foreground: oklch(0.15 0.02 145); + --muted: oklch(0.29 0.018 55); + --muted-foreground: oklch(0.7 0.025 75); + --accent: oklch(0.32 0.03 60); + --accent-foreground: oklch(0.94 0.02 80); + --destructive: oklch(0.62 0.16 30); + --border: oklch(1 0 0 / 12%); + --input: oklch(1 0 0 / 16%); + --ring: oklch(0.72 0.13 55); + --warning: oklch(0.74 0.13 75); + --warning-foreground: oklch(0.2 0.03 60); + --success: oklch(0.62 0.075 145); + --success-foreground: oklch(0.15 0.02 145); + --chart-1: oklch(0.72 0.13 55); + --chart-2: oklch(0.62 0.075 145); + --chart-3: oklch(0.74 0.13 75); + --chart-4: oklch(0.62 0.16 30); + --chart-5: oklch(0.65 0.06 250); + --sidebar: oklch(0.22 0.02 55); + --sidebar-foreground: oklch(0.94 0.02 80); + --sidebar-primary: oklch(0.72 0.13 55); + --sidebar-primary-foreground: oklch(0.2 0.02 55); + --sidebar-accent: oklch(0.32 0.03 60); + --sidebar-accent-foreground: oklch(0.94 0.02 80); + --sidebar-border: oklch(1 0 0 / 12%); + --sidebar-ring: oklch(0.72 0.13 55); } @layer base { @@ -127,4 +139,11 @@ html { @apply font-sans; } + h1, + h2, + h3, + h4 { + font-family: var(--font-heading); + letter-spacing: -0.01em; + } } \ No newline at end of file diff --git a/ApiCore-Frontend/src/app/layout.tsx b/ApiCore-Frontend/src/app/layout.tsx index 09615b9..3529ed8 100644 --- a/ApiCore-Frontend/src/app/layout.tsx +++ b/ApiCore-Frontend/src/app/layout.tsx @@ -1,16 +1,23 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Space_Grotesk, Inter, IBM_Plex_Mono } from "next/font/google"; import "./globals.css"; import { Providers } from "./providers"; -const geistSans = Geist({ - variable: "--font-geist-sans", +const spaceGrotesk = Space_Grotesk({ + variable: "--font-heading", + subsets: ["latin"], + weight: ["500", "600", "700"], +}); + +const inter = Inter({ + variable: "--font-body", subsets: ["latin"], }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const plexMono = IBM_Plex_Mono({ + variable: "--font-data", subsets: ["latin"], + weight: ["400", "500"], }); export const metadata: Metadata = { @@ -22,7 +29,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) { return ( {children} diff --git a/ApiCore-Frontend/src/components/ui/badge.tsx b/ApiCore-Frontend/src/components/ui/badge.tsx index b20959d..4dc9b49 100644 --- a/ApiCore-Frontend/src/components/ui/badge.tsx +++ b/ApiCore-Frontend/src/components/ui/badge.tsx @@ -14,6 +14,8 @@ const badgeVariants = cva( "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", destructive: "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + warning: + "bg-warning/15 text-warning-foreground focus-visible:ring-warning/30 [a]:hover:bg-warning/25", outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", ghost: diff --git a/ApiCore-Frontend/src/components/ui/card.tsx b/ApiCore-Frontend/src/components/ui/card.tsx index 4458dae..48b2063 100644 --- a/ApiCore-Frontend/src/components/ui/card.tsx +++ b/ApiCore-Frontend/src/components/ui/card.tsx @@ -12,7 +12,7 @@ function Card({ data-slot="card" data-size={size} className={cn( - "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl border border-border/60 bg-card py-(--card-spacing) text-sm text-card-foreground shadow-[0_1px_2px_-1px_rgba(33,20,10,0.06),0_2px_8px_-2px_rgba(33,20,10,0.06)] transition-shadow [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", className )} {...props} diff --git a/ApiCore-Frontend/src/lib/status-tone.ts b/ApiCore-Frontend/src/lib/status-tone.ts new file mode 100644 index 0000000..433d8f4 --- /dev/null +++ b/ApiCore-Frontend/src/lib/status-tone.ts @@ -0,0 +1,20 @@ +export type StatusTone = "default" | "secondary" | "warning" | "destructive" | "outline"; + +const WARNING_STATUSES = new Set([ + "WEAK", + "CONSPICUOUS", + "QUEENLESS", + "LOW_STOCK", +]); + +const DESTRUCTIVE_STATUSES = new Set(["DISSOLVED", "DECEASED", "SOLD"]); + +const NEUTRAL_STATUSES = new Set(["MERGED", "CONSUMED"]); + +export function statusTone(status: string): StatusTone { + if (WARNING_STATUSES.has(status)) return "warning"; + if (DESTRUCTIVE_STATUSES.has(status)) return "destructive"; + if (NEUTRAL_STATUSES.has(status)) return "outline"; + if (status === "ACTIVE") return "default"; + return "secondary"; +}