@@ -4,13 +4,16 @@ import { projects } from '../data/projects';
44const releaseProjects = projects .filter ((p ) => p .hasReleases );
55---
66
7- <section id =" releases" style = " display:none " >
7+ <section id =" releases" >
88 <div class =" section-inner" >
99 <div class =" section-label reveal" >Changelog</div >
1010 <h2 class =" reveal" >Latest releases</h2 >
1111 <p class =" section-sub reveal" >Recent updates across our open source projects.</p >
1212
13- <div class =" releases-list reveal" id =" releases-list" ></div >
13+ <div class =" releases-list reveal" id =" releases-list" >
14+ <div class =" skeleton-item" ><div class =" sk sk-tag" ></div ><div class =" sk sk-name" ></div ><div class =" sk sk-date" ></div ></div >
15+ <div class =" skeleton-item" ><div class =" sk sk-tag" ></div ><div class =" sk sk-name" ></div ><div class =" sk sk-date" ></div ></div >
16+ </div >
1417
1518 <div class =" all-links reveal" id =" all-links" ></div >
1619 </div >
@@ -42,11 +45,14 @@ const releaseProjects = projects.filter((p) => p.hasReleases);
4245 );
4346
4447 const valid = results.filter(Boolean);
45- if (valid.length === 0) return;
46-
4748 const list = document.getElementById('releases-list');
4849 const allLinks = document.getElementById('all-links');
4950
51+ if (valid.length === 0) {
52+ list.innerHTML = '<p class="releases-empty">No releases found.</p>';
53+ return;
54+ }
55+
5056 list.innerHTML = valid.map(({ project: p, release: r }) => `
5157 <a href="${r.html_url}" target="_blank" rel="noopener" class="release-item latest">
5258 <div class="release-left">
@@ -66,19 +72,6 @@ const releaseProjects = projects.filter((p) => p.hasReleases);
6672 </a>
6773 `).join('');
6874
69- const section = document.getElementById('releases');
70- section.style.display = '';
71-
72- const observer = new IntersectionObserver((entries) => {
73- entries.forEach((entry) => {
74- if (entry.isIntersecting) {
75- entry.target.classList.add('visible');
76- observer.unobserve(entry.target);
77- }
78- });
79- }, { threshold: 0.1 });
80-
81- section.querySelectorAll('.reveal').forEach((el) => observer.observe(el));
8275 }
8376
8477 loadReleases();
@@ -188,6 +181,41 @@ const releaseProjects = projects.filter((p) => p.hasReleases);
188181
189182 #releases .all-releases:hover { color: var(--rust); }
190183
184+ #releases .releases-empty {
185+ font-family: 'IBM Plex Mono', monospace;
186+ font-size: 0.8rem;
187+ color: var(--text-dim);
188+ padding: 1.5rem;
189+ text-align: center;
190+ }
191+
192+ /* Skeleton */
193+ #releases .skeleton-item {
194+ display: flex;
195+ align-items: center;
196+ gap: 1rem;
197+ padding: 1rem 1.5rem;
198+ border-bottom: 1px solid var(--border);
199+ background: var(--bg-2);
200+ }
201+
202+ #releases .skeleton-item:last-child { border-bottom: none; }
203+
204+ #releases .sk {
205+ background: var(--border-hi);
206+ border-radius: 3px;
207+ animation: sk-pulse 1.4s ease-in-out infinite;
208+ }
209+
210+ #releases .sk-tag { width: 60px; height: 14px; }
211+ #releases .sk-name { width: 160px; height: 12px; flex: 1; max-width: 200px; }
212+ #releases .sk-date { width: 80px; height: 12px; margin-left: auto; }
213+
214+ @keyframes sk-pulse {
215+ 0%, 100% { opacity: 0.4; }
216+ 50% { opacity: 0.8; }
217+ }
218+
191219 @media (max-width: 640px) {
192220 #releases .release-item {
193221 grid-template-columns: auto 1fr auto;
0 commit comments