Initial commit: Kit landing page

- Next.js 16 with Turbopack
- React 19
- Tailwind CSS 4 with CSS-first config
- Framer Motion animations
- Animated logo and hero section
- Tool cards for Vert and Paint
- Glassmorphism effects and gradient animations
- Fully responsive design
- Docker support with Nginx
- Static export ready

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-07 11:26:19 +01:00
commit ce0b2e8470
21 changed files with 4980 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
'use client';
export default function AnimatedBackground() {
return (
<div className="fixed inset-0 -z-10 overflow-hidden">
{/* Animated gradient background */}
<div
className="absolute inset-0 opacity-50"
style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #4facfe 75%, #667eea 100%)',
backgroundSize: '400% 400%',
animation: 'gradient 15s ease infinite',
}}
/>
{/* Grid pattern overlay */}
<div
className="absolute inset-0 opacity-10"
style={{
backgroundImage: `
linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px)
`,
backgroundSize: '50px 50px',
}}
/>
{/* Floating orbs */}
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-purple-500 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-float" />
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-cyan-500 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-float" style={{ animationDelay: '2s' }} />
<div className="absolute bottom-1/4 left-1/3 w-96 h-96 bg-pink-500 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-float" style={{ animationDelay: '4s' }} />
</div>
);
}

43
components/Footer.tsx Normal file
View File

@@ -0,0 +1,43 @@
'use client';
import { motion } from 'framer-motion';
export default function Footer() {
return (
<footer className="relative py-12 px-4 border-t border-gray-800">
<div className="max-w-6xl mx-auto">
<motion.div
className="text-center"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
{/* Brand */}
<div className="mb-4">
<h3 className="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-purple-400 to-cyan-400">
Kit
</h3>
</div>
{/* Links */}
<div className="flex justify-center gap-8 mb-6 text-sm">
<a
href="https://pivoine.art"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-purple-400 transition-colors"
>
pivoine.art
</a>
</div>
{/* Copyright */}
<p className="text-gray-500 text-sm">
© {new Date().getFullYear()} Kit. Built with Next.js & Tailwind CSS.
</p>
</motion.div>
</div>
</footer>
);
}

70
components/Hero.tsx Normal file
View File

@@ -0,0 +1,70 @@
'use client';
import { motion } from 'framer-motion';
import Logo from './Logo';
export default function Hero() {
return (
<section className="relative min-h-screen flex flex-col items-center justify-center px-4 py-20">
<div className="max-w-6xl mx-auto text-center">
{/* Logo */}
<motion.div
className="mb-8 flex justify-center"
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
<Logo size={160} />
</motion.div>
{/* Main heading */}
<motion.h1
className="text-6xl md:text-8xl font-bold mb-6 bg-clip-text text-transparent bg-gradient-to-r from-purple-400 via-pink-400 to-cyan-400"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }}
>
Kit
</motion.h1>
{/* Subtitle */}
<motion.p
className="text-xl md:text-2xl text-gray-300 mb-4 max-w-2xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
>
Your Creative Toolkit
</motion.p>
{/* Description */}
<motion.p
className="text-base md:text-lg text-gray-400 mb-12 max-w-xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }}
>
A curated collection of creative and utility tools for developers and creators.
Simple, powerful, and always at your fingertips.
</motion.p>
{/* Scroll indicator */}
<motion.div
className="flex flex-col items-center gap-2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 1 }}
>
<span className="text-sm text-gray-500">Explore Tools</span>
<motion.div
className="w-6 h-10 border-2 border-gray-600 rounded-full p-1"
animate={{ y: [0, 10, 0] }}
transition={{ duration: 1.5, repeat: Infinity }}
>
<div className="w-1 h-2 bg-gradient-to-b from-purple-400 to-cyan-400 rounded-full mx-auto" />
</motion.div>
</motion.div>
</div>
</section>
);
}

119
components/Logo.tsx Normal file
View File

@@ -0,0 +1,119 @@
'use client';
import { motion } from 'framer-motion';
export default function Logo({ className = '', size = 120 }: { className?: string; size?: number }) {
return (
<motion.svg
width={size}
height={size}
viewBox="0 0 200 200"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
initial={{ opacity: 0, scale: 0.5, rotate: -180 }}
animate={{ opacity: 1, scale: 1, rotate: 0 }}
transition={{ duration: 1, ease: 'easeOut' }}
>
{/* Toolbox base */}
<motion.rect
x="40"
y="80"
width="120"
height="80"
rx="8"
stroke="url(#gradient1)"
strokeWidth="4"
fill="rgba(139, 92, 246, 0.1)"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1.5, ease: 'easeInOut' }}
/>
{/* Toolbox handle */}
<motion.path
d="M 80 80 Q 100 40, 120 80"
stroke="url(#gradient1)"
strokeWidth="4"
fill="none"
strokeLinecap="round"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1.2, delay: 0.3, ease: 'easeInOut' }}
/>
{/* Wrench */}
<motion.g
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.8 }}
>
<path
d="M 70 110 L 70 130"
stroke="url(#gradient2)"
strokeWidth="5"
strokeLinecap="round"
/>
<circle cx="70" cy="105" r="6" fill="url(#gradient2)" />
</motion.g>
{/* Pencil */}
<motion.g
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 1 }}
>
<path
d="M 100 110 L 100 135"
stroke="url(#gradient3)"
strokeWidth="5"
strokeLinecap="round"
/>
<path
d="M 95 110 L 100 105 L 105 110"
fill="url(#gradient3)"
/>
</motion.g>
{/* Brush */}
<motion.g
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 1.2 }}
>
<rect
x="127"
y="110"
width="6"
height="20"
fill="url(#gradient4)"
rx="1"
/>
<path
d="M 125 110 L 130 105 L 135 110"
fill="url(#gradient4)"
/>
</motion.g>
{/* Gradient definitions */}
<defs>
<linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#8b5cf6" />
<stop offset="100%" stopColor="#6366f1" />
</linearGradient>
<linearGradient id="gradient2" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#2dd4bf" />
<stop offset="100%" stopColor="#06b6d4" />
</linearGradient>
<linearGradient id="gradient3" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#f59e0b" />
<stop offset="100%" stopColor="#ef4444" />
</linearGradient>
<linearGradient id="gradient4" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#10b981" />
<stop offset="100%" stopColor="#14b8a6" />
</linearGradient>
</defs>
</motion.svg>
);
}

83
components/ToolCard.tsx Normal file
View File

@@ -0,0 +1,83 @@
'use client';
import { motion } from 'framer-motion';
import { ReactNode } from 'react';
interface ToolCardProps {
title: string;
description: string;
icon: ReactNode;
url: string;
gradient: string;
index: number;
}
export default function ToolCard({ title, description, icon, url, gradient, index }: ToolCardProps) {
return (
<motion.a
href={url}
target="_blank"
rel="noopener noreferrer"
className="group relative block"
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ y: -10 }}
>
<div className="glass relative overflow-hidden rounded-2xl p-8 h-full transition-all duration-300 hover:shadow-2xl">
{/* Gradient overlay on hover */}
<div
className={`absolute inset-0 opacity-0 group-hover:opacity-10 transition-opacity duration-300 ${gradient}`}
/>
{/* Glow effect */}
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 blur-xl -z-10">
<div className={`w-full h-full ${gradient}`} />
</div>
{/* Icon */}
<motion.div
className="mb-6 flex justify-center"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ type: 'spring', stiffness: 300 }}
>
<div className={`p-4 rounded-xl ${gradient}`}>
{icon}
</div>
</motion.div>
{/* Title */}
<h3 className="text-2xl font-bold mb-3 text-white group-hover:text-transparent group-hover:bg-clip-text group-hover:bg-gradient-to-r group-hover:from-purple-400 group-hover:to-cyan-400 transition-all duration-300">
{title}
</h3>
{/* Description */}
<p className="text-gray-400 group-hover:text-gray-300 transition-colors duration-300">
{description}
</p>
{/* Arrow icon */}
<motion.div
className="absolute bottom-8 right-8 text-gray-600 group-hover:text-purple-400"
initial={{ x: 0 }}
whileHover={{ x: 5 }}
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 7l5 5m0 0l-5 5m5-5H6"
/>
</svg>
</motion.div>
</div>
</motion.a>
);
}

81
components/ToolsGrid.tsx Normal file
View File

@@ -0,0 +1,81 @@
'use client';
import { motion } from 'framer-motion';
import ToolCard from './ToolCard';
const tools = [
{
title: 'Vert',
description: 'Privacy-focused file converter that processes images, audio, and documents locally on your device. No file size limits, completely open source.',
url: 'https://vert.kit.pivoine.art',
gradient: 'gradient-purple-blue',
icon: (
<svg className="w-12 h-12 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
),
},
{
title: 'Paint',
description: 'An advanced image editor running in your browser. Edit photos, create graphics, and more.',
url: 'https://paint.kit.pivoine.art',
gradient: 'gradient-cyan-purple',
icon: (
<svg className="w-12 h-12 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
</svg>
),
},
];
export default function ToolsGrid() {
return (
<section className="relative py-20 px-4">
<div className="max-w-6xl mx-auto">
{/* Section heading */}
<motion.div
className="text-center mb-16"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<h2 className="text-4xl md:text-5xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-purple-400 to-cyan-400">
Available Tools
</h2>
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
Explore our collection of carefully crafted tools designed to boost your productivity and creativity.
</p>
</motion.div>
{/* Tools grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{tools.map((tool, index) => (
<ToolCard
key={tool.title}
title={tool.title}
description={tool.description}
icon={tool.icon}
url={tool.url}
gradient={tool.gradient}
index={index}
/>
))}
</div>
{/* Coming soon hint */}
<motion.div
className="mt-16 text-center"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.4 }}
>
<p className="text-gray-500 text-sm">
More tools coming soon...
</p>
</motion.div>
</div>
</section>
);
}