Personal Website for @jaspermayone.com
jaspermayone.com
1"use client";
2
3import { Star, GitFork, ArrowUpRight } from "lucide-react";
4import { ExternalLink } from "@/components/ExternalLink";
5import { RepoStats } from "@/lib/types";
6import { SiGithub } from "react-icons/si";
7
8// GitHub linguist language colors
9const languageColors: Record<string, string> = {
10 TypeScript: "#3178c6",
11 JavaScript: "#f1e05a",
12 Python: "#3572A5",
13 Rust: "#dea584",
14 Go: "#00ADD8",
15 Ruby: "#701516",
16 Java: "#b07219",
17 "C++": "#f34b7d",
18 C: "#555555",
19 "C#": "#178600",
20 PHP: "#4F5D95",
21 Swift: "#F05138",
22 Kotlin: "#A97BFF",
23 HTML: "#e34c26",
24 CSS: "#663399",
25 Shell: "#89e051",
26 Lua: "#000080",
27 Dart: "#00B4AB",
28 Scala: "#c22d40",
29 Elixir: "#6e4a7e",
30 Haskell: "#5e5086",
31 Vue: "#41b883",
32 SCSS: "#c6538c",
33 Dockerfile: "#384d54",
34 Makefile: "#427819",
35};
36
37function getLanguageColor(language: string | null): string {
38 if (!language) return "#56ba8e"; // Site green fallback
39 return languageColors[language] || "#56ba8e"; // Site green fallback
40}
41
42interface OpenSourceCardProps {
43 name: string;
44 description: string;
45 repo: string;
46 stats?: RepoStats;
47 loading?: boolean;
48 featured?: boolean;
49 status?: "active" | "beta" | "deprecated" | "alpha";
50 liveUrl?: string;
51}
52
53export function OpenSourceCard({
54 name,
55 description,
56 repo,
57 stats,
58 loading,
59 featured,
60 status,
61 liveUrl,
62}: OpenSourceCardProps) {
63 const repoUrl = `https://github.com/${repo}`;
64
65 return (
66 <div
67 className={`rounded-lg border p-4 transition-all hover:shadow-md ${
68 featured
69 ? "border-blue-200 bg-blue-50/50 dark:border-blue-800 dark:bg-blue-900/10"
70 : "border-gray-200 bg-white dark:border-gray-700 dark:bg-slate-800"
71 }`}
72 >
73 <div className="mb-2 flex items-start justify-between">
74 <div className="flex items-center gap-2">
75 <h3 className="font-semibold text-gray-900 dark:text-white">
76 {name}
77 </h3>
78 {status === "alpha" && (
79 <span className="rounded-full bg-purple-100 px-2 py-0.5 text-xs text-purple-800 dark:bg-purple-900/30 dark:text-purple-300">
80 Alpha
81 </span>
82 )}
83 {status === "beta" && (
84 <span className="rounded-full bg-yellow-100 px-2 py-0.5 text-xs text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300">
85 Beta
86 </span>
87 )}
88 {status === "deprecated" && (
89 <span className="rounded-full bg-red-100 px-2 py-0.5 text-xs text-red-800 dark:bg-red-900/30 dark:text-red-300">
90 Deprecated
91 </span>
92 )}
93 </div>
94 <div className="flex gap-2">
95 {liveUrl && (
96 <ExternalLink
97 href={liveUrl}
98 className="text-gray-500 hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-400"
99 aria-label="Visit live site"
100 >
101 <ArrowUpRight className="h-4 w-4" />
102 </ExternalLink>
103 )}
104 <ExternalLink
105 href={repoUrl}
106 className="text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
107 aria-label="View repository"
108 >
109 <SiGithub className="h-4 w-4" />
110 </ExternalLink>
111 </div>
112 </div>
113
114 <p className="mb-3 text-sm text-gray-600 dark:text-white/70">
115 {description}
116 </p>
117
118 {/* Stats row */}
119 <div className="flex flex-wrap items-center gap-3 text-xs text-gray-500 dark:text-gray-400">
120 {loading ? (
121 <span className="animate-pulse">Loading stats...</span>
122 ) : stats && !stats.error ? (
123 <>
124 {stats.language && (
125 <span className="flex items-center gap-1">
126 <span
127 className="h-2 w-2 rounded-full"
128 style={{ backgroundColor: getLanguageColor(stats.language) }}
129 />
130 {stats.language}
131 </span>
132 )}
133 <span className="flex items-center gap-1">
134 <Star className="h-3 w-3" />
135 {stats.stars}
136 </span>
137 {stats.forks > 0 && (
138 <span className="flex items-center gap-1">
139 <GitFork className="h-3 w-3" />
140 {stats.forks}
141 </span>
142 )}
143 </>
144 ) : null}
145 </div>
146 </div>
147 );
148}