// Starter template gallery data export const GALLERY_TEMPLATES = [ { id: 'todo-app', title: 'Todo App', description: 'Classic todo list with add, complete, and delete', mode: 'html-css-js', icon: '\u2713', html: `

Todo List

`, css: `body { font-family: -apple-system, sans-serif; background: #f0f2f5; display: flex; justify-content: center; padding: 40px 20px; } .todo-app { background: #fff; border-radius: 12px; padding: 24px; width: 100%; max-width: 400px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } h1 { margin: 0 0 16px; font-size: 24px; color: #1a1a1a; } #todo-form { display: flex; gap: 8px; margin-bottom: 16px; } #todo-input { flex: 1; padding: 10px 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 14px; outline: none; } #todo-input:focus { border-color: #0078d4; } button { background: #0078d4; color: #fff; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; } button:hover { background: #106ebe; } ul { list-style: none; padding: 0; } li { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid #f0f0f0; } li.done span { text-decoration: line-through; color: #999; } li span { flex: 1; font-size: 14px; } li .delete { background: none; color: #e74c3c; padding: 4px 8px; font-size: 18px; } li .delete:hover { background: #fde8e8; border-radius: 4px; }`, js: `const form = document.getElementById('todo-form'); const input = document.getElementById('todo-input'); const list = document.getElementById('todo-list'); form.addEventListener('submit', (e) => { e.preventDefault(); const text = input.value.trim(); if (!text) return; addTodo(text); input.value = ''; }); function addTodo(text) { const li = document.createElement('li'); li.innerHTML = \`\${text}\`; li.querySelector('input').addEventListener('change', () => li.classList.toggle('done')); li.querySelector('.delete').addEventListener('click', () => li.remove()); list.appendChild(li); }`, }, { id: 'api-fetch', title: 'API Fetch', description: 'Fetch data from a REST API and display results', mode: 'html-css-js', icon: '\u21C5', html: `

Random Users

`, css: `body { font-family: -apple-system, sans-serif; background: #f5f5f5; padding: 40px 20px; } .container { max-width: 600px; margin: 0 auto; } h1 { margin: 0 0 16px; color: #333; } button { background: #0078d4; color: #fff; border: none; padding: 10px 24px; border-radius: 8px; cursor: pointer; font-size: 14px; margin-bottom: 20px; } button:hover { background: #106ebe; } .users { display: flex; flex-direction: column; gap: 12px; } .user-card { display: flex; align-items: center; gap: 14px; background: #fff; padding: 14px; border-radius: 10px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); } .user-card img { width: 50px; height: 50px; border-radius: 50%; } .user-info h3 { margin: 0; font-size: 15px; color: #1a1a1a; } .user-info p { margin: 4px 0 0; font-size: 13px; color: #666; }`, js: `document.getElementById('btn-fetch').addEventListener('click', fetchUsers); async function fetchUsers() { const container = document.getElementById('users'); container.innerHTML = '

Loading...

'; try { const res = await fetch('https://randomuser.me/api/?results=5'); const data = await res.json(); container.innerHTML = data.results.map(u => \`
\${u.name.first}

\${u.name.first} \${u.name.last}

\${u.email}

\`).join(''); } catch (e) { container.innerHTML = '

Error fetching users.

'; } } fetchUsers();`, }, { id: 'css-animation', title: 'CSS Animation', description: 'Animated shapes with pure CSS keyframes', mode: 'html-css-js', icon: '\u25CF', html: `
`, css: `body { margin: 0; overflow: hidden; background: #0a0a2e; display: flex; align-items: center; justify-content: center; height: 100vh; } .scene { position: relative; width: 300px; height: 300px; } .sun { position: absolute; top: 50%; left: 50%; width: 60px; height: 60px; margin: -30px 0 0 -30px; background: radial-gradient(circle, #ffd700, #ff8c00); border-radius: 50%; box-shadow: 0 0 40px #ffd700, 0 0 80px #ff8c0066; animation: pulse 2s ease-in-out infinite; } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } } .orbit { position: absolute; top: 50%; left: 50%; width: 200px; height: 200px; margin: -100px 0 0 -100px; border: 1px solid rgba(255,255,255,0.1); border-radius: 50%; animation: spin 6s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .planet { position: absolute; top: -10px; left: 50%; margin-left: -10px; width: 20px; height: 20px; background: radial-gradient(circle, #4fc3f7, #0288d1); border-radius: 50%; box-shadow: 0 0 12px #4fc3f766; } .stars { position: absolute; inset: -50px; background-image: radial-gradient(2px 2px at 20px 30px, #fff, transparent), radial-gradient(2px 2px at 80px 120px, #fff, transparent), radial-gradient(1px 1px at 160px 60px, #ddd, transparent), radial-gradient(2px 2px at 240px 180px, #fff, transparent), radial-gradient(1px 1px at 50px 200px, #ccc, transparent), radial-gradient(1px 1px at 190px 20px, #eee, transparent); background-repeat: repeat; animation: twinkle 4s ease-in-out infinite alternate; } @keyframes twinkle { from { opacity: 0.6; } to { opacity: 1; } }`, js: `// Pure CSS animation - no JS needed! console.log('CSS animation running');`, }, { id: 'form-validation', title: 'Form Validation', description: 'Client-side form validation with live feedback', mode: 'html-css-js', icon: '\u2611', html: `

Sign Up

`, css: `body { font-family: -apple-system, sans-serif; background: #f0f2f5; display: flex; justify-content: center; padding: 40px 20px; } .form-wrap { background: #fff; padding: 32px; border-radius: 12px; width: 100%; max-width: 380px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } h1 { margin: 0 0 20px; font-size: 22px; } .field { margin-bottom: 16px; } label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 4px; color: #333; } input { width: 100%; padding: 10px 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 14px; outline: none; box-sizing: border-box; } input:focus { border-color: #0078d4; } input.invalid { border-color: #e74c3c; } input.valid { border-color: #27ae60; } .error { font-size: 12px; color: #e74c3c; min-height: 18px; display: block; } button { width: 100%; background: #0078d4; color: #fff; border: none; padding: 12px; border-radius: 8px; font-size: 15px; cursor: pointer; } button:hover { background: #106ebe; } .success { text-align: center; color: #27ae60; font-size: 16px; font-weight: 600; padding: 16px 0; } .hidden { display: none; }`, js: `const form = document.getElementById('signup'); const fields = { name: { el: document.getElementById('name'), err: document.getElementById('name-error'), validate: v => v.length >= 2 ? '' : 'Name must be at least 2 characters' }, email: { el: document.getElementById('email'), err: document.getElementById('email-error'), validate: v => /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(v) ? '' : 'Enter a valid email' }, password: { el: document.getElementById('password'), err: document.getElementById('password-error'), validate: v => v.length >= 8 ? '' : 'Password must be at least 8 characters' }, }; Object.values(fields).forEach(f => { f.el.addEventListener('input', () => { const msg = f.validate(f.el.value); f.err.textContent = msg; f.el.classList.toggle('invalid', !!msg); f.el.classList.toggle('valid', !msg && f.el.value.length > 0); }); }); form.addEventListener('submit', (e) => { e.preventDefault(); let valid = true; Object.values(fields).forEach(f => { const msg = f.validate(f.el.value); f.err.textContent = msg; f.el.classList.toggle('invalid', !!msg); f.el.classList.toggle('valid', !msg && f.el.value.length > 0); if (msg) valid = false; }); if (valid) { form.classList.add('hidden'); document.getElementById('success').classList.remove('hidden'); } });`, }, { id: 'react-counter', title: 'React Counter', description: 'Stateful React component with hooks', mode: 'react', icon: '\u269B', html: '', css: `body { font-family: -apple-system, sans-serif; background: #f0f2f5; display: flex; justify-content: center; padding: 60px 20px; } .counter { background: #fff; padding: 40px; border-radius: 16px; text-align: center; box-shadow: 0 2px 12px rgba(0,0,0,0.1); min-width: 280px; } h1 { margin: 0 0 8px; font-size: 22px; color: #333; } .count { font-size: 64px; font-weight: 700; color: #0078d4; margin: 20px 0; } .buttons { display: flex; gap: 12px; justify-content: center; } .buttons button { font-size: 20px; width: 48px; height: 48px; border-radius: 50%; border: 2px solid #0078d4; background: #fff; color: #0078d4; cursor: pointer; display: flex; align-items: center; justify-content: center; } .buttons button:hover { background: #0078d4; color: #fff; } .reset { margin-top: 16px; font-size: 13px; background: none; border: none; color: #999; cursor: pointer; text-decoration: underline; }`, js: `const App = () => { const [count, setCount] = React.useState(0); return (

React Counter

{count}
); }; ReactDOM.createRoot(document.getElementById('root')).render();`, }, { id: 'canvas-game', title: 'Canvas Game', description: 'Simple bouncing ball canvas animation', mode: 'html-css-js', icon: '\u25B6', html: ``, css: `body { margin: 0; background: #1a1a2e; display: flex; align-items: center; justify-content: center; height: 100vh; } canvas { border-radius: 8px; background: #16213e; box-shadow: 0 4px 20px rgba(0,0,0,0.3); }`, js: `const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const balls = Array.from({ length: 8 }, () => ({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, r: 8 + Math.random() * 16, dx: (Math.random() - 0.5) * 4, dy: (Math.random() - 0.5) * 4, color: \`hsl(\${Math.random() * 360}, 70%, 60%)\`, })); function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (const b of balls) { b.x += b.dx; b.y += b.dy; if (b.x - b.r < 0 || b.x + b.r > canvas.width) b.dx *= -1; if (b.y - b.r < 0 || b.y + b.r > canvas.height) b.dy *= -1; ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2); ctx.fillStyle = b.color; ctx.fill(); ctx.shadowColor = b.color; ctx.shadowBlur = 12; } ctx.shadowBlur = 0; requestAnimationFrame(draw); } draw();`, }, { id: 'analog-clock', title: 'Analog Clock', description: 'Real-time analog clock with CSS and JS', mode: 'html-css-js', icon: '\u23F0', html: `
`, css: `body { margin: 0; background: #1a1a2e; display: flex; align-items: center; justify-content: center; height: 100vh; } .clock { position: relative; width: 200px; height: 200px; border: 4px solid #e0e0e0; border-radius: 50%; background: #fff; } .hand { position: absolute; bottom: 50%; left: 50%; transform-origin: bottom center; border-radius: 4px; } .hour { width: 4px; height: 50px; background: #333; margin-left: -2px; } .minute { width: 3px; height: 70px; background: #555; margin-left: -1.5px; } .second { width: 1.5px; height: 80px; background: #e74c3c; margin-left: -0.75px; } .center { position: absolute; top: 50%; left: 50%; width: 10px; height: 10px; margin: -5px 0 0 -5px; background: #333; border-radius: 50%; z-index: 2; }`, js: `function tick() { const now = new Date(); const s = now.getSeconds() + now.getMilliseconds() / 1000; const m = now.getMinutes() + s / 60; const h = (now.getHours() % 12) + m / 60; document.getElementById('second').style.transform = \`rotate(\${s * 6}deg)\`; document.getElementById('minute').style.transform = \`rotate(\${m * 6}deg)\`; document.getElementById('hour').style.transform = \`rotate(\${h * 30}deg)\`; requestAnimationFrame(tick); } tick();`, }, { id: 'svelte-reactive', title: 'Svelte Reactive', description: 'Svelte reactivity with bound inputs and computed values', mode: 'svelte', icon: '\u26A1', html: '', css: '', js: `

{greeting}

{count} (doubled: {doubled})
`, }, ];