๐Ÿ›๏ธ Black Friday Price Dash ๐Ÿท๏ธ

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!
Score: 0 Time: 60s Best: 0
๐Ÿ›’
Ready to shop?
  SALE
Press "Start" when the doors open...

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?

Here is the code
CSS
.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;
}
HTML
<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">&nbsp;</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>
JS
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);