11---
2- type Release = {
3- tag_name: string ;
4- name: string ;
5- published_at: string | null ;
6- html_url: string ;
7- body: string | null ;
8- };
9-
10- let releases: Release [] = [];
11- try {
12- const res = await fetch (' https://api.github.com/repos/rustkit-ai/aimemo/releases' );
13- const data: Release [] = await res .json ();
14- releases = data
15- .filter ((r ) => r .published_at )
16- .sort ((a , b ) => new Date (b .published_at ! ).getTime () - new Date (a .published_at ! ).getTime ())
17- .slice (0 , 5 );
18- } catch {
19- // silently fail
20- }
21-
22- function formatDate(iso : string ) {
23- return new Date (iso ).toLocaleDateString (' en-US' , {
24- year: ' numeric' ,
25- month: ' short' ,
26- day: ' numeric' ,
27- });
28- }
292---
303
31- { releases .length > 0 && (
32- <section id = " releases" >
33- <div class = " section-inner" >
34- <div class = " section-label reveal" >Changelog</div >
35- <h2 class = " reveal" >Latest releases</h2 >
36- <p class = " section-sub reveal" >
37- Recent updates to <a href = " https://github.com/rustkit-ai/aimemo" target = " _blank" rel = " noopener" >aimemo</a >.
38- </p >
39-
40- <div class = " releases-list reveal" >
41- { releases .map ((r , i ) => (
42- <a href = { r .html_url } target = " _blank" rel = " noopener" class = { ` release-item ${i === 0 ? ' latest' : ' ' } ` } >
43- <div class = " release-left" >
44- <span class = " release-tag" >{ r .tag_name } </span >
45- { i === 0 && <span class = " release-badge" >Latest</span >}
46- </div >
47- <div class = " release-name" >{ r .name } </div >
48- <div class = " release-date" >{ formatDate (r .published_at ! )} </div >
49- <span class = " release-arrow" >→</span >
50- </a >
51- ))}
52- </div >
53-
54- <a href = " https://github.com/rustkit-ai/aimemo/releases" target = " _blank" rel = " noopener" class = " all-releases" >
55- View all releases →
56- </a >
57- </div >
58- </section >
59- )}
60-
61- <style >
62- .releases-list {
4+ <section id =" releases" style =" display:none" >
5+ <div class =" section-inner" >
6+ <div class =" section-label reveal" >Changelog</div >
7+ <h2 class =" reveal" >Latest releases</h2 >
8+ <p class =" section-sub reveal" >
9+ Recent updates to <a href =" https://github.com/rustkit-ai/aimemo" target =" _blank" rel =" noopener" >aimemo</a >.
10+ </p >
11+
12+ <div class =" releases-list reveal" id =" releases-list" ></div >
13+
14+ <a href =" https://github.com/rustkit-ai/aimemo/releases" target =" _blank" rel =" noopener" class =" all-releases" >
15+ View all releases →
16+ </a >
17+ </div >
18+ </section >
19+
20+ <script >
21+ function formatDate(iso: string) {
22+ return new Date(iso).toLocaleDateString('en-US', {
23+ year: 'numeric',
24+ month: 'short',
25+ day: 'numeric',
26+ });
27+ }
28+
29+ type Release = {
30+ tag_name: string;
31+ name: string;
32+ published_at: string | null;
33+ html_url: string;
34+ body: string | null;
35+ };
36+
37+ async function loadRelease() {
38+ try {
39+ const res = await fetch('https://api.github.com/repos/rustkit-ai/aimemo/releases');
40+ const data: Release[] = await res.json();
41+ const releases = data
42+ .filter((r) => r.published_at)
43+ .sort((a, b) => new Date(b.published_at!).getTime() - new Date(a.published_at!).getTime())
44+ .slice(0, 1);
45+
46+ if (releases.length === 0) return;
47+
48+ const r = releases[0];
49+ const list = document.getElementById('releases-list')!;
50+ list.innerHTML = `
51+ <a href="${r.html_url}" target="_blank" rel="noopener" class="release-item latest">
52+ <div class="release-left">
53+ <span class="release-tag">${r.tag_name}</span>
54+ <span class="release-badge">Latest</span>
55+ </div>
56+ <div class="release-name">${r.name}</div>
57+ <div class="release-date">${formatDate(r.published_at!)}</div>
58+ <span class="release-arrow">→</span>
59+ </a>
60+ `;
61+
62+ const section = document.getElementById('releases')!;
63+ section.style.display = '';
64+
65+ // Re-observe reveal elements now that the section is visible
66+ const observer = new IntersectionObserver((entries) => {
67+ entries.forEach((entry) => {
68+ if (entry.isIntersecting) {
69+ entry.target.classList.add('visible');
70+ observer.unobserve(entry.target);
71+ }
72+ });
73+ }, { threshold: 0.1 });
74+
75+ section.querySelectorAll('.reveal').forEach((el) => observer.observe(el));
76+ } catch {
77+ // silently fail
78+ }
79+ }
80+
81+ loadRelease();
82+ </script >
83+
84+ <style is:global >
85+ #releases .releases-list {
6386 display: flex;
6487 flex-direction: column;
6588 border: 1px solid var(--border);
@@ -68,7 +91,7 @@ function formatDate(iso: string) {
6891 margin-bottom: 1.5rem;
6992 }
7093
71- .release-item {
94+ #releases .release-item {
7295 display: grid;
7396 grid-template-columns: auto 1fr auto auto;
7497 align-items: center;
@@ -81,19 +104,19 @@ function formatDate(iso: string) {
81104 transition: background 0.2s;
82105 }
83106
84- .release-item:last-child { border-bottom: none; }
107+ #releases .release-item:last-child { border-bottom: none; }
85108
86- .release-item:hover { background: var(--bg-3); }
109+ #releases .release-item:hover { background: var(--bg-3); }
87110
88- .release-item.latest { background: var(--bg-3); }
111+ #releases .release-item.latest { background: var(--bg-3); }
89112
90- .release-left {
113+ #releases .release-left {
91114 display: flex;
92115 align-items: center;
93116 gap: 0.6rem;
94117 }
95118
96- .release-tag {
119+ #releases .release-tag {
97120 font-family: 'Space Mono', monospace;
98121 font-size: 0.85rem;
99122 font-weight: 700;
@@ -102,7 +125,7 @@ function formatDate(iso: string) {
102125 min-width: 60px;
103126 }
104127
105- .release-badge {
128+ #releases .release-badge {
106129 font-family: 'IBM Plex Mono', monospace;
107130 font-size: 0.62rem;
108131 padding: 0.15rem 0.45rem;
@@ -114,7 +137,7 @@ function formatDate(iso: string) {
114137 text-transform: uppercase;
115138 }
116139
117- .release-name {
140+ #releases .release-name {
118141 font-size: 0.88rem;
119142 color: var(--text-muted);
120143 font-family: 'IBM Plex Sans', sans-serif;
@@ -123,39 +146,39 @@ function formatDate(iso: string) {
123146 text-overflow: ellipsis;
124147 }
125148
126- .release-date {
149+ #releases .release-date {
127150 font-family: 'IBM Plex Mono', monospace;
128151 font-size: 0.75rem;
129152 color: var(--text-dim);
130153 white-space: nowrap;
131154 }
132155
133- .release-arrow {
156+ #releases .release-arrow {
134157 color: var(--text-dim);
135158 font-size: 0.85rem;
136159 transition: transform 0.2s, color 0.2s;
137160 }
138161
139- .release-item:hover .release-arrow {
162+ #releases .release-item:hover .release-arrow {
140163 transform: translateX(3px);
141164 color: var(--rust);
142165 }
143166
144- .all-releases {
167+ #releases .all-releases {
145168 font-family: 'IBM Plex Mono', monospace;
146169 font-size: 0.78rem;
147170 color: var(--text-dim);
148171 text-decoration: none;
149172 transition: color 0.2s;
150173 }
151174
152- .all-releases:hover { color: var(--rust); }
175+ #releases .all-releases:hover { color: var(--rust); }
153176
154177 @media (max-width: 640px) {
155- .release-item {
178+ #releases .release-item {
156179 grid-template-columns: auto 1fr auto;
157180 gap: 0.6rem;
158181 }
159- .release-name { display: none; }
182+ #releases .release-name { display: none; }
160183 }
161184</style >
0 commit comments