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:
parent
f6a42c2dd2
commit
e94a7706ab
12 changed files with 641 additions and 548 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useMemo } from "react";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import type { NewsResponse, NewsArticle } from "../api";
|
||||
|
||||
interface NewsGridProps {
|
||||
|
|
@ -14,17 +14,16 @@ const CATEGORIES = [
|
|||
{ key: "allgemein", label: "Allgemein" },
|
||||
] as const;
|
||||
|
||||
/** Map source names to badge colours. */
|
||||
const SOURCE_COLORS: Record<string, string> = {
|
||||
"heise": "bg-orange-500/20 text-orange-300",
|
||||
"golem": "bg-blue-500/20 text-blue-300",
|
||||
"spiegel": "bg-red-500/20 text-red-300",
|
||||
"tagesschau": "bg-sky-500/20 text-sky-300",
|
||||
"zeit": "bg-slate-500/20 text-slate-300",
|
||||
"faz": "bg-emerald-500/20 text-emerald-300",
|
||||
"welt": "bg-indigo-500/20 text-indigo-300",
|
||||
"t3n": "bg-purple-500/20 text-purple-300",
|
||||
"default": "bg-amber-500/15 text-amber-300",
|
||||
heise: "border-gold/40 text-gold bg-gold/5",
|
||||
golem: "border-azure/40 text-azure bg-azure/5",
|
||||
spiegel: "border-cherry/40 text-cherry bg-cherry/5",
|
||||
tagesschau: "border-azure/40 text-azure bg-azure/5",
|
||||
zeit: "border-base-400 text-base-700 bg-base-200",
|
||||
faz: "border-mint/40 text-mint bg-mint/5",
|
||||
welt: "border-iris/40 text-iris bg-iris/5",
|
||||
t3n: "border-iris/40 text-iris bg-iris/5",
|
||||
default: "border-gold/30 text-gold-muted bg-gold/5",
|
||||
};
|
||||
|
||||
function sourceColor(source: string): string {
|
||||
|
|
@ -35,7 +34,6 @@ function sourceColor(source: string): string {
|
|||
return SOURCE_COLORS.default;
|
||||
}
|
||||
|
||||
/** Return a German relative time string like "vor 2 Stunden". */
|
||||
function relativeTime(isoDate: string): string {
|
||||
try {
|
||||
const date = new Date(isoDate);
|
||||
|
|
@ -72,35 +70,34 @@ export default function NewsGrid({ data }: NewsGridProps) {
|
|||
|
||||
return (
|
||||
<div className="animate-fade-in">
|
||||
{/* Header + category tabs */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-4">
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wider text-slate-400">
|
||||
Nachrichten
|
||||
<span className="ml-2 text-xs font-normal text-slate-600">
|
||||
{filteredArticles.length}
|
||||
</span>
|
||||
</h2>
|
||||
{/* Section header */}
|
||||
<div className="section-label">
|
||||
<span className="section-number">04</span>
|
||||
<h2 className="section-title">Nachrichten</h2>
|
||||
<span className="section-rule" />
|
||||
<span className="text-xs font-mono text-base-500">{filteredArticles.length}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
{CATEGORIES.map((cat) => (
|
||||
<button
|
||||
key={cat.key}
|
||||
onClick={() => setActiveCategory(cat.key)}
|
||||
className={`category-tab ${activeCategory === cat.key ? "active" : ""}`}
|
||||
>
|
||||
{cat.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* Category tabs */}
|
||||
<div className="flex gap-1 flex-wrap mb-5">
|
||||
{CATEGORIES.map((cat) => (
|
||||
<button
|
||||
key={cat.key}
|
||||
onClick={() => setActiveCategory(cat.key)}
|
||||
className={`tab-btn ${activeCategory === cat.key ? "active" : ""}`}
|
||||
>
|
||||
{cat.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Articles grid */}
|
||||
{filteredArticles.length === 0 ? (
|
||||
<div className="glass-card p-8 text-center text-slate-500 text-sm">
|
||||
<div className="deck-card p-8 text-center text-base-500 text-sm">
|
||||
Keine Artikel in dieser Kategorie.
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-px bg-base-300">
|
||||
{filteredArticles.map((article) => (
|
||||
<ArticleCard key={article.id} article={article} />
|
||||
))}
|
||||
|
|
@ -116,26 +113,26 @@ function ArticleCard({ article }: { article: NewsArticle }) {
|
|||
href={article.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="glass-card-hover group block p-4 cursor-pointer"
|
||||
className="group block bg-base-50 p-4 hover:bg-base-100 transition-colors relative"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2.5">
|
||||
<span className={`badge ${sourceColor(article.source)}`}>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className={`tag ${sourceColor(article.source)}`}>
|
||||
{article.source}
|
||||
</span>
|
||||
<ExternalLink className="w-3 h-3 text-slate-600 opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
<ArrowUpRight className="w-3 h-3 text-base-400 opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</div>
|
||||
|
||||
<h3 className="text-sm font-medium text-slate-200 leading-snug line-clamp-2 group-hover:text-white transition-colors">
|
||||
<h3 className="text-sm font-medium text-base-800 leading-snug line-clamp-2 group-hover:text-base-900 transition-colors">
|
||||
{article.title}
|
||||
</h3>
|
||||
|
||||
<div className="flex items-center gap-2 mt-3">
|
||||
<div className="flex items-center gap-3 mt-3 pt-3 border-t border-base-200">
|
||||
{article.category && (
|
||||
<span className="text-[10px] text-slate-600 uppercase tracking-wider">
|
||||
<span className="text-[9px] font-mono text-base-500 uppercase tracking-wider">
|
||||
{article.category}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[10px] text-slate-600 ml-auto">
|
||||
<span className="text-[9px] font-mono text-base-500 ml-auto">
|
||||
{relativeTime(article.published_at)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue