#app
View Compiled
* {
  box-sizing: border-box;
}

body {
  background: #ad9c85;
}

:root {
  --initial-delay: 1;
  --delay-step: 0.3;
}

.car {
  height: max(50px, 10vmin);
  position: absolute;
  bottom: 5%;
  left: 50%;
  transform: translate(-50%, 0) translate(var(--offset), 0);
  animation-name: race;
  animation-duration: calc(var(--speed, 2) * 1s);
  animation-fill-mode: forwards;
  animation-timing-function: ease-in;
}

.car--green {
  --offset: -80%;
  --speed: 2.5;
  --step: var(--green-car);
}
.car--red {
  --offset: 80%;
  --speed: 2;
  --step: var(--red-car);
}

img {
  height: 100%;
}

.race__strip {
  width: calc(2 * max(50px, 10vmin));
  height: 100vh;
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, 0);
  background: repeating-linear-gradient(0deg, #fff 0 20px, transparent 20px 25px) no-repeat 50% 0/4px 100%, #bfbfbf;
}

.race__lights {
  padding: 5px;
  background: hsl(0, 0%, 15%);
  position: absolute;
  display: grid;
  grid-gap: 5px 6px;
  grid-template-columns: repeat(5, 1fr);
  grid-template-rows: repeat(2, 1fr);
  left: 50%;
  top: 5%;
  transform: translate(-50%, 0);
  z-index: 2;
}

.race__light {
  background: hsl(0, 0%, 25%);
  border-radius: 50%;
  height: 14px;
  width: 14px;
  position: relative;
}

.race__light--animated {
  animation-name: flash;
  animation-duration: 0.2s;
  animation-timing-function: steps(1);
  animation-fill-mode: both;
}

.race__light--animated,
.race__light-cover,
.car {
  animation-delay: calc((var(--initial-delay, 0) + (var(--step, 0) * var(--delay-step, 0))) * 1s);
}

.race__light--5 {
  --step: var(--light-one);
}
.race__light--6 {
  --step: var(--light-two);
}
.race__light--7 {
  --step: var(--light-three);
}
.race__light--8 {
  --step: var(--light-four);
}
.race__light--9 {
  --step: var(--light-five);
}

.race__light-cover {
  --step: var(--lights-out);
  position: absolute;
  top: 50%;
  left: 50%;
  height: 15px;
  width: 15px;
  border-radius: 50%;
  background: hsl(0, 0%, 25%);
  opacity: 0;
  transform: translate(-50%, -50%);
  animation-name: out;
  animation-duration: 0.2s;
  animation-fill-mode: both;
  animation-timing-function: steps(1);
}


@keyframes out {
  to {
    opacity: 1;
  }
}

@keyframes flash {
  to {
    background: hsl(10, 100%, 50%);
  }
}

@keyframes race {
  to {
    transform: translate(-50%, 0) translate(var(--offset), 0) translate(0, -100vh);
  }
}
import React, { useEffect, useState } from 'https://cdn.skypack.dev/react'
import { render } from 'https://cdn.skypack.dev/react-dom'
import { GUI } from 'https://cdn.skypack.dev/dat.gui'

const ROOT_NODE = document.querySelector('#app')



const App = () => {
  const [time, setTime] = useState(new Date().getTime())
  
  const DEFAULT_CONFIG = {
    'light-one': 0,
    'light-two': 1,
    'light-three': 2,
    'light-four': 3,
    'light-five': 4,
    'lights-out': 8,
    'red-car': 10,
    'green-car': 9,
    'delay-step': 0.3,
    'initial-delay': 1,
    restart: () => setTime(new Date().getTime()),
  }
  
  const [config, setConfig] = useState(DEFAULT_CONFIG)
  
  useEffect(() => {
    const CTRL = new GUI()
    const STEPS = CTRL.addFolder('Steps')
    Object.keys(DEFAULT_CONFIG).forEach((key, index, items) => {
      if (key !== 'restart' && key !== 'initial-delay' && key !== 'delay-step') STEPS.add(DEFAULT_CONFIG, key, 0, 10, 1).onChange(
        () => {
          setTime(new Date().getTime())
          setConfig(DEFAULT_CONFIG)
        }
      )
    })
    CTRL.add(DEFAULT_CONFIG, 'delay-step', 0.1, 1, 0.1).name('Delay Step').onChange(() => {
      setTime(new Date().getTime())
      setConfig(DEFAULT_CONFIG)
    })
    CTRL.add(DEFAULT_CONFIG, 'initial-delay', 0, 5, 0.1).name('Initial Delay').onChange(() => {
      setTime(new Date().getTime())
      setConfig(DEFAULT_CONFIG)
    })    
    CTRL.add(DEFAULT_CONFIG, 'restart').name('Restart')
  }, [])
  
  return (
    <div className='race' key={time} style={{
        '--delay-step': config['delay-step'],
        '--initial-delay': config['initial-delay'],
        '--light-one': config['light-one'],
        '--light-two': config['light-two'],
        '--light-three': config['light-three'],
        '--light-four': config['light-four'],
        '--light-five': config['light-five'],
        '--lights-out': config['lights-out'],          
        '--red-car': config['red-car'],
        '--green-car': config['green-car'],
      }}>
      <div className='race__lights'>
        {new Array(10).fill().map((_, index) => (
          <div className={`race__light race__light--${index} ${index > 4 ? 'race__light--animated' : ''}`}>
            {index > 4 ? <div className="race__light-cover"></div> : null}
          </div>
        ))}
      </div>
      <div className="race__strip">
        <div className='race__car car car--green'>
          <img src="https://assets.codepen.io/605876/green-car.png" alt="green car"/>
        </div>
              <div className='race__car car car--red'>
          <img src="https://assets.codepen.io/605876/little-red-car.png" alt="red car"/>
        </div>
      </div>
    </div>
  )
}

render(<App/>, ROOT_NODE)
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.