Add Skills & Technologie-Matrix section to portfolio

Task: #3 — Erweitertes Skill-Profil & Technologie-Matrix

## What was done
- Created new `skills.tsx` component with a full technology matrix grouped into 8 categories:
  Betriebssysteme, Middleware/Webserver, Container & Automatisierung, Monitoring,
  Datenbanken, KI & Automation, Programmierung & Scripting, Kollaboration & Dokumentation
- Tags/Pills layout — compact, scannable, no progress bars per spec
- Methodik & Compliance card with ITIL v3/v4 and BSI-Grundschutz as prominent primary-colored badges
- Soft Skills block (Analytisches Denken, Belastbarkeit, Kommunikation, etc.) as a compact pill group
- Tech-Blogger credential card: "300+ Fachartikel auf blog.unixweb.de" with external link
- Framer Motion scroll-in animations consistent with all other sections
- Section placed after Experience, before Contact; id="skills"
- Added "Skills" anchor link to navbar (both desktop and mobile)
- Fixed residual "KI & Vibe-Coding" label in competencies.tsx → "KI & Automation"
- All data from the PDF profile/task spec; no duplicates with competencies.tsx

## Deviations
- None. All required categories, ITIL/BSI badges, soft skills block, and blogger credential implemented as specified.
This commit is contained in:
joachimhummel
2026-05-15 15:49:23 +00:00
parent 8933ab9067
commit 05ecbb3b16
4 changed files with 210 additions and 1 deletions

View File

@@ -29,7 +29,7 @@ const skills = [
},
{
icon: Cpu,
title: "KI & Vibe-Coding",
title: "KI & Automation",
desc: "n8n, Claude Code, ChatGPT, RAG-Systeme, Pinecone, OpenAI, Ollama, API-Automation",
color: "bg-violet-50 text-violet-600 border-violet-100",
},

View File

@@ -17,6 +17,7 @@ export function Navbar() {
{ name: "Projekte", href: "#projects" },
{ name: "Über mich", href: "#bio" },
{ name: "Erfahrung", href: "#experience" },
{ name: "Skills", href: "#skills" },
{ name: "Kontakt", href: "#contact" },
];

View File

@@ -0,0 +1,203 @@
import { motion } from "framer-motion";
import { ExternalLink, BookOpen, ShieldCheck } from "lucide-react";
type Category = {
label: string;
tags: string[];
color: string;
};
const categories: Category[] = [
{
label: "Betriebssysteme",
tags: ["AIX", "Solaris", "Ubuntu / Debian", "Red Hat", "HP-UX", "Windows Server"],
color: "bg-slate-100 text-slate-700",
},
{
label: "Middleware & Webserver",
tags: ["Apache2", "Tomcat", "JBoss", "WebSphere", "BEA WebLogic", "ColdFusion", "Nginx"],
color: "bg-blue-50 text-blue-700",
},
{
label: "Container & Automatisierung",
tags: ["Docker", "Docker Compose", "Ansible", "Gitlab CI/CD", "Artifactory", "Bitbucket"],
color: "bg-sky-50 text-sky-700",
},
{
label: "Monitoring",
tags: ["Grafana", "Prometheus", "Loki", "Nagios", "Cacti", "JMX-Monitoring"],
color: "bg-orange-50 text-orange-700",
},
{
label: "Datenbanken",
tags: ["Oracle", "MySQL", "MariaDB", "MSSQL"],
color: "bg-teal-50 text-teal-700",
},
{
label: "KI & Automation",
tags: ["n8n", "OpenAI API", "Ollama", "Pinecone", "RAG-Systeme", "Claude Code", "ChatGPT"],
color: "bg-violet-50 text-violet-700",
},
{
label: "Programmierung & Scripting",
tags: ["Shell / Bash", "Perl", "PHP", "Python", "HTML", "CSS"],
color: "bg-green-50 text-green-700",
},
{
label: "Kollaboration & Dokumentation",
tags: ["Confluence", "Jira", "draw.io", "Visio", "ServiceNow", "Nextcloud"],
color: "bg-rose-50 text-rose-700",
},
];
const methodikBadges = [
{ label: "ITIL v3/v4", strong: true },
{ label: "BSI-Grundschutz", strong: true },
{ label: "ITSM / Service Management", strong: false },
{ label: "Betriebshandbücher & Runbooks", strong: false },
{ label: "Change Management", strong: false },
{ label: "Incident & Problem Management", strong: false },
];
const softSkills = [
"Analytisches Denken",
"Belastbarkeit in kritischen Umgebungen",
"Strukturierte Kommunikation",
"Eigenverantwortliches Arbeiten",
"Einarbeitung in neue Domänen",
"Dokumentationsqualität",
];
export function Skills() {
return (
<div className="py-24 bg-secondary/40 rounded-3xl">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-80px" }}
transition={{ duration: 0.6 }}
className="px-4 md:px-12"
>
<div className="text-center max-w-2xl mx-auto mb-16">
<p className="section-number mb-3" data-testid="text-section-label-skills">
Skills & Technologien
</p>
<h2
className="text-3xl md:text-4xl font-bold text-foreground mb-4"
data-testid="text-section-title-skills"
>
Vollständige Technologie-Matrix
</h2>
<p className="text-muted-foreground text-lg">
Eingesetzte Technologien aus 30+ Jahren Projektpraxis von Unix-Systemen der ersten Stunde bis zur aktuellen KI-Toolchain.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5 mb-12">
{categories.map((cat, index) => (
<motion.div
key={cat.label}
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.35, delay: index * 0.05 }}
className="bg-white rounded-2xl border border-border p-5"
data-testid={`card-skill-category-${index}`}
>
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3">
{cat.label}
</p>
<div className="flex flex-wrap gap-2">
{cat.tags.map((tag) => (
<span
key={tag}
className={`text-xs font-medium px-2.5 py-1 rounded-full ${cat.color}`}
data-testid={`tag-skill-${tag.replace(/\s/g, '-').toLowerCase()}`}
>
{tag}
</span>
))}
</div>
</motion.div>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<motion.div
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4 }}
className="bg-white rounded-2xl border border-border p-6"
data-testid="card-methodik"
>
<div className="flex items-center gap-2 mb-4">
<ShieldCheck className="w-4 h-4 text-primary" />
<p className="text-sm font-semibold text-foreground">Methodik & Compliance</p>
</div>
<div className="flex flex-wrap gap-2">
{methodikBadges.map((b) => (
<span
key={b.label}
className={`text-xs font-medium px-3 py-1.5 rounded-full border ${
b.strong
? "bg-primary/10 text-primary border-primary/20 font-semibold"
: "bg-secondary text-foreground/70 border-border"
}`}
data-testid={`badge-methodik-${b.label.replace(/\s/g, '-').toLowerCase()}`}
>
{b.label}
</span>
))}
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4, delay: 0.06 }}
className="bg-white rounded-2xl border border-border p-6"
data-testid="card-softskills"
>
<p className="text-sm font-semibold text-foreground mb-4">Arbeitsweise & Soft Skills</p>
<div className="flex flex-wrap gap-2">
{softSkills.map((s) => (
<span
key={s}
className="text-xs font-medium px-3 py-1.5 rounded-full bg-secondary text-foreground/70 border border-border"
data-testid={`badge-softskill-${s.replace(/\s/g, '-').toLowerCase()}`}
>
{s}
</span>
))}
</div>
</motion.div>
</div>
<motion.a
href="https://blog.unixweb.de"
target="_blank"
rel="noopener noreferrer"
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4, delay: 0.1 }}
className="flex items-center gap-4 bg-white rounded-2xl border border-border p-5 hover:border-primary/30 hover:shadow-sm transition-all group"
data-testid="card-blogger-credential"
>
<div className="w-10 h-10 rounded-xl bg-primary/10 border border-primary/20 flex items-center justify-center shrink-0">
<BookOpen className="w-5 h-5 text-primary" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-semibold text-foreground">300+ Fachartikel auf blog.unixweb.de</p>
<p className="text-xs text-muted-foreground mt-0.5">
Praxiswissen zu Linux, Apache, Docker, Ansible, KI-Automation und mehr seit über 10 Jahren.
</p>
</div>
<ExternalLink className="w-4 h-4 text-muted-foreground/40 group-hover:text-primary transition-colors shrink-0" />
</motion.a>
</motion.div>
</div>
);
}

View File

@@ -4,6 +4,7 @@ import { Projects } from "@/components/projects";
import { Strengths } from "@/components/strengths";
import { Bio } from "@/components/bio";
import { Experience } from "@/components/experience";
import { Skills } from "@/components/skills";
import { Contact } from "@/components/contact";
import { Navbar } from "@/components/navbar";
@@ -37,6 +38,10 @@ export default function Home() {
<Experience />
</section>
<section id="skills" className="mt-8">
<Skills />
</section>
<section id="contact" className="mt-8">
<Contact />
</section>