feat: plakat redesign for subpages (projects, books, legal, privacy)
- Shared SubpageShell (HeaderClient paper strip + ink footer) and SubpageIntro, btn/ghost/card helpers in app/_ui/subpage.tsx - Projects list: poster cards with hard ink shadows, filter pills, ink search field; no fake 800ms skeleton and no framer-motion opacity:0 in SSR - Project detail: poster hero, .plakat-prose markdown, links/stack aside, back link works without JS - Books: server-side fetch (visible without JS), cover + rating pills - Legal notice / privacy: moved to app/_ui/*Client.tsx, root routes removed (middleware always redirects to /[locale]), consent reset via useConsent - ProjectThumbnail rebuilt on Plakat tokens - Old Header.tsx + test removed; brown/cream/sand palettes and unused glass-panel/admin-glass(-light)/liquid-bg-blob/float CSS dropped (liquid palette, glass-card, admin-glass-card stay for admin + 404) - .plakat-prose replaces the missing typography plugin Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,73 +24,30 @@ interface ProjectThumbnailProps {
|
||||
size?: "card" | "hero";
|
||||
}
|
||||
|
||||
// Plakatflächen statt Pastellverläufe: jede Kategorie bekommt eine feste Ton-Kombination
|
||||
// aus den Plakat-Tokens. Tinte/Papier tauschen im Dark Mode von selbst die Rollen.
|
||||
const tones = {
|
||||
signal: "bg-signal text-ink",
|
||||
ink: "bg-ink text-paper",
|
||||
paper: "bg-paper-2 text-ink",
|
||||
ok: "bg-ok text-ink",
|
||||
} as const;
|
||||
|
||||
const categoryThemes: Record<
|
||||
string,
|
||||
{
|
||||
icon: LucideIcon;
|
||||
gradient: string;
|
||||
darkGradient: string;
|
||||
iconColor: string;
|
||||
darkIconColor: string;
|
||||
tone: keyof typeof tones;
|
||||
pattern: "dots" | "grid" | "diagonal" | "circuit" | "waves" | "terminal";
|
||||
}
|
||||
> = {
|
||||
"Web Development": {
|
||||
icon: Code,
|
||||
gradient: "from-liquid-sky/20 via-liquid-blue/10 to-liquid-lavender/20",
|
||||
darkGradient: "dark:from-liquid-sky/10 dark:via-liquid-blue/5 dark:to-liquid-lavender/10",
|
||||
iconColor: "text-blue-500",
|
||||
darkIconColor: "dark:text-blue-400",
|
||||
pattern: "circuit",
|
||||
},
|
||||
"Mobile Development": {
|
||||
icon: Smartphone,
|
||||
gradient: "from-liquid-mint/20 via-liquid-teal/10 to-liquid-sky/20",
|
||||
darkGradient: "dark:from-liquid-mint/10 dark:via-liquid-teal/5 dark:to-liquid-sky/10",
|
||||
iconColor: "text-emerald-500",
|
||||
darkIconColor: "dark:text-emerald-400",
|
||||
pattern: "waves",
|
||||
},
|
||||
"Web Application": {
|
||||
icon: Globe,
|
||||
gradient: "from-liquid-lavender/20 via-liquid-purple/10 to-liquid-pink/20",
|
||||
darkGradient: "dark:from-liquid-lavender/10 dark:via-liquid-purple/5 dark:to-liquid-pink/10",
|
||||
iconColor: "text-violet-500",
|
||||
darkIconColor: "dark:text-violet-400",
|
||||
pattern: "dots",
|
||||
},
|
||||
"Backend Development": {
|
||||
icon: Cpu,
|
||||
gradient: "from-liquid-amber/20 via-liquid-yellow/10 to-liquid-peach/20",
|
||||
darkGradient: "dark:from-liquid-amber/10 dark:via-liquid-yellow/5 dark:to-liquid-peach/10",
|
||||
iconColor: "text-amber-500",
|
||||
darkIconColor: "dark:text-amber-400",
|
||||
pattern: "grid",
|
||||
},
|
||||
"Full-Stack Development": {
|
||||
icon: Boxes,
|
||||
gradient: "from-liquid-teal/20 via-liquid-mint/10 to-liquid-lavender/20",
|
||||
darkGradient: "dark:from-liquid-teal/10 dark:via-liquid-mint/5 dark:to-liquid-lavender/10",
|
||||
iconColor: "text-teal-500",
|
||||
darkIconColor: "dark:text-teal-400",
|
||||
pattern: "grid",
|
||||
},
|
||||
DevOps: {
|
||||
icon: Shield,
|
||||
gradient: "from-liquid-coral/20 via-liquid-rose/10 to-liquid-peach/20",
|
||||
darkGradient: "dark:from-liquid-coral/10 dark:via-liquid-rose/5 dark:to-liquid-peach/10",
|
||||
iconColor: "text-red-500",
|
||||
darkIconColor: "dark:text-red-400",
|
||||
pattern: "diagonal",
|
||||
},
|
||||
default: {
|
||||
icon: Wrench,
|
||||
gradient: "from-liquid-peach/20 via-liquid-rose/10 to-liquid-lavender/20",
|
||||
darkGradient: "dark:from-liquid-peach/10 dark:via-liquid-rose/5 dark:to-liquid-lavender/10",
|
||||
iconColor: "text-stone-400",
|
||||
darkIconColor: "dark:text-stone-500",
|
||||
pattern: "dots",
|
||||
},
|
||||
"Web Development": { icon: Code, tone: "signal", pattern: "circuit" },
|
||||
"Mobile Development": { icon: Smartphone, tone: "ink", pattern: "waves" },
|
||||
"Web Application": { icon: Globe, tone: "paper", pattern: "dots" },
|
||||
"Backend Development": { icon: Cpu, tone: "ink", pattern: "grid" },
|
||||
"Full-Stack Development": { icon: Boxes, tone: "signal", pattern: "grid" },
|
||||
DevOps: { icon: Shield, tone: "ok", pattern: "diagonal" },
|
||||
default: { icon: Wrench, tone: "paper", pattern: "dots" },
|
||||
};
|
||||
|
||||
const slugIcons: Record<string, LucideIcon> = {
|
||||
@@ -106,7 +63,7 @@ const slugIcons: Record<string, LucideIcon> = {
|
||||
function PatternOverlay({ pattern, id }: { pattern: string; id: string }) {
|
||||
const patterns: Record<string, React.ReactNode> = {
|
||||
dots: (
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.07] dark:opacity-[0.05]" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.14]" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id={`pat-dots-${id}`} x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<circle cx="2" cy="2" r="1.5" fill="currentColor" />
|
||||
@@ -116,7 +73,7 @@ function PatternOverlay({ pattern, id }: { pattern: string; id: string }) {
|
||||
</svg>
|
||||
),
|
||||
grid: (
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.06] dark:opacity-[0.04]" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.14]" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id={`pat-grid-${id}`} x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="currentColor" strokeWidth="1" />
|
||||
@@ -126,7 +83,7 @@ function PatternOverlay({ pattern, id }: { pattern: string; id: string }) {
|
||||
</svg>
|
||||
),
|
||||
diagonal: (
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.06] dark:opacity-[0.04]" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.14]" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id={`pat-diag-${id}`} x="0" y="0" width="24" height="24" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="24" x2="24" y2="0" stroke="currentColor" strokeWidth="1" />
|
||||
@@ -136,7 +93,7 @@ function PatternOverlay({ pattern, id }: { pattern: string; id: string }) {
|
||||
</svg>
|
||||
),
|
||||
circuit: (
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.07] dark:opacity-[0.05]" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.14]" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id={`pat-circ-${id}`} x="0" y="0" width="60" height="60" patternUnits="userSpaceOnUse">
|
||||
<path d="M0 30h20m20 0h20M30 0v20m0 20v20" stroke="currentColor" strokeWidth="0.8" fill="none" />
|
||||
@@ -151,7 +108,7 @@ function PatternOverlay({ pattern, id }: { pattern: string; id: string }) {
|
||||
</svg>
|
||||
),
|
||||
waves: (
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.06] dark:opacity-[0.04]" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.14]" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id={`pat-wave-${id}`} x="0" y="0" width="100" height="20" patternUnits="userSpaceOnUse">
|
||||
<path d="M0 10 Q25 0 50 10 T100 10" fill="none" stroke="currentColor" strokeWidth="1" />
|
||||
@@ -161,7 +118,7 @@ function PatternOverlay({ pattern, id }: { pattern: string; id: string }) {
|
||||
</svg>
|
||||
),
|
||||
terminal: (
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.08] dark:opacity-[0.06]" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg className="absolute inset-0 w-full h-full opacity-[0.14]" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id={`pat-term-${id}`} x="0" y="0" width="200" height="30" patternUnits="userSpaceOnUse">
|
||||
<text x="4" y="18" fontFamily="monospace" fontSize="10" fill="currentColor">$_</text>
|
||||
@@ -197,40 +154,28 @@ export default function ProjectThumbnail({
|
||||
|
||||
const Icon = theme.icon;
|
||||
const isHero = size === "hero";
|
||||
const displayTags = tags?.slice(0, 3) ?? [];
|
||||
const displayTags = tags?.slice(0, isHero ? 5 : 3) ?? [];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 flex items-center justify-center bg-gradient-to-br ${theme.gradient} ${theme.darkGradient}`}
|
||||
>
|
||||
<div className={`absolute inset-0 flex flex-col justify-between ${tones[theme.tone]} ${isHero ? "p-5 sm:p-8" : "p-4 sm:p-5"}`}>
|
||||
<PatternOverlay pattern={theme.pattern} id={uniqueId} />
|
||||
|
||||
<div className="relative z-10 flex flex-col items-center gap-3 sm:gap-4">
|
||||
<div
|
||||
className={`flex items-center justify-center rounded-2xl bg-white/60 dark:bg-white/10 backdrop-blur-sm border border-white/40 dark:border-white/10 ${theme.iconColor} ${theme.darkIconColor} ${isHero ? "w-20 h-20 sm:w-28 sm:h-28" : "w-14 h-14 sm:w-20 sm:h-20"}`}
|
||||
>
|
||||
<Icon className={isHero ? "w-10 h-10 sm:w-14 sm:h-14" : "w-7 h-7 sm:w-10 sm:h-10"} strokeWidth={1.5} />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex items-start justify-between gap-3">
|
||||
<span className="font-mono text-xs leading-snug sm:text-[13px]">{displayTags.join(" · ")}</span>
|
||||
<span
|
||||
className={`font-black tracking-tighter uppercase ${isHero ? "text-2xl sm:text-4xl md:text-5xl" : "text-sm sm:text-lg"} text-stone-400/80 dark:text-stone-500/80`}
|
||||
className={`flex shrink-0 items-center justify-center rounded-full shadow-[inset_0_0_0_2px_currentColor] ${isHero ? "h-14 w-14 sm:h-16 sm:w-16" : "h-10 w-10"}`}
|
||||
>
|
||||
{title}
|
||||
<Icon className={isHero ? "h-7 w-7 sm:h-8 sm:w-8" : "h-5 w-5"} strokeWidth={1.75} />
|
||||
</span>
|
||||
|
||||
{displayTags.length > 0 && (
|
||||
<div className={`flex flex-wrap justify-center gap-1.5 sm:gap-2 ${isHero ? "max-w-md" : "max-w-[200px]"}`}>
|
||||
{displayTags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className={`px-2 py-0.5 rounded-full bg-white/50 dark:bg-white/5 backdrop-blur-sm border border-white/30 dark:border-white/10 text-stone-500 dark:text-stone-400 font-medium ${isHero ? "text-xs sm:text-sm" : "text-[9px] sm:text-[10px]"}`}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<span
|
||||
className={`relative z-10 font-display font-extrabold leading-[0.86] tracking-[-0.045em] [overflow-wrap:anywhere] [font-variation-settings:'wdth'_78] ${
|
||||
isHero ? "text-[clamp(2.6rem,8vw,7rem)]" : "text-[clamp(1.8rem,4vw,2.8rem)]"
|
||||
}`}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user