Forum Discussion
Custom Interactive Product Match – Built with Code Snippet (Accessible & Responsive)
Hi everyone!
For this Build-a-Thon, I wanted to push the boundaries of the Rise 360 Code Block by creating a custom-coded matching activity.
In this challenge, learners must match product benefits with the correct item. Beyond the clean, modern look, I focused on two key pillars:
- Dual-Interaction Accessibility: To ensure a great experience for everyone, I’ve implemented both Drag-and-Drop AND Click-to-Select functionality. This makes the activity fully accessible for users on touch devices or those who prefer clicking.
- Gamification & Feedback: I added a dynamic progress bar and a custom CSS confetti celebration upon completion to reward the learner.
Check out the demo here:
https://360.articulate.com/review/content/c1f2b467-0d8e-4b86-92db-d62456b87e7e/review
I’d love to hear your thoughts!
5 Replies
- KathrinQuillingCommunity Member
I like this gamification pretty much. Maybe you would likte to share the code also?
- LoriSims-837662Community Member
I agree. This is great! Would you consider sharing the code?
- AkiahGipson-1c1Community Member
I love this. Would you please share the code?
- VirginieBergonCommunity Member
Hi everyone,
Here's the code :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;800&display=swap');
:root {
--carrefour-blue: #003896;
--carrefour-green: #27ae60;
--progress-orange: #f39c12;
--bg-soft-green: #f2f7f2;
--accent-light: #e8f5e9;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-soft-green);
margin: 0; padding: 30px 15px;
display: flex; flex-direction: column; align-items: center;
color: #2c3e50; overflow-x: hidden;
-webkit-font-smoothing: antialiased;
user-select: none;
}
h2 { font-weight: 800; font-size: 1.2rem; margin-bottom: 25px; letter-spacing: -0.02em; text-align: center; color: #1a3c1a; }
.progress-container {
width: 100%; max-width: 400px; height: 8px;
background: #e0eadd; border-radius: 10px; margin-bottom: 40px;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
}
.progress-bar {
height: 100%; width: 0%; background: var(--progress-orange);
border-radius: 10px; transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.zones-container {
display: grid; grid-template-columns: repeat(5, 1fr);
gap: 12px; width: 100%; max-width: 1100px; margin-bottom: 50px;
}
.target-box {
background: #ffffff; border-radius: 16px;
border: 2px solid #e1e8e1; display: flex; flex-direction: column;
transition: all 0.3s ease; cursor: pointer;
box-shadow: 0 4px 6px rgba(0,0,0,0.02);
}
.target-box.selected-target { border-color: var(--carrefour-blue); background: var(--accent-light); transform: translateY(-2px); }
.label-product {
padding: 10px 5px; text-align: center; font-size: 0.7rem; font-weight: 800;
color: var(--carrefour-blue); border-bottom: 1px solid #f2f2f2;
text-transform: uppercase; min-height: 40px; display: flex; align-items: center; justify-content: center;
}
.drop-zone {
height: 100px; display: flex; align-items: center; justify-content: center;
padding: 12px; font-size: 0.65rem; color: #adb5bd; text-align: center;
}
.drop-zone.correct { color: #2c3e50; font-weight: 600; font-size: 0.75rem; background: #fff; border-radius: 0 0 16px 16px; }
.sources-container {
display: flex; flex-wrap: wrap; justify-content: center;
gap: 15px; width: 100%; max-width: 900px;
}
.item {
width: 185px; padding: 18px; border-radius: 12px; background: #ffffff;
cursor: grab; font-size: 0.8rem; line-height: 1.5; font-weight: 500;
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.05);
border: 1px solid transparent; touch-action: none;
transition: all 0.3s ease;
}
.item:hover { transform: translateY(-3px); border-color: #d1d8d1; }
.item.selected-item { border: 2px solid var(--progress-orange); background: #fffdfa; }
.item.hidden { display: none !important; }
.confetti {
position: fixed; width: 8px; height: 8px; top: -10px; z-index: 9999;
animation: fall 3s linear forwards;
}
@keyframes fall { to { transform: translateY(105vh) rotate(720deg); opacity: 0; } }
#feedback {
margin-top: 30px; font-weight: 800; color: var(--carrefour-green);
display: none; text-align: center; font-size: 1.1rem;
}
@keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } }
MeDia (max-width: 850px) {
.zones-container { grid-template-columns: repeat(2, 1fr); }
.zones-container > :last-child { grid-column: span 2; }
.item { width: 100%; max-width: 300px; }
}
</style>
</head>
<body>
<h2>🎯 Associez chaque bénéfice au bon produit</h2>
<div class="progress-container">
<div class="progress-bar" id="progressBar"></div>
</div>
<div class="zones-container">
<div class="target-box" data-id="prod1"><div class="label-product">Œufs FQC</div><div class="drop-zone">Glisser ou cliquer</div></div>
<div class="target-box" data-id="prod2"><div class="label-product">Pâtes Bio</div><div class="drop-zone">Glisser ou cliquer</div></div>
<div class="target-box" data-id="prod3"><div class="label-product">Lentilles</div><div class="drop-zone">Glisser ou cliquer</div></div>
<div class="target-box" data-id="prod4"><div class="label-product">Confiture</div><div class="drop-zone">Glisser ou cliquer</div></div>
<div class="target-box" data-id="prod5"><div class="label-product">Pommes Bio</div><div class="drop-zone">Glisser ou cliquer</div></div>
</div>
<div class="sources-container" id="sourceBox">
<div class="item" draggable="true" id="arg1" data-match="prod1">"Riche en Oméga 3 naturels et issu d'une filière sans OGM."</div>
<div class="item" draggable="true" id="arg2" data-match="prod2">"Énergie longue durée grâce à un index glycémique bas."</div>
<div class="item" draggable="true" id="arg3" data-match="prod3">"Alternative végétale riche en protéines, sans additifs."</div>
<div class="item" draggable="true" id="arg4" data-match="prod4">"Le plaisir gourmand avec 30% de sucre en moins."</div>
<div class="item" draggable="true" id="arg5" data-match="prod5">"Zéro résidu de pesticides pour une consommation brute."</div>
</div>
<div id="feedback">🌿 Mission réussie ! Expertise validée.</div>
<script>
let selectedItem = null;
let solved = 0;
const total = 5;
const sourceBox = document.getElementById('sourceBox');
for (let i = sourceBox.children.length; i >= 0; i--) {
sourceBox.appendChild(sourceBox.children[Math.random() * i | 0]);
}
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', e.target.id);
selectedItem = item;
});
item.addEventListener('click', () => {
document.querySelectorAll('.item').forEach(i => i.classList.remove('selected-item'));
selectedItem = item;
item.classList.add('selected-item');
});
});
document.querySelectorAll('.target-box').forEach(box => {
box.addEventListener('dragover', (e) => { e.preventDefault(); box.classList.add('selected-target'); });
box.addEventListener('dragleave', () => box.classList.remove('selected-target'));
box.addEventListener('drop', (e) => {
e.preventDefault();
box.classList.remove('selected-target');
const id = e.dataTransfer.getData('text/plain');
validate(document.getElementById(id), box);
});
box.addEventListener('click', () => { if (selectedItem) validate(selectedItem, box); });
});
function validate(item, box) {
if (!item || box.querySelector('.drop-zone').classList.contains('correct')) return;
if (item.getAttribute('data-match') === box.getAttribute('data-id')) {
const dz = box.querySelector('.drop-zone');
dz.innerText = item.innerText;
dz.classList.add('correct');
item.classList.add('hidden');
solved++;
document.getElementById('progressBar').style.width = (solved/total)*100 + "%";
selectedItem = null;
if(solved === total) finish();
} else {
item.style.animation = "shake 0.4s ease";
setTimeout(() => item.style.animation = "", 400);
item.classList.remove('selected-item');
selectedItem = null;
}
}
function finish() {
document.getElementById('feedback').style.display = 'block';
createConfetti();
// MÉTHODE DE SIGNAL MULTI-PROTOCOLE POUR RISE 360
const signals = [
{ type: 'livelesson-complete', value: true },
{ type: 'complete', value: true },
{ message: 'submit-answer', status: 'completed' }
];
signals.forEach(sig => {
window.parent.postMessage(sig, '*');
});
document.getElementById('feedback').scrollIntoView({ behavior: 'smooth' });
}
function createConfetti() {
const colors = ['#f39c12', '#27ae60', '#003896', '#ed1c24'];
for (let i = 0; i < 70; i++) {
const c = document.createElement('div');
c.className = 'confetti';
c.style.left = Math.random() * 100 + 'vw';
c.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
c.style.animationDuration = (Math.random() * 2 + 1) + 's';
document.body.appendChild(c);
setTimeout(() => c.remove(), 3000);
}
}
</script>
</body>
</html>
Enjoy !
- DavidBaird-AUSCommunity Member
Very nice.