Click when Red – learn JavaScript and CSS by building a reflex game in a single HTML page

Talking about Click when Red game, Game development and Javascript.

There are games that require frameworks, engines, and entire build systems.
And then there are games like this one, which you can create in a single HTML file, with just a bit of JavaScript, some basic CSS, no external files and zero dependencies.

If you’re new to JavaScript, trying to learn from big frameworks or tutorials that throw dozens of files at you is like trying to drive a race car before learning how a steering wheel works.

This game, Click when Red, is designed to teach you the absolute fundamentals:

  • How a web page works from the inside
  • How to use HTML to structure elements on screen
  • How to use CSS to style and position them
  • How to use JavaScript to control behavior and add logic
  • And how to glue everything together in a single, self-contained file

You’ll learn what happens when you click, how to listen to events, how to change content and styles dynamically, and how to write simple game logic using plain JavaScript.

All of this happens in less than 150 lines of code, with no frameworks and no libraries.

I also commented each and every line of code to guide you step by step, using JSDoc block comment where possible.

Have a look at the game:

Click to start, then click when the screen turns red. If you click too early, it’s game over. If you are too slow, it’s game over.

It’s simple. But that simplicity hides real learning: from layout to state management, from timing logic to DOM updates, you’re building something interactive from scratch.

This is the completely commented source code:

HTML
<!DOCTYPE html>
    <html>
        <head>

            <!-- title shown in the browser tab -->
            <title>Click when Red</title> 

            <!-- in-page CSS styling for layout and appearance -->
            <style>
  
                /* general styles */
                html, body {
                    margin              : 0;            /* set margin to zero */
                    padding             : 0;            /* set padding to zero */
                    height              : 100%;         /* set height to 100% (the entire page) */  
                    font-family         : sans-serif;   /* set font family */
                    background-color    : #222222       /* set background color to gray */
                }

                /* main game container that fills the entire screen */
                #game {
                    position            : absolute;     /* place it independently from layout flow */
                    top                 : 0;            /* align to top of viewport */
                    left                : 0;            /* align to left of viewport */
                    width               : 100%;         /* set full width */
                    height              : 100%;         /* set full height */
                    display             : flex;         /* use flexbox for layout */
                    flex-direction      : column;       /* set the main axis to vertical, stacking children from top to bottom */
                    align-items         : center;       /* center children horizontally along the cross axis (left to right) */
                    justify-content     : center;       /* center children vertically along the main axis (top to bottom) */
                    font-size           : 2em;          /* set text size */
                    color               : white;        /* text color */
                    cursor              : pointer;      /* turn the cursor into a pointer to make it clear everything is clickable */
                }

                /* score text under the message */
                #score {
                    font-size           : 0.6em;        /* set text size */
                    margin-top          : 10px;         /* set the margin above the score */
                }

                /* game title at the top */
                #title {
                    position            : absolute;     /* place it independently from layout flow */
                    top                 : 0;            /* align to top of viewport */
                    width               : 100%;         /* set full width */
                    padding             : 10px 0;       /* set vertical padding to 10 pixels, horizontal padding to zero */
                    text-align          : center;       /* center the text */
                    font-size           : 0.5em;        /* set text size */
                    opacity             : 0.8;          /* set opacity (0: transparent; 1: opaque) */
                    background-color    : black;        /* set background color */
                }
            </style>
        </head>
    <body>

        <!-- main game container that fills the entire screen -->
        <div id="game">

            <!-- game title displayed at the top -->
            <div id="title">Click when Red</div>

            <!-- dynamic message shown during gameplay -->
            <div id="message">Click to start</div>

            <!-- score counter shown below the message -->
            <div id="score"></div>
        </div>

        <!-- JavaScript logic for the game, added directly in the HTML -->
        <script>

            /**
            * @type {number}
            * Maximum reaction time allowed, in milliseconds.
            */
            const maxReactionTime = 350;

            /**
            * @type {number}
            * Minimum delay, in milliseconds.
            */
            const minDelay = 200;

            /**
            * @type {number}
            * Maximum delay, in milliseconds.
            */
            const maxDelay = 2000;

            /**
            * @type {HTMLElement}
            * The main game container element.
            */
            const gameElement = document.getElementById('game');

            /**
            * @type {HTMLElement}
            * The element used to display game messages.
            */
            const messageElement = document.getElementById('message');

            /**
            * @type {HTMLElement}
            * The element used to display the current score.
            */
            const scoreElement = document.getElementById('score');

            /**
            * @type {number|null}
            * Timestamp when the red screen appears.
            * Used to measure the player's reaction time.
            */
            let startTime = null;

            /**
            * @type {number|undefined}
            * Stores the ID of the timeout for the red screen trigger.
            */
            let timeoutID;

            /**
            * @type {boolean}
            * Indicates if the game is currently waiting for the red screen.
            */
            let waiting = false;

            /**
            * @type {boolean}
            * Indicates if the game is currently paused.
            */
            let paused = false;

            /**
            * @type {number}
            * Player score.
            */
            let score = 0;

            /**
            * Handles click events during the game.
            *
            * - Starts a new game on initial click.
            * - Resets if player clicks too early or too late.
            * - Updates score if clicked on time.
            */
            game.addEventListener('click', function () {

                // if the game is paused, simply exit the function
                if (paused) {
                    return;
                }

                // if we aren't waiting for the red screen,
                // it's time to start the next round and exit the function
                if (!waiting) {
                    
                    // don't show anything in the score
                    scoreElement.textContent = '';
                    nextRound();
                    return;
                }

                // if player clicked but startTime did not already started, 
                // playerd clicked before the screen turned red, so it's game over.
                // gameOver funcion is called, then we exit the function.
                if (!startTime) {

                    // set the proper message in messageElement
                    messageElement.textContent = 'You clicked too early :(';

                    // it's game over, exit the function                    
                    gameOver();
                    return;
                } 
                
                // determine reaction time 
                const reactionTime = Date.now() - startTime;
                
                // if reaction time is greater than maximum reaction time,
                // gameOver funcion is called, then we exit the function.
                if (reactionTime > maxReactionTime) {
                    
                    // set the proper message in messageElement
                    messageElement.textContent = `${reactionTime} ms? Too slow :(`;
                    
                    // it's game over, exit the function         
                    gameOver();
                    return;
                }
                    
                // the remaining code is executed only if it was a valid click
                // score is updated and written 
                
                // increase score
                score ++;

                // show score
                scoreElement.textContent = `Score: ${score}`;

                // set the proper message in messageElement
                messageElement.textContent = `Good! Reaction time: ${reactionTime} ms`;

                // wa are not waiting anymore
                waiting = false;

                // reset startTime
                startTime = null;

                // game is paused
                paused = true;
                
                // wait a second then start a new round
                setTimeout(nextRound, 1000);     
            });

            /**
            * Starts the next round of the game.
            *
            * - Waits a random time before changing background to red.
            * - Prepares the game for measuring player's reaction time.
            */
            function nextRound() {

                // game is not paused
                paused = false;

                // now we are waiting
                waiting = true;

                // set the proper message in messageElement
                messageElement.textContent = 'Wait for red...';

                // set game background a bit darker
                gameElement.style.backgroundColor = '#222';

                // set a random delay 
                const randomDelay = Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
                
                // execute this funcion after randomDelay milliseconds
                timeoutID = setTimeout(() => {

                    // set the proper message in messageElement
                    messageElement.textContent = 'CLICK!';
                    
                    // set game background red
                    gameElement.style.backgroundColor = 'red';
                    
                    // save current time
                    startTime = Date.now();
                }, randomDelay);
            }

            /**
            * Resets the game state after the player loses.
            *
            * - Resets score, delay and variables.
            */
            function gameOver() {

                // set game background to grey
                gameElement.style.backgroundColor = '#333';

                // reset variables
                clearTimeout(timeoutID);
                score = 0;
                startTime = null;
                waiting = false;
            }

        </script>
    </body>
</html>

This is one of the simplest web games you can even build, but it’s a good start if you are new to JavaScript game development.