Forum Discussion
Cheese Party
Wow, really amazing. Are you able to share the course or some of the code you used in this course? Thank you so much.
Thank you, I’m glad you liked my example. Most of the blocks can be created quite easily with the help of AI or by finding ready-made examples online.
I’d also like to share a block with a “matching lines” mechanic. It’s my favorite block in the project. I remember how many hours it used to take me to set up a similar interaction in Storyline (spoiler: a lot 😆). Now it can be done much faster, and there’s no need to worry that something might accidentally break if a user starts experimenting with it.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cheese Around the World</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Poppins', sans-serif;
margin: 0;
padding: 20px;
background: #fff;
}
h2 {
text-align: center;
color: #d9991e;
font-size: 1.8rem;
}
.instructions {
text-align: center;
margin-bottom: 20px;
font-size: 1.1rem;
}
.container {
display: flex;
justify-content: space-between;
max-width: 900px;
margin: 0 auto;
position: relative;
}
.cheese-list, .country-list {
display: flex;
flex-direction: column;
gap: 15px;
position: relative;
}
.item {
padding: 12px 18px;
background-color: #fef6e4;
border-radius: 8px;
cursor: pointer;
font-size: 1.1rem;
text-align: left;
user-select: none;
position: relative;
transition: transform 0.2s;
}
.item:hover { transform: scale(1.03); }
.item.correct { background-color: #d4edda; }
.item.incorrect { background-color: #f8d7da; }
.item::after {
content: '';
width: 14px;
height: 14px;
background-color: #fbc02d;
border-radius: 50%;
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
}
.cheese-list .item::after { right: -7px; }
.country-list .item::after { left: -7px; }
#svg-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
button {
margin: 10px 5px 0;
background-color: #fbc02d;
color: #000;
border: none;
padding: 10px 18px;
border-radius: 6px;
cursor: pointer;
font-weight: 400;
font-size: 1.1rem;
display: none;
}
button:hover { opacity: 0.9; }
.feedback {
margin: 10px 0 0;
text-align: center;
font-size: 1.1rem;
font-weight: 500;
}
</style>
</head>
<body>
<h2>Cheese Around the World</h2>
<div class="instructions">
Connect each cheese to its country of origin by drawing a horizontal line. Click "Submit" after connecting all.
</div>
<div class="container">
<div class="cheese-list"></div>
<svg id="svg-lines"></svg>
<div class="country-list"></div>
</div>
<div style="text-align:center;">
<div class="feedback" id="feedback"></div>
<button id="submit">Submit</button>
<button id="tryAgain">Try Again</button>
<button id="showAnswers">Show Correct Answers</button>
</div>
<audio id="correct-sound" src="https://actions.google.com/sounds/v1/cartoon/clang_and_wobble.ogg"></audio>
<audio id="wrong-sound" src="https://orangefreesounds.com/wp-content/uploads/2015/01/Fail-sound-effect-2.mp3"></audio>
<script>
const data = [
{cheese:"Brie", country:"France"},
{cheese:"Cheddar", country:"England"},
{cheese:"Gouda", country:"Netherlands"},
{cheese:"Mozzarella", country:"Italy"},
{cheese:"Emmental", country:"Switzerland"}
];
function shuffle(array){ return array.sort(()=>Math.random()-0.5); }
const cheesesContainer = document.querySelector('.cheese-list');
const countriesContainer = document.querySelector('.country-list');
shuffle(data).forEach(d=>{
const c = document.createElement('div');
c.className='item';
c.dataset.cheese=d.cheese;
c.textContent=d.cheese;
cheesesContainer.appendChild(c);
});
shuffle(data).forEach(d=>{
const cn = document.createElement('div');
cn.className='item';
cn.dataset.country=d.country;
cn.textContent=d.country;
countriesContainer.appendChild(cn);
});
const cheeses = document.querySelectorAll('.cheese-list .item');
const countries = document.querySelectorAll('.country-list .item');
let selected = null;
let matches = {};
const svg = document.getElementById('svg-lines');
const feedback = document.getElementById('feedback');
const submitBtn = document.getElementById('submit');
const tryAgainBtn = document.getElementById('tryAgain');
const showAnswersBtn = document.getElementById('showAnswers');
const correctSound = document.getElementById('correct-sound');
const wrongSound = document.getElementById('wrong-sound');
wrongSound.volume = 0.25;
function getPoint(el, side){
const rect = el.getBoundingClientRect();
const parentRect = svg.getBoundingClientRect();
const y = rect.top + rect.height/2 - parentRect.top;
const x = side==='right' ? rect.right - 7 - parentRect.left : rect.left + 7 - parentRect.left;
return {x, y};
}
function selectItem(el){
selected = el;
document.querySelectorAll('.item').forEach(i=>i.style.border='');
el.style.border='2px solid #fbc02d';
}
function connectItem(el){
if(!selected || selected===el) return;
const leftEl = selected.dataset.cheese ? selected : el;
const rightEl = selected.dataset.country ? selected : el;
if(matches[leftEl.dataset.cheese]){
svg.removeChild(matches[leftEl.dataset.cheese].line);
}
for(let key in matches){
if(matches[key].country===rightEl.dataset.country){
svg.removeChild(matches[key].line);
delete matches[key];
}
}
const start = getPoint(leftEl,'right');
const end = getPoint(rightEl,'left');
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
line.setAttribute('x1', start.x);
line.setAttribute('y1', start.y);
line.setAttribute('x2', end.x);
line.setAttribute('y2', end.y);
line.setAttribute('stroke', '#fbc02d');
line.setAttribute('stroke-width', 3);
svg.appendChild(line);
matches[leftEl.dataset.cheese] = {country:rightEl.dataset.country, line, cheeseEl:leftEl, countryEl:rightEl};
selected.style.border='';
selected=null;
if(Object.keys(matches).length===cheeses.length){
submitBtn.style.display='inline-block';
}
}
cheeses.forEach(c=>c.addEventListener('click', ()=>selectItem(c)));
countries.forEach(c=>c.addEventListener('click', ()=>connectItem(c)));
submitBtn.addEventListener('click', ()=>{
submitBtn.style.display='none';
let correctCount=0;
for(let ch in matches){
if(matches[ch].country===data.find(d=>d.cheese===ch).country){
matches[ch].cheeseEl.classList.add('correct');
matches[ch].countryEl.classList.add('correct');
correctCount++;
} else {
matches[ch].cheeseEl.classList.add('incorrect');
matches[ch].countryEl.classList.add('incorrect');
}
}
cheeses.forEach(c => c.style.pointerEvents = 'none');
countries.forEach(c => c.style.pointerEvents = 'none');
if(correctCount===cheeses.length){
feedback.textContent="✅ Great job! All cheeses matched correctly!";
correctSound.play();
} else {
feedback.textContent="❌ Some matches are incorrect. Try again or click 'Show Correct Answers'.";
wrongSound.play();
tryAgainBtn.style.display='inline-block';
showAnswersBtn.style.display='inline-block';
}
});
tryAgainBtn.addEventListener('click', ()=>{
Object.values(matches).forEach(m=> svg.removeChild(m.line));
Object.values(matches).forEach(m=>{
m.cheeseEl.classList.remove('correct','incorrect');
m.countryEl.classList.remove('correct','incorrect');
});
matches={};
feedback.textContent='';
submitBtn.style.display='none';
tryAgainBtn.style.display='none';
showAnswersBtn.style.display='none';
cheeses.forEach(c => c.style.pointerEvents = '');
countries.forEach(c => c.style.pointerEvents = '');
});
showAnswersBtn.addEventListener('click', ()=>{
Object.values(matches).forEach(m=> svg.removeChild(m.line));
matches={};
cheeses.forEach(c=>{
const correctCountry = data.find(d=>d.cheese===c.dataset.cheese).country;
const cn = Array.from(countries).find(x=>x.dataset.country===correctCountry);
const start = getPoint(c,'right');
const end = getPoint(cn,'left');
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
line.setAttribute('x1', start.x);
line.setAttribute('y1', start.y);
line.setAttribute('x2', end.x);
line.setAttribute('y2', end.y);
line.setAttribute('stroke', '#28a745');
line.setAttribute('stroke-width', 3);
svg.appendChild(line);
matches[c.dataset.cheese]={country:correctCountry,line,cheeseEl:c,countryEl:cn};
});
document.querySelectorAll('.item').forEach(i=>{
i.classList.remove('incorrect');
i.classList.add('correct');
});
cheeses.forEach(c => c.style.pointerEvents = 'none');
countries.forEach(c => c.style.pointerEvents = 'none');
tryAgainBtn.style.display='none';
showAnswersBtn.style.display='none';
feedback.textContent = "Here is the correct match";
});
</script>
</body>
</html>
Related Content
- 4 months ago
- 2 years ago