JSFiddle - React, Tailwind, and code Playground

by ckissi

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<div class="text-center">
    <h1 class="text-4xl text-white mb-8">Digital Flip Clock</h1>
    <div class="flex space-x-4">
      <div class="hours">
        <div class="flex space-x-2">
          <div class="flip-card w-24 h-32">
            <div class="top rounded-t-lg"></div>
            <div class="bottom rounded-b-lg"></div>
          </div>
          <div class="flip-card w-24 h-32">
            <div class="top rounded-t-lg"></div>
            <div class="bottom rounded-b-lg"></div>
          </div>
        </div>
        <p class="text-gray-400 mt-2">Hours</p>
      </div>
      
      <div class="text-5xl text-white flex items-center">:</div>
      
      <div class="minutes">
        <div class="flex space-x-2">
          <div class="flip-card w-24 h-32">
            <div class="top rounded-t-lg"></div>
            <div class="bottom rounded-b-lg"></div>
          </div>
          <div class="flip-card w-24 h-32">
            <div class="top rounded-t-lg"></div>
            <div class="bottom rounded-b-lg"></div>
          </div>
        </div>
        <p class="text-gray-400 mt-2">Minutes</p>
      </div>
      
      <div class="text-5xl text-white flex items-center">:</div>
      
      <div class="seconds">
        <div class="flex space-x-2">
          <div class="flip-card w-24 h-32">
            <div class="top rounded-t-lg"></div>
            <div class="bottom rounded-b-lg"></div>
          </div>
          <div class="flip-card w-24 h-32">
            <div class="top rounded-t-lg"></div>
            <div class="bottom rounded-b-lg"></div>
          </div>
        </div>
        <p class="text-gray-400 mt-2">Seconds</p>
      </div>
    </div>
  </div>

CSS

.flip-card {
      perspective: 600px;
      position: relative;
    }
    
    .top, .bottom, .flip-top, .flip-bottom {
      height: 50%;
      overflow: hidden;
      position: absolute;
      width: 100%;
    }
    
    .top, .flip-top {
      background: #2d3748;
      border-top-left-radius: 0.5rem;
      border-top-right-radius: 0.5rem;
      border-bottom: 1px solid #1a202c;
      transform-origin: bottom;
      top: 0;
    }
    
    .bottom, .flip-bottom {
      background: #2d3748;
      border-bottom-left-radius: 0.5rem;
      border-bottom-right-radius: 0.5rem;
      border-top: 1px solid #1a202c;
      transform-origin: top;
      bottom: 0;
    }
    
    .top .content, .bottom .content, .flip-top .content, .flip-bottom .content {
      height: 200%;
      position: absolute;
      width: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.875rem;
      font-weight: bold;
      color: white;
    }
    
    .top .content, .flip-top .content {
      top: 0;
    }
    
    .bottom .content, .flip-bottom .content {
      bottom: 0;
    }
    
    .flip-top, .flip-bottom {
      position: absolute;
      z-index: 2;
    }
    
    @keyframes flipTop {
      0% { transform: rotateX(0deg); }
      100% { transform: rotateX(-90deg); }
    }
    
    @keyframes flipBottom {
      0% { transform: rotateX(90deg); }
      100% { transform: rotateX(0deg); }
    }
    
    .animate-flipTop {
      animation: flipTop 0.3s ease-in;
      animation-fill-mode: forwards;
    }
    
    .animate-flipBottom {
      animation: flipBottom 0.3s ease-out 0.3s;
      animation-fill-mode: forwards;
    }

JavaScript

function updateTime() {
      const now = moment();
      updateDigits('hours', now.format('HH'));
      updateDigits('minutes', now.format('mm'));
      updateDigits('seconds', now.format('ss'));
    }

    function updateDigits(unit, value) {
      const cards = document.querySelector(`.${unit}`).querySelectorAll('.flip-card');
      const digits = value.toString().padStart(2, '0').split('');
      
      digits.forEach((digit, index) => {
        const card = cards[index];
        const currentValue = card.querySelector('.top').textContent;
        
        if (currentValue !== digit) {
          // Update top card
          const top = card.querySelector('.top');
          const bottom = card.querySelector('.bottom');
          
          // Remove existing animation classes
          top.classList.remove('flip-top');
          bottom.classList.remove('flip-bottom');
          
          // Set initial state
          top.textContent = currentValue || '0';
          bottom.textContent = digit;
          
          // Force reflow
          void top.offsetWidth;
          
          // Add animation classes
          top.classList.add('flip-top');
          bottom.classList.add('flip-bottom');
          
          // Update the values after animation
          setTimeout(() => {
            top.textContent = digit;
            bottom.textContent = digit;
          }, 300);
        }
      });
    }

    // Initialize the cards with the current time
    const cards = document.querySelectorAll('.flip-card');
    cards.forEach(card => {
      const top = card.querySelector('.top');
      const bottom = card.querySelector('.bottom');
      
      top.classList.add('flex', 'items-center', 'justify-center', 'text-4xl', 'text-white', 'font-bold');
      bottom.classList.add('flex', 'items-center', 'justify-center', 'text-4xl', 'text-white', 'font-bold');
      
      top.textContent = '0';
      bottom.textContent = '0';
    });

    // Update immediately and then...