CodeLab
🔴 BUILD & LEARN

🚀 Build 10 Real-World Projects

Open a project → switch to Build Mode to code it yourself with an in‑depth guide.

💡 Ctrl+Enter Run · Ctrl+1/2/3 Tabs · Ctrl+G Guide · Ctrl+H Home · Esc Close

`, css: `*{margin:0;padding:0;box-sizing:border-box}body{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#1a1a2e,#16213e);font-family:'Segoe UI',sans-serif}.calculator{background:#1e1e2e;border-radius:20px;padding:24px;box-shadow:0 20px 60px rgba(0,0,0,0.5);width:320px}.display{background:#0a0a15;color:#00cec9;font-size:2.5rem;text-align:right;padding:20px 16px;border-radius:14px;margin-bottom:16px;font-family:'Courier New',monospace;min-height:80px;word-wrap:break-word;overflow:hidden}.buttons{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}button{padding:18px;font-size:1.2rem;border:none;border-radius:12px;cursor:pointer;background:#2a2a3e;color:#fff;font-weight:600;transition:all 0.15s}button:hover{background:#3a3a52;transform:scale(1.04)}button:active{transform:scale(0.95)}.operator{background:#e17055;color:#fff}.operator:hover{background:#f08070}.equal{background:#00cec9;color:#000;grid-column:span 2}.equal:hover{background:#00dfd6}.clear{background:#fdcb6e;color:#000}.clear:hover{background:#ffe082}.delete{background:#d63031;color:#fff}.delete:hover{background:#e84040}`, js: `let display = document.getElementById('display');let currentInput = '0';function updateDisplay(){display.textContent=currentInput}function appendValue(val){if(currentInput==='0'&&val!=='.')currentInput=val;else if(currentInput==='0'&&val==='.')currentInput='0.';else currentInput+=val;updateDisplay()}function clearDisplay(){currentInput='0';updateDisplay()}function deleteLast(){currentInput=currentInput.length>1?currentInput.slice(0,-1):'0';updateDisplay()}function calculate(){try{let expr=currentInput.replace(/×/g,'*').replace(/÷/g,'/').replace(/−/g,'-');let result=eval(expr);currentInput=(result===Infinity||isNaN(result))?'Error':parseFloat(result.toFixed(10)).toString()}catch(e){currentInput='Error'}updateDisplay()}updateDisplay();`, guide: `

🧮 Calculator – Full Tutorial

You'll build a working calculator that handles +, −, ×, ÷ with a clean grid layout.

📝 Step-by‑Step

  1. HTML structure: Create a div with class calculator containing a display area and a div.buttons with all 20 buttons. Each button calls a JavaScript function via onclick.
  2. CSS Grid layout: Use display:grid; grid-template-columns: repeat(4, 1fr) to arrange buttons in a 4‑column grid. Style the display with a dark background and monospace font.
  3. JavaScript logic: Keep the current input in a string variable. The appendValue() function adds characters. clearDisplay() resets to '0'. deleteLast() removes the last character.
  4. Calculation: In calculate(), replace the display symbols (×, ÷, −) with real operators (*, /, -) and use eval() to compute the result. Wrap in a try/catch to handle errors like division by zero.

💡 Pro Tips

🎯 Challenge: Add keyboard support – listen for keydown events and map keys to calculator buttons.
` }, { id: 2, name: 'To-Do List', icon: '📝', desc: 'A persistent task manager with add, complete, and delete. Data saved in localStorage.', difficulty: 'easy', difficultyLabel: 'Easy', tags: ['localStorage', 'CRUD', 'DOM', 'Arrays'], html: `To-Do List

📝 My To-Do List

0 total · 0 completed
`, css: `*{margin:0;padding:0;box-sizing:border-box}body{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#0f0c29,#302b63,#24243e);font-family:'Segoe UI',sans-serif}.todo-container{background:#1e1e35;border-radius:20px;padding:30px;width:440px;max-width:95%;box-shadow:0 20px 50px rgba(0,0,0,0.5)}h1{color:#fdcb6e;text-align:center;margin-bottom:20px;font-size:1.7rem}.input-group{display:flex;gap:10px;margin-bottom:20px}#taskInput{flex:1;padding:14px 16px;border-radius:12px;border:2px solid #3a3a55;background:#0a0a18;color:#fff;font-size:1rem;outline:none;transition:0.3s}#taskInput:focus{border-color:#00cec9}.input-group button{padding:14px 20px;border-radius:12px;border:none;background:#00cec9;color:#000;font-weight:700;cursor:pointer;font-size:1rem;transition:0.2s}.input-group button:hover{background:#00dfd6;transform:scale(1.03)}ul{list-style:none;max-height:350px;overflow-y:auto}li{background:#2a2a42;padding:14px 16px;border-radius:10px;margin-bottom:8px;display:flex;align-items:center;gap:10px;transition:0.2s;animation:fadeIn 0.3s}@keyframes fadeIn{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}li:hover{background:#333355}li.completed{opacity:0.5;text-decoration:line-through;background:#1a2a1a}li.completed .task-text{text-decoration:line-through;color:#888}.task-text{flex:1;color:#e0e0e0;font-size:1rem}li button{background:transparent;border:none;cursor:pointer;font-size:1.2rem;padding:4px 8px;border-radius:6px;transition:0.2s}.delete-btn:hover{background:#e1705533;color:#e17055}.check-btn:hover{background:#00cec933}.stats{text-align:center;margin-top:16px;color:#888;font-size:0.85rem}.stats span{color:#fdcb6e;font-weight:700}`, js: `(function(){let tasks=JSON.parse(localStorage.getItem('myTasks'))||[];const taskInput=document.getElementById('taskInput');const taskList=document.getElementById('taskList');const totalSpan=document.getElementById('totalTasks');const completedSpan=document.getElementById('completedTasks');function save(){localStorage.setItem('myTasks',JSON.stringify(tasks))}function render(){taskList.innerHTML='';tasks.forEach((t,i)=>{const li=document.createElement('li');if(t.completed)li.classList.add('completed');li.innerHTML=\`\${t.text}\`;li.querySelector('.check-btn').addEventListener('click',()=>toggle(i));li.querySelector('.delete-btn').addEventListener('click',()=>del(i));taskList.appendChild(li)});totalSpan.textContent=tasks.length;completedSpan.textContent=tasks.filter(t=>t.completed).length}window.addTask=function(){const text=taskInput.value.trim();if(text){tasks.push({text,completed:false});taskInput.value='';save();render()}};function toggle(i){tasks[i].completed=!tasks[i].completed;save();render()}function del(i){tasks.splice(i,1);save();render()}render();})();`, guide: `

📝 To‑Do List – Full Tutorial

Create a task manager that remembers your tasks even after refreshing the page.

📝 Step‑by‑Step

  1. Data model: Store tasks as an array of objects: { text: "Buy milk", completed: false }.
  2. LocalStorage: Use JSON.stringify() and JSON.parse() to save/load the array.
  3. Rendering: Write a render() function that clears the <ul> and rebuilds it from the array. Attach click listeners to check and delete buttons using addEventListener (event delegation is cleaner).
  4. Toggle & Delete: toggle(i) flips the completed boolean; del(i) uses splice(i,1) to remove the task. Always call save() and render() after changes.
  5. Add new task: On button click (or Enter key), push a new object and update the UI.

💡 Pro Tips

🎯 Challenge: Add an edit feature – double‑click a task to turn it into an input field.
` }, { id: 3, name: 'Tic-Tac-Toe', icon: '🎮', desc: 'Classic 2‑player game with win detection and highlighted winning cells.', difficulty: 'medium', difficultyLabel: 'Medium', tags: ['Game Logic', 'Arrays', 'Win Detection'], html: `Tic-Tac-Toe

🎮 Tic-Tac-Toe

Player X's turn 🎯
`, css: `*{margin:0;padding:0;box-sizing:border-box}body{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#1a1a2e,#0f3460);font-family:'Segoe UI',sans-serif}.game-container{text-align:center;background:#1e1e35;padding:30px;border-radius:20px;box-shadow:0 20px 60px rgba(0,0,0,0.5)}h1{color:#fdcb6e;margin-bottom:16px}.status{font-size:1.3rem;color:#00cec9;margin-bottom:20px;font-weight:700;min-height:30px}.board{display:grid;grid-template-columns:repeat(3,100px);gap:8px;justify-content:center;margin-bottom:20px}.cell{width:100px;height:100px;background:#2a2a42;border-radius:14px;display:flex;align-items:center;justify-content:center;font-size:2.8rem;font-weight:800;cursor:pointer;transition:0.2s;color:#fff;user-select:none}.cell:hover{background:#3a3a58;transform:scale(1.04)}.cell.x{color:#e17055}.cell.o{color:#00cec9}.cell.win{background:#fdcb6e33;animation:pop 0.4s}@keyframes pop{0%{transform:scale(0.8)}50%{transform:scale(1.15)}100%{transform:scale(1)}}.reset-btn{padding:14px 30px;border-radius:25px;border:none;background:#e17055;color:#fff;font-weight:700;font-size:1rem;cursor:pointer;transition:0.2s}.reset-btn:hover{background:#f08070;transform:scale(1.04)}`, js: `let board=['','','','','','','','',''];let currentPlayer='X',gameActive=true;const statusEl=document.getElementById('status');const cells=document.querySelectorAll('.cell');const wins=[[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];cells.forEach(cell=>{cell.addEventListener('click',()=>{const i=cell.dataset.index;if(board[i]!==''||!gameActive)return;board[i]=currentPlayer;cell.textContent=currentPlayer;cell.classList.add(currentPlayer.toLowerCase());if(checkWin()){statusEl.textContent=\`🎉 Player \${currentPlayer} Wins!\`;gameActive=false;highlightWin()}else if(board.every(c=>c!=='')){statusEl.textContent='🤝 Draw!';gameActive=false}else{currentPlayer=currentPlayer==='X'?'O':'X';statusEl.textContent=\`Player \${currentPlayer}'s turn 🎯\`}})});function checkWin(){return wins.some(c=>c.every(i=>board[i]===currentPlayer))}function highlightWin(){wins.forEach(c=>{if(c.every(i=>board[i]===currentPlayer))c.forEach(i=>cells[i].classList.add('win'))})}function resetGame(){board=['','','','','','','','',''];currentPlayer='X';gameActive=true;statusEl.textContent="Player X's turn 🎯";cells.forEach(c=>{c.textContent='';c.classList.remove('x','o','win')})}`, guide: `

🎮 Tic‑Tac‑Toe – Full Tutorial

Implement a two‑player game with all 8 winning combinations.

📝 Step‑by‑Step

  1. Board representation: Use a 9‑element array (board) where each index corresponds to a cell. Initially all empty strings.
  2. Win combinations: Store the 8 possible winning index triplets in an array of arrays.
  3. Click handling: Attach a single listener to each cell. Use data-index to know which cell was clicked. If the cell is empty and the game is active, place the current player's mark.
  4. Win check: After every move, use wins.some() and c.every(i => board[i] === currentPlayer) to test for a winner.
  5. Highlight: If a win is found, add a .win class to the winning cells for a pop animation.

💡 Pro Tips

🎯 Challenge: Build an AI opponent using the minimax algorithm.
` }, { id: 4, name: 'Snake Game', icon: '🐍', desc: 'Classic Snake game on Canvas. Arrow keys to control the snake.', difficulty: 'medium', difficultyLabel: 'Medium', tags: ['Canvas', 'Game Loop', 'Collision'], html: `Snake Game

🐍 Snake Game

Score: 0

Use Arrow Keys ⬆️⬇️⬅️➡️

`, css: `*{margin:0;padding:0;box-sizing:border-box}body{display:flex;justify-content:center;align-items:center;min-height:100vh;background:#0a0a15;font-family:'Segoe UI',sans-serif}.snake-container{text-align:center;background:#1a1a2e;padding:24px;border-radius:20px;box-shadow:0 20px 50px rgba(0,0,0,0.6)}h1{color:#00b894;margin-bottom:8px}.score{font-size:1.3rem;color:#fdcb6e;margin-bottom:12px;font-weight:700}canvas{border:3px solid #333;border-radius:10px;background:#0d0d1a;display:block;margin:0 auto}.hint{color:#888;margin-top:10px;font-size:0.85rem}.reset-btn{margin-top:10px;padding:10px 24px;border-radius:20px;border:none;background:#00b894;color:#000;font-weight:700;cursor:pointer;font-size:0.95rem;transition:0.2s}.reset-btn:hover{background:#00d9a6;transform:scale(1.03)}`, js: `const canvas=document.getElementById('snakeCanvas'),ctx=canvas.getContext('2d');const scoreSpan=document.getElementById('score');let snake=[{x:200,y:200},{x:190,y:200},{x:180,y:200}];let food={},direction={x:10,y:0},score=0,gameLoop,grid=10;function randFood(){food={x:Math.floor(Math.random()*40)*grid,y:Math.floor(Math.random()*40)*grid};if(snake.some(s=>s.x===food.x&&s.y===food.y))randFood()}function draw(){ctx.fillStyle='#0d0d1a';ctx.fillRect(0,0,400,400);snake.forEach((s,i)=>{ctx.fillStyle=i===0?'#00b894':'#00a381';ctx.fillRect(s.x,s.y,grid-1,grid-1)});ctx.fillStyle='#e17055';ctx.beginPath();ctx.arc(food.x+grid/2,food.y+grid/2,grid/2-1,0,Math.PI*2);ctx.fill();scoreSpan.textContent=score}function move(){const head={x:snake[0].x+direction.x,y:snake[0].y+direction.y};if(head.x<0||head.x>=400||head.y<0||head.y>=400||snake.some(s=>s.x===head.x&&s.y===head.y)){clearInterval(gameLoop);alert('Game Over! Score: '+score);return}snake.unshift(head);if(head.x===food.x&&head.y===food.y){score+=10;randFood()}else snake.pop();draw()}document.addEventListener('keydown',e=>{if(e.key==='ArrowUp'&&direction.y===0)direction={x:0,y:-grid};else if(e.key==='ArrowDown'&&direction.y===0)direction={x:0,y:grid};else if(e.key==='ArrowLeft'&&direction.x===0)direction={x:-grid,y:0};else if(e.key==='ArrowRight'&&direction.x===0)direction={x:grid,y:0}});function resetSnake(){clearInterval(gameLoop);snake=[{x:200,y:200},{x:190,y:200},{x:180,y:200}];direction={x:10,y:0};score=0;randFood();draw();gameLoop=setInterval(move,100)}randFood();draw();gameLoop=setInterval(move,100);`, guide: `

🐍 Snake Game – Full Tutorial

Build a retro Snake game using the HTML5 Canvas API.

📝 Step‑by‑Step

  1. Canvas setup: Create a <canvas> element (400×400) and get its 2D context.
  2. Snake data: Use an array of {x, y} objects. The head is at index 0. Move by adding a new head (unshift) and removing the tail (pop) unless food is eaten.
  3. Food: Generate random grid‑aligned coordinates. Make sure it doesn't overlap the snake.
  4. Game loop: Use setInterval(move, 100) to advance the snake every 100ms. Inside move(), check for wall or self‑collision.
  5. Direction: Listen for arrow keys and change direction only if the new direction is not opposite to the current one.

💡 Pro Tips

🎯 Challenge: Add mobile touch controls and a high‑score feature using localStorage.
` }, { id: 5, name: 'Quiz App', icon: '❓', desc: 'Multiple‑choice quiz with a countdown timer and score tracking.', difficulty: 'medium', difficultyLabel: 'Medium', tags: ['Timer', 'Arrays', 'State'], html: `Quiz App

❓ GK Quiz

30s
Loading question...
Question 1/5
Score: 0
`, css: `*{margin:0;padding:0;box-sizing:border-box}body{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#1e1b4b,#312e81);font-family:'Segoe UI',sans-serif}.quiz-container{background:#1e1e3a;border-radius:20px;padding:30px;width:500px;max-width:95%;box-shadow:0 20px 60px rgba(0,0,0,0.5);text-align:center}h1{color:#a78bfa;margin-bottom:12px}.timer{color:#fdcb6e;font-size:1.1rem;margin-bottom:10px;font-weight:700}.question{color:#fff;font-size:1.2rem;margin-bottom:18px;min-height:50px;font-weight:600}.options{display:flex;flex-direction:column;gap:10px;margin-bottom:16px}.option-btn{padding:14px 18px;border-radius:12px;border:2px solid #3a3a55;background:#2a2a42;color:#e0e0e0;cursor:pointer;font-size:1rem;transition:0.2s;text-align:left}.option-btn:hover{background:#3a3a58;border-color:#a78bfa}.option-btn.correct{background:#00b89433;border-color:#00b894;color:#00b894}.option-btn.wrong{background:#e1705533;border-color:#e17055;color:#e17055}.option-btn:disabled{cursor:not-allowed;opacity:0.7}.progress,.score{color:#aaa;margin:6px 0;font-size:0.9rem}.score span{color:#fdcb6e;font-weight:700}.next-btn,.restart-btn{padding:12px 28px;border-radius:25px;border:none;background:#a78bfa;color:#000;font-weight:700;cursor:pointer;font-size:1rem;margin-top:8px;transition:0.2s}.next-btn:hover,.restart-btn:hover{background:#c4b5fd;transform:scale(1.03)}.hidden{display:none!important}`, js: `const questions=[{q:'What does HTML stand for?',opts:['Hyper Text Markup Language','High Tech Modern Language','Hyper Transfer Markup Language','Home Tool Markup Language'],ans:0},{q:'Which keyword declares a variable in JS?',opts:['var','let','const','All of the above'],ans:3},{q:'What does CSS stand for?',opts:['Computer Style Sheets','Cascading Style Sheets','Creative Style System','Colorful Style Sheets'],ans:1},{q:'typeof null returns?',opts:['null','undefined','object','string'],ans:2},{q:'Single-line comment in JS?',opts:['#','//','/*','--'],ans:1}];let currentQ=0,score=0,timer,timeLeft=30;const qEl=document.getElementById('question'),optsEl=document.getElementById('options'),timerEl=document.getElementById('timer'),qNumEl=document.getElementById('qNum'),totalQEl=document.getElementById('totalQ'),scoreEl=document.getElementById('quizScore'),nextBtn=document.getElementById('nextBtn'),restartBtn=document.getElementById('restartBtn');totalQEl.textContent=questions.length;function showQ(){const q=questions[currentQ];qEl.textContent=q.q;qNumEl.textContent=currentQ+1;optsEl.innerHTML='';q.opts.forEach((opt,i)=>{const b=document.createElement('button');b.className='option-btn';b.textContent=opt;b.onclick=()=>select(i);optsEl.appendChild(b)});nextBtn.classList.add('hidden');restartBtn.classList.add('hidden');startTimer()}function select(i){clearInterval(timer);const q=questions[currentQ];const btns=document.querySelectorAll('.option-btn');btns.forEach(b=>b.disabled=true);btns[q.ans].classList.add('correct');if(i===q.ans){score++;scoreEl.textContent=score}else{btns[i].classList.add('wrong')}currentQ{timeLeft--;timerEl.textContent=timeLeft;if(timeLeft<=0){clearInterval(timer);select(-1)}},1000)}function nextQuestion(){currentQ++;showQ()}function restartQuiz(){currentQ=0;score=0;scoreEl.textContent=0;showQ()}showQ();`, guide: `

❓ Quiz App – Full Tutorial

Create a timed quiz that gives instant feedback on correct/incorrect answers.

📝 Step‑by‑Step

  1. Question bank: Define an array of objects with q (question), opts (array of choices), and ans (index of correct answer).
  2. Display question: Build a showQ() function that updates the question text, creates buttons for each option, and starts the timer.
  3. Answer handling: When an option is clicked (or time runs out), disable all buttons and highlight the correct one in green. If the user's choice is wrong, mark it red.
  4. Timer: Use setInterval to decrement timeLeft every second. When it reaches 0, auto‑submit.
  5. Navigation: Show a "Next" button after each question (or "Restart" after the last).

💡 Pro Tips

🎯 Challenge: Fetch questions from the Open Trivia Database API.
` }, { id: 6, name: 'Notes App', icon: '📒', desc: 'Sticky notes with auto‑save. Create, edit, and delete notes.', difficulty: 'easy', difficultyLabel: 'Easy', tags: ['localStorage', 'Auto-save', 'Grid'], html: `Notes App

📒 My Notes

`, css: `*{margin:0;padding:0;box-sizing:border-box}body{min-height:100vh;background:linear-gradient(135deg,#0f172a,#1e293b);font-family:'Segoe UI',sans-serif;padding:20px}.notes-container{max-width:900px;margin:0 auto}h1{color:#fbbf24;text-align:center;margin-bottom:16px;font-size:2rem}.add-note-btn{display:block;margin:0 auto 20px;padding:14px 30px;border-radius:25px;border:none;background:#fbbf24;color:#000;font-weight:700;font-size:1rem;cursor:pointer;transition:0.2s}.add-note-btn:hover{background:#fcd34d;transform:scale(1.03)}.notes-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:16px}.note{background:#1e293b;border-radius:14px;padding:18px;border:1px solid #334155;position:relative;transition:0.2s;animation:fadeIn 0.3s}@keyframes fadeIn{from{opacity:0;transform:scale(0.9)}to{opacity:1;transform:scale(1)}}.note:hover{border-color:#fbbf24;box-shadow:0 8px 24px rgba(251,191,36,0.1)}.note textarea{width:100%;background:transparent;border:none;color:#e2e8f0;font-size:1rem;resize:vertical;min-height:120px;outline:none;font-family:inherit;line-height:1.6}.note .note-date{font-size:0.7rem;color:#64748b;margin-top:8px}.note .delete-note{position:absolute;top:10px;right:10px;background:transparent;border:none;cursor:pointer;font-size:1.1rem;opacity:0.5;transition:0.2s}.note .delete-note:hover{opacity:1;transform:scale(1.2)}`, js: `let notes=JSON.parse(localStorage.getItem('stickyNotes'))||[];function saveNotes(){localStorage.setItem('stickyNotes',JSON.stringify(notes))}function renderNotes(){const grid=document.getElementById('notesGrid');grid.innerHTML='';notes.forEach((note,i)=>{const div=document.createElement('div');div.className='note';div.innerHTML=\`
\${note.date}
\`;grid.appendChild(div)})}function addNote(){notes.unshift({content:'',date:new Date().toLocaleString()});saveNotes();renderNotes()}function updateNote(i,content){notes[i].content=content;saveNotes()}function deleteNote(i){notes.splice(i,1);saveNotes();renderNotes()}renderNotes();`, guide: `

📒 Notes App – Full Tutorial

Create sticky notes that auto‑save to localStorage as you type.

📝 Step‑by‑Step

  1. Storage: Use an array of { content, date } objects. Load/save with localStorage.
  2. Rendering: Build a renderNotes() function that loops over the array and creates a div.note for each item. Inside each note, place a textarea (with oninput) and a delete button.
  3. Auto‑save: The oninput handler calls updateNote(index, this.value), which updates the array and saves to localStorage.
  4. Add/Delete: addNote() uses unshift to place new notes at the top. deleteNote() splices the note from the array.

💡 Pro Tips

🎯 Challenge: Let the user choose a background color for each note.
` }, { id: 7, name: 'Stopwatch', icon: '⏱', desc: 'Precision stopwatch with start/stop, reset, and lap recording.', difficulty: 'easy', difficultyLabel: 'Easy', tags: ['Timer', 'setInterval', 'Formatting'], html: `Stopwatch

⏱ Stopwatch

00:00:00.00
`, css: `*{margin:0;padding:0;box-sizing:border-box}body{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#0c0c1d,#1a1a35);font-family:'Segoe UI',sans-serif}.stopwatch-container{background:#1e1e38;border-radius:24px;padding:36px;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.5);width:400px;max-width:95%}h1{color:#74b9ff;margin-bottom:10px}.display{font-size:3rem;font-family:'Courier New',monospace;color:#fff;background:#0a0a18;padding:20px;border-radius:16px;margin-bottom:20px;letter-spacing:2px}.controls{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-bottom:16px}.controls button{padding:12px 22px;border-radius:25px;border:none;font-weight:700;font-size:0.95rem;cursor:pointer;transition:0.2s}.start-btn{background:#00b894;color:#000}.start-btn:hover{background:#00d9a6;transform:scale(1.03)}.start-btn.running{background:#e17055;color:#fff}.lap-btn{background:#74b9ff;color:#000}.lap-btn:hover{background:#8ecaff;transform:scale(1.03)}.lap-btn:disabled{opacity:0.4;cursor:not-allowed}.reset-btn{background:#636e72;color:#fff}.reset-btn:hover{background:#7f8c8d;transform:scale(1.03)}.laps{max-height:200px;overflow-y:auto;text-align:left}.lap-item{background:#2a2a45;padding:10px 16px;border-radius:8px;margin:4px 0;color:#dfe6e9;display:flex;justify-content:space-between;font-family:'Courier New',monospace;font-size:0.9rem}`, js: `let swInterval,ms=0,running=false;const display=document.getElementById('swDisplay'),startBtn=document.getElementById('startBtn'),lapBtn=document.getElementById('lapBtn'),lapsList=document.getElementById('lapsList');function fmt(ms){const h=Math.floor(ms/3600000),m=Math.floor((ms%3600000)/60000),s=Math.floor((ms%60000)/1000),cs=Math.floor((ms%1000)/10);return String(h).padStart(2,'0')+':'+String(m).padStart(2,'0')+':'+String(s).padStart(2,'0')+'.'+String(cs).padStart(2,'0')}function upd(){display.textContent=fmt(ms)}function startSW(){if(running){clearInterval(swInterval);startBtn.textContent='▶ Start';startBtn.classList.remove('running');lapBtn.disabled=true;running=false}else{swInterval=setInterval(()=>{ms+=10;upd()},10);startBtn.textContent='⏸ Stop';startBtn.classList.add('running');lapBtn.disabled=false;running=true}}function recordLap(){const lap=document.createElement('div');lap.className='lap-item';lap.innerHTML=\`🏁 Lap \${lapsList.children.length+1}\${fmt(ms)}\`;lapsList.prepend(lap)}function resetSW(){clearInterval(swInterval);ms=0;running=false;upd();startBtn.textContent='▶ Start';startBtn.classList.remove('running');lapBtn.disabled=true;lapsList.innerHTML=''}upd();`, guide: `

⏱ Stopwatch – Full Tutorial

Build a high‑precision stopwatch that records lap times.

📝 Step‑by‑Step

  1. Time tracking: Use a ms variable incremented by 10 every 10ms via setInterval.
  2. Formatting: Write a function that converts milliseconds to HH:MM:SS.CS using Math.floor and padStart.
  3. Start/Stop: Toggle a boolean running and clear/set the interval accordingly. Update the button text and style.
  4. Lap: When the user clicks "Lap", create a new div with the current formatted time and prepend it to the laps container.
  5. Reset: Clear the interval, set ms to 0, update the display, and remove all lap items.

💡 Pro Tips

🎯 Challenge: Add a countdown timer mode where the user can set a target time.
` }, { id: 8, name: 'Weather App', icon: '🌤', desc: 'Search a city to get real weather via API, with a glassmorphism UI.', difficulty: 'medium', difficultyLabel: 'Medium', tags: ['Fetch API', 'Async', 'JSON'], html: `Weather App

🌤 Weather App

--
--°C
--
💧 Humidity: --%
💨 Wind: -- km/h

*Try: London, Delhi, Tokyo

`, css: `*{margin:0;padding:0;box-sizing:border-box}body{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#1e3a5f,#2563eb33,#1e3a5f);font-family:'Segoe UI',sans-serif}.weather-container{background:rgba(30,41,59,0.8);backdrop-filter:blur(12px);border-radius:24px;padding:30px;width:400px;max-width:95%;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.08)}h1{color:#fbbf24;margin-bottom:16px}.search-box{display:flex;gap:8px;margin-bottom:20px}#cityInput{flex:1;padding:12px 16px;border-radius:25px;border:2px solid #475569;background:#0f172a;color:#fff;font-size:1rem;outline:none}#cityInput:focus{border-color:#fbbf24}.search-box button{padding:12px 20px;border-radius:25px;border:none;background:#fbbf24;color:#000;font-weight:700;cursor:pointer;font-size:1rem}.weather-card{background:rgba(255,255,255,0.04);border-radius:16px;padding:24px}.city-name{font-size:1.5rem;color:#fff;font-weight:700;margin-bottom:4px}.temp{font-size:3.5rem;color:#fbbf24;font-weight:800}.desc{color:#94a3b8;font-size:1.1rem;margin:6px 0 16px;text-transform:capitalize}.details{display:flex;justify-content:space-around;color:#cbd5e1;font-size:0.9rem}.details span{color:#fbbf24;font-weight:700}.note{margin-top:12px;color:#64748b;font-size:0.75rem}`, js: `const demo={'london':{name:'London',temp:15,desc:'cloudy',humidity:60,wind:20},'delhi':{name:'Delhi',temp:32,desc:'sunny',humidity:45,wind:12},'mumbai':{name:'Mumbai',temp:30,desc:'humid',humidity:75,wind:8}};async function getWeather(){const city=document.getElementById('cityInput').value.trim().toLowerCase();if(!city)return;document.getElementById('cityName').textContent='Loading...';try{const res=await fetch(\`https://api.openweathermap.org/data/2.5/weather?q=\${city}&appid=bd5e378503939ddaee76f12ad7a97608&units=metric\`);if(res.ok){const d=await res.json();updateUI(d.name,d.main.temp,d.weather[0].description,d.main.humidity,d.wind.speed)}else throw new Error()}catch(e){if(demo[city]){const d=demo[city];updateUI(d.name,d.temp,d.desc,d.humidity,d.wind)}else{document.getElementById('cityName').textContent='City not found'}}}function updateUI(name,temp,desc,hum,wind){document.getElementById('cityName').textContent=name;document.getElementById('temperature').textContent=Math.round(temp)+'°C';document.getElementById('description').textContent=desc;document.getElementById('humidity').textContent=hum;document.getElementById('wind').textContent=wind}`, guide: `

🌤 Weather App – Full Tutorial

Connect to the OpenWeatherMap API and display live weather data.

📝 Step‑by‑Step

  1. API key: Use the free OpenWeatherMap API. The provided key works for demo purposes, but you should get your own for production.
  2. Fetch request: Use fetch() with the city name and units=metric. Because fetch is asynchronous, make the function async and await the response.
  3. Parse JSON: The response contains main.temp, weather[0].description, main.humidity, and wind.speed.
  4. Error handling: Wrap the fetch in a try/catch. If the API fails (network error or city not found), fall back to the demo data object.
  5. UI update: Write a helper function that populates the DOM elements with the retrieved data.

💡 Pro Tips

🎯 Challenge: Add a 5‑day forecast using the One Call API.
` }, { id: 9, name: 'Password Generator', icon: '🔐', desc: 'Generate strong random passwords with customizable options and copy‑to‑clipboard.', difficulty: 'easy', difficultyLabel: 'Easy', tags: ['Random', 'Clipboard', 'Strings'], html: `Password Generator

🔐 Password Generator

`, css: `*{margin:0;padding:0;box-sizing:border-box}body{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#0f0c29,#1a1040);font-family:'Segoe UI',sans-serif}.pw-container{background:#1e1e3a;border-radius:20px;padding:30px;width:420px;max-width:95%;box-shadow:0 20px 60px rgba(0,0,0,0.5)}h1{color:#a78bfa;text-align:center;margin-bottom:20px}.output-box{display:flex;gap:8px;margin-bottom:20px}#pwOutput{flex:1;padding:14px 16px;border-radius:12px;border:2px solid #3a3a55;background:#0a0a18;color:#00b894;font-size:1.1rem;font-family:'Courier New',monospace;outline:none;letter-spacing:1px}.output-box button{padding:14px 16px;border-radius:12px;border:none;background:#00b894;color:#000;font-weight:700;cursor:pointer;transition:0.2s}.output-box button:hover{background:#00d9a6}.settings{display:flex;flex-direction:column;gap:10px;margin-bottom:18px;color:#cbd5e1}.settings label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:0.95rem}.settings input[type="checkbox"]{width:18px;height:18px;accent-color:#a78bfa}#lengthSlider{width:100%;accent-color:#a78bfa}.generate-btn{width:100%;padding:14px;border-radius:25px;border:none;background:#a78bfa;color:#000;font-weight:700;font-size:1.1rem;cursor:pointer;transition:0.2s}.generate-btn:hover{background:#c4b5fd;transform:scale(1.02)}.strength{margin-top:10px;text-align:center;font-weight:700;font-size:0.9rem;min-height:20px}`, js: `function generatePassword(){const len=parseInt(document.getElementById('lengthSlider').value);const upper=document.getElementById('uppercase').checked,lower=document.getElementById('lowercase').checked,nums=document.getElementById('numbers').checked,syms=document.getElementById('symbols').checked;let chars='';if(upper)chars+='ABCDEFGHIJKLMNOPQRSTUVWXYZ';if(lower)chars+='abcdefghijklmnopqrstuvwxyz';if(nums)chars+='0123456789';if(syms)chars+='!@#$%^&*()_+-=[]{}|;:,.<>?';if(!chars){alert('Select at least one option!');return}let pw='';for(let i=0;i{const b=document.querySelector('.output-box button');b.textContent='✅ Copied!';setTimeout(()=>b.textContent='📋 Copy',1500)})}function updateStrength(len){const s=document.getElementById('strength');if(len<8){s.textContent='Weak 😟';s.style.color='#e17055'}else if(len<16){s.textContent='Medium 😐';s.style.color='#fdcb6e'}else{s.textContent='Strong 💪';s.style.color='#00b894'}}generatePassword();`, guide: `

🔐 Password Generator – Full Tutorial

Create random passwords by combining character pools.

📝 Step‑by‑Step

  1. Character pools: Based on checkboxes, build a string containing all allowed characters (e.g., uppercase letters, symbols).
  2. Random selection: Loop length times and pick a random character from the pool using Math.random() * chars.length.
  3. Copy feature: Use navigator.clipboard.writeText() to copy the generated password. Show a temporary "Copied!" feedback.
  4. Strength indicator: Evaluate strength simply by length: < 8 is weak, 8‑15 medium, 16+ strong.

💡 Pro Tips

🎯 Challenge: Add a password history log and a "no duplicate characters" option.
` }, { id: 10, name: 'Drawing Canvas', icon: '🎨', desc: 'Paint on an HTML5 Canvas. Choose colors and brush sizes, save your art.', difficulty: 'medium', difficultyLabel: 'Medium', tags: ['Canvas', 'Mouse', 'Touch', 'Export'], html: `Drawing Canvas

🎨 Drawing Canvas

4px
`, css: `*{margin:0;padding:0;box-sizing:border-box}body{display:flex;justify-content:center;align-items:center;min-height:100vh;background:#1a1a2e;font-family:'Segoe UI',sans-serif}.draw-container{background:#1e1e38;border-radius:20px;padding:24px;box-shadow:0 20px 60px rgba(0,0,0,0.5);text-align:center}h1{color:#f472b6;margin-bottom:12px}.toolbar{display:flex;align-items:center;gap:10px;justify-content:center;flex-wrap:wrap;margin-bottom:14px}.toolbar input[type="color"]{width:40px;height:40px;border:none;border-radius:8px;cursor:pointer}.toolbar input[type="range"]{accent-color:#f472b6;width:100px}.toolbar button{padding:10px 16px;border-radius:20px;border:none;background:#f472b6;color:#000;font-weight:700;cursor:pointer;font-size:0.85rem;transition:0.2s}.toolbar button:hover{background:#f9a8d4;transform:scale(1.03)}#sizeVal{color:#fff;font-weight:700;min-width:40px}canvas{background:#fff;border-radius:12px;cursor:crosshair;display:block;margin:0 auto;max-width:100%}`, js: `const canvas=document.getElementById('drawCanvas'),ctx=canvas.getContext('2d');let drawing=false;canvas.addEventListener('mousedown',e=>{drawing=true;ctx.beginPath();ctx.moveTo(e.offsetX,e.offsetY)});canvas.addEventListener('mousemove',e=>{if(!drawing)return;ctx.strokeStyle=document.getElementById('colorPicker').value;ctx.lineWidth=document.getElementById('brushSize').value;ctx.lineCap='round';ctx.lineTo(e.offsetX,e.offsetY);ctx.stroke()});canvas.addEventListener('mouseup',()=>drawing=false);canvas.addEventListener('mouseleave',()=>drawing=false);canvas.addEventListener('touchstart',e=>{e.preventDefault();drawing=true;const r=canvas.getBoundingClientRect();ctx.beginPath();ctx.moveTo(e.touches[0].clientX-r.left,e.touches[0].clientY-r.top)});canvas.addEventListener('touchmove',e=>{e.preventDefault();if(!drawing)return;const r=canvas.getBoundingClientRect();ctx.strokeStyle=document.getElementById('colorPicker').value;ctx.lineWidth=document.getElementById('brushSize').value;ctx.lineCap='round';ctx.lineTo(e.touches[0].clientX-r.left,e.touches[0].clientY-r.top);ctx.stroke()});canvas.addEventListener('touchend',()=>drawing=false);document.getElementById('brushSize').addEventListener('input',function(){document.getElementById('sizeVal').textContent=this.value+'px'});function clearCanvas(){ctx.clearRect(0,0,canvas.width,canvas.height)}function saveDrawing(){const a=document.createElement('a');a.download='my-drawing.png';a.href=canvas.toDataURL();a.click()}`, guide: `

🎨 Drawing Canvas – Full Tutorial

Build a paint app that works with both mouse and touch.

📝 Step‑by‑Step

  1. Canvas context: Get the 2d context and set up drawing styles (lineCap = 'round').
  2. Mouse drawing: On mousedown, start a new path. On mousemove, draw a line from the previous point if the mouse button is held. Stop on mouseup or mouseleave.
  3. Touch support: Duplicate the same logic using touchstart, touchmove, touchend. Use getBoundingClientRect() to convert touch coordinates to canvas coordinates.
  4. Tools: Add a color picker and a range slider for brush size. Update the stroke style and line width accordingly.
  5. Save: Use canvas.toDataURL() to export the drawing as a PNG and trigger a download.

💡 Pro Tips

🎯 Challenge: Add undo/redo functionality and shape tools (rectangle, circle).
` } ]; // ============================================ // APP STATE // ============================================ let currentProject = null; let activeTab = 'html'; let isBuildMode = false; let previewTimeout; function renderProjectCards() { const grid = document.getElementById('projectsGrid'); grid.innerHTML = ''; projects.forEach(proj => { const card = document.createElement('div'); card.className = 'project-card'; const diffClass = proj.difficulty === 'easy' ? 'diff-easy' : 'diff-medium'; card.innerHTML = `
${proj.icon}${proj.difficultyLabel}
${proj.name}
${proj.desc}
${proj.tags.map(t=>`${t}`).join('')}
`; card.addEventListener('click', () => openProject(proj)); grid.appendChild(card); }); } function goHome() { document.getElementById('homeView').classList.remove('hidden'); document.getElementById('editorView').classList.add('hidden'); document.getElementById('guideOverlay').classList.add('hidden'); currentProject = null; isBuildMode = false; document.getElementById('btnBuildMode').style.display = 'none'; window.location.hash = ''; window.scrollTo({top:0,behavior:'smooth'}); } function openProject(proj) { currentProject = proj; document.getElementById('homeView').classList.add('hidden'); document.getElementById('editorView').classList.remove('hidden'); document.getElementById('guideOverlay').classList.add('hidden'); document.getElementById('editorTitle').innerHTML = `${proj.icon} ${proj.name}`; isBuildMode = false; document.getElementById('btnBuildMode').style.display = 'inline-flex'; document.getElementById('btnBuildMode').textContent = '🛠️ Build from Scratch'; loadCodeForMode(); switchTab('html'); updateGuideContent(); runCode(); window.location.hash = 'project-' + proj.id; window.scrollTo({top:0,behavior:'smooth'}); showToast(`📂 Opened: ${proj.name}`); } function toggleBuildMode() { if(!currentProject) return; isBuildMode = !isBuildMode; document.getElementById('btnBuildMode').textContent = isBuildMode ? '📋 Show Solution' : '🛠️ Build from Scratch'; loadCodeForMode(); if(isBuildMode && document.getElementById('guideOverlay').classList.contains('hidden')) toggleGuide(); runCode(); showToast(isBuildMode ? '🛠️ Build Mode: follow the guide!' : '📋 Solution loaded.'); } function loadCodeForMode() { if(!currentProject) return; const htmlEd = document.getElementById('htmlEditor'), cssEd = document.getElementById('cssEditor'), jsEd = document.getElementById('jsEditor'); htmlEd.value = currentProject.html; cssEd.value = isBuildMode ? '/* Add your CSS styles here */' : currentProject.css; jsEd.value = isBuildMode ? '// Write your JavaScript logic here' : currentProject.js; } function switchTab(tab) { activeTab = tab; ['htmlEditor','cssEditor','jsEditor'].forEach(id=>document.getElementById(id).classList.add('hidden')); document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active')); if(tab==='html'){document.getElementById('htmlEditor').classList.remove('hidden');document.querySelector('.tab-btn[data-tab="html"]').classList.add('active')} else if(tab==='css'){document.getElementById('cssEditor').classList.remove('hidden');document.querySelector('.tab-btn[data-tab="css"]').classList.add('active')} else{document.getElementById('jsEditor').classList.remove('hidden');document.querySelector('.tab-btn[data-tab="js"]').classList.add('active')} } function runCode() { const html=document.getElementById('htmlEditor').value, css=document.getElementById('cssEditor').value, js=document.getElementById('jsEditor').value; const iframe=document.getElementById('previewFrame'); let fullHTML = html.trim().toLowerCase().startsWith('Preview${html}`; if(html.trim().toLowerCase().startsWith('', ``); if(js.trim()) fullHTML = fullHTML.replace('', ``); } const blob=new Blob([fullHTML],{type:'text/html'}), url=URL.createObjectURL(blob); if(iframe.src && iframe.src.startsWith('blob:')) URL.revokeObjectURL(iframe.src); iframe.src=url; } ['htmlEditor','cssEditor','jsEditor'].forEach(id=>{const el=document.getElementById(id); if(el) el.addEventListener('input',()=>{clearTimeout(previewTimeout);previewTimeout=setTimeout(runCode,400)})}); function updateGuideContent(){if(currentProject) document.getElementById('guideInner').innerHTML=currentProject.guide;} function toggleGuide(){ const overlay=document.getElementById('guideOverlay'); if(!currentProject && overlay.classList.contains('hidden')){ document.getElementById('guideInner').innerHTML=`

📖 Welcome to CodeLab

Pick a project, then use Build from Scratch to code it yourself with the step‑by‑step guide.
`; overlay.classList.remove('hidden'); }else if(overlay.classList.contains('hidden')){updateGuideContent();overlay.classList.remove('hidden')} else overlay.classList.add('hidden'); } document.getElementById('guideOverlay').addEventListener('click',e=>{if(e.target===document.getElementById('guideOverlay')) document.getElementById('guideOverlay').classList.add('hidden')}); document.addEventListener('keydown',e=>{if(e.key==='Escape') document.getElementById('guideOverlay').classList.add('hidden')}); function showToast(msg){const t=document.createElement('div');t.className='toast';t.textContent=msg;document.body.appendChild(t);setTimeout(()=>t.remove(),2500)} document.addEventListener('keydown',e=>{ if(e.ctrlKey&&e.key==='Enter'){e.preventDefault();runCode()} if(e.ctrlKey&&e.key==='1'){e.preventDefault();switchTab('html')} if(e.ctrlKey&&e.key==='2'){e.preventDefault();switchTab('css')} if(e.ctrlKey&&e.key==='3'){e.preventDefault();switchTab('js')} if(e.ctrlKey&&e.key==='g'){e.preventDefault();toggleGuide()} if(e.ctrlKey&&e.key==='h'){e.preventDefault();goHome()} }); window.addEventListener('hashchange',()=>{ const hash=window.location.hash; if(hash.startsWith('#project-')){ const id=parseInt(hash.replace('#project-','')), p=projects.find(x=>x.id===id); if(p&&(!currentProject||currentProject.id!==id)) openProject(p); }else if(hash===''&¤tProject) goHome(); }); renderProjectCards(); if(window.location.hash) window.dispatchEvent(new Event('hashchange')); window.goHome=goHome; window.switchTab=switchTab; window.runCode=runCode; window.toggleGuide=toggleGuide; window.toggleBuildMode=toggleBuildMode; })();