156 lines
7.1 KiB
TypeScript
156 lines
7.1 KiB
TypeScript
'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 Daily AI Reports',
|
||
slug: 'hackernews',
|
||
description: 'Die Hacker News gelten als zentrale Nachrichtenbörse für versierte, englischsprachige Programmierer. Wer wissen will, was neu oder wichtig ist, findet es dort. Doch die schiere Menge an Beiträgen kann schnell überwältigen.\n\nGenau hier setzt mein Projekt an: Eine KI filtert die wichtigsten Inhalte, fasst sie zusammen, eine andere übersetzt sie. Das Projekt liefert morgens wie abends kompakte Updates. Zusätzlich bewertet sie automatisch, wie relevant die Top-10-Geschichten für Themen wie Künstliche Intelligenz und Maschinelles Lernen sind.\n\nDas System wählt selbstständig geeignete Modelle aus, die Aufgaben effizient und kostengünstig erledigen. So entsteht ein intelligenter Nachrichtenstrom, der effizient Übersicht schafft — und das für lediglich 0 bis 3 Cent pro Ausgabe.',
|
||
tech: ['Python', 'FastAPI', 'Openrouter', 'deepseek-reasoner', 'qwen3-max'],
|
||
status: 'Aktiv',
|
||
url: 'https://jamulix.de/hackernews/',
|
||
featured: true,
|
||
},
|
||
{
|
||
title: 'Sichtbare Sortier-Algorithmen (Demo)',
|
||
slug: 'sorting',
|
||
description: 'Interaktive Visualisierung verschiedener Sortieralgorithmen. Didaktisches Werkzeug für Algorithmen-Verständnis. Digitales Sortieren wird immer wichtiger, weil es viel mehr Daten gibt. Es ist wichtig, sich die Wirkungsweise verschiedener Sortier‑Algorithmen plastisch anzuschauen, weil man so ihre Logik wirklich versteht und nicht nur Pseudocode auswendig lernt. Visuelle Darstellungen zeigen auf einen Blick, wie sich Elemente bewegen, wo sich regionale Ordnung bildet und wie sich Partitionen oder „Blasen“ entwickeln. So wird klar, warum Insertionsort bei fast sortierten Daten schnell wirkt, während Mergesort oder Quicksort bei großen Datenmengen besser skalieren. Zusätzlich hilft die plastische Anschauung, Effizienzunterschiede intuitiv zu erfassen: Man sieht, wann viele Vertauschungen oder Tiefe Rekursion auftreten, und bekommt ein Gefühl für Geschwindigkeit versus Datenverbrauch.',
|
||
tech: ['Vibe-Coding', 'LLM', 'Javascript', 'Tailwind'],
|
||
status: 'Aktiv',
|
||
url: 'https://jamulix.de/sorting',
|
||
featured: true,
|
||
},
|
||
{
|
||
title: 'Digitalisierung einer "Mundorgel"',
|
||
slug: 'upcoming-1',
|
||
description: 'KI-gestützte Digitalisierung eines Liederheftes von 1960.',
|
||
tech: ['LLM-OCR', 'JSON', 'Typescript', 'Tailwind', 'Vibe-Coding'],
|
||
status: 'Aktiv',
|
||
url: 'https://jamulix.de/mundorgel/',
|
||
featured: false,
|
||
},
|
||
{
|
||
title: 'Desinformationsdetektor',
|
||
slug: 'upcoming-2',
|
||
description: 'Automatisierte Prüfung von Medieninhalten auf Manipulationspotenzial. Das Projekt analysiert mit verschiedenen KI-unterstützten Programmen veröffentlichte Texte, Audios und Videos auf zentrale Behauptungen, Belege, Lücken und Logik. Es identifiziert prüfbare Aussagen, recherchiert stützende und widersprechende Quellen mit Links, deckt ausgelassene Fakten auf, prüft argumentative Kohärenz und bewertet Framing, Sentiment und Meinungsdichte. Das Ergebnis ist ein transparentes, nachvollziehbares Gutachten zur Qualität und zum Desinformationsrisiko des Inhalts.',
|
||
tech: ['Python', 'Rust', 'Pi', 'Typescript', 'Whisper', 'SQLite', 'vLLMs', 'APIs', 'Perplexity'],
|
||
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 */}
|
||
<div className="text-muted-foreground leading-relaxed mb-6 space-y-3">
|
||
{project.description.split('\n\n').map((para, i) => (
|
||
<p key={i}>{para}</p>
|
||
))}
|
||
</div>
|
||
|
||
{/* 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 junge 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>
|
||
)
|
||
}
|