☕ Hot Cocoa Rush ❄️

Read each order, add the right number of marshmallows 🍡 and candy canes 🍬, then press Serve before the timer runs out. Three mistakes and the café closes!
Served: 0  |  Lives: ❤️❤️❤️  |  Best: 0
Waiting for customers…
☕
Press "Start" to open the café!

Snow is falling and everyone in town wants a hot cocoa ☕. Read each customer's order, tap to add the right number of marshmallows 🍡 and candy canes 🍬, then press Serve before the timer runs out. On a keyboard, M adds a marshmallow, C a candy cane, Backspace empties the cup and Enter serves. A wrong cup or a customer who waits too long costs you a life, and after three mistakes the café closes. Orders get bigger and the queue moves faster as you go.

How many hot cocoas can you serve before closing time?

Here is the code
CSS
.cocoa-game {
    text-align: center;
}

.cocoa-game .instructions {
    max-width: 500px;
    margin: 0 auto 15px;
    font-size: 1em;
    color: #555;
}

.cocoa-info {
    margin-bottom: 10px;
    font-size: 1.2em;
}

.cocoa-counter {
    width: 100%;
    max-width: 340px;
    margin: 0 auto;
    padding: 14px;
    box-sizing: border-box;
    border: 3px solid #6d3b1f;
    border-radius: 12px;
    background: linear-gradient(#fff4e6, #f3d9bd);
    touch-action: manipulation;
    user-select: none;
    -webkit-user-select: none;
}

.cocoa-order {
    min-height: 2.6em;
    font-size: 1em;
    font-weight: bold;
    color: #4e2a12;
}

.cocoa-timer {
    height: 10px;
    margin: 8px 0 12px;
    border-radius: 5px;
    background: #e8c9a6;
    overflow: hidden;
}

.cocoa-timer-bar {
    height: 100%;
    width: 100%;
    background: #43a047;
}

.cocoa-cup {
    min-height: 40px;
    font-size: 30px;
    line-height: 1.2;
    word-break: break-all;
}

.cocoa-buttons {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-top: 10px;
}

.cocoa-btn {
    padding: 12px 4px;
    font-size: 0.9em;
    font-weight: bold;
    border: none;
    border-radius: 8px;
    background: #8d5524;
    color: #fff;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.cocoa-btn:disabled {
    background: #c9b29b;
    cursor: default;
}

.cocoa-btn.cocoa-reset {
    background: #a1887f;
}

.cocoa-btn.cocoa-serve {
    background: #2e7d32;
}

.cocoa-btn:disabled.cocoa-reset,
.cocoa-btn:disabled.cocoa-serve {
    background: #c9b29b;
}

.cocoa-message {
    margin-top: 15px;
    font-weight: bold;
    min-height: 1.4em;
}

.cocoa-game .start-btn {
    margin-top: 15px;
    padding: 10px 20px;
    font-size: 1em;
    background-color: #6d3b1f;
    color: #fff;
    font-weight: bold;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

.cocoa-game .start-btn:hover {
    background-color: #8d5524;
}

.cocoa-game .start-btn:disabled {
    background-color: #ccc;
    cursor: default;
}
HTML
<div class="cocoa-game">
    <h1>☕ Hot Cocoa Rush ❄️</h1>
    <div class="instructions">
        Read each order, add the right number of marshmallows 🍡 and candy canes 🍬, then press Serve before the timer runs out. Three mistakes and the café closes!
    </div>
    <div class="cocoa-info">Served: <span id="cocoaScore">0</span> &nbsp;|&nbsp; Lives: <span id="cocoaLives">❤️❤️❤️</span> &nbsp;|&nbsp; Best: <span id="cocoaBest">0</span></div>
    <div class="cocoa-counter">
        <div class="cocoa-order" id="cocoaOrder">Waiting for customers…</div>
        <div class="cocoa-timer"><div class="cocoa-timer-bar" id="cocoaTimer"></div></div>
        <div class="cocoa-cup" id="cocoaCup">☕</div>
        <div class="cocoa-buttons">
            <button class="cocoa-btn" id="cocoaMallow" aria-label="Add a marshmallow" disabled>🍡 +1</button>
            <button class="cocoa-btn" id="cocoaCane" aria-label="Add a candy cane" disabled>🍬 +1</button>
            <button class="cocoa-btn cocoa-reset" id="cocoaEmpty" aria-label="Empty the cup" disabled>↺ Empty</button>
            <button class="cocoa-btn cocoa-serve" id="cocoaServe" disabled>Serve ✓</button>
        </div>
    </div>
    <div class="cocoa-message" id="cocoaMessage">Press "Start" to open the café!</div>
    <button class="start-btn" id="cocoaStart">Start</button>
</div>
JS
const orderDisplay = document.getElementById('cocoaOrder');
const timerBar = document.getElementById('cocoaTimer');
const cupDisplay = document.getElementById('cocoaCup');
const mallowButton = document.getElementById('cocoaMallow');
const caneButton = document.getElementById('cocoaCane');
const emptyButton = document.getElementById('cocoaEmpty');
const serveButton = document.getElementById('cocoaServe');
const scoreDisplay = document.getElementById('cocoaScore');
const livesDisplay = document.getElementById('cocoaLives');
const bestDisplay = document.getElementById('cocoaBest');
const message = document.getElementById('cocoaMessage');
const startButton = document.getElementById('cocoaStart');

const MAX_LIVES = 3;
const CUSTOMERS = ['🧑', '👩', '👨', '👧', '🧓', '👦', '⛄'];

let score, lives, running, order, cup, timeLeft, timeTotal, lastTime, animationId, best;

best = 0;
try {
    best = parseInt(localStorage.getItem('hotCocoaRushBest'), 10) || 0;
} catch (e) {
    best = 0;
}
bestDisplay.textContent = best;

// The share buttons are rendered after this script, so look them up when needed.
function setShareButtons(display) {
    const shareButtons = document.querySelector('.share-buttons');
    if (shareButtons) {
        shareButtons.style.display = display;
    }
}

function setScore(value) {
    score = value;
    scoreDisplay.textContent = score;
    const hiddenScore = document.getElementById('hiddenScore');
    if (hiddenScore) {
        hiddenScore.value = score;
    }
}

function showLives() {
    let text = '';
    for (let i = 0; i < MAX_LIVES; i++) {
        text += i < lives ? '❤️' : '🤍';
    }
    livesDisplay.textContent = text;
}

function setButtons(enabled) {
    [mallowButton, caneButton, emptyButton, serveButton].forEach(function (b) {
        b.disabled = !enabled;
    });
}

function repeat(symbol, count) {
    let text = '';
    for (let i = 0; i < count; i++) {
        text += symbol;
    }
    return text;
}

function showCup() {
    cupDisplay.textContent = '☕' + repeat('🍡', cup.mallows) + repeat('🍬', cup.canes);
}

function randomInt(min, max) {
    return min + Math.floor(Math.random() * (max - min + 1));
}

function newOrder() {
    const level = score;
    const maxMallows = Math.min(6, 2 + Math.floor(level / 3));
    order = {
        customer: CUSTOMERS[Math.floor(Math.random() * CUSTOMERS.length)],
        mallows: randomInt(level < 2 ? 1 : 0, maxMallows),
        canes: level < 4 ? 0 : randomInt(0, Math.min(4, 1 + Math.floor((level - 4) / 4)))
    };
    if (order.mallows === 0 && order.canes === 0) {
        order.mallows = 1;
    }
    const parts = [];
    if (order.mallows > 0) {
        parts.push(order.mallows + ' marshmallow' + (order.mallows === 1 ? '' : 's'));
    }
    if (order.canes > 0) {
        parts.push(order.canes + ' candy cane' + (order.canes === 1 ? '' : 's'));
    }
    orderDisplay.textContent = order.customer + ' "Hot cocoa with ' + parts.join(' and ') + ', please!"';
    cup = { mallows: 0, canes: 0 };
    showCup();
    timeTotal = Math.max(2.5, 7 - score * 0.2);
    timeLeft = timeTotal;
}

function mistake(text) {
    lives -= 1;
    showLives();
    message.textContent = text;
    if (lives <= 0) {
        endGame();
    } else {
        newOrder();
    }
}

function serve() {
    if (!running) {
        return;
    }
    if (cup.mallows === order.mallows && cup.canes === order.canes) {
        setScore(score + 1);
        message.textContent = score % 5 === 0 ? score + ' cocoas served! The queue is growing… ☕' : 'Perfect, thank you! ☕';
        newOrder();
    } else {
        mistake('Oops, that is not what they ordered! 😕');
    }
}

function loop(time) {
    if (!running) {
        return;
    }
    const dt = Math.min(0.05, (time - lastTime) / 1000);
    lastTime = time;
    timeLeft -= dt;
    const ratio = Math.max(0, timeLeft / timeTotal);
    timerBar.style.width = (ratio * 100) + '%';
    timerBar.style.background = ratio > 0.5 ? '#43a047' : (ratio > 0.25 ? '#fb8c00' : '#e53935');
    if (timeLeft <= 0) {
        mistake('Too slow, the customer left! ⏰');
    }
    if (running) {
        animationId = requestAnimationFrame(loop);
    }
}

function startGame() {
    setScore(0);
    lives = MAX_LIVES;
    showLives();
    running = true;
    startButton.disabled = true;
    setButtons(true);
    setShareButtons('none');
    message.textContent = 'The café is open! ☕';
    newOrder();
    lastTime = performance.now();
    animationId = requestAnimationFrame(loop);
}

function endGame() {
    running = false;
    cancelAnimationFrame(animationId);
    setButtons(false);
    timerBar.style.width = '0%';
    orderDisplay.textContent = 'The café is closed for today.';
    if (score > best) {
        best = score;
        bestDisplay.textContent = best;
        try {
            localStorage.setItem('hotCocoaRushBest', String(best));
        } catch (e) {
            // ignore storage errors
        }
        message.textContent = 'Café closed! New best: ' + score + ' cocoas served! 🏆';
    } else {
        message.textContent = 'Café closed! You served ' + score + ' cocoas. ☕';
    }
    startButton.disabled = false;
    startButton.textContent = 'Play again';
    setShareButtons('flex');
}

function onPress(button, action) {
    button.addEventListener('touchstart', function (e) {
        e.preventDefault();
        if (!button.disabled) {
            action();
        }
    }, { passive: false });
    button.addEventListener('click', function () {
        action();
    });
}

onPress(mallowButton, function () {
    if (running && cup.mallows < 9) {
        cup.mallows += 1;
        showCup();
    }
});
onPress(caneButton, function () {
    if (running && cup.canes < 9) {
        cup.canes += 1;
        showCup();
    }
});
onPress(emptyButton, function () {
    if (running) {
        cup = { mallows: 0, canes: 0 };
        showCup();
    }
});
onPress(serveButton, serve);

document.addEventListener('keydown', function (e) {
    if (!running) {
        return;
    }
    if (e.key === 'm' || e.key === 'M') {
        cup.mallows = Math.min(9, cup.mallows + 1);
        showCup();
    } else if (e.key === 'c' || e.key === 'C') {
        cup.canes = Math.min(9, cup.canes + 1);
        showCup();
    } else if (e.key === 'Backspace') {
        e.preventDefault();
        cup = { mallows: 0, canes: 0 };
        showCup();
    } else if (e.key === 'Enter') {
        e.preventDefault();
        serve();
    }
});

lives = MAX_LIVES;
showLives();
startButton.addEventListener('click', startGame);