Notation Étoiles Interactive Tailwind CSS

Extraits & Composants HTML 10/04/2026 22:00:00 angularforall.com
Tailwind Css Rating Etoiles Notation Interactif Template Javascript

Système de notation par étoiles interactif en Tailwind CSS : thème mauve, animations fluides, score en temps réel et design utility-first moderne.

<!DOCTYPE html>
<html lang="fr" class="scroll-smooth">
<head>
  <meta charset="UTF-8" />
  <meta name="copyright" content="AngularForAll" />
  <meta name="author" content="AngularForAll" />
  <meta name="robots" content="noindex, nofollow" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="Cache-Control" content="public, max-age=604800" />
  <title>Snippets Rating Tailwindcss 2026 05022046 | AngularForAll</title>
<!-- Tailwind CSS CDN -->
  <script src="https://cdn.tailwindcss.com"></script>

  <!-- Configuration Tailwind personnalisée -->
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            mauve: {
              50: '#faf5ff',
              100: '#f3e8ff',
              200: '#e9d5ff',
              300: '#d8b4fe',
              400: '#c084fc',
              500: '#a855f7',
              600: '#9333ea',
              700: '#7e22ce',
              800: '#6b21a8',
              900: '#581c87',
              950: '#3b0764',
            },
            surface: {
              light: '#fafafa',
              card: '#ffffff',
              dark: '#0f0f1a',
            }
          },
          fontFamily: {
            sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
            display: ['Plus Jakarta Sans', 'Inter', 'sans-serif'],
          },
          borderRadius: {
            '2.5xl': '1.25rem',
            '3xl': '1.5rem',
          },
          boxShadow: {
            'mauve-sm': '0 2px 8px rgba(147, 51, 234, 0.08)',
            'mauve-md': '0 8px 30px rgba(147, 51, 234, 0.12)',
            'mauve-lg': '0 20px 50px rgba(147, 51, 234, 0.15), 0 4px 12px rgba(147, 51, 234, 0.08)',
            'mauve-glow': '0 0 20px rgba(168, 85, 247, 0.3), 0 0 40px rgba(168, 85, 247, 0.1)',
            'mauve-inner': 'inset 0 2px 8px rgba(147, 51, 234, 0.06)',
          },
          animation: {
            'star-pop': 'starPop 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) forwards',
            'float-up': 'floatUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards',
            'pulse-mauve': 'pulseMauve 2s ease-in-out infinite',
            'shimmer': 'shimmer 2s linear infinite',
            'bounce-in': 'bounceIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards',
          },
          keyframes: {
            starPop: {
              '0%': { transform: 'scale(1)' },
              '40%': { transform: 'scale(1.35)' },
              '70%': { transform: 'scale(0.9)' },
              '100%': { transform: 'scale(1)' },
            },
            floatUp: {
              '0%': { opacity: '1', transform: 'translateY(0) scale(1)' },
              '100%': { opacity: '0', transform: 'translateY(-40px) scale(0.8)' },
            },
            pulseMauve: {
              '0%, 100%': { boxShadow: '0 0 0 0 rgba(168, 85, 247, 0.3)' },
              '50%': { boxShadow: '0 0 0 15px rgba(168, 85, 247, 0)' },
            },
            shimmer: {
              '0%': { backgroundPosition: '-200% 0' },
              '100%': { backgroundPosition: '200% 0' },
            },
            bounceIn: {
              '0%': { opacity: '0', transform: 'scale(0.7)' },
              '50%': { transform: 'scale(1.05)' },
              '100%': { opacity: '1', transform: 'scale(1)' },
            },
          },
          backgroundImage: {
            'mauve-gradient': 'linear-gradient(135deg, #7e22ce 0%, #9333ea 30%, #a855f7 60%, #c084fc 100%)',
            'mauve-gradient-light': 'linear-gradient(135deg, #faf5ff 0%, #f3e8ff 50%, #e9d5ff 100%)',
            'mauve-gradient-subtle': 'linear-gradient(160deg, #faf5ff 0%, #f5f3ff 50%, #ede9fe 100%)',
          },
        },
      },
    };
  </script>

  <!-- Google Fonts -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">

  <!-- Font Awesome 6 -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.2/css/all.min.css">

  <style>
    /* Styles personnalisés additionnels */
    body {
      background: linear-gradient(160deg, #faf5ff 0%, #f5f3ff 30%, #ede9fe 60%, #faf5ff 100%);
      background-attachment: fixed;
      min-height: 100vh;
    }

    /* Particules d'arrière-plan */
    body::before {
      content: '';
      position: fixed;
      inset: 0;
      background-image:
        radial-gradient(circle at 15% 25%, rgba(168, 85, 247, 0.06) 0%, transparent 50%),
        radial-gradient(circle at 80% 70%, rgba(147, 51, 234, 0.05) 0%, transparent 50%),
        radial-gradient(circle at 50% 50%, rgba(192, 132, 252, 0.04) 0%, transparent 60%);
      pointer-events: none;
      z-index: 0;
    }

    /* Étoiles en lecture */
    .star-display {
      text-shadow: none;
      transition: all 0.3s ease;
    }

    .star-display.filled {
      color: #a855f7;
      text-shadow: 0 0 12px rgba(168, 85, 247, 0.4);
    }

    .star-display.half {
      position: relative;
      display: inline-block;
      color: #d1d5db;
    }

    .star-display.half::before {
      content: '★';
      position: absolute;
      left: 0;
      top: 0;
      width: 50%;
      overflow: hidden;
      color: #a855f7;
      text-shadow: 0 0 12px rgba(168, 85, 247, 0.4);
    }

    /* Étoiles interactives */
    .star-interactive {
      cursor: pointer;
      transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
      color: #d1d5db;
      display: inline-block;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }

    .star-interactive:hover {
      transform: scale(1.18);
      color: #c084fc;
      filter: drop-shadow(0 0 10px rgba(192, 132, 252, 0.6));
    }

    .star-interactive.active {
      color: #a855f7;
      filter: drop-shadow(0 0 8px rgba(168, 85, 247, 0.5));
    }

    .star-interactive.pop-animation {
      animation: starPop 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
    }

    @keyframes starPop {
      0% { transform: scale(1); }
      40% { transform: scale(1.35); }
      70% { transform: scale(0.9); }
      100% { transform: scale(1); }
    }

    /* Particules d'étoiles */
    .star-particle {
      position: fixed;
      pointer-events: none;
      z-index: 999;
      font-size: 1.5rem;
      animation: floatUp 0.6s ease-out forwards;
      color: #a855f7;
    }

    @keyframes floatUp {
      0% { opacity: 1; transform: translateY(0) scale(1); }
      100% { opacity: 0; transform: translateY(-50px) scale(0.5); }
    }

    /* Toast */
    .toast-notification {
      position: fixed;
      bottom: 30px;
      left: 50%;
      transform: translateX(-50%) translateY(120px);
      z-index: 9999;
      transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
    }

    .toast-notification.show {
      transform: translateX(-50%) translateY(0);
    }

    /* Barre de progression distribution */
    .dist-bar-fill {
      background: linear-gradient(90deg, #a855f7, #c084fc);
      transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
    }

    /* Scrollbar */
    ::-webkit-scrollbar {
      width: 5px;
    }
    ::-webkit-scrollbar-track {
      background: transparent;
    }
    ::-webkit-scrollbar-thumb {
      background: #d8b4fe;
      border-radius: 10px;
    }
  </style>
</head>
<body class="font-sans antialiased text-gray-800 flex items-center justify-center min-h-screen p-4">

  <!-- =============================================
       CONTENEUR PRINCIPAL
       ============================================= -->
  <div class="relative z-10 w-full max-w-lg mx-auto">

    <!-- Carte principale -->
    <div class="bg-white rounded-3xl shadow-mauve-lg overflow-hidden border border-mauve-100">

      <!-- ========== EN-TÊTE ========== -->
      <div class="relative bg-gradient-to-br from-mauve-900 via-mauve-800 to-mauve-950 px-6 sm:px-8 py-6 sm:py-7 overflow-hidden">
        <!-- Cercles décoratifs -->
        <div class="absolute top-0 right-0 w-40 h-40 bg-mauve-500/10 rounded-full -translate-y-1/2 translate-x-1/4"></div>
        <div class="absolute bottom-0 left-1/3 w-32 h-32 bg-mauve-400/10 rounded-full translate-y-1/2"></div>

        <!-- Contenu -->
        <div class="relative z-10 flex items-center justify-between flex-wrap gap-4">
          <div class="flex items-center gap-3">
            <div class="w-11 h-11 bg-mauve-500/20 backdrop-blur-sm rounded-xl flex items-center justify-center text-2xl border border-mauve-400/30 flex-shrink-0">
              ⭐
            </div>
            <div>
              <h1 class="font-display font-bold text-white text-lg sm:text-xl tracking-tight">RatingStars</h1>
              <p class="text-mauve-300 text-xs sm:text-sm">Évaluez ce produit</p>
            </div>
          </div>
          <div class="inline-flex items-center gap-2 bg-mauve-500/20 backdrop-blur-sm border border-mauve-400/30 text-mauve-200 px-3 py-1.5 rounded-full text-xs font-semibold">
            <i class="fa-solid fa-crown"></i>
            Top Qualité
          </div>
        </div>
      </div>

      <!-- ========== CORPS ========== -->
      <div class="px-5 sm:px-7 py-6 sm:py-7 space-y-5">

        <!-- ===== SECTION AFFICHAGE ===== -->
        <div class="bg-mauve-50/80 rounded-2.5xl p-5 sm:p-6 text-center border border-mauve-100 shadow-mauve-inner">
          <p class="text-[10px] sm:text-xs font-bold uppercase tracking-widest text-mauve-400 mb-3">
            <i class="fa-solid fa-chart-simple mr-1"></i>Note moyenne
          </p>

          <!-- Étoiles affichage -->
          <div class="text-3xl sm:text-4xl tracking-wide mb-2" id="starsDisplay">
            <!-- Rempli dynamiquement -->
          </div>

          <!-- Note chiffrée -->
          <div class="font-display font-black text-4xl sm:text-5xl text-gray-900 tracking-tight" id="ratingNumber">
            <!-- Rempli dynamiquement -->
          </div>

          <p class="text-sm font-semibold text-mauve-600 mt-1" id="ratingLabel"></p>
          <p class="text-xs text-mauve-400 mt-0.5" id="ratingCount"></p>
        </div>

        <!-- ===== SECTION ÉDITION ===== -->
        <div class="bg-gradient-to-br from-mauve-50 to-purple-50 rounded-2.5xl p-5 sm:p-6 text-center border-2 border-mauve-200 shadow-mauve-sm">
          <p class="text-[10px] sm:text-xs font-bold uppercase tracking-widest text-mauve-700 mb-3">
            <i class="fa-solid fa-pen-to-square mr-1"></i>Votre évaluation
          </p>

          <!-- Étoiles interactives -->
          <div class="text-4xl sm:text-5xl tracking-wider mb-2" id="starsInteractive">
            <!-- Rempli dynamiquement -->
          </div>

          <p class="text-sm font-semibold text-mauve-700 min-h-[24px] transition-all duration-200" id="hoverMessage">
            Cliquez pour noter
          </p>

          <!-- Émojis rapides -->
          <div class="flex justify-center gap-2 sm:gap-3 my-3" id="emojiContainer">
            <button class="w-10 h-10 sm:w-11 sm:h-11 rounded-full bg-white border-2 border-mauve-200 text-lg sm:text-xl flex items-center justify-center transition-all duration-200 hover:scale-115 hover:border-mauve-400 hover:shadow-mauve-sm cursor-pointer" data-rating="1" title="Très déçu">😡</button>
            <button class="w-10 h-10 sm:w-11 sm:h-11 rounded-full bg-white border-2 border-mauve-200 text-lg sm:text-xl flex items-center justify-center transition-all duration-200 hover:scale-115 hover:border-mauve-400 hover:shadow-mauve-sm cursor-pointer" data-rating="2" title="Déçu">😞</button>
            <button class="w-10 h-10 sm:w-11 sm:h-11 rounded-full bg-white border-2 border-mauve-200 text-lg sm:text-xl flex items-center justify-center transition-all duration-200 hover:scale-115 hover:border-mauve-400 hover:shadow-mauve-sm cursor-pointer" data-rating="3" title="Correct">😐</button>
            <button class="w-10 h-10 sm:w-11 sm:h-11 rounded-full bg-white border-2 border-mauve-200 text-lg sm:text-xl flex items-center justify-center transition-all duration-200 hover:scale-115 hover:border-mauve-400 hover:shadow-mauve-sm cursor-pointer" data-rating="4" title="Bien">😊</button>
            <button class="w-10 h-10 sm:w-11 sm:h-11 rounded-full bg-white border-2 border-mauve-200 text-lg sm:text-xl flex items-center justify-center transition-all duration-200 hover:scale-115 hover:border-mauve-400 hover:shadow-mauve-sm cursor-pointer" data-rating="5" title="Excellent">😍</button>
          </div>

          <!-- Boutons d'action -->
          <div class="flex justify-center gap-3 flex-wrap">
            <button id="btnSave" class="inline-flex items-center gap-2 px-5 py-2.5 bg-mauve-600 text-white font-semibold rounded-full shadow-mauve-md hover:bg-mauve-700 hover:shadow-mauve-glow active:scale-95 transition-all duration-200 text-sm">
              <i class="fa-solid fa-check text-xs"></i>
              Enregistrer
            </button>
            <button id="btnReset" class="inline-flex items-center gap-2 px-5 py-2.5 bg-white text-mauve-600 font-semibold rounded-full border-2 border-mauve-200 hover:bg-mauve-50 hover:border-mauve-300 active:scale-95 transition-all duration-200 text-sm">
              <i class="fa-solid fa-arrow-rotate-left text-xs"></i>
              Réinitialiser
            </button>
          </div>
        </div>

        <!-- ===== SECTION DISTRIBUTION ===== -->
        <div class="bg-mauve-50/80 rounded-2.5xl p-5 sm:p-6 border border-mauve-100 shadow-mauve-inner">
          <p class="text-[10px] sm:text-xs font-bold uppercase tracking-widest text-mauve-400 mb-4 text-center">
            <i class="fa-solid fa-chart-bar mr-1"></i>Répartition des notes
          </p>
          <div class="space-y-2.5" id="distributionContainer">
            <!-- Rempli dynamiquement -->
          </div>
        </div>

      </div>
    </div>

    <!-- Indicateur de confiance -->
    <p class="text-center text-xs text-mauve-400 mt-4 flex items-center justify-center gap-1.5">
      <i class="fa-solid fa-shield-halved"></i>
      Avis vérifiés • Authentiques
    </p>
  </div>

  <!-- ========== TOAST NOTIFICATION ========== -->
  <div class="toast-notification bg-gray-900 text-white px-5 py-3 rounded-full shadow-xl flex items-center gap-2 text-sm font-semibold" id="toastNotification">
    <span id="toastIcon">✅</span>
    <span id="toastMessage">Note enregistrée !</span>
  </div>

  <!-- ========== SCRIPT ========== -->
  <script>
    (function() {
      // ============ CONFIGURATION ============
      const TOTAL_STARS = 5;

      // Distribution des votes (index 1 à 5)
      let distribution = [0, 4, 8, 22, 38, 94];

      // État utilisateur
      let userRating = 0;
      let hoverRating = 0;

      // ============ ÉLÉMENTS DOM ============
      const starsDisplay = document.getElementById('starsDisplay');
      const ratingNumber = document.getElementById('ratingNumber');
      const ratingLabel = document.getElementById('ratingLabel');
      const ratingCount = document.getElementById('ratingCount');
      const starsInteractive = document.getElementById('starsInteractive');
      const hoverMessage = document.getElementById('hoverMessage');
      const distributionContainer = document.getElementById('distributionContainer');
      const btnSave = document.getElementById('btnSave');
      const btnReset = document.getElementById('btnReset');
      const toastNotification = document.getElementById('toastNotification');
      const toastIcon = document.getElementById('toastIcon');
      const toastMessage = document.getElementById('toastMessage');

      // ============ CALCULS ============
      function getAverage() {
        let sum = 0, count = 0;
        for (let i = 1; i <= TOTAL_STARS; i++) {
          sum += i * distribution[i];
          count += distribution[i];
        }
        return count > 0 ? sum / count : 0;
      }

      function getTotalVotes() {
        return distribution.slice(1).reduce((a, b) => a + b, 0);
      }

      function getLabel(avg) {
        if (avg === 0) return 'Aucune note';
        if (avg <= 1.5) return 'Très décevant';
        if (avg <= 2.5) return 'Décevant';
        if (avg <= 3.5) return 'Correct';
        if (avg <= 4.5) return 'Très bien';
        return 'Excellent !';
      }

      function getHoverText(r) {
        const texts = { 0: 'Cliquez pour noter', 1: '😡 Très déçu', 2: '😞 Déçu', 3: '😐 Correct', 4: '😊 Très bien', 5: '😍 Excellent !' };
        return texts[r] || 'Cliquez pour noter';
      }

      // ============ AFFICHAGE DES ÉTOILES (LECTURE) ============
      function renderDisplay() {
        const avg = getAverage();
        const full = Math.floor(avg);
        const dec = avg - full;
        const hasHalf = dec >= 0.25 && dec < 0.75;
        const extra = dec >= 0.75 ? 1 : 0;

        starsDisplay.innerHTML = '';

        for (let i = 1; i <= TOTAL_STARS; i++) {
          const span = document.createElement('span');
          span.className = 'star-display';
          span.textContent = '★';

          if (i <= full + extra) {
            span.classList.add('filled');
          } else if (i === full + 1 && hasHalf) {
            span.classList.add('half');
          }

          starsDisplay.appendChild(span);
        }

        // Nombre
        const formatted = avg.toFixed(1);
        const [whole, decimal] = formatted.split('.');
        ratingNumber.innerHTML = `${whole}<span class="text-xl sm:text-2xl text-mauve-400 font-bold">.${decimal}</span><span class="text-xs sm:text-sm text-mauve-300 font-medium ml-1">/ ${TOTAL_STARS}</span>`;

        ratingLabel.textContent = getLabel(avg);
        ratingCount.textContent = `${getTotalVotes()} avis au total`;
      }

      // ============ ÉTOILES INTERACTIVES ============
      function renderInteractive() {
        starsInteractive.innerHTML = '';

        for (let i = 1; i <= TOTAL_STARS; i++) {
          const star = document.createElement('span');
          star.className = 'star-interactive';
          star.textContent = '★';
          star.dataset.value = i;

          star.addEventListener('mouseenter', () => {
            hoverRating = i;
            updateInteractiveStars();
            hoverMessage.textContent = getHoverText(i);
          });

          star.addEventListener('mouseleave', () => {
            hoverRating = 0;
            updateInteractiveStars();
            hoverMessage.textContent = getHoverText(userRating);
          });

          star.addEventListener('click', (e) => {
            setUserRating(i, e);
          });

          star.addEventListener('touchend', (e) => {
            e.preventDefault();
            setUserRating(i, e);
          });

          starsInteractive.appendChild(star);
        }

        updateInteractiveStars();
      }

      function updateInteractiveStars() {
        const stars = starsInteractive.querySelectorAll('.star-interactive');
        const active = hoverRating > 0 ? hoverRating : userRating;

        stars.forEach((star, idx) => {
          star.classList.remove('active');
          if (idx + 1 <= active) star.classList.add('active');
        });
      }

      function setUserRating(rating, event) {
        userRating = rating;
        hoverRating = 0;
        updateInteractiveStars();
        hoverMessage.textContent = getHoverText(userRating);
        updateEmojiButtons();
        animateStars(rating);

        // Particules si clic sur étoile
        if (event && event.target) {
          spawnParticles(event.target);
        }
      }

      function animateStars(rating) {
        const stars = starsInteractive.querySelectorAll('.star-interactive');
        stars.forEach((star, idx) => {
          if (idx + 1 <= rating) {
            star.classList.remove('pop-animation');
            void star.offsetWidth;
            star.classList.add('pop-animation');
            star.style.animationDelay = `${idx * 0.06}s`;
          }
        });
      }

      // ============ PARTICULES ============
      function spawnParticles(element) {
        const rect = element.getBoundingClientRect();
        const cx = rect.left + rect.width / 2;
        const cy = rect.top + rect.height / 2;

        const emojis = ['✨', '💜', '⭐', '💫', '🌟'];

        for (let i = 0; i < 8; i++) {
          const particle = document.createElement('span');
          particle.className = 'star-particle';
          particle.textContent = emojis[Math.floor(Math.random() * emojis.length)];
          particle.style.left = cx + (Math.random() - 0.5) * 60 + 'px';
          particle.style.top = cy + (Math.random() - 0.5) * 40 + 'px';
          particle.style.fontSize = (1 + Math.random() * 1.5) + 'rem';
          particle.style.animationDuration = (0.5 + Math.random() * 0.5) + 's';

          document.body.appendChild(particle);

          setTimeout(() => {
            particle.remove();
          }, 700);
        }
      }

      // ============ EMOJIS ============
      function setupEmojis() {
        const btns = document.querySelectorAll('#emojiContainer button');
        btns.forEach(btn => {
          btn.addEventListener('click', () => {
            const r = parseInt(btn.dataset.rating);
            setUserRating(r, { target: btn });
          });
        });
      }

      function updateEmojiButtons() {
        const btns = document.querySelectorAll('#emojiContainer button');
        btns.forEach(btn => {
          const r = parseInt(btn.dataset.rating);
          if (r === userRating) {
            btn.classList.add('border-mauve-500', 'bg-mauve-100', 'shadow-mauve-sm');
            btn.classList.remove('border-mauve-200', 'bg-white');
          } else {
            btn.classList.remove('border-mauve-500', 'bg-mauve-100', 'shadow-mauve-sm');
            btn.classList.add('border-mauve-200', 'bg-white');
          }
        });
      }

      // ============ DISTRIBUTION ============
      function renderDistribution() {
        const total = getTotalVotes();
        distributionContainer.innerHTML = '';

        for (let i = TOTAL_STARS; i >= 1; i--) {
          const count = distribution[i];
          const pct = total > 0 ? (count / total) * 100 : 0;

          const row = document.createElement('div');
          row.className = 'flex items-center gap-2 sm:gap-3';
          row.innerHTML = `
            <span class="text-xs font-bold text-mauve-500 w-5 text-right flex-shrink-0">${i}</span>
            <div class="flex-1 h-2 bg-mauve-200 rounded-full overflow-hidden">
              <div class="dist-bar-fill h-full rounded-full" style="width:${pct}%;"></div>
            </div>
            <span class="text-xs text-mauve-400 w-8 text-left flex-shrink-0">${count}</span>
          `;
          distributionContainer.appendChild(row);
        }
      }

      // ============ SAUVEGARDE ============
      function saveRating() {
        if (userRating === 0) {
          showToast('⚠️', 'Veuillez sélectionner une note');
          return;
        }

        distribution[userRating]++;

        renderDisplay();
        renderDistribution();

        userRating = 0;
        hoverRating = 0;
        updateInteractiveStars();
        hoverMessage.textContent = getHoverText(0);
        updateEmojiButtons();

        showToast('✅', 'Votre note a été enregistrée !');
      }

      function resetRating() {
        if (userRating === 0) {
          showToast('ℹ️', 'Aucune note à réinitialiser');
          return;
        }

        userRating = 0;
        hoverRating = 0;
        updateInteractiveStars();
        hoverMessage.textContent = getHoverText(0);
        updateEmojiButtons();

        showToast('🔄', 'Note réinitialisée');
      }

      // ============ TOAST ============
      let toastTimer;

      function showToast(icon, msg) {
        toastIcon.textContent = icon;
        toastMessage.textContent = msg;
        toastNotification.classList.add('show');

        clearTimeout(toastTimer);
        toastTimer = setTimeout(() => {
          toastNotification.classList.remove('show');
        }, 2500);
      }

      // ============ ÉVÉNEMENTS ============
      btnSave.addEventListener('click', saveRating);
      btnReset.addEventListener('click', resetRating);

      document.addEventListener('keydown', (e) => {
        if (e.key >= '1' && e.key <= '5' && document.activeElement === document.body) {
          setUserRating(parseInt(e.key), { target: document.body });
        }
        if (e.key === 'Enter' && document.activeElement === document.body) saveRating();
      });

      // ============ INITIALISATION ============
      function init() {
        renderDisplay();
        renderInteractive();
        renderDistribution();
        setupEmojis();
        updateEmojiButtons();
        // console.info('💜 RatingStars • Mauve Edition • Prêt');
      }

      init();
    })();
  </script>

</body>
</html>

Télécharger le fichier source

Partager