@@ -671,13 +671,15 @@ <h3 style="color: white; margin-bottom: 15px; text-align: center;">🏆 Top Dono
671671 const amountStorageKey = ( hypeMode ? 'tipjar_hype_amount' : 'tipjar_amount' ) + storageSuffix ;
672672 const historyStorageKey = ( hypeMode ? 'tipjar_hype_history' : 'tipjar_history' ) + storageSuffix ;
673673 const completionsStorageKey = 'tipjar_hype_completions' + storageSuffix ;
674- let currentAmount = persistent ? ( parseFloat ( localStorage . getItem ( amountStorageKey ) ) || 0 ) : 0 ;
675- let completionCount = persistent ? ( parseInt ( localStorage . getItem ( completionsStorageKey ) || '0' , 10 ) || 0 ) : 0 ;
676- let completionResetTimer = null ;
677- const recentTips = [ ] ;
678- const maxRecentTips = 5 ;
679- const donationHistory = persistent ? ( JSON . parse ( localStorage . getItem ( historyStorageKey ) || '[]' ) ) : [ ] ;
680- const processedContributions = { } ;
674+ let currentAmount = persistent ? ( parseFloat ( localStorage . getItem ( amountStorageKey ) ) || 0 ) : 0 ;
675+ let completionCount = persistent ? ( parseInt ( localStorage . getItem ( completionsStorageKey ) || '0' , 10 ) || 0 ) : 0 ;
676+ let completionResetTimer = null ;
677+ let levelRolloverTimer = null ;
678+ let levelCompletionState = null ;
679+ const recentTips = [ ] ;
680+ const maxRecentTips = 5 ;
681+ const donationHistory = persistent ? ( JSON . parse ( localStorage . getItem ( historyStorageKey ) || '[]' ) ) : [ ] ;
682+ const processedContributions = { } ;
681683
682684 // Socket server variables
683685 let socketserver = null ;
@@ -823,16 +825,17 @@ <h3 style="color: white; margin-bottom: 15px; text-align: center;">🏆 Top Dono
823825 } ) ;
824826 }
825827
826- function resetTipJarAmount ( showNotice ) {
827- if ( completionResetTimer ) {
828- clearTimeout ( completionResetTimer ) ;
829- completionResetTimer = null ;
830- }
831- currentAmount = 0 ;
832- if ( hypeMode ) {
833- completionCount = 0 ;
834- if ( persistent ) {
835- localStorage . setItem ( completionsStorageKey , completionCount . toString ( ) ) ;
828+ function resetTipJarAmount ( showNotice ) {
829+ if ( completionResetTimer ) {
830+ clearTimeout ( completionResetTimer ) ;
831+ completionResetTimer = null ;
832+ }
833+ clearLevelCompletionState ( ) ;
834+ currentAmount = 0 ;
835+ if ( hypeMode ) {
836+ completionCount = 0 ;
837+ if ( persistent ) {
838+ localStorage . setItem ( completionsStorageKey , completionCount . toString ( ) ) ;
836839 }
837840 }
838841 renderAmount ( ) ;
@@ -848,16 +851,43 @@ <h3 style="color: white; margin-bottom: 15px; text-align: center;">🏆 Top Dono
848851 return command === 'resettipjar' || command === 'reset_tipjar' || command === 'tipjar_reset' ;
849852 }
850853
851- function saveAmount ( ) {
852- if ( persistent ) {
853- localStorage . setItem ( amountStorageKey , currentAmount . toString ( ) ) ;
854- }
855- }
856-
857- function renderAmount ( ) {
858- saveAmount ( ) ;
859- const state = getProgressState ( ) ;
860- const percentage = state . percentage ;
854+ function saveAmount ( ) {
855+ if ( persistent ) {
856+ localStorage . setItem ( amountStorageKey , currentAmount . toString ( ) ) ;
857+ }
858+ }
859+
860+ function clearLevelCompletionState ( ) {
861+ if ( levelRolloverTimer ) {
862+ clearTimeout ( levelRolloverTimer ) ;
863+ levelRolloverTimer = null ;
864+ }
865+ levelCompletionState = null ;
866+ }
867+
868+ function showCompletedLevel ( completedLevel ) {
869+ clearLevelCompletionState ( ) ;
870+ levelCompletionState = {
871+ isLevel : true ,
872+ level : completedLevel ,
873+ completed : completedLevel ,
874+ current : levelSize ,
875+ goal : levelSize ,
876+ total : currentAmount ,
877+ percentage : 100
878+ } ;
879+ renderAmount ( ) ;
880+ levelRolloverTimer = setTimeout ( function ( ) {
881+ levelRolloverTimer = null ;
882+ levelCompletionState = null ;
883+ renderAmount ( ) ;
884+ } , completionDelayMs ) ;
885+ }
886+
887+ function renderAmount ( ) {
888+ saveAmount ( ) ;
889+ const state = getProgressState ( ) ;
890+ const percentage = state . percentage ;
861891
862892 // Update different styles
863893 if ( style === 'jar' ) {
@@ -878,24 +908,33 @@ <h3 style="color: white; margin-bottom: 15px; text-align: center;">🏆 Top Dono
878908 updateCompletionDisplay ( ) ;
879909 }
880910
881- function updateAmount ( amount ) {
882- const previousAmount = currentAmount ;
883- currentAmount += amount ;
884- renderAmount ( ) ;
885-
886- if ( amount <= 0 ) return ;
887-
888- // Recurring level mode: celebrate whenever a new increment band is filled.
889- if ( levelSize > 0 ) {
890- const previousLevel = Math . floor ( previousAmount / levelSize ) ;
891- const newLevel = Math . floor ( currentAmount / levelSize ) ;
892- if ( newLevel > previousLevel ) {
893- triggerLevelUp ( newLevel ) ;
894- }
895- return ;
896- }
897-
898- if ( goal <= 0 ) return ;
911+ function updateAmount ( amount ) {
912+ const previousAmount = currentAmount ;
913+ currentAmount += amount ;
914+
915+ if ( amount <= 0 ) {
916+ clearLevelCompletionState ( ) ;
917+ renderAmount ( ) ;
918+ return ;
919+ }
920+
921+ // Recurring level mode: celebrate whenever a new increment band is filled.
922+ if ( levelSize > 0 ) {
923+ const previousLevel = Math . floor ( previousAmount / levelSize ) ;
924+ const newLevel = Math . floor ( currentAmount / levelSize ) ;
925+ if ( newLevel > previousLevel ) {
926+ showCompletedLevel ( newLevel ) ;
927+ triggerLevelUp ( newLevel ) ;
928+ } else {
929+ clearLevelCompletionState ( ) ;
930+ renderAmount ( ) ;
931+ }
932+ return ;
933+ }
934+
935+ renderAmount ( ) ;
936+
937+ if ( goal <= 0 ) return ;
899938
900939 if ( previousAmount < goal * 0.25 && currentAmount >= goal * 0.25 ) triggerMilestone ( 25 ) ;
901940 if ( previousAmount < goal * 0.50 && currentAmount >= goal * 0.50 ) triggerMilestone ( 50 ) ;
@@ -1179,11 +1218,12 @@ <h3 style="color: white; margin-bottom: 15px; text-align: center;">🏆 Top Dono
11791218 return `$${ forceCents ? num . toFixed ( 2 ) : formatNumber ( num ) } ` ;
11801219 }
11811220
1182- function getProgressState ( ) {
1183- if ( levelSize > 0 ) {
1184- const completed = Math . floor ( currentAmount / levelSize ) ;
1185- const within = currentAmount - ( completed * levelSize ) ;
1186- return {
1221+ function getProgressState ( ) {
1222+ if ( levelSize > 0 ) {
1223+ if ( levelCompletionState ) return levelCompletionState ;
1224+ const completed = Math . floor ( currentAmount / levelSize ) ;
1225+ const within = currentAmount - ( completed * levelSize ) ;
1226+ return {
11871227 isLevel : true ,
11881228 level : completed + 1 ,
11891229 completed : completed ,
0 commit comments