Files
eldov-win/src/components/stack-icon.tsx
T
Jan Wagner 6cdd8438ae 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.
2026-08-31 20:58:48 +02:00

137 lines
4.1 KiB
TypeScript

// Inline SVG-Icons für Stack-Items. Klein, theme-aware (currentColor),
// monospace-friendly. Keine externe Library, kein CDN, keine Hydration.
import type { ReactNode } from "react";
type IconProps = { className?: string; title?: string };
function S({ children, title, className }: { children: ReactNode; title?: string; className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden={title ? undefined : true}
role={title ? "img" : undefined}
>
{title ? <title>{title}</title> : null}
{children}
</svg>
);
}
export const StackIcons = {
typescript: (p: IconProps) => (
<S {...p}>
<rect x="3" y="3" width="18" height="18" rx="2" />
<path d="M8 11h4M10 18V11" />
<path d="M14 18h4M15 13c0-1 .8-1.5 2-1.5s2 .5 2 1.5c0 1.5-2 1.5-2 3" />
</S>
),
node: (p: IconProps) => (
<S {...p}>
<path d="M12 2L3 7v10l9 5 9-5V7z" />
<path d="M12 22V12" />
<path d="M3 7l9 5 9-5" />
<path d="M7.5 4.5L16.5 9.5" />
</S>
),
python: (p: IconProps) => (
<S {...p}>
<path d="M9 3h6a3 3 0 013 3v3a3 3 0 01-3 3H9" />
<path d="M15 21H9a3 3 0 01-3-3v-3a3 3 0 013-3h6" />
<path d="M9 3a3 3 0 00-3 3v6a3 3 0 003 3" />
<path d="M15 21a3 3 0 003-3v-6a3 3 0 00-3-3" />
</S>
),
rust: (p: IconProps) => (
<S {...p}>
<circle cx="12" cy="12" r="9" />
<path d="M9 7l3 5 3-5M9 17l3-5 3 5" />
<circle cx="12" cy="12" r="2" fill="currentColor" />
</S>
),
sqlite: (p: IconProps) => (
<S {...p}>
<ellipse cx="12" cy="6" rx="8" ry="3" />
<path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6" />
<path d="M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6" />
</S>
),
tailwind: (p: IconProps) => (
<S {...p}>
<path d="M3 12c0-3 2-6 6-6 3 0 5 2 5 5 0-3 2-5 5-5 3 0 5 2 5 5s-2 6-6 6c-3 0-5-2-5-5-1 3-3 5-6 5-4 0-4-3-4-5z" />
</S>
),
react: (p: IconProps) => (
<S {...p}>
<circle cx="12" cy="12" r="2" fill="currentColor" />
<ellipse cx="12" cy="12" rx="9" ry="3.5" />
<ellipse cx="12" cy="12" rx="9" ry="3.5" transform="rotate(60 12 12)" />
<ellipse cx="12" cy="12" rx="9" ry="3.5" transform="rotate(120 12 12)" />
</S>
),
docker: (p: IconProps) => (
<S {...p}>
<rect x="3" y="11" width="3" height="3" />
<rect x="7" y="11" width="3" height="3" />
<rect x="11" y="11" width="3" height="3" />
<rect x="7" y="7" width="3" height="3" />
<rect x="11" y="7" width="3" height="3" />
<rect x="11" y="3" width="3" height="3" />
<rect x="15" y="11" width="3" height="3" />
<path d="M3 14h17l-1 4H4z" />
</S>
),
gitea: (p: IconProps) => (
<S {...p}>
<circle cx="12" cy="12" r="9" />
<path d="M8 8c0-1 1-2 2-2h2c1 0 2 1 2 2v3M8 11h8M10 11l2 6M14 11l-2 6" />
</S>
),
next: (p: IconProps) => (
<S {...p}>
<circle cx="12" cy="12" r="9" />
<path d="M9 8l6 8M9 16h6" />
</S>
),
nix: (p: IconProps) => (
<S {...p}>
<rect x="3" y="3" width="18" height="18" rx="2" />
<path d="M8 8h8M8 16h8" />
<path d="M12 4v16M4 12h16" />
</S>
),
hermes: (p: IconProps) => (
<S {...p}>
<path d="M12 2v6M12 22v-6" />
<path d="M5 7c0-1 1-2 3-2s3 1 3 2 1 2-1 2-2 1-2 3M19 7c0-1-1-2-3-2s-3 1-3 2-1 2 1 2 2 1 2 3" />
<circle cx="12" cy="13" r="2" />
</S>
),
shell: (p: IconProps) => (
<S {...p}>
<rect x="3" y="4" width="18" height="16" rx="2" />
<path d="M7 9l3 3-3 3M13 15h4" />
</S>
),
selfhost: (p: IconProps) => (
<S {...p}>
<path d="M3 12h6l2-6 4 12 2-6h4" />
</S>
),
};
// Mapping: Token-Name → Icon-Komponente. Fallback: shell-icon.
export function StackIcon({ name, className, title }: { name: string; className?: string; title?: string }) {
const key = name.toLowerCase().trim();
for (const [k, c] of Object.entries(StackIcons)) {
if (key.includes(k)) return c({ className, title });
}
return StackIcons.shell({ className, title });
}