feat(design): comprehensive redesign — top-tier portfolio UX
Größeres Redesign, das die Seite spürbar besser macht. Behält das
Magenta/Grün-Dark-Thing, poliert aber alles drumherum.
Neue Komponenten:
- hero-banner: full-bleed Hero mit AI-generiertem Hintergrund (grok-imagine-image,
1280×720, Magenta/Grün-Akzente), Terminal-Boot-Animation mit Tipp-Effekt,
Rotating-Role-Animation, animiertes Grid-Overlay.
- command-palette: �K / Ctrl+K öffnet ein durchsuchbares Modal mit Navigation,
Projekt-Suche, Aktionen (Gitea/w-make.com/Admin) und Easter Eggs. Tastatur
↑↓/Enter/Esc, gruppiert nach Typ, mit Vorschau-Hints.
- custom-cursor: Canvas-basierter Cursor mit Trail für Desktop, 14 Punkte,
Farbwechsel Magenta↔Grün, Hover-State für interaktive Elemente. Touch-
Devices behalten den Default-Cursor.
- scroll-progress: Top-Bar mit Magenta→Grün-Gradient.
- tilt-card: 3D-Hover mit Mouse-Tracking (CSS-Variablen --rx/--ry/--mx/--my).
- stack-icon: 13 handgezeichnete inline SVGs (TypeScript, Node, Python, Rust,
SQLite, Tailwind, React, Docker, Gitea, Next, Nix, Hermes, Shell, Self-Host).
Kein CDN, kein Hydration, currentColor-aware.
- project-filter: tag-basierter Filter für Projekt-Liste, Status-Tone-Tags
(live/warm/muted) plus Kategorie-Tags, useTransition für sanfte Updates.
Pages-Update:
- Home (/): neuer Hero-Banner, Featured-Grid mit Tilt-Cards und KI-generierten
Thumbnails (hermes.jpg, casino-bot.jpg, polymarket-trader.jpg), darunter
filterbare Projekt-Liste, Bio + Links Sidebar.
- Projects: filterbar nach Kategorie und Status.
- Project-Detail: Hero-Image (für 3 Projekte), Stack-Pills mit SVG-Icons,
bessere Sektionierung (~/stack, ~/notes).
- About: Stack-Visualisierung mit 13 Einträgen in einem Grid.
- OpenGraph-Image pro Projekt (Next.js native, 1200×630): eldov-Logo,
Projekt-Slug in Magenta, Status-Badge, Kategorie-Badge.
Build-Hash-Support:
- NEXT_PUBLIC_BUILD_HASH + NEXT_PUBLIC_BUILD_DATE werden via Docker ARG in
die Footer geschrieben ("build abc1234 · 2026-08-31"), deploy-vps.sh
ermittelt Commit-Hash automatisch aus git und gibt ihn weiter.
Theme v2:
- Tiefere Palette (--bg #0a0a10, --bg-deep #050508), --fg-dim für Hierarchie.
- Neue Komponenten-Klassen: tilt-card, stack-pill, tag, hero-bg, hero-banner,
scroll-progress, cursor-blink, type-in, glow-text.
- ASCII-Box-Hover-Effekt mit Akzent-Border.
Public-Bilder unter public/img/{hero,projects}/ — KI-generiert via xAI
grok-imagine-image (Token aus .hermes-state/credentials/xai.json), alle
im Marken-Stil (Magenta/Grün/dunkel). 4 Bilder, ~960 KB total.
Sonstiges:
- Header mit ⌘K-Hint, sticky+blur.
- Footer mit Build-Hash, externen Links, Datenschutz-Badge.
- About-Page mit Stack-Grid.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const BOOT_LINES = [
|
||||
"> initializing eldov.win...",
|
||||
"> loading context bus...",
|
||||
"> scanning fleet...",
|
||||
"> ready.",
|
||||
];
|
||||
|
||||
const ROTATING = [
|
||||
"Self-Hoster",
|
||||
"Bot-Bauer",
|
||||
"Multi-Agent-Fleet-Owner",
|
||||
"Open-Source-Themer",
|
||||
"Casino-Bot-Dev",
|
||||
"Prediction-Market-Trader",
|
||||
];
|
||||
|
||||
export function HeroBanner({ dict, heroImage }: { dict: { kicker: string; title: string; lede: string; cta: { title: string; body: string } }; heroImage: string }) {
|
||||
const [lineIdx, setLineIdx] = useState(0);
|
||||
const [typed, setTyped] = useState("");
|
||||
const [done, setDone] = useState(false);
|
||||
const [rotIdx, setRotIdx] = useState(0);
|
||||
|
||||
// Boot-Animation: zeilenweise tippen, 280ms pro Zeile.
|
||||
useEffect(() => {
|
||||
if (lineIdx >= BOOT_LINES.length) { setDone(true); return; }
|
||||
const line = BOOT_LINES[lineIdx];
|
||||
let i = 0;
|
||||
const id = setInterval(() => {
|
||||
i += 1;
|
||||
setTyped(line.slice(0, i));
|
||||
if (i >= line.length) {
|
||||
clearInterval(id);
|
||||
setTimeout(() => {
|
||||
setLineIdx((v) => v + 1);
|
||||
setTyped("");
|
||||
}, 380);
|
||||
}
|
||||
}, 24);
|
||||
return () => clearInterval(id);
|
||||
}, [lineIdx]);
|
||||
|
||||
// Rotating titles (für die Unterzeile), 2.5s pro Wort.
|
||||
useEffect(() => {
|
||||
if (!done) return;
|
||||
const id = setInterval(() => setRotIdx((v) => (v + 1) % ROTATING.length), 2500);
|
||||
return () => clearInterval(id);
|
||||
}, [done]);
|
||||
|
||||
return (
|
||||
<section className="hero-banner">
|
||||
<div
|
||||
className="hero-bg"
|
||||
style={{
|
||||
["--hero-bg" as string]: `url('${heroImage}')`,
|
||||
}}
|
||||
/>
|
||||
{/* Grid-Linien (sehr subtil) */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute inset-0 opacity-[0.04]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(var(--accent) 1px, transparent 1px), linear-gradient(90deg, var(--accent) 1px, transparent 1px)",
|
||||
backgroundSize: "64px 64px",
|
||||
maskImage: "linear-gradient(to bottom, black, transparent 80%)",
|
||||
WebkitMaskImage: "linear-gradient(to bottom, black, transparent 80%)",
|
||||
}}
|
||||
/>
|
||||
<div className="relative mx-auto max-w-[var(--max-w)] px-6 py-24 md:py-36 lg:py-48">
|
||||
<div className="grid gap-12 lg:grid-cols-[1.4fr_1fr] lg:items-center">
|
||||
<div>
|
||||
<p className="mb-4 flex items-center gap-2 font-mono text-xs uppercase tracking-[0.18em] text-accent">
|
||||
<span className="inline-block h-1 w-6 bg-accent" />
|
||||
{dict.kicker}
|
||||
</p>
|
||||
<h1
|
||||
className="headline-glitch glow-text mb-6 text-5xl font-semibold leading-[1.05] tracking-tight md:text-7xl lg:text-8xl"
|
||||
data-text={dict.title}
|
||||
>
|
||||
{dict.title}
|
||||
</h1>
|
||||
<p className="mb-8 max-w-xl text-lg leading-relaxed text-[var(--fg-dim)] md:text-xl">
|
||||
{dict.lede}
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<a
|
||||
href="#featured"
|
||||
className="group inline-flex items-center gap-2 border border-accent bg-accent px-5 py-2.5 font-mono text-sm uppercase tracking-wider text-[var(--bg)] transition-colors hover:bg-transparent hover:text-accent"
|
||||
data-cursor="hover"
|
||||
>
|
||||
<span>{dict.cta.title}</span>
|
||||
<span aria-hidden className="transition-transform group-hover:translate-x-0.5">→</span>
|
||||
</a>
|
||||
<span className="font-mono text-xs text-muted">
|
||||
<kbd className="rounded border border-hairline px-1.5 py-0.5">⌘</kbd>
|
||||
<kbd className="ml-0.5 rounded border border-hairline px-1.5 py-0.5">K</kbd>
|
||||
<span className="ml-2">search</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden lg:block">
|
||||
<BootSequence typed={typed} lineIdx={lineIdx} done={done} rotIdx={rotIdx} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Subtle Bottom-Fade zu bg */}
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-24 bg-gradient-to-b from-transparent to-[var(--bg)]" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function BootSequence({ typed, lineIdx, done, rotIdx }: { typed: string; lineIdx: number; done: boolean; rotIdx: number }) {
|
||||
return (
|
||||
<div
|
||||
aria-hidden
|
||||
className="ascii-box font-mono text-sm leading-relaxed"
|
||||
style={{ boxShadow: "0 24px 60px rgba(0,0,0,0.5)" }}
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between border-b border-hairline pb-2">
|
||||
<span className="flex items-center gap-2 text-muted">
|
||||
<span className="inline-block h-2 w-2 rounded-full bg-[color-mix(in_oklab,var(--accent)_50%,transparent)]" />
|
||||
<span className="text-[10px] uppercase tracking-wider">terminal ~ eldov</span>
|
||||
</span>
|
||||
<span className="text-[10px] uppercase tracking-wider text-muted">v0.42.0</span>
|
||||
</div>
|
||||
<div className="space-y-1.5 text-[var(--fg-dim)]">
|
||||
{BOOT_LINES.slice(0, lineIdx).map((l, i) => (
|
||||
<div key={i}>
|
||||
<span className="text-muted">{l}</span>
|
||||
</div>
|
||||
))}
|
||||
{!done ? (
|
||||
<div>
|
||||
<span className="text-accent">{BOOT_LINES[lineIdx]?.slice(0, typed.length)}</span>
|
||||
<span className="cursor-blink inline-block w-2 -mb-0.5 bg-accent align-baseline"> </span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-accent">{BOOT_LINES[BOOT_LINES.length - 1]}</div>
|
||||
<div className="mt-3 text-[var(--fg)]">
|
||||
<span className="text-muted">role:</span>{" "}
|
||||
<span
|
||||
className="inline-block min-w-[8ch] text-accent"
|
||||
style={{ transition: "opacity 200ms" }}
|
||||
>
|
||||
{ROTATING[rotIdx]}
|
||||
</span>
|
||||
<span className="cursor-blink inline-block w-2 -mb-0.5 bg-accent align-baseline"> </span>
|
||||
</div>
|
||||
<div className="text-[var(--fg-dim)]">
|
||||
<span className="text-muted">since:</span> 2003 (online-handle)
|
||||
</div>
|
||||
<div className="text-[var(--fg-dim)]">
|
||||
<span className="text-muted">now:</span> building things that survive contact with users
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user