redesign: THERMAL warm brutalist dashboard UI

Complete visual redesign of all dashboard components with a warm
brutalist command terminal aesthetic. Features editorial section
numbering, IBM Plex typography, sharp zero-radius cards with colored
accent strips, film grain overlay, and data-glow effects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sam 2026-03-02 10:54:28 +01:00
parent f6a42c2dd2
commit e94a7706ab
12 changed files with 641 additions and 548 deletions

View file

@ -6,36 +6,22 @@ interface WeatherCardProps {
accent: "cyan" | "amber";
}
const accentMap = {
cyan: {
gradient: "from-cyan-500/10 to-cyan-900/5",
text: "text-cyan-400",
border: "border-cyan-500/20",
badge: "bg-cyan-500/15 text-cyan-300",
statIcon: "text-cyan-400/70",
ring: "ring-cyan-500/10",
},
amber: {
gradient: "from-amber-500/10 to-amber-900/5",
text: "text-amber-400",
border: "border-amber-500/20",
badge: "bg-amber-500/15 text-amber-300",
statIcon: "text-amber-400/70",
ring: "ring-amber-500/10",
},
const ACCENT = {
cyan: { strip: "gold", text: "text-gold", glow: "data-glow-gold", tag: "border-gold/30 text-gold bg-gold/5" },
amber: { strip: "mint", text: "text-mint", glow: "data-glow-mint", tag: "border-mint/30 text-mint bg-mint/5" },
} as const;
export default function WeatherCard({ data, accent }: WeatherCardProps) {
const a = accentMap[accent];
const a = ACCENT[accent];
if (data.error) {
return (
<div className="glass-card p-5 animate-fade-in">
<div className="flex items-center gap-3 text-red-400">
<div className="deck-card p-5 animate-fade-in">
<div className="flex items-center gap-3 text-cherry">
<CloudOff className="w-5 h-5" />
<div>
<p className="text-sm font-medium">Wetter nicht verfuegbar</p>
<p className="text-xs text-slate-500 mt-0.5">{data.location || "Unbekannt"}</p>
<p className="text-sm font-medium">Wetter nicht verfügbar</p>
<p className="text-xs text-base-600 mt-0.5">{data.location || "Unbekannt"}</p>
</div>
</div>
</div>
@ -43,73 +29,66 @@ export default function WeatherCard({ data, accent }: WeatherCardProps) {
}
return (
<div className={`glass-card p-5 animate-fade-in bg-gradient-to-br ${a.gradient}`}>
{/* Header: location badge */}
<div className="flex items-center justify-between mb-4">
<span className={`badge ${a.badge}`}>{data.location}</span>
<div className="deck-card corner-marks p-6 animate-fade-in" data-accent={a.strip}>
{/* Location tag */}
<div className="mb-5">
<span className={`tag ${a.tag}`}>{data.location}</span>
</div>
{/* Main row: icon + temp */}
<div className="flex items-center justify-between mb-4">
{/* Temperature hero */}
<div className="flex items-start justify-between mb-6">
<div>
<div className="flex items-baseline gap-1">
<span
className="text-5xl font-extrabold text-white tracking-tighter"
style={{ fontFamily: "'JetBrains Mono', monospace" }}
>
<div className="flex items-baseline gap-0.5">
<span className={`text-5xl font-mono font-bold ${a.text} ${a.glow} tracking-tighter`}>
{Math.round(data.temp)}
</span>
<span className="text-2xl font-light text-slate-400">&deg;</span>
<span className="text-xl font-light text-base-500">°</span>
</div>
<p className="text-sm text-slate-400 mt-1 capitalize">{data.description}</p>
<p className="text-sm text-base-600 mt-1 capitalize font-light">{data.description}</p>
</div>
<span className="text-5xl select-none" role="img" aria-label="weather">
<span className="text-5xl select-none opacity-80" role="img" aria-label="weather">
{data.icon}
</span>
</div>
{/* Stat grid */}
<div className="grid grid-cols-3 gap-3">
<StatItem
icon={<Thermometer className={`w-3.5 h-3.5 ${a.statIcon}`} />}
label="Gefuehlt"
value={`${Math.round(data.feels_like)}\u00B0`}
{/* Stats row */}
<div className="grid grid-cols-3 gap-px bg-base-300">
<StatCell
icon={<Thermometer className="w-3 h-3 text-base-500" />}
label="Gefühlt"
value={`${Math.round(data.feels_like)}°`}
/>
<StatItem
icon={<Droplets className={`w-3.5 h-3.5 ${a.statIcon}`} />}
<StatCell
icon={<Droplets className="w-3 h-3 text-base-500" />}
label="Feuchte"
value={`${data.humidity}%`}
/>
<StatItem
icon={<Wind className={`w-3.5 h-3.5 ${a.statIcon}`} />}
<StatCell
icon={<Wind className="w-3 h-3 text-base-500" />}
label="Wind"
value={`${Math.round(data.wind_kmh)} km/h`}
value={`${Math.round(data.wind_kmh)}`}
unit="km/h"
/>
</div>
</div>
);
}
function StatItem({
icon,
label,
value,
}: {
function StatCell({ icon, label, value, unit }: {
icon: React.ReactNode;
label: string;
value: string;
unit?: string;
}) {
return (
<div className="flex flex-col items-center gap-1.5 rounded-xl bg-white/[0.03] border border-white/[0.04] px-2 py-2.5">
<div className="flex flex-col items-center gap-1.5 py-3 bg-base-50">
{icon}
<span
className="text-sm font-semibold text-white"
style={{ fontFamily: "'JetBrains Mono', monospace" }}
>
{value}
</span>
<span className="text-[10px] uppercase tracking-wider text-slate-500">{label}</span>
<div className="flex items-baseline gap-0.5">
<span className="text-sm data-value text-base-900">{value}</span>
{unit && <span className="text-[9px] text-base-500">{unit}</span>}
</div>
<span className="data-label">{label}</span>
</div>
);
}