Initial import: was v0 exportiert hat
This commit is contained in:
commit
0194931215
167 changed files with 16465 additions and 0 deletions
154
components/projects-section.tsx
Normal file
154
components/projects-section.tsx
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { ArrowUpRight, ExternalLink } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import Link from 'next/link'
|
||||
|
||||
const projects = [
|
||||
{
|
||||
title: 'Hacker News Reader',
|
||||
slug: 'hackernews',
|
||||
description: 'Ein minimalistischer, schneller Hacker News Client. Fokus auf Lesbarkeit und schnelles Laden.',
|
||||
tech: ['Python', 'FastAPI', 'HTMX'],
|
||||
status: 'Aktiv',
|
||||
url: 'https://jamulix.de/hackernews/',
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
title: 'Sorting Visualizer',
|
||||
slug: 'sorting',
|
||||
description: 'Interaktive Visualisierung verschiedener Sortieralgorithmen. Didaktisches Werkzeug für Algorithmen-Verständnis.',
|
||||
tech: ['Rust', 'WASM', 'Canvas'],
|
||||
status: 'Aktiv',
|
||||
url: 'https://jamulix.de/sorting',
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
title: 'Projekt in Arbeit',
|
||||
slug: 'upcoming-1',
|
||||
description: 'KI-gestütztes Werkzeug für Code-Analyse und Dokumentation. Details folgen.',
|
||||
tech: ['Python', 'LLM', 'AST'],
|
||||
status: 'In Entwicklung',
|
||||
url: null,
|
||||
featured: false,
|
||||
},
|
||||
{
|
||||
title: 'Geplant',
|
||||
slug: 'upcoming-2',
|
||||
description: 'Self-hosted Infrastruktur-Monitoring. Lightweight, ohne Cloud-Abhängigkeiten.',
|
||||
tech: ['Rust', 'Linux', 'SQLite'],
|
||||
status: 'Geplant',
|
||||
url: null,
|
||||
featured: false,
|
||||
},
|
||||
]
|
||||
|
||||
function ProjectCard({ project, index }: { project: typeof projects[0]; index: number }) {
|
||||
const isClickable = project.url !== null
|
||||
|
||||
return (
|
||||
<motion.article
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-50px' }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className={`group relative ${project.featured ? 'lg:col-span-1' : ''}`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
relative h-full p-6 lg:p-8 rounded-lg border border-border
|
||||
bg-card transition-all duration-300
|
||||
${isClickable ? 'hover:border-accent/50 hover:bg-card/80' : 'opacity-70'}
|
||||
`}
|
||||
>
|
||||
{/* Status badge */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span
|
||||
className={`
|
||||
font-mono text-xs px-2 py-1 rounded
|
||||
${project.status === 'Aktiv'
|
||||
? 'bg-accent/10 text-accent'
|
||||
: project.status === 'In Entwicklung'
|
||||
? 'bg-secondary text-muted-foreground'
|
||||
: 'bg-secondary/50 text-muted-foreground/70'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{project.status}
|
||||
</span>
|
||||
{isClickable && (
|
||||
<ArrowUpRight className="size-5 text-muted-foreground group-hover:text-accent transition-colors" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="font-serif text-xl lg:text-2xl mb-3 group-hover:text-accent transition-colors">
|
||||
{project.title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-muted-foreground leading-relaxed mb-6">
|
||||
{project.description}
|
||||
</p>
|
||||
|
||||
{/* Tech stack */}
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
{project.tech.map((tech) => (
|
||||
<span
|
||||
key={tech}
|
||||
className="font-mono text-xs px-2 py-1 bg-secondary text-secondary-foreground rounded"
|
||||
>
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Link */}
|
||||
{isClickable && (
|
||||
<Button asChild variant="outline" size="sm" className="group/btn">
|
||||
<Link href={project.url} target="_blank" rel="noopener noreferrer">
|
||||
Ansehen
|
||||
<ExternalLink className="ml-2 size-3 transition-transform group-hover/btn:translate-x-0.5" />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</motion.article>
|
||||
)
|
||||
}
|
||||
|
||||
export function ProjectsSection() {
|
||||
return (
|
||||
<section id="projekte" className="py-24 lg:py-32 bg-secondary/30">
|
||||
<div className="max-w-6xl mx-auto px-6 lg:px-8">
|
||||
{/* Section Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="mb-16"
|
||||
>
|
||||
<span className="font-mono text-xs tracking-wider text-accent uppercase">
|
||||
Experimente
|
||||
</span>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl lg:text-5xl mt-4 text-balance">
|
||||
Ausgewählte Projekte
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-muted-foreground max-w-2xl">
|
||||
Technische Experimente und Werkzeuge. Weniger Portfolio,
|
||||
mehr Labor für Ideen.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Projects Grid */}
|
||||
<div className="grid md:grid-cols-2 gap-6 lg:gap-8">
|
||||
{projects.map((project, index) => (
|
||||
<ProjectCard key={project.slug} project={project} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue