Frontend Forever App
We have a mobile app for you to download and use. And you can unlock many features in the app.
Get it now
Intall Later
Run
HTML
CSS
Javascript
Output
Document
Random Picker Racing
GO
Again, again!
@charset "UTF-8"; @import url(https://fonts.googleapis.com/css?family=Nunito+Sans:300,400,600,700,800); *, :after, :before { box-sizing: border-box; padding: 0; margin: 0; } :root { --moveTime: 500ms; } body { background-image: linear-gradient(#446, #222); block-size: 100dvh; font-family: sans-serif; display: flex; inline-size: 100dvw; margin: 0; } main { flex: 1 0 auto; padding: 0 2rem; } h1 { color: hotpink; filter: drop-shadow(0 0 .1em #000); font-size: 6vw; margin: 0; padding: 1rem 0; text-align: center; text-transform: uppercase; } aside { background-color: #222; color: #ccc; flex: 0 1 auto; padding: 0 2rem; } #names { background-color: #444; border: solid 1px #ccc; box-sizing: border-box; color: #ccc; inline-size: 100%; margin-block-start: .5rem; padding: .5rem; } #emoji { background-color: #444; border: solid 1px #ccc; box-sizing: border-box; font-size: 2rem; inline-size: 4rem; margin-block-start: .5rem; padding: .5rem; text-align: center; } #lanes { background-color: #ccc; background-image:linear-gradient(#ccc, #bbb); border-inline-start: solid 2px hotpink; list-style: none; margin: 0 0 1rem 0; padding: 0 2rem; > li { block-size: 4rem; position: relative; & + li { border-top: dashed 2px #fff; } &:nth-child(6n - 5) { filter: hue-rotate(0deg); } &:nth-child(6n - 1) { filter: hue-rotate(60deg); } &:nth-child(6n - 3) { filter: hue-rotate(120deg); } &:nth-child(6n - 4) { filter: hue-rotate(180deg); } &:nth-child(6n - 2) { filter: hue-rotate(240deg); } &:nth-child(6n) { filter: hue-rotate(300deg); } } } .racer { display: inline-block; font-size: 2rem; position: absolute; right: -2rem; top: 1rem; transition: right var(--moveTime) linear; .emoji { display: inline-block; } .name { font-size: small; left: 0; position: absolute; right: 0; text-align: center; top: -0.75rem; } } .active .emoji { animation: tilt-n-move-shaking 0.25s infinite .5s; } .button-panel { text-align: end; } #go, #reset { animation: pulse 2s infinite; border: none; } #go { background-color: hotpink; border-radius: 50%; block-size: 3rem; font-weight: bold; inline-size: 3rem; } #reset { background-color: hotpink; border-radius: 1rem; color: #fff; padding: .5rem 1rem; } main > p { text-align: center; } #commentary { background-color: #fff; border-radius: 1rem; display: inline-block; font-size: 1.5rem; padding: .5rem 1rem; &:empty { padding: 0; } } @keyframes tilt-n-move-shaking { 0% { transform: translate(0, 0) rotate(0deg); } 25% { transform: translate(1px, 1px) rotate(1deg); } 50% { transform: translate(0, 0) rotate(0eg); } 75% { transform: translate(-1px, 1px) rotate(-1deg); } 100% { transform: translate(0, 0) rotate(0deg); } } @keyframes pulse { 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7); } 70% { transform: scale(1); box-shadow: 0 0 0 1rem rgba(0, 0, 0, 0); } 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); } }
console.log("Event Fired") const names = document.querySelector("#names"); const emojiInput = document.querySelector("#emoji"); const lanes = document.querySelector("#lanes"); const go = document.querySelector("#go"); const reset = document.querySelector("#reset"); const commentary = document.querySelector("#commentary"); const boosts = [1, 2, 3, 5]; const moveTime = 400; // loaded from CDN const jsConfetti = new JSConfetti(); let list = []; let loop = undefined; let previousLeader = undefined; const defaultList = [ { name: "A", score: 0 }, { name: "B", score: 0 }, { name: "C", score: 0 }, { name: "D", score: 0 } ]; // ---------- settings ---------- const getListFromTextarea = () => { let lines = names.value.split("\n"); lines = lines.filter(Boolean); // removes any empty strings list = lines.map(line => { return { name: line, score: 0}; }); }; const getRandomItem = () => { getListFromTextarea(); return list.length ? list[Math.floor(list.length * Math.random())].name : "No items found"; } const updateStoredList = () => { getListFromTextarea(); localStorage.setItem("list", JSON.stringify(list)); updateItemsList(); }; const loadStoredList = () => { list = JSON.parse(localStorage.getItem("list")) || defaultList; const items = list.map(i => i.name); names.value = items.join("\n"); updateItemsList(); } const checkIfWeHaveEnoughRacers = () => { go.hidden = lanes.children.length < 2; }; const updateItemsList = () => { lanes.innerHTML = ""; const emoji = emojiInput.value || "🚴"; const frag = document.createDocumentFragment(); list.forEach((item) => { if (item.name && item.name.length) { const li = document.createElement("li"); li.innerHTML = `
${emoji}
${item.name}
`; frag.appendChild(li); } }); lanes.appendChild(frag); checkIfWeHaveEnoughRacers(); }; // ---------- race mechanics ---------- const randomBoost = () => { return boosts[Math.floor(boosts.length * Math.random())]; }; const randomRacerIndex = () => { return Math.floor(list.length * Math.random()); }; const checkForWin = () => { const winner = list.find(item => item.score > 99); if (winner) { clearTimeout(loop); setTimeout(() => { reset.hidden = false; commentary.textContent = `${winner.name} wins! 🏆`; jsConfetti.addConfetti(); }, moveTime * 1.5); lanes.classList.remove("active"); } return; }; const randomPhrase = () => { const phrases = [ "takes the lead", "is ahead", "in the lead", "is winning", "leading", "at the front", "is first" ]; return phrases[Math.floor(phrases.length * Math.random())]; }; const move = () => { const racerIndex = randomRacerIndex(); const boost = randomBoost(); list.forEach((racer, index) => { racer.score++; if (index === racerIndex) { racer.score += boost; } }); list.forEach((item, index) => { const racer = lanes.querySelector(`li:nth-child(${index + 1}) .racer`); racer.style.right = `${list[index].score}%`; }); // get index of highest score const winning = list.map(item => item.score).reduce((iMax, x, i, arr) => x > arr[iMax] ? i : iMax, 0); if (winning !== previousLeader) { const winner = list.map(item => item.name)[winning]; const phrase = randomPhrase(); commentary.textContent = `${winner} ${phrase}`; previousLeader = winning; } checkForWin(); }; const race = () => { commentary.textContent = "And they're off"; go.hidden = true; lanes.classList.add("active"); names.disabled = true; emojiInput.disabled = true; loop = setInterval(() => { move(); }, moveTime); } const startAgain = () => { list.forEach(item => item.score = 0); [...lanes.querySelectorAll(".racer")].forEach(racer => { racer.style.right = "-2rem"; }) reset.hidden = true; go.hidden = false; commentary.textContent = ""; names.disabled = false; emojiInput.disabled = false; jsConfetti.clearCanvas(); }; // ---------- events ----------- emojiInput.addEventListener("change", updateItemsList); names.addEventListener("input", updateStoredList); go.addEventListener("click", race); reset.addEventListener("click", startAgain); window.onload = loadStoredList;