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:
@@ -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",
|
||||
},
|
||||
|
||||
@@ -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" },
|
||||
];
|
||||
|
||||
|
||||
203
artifacts/joachim-portfolio/src/components/skills.tsx
Normal file
203
artifacts/joachim-portfolio/src/components/skills.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user