feat: Add LEVIS Holzbau — Kinder-Holzwerk-Website (Port 3013)
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 39s
CI / test-python-voice (push) Successful in 37s
CI / test-bqas (push) Successful in 37s

Neue statische Website fuer Kinder (6-12 Jahre) mit 8 Holzprojekten,
SVG-Illustrationen, Sicherheitshinweisen und kindgerechtem Design.
Next.js 15 + Tailwind + Framer Motion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-11 10:03:21 +01:00
parent 32aade553d
commit 0770ff499b
31 changed files with 3329 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@500;600;700&family=Nunito:wght@400;600;700&display=swap');
html {
scroll-behavior: smooth;
}
body {
font-family: 'Nunito', sans-serif;
background-color: #FDF8F0;
color: #2C2C2C;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Quicksand', sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}

View File

@@ -0,0 +1,21 @@
import type { Metadata } from 'next'
import './globals.css'
import { Navbar } from '@/components/Navbar'
import { Footer } from '@/components/Footer'
export const metadata: Metadata = {
title: 'LEVIS Holzbau — Kinder-Holzwerkstatt',
description: 'Lerne Holzfiguren schnitzen und kleine Holzprojekte bauen! Kindgerechte Anleitungen fuer junge Holzwerker.',
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="de">
<body className="min-h-screen flex flex-col">
<Navbar />
<main className="flex-1">{children}</main>
<Footer />
</body>
</html>
)
}

View File

@@ -0,0 +1,71 @@
'use client'
import { motion } from 'framer-motion'
import { Hammer, TreePine, ShieldCheck } from 'lucide-react'
import { HeroSection } from '@/components/HeroSection'
import { ProjectCard } from '@/components/ProjectCard'
import { projects } from '@/lib/projects'
const features = [
{
icon: Hammer,
title: 'Schnitzen',
description: 'Lerne mit Schnitzmesser und Holz umzugehen und forme eigene Figuren.',
color: 'bg-primary/10 text-primary',
},
{
icon: TreePine,
title: 'Bauen',
description: 'Saege, leime und nagle — baue nuetzliche Dinge aus Holz!',
color: 'bg-secondary/10 text-secondary',
},
{
icon: ShieldCheck,
title: 'Sicherheit',
description: 'Jedes Projekt zeigt dir, wie du sicher mit Werkzeug arbeitest.',
color: 'bg-accent/10 text-accent',
},
]
export default function HomePage() {
const featured = projects.slice(0, 4)
return (
<>
<HeroSection />
{/* Features */}
<section className="max-w-6xl mx-auto px-4 py-16">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
{features.map((f, i) => (
<motion.div
key={f.title}
className="bg-white rounded-2xl p-6 shadow-sm border border-primary/5 text-center"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.1 }}
>
<div className={`w-14 h-14 rounded-xl ${f.color} flex items-center justify-center mx-auto mb-4`}>
<f.icon className="w-7 h-7" />
</div>
<h3 className="font-heading font-bold text-lg mb-2">{f.title}</h3>
<p className="text-sm text-dark/60">{f.description}</p>
</motion.div>
))}
</div>
</section>
{/* Popular Projects */}
<section className="max-w-6xl mx-auto px-4 pb-16">
<h2 className="font-heading font-bold text-3xl text-center mb-8">
Beliebte Projekte
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{featured.map((p) => (
<ProjectCard key={p.slug} project={p} />
))}
</div>
</section>
</>
)
}

View File

@@ -0,0 +1,120 @@
import { notFound } from 'next/navigation'
import Link from 'next/link'
import { ArrowLeft, Clock, Wrench, Package } from 'lucide-react'
import { projects, getProject, getRelatedProjects } from '@/lib/projects'
import { DifficultyBadge } from '@/components/DifficultyBadge'
import { AgeBadge } from '@/components/AgeBadge'
import { StepCard } from '@/components/StepCard'
import { SafetyTip } from '@/components/SafetyTip'
import { ToolIcon } from '@/components/ToolIcon'
import { ProjectIllustration } from '@/components/ProjectIllustration'
import { ProjectCard } from '@/components/ProjectCard'
export function generateStaticParams() {
return projects.map((p) => ({ slug: p.slug }))
}
export default async function ProjectPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const project = getProject(slug)
if (!project) notFound()
const related = getRelatedProjects(slug)
return (
<div className="max-w-4xl mx-auto px-4 py-8">
{/* Back */}
<Link href="/projekte" className="inline-flex items-center gap-1 text-accent hover:underline mb-6 text-sm font-semibold">
<ArrowLeft className="w-4 h-4" /> Alle Projekte
</Link>
{/* Hero */}
<div className="bg-white rounded-2xl shadow-sm border border-primary/5 overflow-hidden mb-8">
<div className="bg-cream p-10 flex items-center justify-center">
<ProjectIllustration slug={project.slug} size={180} />
</div>
<div className="p-6 sm:p-8">
<div className="flex flex-wrap items-center gap-3 mb-3">
<AgeBadge range={project.ageRange} />
<DifficultyBadge level={project.difficulty} />
<span className="flex items-center gap-1 text-sm text-dark/50">
<Clock className="w-4 h-4" /> {project.duration}
</span>
</div>
<h1 className="font-heading font-bold text-3xl sm:text-4xl mb-3">{project.name}</h1>
<p className="text-dark/70 text-lg leading-relaxed">{project.description}</p>
</div>
</div>
{/* Tools & Materials */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
<div className="bg-white rounded-2xl p-6 border border-primary/5">
<h2 className="font-heading font-bold text-lg flex items-center gap-2 mb-4">
<Wrench className="w-5 h-5 text-primary" /> Werkzeuge
</h2>
<ul className="space-y-2">
{project.tools.map((t) => (
<li key={t} className="flex items-center gap-2 text-sm">
<ToolIcon name={t} />
{t}
</li>
))}
</ul>
</div>
<div className="bg-white rounded-2xl p-6 border border-primary/5">
<h2 className="font-heading font-bold text-lg flex items-center gap-2 mb-4">
<Package className="w-5 h-5 text-secondary" /> Material
</h2>
<ul className="space-y-2">
{project.materials.map((m) => (
<li key={m} className="flex items-center gap-2 text-sm">
<span className="w-2 h-2 rounded-full bg-secondary flex-shrink-0" />
{m}
</li>
))}
</ul>
</div>
</div>
{/* Safety */}
<div className="space-y-3 mb-10">
<h2 className="font-heading font-bold text-xl mb-2">Sicherheitshinweise</h2>
{project.safetyTips.map((tip) => (
<SafetyTip key={tip}>{tip}</SafetyTip>
))}
</div>
{/* Steps */}
<div className="mb-10">
<h2 className="font-heading font-bold text-xl mb-6">Schritt fuer Schritt</h2>
<div className="space-y-0">
{project.steps.map((step, i) => (
<StepCard key={i} step={step} index={i} />
))}
</div>
</div>
{/* Skills */}
<div className="bg-secondary/5 rounded-2xl p-6 mb-12">
<h2 className="font-heading font-bold text-xl mb-3">Was du lernst</h2>
<div className="flex flex-wrap gap-2">
{project.skills.map((s) => (
<span key={s} className="px-3 py-1.5 bg-secondary/10 text-secondary rounded-full text-sm font-semibold">
{s}
</span>
))}
</div>
</div>
{/* Related */}
<div>
<h2 className="font-heading font-bold text-xl mb-6">Aehnliche Projekte</h2>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{related.map((p) => (
<ProjectCard key={p.slug} project={p} />
))}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,59 @@
'use client'
import { useState } from 'react'
import { motion } from 'framer-motion'
import { ProjectCard } from '@/components/ProjectCard'
import { projects } from '@/lib/projects'
const filters = [
{ label: 'Alle', value: 0 },
{ label: 'Anfaenger', value: 1 },
{ label: 'Fortgeschritten', value: 2 },
{ label: 'Profi', value: 3 },
]
export default function ProjektePage() {
const [filter, setFilter] = useState(0)
const filtered = filter === 0 ? projects : projects.filter((p) => p.difficulty === filter)
return (
<div className="max-w-6xl mx-auto px-4 py-12">
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="text-center mb-10"
>
<h1 className="font-heading font-bold text-4xl mb-3">Alle Projekte</h1>
<p className="text-dark/60 text-lg">Waehle ein Projekt und leg los!</p>
</motion.div>
{/* Filter */}
<div className="flex justify-center gap-2 mb-10">
{filters.map((f) => (
<button
key={f.value}
onClick={() => setFilter(f.value as 0 | 1 | 2 | 3)}
className={`px-4 py-2 rounded-xl font-semibold text-sm transition-colors ${
filter === f.value
? 'bg-primary text-white'
: 'bg-white text-dark/60 hover:bg-primary/5'
}`}
>
{f.label}
</button>
))}
</div>
{/* Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{filtered.map((p) => (
<ProjectCard key={p.slug} project={p} />
))}
</div>
{filtered.length === 0 && (
<p className="text-center text-dark/40 mt-12">Keine Projekte in dieser Kategorie.</p>
)}
</div>
)
}

View File

@@ -0,0 +1,101 @@
'use client'
import { motion } from 'framer-motion'
import { ShieldCheck, Eye, Hand, Scissors, AlertTriangle, Users } from 'lucide-react'
import { SafetyTip } from '@/components/SafetyTip'
const rules = [
{ icon: Users, title: 'Immer mit Erwachsenen', text: 'Bei Saegen, Bohren und Schnitzen muss immer ein Erwachsener dabei sein.' },
{ icon: Hand, title: 'Vom Koerper weg', text: 'Schnitze, saege und schneide immer vom Koerper weg. So kannst du dich nicht verletzen.' },
{ icon: Eye, title: 'Schutzbrille tragen', text: 'Beim Saegen und Schleifen fliegen Spaene — eine Schutzbrille schuetzt deine Augen.' },
{ icon: Scissors, title: 'Werkzeug richtig halten', text: 'Greife Werkzeuge immer am Griff. Trage Messer und Saegen mit der Spitze nach unten.' },
{ icon: AlertTriangle, title: 'Aufgeraeumter Arbeitsplatz', text: 'Raeume Werkzeug nach dem Benutzen weg. Ein ordentlicher Platz ist ein sicherer Platz!' },
{ icon: ShieldCheck, title: 'Scharfes Werkzeug', text: 'Klingt komisch, aber: Scharfe Messer sind sicherer als stumpfe, weil du weniger Kraft brauchst.' },
]
const toolGuides = [
{ name: 'Schnitzmesser', age: 'Ab 6 Jahren (mit Hilfe)', tips: ['Immer vom Koerper weg schnitzen', 'Nach dem Benutzen zuklappen', 'Weiches Holz (Linde) verwenden'] },
{ name: 'Handsaege', age: 'Ab 7 Jahren (mit Hilfe)', tips: ['Holz immer fest einspannen', 'Langsam und gleichmaessig saegen', 'Nicht auf die Klinge druecken'] },
{ name: 'Hammer', age: 'Ab 5 Jahren', tips: ['Leichten Kinderhammer verwenden', 'Naegel mit Zange halten, nie mit Fingern', 'Auf stabile Unterlage achten'] },
{ name: 'Schleifpapier', age: 'Ab 5 Jahren', tips: ['Immer in eine Richtung schleifen', 'Staub nicht einatmen', 'Erst grob, dann fein'] },
{ name: 'Holzleim', age: 'Ab 5 Jahren', tips: ['Nicht giftig, aber nicht essen', 'Duenn auftragen reicht', 'Mindestens 1 Stunde trocknen lassen'] },
]
export default function SicherheitPage() {
return (
<div className="max-w-4xl mx-auto px-4 py-12">
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="text-center mb-12"
>
<div className="w-16 h-16 bg-warning/10 rounded-2xl flex items-center justify-center mx-auto mb-4">
<ShieldCheck className="w-8 h-8 text-warning" />
</div>
<h1 className="font-heading font-bold text-4xl mb-3">Sicherheit geht vor!</h1>
<p className="text-dark/60 text-lg max-w-2xl mx-auto">
Holzarbeiten macht riesig Spass aber nur, wenn du sicher arbeitest.
Hier findest du die wichtigsten Regeln.
</p>
</motion.div>
{/* Rules Grid */}
<section className="mb-16">
<h2 className="font-heading font-bold text-2xl mb-6">Die goldenen Regeln</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{rules.map((r, i) => (
<motion.div
key={r.title}
className="bg-white rounded-2xl p-5 border border-primary/5 flex gap-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.05 }}
>
<div className="w-10 h-10 bg-warning/10 rounded-xl flex items-center justify-center flex-shrink-0">
<r.icon className="w-5 h-5 text-warning" />
</div>
<div>
<h3 className="font-heading font-bold mb-1">{r.title}</h3>
<p className="text-sm text-dark/60">{r.text}</p>
</div>
</motion.div>
))}
</div>
</section>
{/* Tool Guides */}
<section className="mb-16">
<h2 className="font-heading font-bold text-2xl mb-6">Werkzeug-Guide</h2>
<div className="space-y-4">
{toolGuides.map((tool) => (
<div key={tool.name} className="bg-white rounded-2xl p-5 border border-primary/5">
<div className="flex items-center justify-between mb-3">
<h3 className="font-heading font-bold text-lg">{tool.name}</h3>
<span className="text-xs font-semibold bg-accent/10 text-accent px-2.5 py-1 rounded-full">{tool.age}</span>
</div>
<ul className="space-y-1.5">
{tool.tips.map((tip) => (
<li key={tip} className="flex items-center gap-2 text-sm text-dark/70">
<span className="w-1.5 h-1.5 rounded-full bg-primary flex-shrink-0" />
{tip}
</li>
))}
</ul>
</div>
))}
</div>
</section>
{/* Parents */}
<section>
<h2 className="font-heading font-bold text-2xl mb-4">Hinweise fuer Eltern</h2>
<div className="space-y-3">
<SafetyTip>Beaufsichtigen Sie Ihr Kind bei allen Projekten besonders beim Umgang mit Schneidwerkzeugen.</SafetyTip>
<SafetyTip>Stellen Sie altersgerechtes Werkzeug bereit. Kinderschnitzmesser haben abgerundete Spitzen.</SafetyTip>
<SafetyTip>Richten Sie einen festen Arbeitsplatz ein idealerweise auf einer stabilen Werkbank oder einem alten Tisch.</SafetyTip>
<SafetyTip>Leinoel und Acrylfarben sind fuer Kinder unbedenklich. Vermeiden Sie Lacke mit Loesungsmitteln.</SafetyTip>
</div>
</section>
</div>
)
}

View File

@@ -0,0 +1,83 @@
'use client'
import { motion } from 'framer-motion'
import { TreePine, Heart, Sparkles, Users } from 'lucide-react'
import Link from 'next/link'
const reasons = [
{ icon: Sparkles, title: 'Kreativitaet', text: 'Du kannst dir selbst ausdenken, was du baust — und es dann wirklich machen!' },
{ icon: Heart, title: 'Stolz', text: 'Wenn du etwas mit deinen eigenen Haenden baust, macht dich das richtig stolz.' },
{ icon: TreePine, title: 'Natur', text: 'Holz ist ein natuerliches Material. Du lernst die Natur besser kennen.' },
{ icon: Users, title: 'Zusammen', text: 'Holzarbeiten macht zusammen mit Freunden oder der Familie am meisten Spass!' },
]
export default function UeberPage() {
return (
<div className="max-w-4xl mx-auto px-4 py-12">
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="text-center mb-12"
>
<h1 className="font-heading font-bold text-4xl mb-3">Ueber LEVIS Holzbau</h1>
<p className="text-dark/60 text-lg max-w-2xl mx-auto">
Wir zeigen dir, wie du aus einem einfachen Stueck Holz etwas Tolles machen kannst!
</p>
</motion.div>
{/* Story */}
<div className="bg-white rounded-2xl p-6 sm:p-8 border border-primary/5 mb-12">
<h2 className="font-heading font-bold text-2xl mb-4">Was ist LEVIS Holzbau?</h2>
<div className="space-y-4 text-dark/70 leading-relaxed">
<p>
LEVIS Holzbau ist deine Online-Holzwerkstatt! Hier findest du Anleitungen fuer tolle Projekte
aus Holz vom einfachen Zauberstab bis zum echten Vogelhaus.
</p>
<p>
Jedes Projekt erklaert dir Schritt fuer Schritt, was du tun musst. Du siehst welches Werkzeug
und Material du brauchst, und wir zeigen dir immer, worauf du bei der Sicherheit achten musst.
</p>
<p>
Egal ob du 6 oder 12 Jahre alt bist fuer jedes Alter gibt es passende Projekte.
Faengst du gerade erst an? Dann probier den Zauberstab oder die Nagelbilder. Bist du
schon ein Profi? Dann trau dich an den Fliegenpilz!
</p>
</div>
</div>
{/* Why woodworking */}
<h2 className="font-heading font-bold text-2xl mb-6 text-center">Warum Holzarbeiten Spass macht</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-12">
{reasons.map((r, i) => (
<motion.div
key={r.title}
className="bg-white rounded-2xl p-5 border border-primary/5 flex gap-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.1 }}
>
<div className="w-10 h-10 bg-secondary/10 rounded-xl flex items-center justify-center flex-shrink-0">
<r.icon className="w-5 h-5 text-secondary" />
</div>
<div>
<h3 className="font-heading font-bold mb-1">{r.title}</h3>
<p className="text-sm text-dark/60">{r.text}</p>
</div>
</motion.div>
))}
</div>
{/* CTA */}
<div className="text-center bg-gradient-to-br from-primary/5 to-secondary/5 rounded-2xl p-8">
<h2 className="font-heading font-bold text-2xl mb-3">Bereit loszulegen?</h2>
<p className="text-dark/60 mb-6">Schau dir unsere Projekte an und such dir eins aus!</p>
<Link
href="/projekte"
className="inline-flex items-center gap-2 bg-primary hover:bg-primary/90 text-white font-bold px-8 py-3 rounded-2xl transition-colors"
>
Zu den Projekten
</Link>
</div>
</div>
)
}