Notation Étoiles Interactive HTML CSS JS

Extraits & Composants HTML 10/04/2026 17:00:00 angularforall.com
Html Css Javascript Rating Etoiles Notation Template

Système de notation par étoiles interactif en HTML CSS JS pur : hover animé, score dynamique, couleurs ambre et zéro dépendance externe.

<!DOCTYPE html>
<html lang="fr">
<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 HTML CSS JS 2026 05022048 | AngularForAll</title>
<style>
    /* =============================================
       RESET & VARIABLES CSS
       ============================================= */
    *, *::before, *::after {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    :root {
      --star-color: #e2e8f0;
      --star-active: #f59e0b;
      --star-hover: #fbbf24;
      --star-shadow: rgba(245, 158, 11, 0.3);
      
      --bg-primary: #f8fafc;
      --bg-card: #ffffff;
      --text-primary: #1e293b;
      --text-secondary: #64748b;
      --text-muted: #94a3b8;
      --border-color: #e2e8f0;
      
      --radius-sm: 10px;
      --radius-md: 16px;
      --radius-lg: 24px;
      --radius-xl: 28px;
      
      --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
      --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.04);
      --shadow-lg: 0 20px 50px rgba(0, 0, 0, 0.08), 0 4px 12px rgba(0, 0, 0, 0.04);
      --shadow-xl: 0 25px 60px rgba(0, 0, 0, 0.1);
      
      --transition-fast: 0.15s ease;
      --transition-smooth: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      --transition-bounce: 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
    }

    body {
      font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
      background: linear-gradient(160deg, #f8fafc 0%, #f1f5f9 30%, #fef3c7 70%, #fff7ed 100%);
      background-attachment: fixed;
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 20px;
      color: var(--text-primary);
      line-height: 1.6;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }

    /* Motif de fond subtil */
    body::before {
      content: '';
      position: fixed;
      inset: 0;
      background-image: 
        radial-gradient(circle at 20% 30%, rgba(245, 158, 11, 0.06) 0%, transparent 50%),
        radial-gradient(circle at 75% 60%, rgba(251, 191, 36, 0.05) 0%, transparent 50%),
        radial-gradient(circle at 50% 80%, rgba(245, 158, 11, 0.04) 0%, transparent 40%);
      pointer-events: none;
      z-index: 0;
    }

    /* =============================================
       CONTENEUR PRINCIPAL
       ============================================= */
    .rating-container {
      position: relative;
      z-index: 1;
      width: 100%;
      max-width: 520px;
    }

    .rating-card {
      background: var(--bg-card);
      border-radius: var(--radius-xl);
      box-shadow: var(--shadow-xl);
      overflow: hidden;
      border: 1px solid rgba(0, 0, 0, 0.04);
      backdrop-filter: blur(10px);
    }

    /* =============================================
       EN-TÊTE
       ============================================= */
    .rating-header {
      background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
      padding: 30px 28px;
      color: white;
      position: relative;
      overflow: hidden;
    }

    .rating-header::before {
      content: '';
      position: absolute;
      top: -40%;
      right: -20%;
      width: 180px;
      height: 180px;
      background: radial-gradient(circle, rgba(245, 158, 11, 0.2) 0%, transparent 70%);
      border-radius: 50%;
    }

    .rating-header::after {
      content: '';
      position: absolute;
      bottom: -30%;
      left: 30%;
      width: 150px;
      height: 150px;
      background: radial-gradient(circle, rgba(251, 191, 36, 0.1) 0%, transparent 70%);
      border-radius: 50%;
    }

    .header-content {
      position: relative;
      z-index: 1;
      display: flex;
      align-items: center;
      justify-content: space-between;
      flex-wrap: wrap;
      gap: 12px;
    }

    .header-left {
      display: flex;
      align-items: center;
      gap: 14px;
    }

    .header-icon {
      width: 48px;
      height: 48px;
      background: rgba(245, 158, 11, 0.2);
      border-radius: 14px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 24px;
      flex-shrink: 0;
      border: 1px solid rgba(245, 158, 11, 0.3);
    }

    .header-text h1 {
      font-size: 1.3rem;
      font-weight: 700;
      letter-spacing: -0.02em;
      margin-bottom: 2px;
    }

    .header-text p {
      font-size: 0.78rem;
      opacity: 0.65;
    }

    .header-badge {
      background: rgba(245, 158, 11, 0.2);
      border: 1px solid rgba(245, 158, 11, 0.3);
      color: #fbbf24;
      padding: 8px 16px;
      border-radius: 25px;
      font-size: 0.8rem;
      font-weight: 600;
      display: flex;
      align-items: center;
      gap: 6px;
    }

    /* =============================================
       CORPS DE LA CARTE
       ============================================= */
    .rating-body {
      padding: 28px;
    }

    /* =============================================
       SECTION AFFICHAGE
       ============================================= */
    .display-section {
      text-align: center;
      margin-bottom: 28px;
      padding: 20px;
      background: var(--bg-primary);
      border-radius: var(--radius-lg);
      border: 1px solid var(--border-color);
    }

    .display-label {
      font-size: 0.7rem;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 0.08em;
      color: var(--text-muted);
      margin-bottom: 12px;
    }

    .stars-display {
      display: flex;
      justify-content: center;
      gap: 6px;
      margin-bottom: 8px;
    }

    .star-display {
      font-size: 2.2rem;
      color: var(--star-color);
      transition: all var(--transition-smooth);
      position: relative;
    }

    .star-display.filled {
      color: var(--star-active);
      filter: drop-shadow(0 0 6px var(--star-shadow));
    }

    .star-display.half {
      position: relative;
      color: var(--star-color);
    }

    .star-display.half::before {
      content: '★';
      position: absolute;
      left: 0;
      top: 0;
      width: 50%;
      overflow: hidden;
      color: var(--star-active);
      filter: drop-shadow(0 0 6px var(--star-shadow));
    }

    .rating-value {
      font-size: 3rem;
      font-weight: 800;
      color: var(--text-primary);
      letter-spacing: -0.03em;
      line-height: 1;
    }

    .rating-value .decimal {
      font-size: 1.8rem;
      color: var(--text-secondary);
    }

    .rating-value .total {
      font-size: 1rem;
      color: var(--text-muted);
      font-weight: 500;
    }

    .rating-text {
      font-size: 0.85rem;
      color: var(--text-secondary);
      margin-top: 4px;
      font-weight: 500;
    }

    .rating-count {
      font-size: 0.75rem;
      color: var(--text-muted);
      margin-top: 2px;
    }

    /* =============================================
       SECTION ÉDITION
       ============================================= */
    .edit-section {
      text-align: center;
      padding: 20px;
      background: linear-gradient(135deg, #fffbeb, #fff7ed);
      border-radius: var(--radius-lg);
      border: 1px solid #fde68a;
      position: relative;
    }

    .edit-label {
      font-size: 0.7rem;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 0.08em;
      color: #92400e;
      margin-bottom: 14px;
    }

    .stars-interactive {
      display: flex;
      justify-content: center;
      gap: 8px;
      margin-bottom: 10px;
    }

    .star-interactive {
      font-size: 2.5rem;
      color: var(--star-color);
      cursor: pointer;
      transition: all var(--transition-bounce);
      position: relative;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }

    .star-interactive:hover {
      transform: scale(1.15);
      color: var(--star-hover);
      filter: drop-shadow(0 0 10px rgba(251, 191, 36, 0.5));
    }

    .star-interactive.active {
      color: var(--star-active);
      filter: drop-shadow(0 0 8px var(--star-shadow));
      animation: starPop 0.3s ease forwards;
    }

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

    .hover-text {
      font-size: 0.85rem;
      font-weight: 600;
      color: #92400e;
      min-height: 24px;
      transition: all var(--transition-fast);
    }

    .edit-buttons {
      display: flex;
      gap: 10px;
      justify-content: center;
      margin-top: 12px;
      flex-wrap: wrap;
    }

    .btn {
      display: inline-flex;
      align-items: center;
      gap: 6px;
      padding: 10px 20px;
      border-radius: 25px;
      font-size: 0.85rem;
      font-weight: 600;
      font-family: inherit;
      cursor: pointer;
      transition: all var(--transition-fast);
      border: 2px solid transparent;
      outline: none;
      white-space: nowrap;
    }

    .btn:active {
      transform: scale(0.95);
    }

    .btn-save {
      background: #f59e0b;
      color: #1e293b;
      border-color: #f59e0b;
      box-shadow: 0 4px 14px rgba(245, 158, 11, 0.3);
    }

    .btn-save:hover {
      background: #fbbf24;
      border-color: #fbbf24;
      box-shadow: 0 6px 20px rgba(245, 158, 11, 0.4);
    }

    .btn-reset {
      background: white;
      color: #64748b;
      border-color: #e2e8f0;
    }

    .btn-reset:hover {
      background: #f8fafc;
      border-color: #cbd5e1;
    }

    .btn-emoji {
      background: white;
      border: 2px solid #e2e8f0;
      border-radius: 50%;
      width: 42px;
      height: 42px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.2rem;
      cursor: pointer;
      transition: all var(--transition-bounce);
      padding: 0;
    }

    .btn-emoji:hover {
      transform: scale(1.15);
      border-color: #f59e0b;
      box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2);
    }

    .btn-emoji.selected {
      background: #fef3c7;
      border-color: #f59e0b;
      box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.15);
    }

    /* =============================================
       BARRES DE DISTRIBUTION
       ============================================= */
    .distribution-section {
      margin-top: 24px;
      padding: 20px;
      background: var(--bg-primary);
      border-radius: var(--radius-lg);
      border: 1px solid var(--border-color);
    }

    .distribution-title {
      font-size: 0.7rem;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 0.08em;
      color: var(--text-muted);
      margin-bottom: 14px;
      text-align: center;
    }

    .distribution-row {
      display: flex;
      align-items: center;
      gap: 10px;
      margin-bottom: 8px;
    }

    .distribution-label {
      font-size: 0.75rem;
      font-weight: 600;
      color: var(--text-secondary);
      width: 24px;
      text-align: right;
      flex-shrink: 0;
    }

    .distribution-bar-bg {
      flex: 1;
      height: 8px;
      background: #e2e8f0;
      border-radius: 10px;
      overflow: hidden;
    }

    .distribution-bar-fill {
      height: 100%;
      background: linear-gradient(90deg, #f59e0b, #fbbf24);
      border-radius: 10px;
      transition: width var(--transition-smooth);
    }

    .distribution-count {
      font-size: 0.72rem;
      color: var(--text-muted);
      width: 36px;
      text-align: left;
      flex-shrink: 0;
    }

    /* =============================================
       TOAST NOTIFICATION
       ============================================= */
    .toast {
      position: fixed;
      bottom: 30px;
      left: 50%;
      transform: translateX(-50%) translateY(120px);
      background: #1e293b;
      color: white;
      padding: 12px 24px;
      border-radius: 30px;
      font-size: 0.85rem;
      font-weight: 600;
      z-index: 1000;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
      transition: transform var(--transition-smooth);
      display: flex;
      align-items: center;
      gap: 8px;
    }

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

    /* =============================================
       RESPONSIVE
       ============================================= */
    @media (max-width: 600px) {
      .rating-header {
        padding: 22px 18px;
      }

      .rating-body {
        padding: 18px;
      }

      .header-text h1 {
        font-size: 1.1rem;
      }

      .star-display {
        font-size: 1.7rem;
      }

      .star-interactive {
        font-size: 2rem;
        gap: 4px;
      }

      .stars-interactive {
        gap: 4px;
      }

      .rating-value {
        font-size: 2.4rem;
      }

      .display-section,
      .edit-section,
      .distribution-section {
        padding: 16px;
      }

      .btn {
        padding: 8px 16px;
        font-size: 0.78rem;
      }
    }

    @media (max-width: 380px) {
      .star-display {
        font-size: 1.4rem;
      }

      .star-interactive {
        font-size: 1.7rem;
      }

      .btn-emoji {
        width: 35px;
        height: 35px;
        font-size: 1rem;
      }
    }
  </style>
</head>
<body>

  <!-- =============================================
       CONTENEUR PRINCIPAL
       ============================================= -->
  <div class="rating-container">
    <div class="rating-card">
      
      <!-- ========== EN-TÊTE ========== -->
      <div class="rating-header">
        <div class="header-content">
          <div class="header-left">
            <div class="header-icon">⭐</div>
            <div class="header-text">
              <h1>RatingStars</h1>
              <p>Évaluez ce produit</p>
            </div>
          </div>
          <div class="header-badge">
            <span>🏆</span> Top Qualité
          </div>
        </div>
      </div>

      <!-- ========== CORPS ========== -->
      <div class="rating-body">
        
        <!-- Section Affichage -->
        <div class="display-section">
          <div class="display-label">📊 Note moyenne</div>
          <div class="stars-display" id="starsDisplay">
            <!-- Rempli par JS -->
          </div>
          <div class="rating-value" id="ratingValue">
            <!-- Rempli par JS -->
          </div>
          <div class="rating-text" id="ratingText"></div>
          <div class="rating-count" id="ratingCount"></div>
        </div>

        <!-- Section Édition -->
        <div class="edit-section">
          <div class="edit-label">✏️ Votre évaluation</div>
          <div class="stars-interactive" id="starsInteractive">
            <!-- Rempli par JS -->
          </div>
          <div class="hover-text" id="hoverText">Cliquez pour noter</div>
          
          <!-- Émotions rapides -->
          <div style="display:flex;gap:8px;justify-content:center;margin-top:8px;" id="emojiButtons">
            <button class="btn-emoji" data-rating="1" title="Très déçu">😡</button>
            <button class="btn-emoji" data-rating="2" title="Déçu">😞</button>
            <button class="btn-emoji" data-rating="3" title="Correct">😐</button>
            <button class="btn-emoji" data-rating="4" title="Bien">😊</button>
            <button class="btn-emoji" data-rating="5" title="Excellent">😍</button>
          </div>

          <div class="edit-buttons">
            <button class="btn btn-save" id="btnSave">
              ✓ Enregistrer
            </button>
            <button class="btn btn-reset" id="btnReset">
              ↺ Réinitialiser
            </button>
          </div>
        </div>

        <!-- Section Distribution -->
        <div class="distribution-section">
          <div class="distribution-title">📈 Répartition des notes</div>
          <div id="distributionBars">
            <!-- Rempli par JS -->
          </div>
        </div>

      </div>
    </div>
  </div>

  <!-- ========== TOAST ========== -->
  <div class="toast" id="toast">
    <span id="toastIcon">✓</span>
    <span id="toastMessage">Note enregistrée !</span>
  </div>

  <script>
    (function() {
      // =============================================
      // DONNÉES INITIALES
      // =============================================
      const TOTAL_STARS = 5;
      
      // Simulation de notes existantes
      let ratings = [0, 3, 8, 27, 42, 85]; // index 1 à 5 (index 0 inutilisé)
      let userRating = 0; // Note de l'utilisateur (0 = pas encore noté)
      let currentHover = 0; // Étoile survolée

      // =============================================
      // ÉLÉMENTS DOM
      // =============================================
      const starsDisplay = document.getElementById('starsDisplay');
      const ratingValue = document.getElementById('ratingValue');
      const ratingText = document.getElementById('ratingText');
      const ratingCount = document.getElementById('ratingCount');
      const starsInteractive = document.getElementById('starsInteractive');
      const hoverText = document.getElementById('hoverText');
      const distributionBars = document.getElementById('distributionBars');
      const btnSave = document.getElementById('btnSave');
      const btnReset = document.getElementById('btnReset');
      const toast = document.getElementById('toast');
      const toastIcon = document.getElementById('toastIcon');
      const toastMessage = document.getElementById('toastMessage');

      // =============================================
      // FONCTIONS DE CALCUL
      // =============================================
      
      /** Calcule la note moyenne */
      function getAverageRating() {
        let totalSum = 0;
        let totalCount = 0;
        for (let i = 1; i <= TOTAL_STARS; i++) {
          totalSum += i * ratings[i];
          totalCount += ratings[i];
        }
        if (totalCount === 0) return 0;
        return totalSum / totalCount;
      }

      /** Calcule le nombre total de votes */
      function getTotalVotes() {
        return ratings.reduce((sum, val, idx) => idx > 0 ? sum + val : sum, 0);
      }

      /** Retourne le texte correspondant à une note */
      function getRatingLabel(rating) {
        if (rating === 0) return 'Aucune note';
        if (rating <= 1.4) return 'Très décevant';
        if (rating <= 2.4) return 'Décevant';
        if (rating <= 3.4) return 'Correct';
        if (rating <= 4.4) return 'Très bien';
        return 'Excellent';
      }

      // =============================================
      // AFFICHAGE DES ÉTOILES (MODE LECTURE)
      // =============================================
      function renderDisplayStars() {
        const average = getAverageRating();
        const fullStars = Math.floor(average);
        const decimalPart = average - fullStars;
        const hasHalfStar = decimalPart >= 0.25 && decimalPart < 0.75;
        const extraFullStar = decimalPart >= 0.75 ? 1 : 0;
        
        starsDisplay.innerHTML = '';
        
        for (let i = 1; i <= TOTAL_STARS; i++) {
          const star = document.createElement('span');
          star.className = 'star-display';
          
          if (i <= fullStars + extraFullStar) {
            star.classList.add('filled');
            star.textContent = '★';
          } else if (i === fullStars + 1 && hasHalfStar) {
            star.classList.add('half');
            star.textContent = '★';
          } else {
            star.textContent = '★';
          }
          
          starsDisplay.appendChild(star);
        }

        // Mise à jour de la valeur numérique
        const avgFormatted = average.toFixed(1);
        const [whole, decimal] = avgFormatted.split('.');
        ratingValue.innerHTML = `
          ${whole}<span class="decimal">.${decimal}</span>
          <span class="total">/ ${TOTAL_STARS}</span>
        `;

        // Texte et compteur
        ratingText.textContent = getRatingLabel(average);
        const totalVotes = getTotalVotes();
        ratingCount.textContent = `${totalVotes} avis au total`;
      }

      // =============================================
      // AFFICHAGE DES ÉTOILES (MODE ÉDITION)
      // =============================================
      function renderInteractiveStars() {
        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;
          
          // Survol
          star.addEventListener('mouseenter', () => {
            currentHover = i;
            updateInteractiveStars();
            updateHoverText(i);
          });
          
          star.addEventListener('mouseleave', () => {
            currentHover = 0;
            updateInteractiveStars();
            updateHoverText(userRating);
          });
          
          // Clic
          star.addEventListener('click', () => {
            userRating = i;
            currentHover = 0;
            updateInteractiveStars();
            updateHoverText(userRating);
            updateEmojiButtons();
            
            // Animation de la note
            animateRatingChange(i);
          });
          
          // Tactile
          star.addEventListener('touchend', (e) => {
            e.preventDefault();
            userRating = i;
            currentHover = 0;
            updateInteractiveStars();
            updateHoverText(userRating);
            updateEmojiButtons();
            animateRatingChange(i);
          });
          
          starsInteractive.appendChild(star);
        }
        
        updateInteractiveStars();
        updateEmojiButtons();
      }

      function updateInteractiveStars() {
        const stars = starsInteractive.querySelectorAll('.star-interactive');
        const activeValue = currentHover > 0 ? currentHover : userRating;
        
        stars.forEach((star, index) => {
          const value = index + 1;
          star.classList.remove('active');
          
          if (value <= activeValue) {
            star.classList.add('active');
          }
        });
      }

      function updateHoverText(rating) {
        const texts = {
          0: 'Cliquez pour noter',
          1: '😡 Très déçu',
          2: '😞 Déçu',
          3: '😐 Correct',
          4: '😊 Très bien',
          5: '😍 Excellent !'
        };
        hoverText.textContent = texts[rating] || 'Cliquez pour noter';
        
        // Animation
        hoverText.style.transform = 'scale(1.05)';
        setTimeout(() => {
          hoverText.style.transform = 'scale(1)';
        }, 150);
      }

      function animateRatingChange(rating) {
        const stars = starsInteractive.querySelectorAll('.star-interactive');
        stars.forEach((star, index) => {
          if (index + 1 <= rating) {
            star.style.animation = 'none';
            star.offsetHeight;
            star.style.animation = 'starPop 0.3s ease forwards';
            star.style.animationDelay = `${index * 0.06}s`;
          }
        });
      }

      // =============================================
      // BOUTONS EMOJI
      // =============================================
      function updateEmojiButtons() {
        const buttons = document.querySelectorAll('#emojiButtons .btn-emoji');
        buttons.forEach(btn => {
          const btnRating = parseInt(btn.dataset.rating);
          btn.classList.remove('selected');
          if (btnRating === userRating) {
            btn.classList.add('selected');
          }
        });
      }

      function setupEmojiButtons() {
        const buttons = document.querySelectorAll('#emojiButtons .btn-emoji');
        buttons.forEach(btn => {
          btn.addEventListener('click', () => {
            const rating = parseInt(btn.dataset.rating);
            userRating = rating;
            currentHover = 0;
            updateInteractiveStars();
            updateHoverText(userRating);
            updateEmojiButtons();
            animateRatingChange(rating);
          });
        });
      }

      // =============================================
      // BARRES DE DISTRIBUTION
      // =============================================
      function renderDistribution() {
        const totalVotes = getTotalVotes();
        distributionBars.innerHTML = '';
        
        for (let i = TOTAL_STARS; i >= 1; i--) {
          const count = ratings[i];
          const percentage = totalVotes > 0 ? (count / totalVotes) * 100 : 0;
          
          const row = document.createElement('div');
          row.className = 'distribution-row';
          
          const starsLabel = '★'.repeat(i);
          
          row.innerHTML = `
            <div class="distribution-label">${i}</div>
            <div class="distribution-bar-bg">
              <div class="distribution-bar-fill" style="width: ${percentage}%;"></div>
            </div>
            <div class="distribution-count">${count}</div>
          `;
          
          distributionBars.appendChild(row);
        }
      }

      // =============================================
      // SAUVEGARDE ET RÉINITIALISATION
      // =============================================
      function saveRating() {
        if (userRating === 0) {
          showToast('⚠️', 'Veuillez sélectionner une note');
          return;
        }
        
        // Ajouter la note
        ratings[userRating]++;
        
        // Mettre à jour l'affichage
        renderDisplayStars();
        renderDistribution();
        
        // Réinitialiser la sélection
        userRating = 0;
        currentHover = 0;
        updateInteractiveStars();
        updateHoverText(0);
        updateEmojiButtons();
        
        showToast('✅', 'Votre note a été enregistrée !');
      }

      function resetRating() {
        if (userRating === 0) {
          showToast('ℹ️', 'Aucune note à réinitialiser');
          return;
        }
        
        userRating = 0;
        currentHover = 0;
        updateInteractiveStars();
        updateHoverText(0);
        updateEmojiButtons();
        
        showToast('🔄', 'Note réinitialisée');
      }

      // =============================================
      // TOAST NOTIFICATION
      // =============================================
      let toastTimeout;

      function showToast(icon, message) {
        toastIcon.textContent = icon;
        toastMessage.textContent = message;
        
        clearTimeout(toastTimeout);
        toast.classList.add('show');
        
        toastTimeout = setTimeout(() => {
          toast.classList.remove('show');
        }, 2500);
      }

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

      // Raccourci clavier
      document.addEventListener('keydown', (e) => {
        if (e.key >= '1' && e.key <= '5') {
          const rating = parseInt(e.key);
          userRating = rating;
          currentHover = 0;
          updateInteractiveStars();
          updateHoverText(userRating);
          updateEmojiButtons();
          animateRatingChange(rating);
        }
        if (e.key === 'Enter') {
          saveRating();
        }
        if (e.key === 'Escape') {
          resetRating();
        }
      });

      // =============================================
      // INITIALISATION
      // =============================================
      function init() {
        renderDisplayStars();
        renderInteractiveStars();
        renderDistribution();
        setupEmojiButtons();
        console.log('⭐ RatingStars initialisé • Prêt à évaluer !');
      }

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

</body>
</html>

Télécharger le fichier source

Partager