<section class="buttons">
  <button class="start">
    turn on
  </button>
  <button class="end">
    turn off
  </button>
</section>
<section class="lights">
  <div id='green' class="light green"></div>
  <div id='yellow' class="light yellow"></div>
  <div id='red' class="light red"></div>
</section>
section.buttons {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 20px;
  gap: 8px;

  button {
    font-size: 20px;
  }
}

.lights {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  padding: 20px;
  border-radius: 12px;
  background-color: #06402b;
  width: fit-content;
  margin: 20px auto;
}

.light {
  width: 100px;
  height: 100px;
  border: 2px solid black;
  border-radius: 50%;
  background-color: grey;
}

.active {
  &.green {
    background-color: green;
  }

  &.yellow {
    background-color: yellow;
  }

  &.red {
    background-color: red;
  }
}
// state control
enum States {
  green = "green",
  yellow = "yellow",
  red = "red"
}

const machine = {
  initial: States.green,
  states: {
    green: {
      on: { TIMER: { target: States.yellow, delay: 3000 } }
    },
    yellow: {
      on: { TIMER: { target: States.red, delay: 1000 } }
    },
    red: {
      on: { TIMER: { target: States.green, delay: 2000 } }
    }
  }
};

function createMachine(machine) {
  return {
    state: machine.initial,
    preState: null,
    init() {
      this.state = machine.initial;
      this.preState = null;
    },
    transition(state: States, action) {
      return machine.states[state].on[action];
    },
    send(action) {
      const nextState = this.transition(this.state, action);
      this.preState = this.state;
      this.state = nextState.target;
    }
  };
}

const service = createMachine(machine);

// ui
const startButton = document.querySelector("button.start");
const endButton = document.querySelector("button.end");
const [green, yellow, red] = document.querySelectorAll(".light");
const lights = { green, yellow, red };

let timer: ReturnType<typeof setTimeout>;

const updateState = (state, preState) => {
  lights[preState]?.classList.remove("active");
  lights[state]?.classList.add("active");
  const { delay } = service.transition(state, "TIMER");

  timer = setTimeout(() => {
    service.send("TIMER");
    updateState(service.state, service.preState);
  }, delay);
};

startButton.addEventListener("click", () => {
  if (timer) clearTimeout(timer);
  updateState(service.state, service.preState);
});

endButton.addEventListener("click", () => {
  clearTimeout(timer);
  lights[service.state]?.classList.remove("active");
  service.init();
});
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.