JSFiddle - React, Tailwind, and code Playground

by leenie

HTML

<div class="entire_container">
  <div class="overlay-text visible">
    Klicke, um zu starten
    <span class="overlay-text-small">Finde die acht Paare rechtzeitig!</span>
  </div>
  <div id="game-over-text" class="overlay-text">
    GAME OVER 
    <span class="overlay-text-small">Du hast die Paare leider nicht gefunden. Klicke erneut, um zu starten.</span>
  </div>
  <div id="victory-text" class="overlay-text">
    GEWONNEN!
    <span class="overlay-text-small">
      Ein kleines Geschenk für dich:<br>
      Code: kleinessouvenir<br><br>
      
      Klicke, um neuzustarten.</span>
  </div>
  <div class="game-container">
    <div class="game-info-container">
      <div class="game-info">
        Zeit <span id="time-remaining"></span>
      </div>
      <div class="game-info">
        Umgedreht <span id="flips">0</span>
      </div>
    </div>
    <div class="card">
      <div class="card-back card-face">
        <img class="back-image" src="https://imgur.com/mj7g45P.png">
      </div>
      <div class="card-front card-face">
        <img class="card-value" src="https://imgur.com/cz6MRAp.png">
      </div>
    </div>
    <div class="card">
      <div class="card-back card-face">
        <img class="back-image" src="https://imgur.com/mj7g45P.png">
      </div>
      <div class="card-front card-face">
        <img class="card-value" src="https://imgur.com/cz6MRAp.png">
      </div>
    </div>
    <div class="card">
      <div class="card-back card-face">
        <img class="back-image" src="https://imgur.com/mj7g45P.png">
      </div>
      <div class="card-front card-face">
        <img class="card-value" src="https://imgur.com/T9HAA4L.png">
      </div>
    </div>
    <div class="card">
      <div class="card-back card-face">
        <img class="back-image" src="https://imgur.com/mj7g45P.png">
      </div>
      <div class="card-front card-face">
        <img class="card-value" src="https://imgur.com/T9HAA4L.png">
      </div>
    </div>
    <div class="card">
  ...

CSS

* {
  box-sizing: border-box;
}

.entire_container {
    position: relative;
    overflow: hidden;
    background: url(https://business24-agenturbelmediag.netdna-ssl.com/wp-content/uploads/2014/05/Sand-StudioSmart-Shutterstock.com_.jpg);
    
   
    width: 960px;
    height: 500px;
}

.game-info-container {
  grid-column: 1 / -1;
  display: flex;
  justify-content: space-between;
}

.game-info {
  font-family: Arial, serif;
  color: #000;
  font-size: 30px;
}

.game-container {
  display: grid;
  grid-template-columns: repeat(4, auto);
  grid-gap: 10px;
  margin: 50px;
  justify-content: center;
  perspective: 500px;
}

.card {
  position: relative;
  height: 80px;
  width: 125px;
}

.card:hover {
  cursor: pointer;
}

.card-face {
  position: absolute;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  backface-visibility: hidden;
  border-radius: 12px;
  transition: transform 500ms ease-in-out;
}

.card.visible .card-back {
  transform: rotateY(-180deg);
}

.card.visible .card-front {
  transform: rotateY(0);
}

.card.matched .card-value {
  animation: dance 1s linear 1 500ms;
}

.overlay-text {
  display: none;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  justify-content: center;
  align-items: center;
  z-index: 100;
  color: #fff;
  font-family: Arial, serif;
  cursor: pointer;
}

.overlay-text-small {
  font-size: 20px;
  text-align: center;
  padding: 0px 50px;
  animation: overlay-grow-small 500ms forwards;
}

.overlay-text.visible {
  display: flex;
  flex-direction: column;
  animation: overlay-grow 500ms forwards;
}

@keyframes overlay-grow {
  from {
    background-color: rgba(0, 0, 0, 0);
    font-size: 0;
  }
  to {
    background-color: rgba(0, 0, 0, 0.8);
    font-size: 60px;
  }
}

@keyframes overlay-grow-small {
  from {
    font-size: 0;
  }
  to {
    font-size: 20px;
  }
}

@keyframes dance {
  0%, 100%  {
    transform: rotate(0);
  }
  25% {
   ...

JavaScript

class Memory {
    constructor(totalTime, cards) {
        this.cardsArray = cards;
        this.totalTime = totalTime;
        this.timeRemaining = totalTime;
        this.timer = document.getElementById('time-remaining')
        this.ticker = document.getElementById('flips');
    }

    startGame() {
        this.totalClicks = 0;
        this.timeRemaining = this.totalTime;
        this.cardToCheck = null;
        this.matchedCards = [];
        this.busy = true;
        setTimeout(() => {
            this.shuffleCards(this.cardsArray);
            this.countdown = this.startCountdown();
            this.busy = false;
        }, 500);
        this.hideCards();
        this.timer.innerText = this.timeRemaining;
        this.ticker.innerText = this.totalClicks;
    }
    startCountdown() {
        return setInterval(() => {
            this.timeRemaining--;
            this.timer.innerText = this.timeRemaining;
            if(this.timeRemaining === 0)
                this.gameOver();
        }, 1000);
    }
    gameOver() {
        clearInterval(this.countdown);
        document.getElementById('game-over-text').classList.add('visible');
    }
    victory() {
        clearInterval(this.countdown);
        document.getElementById('victory-text').classList.add('visible');
    }
    hideCards() {
        this.cardsArray.forEach(card => {
            card.classList.remove('visible');
            card.classList.remove('matched');
        });
    }
    flipCard(card) {
        if(this.canFlipCard(card)) {
            this.totalClicks++;
            this.ticker.innerText = this.totalClicks;
            card.classList.add('visible');

            if(this.cardToCheck) {
                this.checkForCardMatch(card);
            } else {
                this.cardToCheck = card;
            }
        }
    }
    checkForCardMatch(card) {
        if(this.getCardType(card) === this.getCardType(this.cardToCheck))
            this.cardMatch(card, this.cardToCheck);
        else 
...