design v5: Übersicht als Vorschau, Grundbausteine im neuen Look

Tiefblauer Grund mit Lichtflecken in den Bereichsfarben, Glasflächen mit
Lichtkante (flaeche), ruhigere Schrift (Überschriften normal geschrieben,
Großbuchstaben nur als kleines Etikett) und dezente Bewegung.

- Startseite neu: Geräte-Ring mit Gesamtlage, drei Eckwerte, Instrumente
  (8 Messringe), Bereichskarten mit Kacheln je Modell, Baustein und Gerät
  (Symbol, Zustandspunkt, Update direkt an der Kachel), Speicher/NAS,
  Zeitleisten „Als Nächstes“ und „Zuletzt eingespielt“.
- Panel, Schildchen, Kennzahl, Verlaufslinie, Seitenleiste und Kopf im
  neuen Look — wirkt schon auf allen Seiten.
- Symbole je Gerät (lib/geraetebild): Farbe des Bereichs, Zustände nur als
  Punkt. Vorbilder: Heimdall, Dashy, Homepage, umbrelOS, Pulse.

Noch nicht ausgerollt (kein dist): erst nach dem Ja des Users.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-25 14:22:39 +02:00
co-authored by Claude Opus 5.5
parent b816fe6d32
commit 948e55e4f2
13 changed files with 1006 additions and 298 deletions
+15 -8
View File
@@ -3,18 +3,25 @@ import { cn } from "cn"
export type ChipArt = "gruen" | "bernstein" | "cyan" | "rot" | "grau"
// v5: zarte Tönung mit feinem Innenrand und einem Punkt vor dem Wort — Zustand als Farbe, Punkt und Wort.
const ART: Record<ChipArt, string> = {
gruen: "border-gruen-rand bg-gruen-grund text-gruen",
bernstein: "border-bernstein/60 bg-bernstein-grund text-bernstein",
cyan: "border-cyan-rand bg-cyan-grund text-cyan",
rot: "border-rot-rand bg-rot-grund text-rot-text",
grau: "border-linie bg-erhaben text-text-2",
gruen: "bg-gruen/10 text-gruen ring-gruen/30",
bernstein: "bg-bernstein/10 text-bernstein ring-bernstein/35",
cyan: "bg-cyan/10 text-cyan-text ring-cyan/30",
rot: "bg-rot/12 text-rot-text ring-rot/40",
grau: "bg-white/[0.04] text-text-2 ring-white/12",
}
/** Kleines Zustandsschild („GELADEN“, „PASST“, „FEHLGESCHLAGEN“). */
export function Chip({ art = "grau", children }: { art?: ChipArt; children: ReactNode }) {
return (
<span className={cn("schild inline-flex h-7 items-center rounded-full border px-2.5 text-xs whitespace-nowrap", ART[art])}>
<span
className={cn(
"etikett inline-flex h-6 items-center gap-1.5 rounded-full px-2.5 text-[11px] tracking-[0.08em] whitespace-nowrap ring-1 ring-inset",
ART[art],
)}
>
<span aria-hidden className="size-1.5 shrink-0 rounded-full bg-current opacity-90" />
{children}
</span>
)
@@ -26,8 +33,8 @@ export function Zustandsfeld({ fehler, text }: { fehler?: boolean; text: string
<div
role={fehler ? "alert" : "status"}
className={cn(
"rounded-2xl border px-5 py-8 text-center text-[15px]",
fehler ? "border-rot-rand bg-rot-grund text-rot-text" : "border-linie bg-panel text-text-2",
"rounded-2xl px-5 py-8 text-center text-[15px]",
fehler ? "border border-rot-rand bg-rot-grund text-rot-text" : "flaeche text-text-2",
)}
>
{text}
@@ -0,0 +1,48 @@
import { cn } from "cn"
import { type Geraetebild, PUNKT, type Punktzustand } from "@/lib/geraetebild"
// Kachel in der Farbe des Bereichs; Zustände trägt nur der Punkt (grün/bernstein/rot/cyan) — nie das Symbol.
const BEREICH = {
box: "from-box/30 to-box/[0.05] ring-box/35 text-box-text",
homelab: "from-lab/30 to-lab/[0.05] ring-lab/35 text-lab-text",
} as const
const LEUCHTEN = {
box: "shadow-[0_0_28px_-6px_var(--box)]",
homelab: "shadow-[0_0_28px_-6px_var(--lab)]",
} as const
const GROESSE = {
sm: { kachel: "size-8 rounded-[10px]", icon: "size-4", punkt: "size-2.5" },
md: { kachel: "size-10 rounded-xl", icon: "size-5", punkt: "size-2.5" },
lg: { kachel: "size-12 rounded-2xl", icon: "size-6", punkt: "size-3" },
} as const
/**
* Das Symbol eines Geräts oder Dienstes als kleine Kachel, optional mit Zustandspunkt unten rechts. Rein
* schmückend (aria-hidden) — Name und Zustand stehen daneben als Text.
*/
export function Geraetesymbol({ bild, groesse = "md", punkt, leuchtet = false, className }: {
bild: Geraetebild
groesse?: keyof typeof GROESSE
punkt?: Punktzustand | null
/** Weicher Schein in der Bereichsfarbe — für die großen Kacheln im Kopf der Bereichskarten. */
leuchtet?: boolean
className?: string
}) {
const g = GROESSE[groesse]
return (
<span
aria-hidden
className={cn(
"relative inline-flex shrink-0 items-center justify-center bg-gradient-to-br ring-1 ring-inset",
BEREICH[bild.bereich], g.kachel, leuchtet && LEUCHTEN[bild.bereich], className,
)}
>
<bild.Icon className={g.icon} strokeWidth={1.75} />
{punkt && (
<span className={cn("absolute -right-0.5 -bottom-0.5 rounded-full ring-2 ring-[#0b0f16]", g.punkt, PUNKT[punkt])} />
)}
</span>
)
}
+14 -3
View File
@@ -1,29 +1,40 @@
import type { ReactNode } from "react"
import type { LucideIcon } from "lucide-react"
import { cn } from "cn"
/** Ein Feld des Instrumentenbretts: dunkle Platte, schmale Beschriftung oben links. */
/**
* Ein Feld des Instrumentenbretts: Glasfläche mit Lichtkante (Design v5), Überschrift in normaler Schreibung,
* auf Wunsch mit Symbol davor.
*/
export function Panel({
titel,
rechts,
children,
className,
id,
symbol: Symbol,
}: {
titel?: string
rechts?: ReactNode
children: ReactNode
className?: string
id?: string
symbol?: LucideIcon
}) {
return (
<section
id={id}
aria-label={titel}
className={cn("flex min-w-0 flex-col gap-4 rounded-2xl border border-linie bg-panel p-5 sm:p-6", className)}
className={cn("flaeche flex min-w-0 flex-col gap-4 rounded-2xl p-5 sm:p-6", className)}
>
{(titel || rechts) && (
<div className="flex flex-wrap items-center justify-between gap-3">
{titel && <h2 className="schild text-lg font-bold tracking-[0.22em] text-foreground">{titel}</h2>}
{titel && (
<h2 className="flex items-center gap-2.5 text-[17px] leading-tight font-semibold tracking-[0.005em] text-foreground">
{Symbol && <Symbol className="size-[18px] text-text-3" aria-hidden />}
{titel}
</h2>
)}
{rechts}
</div>
)}