Skip to content

Commit 74abcc7

Browse files
authored
Add star prompt. Overengineered a "please star us" popup with live GitHub API, progress bars, and graceful degradation because why not (#50)
Co-authored-by: Panos <>
1 parent e954219 commit 74abcc7

File tree

4 files changed

+235
-1
lines changed

4 files changed

+235
-1
lines changed

static/css/styles.css

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,116 @@ body {
210210
box-shadow: var(--shadow);
211211
}
212212

213+
/* GitHub star prompt toast */
214+
.star-toast {
215+
position: fixed;
216+
right: 1.5rem;
217+
top: 1.5rem;
218+
width: 320px;
219+
background: rgba(12, 12, 28, 0.98);
220+
border: 1px solid rgba(255, 255, 255, 0.1);
221+
border-radius: 14px;
222+
padding: 1.25rem;
223+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
224+
transition: opacity 0.3s ease, transform 0.3s ease;
225+
z-index: 1000;
226+
}
227+
228+
.star-toast.is-hidden {
229+
opacity: 0;
230+
transform: translateY(-12px);
231+
pointer-events: none;
232+
}
233+
234+
.star-toast-close {
235+
position: absolute;
236+
top: 0.75rem;
237+
right: 0.75rem;
238+
width: 24px;
239+
height: 24px;
240+
background: transparent;
241+
border: none;
242+
color: rgba(255, 255, 255, 0.3);
243+
font-size: 1.1rem;
244+
line-height: 1;
245+
cursor: pointer;
246+
transition: color 0.2s ease;
247+
display: flex;
248+
align-items: center;
249+
justify-content: center;
250+
}
251+
252+
.star-toast-close:hover {
253+
color: rgba(255, 255, 255, 0.7);
254+
}
255+
256+
.star-toast-title {
257+
font-size: 1.05rem;
258+
font-weight: 600;
259+
color: var(--text-primary);
260+
margin-bottom: 0.25rem;
261+
padding-right: 1.5rem;
262+
}
263+
264+
.star-toast-text {
265+
font-size: 0.9rem;
266+
color: rgba(255, 255, 255, 0.5);
267+
margin-bottom: 1rem;
268+
}
269+
270+
.star-toast-progress-wrap {
271+
margin-bottom: 1rem;
272+
}
273+
274+
.star-toast-progress-wrap.is-hidden {
275+
display: none;
276+
}
277+
278+
.star-toast-progress-bar {
279+
height: 5px;
280+
background: rgba(255, 255, 255, 0.1);
281+
border-radius: 4px;
282+
overflow: hidden;
283+
margin-bottom: 0.4rem;
284+
}
285+
286+
.star-toast-progress-fill {
287+
height: 100%;
288+
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
289+
border-radius: 4px;
290+
transition: width 0.5s ease;
291+
}
292+
293+
.star-toast-milestone {
294+
font-size: 0.8rem;
295+
color: rgba(255, 255, 255, 0.45);
296+
}
297+
298+
.star-toast-btn {
299+
width: 100%;
300+
border: none;
301+
border-radius: 10px;
302+
padding: 0.7rem 1rem;
303+
font-size: 0.9rem;
304+
font-weight: 600;
305+
cursor: pointer;
306+
transition: all 0.2s ease;
307+
background: linear-gradient(135deg, #4facfe 0%, #00c6fb 100%);
308+
color: #ffffff;
309+
display: inline-flex;
310+
align-items: center;
311+
justify-content: center;
312+
gap: 0.5rem;
313+
}
314+
315+
.star-toast-btn svg {
316+
flex-shrink: 0;
317+
}
318+
319+
.star-toast-btn:hover {
320+
filter: brightness(1.1);
321+
}
322+
213323
.status-indicator {
214324
display: flex;
215325
align-items: center;
@@ -1315,6 +1425,13 @@ body {
13151425
height: 120px;
13161426
}
13171427

1428+
.star-toast {
1429+
right: 1rem;
1430+
top: 1rem;
1431+
width: calc(100% - 2rem);
1432+
max-width: 320px;
1433+
}
1434+
13181435
.charts-section {
13191436
padding: 0 1.5rem 1.5rem 1.5rem;
13201437
}

static/js/app.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

templates/index.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,22 @@ <h1>🔥 GPU Hot</h1>
9595
</div>
9696
</div>
9797

98+
<div class="star-toast is-hidden" id="star-toast" role="status" aria-live="polite">
99+
<button class="star-toast-close" type="button" data-action="dismiss" aria-label="Close">&times;</button>
100+
<div class="star-toast-title">Star us on GitHub</div>
101+
<div class="star-toast-text">Help others discover GPU Hot</div>
102+
<div class="star-toast-progress-wrap is-hidden" id="star-progress-wrap">
103+
<div class="star-toast-progress-bar">
104+
<div class="star-toast-progress-fill" id="star-progress-fill"></div>
105+
</div>
106+
<div class="star-toast-milestone" id="star-toast-milestone"></div>
107+
</div>
108+
<button class="star-toast-btn" type="button" data-action="star">
109+
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"/></svg>
110+
Star
111+
</button>
112+
</div>
113+
98114
<!-- Application Scripts -->
99115
<!-- Load in order: chart-config -> chart-manager -> gpu-cards -> ui -> socket-handlers -> app -->
100116
<script src="/static/js/chart-config.js"></script>

version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
"""Version information for GPU Hot"""
22

3-
__version__ = "1.6.0"
3+
__version__ = "1.6.1"
44

0 commit comments

Comments
 (0)