@@ -17,6 +17,9 @@ document.addEventListener('DOMContentLoaded', function() {
1717
1818 // Check for version updates
1919 checkVersion ( ) ;
20+
21+ // Initialize GitHub star prompt
22+ initStarPrompt ( ) ;
2023} ) ;
2124
2225/**
@@ -48,3 +51,101 @@ async function checkVersion() {
4851 }
4952 }
5053}
54+
55+ /**
56+ * Show a non-modal prompt to star the project on GitHub.
57+ * Triggers on the third visit and after a short uptime delay.
58+ */
59+ function initStarPrompt ( ) {
60+ const dismissKey = 'gpuHotStarPromptDismissed' ;
61+ const toast = document . getElementById ( 'star-toast' ) ;
62+
63+ if ( ! toast ) {
64+ return ;
65+ }
66+
67+ if ( localStorage . getItem ( dismissKey ) === 'true' ) {
68+ return ;
69+ }
70+
71+ const starButton = toast . querySelector ( '[data-action="star"]' ) ;
72+ const closeButton = toast . querySelector ( '[data-action="dismiss"]' ) ;
73+
74+ const dismissPrompt = ( ) => {
75+ localStorage . setItem ( dismissKey , 'true' ) ;
76+ toast . classList . add ( 'is-hidden' ) ;
77+ } ;
78+
79+ if ( starButton ) {
80+ starButton . addEventListener ( 'click' , ( ) => {
81+ window . open ( 'https://github.com/psalias2006/gpu-hot' , '_blank' , 'noopener,noreferrer' ) ;
82+ dismissPrompt ( ) ;
83+ } ) ;
84+ }
85+
86+ if ( closeButton ) {
87+ closeButton . addEventListener ( 'click' , dismissPrompt ) ;
88+ }
89+
90+ // Fire-and-forget: fetch star count async, don't block anything
91+ fetchStarCount ( ) . catch ( ( ) => { } ) ;
92+
93+ // Show after 1 minute of active use
94+ const showDelayMs = 60 * 1000 ;
95+ setTimeout ( ( ) => {
96+ if ( localStorage . getItem ( dismissKey ) !== 'true' ) {
97+ toast . classList . remove ( 'is-hidden' ) ;
98+ }
99+ } , showDelayMs ) ;
100+ }
101+
102+ /**
103+ * Fetch GitHub star count and update UI if successful.
104+ * Completely async and non-blocking. If it fails, the generic message stays.
105+ */
106+ async function fetchStarCount ( ) {
107+ const progressWrap = document . getElementById ( 'star-progress-wrap' ) ;
108+ const milestoneEl = document . getElementById ( 'star-toast-milestone' ) ;
109+ const progressFillEl = document . getElementById ( 'star-progress-fill' ) ;
110+
111+ const controller = new AbortController ( ) ;
112+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 5000 ) ; // 5s timeout
113+
114+ try {
115+ const response = await fetch ( 'https://api.github.com/repos/psalias2006/gpu-hot' , {
116+ signal : controller . signal
117+ } ) ;
118+ clearTimeout ( timeoutId ) ;
119+
120+ if ( ! response . ok ) return ;
121+
122+ const data = await response . json ( ) ;
123+ const stars = Number ( data . stargazers_count ) ;
124+
125+ if ( ! Number . isFinite ( stars ) || stars <= 0 ) return ;
126+
127+ const nextGoal = stars % 1000 === 0 ? stars + 1000 : Math . ceil ( stars / 1000 ) * 1000 ;
128+ const starsToGo = nextGoal - stars ;
129+ const prevMilestone = nextGoal - 1000 ;
130+ const progressPercent = Math . min ( 100 , Math . max ( 0 , ( ( stars - prevMilestone ) / 1000 ) * 100 ) ) ;
131+ const goalLabel = nextGoal >= 1000 ? ( nextGoal / 1000 ) + 'k' : String ( nextGoal ) ;
132+
133+ // Update UI with real data
134+ if ( milestoneEl ) {
135+ milestoneEl . textContent = `${ formatNumber ( starsToGo ) } stars to ${ goalLabel } ` ;
136+ }
137+ if ( progressFillEl ) {
138+ progressFillEl . style . width = progressPercent + '%' ;
139+ }
140+ if ( progressWrap ) {
141+ progressWrap . classList . remove ( 'is-hidden' ) ;
142+ }
143+ } catch ( error ) {
144+ // Silent fail - generic message stays, no progress bar shown
145+ clearTimeout ( timeoutId ) ;
146+ }
147+ }
148+
149+ function formatNumber ( value ) {
150+ return Number ( value ) . toLocaleString ( 'en-US' ) ;
151+ }
0 commit comments