Skip to content

Commit b18580b

Browse files
committed
- add skeleton loading UI for improved UX
- implement responsive skeleton components for cards and stats - add animation with CSS gradients for loading effect - auto-calculate optimal skeleton count based on viewport
1 parent 3f3ac4d commit b18580b

File tree

1 file changed

+138
-3
lines changed

1 file changed

+138
-3
lines changed

index.html

Lines changed: 138 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,80 @@
109109
.filter-item:hover {background:var(--gray);}
110110
.filter-item.active {background:var(--primary); color:white;}
111111
.clear-search {position:absolute; right:2.5rem; top:50%; transform:translateY(-50%); color:#6c757d; cursor:pointer; font-size:0.8rem; display:none;}
112+
113+
/* Skeleton loader styles */
114+
.skeleton {
115+
background: linear-gradient(90deg, var(--card) 0%, var(--gray) 50%, var(--card) 100%);
116+
background-size: 200% 100%;
117+
animation: skeleton-loading 1.5s infinite;
118+
border-radius: var(--radius);
119+
}
120+
121+
@keyframes skeleton-loading {
122+
0% { background-position: 200% 0; }
123+
100% { background-position: -200% 0; }
124+
}
125+
126+
.skeleton-card {
127+
background: var(--card);
128+
border-radius: var(--radius);
129+
box-shadow: var(--shadow);
130+
overflow: hidden;
131+
height: 100%;
132+
display: flex;
133+
flex-direction: column;
134+
}
135+
136+
.skeleton-img {
137+
width: 100%;
138+
aspect-ratio: 16/10;
139+
}
140+
141+
.skeleton-title {
142+
height: 20px;
143+
width: 80%;
144+
margin: 1rem 1rem 0.5rem 1rem;
145+
}
146+
147+
.skeleton-desc {
148+
height: 12px;
149+
width: 90%;
150+
margin: 0.5rem 1rem;
151+
}
152+
153+
.skeleton-desc-short {
154+
width: 60%;
155+
}
156+
157+
.skeleton-btn {
158+
height: 36px;
159+
width: 100px;
160+
margin: auto 1rem 1rem 1rem;
161+
}
162+
163+
.skeleton-stat-card {
164+
background: var(--card);
165+
border-radius: var(--radius);
166+
padding: 1rem;
167+
box-shadow: var(--shadow);
168+
min-width: 150px;
169+
text-align: center;
170+
flex-grow: 1;
171+
max-width: 220px;
172+
}
173+
174+
.skeleton-stat-number {
175+
height: 28px;
176+
width: 60%;
177+
margin: 0 auto 0.5rem auto;
178+
}
179+
180+
.skeleton-stat-label {
181+
height: 16px;
182+
width: 80%;
183+
margin: 0 auto;
184+
}
185+
112186
@media (max-width:768px) {
113187
.header-content {flex-wrap:wrap; height:auto; padding:1rem;}
114188
.search-bar {width:100%; margin-top:1rem; order:3;}
@@ -146,6 +220,9 @@
146220
.back-to-top { background-color: var(--primary); color: white; }
147221
.pill.game { background: rgba(77, 132, 163, 0.3); color: var(--primary); }
148222
.pill.tool { background: rgba(245, 168, 73, 0.3); color: var(--secondary); }
223+
.skeleton { background: linear-gradient(90deg, var(--card) 0%, var(--gray) 50%, var(--card) 100%); }
224+
.skeleton-card { background-color: var(--card); }
225+
.skeleton-stat-card { background-color: var(--card); }
149226
}
150227

151228
.svg-inline--fa {
@@ -220,13 +297,17 @@
220297
</div>
221298

222299
<main class="container">
223-
<div class="stats" id="statsContainer"></div>
300+
<div class="stats" id="statsContainer">
301+
<!-- Skeleton stats will be placed here -->
302+
</div>
224303

225304
<section class="main-section">
226305
<div class="section-header">
227306
<h2 class="section-title" id="mainTitle">Featured Games & Tools</h2>
228307
</div>
229-
<div class="grid-view" id="mainGrid"></div>
308+
<div class="grid-view" id="mainGrid">
309+
<!-- Skeleton cards will be placed here -->
310+
</div>
230311
<div class="loading" id="loadingIndicator">
231312
<div class="spinner"></div>
232313
<p>Loading more items...</p>
@@ -295,6 +376,60 @@ <h3>No results found</h3>
295376
const itemsPerPage = 24;
296377
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
297378

379+
// Initialize skeleton loaders
380+
function createSkeletonStatCard() {
381+
return `
382+
<div class="skeleton-stat-card">
383+
<div class="skeleton skeleton-stat-number"></div>
384+
<div class="skeleton skeleton-stat-label"></div>
385+
</div>
386+
`;
387+
}
388+
389+
function createSkeletonCard() {
390+
return `
391+
<div class="skeleton-card">
392+
<div class="skeleton skeleton-img"></div>
393+
<div class="skeleton skeleton-title"></div>
394+
<div class="skeleton skeleton-desc"></div>
395+
<div class="skeleton skeleton-desc skeleton-desc-short"></div>
396+
<div class="skeleton skeleton-btn"></div>
397+
</div>
398+
`;
399+
}
400+
401+
function initSkeletons() {
402+
// Init stats skeletons
403+
let statsSkeletons = '';
404+
for (let i = 0; i < 5; i++) {
405+
statsSkeletons += createSkeletonStatCard();
406+
}
407+
statsContainer.innerHTML = statsSkeletons;
408+
409+
// Init card skeletons - calculate how many fit in the viewport
410+
const viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
411+
const viewportHeight = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
412+
413+
const cardWidth = isMobile ? 160 : 220;
414+
const cardHeight = 280;
415+
const gridGap = 20;
416+
417+
const cardsPerRow = Math.floor(viewportWidth / (cardWidth + gridGap));
418+
const rowsVisible = Math.ceil((viewportHeight - 300) / (cardHeight + gridGap));
419+
420+
// Create at least 8 cards or enough to fill viewport plus one row
421+
const skeletonCount = Math.max(8, cardsPerRow * (rowsVisible + 1));
422+
423+
let gridSkeletons = '';
424+
for (let i = 0; i < skeletonCount; i++) {
425+
gridSkeletons += createSkeletonCard();
426+
}
427+
mainGrid.innerHTML = gridSkeletons;
428+
}
429+
430+
// Call immediately to show skeletons before data loads
431+
initSkeletons();
432+
298433
// Category mapping function
299434
function getCategory(item) {
300435
const title = item.title.toLowerCase();
@@ -712,4 +847,4 @@ <h3 class="card-title" title="${item.title}">${item.title}</h3>
712847
<!--<script src="util/chatbot/chatbot.js"></script>-->
713848
<script data-goatcounter="https://gptgames.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>
714849
</body>
715-
</html>
850+
</html>

0 commit comments

Comments
 (0)