🪔 Diya Glow ✨

Watch the diyas light up, then tap them in the same order. Each round adds one more lamp to the sequence and plays a little faster. Keys 1 to 4 work too!
Score: 0 Best: 0
Press "Start" to light up Diwali...

Diwali is the festival of lights 🪔, and tonight four little clay diyas want to glow in the right order! Watch them light up one after another, then tap them in exactly the same sequence. Every time you get it right, one more lamp joins the pattern and the lights flicker a little faster. Play with a tap, a click or the keys 1 to 4. One wrong lamp and the light goes out, so focus and remember! How many diyas can you keep glowing in a row this Diwali?

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

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

.diya-info {
    display: flex;
    justify-content: center;
    gap: 24px;
    margin-bottom: 10px;
    font-size: 1.2em;
}

.diya-board {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    width: 320px;
    max-width: 100%;
    margin: 0 auto;
    padding: 16px;
    box-sizing: border-box;
    background: radial-gradient(circle at 50% 40%, #3b1d5e, #1a0d2e);
    border: 3px solid #d4a017;
    border-radius: 14px;
    touch-action: manipulation;
}

.diya-lamp {
    position: relative;
    aspect-ratio: 1 / 1;
    border: none;
    border-radius: 14px;
    background: rgba(255, 255, 255, 0.06);
    cursor: pointer;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.12s, box-shadow 0.12s, transform 0.08s;
}

.diya-lamp:disabled {
    cursor: default;
}

.diya-pot {
    position: absolute;
    left: 18%;
    right: 18%;
    bottom: 18%;
    height: 26%;
    border-radius: 0 0 60% 60% / 0 0 100% 100%;
    background: var(--diya-color);
    box-shadow: inset 0 -6px 0 rgba(0, 0, 0, 0.25);
}

.diya-flame {
    position: absolute;
    left: 50%;
    bottom: 42%;
    width: 18%;
    height: 30%;
    margin-left: -9%;
    border-radius: 50% 50% 50% 50% / 65% 65% 35% 35%;
    background: radial-gradient(circle at 50% 70%, #fff7c2, #ffb52e 55%, #e8590c);
    opacity: 0.25;
    transform: scale(0.7);
    transform-origin: 50% 100%;
    transition: opacity 0.12s, transform 0.12s;
}

.diya-gold { --diya-color: #e0a526; }
.diya-pink { --diya-color: #e0457b; }
.diya-teal { --diya-color: #1fa39a; }
.diya-violet { --diya-color: #8a4fd6; }

.diya-lamp.diya-lit {
    background: rgba(255, 196, 77, 0.22);
    box-shadow: 0 0 28px 6px rgba(255, 190, 70, 0.55);
    transform: scale(1.04);
}

.diya-lamp.diya-lit .diya-flame {
    opacity: 1;
    transform: scale(1.15);
    animation: diya-flicker 0.18s infinite alternate;
}

.diya-lamp.diya-wrong {
    background: rgba(220, 40, 40, 0.35);
}

@keyframes diya-flicker {
    from { transform: scale(1.1) rotate(-3deg); }
    to { transform: scale(1.2) rotate(3deg); }
}

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

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

.diya-btn:hover {
    background-color: #e6b52c;
}

.diya-btn:disabled {
    background-color: #ccc;
    color: #666;
    cursor: not-allowed;
}
HTML
<div class="diya-game">
    <h1>🪔 Diya Glow ✨</h1>
    <div class="instructions">
        Watch the diyas light up, then tap them in the same order. Each round adds one more lamp to the sequence and plays a little faster. Keys 1 to 4 work too!
    </div>
    <div class="diya-info">
        <span>Score: <span id="diyaScore">0</span></span>
        <span>Best: <span id="diyaBest">0</span></span>
    </div>
    <div class="diya-board" id="diyaBoard">
        <button class="diya-lamp diya-gold" data-lamp="0" aria-label="Gold diya (key 1)"><span class="diya-flame"></span><span class="diya-pot"></span></button>
        <button class="diya-lamp diya-pink" data-lamp="1" aria-label="Pink diya (key 2)"><span class="diya-flame"></span><span class="diya-pot"></span></button>
        <button class="diya-lamp diya-teal" data-lamp="2" aria-label="Teal diya (key 3)"><span class="diya-flame"></span><span class="diya-pot"></span></button>
        <button class="diya-lamp diya-violet" data-lamp="3" aria-label="Violet diya (key 4)"><span class="diya-flame"></span><span class="diya-pot"></span></button>
    </div>
    <div class="diya-message" id="diyaMessage">Press "Start" to light up Diwali...</div>
    <button class="diya-btn" id="diyaStart">Start</button>
</div>
JS
const diyaLamps = Array.prototype.slice.call(document.querySelectorAll('.diya-lamp'));
const diyaScoreEl = document.getElementById('diyaScore');
const diyaBestEl = document.getElementById('diyaBest');
const diyaMessageEl = document.getElementById('diyaMessage');
const diyaStartBtn = document.getElementById('diyaStart');
const diyaHiddenScore = document.getElementById('hiddenScore');

const DIYA_TONES = [329.63, 392.0, 493.88, 587.33];   // E4, G4, B4, D5
const DIYA_START_ON = 520;     // ms a lamp stays lit during playback, round 1
const DIYA_MIN_ON = 220;       // fastest it gets
const DIYA_GAP_RATIO = 0.45;   // pause between lamps, as a share of the lit time

let diyaSequence = [];
let diyaInputIndex = 0;
let diyaScore = 0;
let diyaAcceptInput = false;
let diyaPlaying = false;
let diyaTimers = [];
let diyaAudio = null;

function diyaReadBest() {
    try {
        return parseInt(localStorage.getItem('diyaGlowBest') || '0', 10) || 0;
    } catch (e) {
        return 0;
    }
}

function diyaSaveBest(value) {
    try {
        localStorage.setItem('diyaGlowBest', String(value));
    } catch (e) {
        // storage blocked: the best score only lasts for this visit
    }
}

let diyaBest = diyaReadBest();
diyaBestEl.textContent = diyaBest;

function diyaSetShare(visible) {
    // The share buttons come after the game in the page, so look them up each time.
    const share = document.querySelector('.share-buttons');
    if (share) {
        share.style.display = visible ? 'flex' : 'none';
    }
}

function diyaSetHidden(value) {
    const hidden = diyaHiddenScore || document.getElementById('hiddenScore');
    if (hidden) {
        hidden.value = value;
    }
}

function diyaBeep(index, duration, wrong) {
    try {
        if (!diyaAudio) {
            const Ctx = window.AudioContext || window.webkitAudioContext;
            if (!Ctx) {
                return;
            }
            diyaAudio = new Ctx();
        }
        const osc = diyaAudio.createOscillator();
        const gain = diyaAudio.createGain();
        const now = diyaAudio.currentTime;
        osc.type = wrong ? 'sawtooth' : 'sine';
        osc.frequency.value = wrong ? 110 : DIYA_TONES[index];
        gain.gain.setValueAtTime(0.0001, now);
        gain.gain.exponentialRampToValueAtTime(0.18, now + 0.02);
        gain.gain.exponentialRampToValueAtTime(0.0001, now + duration / 1000);
        osc.connect(gain);
        gain.connect(diyaAudio.destination);
        osc.start(now);
        osc.stop(now + duration / 1000 + 0.05);
    } catch (e) {
        // no sound available: the game works silently
    }
}

function diyaLater(fn, ms) {
    diyaTimers.push(setTimeout(fn, ms));
}

function diyaClearTimers() {
    diyaTimers.forEach(function (t) { clearTimeout(t); });
    diyaTimers = [];
}

function diyaFlash(index, ms) {
    const lamp = diyaLamps[index];
    lamp.classList.add('diya-lit');
    diyaBeep(index, ms, false);
    diyaLater(function () { lamp.classList.remove('diya-lit'); }, ms);
}

function diyaSetLampsEnabled(enabled) {
    diyaLamps.forEach(function (lamp) { lamp.disabled = !enabled; });
}

function diyaOnTime() {
    // Each round plays a little faster, down to a floor.
    return Math.max(DIYA_MIN_ON, DIYA_START_ON - diyaSequence.length * 25);
}

function diyaPlaySequence() {
    diyaAcceptInput = false;
    diyaPlaying = true;
    diyaSetLampsEnabled(false);
    diyaMessageEl.textContent = 'Watch the diyas... (' + diyaSequence.length + ' to remember)';
    const on = diyaOnTime();
    const step = on + on * DIYA_GAP_RATIO;
    diyaSequence.forEach(function (lampIndex, i) {
        diyaLater(function () { diyaFlash(lampIndex, on); }, 500 + i * step);
    });
    diyaLater(function () {
        diyaPlaying = false;
        diyaAcceptInput = true;
        diyaInputIndex = 0;
        diyaSetLampsEnabled(true);
        diyaMessageEl.textContent = 'Your turn! Light them in the same order.';
    }, 500 + diyaSequence.length * step);
}

function diyaNextRound() {
    diyaSequence.push(Math.floor(Math.random() * 4));
    diyaPlaySequence();
}

function diyaGameOver(wrongIndex) {
    diyaAcceptInput = false;
    diyaSetLampsEnabled(false);
    const lamp = diyaLamps[wrongIndex];
    lamp.classList.add('diya-wrong');
    diyaBeep(wrongIndex, 450, true);
    diyaLater(function () { lamp.classList.remove('diya-wrong'); }, 600);

    // Show the lamp that should have been lit.
    const expected = diyaSequence[diyaInputIndex];
    diyaLater(function () { diyaFlash(expected, 500); }, 650);

    const diyaWord = diyaScore === 1 ? ' diya' : ' diyas';
    if (diyaScore > diyaBest) {
        diyaBest = diyaScore;
        diyaSaveBest(diyaBest);
        diyaBestEl.textContent = diyaBest;
        diyaMessageEl.textContent = 'The light went out! New best: ' + diyaScore + diyaWord + ' in a row 🎉';
    } else {
        diyaMessageEl.textContent = 'The light went out! You remembered ' + diyaScore + diyaWord + ' in a row.';
    }
    diyaStartBtn.disabled = false;
    diyaStartBtn.textContent = 'Play again';
    diyaSetShare(true);
}

function diyaPress(index) {
    if (!diyaAcceptInput || diyaPlaying) {
        return;
    }
    if (diyaSequence[diyaInputIndex] !== index) {
        diyaGameOver(index);
        return;
    }
    diyaFlash(index, 220);
    diyaInputIndex++;
    if (diyaInputIndex === diyaSequence.length) {
        diyaAcceptInput = false;
        diyaScore = diyaSequence.length;
        diyaScoreEl.textContent = diyaScore;
        diyaSetHidden(diyaScore);
        diyaMessageEl.textContent = '✨ Beautiful! ' + diyaScore + (diyaScore === 1 ? ' diya' : ' diyas') + ' glowing.';
        diyaLater(diyaNextRound, 700);
    }
}

function diyaStart() {
    diyaClearTimers();
    diyaSequence = [];
    diyaScore = 0;
    diyaScoreEl.textContent = '0';
    diyaSetHidden(0);
    diyaSetShare(false);
    diyaStartBtn.disabled = true;
    diyaLamps.forEach(function (lamp) { lamp.classList.remove('diya-lit', 'diya-wrong'); });
    // Create the audio context inside the user gesture so mobile browsers allow it.
    diyaBeep(0, 1, false);
    diyaNextRound();
}

diyaLamps.forEach(function (lamp) {
    const index = parseInt(lamp.getAttribute('data-lamp'), 10);
    lamp.addEventListener('touchstart', function (e) {
        e.preventDefault();
        diyaPress(index);
    }, {passive: false});
    lamp.addEventListener('mousedown', function (e) {
        e.preventDefault();
        diyaPress(index);
    });
});

document.addEventListener('keydown', function (e) {
    const keys = {'1': 0, '2': 1, '3': 2, '4': 3};
    if (Object.prototype.hasOwnProperty.call(keys, e.key)) {
        e.preventDefault();
        diyaPress(keys[e.key]);
    }
});

diyaStartBtn.addEventListener('click', diyaStart);
diyaSetLampsEnabled(false);