'use client' import { useState, useCallback, useMemo } from 'react' import { Sparkles, Layers, Box, Type, Zap, ChevronRight, Check, RefreshCw } from 'lucide-react' interface DesignPanelProps { onCodeUpdate?: (code: { html?: string; css?: string; js?: string }) => void } type Category = 'animations' | 'threejs' | 'ui' | 'text' interface Param { label: string key: string type: 'range' | 'color' | 'select' min?: number max?: number step?: number default: number | string options?: string[] unit?: string } interface Preset { id: string name: string description: string category: Category emoji: string params: Param[] generate: (params: Record) => { html?: string; css?: string; js?: string } } const PRESETS: Preset[] = [ // ── ANIMATIONS ────────────────────────────────────────────────────────────── { id: 'fade-in', name: 'Fade In', description: 'Smooth opacity reveal on load', category: 'animations', emoji: '✨', params: [ { label: 'Duration (s)', key: 'duration', type: 'range', min: 0.1, max: 3, step: 0.1, default: 1, unit: 's' }, { label: 'Delay (s)', key: 'delay', type: 'range', min: 0, max: 2, step: 0.1, default: 0.2, unit: 's' }, ], generate: (p) => ({ css: `.fade-in { animation: fadeIn ${p.duration}s ease forwards; animation-delay: ${p.delay}s; opacity: 0; } @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }`, html: `

Fade In Element

This element fades in smoothly on load.

`, }), }, { id: 'pulse-glow', name: 'Pulse Glow', description: 'Rhythmic glowing border pulse', category: 'animations', emoji: '💫', params: [ { label: 'Color', key: 'color', type: 'color', default: '#667eea' }, { label: 'Speed (s)', key: 'speed', type: 'range', min: 0.5, max: 4, step: 0.1, default: 1.5, unit: 's' }, { label: 'Intensity (px)', key: 'blur', type: 'range', min: 5, max: 40, step: 1, default: 20, unit: 'px' }, ], generate: (p) => ({ css: `.pulse-glow { animation: pulseGlow ${p.speed}s ease-in-out infinite; border: 2px solid ${p.color}; border-radius: 12px; padding: 24px; display: inline-block; } @keyframes pulseGlow { 0%, 100% { box-shadow: 0 0 ${p.blur}px ${p.color}; } 50% { box-shadow: 0 0 ${Number(p.blur) * 2}px ${p.color}, 0 0 ${Number(p.blur) * 3}px ${p.color}44; } }`, html: `

Pulse Glow

Glowing border element

`, }), }, { id: 'slide-in', name: 'Slide In', description: 'Direction-based slide entrance', category: 'animations', emoji: '➡️', params: [ { label: 'Direction', key: 'dir', type: 'select', default: 'left', options: ['left', 'right', 'top', 'bottom'] }, { label: 'Duration (s)', key: 'duration', type: 'range', min: 0.2, max: 2, step: 0.1, default: 0.6, unit: 's' }, { label: 'Distance (px)', key: 'dist', type: 'range', min: 20, max: 200, step: 10, default: 60, unit: 'px' }, ], generate: (p) => { const transforms: Record = { left: `translateX(-${p.dist}px)`, right: `translateX(${p.dist}px)`, top: `translateY(-${p.dist}px)`, bottom: `translateY(${p.dist}px)`, } return { css: `.slide-in { animation: slideIn ${p.duration}s cubic-bezier(.22,.68,0,1.2) forwards; opacity: 0; } @keyframes slideIn { from { opacity: 0; transform: ${transforms[p.dir as string]}; } to { opacity: 1; transform: translate(0); } }`, html: `

Slide In from ${p.dir}

Slides in with a smooth easing curve.

`, } }, }, { id: 'stagger-list', name: 'Stagger List', description: 'Items reveal one after another', category: 'animations', emoji: '🎯', params: [ { label: 'Stagger (ms)', key: 'stagger', type: 'range', min: 50, max: 400, step: 10, default: 120, unit: 'ms' }, { label: 'Color', key: 'color', type: 'color', default: '#8b9cff' }, ], generate: (p) => ({ css: `.stagger-item { opacity: 0; transform: translateX(-20px); animation: staggerReveal 0.5s ease forwards; } .stagger-item:nth-child(1) { animation-delay: ${Number(p.stagger) * 0}ms; } .stagger-item:nth-child(2) { animation-delay: ${Number(p.stagger) * 1}ms; } .stagger-item:nth-child(3) { animation-delay: ${Number(p.stagger) * 2}ms; } .stagger-item:nth-child(4) { animation-delay: ${Number(p.stagger) * 3}ms; } @keyframes staggerReveal { to { opacity: 1; transform: translateX(0); } } .stagger-list { list-style: none; padding: 0; } .stagger-list li { padding: 8px 0; border-left: 3px solid ${p.color}; padding-left: 12px; margin-bottom: 8px; }`, html: ``, }), }, // ── THREE.JS ──────────────────────────────────────────────────────────────── { id: 'particle-field', name: 'Particle Field', description: 'Floating 3D particle system', category: 'threejs', emoji: '🌌', params: [ { label: 'Count', key: 'count', type: 'range', min: 100, max: 2000, step: 50, default: 500 }, { label: 'Color', key: 'color', type: 'color', default: '#667eea' }, { label: 'Speed', key: 'speed', type: 'range', min: 0.1, max: 3, step: 0.1, default: 0.5 }, { label: 'Size', key: 'size', type: 'range', min: 0.01, max: 0.2, step: 0.01, default: 0.05 }, ], generate: (p) => ({ html: ``, js: `(function() { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js'; script.onload = function() { const canvas = document.getElementById('particle-canvas'); const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }); renderer.setSize(canvas.clientWidth, canvas.clientHeight); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 1000); camera.position.z = 3; const geometry = new THREE.BufferGeometry(); const positions = new Float32Array(${p.count} * 3); for (let i = 0; i < ${p.count} * 3; i++) positions[i] = (Math.random() - 0.5) * 10; geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); const material = new THREE.PointsMaterial({ color: '${p.color}', size: ${p.size} }); const particles = new THREE.Points(geometry, material); scene.add(particles); function animate() { requestAnimationFrame(animate); particles.rotation.y += ${Number(p.speed) * 0.002}; particles.rotation.x += ${Number(p.speed) * 0.001}; renderer.render(scene, camera); } animate(); }; document.head.appendChild(script); })();`, }), }, { id: 'rotating-cube', name: 'Rotating Cube', description: 'Smooth 3D rotating cube', category: 'threejs', emoji: '🎲', params: [ { label: 'Color', key: 'color', type: 'color', default: '#764ba2' }, { label: 'Speed', key: 'speed', type: 'range', min: 0.1, max: 5, step: 0.1, default: 1 }, { label: 'Wireframe', key: 'wire', type: 'select', default: 'false', options: ['false', 'true'] }, { label: 'Size', key: 'size', type: 'range', min: 0.5, max: 3, step: 0.1, default: 1.5 }, ], generate: (p) => ({ html: ``, js: `(function() { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js'; script.onload = function() { const canvas = document.getElementById('cube-canvas'); const renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); renderer.setSize(canvas.clientWidth, canvas.clientHeight); renderer.setClearColor(0x0a0a0a); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(60, canvas.clientWidth / canvas.clientHeight, 0.1, 100); camera.position.z = 4; const geometry = new THREE.BoxGeometry(${p.size}, ${p.size}, ${p.size}); const material = new THREE.MeshPhongMaterial({ color: '${p.color}', wireframe: ${p.wire} }); const cube = new THREE.Mesh(geometry, material); scene.add(cube); scene.add(new THREE.AmbientLight(0xffffff, 0.4)); const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(5, 5, 5); scene.add(light); function animate() { requestAnimationFrame(animate); cube.rotation.x += ${Number(p.speed) * 0.01}; cube.rotation.y += ${Number(p.speed) * 0.015}; renderer.render(scene, camera); } animate(); }; document.head.appendChild(script); })();`, }), }, { id: 'wave-plane', name: 'Wave Plane', description: 'Animated wave mesh surface', category: 'threejs', emoji: '🌊', params: [ { label: 'Color', key: 'color', type: 'color', default: '#00d4ff' }, { label: 'Amplitude', key: 'amp', type: 'range', min: 0.1, max: 2, step: 0.05, default: 0.5 }, { label: 'Frequency', key: 'freq', type: 'range', min: 0.5, max: 5, step: 0.1, default: 2 }, { label: 'Speed', key: 'speed', type: 'range', min: 0.5, max: 5, step: 0.1, default: 2 }, ], generate: (p) => ({ html: ``, js: `(function() { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js'; script.onload = function() { const canvas = document.getElementById('wave-canvas'); const renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); renderer.setSize(canvas.clientWidth, canvas.clientHeight); renderer.setClearColor(0x0a0a0a); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(60, canvas.clientWidth / canvas.clientHeight, 0.1, 100); camera.position.set(0, 3, 5); camera.lookAt(0, 0, 0); const geo = new THREE.PlaneGeometry(10, 10, 40, 40); const mat = new THREE.MeshBasicMaterial({ color: '${p.color}', wireframe: true }); const plane = new THREE.Mesh(geo, mat); plane.rotation.x = -Math.PI / 4; scene.add(plane); let t = 0; function animate() { requestAnimationFrame(animate); t += 0.01 * ${p.speed}; const pos = geo.attributes.position; for (let i = 0; i < pos.count; i++) { const x = pos.getX(i), y = pos.getY(i); pos.setZ(i, Math.sin(x * ${p.freq} + t) * ${p.amp} + Math.cos(y * ${p.freq} + t) * ${p.amp} * 0.5); } pos.needsUpdate = true; renderer.render(scene, camera); } animate(); }; document.head.appendChild(script); })();`, }), }, // ── UI COMPONENTS ──────────────────────────────────────────────────────────── { id: 'glass-card', name: 'Glass Card', description: 'Frosted glass morphism card', category: 'ui', emoji: '🪟', params: [ { label: 'Blur (px)', key: 'blur', type: 'range', min: 4, max: 40, step: 1, default: 16, unit: 'px' }, { label: 'Opacity', key: 'opacity', type: 'range', min: 0.05, max: 0.4, step: 0.01, default: 0.1 }, { label: 'Border Color', key: 'border', type: 'color', default: '#ffffff' }, { label: 'Radius (px)', key: 'radius', type: 'range', min: 4, max: 40, step: 2, default: 20, unit: 'px' }, ], generate: (p) => ({ css: `.glass-card { background: rgba(255,255,255,${p.opacity}); backdrop-filter: blur(${p.blur}px); -webkit-backdrop-filter: blur(${p.blur}px); border: 1px solid rgba(255,255,255,0.2); border-radius: ${p.radius}px; padding: 32px; box-shadow: 0 8px 32px rgba(0,0,0,0.3); color: #fff; max-width: 360px; }`, html: `

Glass Card

Frosted glass morphism with backdrop blur effect.

`, }), }, { id: 'neon-button', name: 'Neon Button', description: 'Glowing neon CTA button', category: 'ui', emoji: '⚡', params: [ { label: 'Color', key: 'color', type: 'color', default: '#00ff88' }, { label: 'Glow Size (px)', key: 'glow', type: 'range', min: 5, max: 40, step: 1, default: 15, unit: 'px' }, { label: 'Border (px)', key: 'border', type: 'range', min: 1, max: 4, step: 1, default: 2, unit: 'px' }, ], generate: (p) => ({ css: `.neon-btn { background: transparent; color: ${p.color}; border: ${p.border}px solid ${p.color}; padding: 14px 32px; font-size: 1rem; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; cursor: pointer; border-radius: 4px; transition: all 0.3s ease; box-shadow: 0 0 ${p.glow}px ${p.color}88, inset 0 0 ${p.glow}px ${p.color}22; text-shadow: 0 0 8px ${p.color}; } .neon-btn:hover { background: ${p.color}22; box-shadow: 0 0 ${Number(p.glow) * 2}px ${p.color}, inset 0 0 ${p.glow}px ${p.color}44; }`, html: `
`, }), }, { id: 'gradient-card', name: 'Gradient Card', description: 'Animated gradient background card', category: 'ui', emoji: '🎨', params: [ { label: 'Color 1', key: 'c1', type: 'color', default: '#667eea' }, { label: 'Color 2', key: 'c2', type: 'color', default: '#764ba2' }, { label: 'Color 3', key: 'c3', type: 'color', default: '#f64f59' }, { label: 'Speed (s)', key: 'speed', type: 'range', min: 2, max: 12, step: 0.5, default: 6, unit: 's' }, ], generate: (p) => ({ css: `.gradient-card { background: linear-gradient(270deg, ${p.c1}, ${p.c2}, ${p.c3}); background-size: 400% 400%; animation: gradientShift ${p.speed}s ease infinite; border-radius: 20px; padding: 40px; color: white; max-width: 400px; } @keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }`, html: `

Gradient Card

Animated flowing gradient background that shifts between colors.

`, }), }, { id: 'progress-bar', name: 'Animated Progress', description: 'Smooth animated progress bar', category: 'ui', emoji: '📊', params: [ { label: 'Color', key: 'color', type: 'color', default: '#667eea' }, { label: 'Progress (%)', key: 'pct', type: 'range', min: 5, max: 100, step: 1, default: 75 }, { label: 'Height (px)', key: 'h', type: 'range', min: 4, max: 24, step: 2, default: 10, unit: 'px' }, { label: 'Duration (s)', key: 'dur', type: 'range', min: 0.5, max: 3, step: 0.1, default: 1.2, unit: 's' }, ], generate: (p) => ({ css: `.progress-track { background: rgba(255,255,255,0.1); border-radius: 999px; height: ${p.h}px; overflow: hidden; width: 100%; max-width: 400px; } .progress-fill { height: 100%; width: 0; background: linear-gradient(90deg, ${p.color}88, ${p.color}); border-radius: 999px; animation: fillProgress ${p.dur}s cubic-bezier(.4,0,.2,1) forwards; box-shadow: 0 0 12px ${p.color}88; } @keyframes fillProgress { to { width: ${p.pct}%; } }`, html: `

Loading... ${p.pct}%

`, }), }, // ── TEXT EFFECTS ───────────────────────────────────────────────────────────── { id: 'typewriter', name: 'Typewriter', description: 'Character-by-character typing effect', category: 'text', emoji: '⌨️', params: [ { label: 'Speed (ms)', key: 'speed', type: 'range', min: 20, max: 300, step: 10, default: 80, unit: 'ms' }, { label: 'Color', key: 'color', type: 'color', default: '#8b9cff' }, { label: 'Cursor Color', key: 'cursor', type: 'color', default: '#667eea' }, ], generate: (p) => ({ css: `.typewriter-text { color: ${p.color}; font-size: 1.5rem; font-weight: 600; border-right: 3px solid ${p.cursor}; white-space: nowrap; overflow: hidden; display: inline-block; animation: blink 0.75s step-end infinite; } @keyframes blink { 0%,100% { border-color: ${p.cursor}; } 50% { border-color: transparent; } }`, html: `
`, js: `(function(){ const el = document.getElementById('tw-el'); const text = 'Hello, World! 👋'; let i = 0; function type() { if (i < text.length) { el.textContent += text[i++]; setTimeout(type, ${p.speed}); } } type(); })();`, }), }, { id: 'glitch-text', name: 'Glitch Text', description: 'Cyberpunk-style glitch distortion', category: 'text', emoji: '👾', params: [ { label: 'Color', key: 'color', type: 'color', default: '#00ff88' }, { label: 'Speed (s)', key: 'speed', type: 'range', min: 0.5, max: 5, step: 0.1, default: 1.5, unit: 's' }, { label: 'Intensity (px)', key: 'dist', type: 'range', min: 2, max: 20, step: 1, default: 6, unit: 'px' }, ], generate: (p) => ({ css: `.glitch { font-size: 3rem; font-weight: 900; color: ${p.color}; position: relative; display: inline-block; text-transform: uppercase; letter-spacing: 4px; } .glitch::before, .glitch::after { content: attr(data-text); position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .glitch::before { color: #ff0044; animation: glitchTop ${p.speed}s infinite; clip-path: polygon(0 0, 100% 0, 100% 35%, 0 35%); } .glitch::after { color: #00eeff; animation: glitchBot ${p.speed}s infinite; clip-path: polygon(0 65%, 100% 65%, 100% 100%, 0 100%); } @keyframes glitchTop { 0%,90%,100% { transform: translate(0); } 92% { transform: translate(-${p.dist}px, 1px); } 94% { transform: translate(${p.dist}px, -1px); } } @keyframes glitchBot { 0%,90%,100% { transform: translate(0); } 92% { transform: translate(${p.dist}px, 1px); } 94% { transform: translate(-${p.dist}px, -1px); } }`, html: `
GLITCH
`, }), }, { id: 'gradient-text', name: 'Gradient Text', description: 'Animated gradient flowing through text', category: 'text', emoji: '🌈', params: [ { label: 'Color 1', key: 'c1', type: 'color', default: '#667eea' }, { label: 'Color 2', key: 'c2', type: 'color', default: '#f64f59' }, { label: 'Color 3', key: 'c3', type: 'color', default: '#ffd700' }, { label: 'Speed (s)', key: 'speed', type: 'range', min: 1, max: 8, step: 0.5, default: 3, unit: 's' }, ], generate: (p) => ({ css: `.gradient-text { font-size: 3rem; font-weight: 900; background: linear-gradient(90deg, ${p.c1}, ${p.c2}, ${p.c3}, ${p.c1}); background-size: 300% 100%; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; animation: textShift ${p.speed}s linear infinite; display: inline-block; } @keyframes textShift { 0% { background-position: 0% center; } 100% { background-position: 300% center; } }`, html: `
Beautiful Text
`, }), }, { id: 'split-reveal', name: 'Split Reveal', description: 'Words reveal with split-clip animation', category: 'text', emoji: '✂️', params: [ { label: 'Duration (s)', key: 'dur', type: 'range', min: 0.3, max: 1.5, step: 0.05, default: 0.7, unit: 's' }, { label: 'Stagger (ms)', key: 'stagger', type: 'range', min: 50, max: 400, step: 25, default: 150, unit: 'ms' }, { label: 'Color', key: 'color', type: 'color', default: '#ffffff' }, ], generate: (p) => ({ css: `.split-word { display: inline-block; overflow: hidden; vertical-align: top; margin-right: 0.25em; } .split-inner { display: inline-block; transform: translateY(110%); animation: splitReveal ${p.dur}s cubic-bezier(.16,1,.3,1) forwards; color: ${p.color}; font-size: 2.5rem; font-weight: 800; } .split-word:nth-child(1) .split-inner { animation-delay: 0ms; } .split-word:nth-child(2) .split-inner { animation-delay: ${p.stagger}ms; } .split-word:nth-child(3) .split-inner { animation-delay: ${Number(p.stagger) * 2}ms; } .split-word:nth-child(4) .split-inner { animation-delay: ${Number(p.stagger) * 3}ms; } @keyframes splitReveal { to { transform: translateY(0); } }`, html: `
Hello Beautiful World
`, }), }, ] const CATEGORIES: { id: Category; label: string; icon: React.ReactNode }[] = [ { id: 'animations', label: 'Animations', icon: }, { id: 'threejs', label: '3D / Three.js', icon: }, { id: 'ui', label: 'UI Components', icon: }, { id: 'text', label: 'Text Effects', icon: }, ] // Build a full srcdoc HTML string from generated code function buildPreviewDoc(code: { html?: string; css?: string; js?: string }): string { return ` ${code.html || ''}