The doors are open and the Black Friday deals are flying ๐! Headphones at 25% off, a smart TV at -40%, sneakers at half price: but what do they really cost? Each deal shows the original price and the discount, and you pick the right sale price among three. Tap an answer, or press 1, 2 or 3 on your keyboard. You have 60 seconds, and every wrong price costs you 3 seconds. The discounts start easy and get trickier as your score grows, from 50% off round prices to 35% off odd ones. How many bargains can you grab before the store closes?
.bfd-game {
text-align: center;
}
.bfd-game .instructions {
max-width: 500px;
margin: 0 auto 15px;
font-size: 1em;
color: #555;
}
.bfd-info {
display: flex;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
margin-bottom: 12px;
font-size: 1.1em;
}
.bfd-card {
max-width: 360px;
margin: 0 auto;
padding: 18px 16px;
background: #111111;
color: #ffffff;
border-radius: 14px;
border: 3px solid #ffd400;
box-shadow: 0 6px 0 #000000;
}
.bfd-card.right {
border-color: #2ecc71;
}
.bfd-card.wrong {
border-color: #ff3b3b;
animation: bfd-shake 0.35s;
}
@keyframes bfd-shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-8px); }
75% { transform: translateX(8px); }
}
.bfd-item {
font-size: 3em;
line-height: 1.2;
}
.bfd-name {
font-size: 1.2em;
font-weight: bold;
margin: 4px 0 10px;
}
.bfd-deal {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.bfd-was {
font-size: 1.4em;
color: #bbbbbb;
text-decoration: line-through;
}
.bfd-tag {
background: #ffd400;
color: #111111;
font-weight: bold;
font-size: 1.3em;
padding: 4px 12px;
border-radius: 6px;
transform: rotate(-4deg);
}
.bfd-answers {
display: flex;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
max-width: 420px;
margin: 16px auto 0;
touch-action: manipulation;
}
.bfd-answer {
flex: 1 1 100px;
min-height: 56px;
font-size: 1.4em;
font-weight: bold;
background: #ffffff;
color: #111111;
border: 3px solid #111111;
border-radius: 10px;
cursor: pointer;
box-shadow: 0 4px 0 #111111;
touch-action: manipulation;
}
.bfd-answer:active:not(:disabled) {
transform: translateY(2px);
box-shadow: 0 2px 0 #111111;
}
.bfd-answer:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.bfd-answer.good {
background: #2ecc71;
border-color: #1e8e4e;
opacity: 1;
}
.bfd-answer.bad {
background: #ff3b3b;
border-color: #b01f1f;
color: #ffffff;
opacity: 1;
}
.bfd-message {
margin-top: 15px;
font-weight: bold;
min-height: 1.5em;
}
.bfd-btn {
margin-top: 12px;
padding: 10px 24px;
font-size: 1em;
background-color: #111111;
color: #ffd400;
font-weight: bold;
border: none;
border-radius: 5px;
cursor: pointer;
}
.bfd-btn:hover {
background-color: #333333;
}
.bfd-btn:disabled {
background-color: #ccc;
color: #666;
cursor: not-allowed;
}
<div class="bfd-game">
<h1>๐๏ธ Black Friday Price Dash ๐ท๏ธ</h1>
<div class="instructions">
The sale is on! Pick the right sale price for each deal before the 60 seconds run out. Tap an answer, or press 1, 2 or 3. A wrong price costs you 3 seconds!
</div>
<div class="bfd-info">
<span>Score: <span id="bfdScore">0</span></span>
<span>Time: <span id="bfdTime">60</span>s</span>
<span>Best: <span id="bfdBest">0</span></span>
</div>
<div class="bfd-card" id="bfdCard">
<div class="bfd-item" id="bfdItem">๐</div>
<div class="bfd-name" id="bfdName">Ready to shop?</div>
<div class="bfd-deal">
<span class="bfd-was" id="bfdWas"> </span>
<span class="bfd-tag" id="bfdTag">SALE</span>
</div>
</div>
<div class="bfd-answers" id="bfdAnswers">
<button class="bfd-answer" data-index="0" disabled>?</button>
<button class="bfd-answer" data-index="1" disabled>?</button>
<button class="bfd-answer" data-index="2" disabled>?</button>
</div>
<div class="bfd-message" id="bfdMessage">Press "Start" when the doors open...</div>
<button class="bfd-btn" id="bfdStart">Start</button>
</div>
const bfdScoreEl = document.getElementById('bfdScore');
const bfdTimeEl = document.getElementById('bfdTime');
const bfdBestEl = document.getElementById('bfdBest');
const bfdCardEl = document.getElementById('bfdCard');
const bfdItemEl = document.getElementById('bfdItem');
const bfdNameEl = document.getElementById('bfdName');
const bfdWasEl = document.getElementById('bfdWas');
const bfdTagEl = document.getElementById('bfdTag');
const bfdMessageEl = document.getElementById('bfdMessage');
const bfdStartBtn = document.getElementById('bfdStart');
const bfdAnswerBtns = Array.prototype.slice.call(document.querySelectorAll('.bfd-answer'));
const bfdHiddenScore = document.getElementById('hiddenScore');
const BFD_GAME_SECONDS = 60;
const BFD_WRONG_PENALTY = 3;
const BFD_ITEMS = [
['๐ง', 'Headphones'], ['๐บ', 'Smart TV'], ['๐', 'Sneakers'], ['๐ฎ', 'Game console'],
['๐ป', 'Laptop'], ['๐ฑ', 'Phone'], ['โ', 'Smartwatch'], ['๐งฅ', 'Winter coat'],
['๐งธ', 'Teddy bear'], ['โ', 'Coffee machine'], ['๐ท', 'Camera'], ['๐', 'Backpack'],
['๐ถ๏ธ', 'Sunglasses'], ['๐ฒ', 'Bike'], ['๐งณ', 'Suitcase'], ['๐ธ', 'Guitar'],
['๐๏ธ', 'Sofa'], ['๐ณ', 'Frying pan'], ['๐งด', 'Perfume'], ['๐', 'Box of books']
];
// Discounts and price steps unlocked as the score grows: easy round numbers first.
const BFD_LEVELS = [
{discounts: [50, 10, 20], step: 10, max: 200},
{discounts: [50, 25, 10, 20, 30], step: 4, max: 300},
{discounts: [25, 30, 40, 60, 70, 75, 15], step: 2, max: 400},
{discounts: [15, 35, 45, 65, 80, 5, 33], step: 1, max: 600}
];
let bfdScore = 0;
let bfdTimeLeft = BFD_GAME_SECONDS;
let bfdRunning = false;
let bfdCorrectIndex = 0;
let bfdLocked = false;
let bfdLastTick = 0;
let bfdFrame = null;
let bfdLastItem = -1;
function bfdReadBest() {
try {
return parseInt(localStorage.getItem('blackFridayPriceDashBest') || '0', 10) || 0;
} catch (e) {
return 0;
}
}
function bfdSaveBest(value) {
try {
localStorage.setItem('blackFridayPriceDashBest', String(value));
} catch (e) {
// storage blocked: best score only lasts for this visit
}
}
let bfdBest = bfdReadBest();
bfdBestEl.textContent = bfdBest;
function bfdRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function bfdPick(list) {
return list[Math.floor(Math.random() * list.length)];
}
function bfdMoney(value) {
const rounded = Math.round(value * 100) / 100;
return '$' + (rounded % 1 === 0 ? String(rounded) : rounded.toFixed(2));
}
function bfdLevel() {
if (bfdScore < 4) return BFD_LEVELS[0];
if (bfdScore < 9) return BFD_LEVELS[1];
if (bfdScore < 15) return BFD_LEVELS[2];
return BFD_LEVELS[3];
}
// Wrong answers that look plausible: the discount amount instead of the price,
// a neighbouring discount, or a near miss that gets closer as the score grows.
function bfdDistractors(original, discount, answer) {
const options = [];
const saved = original * discount / 100;
const closeness = bfdScore < 9 ? 0.2 : (bfdScore < 15 ? 0.1 : 0.05);
const candidates = [
saved,
original * (100 - discount - 10) / 100,
original * (100 - discount + 10) / 100,
answer * (1 + closeness),
answer * (1 - closeness),
original - discount
];
for (let i = 0; i < candidates.length; i++) {
const value = Math.round(candidates[i] * 100) / 100;
const label = bfdMoney(value);
if (value > 0 && value < original && label !== bfdMoney(answer) && options.indexOf(label) === -1) {
options.push(label);
}
}
const shuffled = options.sort(function () { return Math.random() - 0.5; });
return shuffled.slice(0, 2);
}
function bfdNextDeal() {
const level = bfdLevel();
let itemIndex = bfdRandom(0, BFD_ITEMS.length - 1);
if (itemIndex === bfdLastItem) {
itemIndex = (itemIndex + 1) % BFD_ITEMS.length;
}
bfdLastItem = itemIndex;
const item = BFD_ITEMS[itemIndex];
let discount = bfdPick(level.discounts);
let original = bfdRandom(2, Math.floor(level.max / level.step)) * level.step;
let answer = original * (100 - discount) / 100;
let distractors = bfdDistractors(original, discount, answer);
// Rare edge case (tiny prices): fall back to an easy deal with two clear wrong answers.
if (distractors.length < 2) {
original = 100;
discount = 50;
answer = 50;
distractors = ['$40', '$60'];
}
const labels = distractors.slice();
bfdCorrectIndex = bfdRandom(0, 2);
labels.splice(bfdCorrectIndex, 0, bfdMoney(answer));
bfdItemEl.textContent = item[0];
bfdNameEl.textContent = item[1];
bfdWasEl.textContent = bfdMoney(original);
bfdTagEl.textContent = '-' + discount + '%';
bfdCardEl.className = 'bfd-card';
bfdAnswerBtns.forEach(function (btn, index) {
btn.textContent = labels[index];
btn.className = 'bfd-answer';
btn.disabled = false;
});
bfdLocked = false;
}
function bfdAnswer(index) {
if (!bfdRunning || bfdLocked) {
return;
}
bfdLocked = true;
const right = index === bfdCorrectIndex;
bfdAnswerBtns[bfdCorrectIndex].classList.add('good');
if (right) {
bfdScore++;
bfdScoreEl.textContent = bfdScore;
bfdHiddenScore.value = bfdScore;
bfdCardEl.className = 'bfd-card right';
bfdMessageEl.textContent = bfdPick(['Great deal! ๐ค', 'In the bag! ๐๏ธ', 'Bargain hunter! ๐ท๏ธ', 'Sold! โ
']);
} else {
bfdAnswerBtns[index].classList.add('bad');
bfdTimeLeft = Math.max(0, bfdTimeLeft - BFD_WRONG_PENALTY);
bfdCardEl.className = 'bfd-card wrong';
bfdMessageEl.textContent = 'Oops, wrong price! -' + BFD_WRONG_PENALTY + 's';
}
bfdAnswerBtns.forEach(function (btn) { btn.disabled = true; });
setTimeout(function () {
if (bfdRunning) {
bfdNextDeal();
}
}, right ? 450 : 900);
}
function bfdTick(now) {
if (!bfdRunning) {
return;
}
const elapsed = (now - bfdLastTick) / 1000;
bfdLastTick = now;
bfdTimeLeft = Math.max(0, bfdTimeLeft - elapsed);
bfdTimeEl.textContent = Math.ceil(bfdTimeLeft);
if (bfdTimeLeft <= 0) {
bfdEnd();
return;
}
bfdFrame = requestAnimationFrame(bfdTick);
}
function bfdStart() {
bfdScore = 0;
bfdTimeLeft = BFD_GAME_SECONDS;
bfdRunning = true;
bfdScoreEl.textContent = '0';
bfdTimeEl.textContent = BFD_GAME_SECONDS;
bfdHiddenScore.value = 0;
document.querySelector('.share-buttons').style.display = 'none';
bfdStartBtn.disabled = true;
bfdMessageEl.textContent = 'The doors are open, go go go! ๐';
bfdNextDeal();
bfdLastTick = performance.now();
if (bfdFrame) {
cancelAnimationFrame(bfdFrame);
}
bfdFrame = requestAnimationFrame(bfdTick);
}
function bfdEnd() {
bfdRunning = false;
bfdLocked = true;
bfdTimeEl.textContent = '0';
bfdAnswerBtns.forEach(function (btn) { btn.disabled = true; });
let text = 'Store closed! You scored ' + bfdScore + ' deal' + (bfdScore === 1 ? '' : 's') + '.';
if (bfdScore > bfdBest) {
bfdBest = bfdScore;
bfdBestEl.textContent = bfdBest;
bfdSaveBest(bfdBest);
text += ' New best! ๐';
}
bfdMessageEl.textContent = text;
bfdStartBtn.textContent = 'Play again';
bfdStartBtn.disabled = false;
document.querySelector('.share-buttons').style.display = 'flex';
}
bfdAnswerBtns.forEach(function (btn, index) {
btn.addEventListener('touchstart', function (event) {
event.preventDefault();
bfdAnswer(index);
}, {passive: false});
btn.addEventListener('click', function () {
bfdAnswer(index);
});
});
document.addEventListener('keydown', function (event) {
if (!bfdRunning) {
return;
}
if (event.key === '1' || event.key === '2' || event.key === '3') {
event.preventDefault();
bfdAnswer(parseInt(event.key, 10) - 1);
}
});
bfdStartBtn.addEventListener('click', bfdStart);