11---
2- // Fetch stars at build time from GitHub API
2+ import { projects } from ' ../data/projects' ;
3+
34type Repo = { name: string ; stargazers_count: number };
45
56let repoStars: Record <string , number > = {};
67try {
78 const res = await fetch (' https://api.github.com/orgs/rustkit-ai/repos' );
89 const repos: Repo [] = await res .json ();
910 repos .forEach ((r ) => { repoStars [r .name ] = r .stargazers_count ; });
10- } catch {
11- // fallback — stars stay 0
12- }
13-
14- const projects = [
15- {
16- name: ' memo' ,
17- description: ' Persistent memory for AI coding agents — Claude Code, Cursor, Windsurf, and Copilot. One command setup, zero manual steps.' ,
18- url: ' https://github.com/rustkit-ai/memo' ,
19- install: ' cargo install memo' ,
20- stars: repoStars [' memo' ] ?? 0 ,
21- icon: ' brain' ,
22- },
23- {
24- name: ' rustkit-mcp' ,
25- description: ' An MCP proxy that dramatically reduces LLM token costs. Sit between your AI tools and APIs — efficient, transparent, fast.' ,
26- url: ' https://github.com/rustkit-ai/rustkit-mcp' ,
27- install: ' cargo install rustkit-mcp' ,
28- stars: repoStars [' rustkit-mcp' ] ?? 0 ,
29- icon: ' activity' ,
30- },
31- {
32- name: ' rustkit-semantic' ,
33- description: ' Local semantic search for Rust apps — store text, search by meaning. No cloud, no API key. Powered by BGE-Small + HNSW + SQLite, runs fully on-device.' ,
34- url: ' https://github.com/rustkit-ai/rustkit-semantic' ,
35- install: ' cargo add rustkit-semantic' ,
36- stars: repoStars [' rustkit-semantic' ] ?? 0 ,
37- icon: ' search' ,
38- },
39- ];
11+ } catch {}
12+
13+ const icons: Record <string , string > = {
14+ ' memo-agent' : ' brain' ,
15+ ' rustkit-mcp' : ' activity' ,
16+ ' rustkit-semantic' : ' search' ,
17+ };
4018---
4119
4220<section id =" projects" >
@@ -46,54 +24,58 @@ const projects = [
4624 <p class =" section-sub reveal" >Open source tools designed to make AI-assisted development faster and smarter.</p >
4725
4826 <div class =" projects-grid" >
49- { projects .map ((p ) => (
50- <a href = { p .url } target = " _blank" rel = " noopener" class = " project-card reveal" >
51- <div class = " card-header" >
52- <div class = " card-icon" >
53- { p .icon === ' brain' && (
54- <svg viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" stroke-linecap = " round" stroke-linejoin = " round" >
55- <path d = " M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z" /><circle cx = " 12" cy = " 12" r = " 3" />
56- </svg >
57- )}
58- { p .icon === ' activity' && (
59- <svg viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" stroke-linecap = " round" stroke-linejoin = " round" >
60- <polyline points = " 22 12 18 12 15 21 9 3 6 12 2 12" />
61- </svg >
62- )}
63- { p .icon === ' search' && (
64- <svg viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" stroke-linecap = " round" stroke-linejoin = " round" >
65- <circle cx = " 11" cy = " 11" r = " 8" /><line x1 = " 21" y1 = " 21" x2 = " 16.65" y2 = " 16.65" />
66- </svg >
67- )}
27+ { projects .map ((p ) => {
28+ const stars = repoStars [p .slug ] ?? 0 ;
29+ const icon = icons [p .slug ] ?? ' search' ;
30+ return (
31+ <a href = { ` /projects/${p .slug } ` } class = " project-card reveal" >
32+ <div class = " card-header" >
33+ <div class = " card-icon" >
34+ { icon === ' brain' && (
35+ <svg viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" stroke-linecap = " round" stroke-linejoin = " round" >
36+ <path d = " M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z" /><circle cx = " 12" cy = " 12" r = " 3" />
37+ </svg >
38+ )}
39+ { icon === ' activity' && (
40+ <svg viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" stroke-linecap = " round" stroke-linejoin = " round" >
41+ <polyline points = " 22 12 18 12 15 21 9 3 6 12 2 12" />
42+ </svg >
43+ )}
44+ { icon === ' search' && (
45+ <svg viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" stroke-linecap = " round" stroke-linejoin = " round" >
46+ <circle cx = " 11" cy = " 11" r = " 8" /><line x1 = " 21" y1 = " 21" x2 = " 16.65" y2 = " 16.65" />
47+ </svg >
48+ )}
49+ </div >
50+ <div class = " card-badges" >
51+ <span class = " badge badge-rust" >{ p .language } </span >
52+ { stars > 0 && (
53+ <span class = " badge badge-stars" >
54+ <svg width = " 10" height = " 10" viewBox = " 0 0 24 24" fill = " currentColor" ><polygon points = " 12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" /></svg >
55+ { stars }
56+ </span >
57+ )}
58+ </div >
6859 </div >
69- <div class = " card-badges" >
70- <span class = " badge badge-rust" >Rust</span >
71- { p .stars > 0 && (
72- <span class = " badge badge-stars" >
73- <svg width = " 10" height = " 10" viewBox = " 0 0 24 24" fill = " currentColor" ><polygon points = " 12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" /></svg >
74- { p .stars }
75- </span >
76- )}
77- </div >
78- </div >
7960
80- <div class = " card-name" >{ p .name } </div >
81- <p class = " card-desc" >{ p .description } </p >
61+ <div class = " card-name" >{ p .name } </div >
62+ <p class = " card-desc" >{ p .description } </p >
63+
64+ { p .install && (
65+ <div class = " card-install" >
66+ <span class = " install-prompt" >$</span >
67+ <code >{ p .install } </code >
68+ </div >
69+ )}
8270
83- { p . install && (
84- < div class = " card-install " >
85- < span class = " install-prompt " >$</ span >
86- <code > { p . install } </ code >
71+ < div class = " card-footer " >
72+ < svg width = " 12 " height = " 12 " viewBox = " 0 0 24 24 " fill = " currentColor " >< path d = " M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z " /></ svg >
73+ rustkit-ai/ { p . name }
74+ <span class = " card-arrow " >→</ span >
8775 </div >
88- )}
89-
90- <div class = " card-footer" >
91- <svg width = " 12" height = " 12" viewBox = " 0 0 24 24" fill = " currentColor" ><path d = " M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" /></svg >
92- rustkit-ai/{ p .name }
93- <span class = " card-arrow" >→</span >
94- </div >
95- </a >
96- ))}
76+ </a >
77+ );
78+ })}
9779 </div >
9880 </div >
9981</section >
@@ -136,31 +118,18 @@ const projects = [
136118 .project-card:hover { background: var(--bg-3); }
137119 .project-card:hover::before { transform: scaleX(1); }
138120
139- .card-header {
140- display: flex;
141- align-items: flex-start;
142- justify-content: space-between;
143- gap: 1rem;
144- }
121+ .card-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
145122
146123 .card-icon {
147- width: 36px;
148- height: 36px;
124+ width: 36px; height: 36px;
149125 border-radius: 6px;
150126 background: var(--rust-glow);
151127 border: 1px solid var(--rust-dim);
152- display: flex;
153- align-items: center;
154- justify-content: center;
128+ display: flex; align-items: center; justify-content: center;
155129 flex-shrink: 0;
156130 }
157131
158- .card-icon svg {
159- width: 18px;
160- height: 18px;
161- color: var(--rust);
162- }
163-
132+ .card-icon svg { width: 18px; height: 18px; color: var(--rust); }
164133 .card-badges { display: flex; gap: 0.4rem; flex-wrap: wrap; }
165134
166135 .badge {
@@ -173,62 +142,24 @@ const projects = [
173142 }
174143
175144 .badge-rust { background: #e8591a18; color: #e87a4a; border: 1px solid #e8591a30; }
176- .badge-stars {
177- background: var(--blue-dim);
178- color: var(--blue);
179- border: 1px solid #4a9eff30;
180- display: flex;
181- align-items: center;
182- gap: 0.3rem;
183- }
184-
185- .card-name {
186- font-family: 'Space Mono', monospace;
187- font-size: 1rem;
188- font-weight: 700;
189- letter-spacing: -0.02em;
190- color: var(--text);
191- }
145+ .badge-stars { background: var(--blue-dim); color: var(--blue); border: 1px solid #4a9eff30; display: flex; align-items: center; gap: 0.3rem; }
192146
147+ .card-name { font-family: 'Space Mono', monospace; font-size: 1rem; font-weight: 700; letter-spacing: -0.02em; color: var(--text); }
193148 .card-desc { font-size: 0.88rem; color: var(--text-muted); line-height: 1.6; flex: 1; }
194149
195150 .card-install {
196- display: flex;
197- align-items: center;
198- gap: 0.5rem;
199- background: var(--bg);
200- border: 1px solid var(--border);
201- border-radius: 4px;
202- padding: 0.4rem 0.75rem;
203- }
204-
205- .install-prompt {
206- font-family: 'IBM Plex Mono', monospace;
207- font-size: 0.75rem;
208- color: var(--rust);
209- }
210-
211- .card-install code {
212- font-family: 'IBM Plex Mono', monospace;
213- font-size: 0.75rem;
214- color: var(--text-muted);
151+ display: flex; align-items: center; gap: 0.5rem;
152+ background: var(--bg); border: 1px solid var(--border);
153+ border-radius: 4px; padding: 0.4rem 0.75rem;
215154 }
216155
217- .card-footer {
218- display: flex;
219- align-items: center;
220- gap: 0.4rem;
221- font-family: 'IBM Plex Mono', monospace;
222- font-size: 0.75rem;
223- color: var(--text-dim);
224- transition: color 0.2s;
225- }
156+ .install-prompt { font-family: 'IBM Plex Mono', monospace; font-size: 0.75rem; color: var(--rust); }
157+ .card-install code { font-family: 'IBM Plex Mono', monospace; font-size: 0.75rem; color: var(--text-muted); }
226158
159+ .card-footer { display: flex; align-items: center; gap: 0.4rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.75rem; color: var(--text-dim); transition: color 0.2s; }
227160 .project-card:hover .card-footer { color: var(--rust); }
228161 .card-arrow { margin-left: auto; transition: transform 0.2s; }
229162 .project-card:hover .card-arrow { transform: translateX(3px); }
230163
231- @media (max-width: 640px) {
232- .projects-grid { grid-template-columns: 1fr; }
233- }
164+ @media (max-width: 640px) { .projects-grid { grid-template-columns: 1fr; } }
234165</style >
0 commit comments