From f3c87878ce6208277ff16393bbfa24eed89f65c6 Mon Sep 17 00:00:00 2001
From: Mathias Wagner <germannewsmaker@gmail.com>
Date: Wed, 14 May 2025 22:10:27 +0200
Subject: [PATCH] Add vote progress bar and related styles to enhance voting
 feedback

---
 .../styles/components/voting-screen.sass      | 227 ++++++++++++++++++
 client/src/components/VotingScreen.jsx        |  71 ++++++
 2 files changed, 298 insertions(+)

diff --git a/client/src/common/styles/components/voting-screen.sass b/client/src/common/styles/components/voting-screen.sass
index 7345a67..e33650b 100644
--- a/client/src/common/styles/components/voting-screen.sass
+++ b/client/src/common/styles/components/voting-screen.sass
@@ -447,3 +447,230 @@
   justify-content: center
   margin: 2rem 0
   animation: pixel-float 3s infinite ease-in-out
+
+// Vote Progress Bar
+.vote-progress-container
+  width: 100%
+  margin-top: 1.5rem
+  margin-bottom: 2rem
+  padding: 0 0.5rem
+  animation: fade-in-up 0.5s ease forwards
+  position: relative
+
+@keyframes fade-in-up
+  from
+    opacity: 0
+    transform: translateY(10px)
+  to
+    opacity: 1
+    transform: translateY(0)
+
+.vote-progress-labels
+  display: flex
+  justify-content: space-between
+  margin-bottom: 0.5rem
+  font-size: 0.8rem
+  font-family: 'Press Start 2P', monospace
+
+  .vote-label
+    padding: 0.5rem 0.75rem
+    background-color: rgba(0, 0, 0, 0.7)
+    border: 2px solid #000
+    transform: translateY(0)
+    transition: transform 0.3s ease
+    position: relative
+    z-index: 2
+
+    &.left
+      color: $primary
+      border-left-color: $primary
+      border-top-color: $primary
+      border-bottom-color: $primary
+      box-shadow: -3px 3px 0 rgba($primary, 0.3)
+      &:hover
+        transform: translateY(-3px)
+        box-shadow: -5px 5px 0 rgba($primary, 0.5)
+    
+    &.right
+      color: $accent
+      border-right-color: $accent
+      border-top-color: $accent
+      border-bottom-color: $accent
+      box-shadow: 3px 3px 0 rgba($accent, 0.3)
+      &:hover
+        transform: translateY(-3px)
+        box-shadow: 5px 5px 0 rgba($accent, 0.5)
+
+    &.winning
+      background-color: rgba(#000, 0.8)
+      border-width: 3px
+      transform: translateY(-3px)
+      position: relative
+      z-index: 3
+      animation: winner-pulse 1.5s infinite alternate
+      
+      &.left
+        box-shadow: -5px 5px 0 rgba($primary, 0.7), 0 0 15px rgba($primary, 0.7)
+      
+      &.right
+        box-shadow: 5px 5px 0 rgba($accent, 0.7), 0 0 15px rgba($accent, 0.7)
+
+    &.landslide
+      transform: translateY(-5px)
+      font-weight: bold
+      animation: winner-landslide-pulse 1s infinite alternate
+      
+      &.left
+        box-shadow: -6px 6px 0 rgba($primary, 0.7), 0 0 25px rgba($primary, 0.9)
+      
+      &.right
+        box-shadow: 6px 6px 0 rgba($accent, 0.7), 0 0 25px rgba($accent, 0.9)
+
+@keyframes winner-landslide-pulse
+  from
+    box-shadow: -6px 6px 0 rgba($primary, 0.7), 0 0 25px rgba($primary, 0.9)
+  to
+    box-shadow: -6px 6px 0 rgba($primary, 0.7), 0 0 40px rgba($primary, 1)
+
+.vote-progress-bar
+  height: 36px
+  background-color: #222
+  border: 4px solid #000
+  position: relative
+  overflow: hidden
+  display: flex
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3)
+
+  .vote-progress-fill
+    height: 100%
+    transition: width 1.2s cubic-bezier(0.34, 1.56, 0.64, 1)
+    position: relative
+    min-width: 0
+
+    &.song1
+      background: linear-gradient(to right, darken($primary, 20%), $primary)
+      box-shadow: 0 8px 24px rgba($primary, 0.3), 0 0 10px rgba($primary, 0.2)
+      animation: pulse-song1 2s infinite alternate
+
+    &.song2
+      background: linear-gradient(to left, darken($accent, 20%), $accent)
+      box-shadow: 0 8px 24px rgba($accent, 0.3), 0 0 10px rgba($accent, 0.2)
+      animation: pulse-song2 2s infinite alternate
+    
+    .vote-progress-decoration
+      position: absolute
+      top: 0
+      bottom: 0
+      width: 20px
+      background-size: 4px 4px
+      opacity: 0.6
+      
+      &.left
+        right: 10px
+        background-image: linear-gradient(45deg, rgba(255,255,255,0.4) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.4) 50%, rgba(255,255,255,0.4) 75%, transparent 75%, transparent)
+        animation: move-stripes-left 20s linear infinite
+      
+      &.right
+        left: 10px
+        background-image: linear-gradient(-45deg, rgba(255,255,255,0.4) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.4) 50%, rgba(255,255,255,0.4) 75%, transparent 75%, transparent)
+        animation: move-stripes-right 20s linear infinite
+
+  &.landslide
+    border-width: 5px
+    animation: landslide-border-pulse 1.5s infinite alternate
+    
+    &.song1
+      border-color: lighten($primary, 20%)
+    
+    &.song2
+      border-color: lighten($accent, 20%)
+
+  .vote-progress-divider
+    position: absolute
+    top: -8px
+    bottom: -8px
+    width: 6px
+    background-color: #fff
+    transform: translateX(-50%)
+    z-index: 10
+    box-shadow: 0 0 15px rgba(255, 255, 255, 0.7), 0 0 5px rgba(0, 0, 0, 0.5)
+    transition: left 1.2s cubic-bezier(0.34, 1.56, 0.64, 1)
+    &:before, &:after
+      content: ''
+      position: absolute
+      left: 50%
+      width: 12px
+      height: 12px
+      background-color: #fff
+      transform: translateX(-50%) rotate(45deg)
+      box-shadow: 0 0 10px rgba(255, 255, 255, 0.7), 0 0 3px rgba(0, 0, 0, 0.5)
+    
+    &:before
+      top: 0
+    
+    &:after
+      bottom: 0
+
+.vote-progress-percentages
+  display: flex
+  justify-content: space-between
+  margin-top: 0.75rem
+  font-family: 'Press Start 2P', monospace
+  font-size: 1rem
+
+  .vote-percent
+    font-weight: bold
+    text-shadow: 2px 2px 0 #000
+    padding: 0.4rem 0.75rem
+    background-color: rgba(0, 0, 0, 0.7)
+    border: 2px solid #000
+    letter-spacing: 1px
+    position: relative
+    
+    &.left
+      color: $primary
+      border-left-color: $primary
+      border-bottom-color: $primary
+      transform: skew(-10deg)
+      box-shadow: -3px 3px 0 rgba($primary, 0.4)
+    
+    &.right
+      color: $accent
+      border-right-color: $accent
+      border-bottom-color: $accent
+      transform: skew(10deg)
+      box-shadow: 3px 3px 0 rgba($accent, 0.4)
+
+    &.winning
+      font-size: 1.2rem
+      font-weight: bolder
+      animation: winner-text-pulse 2s infinite alternate
+      
+      &.left
+        box-shadow: -5px 5px 0 rgba($primary, 0.5), 0 0 10px rgba($primary, 0.5)
+      
+      &.right
+        box-shadow: 5px 5px 0 rgba($accent, 0.5), 0 0 10px rgba($accent, 0.5)
+
+    &.landslide
+      font-size: 1.3rem
+      animation: landslide-text-pulse 1s infinite alternate
+      
+      &.left
+        box-shadow: -6px 6px 0 rgba($primary, 0.6), 0 0 15px rgba($primary, 0.6)
+      
+      &.right
+        box-shadow: 6px 6px 0 rgba($accent, 0.6), 0 0 15px rgba($accent, 0.6)
+
+@keyframes landslide-text-pulse
+  from
+    text-shadow: 2px 2px 0 #000, 0 0 10px currentColor
+  to
+    text-shadow: 2px 2px 0 #000, 0 0 25px currentColor, 0 0 5px #fff
+
+// Bye container for automatic advances
+.bye-container
+  display: flex
+  justify-content: center
+  margin: 2rem 0
+  animation: pixel-float 3s infinite ease-in-out
diff --git a/client/src/components/VotingScreen.jsx b/client/src/components/VotingScreen.jsx
index 7995391..52fb814 100644
--- a/client/src/components/VotingScreen.jsx
+++ b/client/src/components/VotingScreen.jsx
@@ -37,6 +37,27 @@ function VotingScreen() {
     return 'Vorrunde';
   }, [lobby, battle]);
 
+  // Berechne das Verhältnis der Stimmen für den Fortschrittsbalken
+  const voteRatio = useMemo(() => {
+    if (!battle || !hasVoted) return { song1Percent: 50, song2Percent: 50 };
+    
+    const totalVotes = (battle.song1Votes || 0) + (battle.song2Votes || 0);
+    
+    if (totalVotes === 0) return { song1Percent: 50, song2Percent: 50 };
+    
+    const song1Percent = Math.round((battle.song1Votes / totalVotes) * 100);
+    const song2Percent = 100 - song1Percent;
+    
+    // Determine winner (for styling purposes)
+    const winner = song1Percent > song2Percent ? 'song1' : song1Percent < song2Percent ? 'song2' : 'tie';
+    
+    // Calculate margin of victory for animation intensity
+    const margin = Math.abs(song1Percent - song2Percent);
+    const isLandslide = margin >= 30;
+    
+    return { song1Percent, song2Percent, winner, totalVotes, isLandslide, margin };
+  }, [battle, hasVoted]);
+
   // Prüfe, ob der Spieler bereits abgestimmt hat
   useEffect(() => {
     if (battle && battle.votes && currentPlayer) {
@@ -296,6 +317,56 @@ function VotingScreen() {
         </div>
       </div>
       
+      {/* Vote Progress Bar - Only show after voting */}
+      {hasVoted && (
+        <div className="vote-progress-container">
+          <div className="vote-progress-labels">
+            <span className={`vote-label left ${voteRatio.winner === 'song1' ? 'winning' : ''} ${voteRatio.winner === 'song1' && voteRatio.isLandslide ? 'landslide' : ''}`}>
+              {battle.song1Votes || 0} Stimmen
+              {voteRatio.winner === 'song1' && voteRatio.totalVotes > 1 && 
+                <span className="winner-crown"><FontAwesomeIcon icon={faCrown} /></span>
+              }
+            </span>
+            <span className={`vote-label right ${voteRatio.winner === 'song2' ? 'winning' : ''} ${voteRatio.winner === 'song2' && voteRatio.isLandslide ? 'landslide' : ''}`}>
+              {battle.song2Votes || 0} Stimmen
+              {voteRatio.winner === 'song2' && voteRatio.totalVotes > 1 && 
+                <span className="winner-crown"><FontAwesomeIcon icon={faCrown} /></span>
+              }
+            </span>
+          </div>
+          <div className={`vote-progress-bar ${voteRatio.winner !== 'tie' ? voteRatio.winner : ''} ${voteRatio.isLandslide ? 'landslide' : ''}`}>
+            <div 
+              className={`vote-progress-fill song1 ${voteRatio.winner === 'song1' ? 'winning' : ''}`}
+              style={{ width: `${voteRatio.song1Percent}%` }}
+            >
+              {voteRatio.song1Percent > 5 && (
+                <div className="vote-progress-decoration left"></div>
+              )}
+            </div>
+            <div 
+              className={`vote-progress-divider ${voteRatio.margin <= 10 ? 'close-race' : ''}`}
+              style={{ left: `${voteRatio.song1Percent}%` }}
+            ></div>
+            <div 
+              className={`vote-progress-fill song2 ${voteRatio.winner === 'song2' ? 'winning' : ''}`}
+              style={{ width: `${voteRatio.song2Percent}%` }}
+            >
+              {voteRatio.song2Percent > 5 && (
+                <div className="vote-progress-decoration right"></div>
+              )}
+            </div>
+          </div>
+          <div className="vote-progress-percentages">
+            <span className={`vote-percent left ${voteRatio.winner === 'song1' ? 'winning' : ''} ${voteRatio.winner === 'song1' && voteRatio.isLandslide ? 'landslide' : ''}`}>
+              {voteRatio.song1Percent}%
+            </span>
+            <span className={`vote-percent right ${voteRatio.winner === 'song2' ? 'winning' : ''} ${voteRatio.winner === 'song2' && voteRatio.isLandslide ? 'landslide' : ''}`}>
+              {voteRatio.song2Percent}%
+            </span>
+          </div>
+        </div>
+      )}
+      
       {!hasVoted && (
         <div className="voting-actions">
           <button