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

12
.dockerignore Normal file
View File

@@ -0,0 +1,12 @@
node_modules
.next
out
.git
.gitignore
README.md
.env*.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store
*.pem

36
.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

31
Dockerfile Normal file
View File

@@ -0,0 +1,31 @@
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY package.json pnpm-lock.yaml* ./
# Install pnpm and dependencies
RUN corepack enable pnpm && pnpm install --frozen-lockfile
# Copy source code
COPY . .
# Build the application
RUN pnpm build
# Production stage
FROM nginx:alpine
# Copy custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Copy built static files from builder
COPY --from=builder /app/out /usr/share/nginx/html
# Expose port 80
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

129
README.md Normal file
View File

@@ -0,0 +1,129 @@
# Kit Landing Page
A stylish, animated landing page for [kit.pivoine.art](https://kit.pivoine.art) - your creative toolkit.
## Features
-**Animated UI** - Smooth animations with Framer Motion
- 🎨 **Modern Design** - Glassmorphism effects, gradients, and animated backgrounds
- 📱 **Responsive** - Mobile-first design that works on all devices
-**Fast** - Static export with Next.js 16 and Turbopack for optimal performance
- 🎯 **SEO Optimized** - Proper meta tags and semantic HTML
- 🚀 **Production Ready** - Docker support with Nginx
## Tech Stack
- **Next.js 16** - React framework with App Router and Turbopack
- **React 19** - Latest React with modern features
- **Tailwind CSS 4** - Utility-first CSS with CSS-first configuration
- **Framer Motion** - Professional animation library
- **TypeScript 5** - Type-safe development
- **ESLint 9** - Latest linting with flat config
- **pnpm** - Fast, efficient package manager
## Getting Started
### Prerequisites
- Node.js 20+
- pnpm (via corepack)
### Development
```bash
# Install dependencies
pnpm install
# Run development server
pnpm dev
# Build for production
pnpm build
# Preview production build locally
pnpm start
```
Visit [http://localhost:3000](http://localhost:3000) to see the site.
## Docker Deployment
Build and run with Docker:
```bash
# Build the image
docker build -t kit-landing .
# Run the container
docker run -p 80:80 kit-landing
```
Or with docker-compose (see `/home/valknar/Projects/docker-compose/kit/compose.yaml`).
## Project Structure
```
.
├── app/
│ ├── layout.tsx # Root layout with metadata
│ ├── page.tsx # Home page
│ └── globals.css # Global styles and utilities
├── components/
│ ├── AnimatedBackground.tsx # Animated gradient background
│ ├── Hero.tsx # Hero section with logo
│ ├── Logo.tsx # Animated SVG logo
│ ├── ToolCard.tsx # Tool card component
│ ├── ToolsGrid.tsx # Grid of available tools
│ └── Footer.tsx # Footer component
├── public/ # Static assets
├── Dockerfile # Multi-stage Docker build
├── nginx.conf # Nginx configuration
└── next.config.ts # Next.js configuration
```
## Customization
### Adding New Tools
Edit `components/ToolsGrid.tsx` and add a new tool object to the `tools` array:
```typescript
{
title: 'Tool Name',
description: 'Tool description',
url: 'https://tool.kit.pivoine.art',
gradient: 'gradient-purple-blue', // or 'gradient-cyan-purple'
icon: (
// Your SVG icon here
),
}
```
### Styling
Tailwind CSS 4 uses a new CSS-first configuration approach:
- **Theme customization**: Edit the `@theme` block in `app/globals.css`
- **Custom utilities**: Add `@utility` blocks in `app/globals.css`
- **Animations**: Define keyframes directly in the `@theme` block
- **Colors & fonts**: Configure via CSS custom properties in `@theme`
## Available Tools
- **Vert** - Minimalist pastebin for code snippets
- **Paint** - Browser-based image editor
## Performance
- Static export for fast loading
- Optimized images and assets
- Gzip compression via Nginx
- Proper caching headers
## License
Private project - All rights reserved.
## Author
Created for [pivoine.art](https://pivoine.art)

70
app/globals.css Normal file
View File

@@ -0,0 +1,70 @@
@import "tailwindcss";
@source "../components/*.{js,ts,jsx,tsx}";
@source "../components/ui/*.{js,ts,jsx,tsx}";
@source "*.{js,ts,jsx,tsx}";
@theme {
--color-background: #0a0a0f;
--color-foreground: #ffffff;
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
/* Custom animations */
--animate-gradient: gradient 8s linear infinite;
--animate-float: float 3s ease-in-out infinite;
--animate-glow: glow 2s ease-in-out infinite alternate;
@keyframes gradient {
0%, 100% {
background-size: 200% 200%;
background-position: left center;
}
50% {
background-size: 200% 200%;
background-position: right center;
}
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
@keyframes glow {
from {
box-shadow: 0 0 20px rgba(139, 92, 246, 0.3);
}
to {
box-shadow: 0 0 30px rgba(139, 92, 246, 0.6), 0 0 40px rgba(139, 92, 246, 0.3);
}
}
}
body {
color: var(--color-foreground);
background: var(--color-background);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@utility text-balance {
text-wrap: balance;
}
@utility glass {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
@utility gradient-purple-blue {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
@utility gradient-cyan-purple {
background: linear-gradient(135deg, #2dd4bf 0%, #8b5cf6 100%);
}

22
app/layout.tsx Normal file
View File

@@ -0,0 +1,22 @@
import type { Metadata } from 'next';
import './globals.css';
export const metadata: Metadata = {
title: 'Kit - Your Creative Toolkit',
description: 'A curated collection of creative and utility tools for developers and creators',
keywords: ['tools', 'utilities', 'pastebin', 'paint', 'creative toolkit'],
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className="antialiased">
{children}
</body>
</html>
);
}

15
app/page.tsx Normal file
View File

@@ -0,0 +1,15 @@
import AnimatedBackground from '@/components/AnimatedBackground';
import Hero from '@/components/Hero';
import ToolsGrid from '@/components/ToolsGrid';
import Footer from '@/components/Footer';
export default function Home() {
return (
<main className="relative min-h-screen">
<AnimatedBackground />
<Hero />
<ToolsGrid />
<Footer />
</main>
);
}

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>
);
}

16
eslint.config.mjs Normal file
View File

@@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals"),
];
export default eslintConfig;

10
next.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'export',
images: {
unoptimized: true,
},
};
export default nextConfig;

56
nginx.conf Normal file
View File

@@ -0,0 +1,56 @@
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Performance
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}

28
package.json Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "kit.pivoine.art",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"framer-motion": "^11.11.17",
"next": "^16.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.17",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9.39.1",
"eslint-config-next": "^16.0.1",
"postcss": "^8",
"tailwindcss": "^4.0.0",
"typescript": "^5"
}
}

4067
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

8
postcss.config.mjs Normal file
View File

@@ -0,0 +1,8 @@
/** @type {import('postcss').Config} */
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
};
export default config;

9
tailwind.config.ts Normal file
View File

@@ -0,0 +1,9 @@
// Tailwind CSS 4 configuration
// Most configuration is now done in CSS using @theme
export default {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
};

41
tsconfig.json Normal file
View File

@@ -0,0 +1,41 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}