Tired of tutorial hell? This is where your real frontend journey begins.
Whether you're:
β A total beginner looking to build your first real projects
πΌ A job seeker needing portfolio pieces that impress employers
π An aspiring freelancer wanting practical, client-ready skills
These 12 carefully crafted projects will give you the hands-on experience most courses only talk about. Each one:
β
Solves real problems (no "foo/bar" examples)
β
Grows with your skills (from basic DOM to complex APIs)
β
Looks portfolio-ready (modern UI, clean code)
β
Teaches professional workflows (debugging, optimization, deployment)
All projects use vanilla JavaScript (no frameworks) with clean code.
Todo List App
A dynamic task manager with add, edit, delete, and prioritization features. Practice CRUD operations and localStorage for data persistence while mastering DOM manipulation.
π Difficulty: β β β β
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TODO LIST</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="container"> <h1>Todo List</h1> <div class="input-container"> <input class="todo-input" placeholder="Add a new task..."> <button class="add-button"> <i class="fa fa-plus-circle"></i> </button> </div> <div class="filters"> <div class="filter" data-filter="completed">Complete</div> <div class="filter" data-filter="pending">Incomplete</div> <div class="delete-all">Delete All</div> </div> <div class="todos-container"> <ul class="todos"></ul> <img class="empty-image" src="./empty.svg"> </div> </div> <script src="./script.js"></script> </body> </html>style.css
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Roboto', sans-serif; height: 100vh; display: flex; align-items: center; justify-content: center; background: url(./background.jpg) no-repeat; background-position: center; background-size: cover; } .container { width: 400px; height: auto; min-height: 400px; padding: 30px; background: transparent; border: 2px solid #e6b7eca1; border-radius: 10px; backdrop-filter: blur(15px); } h1 { color: #eee; text-align: center; margin-bottom: 36px; } .input-container { display: flex; justify-content: space-between; margin-bottom: 25px; } .todo-input { flex: 1; outline: none; padding: 10px 10px 10px 20px; background-color: transparent; border: 2px solid #e6b7eca1; border-radius: 30px; color: #eee; font-size: 16px; margin-right: 10px; } .todo-input::placeholder { color: #bfbfbf; } .add-button { border: none; outline: none; background: #e6b7eca1; color: #fff; font-size: 35px; cursor: pointer; border-radius: 40px; width: 40px; height: 40px; } .empty-image { margin: 55px auto 0; display: block; } .todo { list-style: none; display: flex; align-items: center; justify-content: space-between; background-color: #463c7b; border-radius: 5px; margin: 10px 0; padding: 10px 12px; border: 2px solid #e6b7eca1; transition: all 0.2s ease; } .todo:first-child { margin-top: 0; } .todo:last-child { margin-bottom: 0; } .todo:hover { background-color:#e6b7eca1; } .todo label { cursor: pointer; width: fit-content; display: flex; align-items: center; font-family: 'Roboto', sans-serif; color: #eee; } .todo span { padding-left: 20px; position: relative; cursor: pointer; } span::before { content: ""; height: 20px; width: 20px; position: absolute; margin-left: -30px; border-radius: 100px; border: 2px solid #e6b7eca1; } input[type='checkbox'] { visibility: hidden; } input:checked + span { text-decoration: line-through } .todo:hover input:checked + span::before, input:checked + span::before { background: url(./check.svg) 50% 50% no-repeat #09bb21; border-color: #09bb21; } .todo:hover span::before { border-color: #eee; } .todo .delete-btn { background-color: transparent; border: none; cursor: pointer; color: #eee; font-size: 24px; } .todos-container { height: 300px; overflow: overlay; } .todos-container::-webkit-scrollbar-track { background: rgb(247, 247, 247); border-radius: 20px } .todos-container::-webkit-scrollbar { width: 0; } .todos-container:hover::-webkit-scrollbar { width: 7px; } .todos-container::-webkit-scrollbar-thumb { background: #d5d5d5; border-radius: 20px; } .filters { display: flex; justify-content: space-between; margin-bottom: 25px; } .filter { color: #eee; padding: 5px 15px; border-radius: 100px; border: 2px solid #e6b7eca1; transition: all 0.2s ease; cursor: pointer; } .filter.active, .filter:hover { background-color: #e6b7eca1; } .delete-all { display: flex; color: #eee; padding: 7px 15px; cursor: pointer; transition: all 0.2s ease; } .delete-all:hover { border-radius: 5px; background-color: #e6b7eca1; }script.js
const input = document.querySelector("input"); const addButton = document.querySelector(".add-button"); const todosHtml = document.querySelector(".todos"); const emptyImage = document.querySelector(".empty-image"); let todosJson = JSON.parse(localStorage.getItem("todos")) || []; const deleteAllButton = document.querySelector(".delete-all"); const filters = document.querySelectorAll(".filter"); let filter = ''; showTodos(); function getTodoHtml(todo, index) { if (filter && filter != todo.status) { return ''; } let checked = todo.status == "completed" ? "checked" : ""; return /* html */ ` <li class="todo"> <label for="${index}"> <input id="${index}" onclick="updateStatus(this)" type="checkbox" ${checked}> <span class="${checked}">${todo.name}</span> </label> <button class="delete-btn" data-index="${index}" onclick="remove(this)"><i class="fa fa-times"></i></button> </li> `; } function showTodos() { if (todosJson.length == 0) { todosHtml.innerHTML = ''; emptyImage.style.display = 'block'; } else { todosHtml.innerHTML = todosJson.map(getTodoHtml).join(''); emptyImage.style.display = 'none'; } } function addTodo(todo) { input.value = ""; todosJson.unshift({ name: todo, status: "pending" }); localStorage.setItem("todos", JSON.stringify(todosJson)); showTodos(); } input.addEventListener("keyup", e => { let todo = input.value.trim(); if (!todo || e.key != "Enter") { return; } addTodo(todo); }); addButton.addEventListener("click", () => { let todo = input.value.trim(); if (!todo) { return; } addTodo(todo); }); function updateStatus(todo) { let todoName = todo.parentElement.lastElementChild; if (todo.checked) { todoName.classList.add("checked"); todosJson[todo.id].status = "completed"; } else { todoName.classList.remove("checked"); todosJson[todo.id].status = "pending"; } localStorage.setItem("todos", JSON.stringify(todosJson)); } function remove(todo) { const index = todo.dataset.index; todosJson.splice(index, 1); showTodos(); localStorage.setItem("todos", JSON.stringify(todosJson)); } filters.forEach(function (el) { el.addEventListener("click", (e) => { if (el.classList.contains('active')) { el.classList.remove('active'); filter = ''; } else { filters.forEach(tag => tag.classList.remove('active')); el.classList.add('active'); filter = e.target.dataset.filter; } showTodos(); }); }); deleteAllButton.addEventListener("click", () => { todosJson = []; localStorage.setItem("todos", JSON.stringify(todosJson)); showTodos(); });
Music Player
A sleek audio player with playlist management. Dive into the Web Audio API and media controls.
π Difficulty: β β β β
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Music player</title> <link rel="stylesheet" href="./style.css"> <script src="https://code.iconify.design/iconify-icon/1.0.8/iconify-icon.min.js"></script> </head> <body> <div class="player"> <div class="thumbnail"> <img src="" id="thumb"> </div> <div class="details"> <h3 id="title"></h3> <span id="artist"></span> </div> <audio id="song"> <source src="" type="audio/mp3"></source> </audio> <div class="time"> <span id="start">00:00</span> <span id="end">00:00</span> </div> <input type="range" class="progress-bar" id="progress" value="0" max="0"> <div class="action"> <button class="prev-play"> <iconify-icon icon="iconoir:skip-prev-solid"></iconify-icon> </button> <button class="play" id="play"> <iconify-icon icon="iconoir:play-solid" class="icon-play"></iconify-icon> <iconify-icon icon="iconoir:pause-solid" class="icon-pause hidden"></iconify-icon> </button> <button class="next-play"> <iconify-icon icon="iconoir:skip-next-solid"></iconify-icon> </button> </div> </div> <script type="module" src="script.js"></script> </body> </html>style.css
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --primary-color: #0170fe; --secondary-color: #11d2d9; --white-color: #fff; } body { font-family: Poppins, sans-serif; min-height: 100vh; background-image: url("background.jpg"); background-position: center; background-size: cover; display: flex; justify-content: center; align-items: center; } .player { width: 100%; max-width: 350px; backdrop-filter: blur(40px); box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); padding: 60px 40px; border-radius: 10px; } img { width: 150px; height: 150px; border-radius: 50%; } .thumbnail { background-image: linear-gradient(120deg, var(--secondary-color),var(--primary-color)); width: 160px; height: 160px; border-radius: 50%; display: flex; justify-content: center; align-items: center; margin: auto; } img.play { animation: 15s rotate linear infinite; } @keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .details { text-align: center; margin-top: 30px; } .details span { font-size: 15px; margin-top: 5px; } .time { margin-top: 30px; display: flex; justify-content: space-between; } .progress-bar { margin: 10px 0 30px; width: 100%; appearance: none; height: 6px; cursor: pointer; } .progress-bar::-webkit-slider-thumb { appearance: none; width: 12px; height: 12px; border-radius: 3px; background-image: linear-gradient(120deg, var(--secondary-color),var(--primary-color)); transition: 0.2s ease-in-out; } .action { display: flex; justify-content: space-evenly; } .action button { width: 60px; height: 60px; background-color: var(--white-color); border-radius: 50%; border: 2px solid var(--white-color); cursor: pointer; } .action button.prev-play:hover, .action button.next-play:hover { background-color: #EEE; } .action button iconify-icon { font-size: 20px; } .action button.play { background-image: linear-gradient(120deg, var(--secondary-color),var(--primary-color)); color: var(--white-color); } .action button.prev-play iconify-icon, .action button.next-play iconify-icon { color: var(--primary-color); } .hidden { display: none; }script.js
const songs = await fetch("songs.json").then((res) => res.json()); const playBtn = document.querySelector(".icon-play"); const pauseBtn = document.querySelector(".icon-pause"); const song = document.querySelector("#song"); const title = document.querySelector("#title"); const artist = document.querySelector("#artist"); const thumb = document.querySelector("#thumb"); const progress = document.querySelector("#progress"); const start = document.querySelector("#start"); const end = document.querySelector("#end"); const playButton = document.querySelector(".play"); const prevPlayButton = document.querySelector(".prev-play"); const nextPlayButton = document.querySelector(".next-play"); playButton.addEventListener("click", playPause); prevPlayButton.addEventListener("click", () => changeSong(-1)); nextPlayButton.addEventListener("click", () => changeSong(1)); progress.addEventListener("change", updateSongProgress); let index = 0; let interval; let songProgressTrackerInterval; setSongDetails(index); function setSongDetails(songIndex) { song.src = songs[songIndex].link; title.innerHTML = songs[songIndex].name; artist.innerHTML = songs[songIndex].artists; thumb.src = songs[songIndex].image; start.innerHTML = "00:00"; end.innerHTML = "00:00"; clearInterval(interval); song.onloadedmetadata = loadMetadata; } function loadMetadata() { progress.max = song.duration; progress.value = song.currentTime; updateSongTimeDisplay(); interval = setInterval(updateSongTimeDisplay, 1000); } function updateSongTimeDisplay() { let min = Math.floor(song.duration / 60); let sec = Math.floor(song.duration % 60); let curMin = Math.floor(song.currentTime / 60); let curSec = Math.floor(song.currentTime % 60); if (sec < 10) { sec = "0" + sec; } if (curSec < 10) { curSec = "0" + curSec; } if (min < 10) { min = "0" + min; } if (curMin < 10) { curMin = "0" + curMin; } end.innerHTML = min + ":" + sec; start.innerHTML = curMin + ":" + curSec; } function changeSong(increment) { index = (index + increment + songs.length) % songs.length; setSongDetails(index); song.play(); } function playPause() { if (!pauseBtn.classList.contains("hidden")) { song.pause(); pauseBtn.classList.add("hidden"); playBtn.classList.remove("hidden"); thumb.classList.remove("play"); return; } song.play(); startSongProgressTracker(); playBtn.classList.add("hidden"); pauseBtn.classList.remove("hidden"); thumb.classList.add("play"); } function updateSongProgress() { clearInterval(songProgressTrackerInterval); song.currentTime = progress.value; startSongProgressTracker(); }; function startSongProgressTracker() { clearInterval(songProgressTrackerInterval); songProgressTrackerInterval = setInterval(() => { progress.value = song.currentTime; if (song.currentTime == song.duration) { changeSong(1); } }, 1000); }songs.json
[ { "name": "Happier", "link": "https://www.dropbox.com/s/zp1xfir101y4sc3/happier.mp3?raw=1", "artists": "Marshmello", "image": "https://www.dropbox.com/s/xxmwcz14hkn7iwl/happier.png?raw=1" }, { "name": "Stay", "link": "https://www.dropbox.com/s/umam9olakop001d/stay.mp3?raw=1", "artists": "Justin Bieber", "image": "https://www.dropbox.com/s/kierj5lzst1yx9n/stay.jpg?raw=1" }, { "name": "Perfect", "link": "https://www.dropbox.com/s/3mjzj73400sxovk/perfect.mp3?raw=1", "artists": "Ed Sheeran", "image": "https://www.dropbox.com/s/crlthbozdznb13g/perfect.jpeg?raw=1" }, { "name": "7 Rings", "link": "https://www.dropbox.com/s/yo5tcfdjoz95ozf/7-rings.mp3?raw=1", "artists": "Ariana Grande", "image": "https://www.dropbox.com/s/gobvfxj4r0t053v/7-rings.jpg?raw=1" }, { "name": "Girls Like You", "link": "https://www.dropbox.com/s/yi1cpg16snrl3fc/girls-like-you.mp3?raw=1", "artists": "Maroon 5", "image": "https://www.dropbox.com/s/ouq5zzgbqsk9zx0/girls-like-you.png?raw=1" } ]
π Ressources
Weather App
Get real-time forecasts using geolocation. Connect to weather APIs.
π Difficulty: β β β β
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Weather App</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="container"> <div class="input-container"> <input class="search-input" id="searchInput" placeholder="Search..."> <button class="search-button" id="searchButton"> <i class="fa fa-search"></i> </button> </div> <div class="weather-container"> <div id="cityWeather" class="city-weather hidden"> <img class="icon" id="icon" alt> <h1 class="temperature" id="temperature"></h1> <h2 class="city" id="city"></h2> <div class="details"> <div class="detail"> <img src="./images/icons/humidity.png"> <div class="description"> <p class="value" id="humidity">%</p> <p>Humidity</p> </div> </div> <div class="detail"> <img src="./images/icons/wind.png"> <div class="description"> <p class="value" id="wind">km/h</p> <p>Wind</p> </div> </div> </div> </div> <div id="noCityFound" class="no-city-found hidden"> <img src="images/location-not-found.png" alt> <p>Oops! Location not found!</p> </div> </div> </div> <script src="./script.js"></script> </body> </html>style.css
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } :root { --primary-color: #4c77b6; --white-color: #fff; } body { font-family: "Roboto", sans-serif; min-height: 100vh; background: url(images/background.jpg) no-repeat; background-position: center; background-size: cover; color: var(--white-color); display: flex; justify-content: center; align-items: center; } .container { width: 400px; border: 2px solid var(--white-color); padding: 30px; border-radius: 10px; backdrop-filter: blur(40px); text-align: center; } .city-weather,.no-city-found { height: 0px; overflow: hidden; transition: height 0.7s ease; } .no-city-found { height: 300px; } .city-weather { height: 410px; } .hidden { height: 0; } .input-container { display: flex; justify-content: space-between; } .search-input { flex: 1; padding: 10px 10px 10px 20px; border-radius: 30px; border: 2px solid transparent; font-size: 16px; margin-right: 10px; outline: none; } .search-button { width: 40px; height: 40px; background: var(--primary-color); color: var(--white-color); border: none; border-radius: 40px; font-size: 24px; cursor: pointer; outline: none; } .no-city-found img { width: 100%; margin-top: 20px; } .no-city-found p { font-size: 24px; } .city-weather .icon { width: 170px; } .city-weather .temperature { font-size: 75px; font-weight: 500; } .city-weather .city { font-size: 40px; font-weight: 400; } .city-weather .details { display: flex; justify-content: space-between; align-items: center; margin-top: 48px; } .details .detail { display: flex; align-items: center; column-gap: 8px; text-align: left; } .detail img { width: 40px; } .detail .value { font-size: 24px; }script.js
const API_KEY = "REPLACE_IT_WITH_YOUR_API_KEY: https://api.openweathermap.org"; const searchInput = document.getElementById("searchInput"); const searchButton = document.getElementById("searchButton"); const city = document.getElementById("city"); const temperature = document.getElementById("temperature"); const humidity = document.getElementById("humidity"); const wind = document.getElementById("wind"); const icon = document.getElementById("icon"); const cityWeather = document.getElementById("cityWeather"); const noCityFound = document.getElementById("noCityFound"); searchButton.addEventListener("click", updateWeatherData); searchInput.addEventListener("keydown",(event) => event.key === "Enter" && updateWeatherData()); async function updateWeatherData() { try { if (!searchInput.value.trim()) { alert("Please enter the city"); return; } const weatherData = await getWeatherData(searchInput.value); noCityFound.classList.add("hidden"); cityWeather.classList.remove("hidden"); city.textContent = weatherData.city; temperature.textContent = weatherData.temperature + "Β°C"; humidity.textContent = weatherData.humidity + "%"; wind.textContent = weatherData.wind + " km/h"; icon.src = weatherData.icon; } catch (error) { if (error?.code === "404") { cityWeather.classList.add("hidden"); noCityFound.classList.remove("hidden"); console.error(error.message); } else { alert("Unknown error: " + error); } } } function getIconUrl(icon) { return `./images/icons/${icon.toLowerCase()}.png`; } async function getWeatherData(city) { const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${API_KEY}`; const apiResponse = await fetch(apiUrl); const apiResponseBody = await apiResponse.json(); if (apiResponseBody.cod == "404" || apiResponseBody.cod == "400") { throw { code: "404", message: "City not found" }; } return { city: apiResponseBody.name, temperature: Math.round(apiResponseBody?.main?.temp), humidity: apiResponseBody.main.humidity, wind: Math.round(apiResponseBody.wind.speed), icon: getIconUrl(apiResponseBody.weather[0].main), }; }
π Ressources
QR Code Generator
Convert URLs/text to scannable QR codes. Integrate with third-party APIs.
π Difficulty: β β β β
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>QR Code Generator</title> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="container"> <div class="header-container mb-35"> <h1>QR Code Generator</h1> <h2> Online tool to generate QR Codes from texts, urls and much more. </h2> </div> <input type="text" class="text-input mb-35" placeholder="Enter text or a link"> <button class="generate-button">Generate QR Code</button> <div class="qrcode-container"> <div class="qrcode"></div> </div> <button class="download-button hidden">Download QR Code</button> </div> <script src="https://cdn.rawgit.com/davidshimjs/qrcodejs/gh-pages/qrcode.min.js"></script> <script src="script.js"></script> </body> </html>style.css
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --primary-color: #662d72; --secondary-color: #eb4e9b; --background-color: #f3f2f245; --hover-1-color: #783885; --hover-2-color: #f15ea6; --text-color: #bb408c; } body { font-family: Poppins, sans-serif; min-height: 100vh; background: url(background.jpg) no-repeat; background-position: center; background-size: cover; display: flex; justify-content: center; align-items: center; } .hidden { display: none; } .container { background: var(--background-color); width: 500px; padding: 50px; backdrop-filter: blur(40px); border-radius: 10px; display: flex; flex-direction: column; } .header-container { text-align: center; } .mb-35 { margin-bottom: 35px; } h1 { color: var(--primary-color); font-size: 36px; margin-bottom: 20px; } h2 { color: var(--text-color); font-size: 22px; } input { font-size: 18px; padding: 10px; border: none; border-radius: 5px; outline: none; } button { font-size: 18px; padding: 12px 0; border: none; border-radius: 5px; color: white; cursor: pointer; transition: background-color 0.3s; } .generate-button { background-color: var(--primary-color); } .generate-button:hover { background-color: var(--hover-1-color); } .download-button { background-color:var(--secondary-color) } .download-button:hover { background-color:var(--hover-2-color); } .qrcode { display: flex; justify-content: center; height: 0; overflow: hidden; transition: height 0.35s linear; } .qrcode img { padding: 10px; background: white; border-radius: 3px; height: 250px; width: 250px; }script.js
const textInput = document.querySelector(".text-input"); const generateButton = document.querySelector(".generate-button"); const qrCodeContainer = document.querySelector(".qrcode-container"); const qrCodeElement = document.querySelector(".qrcode"); const downloadButton = document.querySelector(".download-button"); generateButton.addEventListener("click", generateQRCode); textInput.addEventListener("keydown",(event) => event.key === "Enter" && generateQRCode()); downloadButton.addEventListener("click", download); function generateQRCode() { const text = textInput.value.trim(); if (!text) { alert("Please enter some text to generate a QR code."); return; } const firstGeneration = qrCodeElement.innerHTML == ""; qrCodeElement.innerHTML = ""; qrCodeElement.style.height = "0"; if (!firstGeneration) { qrCodeContainer.style.height = "250px"; } new QRCode(qrCodeElement, { text: text, width: 250, height: 250, }); setTimeout(() => { qrCodeElement.style.height = "250px"; downloadButton.classList.remove("hidden"); generateButton.classList.add("mb-35"); qrCodeContainer.classList.add("mb-35"); }, firstGeneration ? 0 : 750); } function download() { const qrCodeImage = document.querySelector(".qrcode img"); const downloadLink = document.createElement("a"); downloadLink.href = qrCodeImage.src; downloadLink.download = "qrcode.png"; downloadLink.click(); }
π Ressources
Calculator
A fully functional calculator with keyboard support. Perfect for understanding mathematical operations and event handling in JavaScript.
π Difficulty: β β ββ
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Calculator</title> <link rel="stylesheet" href="./style.css" /> </head> <body> <div class="container"> <h1>Calculator</h1> <div class="display"> <div class="expression">0</div> <div class="result hidden"></div> </div> <div class="buttons"> <div class="button">C</div> <div class="button">(</div> <div class="button">)</div> <div class="button">/</div> <div class="button">7</div> <div class="button">8</div> <div class="button">9</div> <div class="button">*</div> <div class="button">4</div> <div class="button">5</div> <div class="button">6</div> <div class="button">-</div> <div class="button">1</div> <div class="button">2</div> <div class="button">3</div> <div class="button">+</div> <div class="button">0</div> <div class="button valuse">00</div> <div class="button">.</div> <div class="button equals">=</div> </div> </div> <script src="script.js"></script> </body> </html>style.css
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --primary-color: #7552a0; --secondary-color: #43377f; --text-color: #fff; --border-color: #7552a0A0; } body { font-family: Poppins, sans-serif; min-height: 100vh; background: url(background.jpg) no-repeat; background-position: center; background-size: cover; display: flex; justify-content: center; align-items: center; } .hidden { display: none; } .container { width: 300px; padding: 30px; border: 2px var(--border-color) solid; border-radius: 10px; backdrop-filter: blur(20px); } @media only screen and (min-width: 768px) { .container { width: 400px } } h1 { text-align: center; color: var(--text-color); font-size: 30px; margin-bottom: 16px; } @media only screen and (min-width: 576px) { h1 { font-size: 40px; } } .display { height: 200px; border: 2px var(--border-color) solid; border-radius: 5px; margin-bottom: 25px; display: flex; flex-direction: column; justify-content: end; align-items: end; } .buttons { display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 20px; } .display .expression { font-size: 36px; padding: 0 10px; } .display .result { font-size: 46px; font-weight: 500; padding: 0 10px; } .display .result::before { content: '='; padding-right: 5px; } .button { border: 2px solid var(--primary-color); border-radius: 100px; width: 45px; height: 45px; display: flex; justify-content: center; align-items: center; font-size: 20px; cursor: pointer; user-select: none; transition: all .2s ease-in; } @media only screen and (min-width: 768px) { .button { width: 60px; height: 60px; font-size: 24px; } } .button:hover { background-color: var(--secondary-color); color: var(--text-color); border-color: transparent; } .button:nth-child(1), .button:nth-child(2), .button:nth-child(3), .button:nth-child(4), .button:nth-child(8), .button:nth-child(12), .button:nth-child(16), .button:nth-child(20) { border: none; background-color: var(--primary-color); color: var(--text-color); font-weight: 600; } :is( .button:nth-child(1), .button:nth-child(2), .button:nth-child(3), .button:nth-child(4), .button:nth-child(8), .button:nth-child(12), .button:nth-child(16), .button:nth-child(20) ):hover { opacity: .85; }script.js
const displayContainer = document.querySelector('.display'); const displayExpression = document.querySelector('.display .expression'); const displayResult = document.querySelector('.display .result'); const isResultHidden = () => displayResult.classList.contains('hidden'); const isResultShown = () => !isResultHidden(); const getResultValue = () => displayResult.innerText == 'Error' ? '0' : displayResult.innerText; const isSymbol = (button) => /[\+\-\*\/]$/.test(button.innerHTML); document.querySelectorAll('.button').forEach(button => button.addEventListener('click', () => handleButtonClick(button))); function handleButtonClick(button) { switch(button.innerHTML) { case 'C': { displayExpression.innerHTML = '0'; displayResult.classList.add('hidden'); break; } case '=': { try { displayResult.classList.remove('hidden'); displayResult.innerHTML = eval(displayExpression.innerHTML); } catch { displayResult.innerHTML = "Error"; } break; } default: { if (isResultShown()) { displayExpression.innerHTML = isSymbol(button) ? getResultValue() : '0'; displayResult.classList.add('hidden'); } if (displayExpression.innerHTML == '0') { displayExpression.innerHTML = button.innerHTML == '00' ? '0' : button.innerHTML; } else { displayExpression.innerHTML += button.innerHTML; } } } }
π Ressources
BMI Calculator
A health tool that calculates Body Mass Index with visual feedback. Learn form validation and dynamic DOM updates.
π Difficulty: β β ββ
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>BMI Calculator</title> <link rel="stylesheet" href="./style.css" /> </head> <body> <div class="container"> <div class="header-container"> <h1>BMI Calculator</h1> <h2> Calculate your Body Mass Index easily and accurately </h2> </div> <form autocomplete="off"> <div class="inputs-container"> <div class="input-container height-container"> <input id="height" placeholder=" "> <p class="error"></p> <label for="height">Your height in cm</label> </div> <div class="input-container weight-container"> <input id="weight" placeholder=" "> <p class="error"></p> <label for="weight">Your weight in kg</label> </div> </div> <button>Calculate your BMI</button> </form> <div class="bmi-result"> <p class="bmi-value"></p> <p class="bmi-description"></p> </div> </div> <div class="container description-container"> <h2>Description</h2> <p class="description"> Let's calculate the Body Mass Index (BMI) to assess the risks associated with overweight based on the person's height and weight. According to the World Health Organization (WHO), the formula is <b>BMI = W/HΒ²</b>, where <b>W</b> is the person's weight in kilograms and <b>H</b> is the height in meters </p> <div class="bmi-ranges"> <div class="bmi-range-container underweight"> <p class="bmi-range"><18,5</p> <p class="bmi-range-label">Underweight</p> </div> <div class="bmi-range-container normal"> <p class="bmi-range">18,5-25</p> <p class="bmi-range-label">Normal</p> </div> <div class="bmi-range-container overweight"> <p class="bmi-range">25-30</p> <p class="bmi-range-label">Overweight</p> </div> <div class="bmi-range-container obesity"> <p class="bmi-range">30-40</p> <p class="bmi-range-label">Obese</p> </div> <div class="bmi-range-container morbid-obesity"> <p class="bmi-range">>40</p> <p class="bmi-range-label">Extremely obesity</p> </div> </div> </div> <script src="script.js"></script> </body> </html>style.css
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --primary-color: #b34083; --primary-text-color: #fff; --secondary-color: #f0b23a; --secondary-text-color: #ea96c7; --hover-color: #983979; --border-color: #d78fb980; --error-color: #f94a4a; } body { font-family: Poppins, sans-serif; min-height: 100vh; background: url(background.jpg) no-repeat; background-position: center; background-size: cover; color: var(--primary-text-color); display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 100px 0; } .container { max-width: 700px; border: 2px var(--border-color) solid; padding: 50px; border-radius: 10px; backdrop-filter: blur(10px); } .header-container { text-align: center; margin-bottom: 45px; } h1 { font-size: 30px; display: inline-block; background: linear-gradient(to right, var(--secondary-color), var(--primary-color)); -webkit-text-fill-color: transparent; background-clip: text; -webkit-background-clip: text; margin-bottom: 12px; } @media only screen and (min-width: 576px) { h1 { font-size: 40px; } } h2 { font-size: 18px; font-weight: 400; color: var(--secondary-text-color); } @media only screen and (min-width: 576px) { h2 { font-size: 22px; } } form { display: flex; flex-direction: column; row-gap: 20px; } .inputs-container { display: flex; flex-direction: column; row-gap: 20px; } .input-container { display: flex; flex-direction: column; flex: 1; position: relative; } label { font-size: 18px; font-weight: 500; position: absolute; left: 0px; top: 0px; transition: all 0.2s ease; pointer-events: none; } input { background: transparent; padding: 8px 0; border: none; border-bottom: 2px solid var(--primary-text-color); margin-top: 5px; font-size: 18px; outline: none; font-weight: 600; color: var(--secondary-color); } input:focus ~ label, input:not(:placeholder-shown) ~ label { top: -16px; font-size: 13px; } .input-container.error input { color: var(--error-color); border-color: var(--error-color); outline-color: transparent; } .input-container .error { color: var(--error-color); font-size: 12px; font-weight: 600; height: 20px; } button { background-color: var(--primary-color); color: var(--primary-text-color); font-size: 20px; padding: 12px; border: none; border-radius: 3px; cursor: pointer; transition: background-color 0.2s ease-in; } button:hover, button:focus { background-color: var(--hover-color); } .bmi-result { margin-top: 20px; height: 0; transition: height 0.2s ease-in; } .bmi-result .bmi-value, .bmi-result .bmi-description { text-align: center; } .bmi-result .bmi-value { font-size: 64px; font-weight: 600; } .bmi-result .bmi-description { font-size: 20px; font-weight: 500; } .container.description-container { margin-top: 60px; } .description { margin-top: 20px; font-size: 15px; text-align: justify; } .bmi-ranges { margin-top: 30px; display: flex; column-gap: 5px; row-gap: 5px; flex-wrap: wrap; } .bmi-range-container { --color: #eee; flex: 1; border: 2px solid var(--color); display: flex; flex-direction: column; border-radius: 3px; min-width: 82px; } .bmi-range { text-align: center; padding: 6px 10px; background-color: var(--color); font-weight: 600; } .bmi-range-label { padding: 6px 10px; height: 60px; display: flex; align-items: center; justify-content: center; text-align: center; font-weight: 600; font-size: 14px; } .underweight { --color: #5BC8F3; } .normal { --color: #9DB63B; } .overweight { --color: #FAC90E; } .obesity { --color: #F48A20; } .morbid-obesity { --color: #FF0000; }script.js
const BMIRanges = [ { description: "Underweight", color: "#5BC8F3", range: [0, 18.5] }, { description: "Normal", color: "#9DB63B", range: [18.5, 25] }, { description: "Overweight", color: "#FAC90E", range: [25, 30] }, { description: "Obese", color: "#F48A20", range: [30, 40] }, { description: "Extremely obesity", color: "#FF0000",range: [40, Number.MAX_VALUE]} ]; const form = document.querySelector("form"); const heightInput = document.querySelector("#height"); const heightInputError = document.querySelector("#height + .error"); const weightInput = document.querySelector("#weight"); const weightInputError = document.querySelector("#weight + .error"); const bmiValue = document.querySelector(".bmi-value"); const bmiDescription = document.querySelector(".bmi-description"); const bmiResult = document.querySelector(".bmi-result"); form.addEventListener("submit", handleForm); heightInput.addEventListener("keyup", validateInput); weightInput.addEventListener("keyup", validateInput); function handleForm(event) { event.preventDefault(); resetErrors(); let containsErrors = false; if (isNotNumber(heightInput)) { containsErrors = true; showError(heightInput); } if (isNotNumber(weightInput)) { containsErrors = true; showError(weightInput); } if (containsErrors) { return; } const height = parseInt(heightInput.value); const weight = parseInt(weightInput.value); showBmi(calculateBmi(weight, height)); } function validateInput(event) { if (event.key == "Tab") return; const input = event.target; isNotNumber(input) ? showError(input) : resetError(input); } const isNumber = (input) => /^[0-9]+$/.test(input.value); const isNotNumber = (input) => !isNumber(input); function showBmi(BMI) { const bmiRange = getBmiRange(BMI); bmiValue.innerHTML = BMI; bmiValue.style.color = bmiRange.color; bmiDescription.innerHTML = bmiRange.description; bmiDescription.style.color = bmiRange.color; bmiResult.style.height = "115px"; } function calculateBmi(weight, height) { return (weight / Math.pow(height / 100, 2)).toFixed(1); } function resetError(input) { input.parentElement.classList.remove("error"); input.nextElementSibling.innerHTML = ""; } function showError(input) { bmiValue.innerHTML = ""; bmiDescription.innerHTML = ""; bmiResult.style.height = "0"; input.parentElement.classList.add("error"); input.nextElementSibling.innerHTML = "Value invalide"; } function resetErrors() { heightInput.parentElement.classList.remove("error"); weightInput.parentElement.classList.remove("error"); heightInputError.innerHTML = ""; weightInputError.innerHTML = ""; } function getBmiRange(BMI) { return BMIRanges.find((data) => BMI >= data.range[0] && BMI < data.range[1]); }
π Ressources
Countdown Timer
An interactive timer with pause/resume functionality. Master date handling and UI animations.
π Difficulty: β β ββ
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>COUNT DOWN</title> <link rel="stylesheet" href="./style.css"> </head> <body> <main> <h1>Comming Soon</h1> <div class="container"> <div class="counter-container"> <span id="days" class="counter">0</span> <span>Days</span> </div> <div class="counter-container"> <span id="hours" class="counter">0</span> <span>Hours</span> </div> <div class="counter-container"> <span id="minutes" class="counter">0</span> <span>Minutes</span> </div> <div class="counter-container"> <span id="seconds" class="counter">0</span> <span>Seconds</span> </div> </div> </main> <script src="./script.js"></script> <script src="./snow.js"></script> </body> </html>style.css
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Roboto', sans-serif; height: 100vh; display: flex; align-items: center; justify-content: center; background: url(./background.png) no-repeat; background-position: center; background-size: cover; } h1 { text-align: center; color: #C75263; font-size: 50px; margin-bottom: 30px; text-transform: uppercase; } .container { display: flex; } .counter-container { width: 120px; height: 120px; display: flex; flex-direction: column; justify-content: center; text-align: center; background: transparent; border: 2px solid #8c1c44; border-radius: 10px; backdrop-filter: blur(15px); color: #8c1c44; margin: 0 5px; text-transform: uppercase; } .counter { display: block; font-size: 35px; font-weight: 600; }script.js
const countDownDate = new Date("Jan 1, 2024 00:00:00").getTime(); const days = document.getElementById("days"); const hours = document.getElementById("hours"); const minutes = document.getElementById("minutes"); const seconds = document.getElementById("seconds"); const interval = setInterval(() => { const now = new Date().getTime(); const duration = countDownDate - now; if (duration < 0) { clearInterval(interval); updateDuration(0); return; } updateDuration(duration); }, 1000); function updateDuration(duration) { const days = Math.floor(duration / (1000 * 60 * 60 * 24)); const hours = Math.floor((duration % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((duration % (1000 * 60)) / 1000); days.innerHTML = days; hours.innerHTML = hours; minutes.innerHTML = minutes; seconds.innerHTML = seconds; }
π Ressources
Password Generator
Create secure passwords with customizable rules. Learn randomization and strength validation.
π Difficulty: β β ββ
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Password generator</title> <script src="https://code.iconify.design/iconify-icon/1.0.8/iconify-icon.min.js"></script> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="container"> <div class="header-container"> <h1 class="gradient-font">Password Generator</h1> <h2> Online tool to generate strong and secure password difficult to crack </h2> </div> <div class="generated-password-container" title="Copy Password"> <span class="password" id="password"></span> <button class="icon-button copy-button" id="copyButton" title="Copy Password"> <iconify-icon icon="ci:copy" class="icon"></iconify-icon> </button> </div> <div class="options-container"> <div class="length-container"> <p class="label"> LENGTH: <span id="lengthValue">20</span> </p> <div class="length"> <span>5</span> <div class="length-container"> <input type="range" min="5" max="30" value="20" id="lengthInput"> </div> <span>30</span> </div> </div> <div class="error-message" id="errorMessage"> At least one option must be selected </div> <p class="label">SETTINGS</p> <div class="boolean-options-container"> <label for="includeUppercase"> Include Uppercase <input type="checkbox" id="includeUppercase" checked> <div class="toggle-switch"></div> </label> <label for="includeLowercase"> Include Lowercase <input type="checkbox" id="includeLowercase" checked> <div class="toggle-switch"></div> </label> <label for="includeNumber"> Include Numbers <input type="checkbox" id="includeNumber" checked> <div class="toggle-switch"></div> </label> <label for="includeSymbols"> Include Symbols <input type="checkbox" id="includeSymbols" checked> <div class="toggle-switch"></div> </label> </div> </div> <button class="primary" type="button" id="generatePasswordButton"> Generate Password </button> </div> <script src="script.js"></script> </body> </html>style.css
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); @import url("https://fonts.googleapis.com/css?family=Roboto Mono"); *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --primary-color: #ee4339; --secondary-color: #ffdbc5e3; --brick-red: #820233; --text-color: #f6dbce; --white-color: #fff; --border-color: #ffe3e299; } body { font-family: Poppins, sans-serif; min-height: 100vh; background: url(background.jpg) no-repeat; background-position: center; background-size: cover; color: var(--brick-red); display: flex; justify-content: center; align-items: center; } .hidden { display: none; } .container { max-width: 850px; border: 2px var(--border-color) solid; padding: 50px; border-radius: 10px; box-shadow: 0px 0px 7px 0 rgba(0, 0, 0, 0.1); backdrop-filter: blur(40px); } .header-container { text-align: center; margin-bottom: 30px; } h1 { font-size: 30px; margin-bottom: 12px; color: var(--primary-color); } @media only screen and (min-width: 576px) { h1 { font-size: 40px; } } h2 { font-size: 14px; font-weight: 500; } @media only screen and (min-width: 576px) { h2 { font-size: 17px; } } .generated-password-container { background: var(--secondary-color); border: var(--border-color) solid 2px; height: 53px; border-radius: 5px; display: flex; align-items: center; justify-content: center; margin-bottom: 32px; cursor: pointer; position: relative; } .password { font-size: 14px; font-weight: 600; font-family: "Roboto Mono"; } @media only screen and (min-width: 576px) { .password { font-size: 18px; } } .icon-button { all: initial; position: absolute; right: 8px; height: 20px; width: 20px; border: 2px solid transparent; border-radius: 3px; display: flex; align-items: center; justify-content: center; cursor: pointer; background-color: transparent; transition: background-color 0.25s ease; } @media only screen and (min-width: 576px) { .icon-button { height: 30px; width: 30px; } } .icon-button:hover { background-color: var(--brick-red); } .icon { font-size: 25px; color: var(--brick-red); transition: color 0.25s ease; } .icon-button:hover .icon { color: var(--text-color); } .length { background: var(--secondary-color); padding: 13px; margin-bottom: 20px; border-radius: 5px; display: flex; align-items: center; justify-content: center; column-gap: 15px; } .length-container { width: 100%; } input[type="range"] { all: initial; background: var(--brick-red); width: 100%; height: 7px; background-image: linear-gradient(var(--primary-color), var(--primary-color)); background-size: 60% 100%; background-repeat: no-repeat; border-radius: 5px; cursor: pointer; } input[type="range"]::-webkit-slider-thumb { appearance: none; width: 20px; height: 20px; background-color: var(--primary-color); border-radius: 50%; } .options-container .label { color: var(--text-color); } .boolean-options-container { display: grid; grid-template-columns: 1fr; row-gap: 15px; column-gap: 15px; } @media only screen and (min-width: 576px) { .boolean-options-container { grid-template-columns: 1fr 1fr; } } label { background: var(--secondary-color); padding: 11px; border-radius: 5px; display: flex; align-items: center; justify-content: space-between; font-size: 12px; font-weight: 600; cursor: pointer; } .toggle-switch { background-color: var(--brick-red); width: 40px; height: 22px; border-radius: 100px; position: relative; cursor: pointer; transition: background-color 0.25s ease; } .toggle-switch:before { content: ""; background-color: var(--white-color); height: 18px; width: 18px; position: absolute; top: 2px; left: 4px; border-radius: 100px; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25); transition: left 0.25s ease; } input { display: none; } input:checked + .toggle-switch { background-color: var(--primary-color); } input:checked + .toggle-switch:before { left: 20px; } button { background-color: var(--primary-color); color: var(--text-color); width: 100%; margin-top: 40px; padding: 15px 0; border: none; border-radius: 5px; text-transform: uppercase; font-weight: 600; letter-spacing: 1.6px; cursor: pointer; } .error-message { color: var(--primary-color); font-size: 20px; font-weight: 600; text-align: center; margin-top: 10px; display: none; }script.js
const generatedPassword = document.getElementById("password"); const lengthValue = document.getElementById("lengthValue"); const lengthInput = document.getElementById("lengthInput"); const options = document.querySelector(".boolean-options-container") const includeLowercase = document.getElementById("includeLowercase"); const includeUppercase = document.getElementById("includeUppercase"); const includeNumber = document.getElementById("includeNumber"); const includeSymbols = document.getElementById("includeSymbols"); const errorMessage = document.getElementById("errorMessage"); const generatePasswordButton = document.getElementById("generatePasswordButton"); const copyButton = document.getElementById("copyButton"); lengthInput.addEventListener("input", handleLengthChange); includeLowercase.addEventListener("change", handleCheckboxChange); includeUppercase.addEventListener("change", handleCheckboxChange); includeNumber.addEventListener("change", handleCheckboxChange); includeSymbols.addEventListener("change", handleCheckboxChange); generatePasswordButton.addEventListener("click", generatePassword); copyButton.addEventListener("click", copyPasswordToClipboard); generatePassword(); function generatePassword() { let chars = buildCharacterString(); let passwordLength = lengthValue.textContent; let password = ""; for (let i = 1; i <= passwordLength; i++) { password += generateRandomCharacter(chars); } generatedPassword.innerText = password; } function buildCharacterString() { let chars = ""; if (includeLowercase.checked) { chars += "abcdefghijklmnopqrstuvwxyz"; } if (includeUppercase.checked) { chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; } if (includeNumber.checked) { chars += "0123456789"; } if (includeSymbols.checked) { chars += "!@#$%^&*()"; } return chars; } function generateRandomCharacter(chars) { let randomNumber = Math.floor(Math.random() * chars.length); return chars.substring(randomNumber, randomNumber + 1); } function handleLengthChange(event) { lengthValue.textContent = event.target.value; lengthInput.style.backgroundSize = ((event.target.value - 5) / 25) * 100 + "% 100%"; generatePassword(); } function handleCheckboxChange(event) { errorMessage.style.display = "none"; if (isNoOptionChecked()) { errorMessage.style.display = "block"; event.target.checked = true; return; } generatePassword(); } const isAtLeastOneOptionChecked = () => options.querySelector("input[type=checkbox]:checked") ? true : false; const isNoOptionChecked = () => !isAtLeastOneOptionChecked(); async function copyPasswordToClipboard() { try { if (!navigator?.clipboard?.writeText) return; await navigator.clipboard.writeText(generatedPassword.textContent); const generatedPasswordValue = generatedPassword.textContent; generatedPassword.textContent = 'Copied!'; setTimeout(() => generatedPassword.textContent = generatedPasswordValue, "1000"); } catch (err) { console.error(err); } }
π Ressources
Quiz App
A timed trivia game with scoring. Perfect for array manipulation practice.
π Difficulty: β β β β
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Quiz App</title> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="container" id="quiz"> <div class="header-container"> <h1>Quiz App</h1> <h2 id="question">Question text</h2> </div> <div class="options"> </div> <button id="submit">Submit</button> </div> <script type="module" src="script.js"></script> </body> </html>style.css
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --primary-color: #a5183b; --secondary-color: #ff6e3b; --text-color: #680131; --white-color: #fff; --border-color: #a5183b21; --option-background-color :#f7efdca8; --option-hover-background-color :#f7efdc; } body { font-family: "Poppins", sans-serif; min-height: 100vh; background-image: url("background.jpg"); background-position: center; background-size: cover; color: var(--text-color); display: flex; justify-content: center; align-items: center; } .container { backdrop-filter: blur(100px); padding: 50px; border: 2px var(--border-color) solid; border-radius: 10px; } @media(min-width: 768px) { .container { width: 700px; } } @media(min-width: 992px) { .container { width: 900px; } } .header-container { text-align: center; margin-bottom: 30px; } h1 { color: var(--primary-color); font-size: 30px; margin-bottom: 12px; } @media only screen and (min-width: 576px) { h1 { font-size: 40px; } } h2 { font-size: 18px; font-weight: 600; } @media only screen and (min-width: 576px) { h2 { font-size: 22px; } } button { width: 100%; font-size: 20px; background-color: var(--primary-color); color: var(--white-color); padding: 14px 0; border: none; border-radius: 5px; cursor: pointer; transition: opacity 0.5s ease; } button:hover, button:focus { opacity: 0.90; } .options { display: grid; grid-template-columns: 1fr; column-gap: 15px; row-gap: 15px; margin: 0 0 35px; } @media(min-width: 768px) { .options { grid-template-columns: repeat(2, 1fr); } } .option { background-color: var(--option-background-color); display: flex; align-items: center; padding: 10px 20px; border-radius: 5px; font-size: 18px; cursor: pointer; transition: background-color 0.5s ease; } .option:hover { background-color: var(--option-hover-background-color); } .option.selected { background-color: var(--secondary-color); } .option.selected span { color: var(--white-color); } .index { background-color: var(--secondary-color); color: var(--white-color); width: 40px; height: 40px; min-width: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-right: 10px; font-size: 22px; text-transform: uppercase; transition: background-color 1s ease; } .option.selected .index { color: var(--primary-color); background-color: var(--white-color); } .answer { text-align: center; margin-bottom: 40px; } .score { color: var(--primary-color); font-size: 30px; font-weight: 900; }script.js
const quizData = await fetch("quizzes.json").then((res) => res.json()); const container = document.querySelector(".container"); const options = document.querySelector(".options"); const questionEl = document.getElementById("question"); const submitBtn = document.getElementById("submit"); let currentQuiz = 0; let score = 0; loadQuiz(); options.addEventListener("click", selectAnswer); submitBtn.addEventListener("click", answerQuestion); function loadQuiz() { const currentQuizData = quizData[currentQuiz]; const optionIndices = ["a", "b", "c", "d"]; questionEl.innerText = currentQuizData.question; options.innerHTML = ""; currentQuizData.options.forEach((option, index) => { options.innerHTML += ` <div class="option" data-option="${option}"> <div class="index">${optionIndices[index]}</div> <span class="option-answer">${option}</span> </div>`; }); } function selectAnswer(event) { const selectedOption = event.target.closest('.option'); if (!selectedOption) { return; } const previouslySelectedOption = options.querySelector('.option.selected'); if (previouslySelectedOption) { previouslySelectedOption.classList.remove("selected"); } selectedOption.classList.add("selected"); } function answerQuestion() { const selectedOption = getSelected(); if (isAnswerCorrect(selectedOption)) { score++; } currentQuiz++; if (!isQuizComplete()) { loadQuiz(); } else { showResults(); } } function getSelected() { const selectedOption = [...options.querySelectorAll(".option")].find( (option) => option.classList.contains("selected") ); return selectedOption ? selectedOption.getAttribute("data-option") : null; } function isAnswerCorrect(selectedOption) { return selectedOption && selectedOption === getCurrentCorrectAnswer(); } function getCurrentCorrectAnswer() { return quizData[currentQuiz].correct; } function isQuizComplete() { return currentQuiz >= quizData.length; } function showResults() { container.innerHTML = ` <h2 class="answer">You answered correctly at <span class="score">${score}/${quizData.length}</span> questions</h2> <button onclick="location.reload()">Reload</button> `; }quizzes.json
[ { "question": "Which language runs in a web browser?", "options": [ "Java", "C", "Python", "JavaScript" ], "correct": "JavaScript" }, { "question": "Which attribute is used to specify the URL of the page that the link goes to?", "options": [ "src", "href", "url", "link" ], "correct": "href" }, { "question": "Which CSS property is used to change the font size of an element?", "options": [ "font-style", "font-family", "font-size", "text-size" ], "correct": "font-size" }, { "question": "What keyword is used to declare a function in JavaScript?", "options": [ "func", "def", "function", "method" ], "correct": "function" } ]
π Ressources
Tic Tac Toe Game
The classic Tic Tac Toe game. Master game state management.
π Difficulty: β β β β
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>tic tac toe</title> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="container"> <h1>Tic Tac Toe</h1> <div class="game-message current-player">Who's Turn : <span>X</span> </div> <div class="game-board"> <div class="row"> <div class="cell" id="cell-1"></div> <div class="cell" id="cell-2"></div> <div class="cell" id="cell-3"></div> </div> <div class="row"> <div class="cell" id="cell-4"></div> <div class="cell" id="cell-5"></div> <div class="cell" id="cell-6"></div> </div> <div class="row"> <div class="cell" id="cell-7"></div> <div class="cell" id="cell-8"></div> <div class="cell" id="cell-9"></div> </div> </div> <button id="resetButton">Reset Game</button> </div> <script src="script.js"></script> </body> </html>style.css
@import url("https://fonts.googleapis.com/css2?family=Varela+Round&display=swap"); *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } :root { --primary-color: #f8a126; --secondary-color: #8d185e; --white-color: #fff; --hover-color: #e07b23; --border-color: #f8a1267a; --bright-blue: #2f76ff; --raspberry-red: #e12472; } body { font-family: "Varela Round", sans-serif; min-height: 100vh; background: url(background.jpg) no-repeat; background-position: center; background-size: cover; color: var(--white-color); display: flex; justify-content: center; align-items: center; } .container { border: 2px solid var(--border-color); padding: 40px; border-radius: 10px; backdrop-filter: blur(15px); display: flex; flex-direction: column; } h1 { text-align: center; margin-bottom: 20px; } .current-player { color: var(--primary-color); font-size: 20px; font-weight: 600; height: 56px; } .current-player span { color: var(--white-color); background: var(--primary-color); display: inline-flex; width: 30px; height: 30px; justify-content: center; align-items: center; border-radius: 3px; } .game-board { display: grid; grid-template-columns: repeat(3, 100px); grid-template-rows: repeat(3, 100px); gap: 10px; } .cell { font-size: 90px; background-color: #00000045; height: 100px; margin-bottom: 10px; border-radius: 5px; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: background-color 0.3s ease; } .cell:hover { background-color: #00000060; } .cell-O { text-shadow: 0 0 10px var(--raspberry-red), 0 0 20px var(--raspberry-red), 0 0 30px var(--raspberry-red); } .cell-X { text-shadow: 0 0 10px var(--bright-blue), 0 0 20px var(--bright-blue), 0 0 30px var(--bright-blue); } :is(.cell-O, .cell-X):hover { background-color: #00000045; } .cell.correct { background-color: var(--secondary-color); } .cell.correct:hover { background-color: var(--secondary-color); } button { background-color: var(--primary-color); color: var(--white-color); font-size: 20px; margin: 40px auto 0; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s ease; } button:hover { background-color: var(--hover-color); } @media (max-width: 600px) { .game-board { grid-template-columns: repeat(3, 60px); grid-template-rows: repeat(3, 60px); } .cell { width: 60px; height: 60px; font-size: 60px; } button { font-size: 16px; } }script.js
const EGameStatus = { NOT_STARTED: "NOT_STARTED", IN_PROGRESS: "IN_PROGRESS", FINISHED_WITH_WINNER: "FINISHED_WITH_WINNER", FINISHED_WITH_TIE: "FINISHED_WITH_TIE", }; let gameStatus = EGameStatus.NOT_STARTED; let currentPlayer = "X"; const cells = document.querySelectorAll(".cell"); const resetButton = document.getElementById("resetButton"); const gameMessage = document.querySelector(".game-message"); updateGameMessage(); resetButton.addEventListener("click", reset); cells.forEach((cell, index) => cell.addEventListener("click", () => handleCellClick(index)) ); function handleCellClick(index) { if (isGameFinished() || cells[index].innerHTML !== "") { return; } gameStatus = EGameStatus.IN_PROGRESS; cells[index].innerHTML = currentPlayer; cells[index].classList.add("cell-" + currentPlayer); if (checkWinner()) { gameStatus = EGameStatus.FINISHED_WITH_WINNER; updateGameMessage(); return; } if (checkTie()) { gameStatus = EGameStatus.FINISHED_WITH_TIE; updateGameMessage(); return; } currentPlayer = currentPlayer === "X" ? "O" : "X"; updateGameMessage(); } function checkWinner() { const winPatterns = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], // Rows [0, 3, 6], [1, 4, 7], [2, 5, 8], // Columns [0, 4, 8], [2, 4, 6], // Diagonals ]; for (const pattern of winPatterns) { const [a, b, c] = pattern; if ( cells[a].innerHTML !== "" && cells[a].innerHTML === cells[b].innerHTML && cells[a].innerHTML === cells[c].innerHTML ) { updateCellsWinners(cells[a], cells[b], cells[c]); return true; } } return false; } function updateCellsWinners(a, b, c) { a.classList.add("correct"); b.classList.add("correct"); c.classList.add("correct"); } function checkTie() { return Array.from(cells).every((cell) => cell.innerHTML !== ""); } function reset() { gameStatus = EGameStatus.NOT_STARTED; currentPlayer = "X"; updateGameMessage(); cells.forEach((cell) => { cell.innerText = ""; cell.className = "cell"; }); } function updateGameMessage() { if (gameStatus === EGameStatus.FINISHED_WITH_TIE) { gameMessage.innerHTML = "It's a tie!"; } else if (gameStatus === EGameStatus.FINISHED_WITH_WINNER) { gameMessage.innerHTML = `Winner : <span>${currentPlayer}</span>`; } else { gameMessage.innerHTML = `Who's Turn : <span>${currentPlayer}</span>`; } } function isGameFinished() { return gameStatus == EGameStatus.FINISHED_WITH_TIE || gameStatus == EGameStatus.FINISHED_WITH_TIE; }
π Ressources
Typing Speed Test
Measure words-per-minute with accuracy tracking. Practice keyboard events.
π Difficulty: β β β β
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Typing Speed Test</title> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="container"> <div class="header-container"> <h1>Typing Speed Test</h1> <h2> Online tool to help you improve your typing speed and accuracy. </h2> </div> <div class="text-to-type-container"> <div class="text-to-type"> </div> <div class="footer-container"> <div class="typed-text-stat-container"> <div class="typed-text-stat-value-container"> <span class="typed-text-stat-value" id="nbWord"> 0 </span> <span class="typed-text-stat-label">Word/min</span> </div> <div class="typed-text-stat-value-container"> <span class="typed-text-stat-value" id="errors"> 0 </span> <span class="typed-text-stat-label">Errors</span> </div> </div> <button id="tryAgainBtn">Try again</button> </div> </div> </div> <script src="lorem-ipsum.js"></script> <script src="script.js"></script> </body> </html>style.css
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); @import url("https://fonts.googleapis.com/css?family=Roboto Mono"); *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } :root { --primary-color: #f7933d; --text-color: #424242; --white-color: #fff; --red-color: #f57173; --green-color: #34a055; --background-color: #ffffff95; --cursor-color: #222; --border-color-1: #f8a3a269; --border-color-2: #74334799; } body { font-family: "Roboto", sans-serif; min-height: 100vh; background-image: url("background.jpg"); background-position: center; background-size: cover; display: flex; justify-content: center; align-items: center; } .container { width: 800px; height: auto; text-align: center; padding: 50px; border: 2px solid var(--border-color-1); border-radius: 10px; backdrop-filter: blur(30px); } .header-container { font-family: Poppins, sans-serif; text-align: center; margin-bottom: 30px; } h1 { color: var(--primary-color); font-size: 36px; margin-bottom: 16px; } h2 { color: var(--white-color); font-size: 14px; font-weight: 400; } @media only screen and (min-width: 576px) { h2 { font-size: 17px; } } .text-to-type-container { background: var(--background-color); border-radius: 5px; padding: 32px; } .text-to-type-container .footer-container { display: flex; justify-content: space-between; border-top: 1px var(--border-color-2) solid; padding-top: 14px; } .footer-container .typed-text-stat-container { display: flex; } .typed-text-stat-value-container { padding: 8px 16px; border-right: 1px var(--border-color-2) solid; } .typed-text-stat-value { font-weight: 600; } .typed-text-stat-value-container:first-child { padding-left: 0; } button { background-color: var(--primary-color); color: var(--white-color); padding: 8px; font-size: 16px; border: 0; border-radius: 5px; outline: none; } .text-to-type-container .text-to-type { font-family: "Roboto Mono"; font-size: 22px; word-break: break-all; padding-bottom: 32px; } .text-to-type span { color: var(--text-color); } .text-to-type-container .text-to-type.blink span.cursor { color: var(--white-color); background-color: var(--cursor-color); } .text-to-type-container span.NOK { color: var(--red-color); } .text-to-type-container span.OK { color: var(--green-color); }script.js
const textToType = document.querySelector(".text-to-type"); const tryAgainBtn = document.querySelector("#tryAgainBtn"); const errorsElement = document.querySelector("#errors"); const nbWordElement = document.querySelector("#nbWord"); const loremIpsum = new LoremIpsum(); let caractersToType; let caracterToTypeIndex; let typedCaractersErrors; let typingStartTime; let finished; initState(); toggleCursor(); document.addEventListener("keydown", handleKeyPress); tryAgainBtn.addEventListener("click", tryAgain); document.addEventListener("keydown",(event) => event.key === "Enter" && tryAgain()); function tryAgain(){ initState(); } function initState() { caracterToTypeIndex = 0; typedCaractersErrors = 0; typingStartTime = new Date().getTime(); finished = false; setTextToType(); } function setTextToType() { textToType.innerHTML = getRandomText() .split("") .map((char, index) => `<span class="${index === 0 ? "cursor" : ""}">${char}</span>`) .join(""); caractersToType = textToType.querySelectorAll("span"); } function getRandomText() { return loremIpsum.sentence(20).toLowerCase().replace(/[^a-z ]/g, ""); } function toggleCursor() { setInterval(() => textToType.classList.toggle("blink"), 500); } function isLastcaracter() { return caracterToTypeIndex >= caractersToType.length - 1; } function handleKeyPress(event) { if (isNotValidTypedKey(event.key) || finished) { return; } handleTypedKey(event.key); if (isLastcaracter()) { finishTyping(); return; } moveCursor(); } function isNotValidTypedKey(key) { return !/^[a-z ]$/.test(key); } function finishTyping() { finished = true; updateTypingStats(); removeCursor(); } function isTypedCharacterCorrect(key) { return key === caractersToType[caracterToTypeIndex].innerText; } function handleTypedKey(key) { if (isTypedCharacterCorrect(key)) { caractersToType[caracterToTypeIndex].classList.add("OK"); } else { caractersToType[caracterToTypeIndex].classList.add("NOK"); typedCaractersErrors++; } } function updateTypingStats() { errorsElement.innerHTML = typedCaractersErrors; nbWordElement.innerHTML = calculateNbWord(typingStartTime); } function calculateNbWord(startTime){ const elapsedTimeInMinutes = (new Date().getTime() - startTime) / (1000 * 60); // Convert milliseconds to minutes const totalWordsTyped = Math.ceil(caracterToTypeIndex / 5); // Assuming an average word length of 5 characters return Math.round(totalWordsTyped / elapsedTimeInMinutes); } function moveCursor() { caractersToType[caracterToTypeIndex++].classList.remove("cursor"); caractersToType[caracterToTypeIndex].classList.add("cursor"); } function removeCursor() { caractersToType[caracterToTypeIndex].classList.remove("cursor"); }lorem-ipsum.js
;(function() { "use strict"; /** * LoremIpsum constructor * * @type {Function} */ window.LoremIpsum = function() { // pass } /** * LoremIpsum prototype * * @type {Object} */ window.LoremIpsum.prototype = { /** * Possible words * * @type {Array} */ _words: [ "a", "ac", "accumsan", "ad", "adipiscing", "aenean", "aenean", "aliquam", "aliquam", "aliquet", "amet", "ante", "aptent", "arcu", "at", "auctor", "augue", "bibendum", "blandit", "class", "commodo", "condimentum", "congue", "consectetur", "consequat", "conubia", "convallis", "cras", "cubilia", "curabitur", "curabitur", "curae", "cursus", "dapibus", "diam", "dictum", "dictumst", "dolor", "donec", "donec", "dui", "duis", "egestas", "eget", "eleifend", "elementum", "elit", "enim", "erat", "eros", "est", "et", "etiam", "etiam", "eu", "euismod", "facilisis", "fames", "faucibus", "felis", "fermentum", "feugiat", "fringilla", "fusce", "gravida", "habitant", "habitasse", "hac", "hendrerit", "himenaeos", "iaculis", "id", "imperdiet", "in", "inceptos", "integer", "interdum", "ipsum", "justo", "lacinia", "lacus", "laoreet", "lectus", "leo", "libero", "ligula", "litora", "lobortis", "lorem", "luctus", "maecenas", "magna", "malesuada", "massa", "mattis", "mauris", "metus", "mi", "molestie", "mollis", "morbi", "nam", "nec", "neque", "netus", "nibh", "nisi", "nisl", "non", "nostra", "nulla", "nullam", "nunc", "odio", "orci", "ornare", "pellentesque", "per", "pharetra", "phasellus", "placerat", "platea", "porta", "porttitor", "posuere", "potenti", "praesent", "pretium", "primis", "proin", "pulvinar", "purus", "quam", "quis", "quisque", "quisque", "rhoncus", "risus", "rutrum", "sagittis", "sapien", "scelerisque", "sed", "sem", "semper", "senectus", "sit", "sociosqu", "sodales", "sollicitudin", "suscipit", "suspendisse", "taciti", "tellus", "tempor", "tempus", "tincidunt", "torquent", "tortor", "tristique", "turpis", "ullamcorper", "ultrices", "ultricies", "urna", "ut", "ut", "varius", "vehicula", "vel", "velit", "venenatis", "vestibulum", "vitae", "vivamus", "viverra", "volutpat", "vulputate" ], /** * Get random number * * @param {Number} x * @param {Number} y * @return {Number} */ _random: function(x, y) { var rnd = (Math.random() * 2 - 1) + (Math.random() * 2 - 1) + (Math.random() * 2 - 1); return Math.round(Math.abs(rnd) * x + y); }, /** * Get random number between min and max * * @param {Number} min (optional) lower result limit * @param {Number} max (optional) upper result limit * @return {Number} random number */ _count: function(min, max) { var result; if (min && max) result = Math.floor(Math.random() * (max - min + 1) + min); else if (min) result = min; else if (max) result = max; else result = this._random(8, 2); return result; }, /** * Get random words * * @param {Number} min (optional) minimal words count * @param {Number} max (optional) maximal words count * @return {Object} array of random words */ words: function(min, max) { var result = []; var count = this._count(min, max); // get random words while (result.length < count) { var pos = Math.floor(Math.random() * this._words.length); var rnd = this._words[pos]; // do not allow same word twice in a row if (result.length && result[result.length - 1] === rnd) { continue; } result.push(rnd); } return result; }, /** * Generate sentence * * @param {Number} min (optional) minimal words count * @param {Number} max (optional) maximal words count * @return {String} sentence */ sentence: function(min, max) { var words = this.words(min, max); // add comma(s) to sentence var index = this._random(6, 2); while (index < words.length - 2) { words[index] += ","; index += this._random(6, 2); } // append puctation on end var punct = "...!?" words[words.length - 1] += punct.charAt(Math.floor(Math.random() * punct.length)); // uppercase first letter words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1); return words.join(" "); }, /** * Generate paragraph * * @param {Number} min (optional) minimal words count * @param {Number} max (optional) maximal words count * @return {String} paragraph */ paragraph: function(min, max) { if (!min && !max) { min = 20; max = 60; } var result = ""; var count = this._count(min, max); // append sentences until limit is reached while (result.slice(0, -1).split(" ").length < count) { result += this.sentence() + " "; } result = result.slice(0, -1) // remove words if (result.split(" ").length > count) { var punct = result.slice(-1); result = result.split(" ").slice(0, count).join(" "); result = result.replace(/,$/, ""); result += punct; } return result; } } })();
π Ressources
Hash Generator
Generate cryptographic hashes (MD5, SHA-1) from user input. Explore web cryptography APIs.
π Difficulty: β β β β
π Source Code
code-tabs
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hash generator</title> <script src="https://code.iconify.design/iconify-icon/1.0.8/iconify-icon.min.js"></script> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="container"> <div class="header-container"> <h1 class="gradient-font">Hash Generator</h1> <h2> Online tool to instantly generate the hash value of an input text </h2> </div> <div class="generated-hash-container"> <textarea id="inputText">Text example</textarea> <div class="calculated-hashes-container"> <div class="calculated-hash-container"> <div class="left"> <span class="hash-code-algo">SHA-1</span> <span class="hash-code-value" id="SHA1"></span> </div> <button class="icon-button copy-button" title="Copy"> <iconify-icon icon="ci:copy" class="icon"></iconify-icon> </button> </div> <div class="calculated-hash-container"> <div class="left"> <span class="hash-code-algo">SHA-256</span> <span class="hash-code-value" id="SHA256"></span> </div> <button class="icon-button copy-button" title="Copy"> <iconify-icon icon="ci:copy" class="icon"></iconify-icon> </button> </div> <div class="calculated-hash-container"> <div class="left"> <span class="hash-code-algo">SHA-384</span> <span class="hash-code-value" id="SHA384"></span> </div> <button class="icon-button copy-button" title="Copy"> <iconify-icon icon="ci:copy" class="icon"></iconify-icon> </button> </div> <div class="calculated-hash-container"> <div class="left"> <span class="hash-code-algo">SHA-512</span> <span class="hash-code-value" id="SHA512"></span> </div> <button class="icon-button copy-button" title="Copy"> <iconify-icon icon="ci:copy" class="icon"></iconify-icon> </button> </div> </div> </div> </div> <script src="script.js"></script> </body> </html>style.css
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --primary-color: #c3e3ee; --secondary-color: #26688b; --white-color: #fff; --text-color: #424242; --border-color: #c3e3ee73; --hover-color: #ddd; } body { font-family: "Poppins", sans-serif; min-height: 100vh; background-image: url("background.jpg"); background-position: center; background-size: cover; display: flex; justify-content: center; align-items: center; } .container { backdrop-filter: blur(30px); max-width: 950px; border: 2px var(--border-color) solid; padding: 50px; border-radius: 10px; box-shadow: 0px 0px 7px 0 rgba(0, 0, 0, 0.1); } @media only screen and (min-width: 576px) { .container { padding: 50px 100px; } } .header-container { text-align: center; margin-bottom: 30px; } h1 { color: var(--secondary-color); font-size: 30px; } @media only screen and (min-width: 576px) { h1 { font-size: 40px; } } h2 { font-size: 14px; font-weight: 400; } @media only screen and (min-width: 576px) { h2 { font-size: 17px; } } textarea { width: 100%; min-height: 150px; background-color: var(--primary-color); padding: 8px 16px; font-size: 18px; border-radius: 5px; border: none; outline: none; transition: background-color 1s ease; } textarea:hover { background-color: var(--hover-color); } .calculated-hashes-container { display: grid; grid-template-columns: 1fr; row-gap: 10px; margin-top: 10px; } .calculated-hashes-container .calculated-hash-container { background-color: var(--primary-color); min-width: 0; padding: 8px 16px; display: flex; align-items: center; justify-content: space-between; border-radius: 5px; } .calculated-hash-container .left { min-width: 0; display: flex; align-items: center; column-gap: 10px; } .calculated-hash-container .left .hash-code-algo { background-color: var(--secondary-color); color: var(--white-color); flex-shrink: 0; font-size: 14px; font-weight: 600; width: 85px; text-align: center; border-radius: 5px; } .calculated-hash-container .left .hash-code-value { overflow: hidden; text-overflow: ellipsis; letter-spacing: 1px; font-size: 14px; white-space: nowrap; } .icon-button { all: initial; height: 20px; width: 20px; border-radius: 3px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: background-color 1s ease; } @media only screen and (min-width: 576px) { .icon-button { height: 30px; width: 30px; } } .icon-button:hover { background-color: var(--secondary-color); } .icon { font-size: 25px; color: var(--text-color); } .icon-button:hover .icon { color: var(--white-color); }script.js
const SHA1 = document.getElementById("SHA1"); const SHA256 = document.getElementById("SHA256"); const SHA384 = document.getElementById("SHA384"); const SHA512 = document.getElementById("SHA512"); const inputText = document.getElementById("inputText"); const copyButtons = document.querySelectorAll(".copy-button"); handleKeyPress(); document.addEventListener("keyup", handleKeyPress); copyButtons.forEach((copyButton) => copyButton.addEventListener("click", (event) => copyHashCodeToClipboard(event)) ); function handleKeyPress() { if (inputText.value == "") { SHA1.innerHTML = ""; SHA256.innerHTML = ""; SHA384.innerHTML = ""; SHA512.innerHTML = ""; return; } encode(inputText.value, "SHA-1").then((hash) => { SHA1.innerHTML = hash; }); encode(inputText.value, "SHA-256").then((hash) => { SHA256.innerHTML = hash; }); encode(inputText.value, "SHA-384").then((hash) => { SHA384.innerHTML = hash; }); encode(inputText.value, "SHA-512").then((hash) => { SHA512.innerHTML = hash; }); } async function encode(text, algo) { const encoder = new TextEncoder(); const data = encoder.encode(text); const hashBuffer = await crypto.subtle.digest(algo, data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray .map((byte) => byte.toString(16).padStart(2, "0")) .join(""); return hashHex; } async function copyHashCodeToClipboard(event) { try { if (!navigator?.clipboard?.writeText) { return; } const hashCodeElement = event.target.closest(".calculated-hash-container").querySelector(".hash-code-value"); await navigator.clipboard.writeText(hashCodeElement.textContent); const hashCodeValue = hashCodeElement.textContent; hashCodeElement.textContent = 'Copied!'; setTimeout(() => hashCodeElement.textContent = hashCodeValue, "1000"); } catch (err) { console.error(err); } }
π Ressources
π Ready to Build Your Portfolio?
You now have 12 practical web projects to level up your frontend skillsβfrom beginner-friendly calculators to expert-level weather apps. Each project is designed to:
β
Teach real-world skills (APIs, DOM manipulation, algorithms)
β
Boost your portfolio with interactive, deployable apps
β
Prepare you for interviews with common technical challenges