Redesign: Modernes SaaS-Design für Joachim Hummel Portfolio

Task #4 — vollständiges Redesign von dunkler Terminal-Ästhetik zu modernem SaaS-Look.

Änderungen:
- index.css: Komplette Neudefinition der CSS-Variablen. Helles Farbschema (weißer Hintergrund),
  Primärfarbe Indigo/Blau (hsl 234 89% 60%), neutrale Grautöne, Google Font Inter eingebunden.
  Utility-Klassen .section-number, .hero-gradient, .dot-pattern, .card-hover hinzugefügt.
  Terminal-spezifische Klassen (.glow-text, .glow-box, .bg-grid-pattern) entfernt.

- navbar.tsx: Sticky Nav mit transparentem Start → weißem Hintergrund + Schatten beim Scrollen.
  Logo "JH" als gefülltes Indigo-Badge + "Joachim Hummel" Text. Pill-Style Navigation Links.
  Primär-CTA Button. Vollständiges Mobile-Menü mit Hamburger/X Toggle.

- hero.tsx: Zentriertes Layout, großer Display-Headline mit Indigo-Akzent auf Nachname.
  "Verfügbar für neue Projekte" Badge mit grünem Puls. Skill-Badges (30+ Jahre, ITIL, etc.).
  Zwei CTAs: gefüllter Primärbutton + Outline-Button. Subtiler Radial-Gradient + Dot-Pattern.

- competencies.tsx: Sechs weiße Karten im Grid mit farbigen Icon-Badges (blau, sky, grün, orange,
  violett, rose). Tech-Icon-Leiste (Linux, Ubuntu, Docker, Grafana, Nginx, Ansible, Prometheus)
  mit Hover-Farbeffekt und Labels.

- strengths.tsx: Drei weiße Karten auf hellgrauem Hintergrund. Nummerierung (01/02/03) als
  dekoratives Element. Highlight-Badge je Karte. Kein Glow, kein Neon.

- projects.tsx: Sechs Projektkarten mit farbigen Icon-Badges je Projektkategorie. Externe Links
  für SafeDocs Portal (safedocsportal.com) und zensend.email als ArrowUpRight-Icon-Buttons.
  Pill-Badges für Tags.

- bio.tsx: Zweispaltiges Layout. Links: Fließtext + Kunden-Badges. Rechts: Weißes Highlights-Panel
  mit CheckCircle-Icons (7 Punkte inkl. ITIL, BSI, Tech-Blogger).

- contact.tsx: Primärfarbige E-Mail-Karte + drei kompakte Link-Cards (Tech-Blog, KI-Blog, Website).
  Footer-Zeile mit Name und Standort.

- home.tsx: Vereinfacht — kein Fixed-Background, kein Grid-Pattern, kein Ambient-Glow.
  max-w-6xl Container für alle Sektionen.
This commit is contained in:
joachimhummel
2026-05-15 15:40:45 +00:00
parent 58234b8621
commit 46358579c4
9 changed files with 526 additions and 246 deletions

View File

@@ -1,56 +1,104 @@
import { useState, useEffect } from "react";
import { motion, useScroll, useMotionValueEvent } from "framer-motion";
import { Menu, X } from "lucide-react";
export function Navbar() {
const [isScrolled, setIsScrolled] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
const { scrollY } = useScroll();
useMotionValueEvent(scrollY, "change", (latest) => {
setIsScrolled(latest > 50);
setIsScrolled(latest > 20);
});
const navLinks = [
{ name: "Kompetenzen", href: "#competencies" },
{ name: "3 Welten", href: "#strengths" },
{ name: "Stärken", href: "#strengths" },
{ name: "Projekte", href: "#projects" },
{ name: "Über mich", href: "#bio" },
{ name: "Kontakt", href: "#contact" },
];
return (
<motion.header
className={`fixed top-0 w-full z-50 transition-all duration-300 ${
isScrolled
? "bg-background/80 backdrop-blur-md border-b border-border/50 py-4"
: "bg-transparent py-6"
isScrolled
? "bg-white/95 backdrop-blur-md border-b border-border shadow-sm py-3"
: "bg-transparent py-5"
}`}
initial={{ y: -100 }}
animate={{ y: 0 }}
transition={{ duration: 0.5 }}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<div className="container mx-auto px-4 md:px-8 flex items-center justify-between">
<a href="#hero" className="text-xl font-bold font-mono text-primary glow-text">
JH<span className="text-muted-foreground">.</span>
<div className="max-w-6xl mx-auto px-4 md:px-8 flex items-center justify-between">
<a
href="#hero"
className="flex items-center gap-2 text-foreground font-bold text-lg tracking-tight"
data-testid="link-logo"
>
<span className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center text-white text-sm font-bold">
JH
</span>
<span className="hidden sm:block text-foreground">Joachim Hummel</span>
</a>
<nav className="hidden md:flex gap-8">
<nav className="hidden md:flex items-center gap-1">
{navLinks.map((link) => (
<a
key={link.name}
<a
key={link.name}
href={link.href}
className="text-sm font-mono text-muted-foreground hover:text-primary transition-colors"
className="px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-secondary rounded-lg transition-all"
data-testid={`link-nav-${link.name.toLowerCase().replace(/\s/g, '-')}`}
>
{link.name}
</a>
))}
</nav>
<a
href="#contact"
className="md:hidden px-4 py-2 border border-border rounded text-sm font-mono text-foreground hover:border-primary transition-colors"
<div className="hidden md:block">
<a
href="#contact"
className="px-4 py-2 bg-primary text-white text-sm font-semibold rounded-lg hover:bg-primary/90 transition-colors"
data-testid="button-nav-contact"
>
Kontakt
</a>
</div>
<button
className="md:hidden p-2 rounded-lg text-muted-foreground hover:bg-secondary transition-colors"
onClick={() => setMobileOpen(!mobileOpen)}
data-testid="button-mobile-menu"
>
Kontakt
</a>
{mobileOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
</div>
{mobileOpen && (
<motion.div
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
className="md:hidden bg-white border-t border-border px-4 py-4 flex flex-col gap-1"
>
{navLinks.map((link) => (
<a
key={link.name}
href={link.href}
onClick={() => setMobileOpen(false)}
className="px-4 py-3 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-secondary rounded-lg transition-all"
>
{link.name}
</a>
))}
<a
href="#contact"
onClick={() => setMobileOpen(false)}
className="mt-2 px-4 py-3 bg-primary text-white text-sm font-semibold rounded-lg text-center"
>
Kontakt aufnehmen
</a>
</motion.div>
)}
</motion.header>
);
}