From 37920401b368e2152b66b81db32db3fc7bd3aae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Thu, 20 Nov 2025 21:12:38 +0100 Subject: [PATCH] feat: complete Phase 1 - project foundation with Next.js 16 and Tailwind CSS 4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initialize modern tech stack following audio-ui patterns: **Framework Setup** - Next.js 16 with App Router and Turbopack (ready in 754ms) - React 19 with TypeScript 5 - Static export configuration for deployment **Styling System** - Tailwind CSS 4 with @tailwindcss/postcss - OKLCH color space for vibrant, perceptually uniform colors - Custom CSS variables for theming (light/dark modes) - Canvas-specific color palette (canvas-bg, canvas-grid, canvas-selection) - Custom animations (fadeIn, slideDown, scaleIn, etc.) - Checkerboard pattern utility for transparency preview - Custom scrollbar styling **State Management** - Zustand installed for layers, canvas, and history state **Canvas Libraries** - pica for high-quality image resizing - file-saver for export functionality - uuid for layer ID generation **Development Experience** - Path aliases (@/* pattern) configured - Strict TypeScript with proper type checking - Auto dark mode detection with localStorage persistence - Responsive layout with overflow handling **Initial UI** - Root layout with theme system - Landing page showing Phase 1 completion status - Clean, modern design with gradient title Ready for Phase 2: Core Canvas Engine implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/globals.css | 338 ++++ app/layout.tsx | 40 + app/page.tsx | 45 + next.config.ts | 22 + package.json | 36 + pnpm-lock.yaml | 4145 +++++++++++++++++++++++++++++++++++++++++++++ postcss.config.js | 8 + tsconfig.json | 41 + 8 files changed, 4675 insertions(+) create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 next.config.ts create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 postcss.config.js create mode 100644 tsconfig.json diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..a3351ce --- /dev/null +++ b/app/globals.css @@ -0,0 +1,338 @@ +@import "tailwindcss"; + +/* Source directives - scan components for Tailwind classes */ +@source "../components/editor/*.{js,ts,jsx,tsx}"; +@source "../components/canvas/*.{js,ts,jsx,tsx}"; +@source "../components/tools/*.{js,ts,jsx,tsx}"; +@source "../components/layers/*.{js,ts,jsx,tsx}"; +@source "../components/effects/*.{js,ts,jsx,tsx}"; +@source "../components/colors/*.{js,ts,jsx,tsx}"; +@source "../components/modals/*.{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 - creative palette for design tools */ + --background: oklch(98% 0.02 220); + --foreground: oklch(18% 0.08 270); + + --card: oklch(99% 0.01 220); + --card-foreground: oklch(18% 0.08 270); + + --popover: oklch(99% 0.01 220); + --popover-foreground: oklch(18% 0.08 270); + + --primary: oklch(56% 0.25 265); + --primary-foreground: oklch(99% 0.01 220); + + --secondary: oklch(92% 0.06 240); + --secondary-foreground: oklch(22% 0.12 270); + + --muted: oklch(94% 0.04 230); + --muted-foreground: oklch(42% 0.10 260); + + --accent: oklch(88% 0.10 200); + --accent-foreground: oklch(22% 0.15 270); + + --destructive: oklch(58% 0.26 25); + --destructive-foreground: oklch(99% 0.01 220); + + --border: oklch(86% 0.06 230); + --input: oklch(92% 0.05 230); + --ring: oklch(56% 0.25 265); + + --radius: 0.5rem; + + --success: oklch(58% 0.22 150); + --success-foreground: oklch(99% 0.01 220); + + --warning: oklch(68% 0.22 80); + --warning-foreground: oklch(18% 0.08 270); + + --info: oklch(60% 0.22 240); + --info-foreground: oklch(99% 0.01 220); + + /* Canvas-specific colors */ + --canvas-bg: oklch(96% 0.01 220); + --canvas-grid: oklch(88% 0.03 230); + --canvas-selection: oklch(56% 0.25 265 / 0.3); + --canvas-selection-border: oklch(56% 0.25 265); + --ruler-bg: oklch(94% 0.03 220); + --ruler-fg: oklch(40% 0.08 260); + } + + .dark { + /* Dark mode colors using OKLCH - professional design tool palette */ + --background: oklch(16% 0.015 270); + --foreground: oklch(92% 0.02 220); + + --card: oklch(19% 0.02 275); + --card-foreground: oklch(92% 0.02 220); + + --popover: oklch(19% 0.02 275); + --popover-foreground: oklch(92% 0.02 220); + + --primary: oklch(72% 0.22 270); + --primary-foreground: oklch(19% 0.02 275); + + --secondary: oklch(23% 0.03 280); + --secondary-foreground: oklch(85% 0.12 220); + + --muted: oklch(21% 0.02 275); + --muted-foreground: oklch(64% 0.08 240); + + --accent: oklch(26% 0.03 285); + --accent-foreground: oklch(82% 0.18 260); + + --destructive: oklch(62% 0.24 25); + --destructive-foreground: oklch(92% 0.02 220); + + --border: oklch(32% 0.04 280); + --input: oklch(23% 0.03 280); + --ring: oklch(72% 0.22 270); + + --success: oklch(68% 0.20 150); + --success-foreground: oklch(19% 0.02 275); + + --warning: oklch(72% 0.20 80); + --warning-foreground: oklch(19% 0.02 275); + + --info: oklch(68% 0.20 240); + --info-foreground: oklch(19% 0.02 275); + + /* Canvas-specific colors */ + --canvas-bg: oklch(14% 0.015 270); + --canvas-grid: oklch(28% 0.03 280); + --canvas-selection: oklch(72% 0.22 270 / 0.25); + --canvas-selection-border: oklch(72% 0.22 270); + --ruler-bg: oklch(18% 0.02 275); + --ruler-fg: oklch(68% 0.08 240); + } +} + +/* 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-canvas-bg: var(--canvas-bg); + --color-canvas-grid: var(--canvas-grid); + --color-canvas-selection: var(--canvas-selection); + --color-canvas-selection-border: var(--canvas-selection-border); + --color-ruler-bg: var(--ruler-bg); + --color-ruler-fg: var(--ruler-fg); + + --radius: var(--radius); +} + +/* Global styles */ +@layer base { + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + font-feature-settings: "rlig" 1, "calt" 1; + } + + /* Apply custom scrollbar globally */ + * { + scrollbar-width: thin; + } + + *::-webkit-scrollbar { + width: 10px; + height: 10px; + } + + *::-webkit-scrollbar-track { + background: var(--muted); + border-radius: 5px; + } + + *::-webkit-scrollbar-thumb { + background: color-mix(in oklch, var(--muted-foreground) 30%, transparent); + border-radius: 5px; + border: 2px solid var(--muted); + transition: background 0.2s ease; + } + + *::-webkit-scrollbar-thumb:hover { + background: color-mix(in oklch, var(--muted-foreground) 50%, transparent); + } + + *::-webkit-scrollbar-thumb:active { + background: color-mix(in oklch, var(--muted-foreground) 70%, transparent); + } + + /* Scrollbar corners */ + *::-webkit-scrollbar-corner { + background: var(--muted); + } +} + +/* 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 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-spin { + animation: spin 1s linear infinite; + } +} + +/* Canvas-specific utilities */ +@layer utilities { + .checkerboard { + background-image: + linear-gradient(45deg, oklch(86% 0.01 220) 25%, transparent 25%), + linear-gradient(-45deg, oklch(86% 0.01 220) 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, oklch(86% 0.01 220) 75%), + linear-gradient(-45deg, transparent 75%, oklch(86% 0.01 220) 75%); + background-size: 20px 20px; + background-position: 0 0, 0 10px, 10px -10px, -10px 0px; + } + + .dark .checkerboard { + background-image: + linear-gradient(45deg, oklch(22% 0.02 270) 25%, transparent 25%), + linear-gradient(-45deg, oklch(22% 0.02 270) 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, oklch(22% 0.02 270) 75%), + linear-gradient(-45deg, transparent 75%, oklch(22% 0.02 270) 75%); + background-size: 20px 20px; + background-position: 0 0, 0 10px, 10px -10px, -10px 0px; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..11340dd --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,40 @@ +import type { Metadata } from 'next'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'Paint UI - Browser Image Editor', + description: 'Modern browser-based image editor built with Next.js. Multi-layer canvas, drawing tools, effects, and more.', + keywords: ['image editor', 'canvas', 'drawing', 'paint', 'layers', 'effects', 'photoshop alternative'], +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + +