From 591f726899d53991be304e0f480234880fb033d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Mon, 17 Nov 2025 15:23:00 +0100 Subject: [PATCH] feat: initialize Next.js 16 project with Tailwind CSS 4 and Docker support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 Implementation: - Set up Next.js 16 with React 19, TypeScript 5, and Turbopack - Configure Tailwind CSS 4 with OKLCH color system - Implement dark/light theme support - Create core UI components: Button, Card, Slider, Progress, Toast - Add ThemeToggle component for theme switching - Set up project directory structure for audio editor - Create storage utilities for settings management - Add Dockerfile with multi-stage build (Node + nginx) - Configure nginx for static file serving with caching - Add docker-compose.yml for easy deployment - Configure static export mode for production Tech Stack: - Next.js 16 with Turbopack - React 19 - TypeScript 5 - Tailwind CSS 4 - pnpm 9.0.0 - nginx 1.27 (for Docker deployment) Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .dockerignore | 44 + .gitignore | 53 + Dockerfile | 41 + app/globals.css | 311 +++ app/layout.tsx | 40 + app/page.tsx | 171 ++ components/layout/ThemeToggle.tsx | 43 + components/ui/Button.tsx | 47 + components/ui/Card.tsx | 78 + components/ui/Progress.tsx | 58 + components/ui/Slider.tsx | 81 + components/ui/Toast.tsx | 134 + docker-compose.yml | 27 + lib/storage/settings.ts | 72 + lib/utils/cn.ts | 11 + next.config.ts | 28 + nginx.conf | 94 + package.json | 31 + pnpm-lock.yaml | 4070 +++++++++++++++++++++++++++++ tsconfig.json | 41 + 20 files changed, 5475 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 components/layout/ThemeToggle.tsx create mode 100644 components/ui/Button.tsx create mode 100644 components/ui/Card.tsx create mode 100644 components/ui/Progress.tsx create mode 100644 components/ui/Slider.tsx create mode 100644 components/ui/Toast.tsx create mode 100644 docker-compose.yml create mode 100644 lib/storage/settings.ts create mode 100644 lib/utils/cn.ts create mode 100644 next.config.ts create mode 100644 nginx.conf create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5d833b9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,44 @@ +# Dependencies +node_modules +npm-debug.log +yarn-error.log +pnpm-debug.log + +# Next.js +.next +out + +# Environment variables +.env +.env*.local + +# Git +.git +.gitignore + +# IDE +.vscode +.idea +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Documentation +README.md +PLAN.md +*.md + +# Docker +Dockerfile +docker-compose.yml +.dockerignore + +# Testing +coverage +.nyc_output + +# Misc +*.log diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6dbd7ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +.pnpm-debug.log* + +# local env files +.env*.local +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +Thumbs.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..685fe12 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# Multi-stage build for Next.js static export + +# Stage 1: Build the application +FROM node:22-alpine AS builder + +# Install pnpm +RUN corepack enable && corepack prepare pnpm@9.0.0 --activate + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml* ./ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy application files +COPY . . + +# Build the Next.js application (static export) +RUN pnpm build + +# Stage 2: Production server with nginx +FROM nginx:1.27-alpine AS runner + +# Copy custom nginx configuration +COPY nginx.conf /etc/nginx/nginx.conf + +# Copy built static files from builder stage +COPY --from=builder /app/out /usr/share/nginx/html + +# Expose port 80 +EXPOSE 80 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..66f4ba1 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,311 @@ +@import "tailwindcss"; + +/* Source directives - scan components for Tailwind classes */ +@source "../components/editor/*.{js,ts,jsx,tsx}"; +@source "../components/effects/*.{js,ts,jsx,tsx}"; +@source "../components/tracks/*.{js,ts,jsx,tsx}"; +@source "../components/automation/*.{js,ts,jsx,tsx}"; +@source "../components/analysis/*.{js,ts,jsx,tsx}"; +@source "../components/recording/*.{js,ts,jsx,tsx}"; +@source "../components/export/*.{js,ts,jsx,tsx}"; +@source "../components/project/*.{js,ts,jsx,tsx}"; +@source "../components/layout/*.{js,ts,jsx,tsx}"; +@source "../components/ui/*.{js,ts,jsx,tsx}"; +@source "*.{js,ts,jsx,tsx}"; + +/* Custom dark mode variant */ +@custom-variant dark (&:is(.dark *)); + +/* CSS Variables for theming */ +@layer base { + :root { + /* Light mode colors using OKLCH */ + --background: oklch(100% 0 0); + --foreground: oklch(9.8% 0.038 285.8); + + --card: oklch(100% 0 0); + --card-foreground: oklch(9.8% 0.038 285.8); + + --popover: oklch(100% 0 0); + --popover-foreground: oklch(9.8% 0.038 285.8); + + --primary: oklch(22.4% 0.053 285.8); + --primary-foreground: oklch(98% 0 0); + + --secondary: oklch(96.1% 0 0); + --secondary-foreground: oklch(13.8% 0.038 285.8); + + --muted: oklch(96.1% 0 0); + --muted-foreground: oklch(45.1% 0.015 285.9); + + --accent: oklch(96.1% 0 0); + --accent-foreground: oklch(13.8% 0.038 285.8); + + --destructive: oklch(60.2% 0.168 29.2); + --destructive-foreground: oklch(98% 0 0); + + --border: oklch(89.8% 0 0); + --input: oklch(89.8% 0 0); + --ring: oklch(22.4% 0.053 285.8); + + --radius: 0.5rem; + + --success: oklch(60% 0.15 145); + --success-foreground: oklch(98% 0 0); + + --warning: oklch(75% 0.15 85); + --warning-foreground: oklch(20% 0 0); + + --info: oklch(65% 0.15 240); + --info-foreground: oklch(98% 0 0); + + /* Audio-specific colors */ + --waveform: oklch(50% 0.1 240); + --waveform-progress: oklch(60% 0.15 145); + --waveform-selection: oklch(65% 0.15 240); + --waveform-bg: oklch(98% 0 0); + } + + .dark { + /* Dark mode colors using OKLCH */ + --background: oklch(9.8% 0.038 285.8); + --foreground: oklch(98% 0 0); + + --card: oklch(9.8% 0.038 285.8); + --card-foreground: oklch(98% 0 0); + + --popover: oklch(9.8% 0.038 285.8); + --popover-foreground: oklch(98% 0 0); + + --primary: oklch(98% 0 0); + --primary-foreground: oklch(13.8% 0.038 285.8); + + --secondary: oklch(17.7% 0.038 285.8); + --secondary-foreground: oklch(98% 0 0); + + --muted: oklch(17.7% 0.038 285.8); + --muted-foreground: oklch(63.9% 0.012 285.9); + + --accent: oklch(17.7% 0.038 285.8); + --accent-foreground: oklch(98% 0 0); + + --destructive: oklch(50% 0.2 29.2); + --destructive-foreground: oklch(98% 0 0); + + --border: oklch(17.7% 0.038 285.8); + --input: oklch(17.7% 0.038 285.8); + --ring: oklch(83.1% 0.012 285.9); + + --success: oklch(55% 0.15 145); + --success-foreground: oklch(98% 0 0); + + --warning: oklch(70% 0.15 85); + --warning-foreground: oklch(20% 0 0); + + --info: oklch(60% 0.15 240); + --info-foreground: oklch(98% 0 0); + + /* Audio-specific colors */ + --waveform: oklch(70% 0.15 240); + --waveform-progress: oklch(65% 0.15 145); + --waveform-selection: oklch(70% 0.15 240); + --waveform-bg: oklch(12% 0.038 285.8); + } +} + +/* Theme inline - map CSS variables to Tailwind colors */ +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-success: var(--success); + --color-success-foreground: var(--success-foreground); + --color-warning: var(--warning); + --color-warning-foreground: var(--warning-foreground); + --color-info: var(--info); + --color-info-foreground: var(--info-foreground); + --color-waveform: var(--waveform); + --color-waveform-progress: var(--waveform-progress); + --color-waveform-selection: var(--waveform-selection); + --color-waveform-bg: var(--waveform-bg); + + --radius: var(--radius); +} + +/* Global styles */ +@layer base { + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + font-feature-settings: "rlig" 1, "calt" 1; + } +} + +/* Custom animations */ +@layer utilities { + @keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + @keyframes slideInFromRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } + } + + @keyframes slideDown { + from { + transform: translateY(-10px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } + } + + @keyframes slideUp { + from { + transform: translateY(10px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } + } + + @keyframes scaleIn { + from { + transform: scale(0.95); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } + } + + @keyframes pulseSubtle { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.8; + } + } + + @keyframes shimmer { + 0% { + background-position: -1000px 0; + } + 100% { + background-position: 1000px 0; + } + } + + @keyframes progress { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } + } + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + .animate-fadeIn { + animation: fadeIn 0.2s ease-out; + } + + .animate-slideInFromRight { + animation: slideInFromRight 0.3s ease-out; + } + + .animate-slideDown { + animation: slideDown 0.3s ease-out; + } + + .animate-slideUp { + animation: slideUp 0.3s ease-out; + } + + .animate-scaleIn { + animation: scaleIn 0.2s ease-out; + } + + .animate-pulseSubtle { + animation: pulseSubtle 2s ease-in-out infinite; + } + + .animate-shimmer { + animation: shimmer 2s linear infinite; + } + + .animate-progress { + animation: progress 1.5s ease-in-out infinite; + } + + .animate-spin { + animation: spin 1s linear infinite; + } +} + +/* Custom scrollbar */ +@layer utilities { + .custom-scrollbar::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + .custom-scrollbar::-webkit-scrollbar-track { + @apply bg-muted; + border-radius: 4px; + } + + .custom-scrollbar::-webkit-scrollbar-thumb { + @apply bg-muted-foreground/30; + border-radius: 4px; + } + + .custom-scrollbar::-webkit-scrollbar-thumb:hover { + @apply bg-muted-foreground/50; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..09ec0ec --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,40 @@ +import type { Metadata } from 'next'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'Audio UI - Browser Audio Editor', + description: 'Professional audio editing in your browser. Multi-track editing, effects, recording, and more.', + keywords: ['audio editor', 'waveform editor', 'web audio', 'daw', 'music production', 'audio effects'], +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + +