Skip to content

Commit 3a5150b

Browse files
feat(ui): Enhance layout and update GitHub open-source contributors
Improve page layout with full-height container and expand GitHub contributors list with usernames and avatar URLs for better representation and attribution of open-source contributors.
1 parent b272d37 commit 3a5150b

File tree

4 files changed

+379
-70
lines changed

4 files changed

+379
-70
lines changed

apps/web/src/components/github-open-source.tsx

Lines changed: 272 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,60 @@ const ORG_REPO = "fastrepl/hyprnote";
55

66
// Curated list of profiles to display
77
const CURATED_PROFILES = [
8-
// TODO: Add your curated list of avatar URLs here
9-
"https://avatars.githubusercontent.com/u/61503739?v=4",
10-
"https://avatars.githubusercontent.com/u/105270342?v=4",
11-
"https://avatars.githubusercontent.com/u/30039641?v=4",
12-
"https://avatars.githubusercontent.com/u/63365510?v=4",
13-
"https://avatars.githubusercontent.com/u/97124713?v=4",
14-
"https://avatars.githubusercontent.com/u/59800?v=4",
15-
"https://avatars.githubusercontent.com/u/51254761?v=4",
16-
"https://avatars.githubusercontent.com/u/76832007?v=4",
17-
"https://avatars.githubusercontent.com/u/86834898?v=4",
18-
"https://avatars.githubusercontent.com/u/48201223?v=4",
19-
"https://avatars.githubusercontent.com/u/26774729?v=4",
20-
"https://avatars.githubusercontent.com/u/23347263?v=4",
8+
{ username: "tobi", avatar: "https://avatars.githubusercontent.com/u/347?v=4" },
9+
{ username: "DidierRLopes", avatar: "https://avatars.githubusercontent.com/u/25267873?v=4" },
10+
{ username: "FelixMalfait", avatar: "https://avatars.githubusercontent.com/u/6399865?v=4" },
11+
{ username: "jeremyfowers", avatar: "https://avatars.githubusercontent.com/u/80718789?v=4" },
12+
{ username: "micheleriva", avatar: "https://avatars.githubusercontent.com/u/14977595?v=4" },
13+
{ username: "thomwolf", avatar: "https://avatars.githubusercontent.com/u/7353373?v=4" },
14+
{ username: "brodock", avatar: "https://avatars.githubusercontent.com/u/20575?v=4" },
15+
{ username: "anthonycorletti", avatar: "https://avatars.githubusercontent.com/u/3477132?v=4" },
16+
{ username: "followingell", avatar: "https://avatars.githubusercontent.com/u/5324956?v=4" },
17+
{ username: "mbanzi", avatar: "https://avatars.githubusercontent.com/u/405127?v=4" },
18+
{ username: "kevinxh", avatar: "https://avatars.githubusercontent.com/u/10948652?v=4" },
19+
{ username: "gregnr", avatar: "https://avatars.githubusercontent.com/u/4133076?v=4" },
20+
{ username: "JoeDo", avatar: "https://avatars.githubusercontent.com/u/775702?v=4" },
21+
{ username: "toby", avatar: "https://avatars.githubusercontent.com/u/83556?v=4" },
22+
{ username: "patrick91", avatar: "https://avatars.githubusercontent.com/u/667029?v=4" },
23+
{ username: "timrogers", avatar: "https://avatars.githubusercontent.com/u/116134?v=4" },
24+
{ username: "freeqaz", avatar: "https://avatars.githubusercontent.com/u/4573221?v=4" },
25+
{ username: "robertefreeman", avatar: "https://avatars.githubusercontent.com/u/6842762?v=4" },
26+
{ username: "mriley", avatar: "https://avatars.githubusercontent.com/u/28009?v=4" },
27+
{ username: "pmdartus", avatar: "https://avatars.githubusercontent.com/u/2567083?v=4" },
28+
{ username: "ezekg", avatar: "https://avatars.githubusercontent.com/u/6979737?v=4" },
29+
{ username: "Jonathanvwersch", avatar: "https://avatars.githubusercontent.com/u/38623677?v=4" },
30+
{ username: "thewh1teagle", avatar: "https://avatars.githubusercontent.com/u/61390950?v=4" },
31+
{ username: "dguido", avatar: "https://avatars.githubusercontent.com/u/294844?v=4" },
32+
{ username: "calvinfo", avatar: "https://avatars.githubusercontent.com/u/487539?v=4" },
33+
{ username: "velyan", avatar: "https://avatars.githubusercontent.com/u/1313779?v=4" },
34+
{ username: "mfts", avatar: "https://avatars.githubusercontent.com/u/4049052?v=4" },
35+
{ username: "devgony", avatar: "https://avatars.githubusercontent.com/u/51254761?v=4" },
36+
{ username: "bartoszgrabski", avatar: "https://avatars.githubusercontent.com/u/5851315?v=4" },
37+
{ username: "mpazik", avatar: "https://avatars.githubusercontent.com/u/4086126?v=4" },
38+
{ username: "Necromenta", avatar: "https://avatars.githubusercontent.com/u/95664440?v=4" },
39+
{ username: "jonpage0", avatar: "https://avatars.githubusercontent.com/u/48391075?v=4" },
40+
{ username: "ralder", avatar: "https://avatars.githubusercontent.com/u/10889830?v=4" },
41+
{ username: "mateusrevoredo", avatar: "https://avatars.githubusercontent.com/u/1175432?v=4" },
42+
{ username: "annieappflowy", avatar: "https://avatars.githubusercontent.com/u/12026239?v=4" },
43+
{ username: "carllippert", avatar: "https://avatars.githubusercontent.com/u/16457876?v=4" },
44+
{ username: "avneetsb", avatar: "https://avatars.githubusercontent.com/u/5681972?v=4" },
45+
{ username: "anrath", avatar: "https://avatars.githubusercontent.com/u/62771105?v=4" },
46+
{ username: "srikanta30", avatar: "https://avatars.githubusercontent.com/u/28688901?v=4" },
47+
{ username: "allisoneer", avatar: "https://avatars.githubusercontent.com/u/20910163?v=4" },
48+
{ username: "kebot", avatar: "https://avatars.githubusercontent.com/u/289392?v=4" },
49+
{ username: "daevaorn", avatar: "https://avatars.githubusercontent.com/u/37366?v=4" },
50+
{ username: "rdt712", avatar: "https://avatars.githubusercontent.com/u/13369991?v=4" },
51+
{ username: "olabrainy", avatar: "https://avatars.githubusercontent.com/u/28204401?v=4" },
52+
{ username: "aaronrau", avatar: "https://avatars.githubusercontent.com/u/207538?v=4" },
53+
{ username: "jhbao", avatar: "https://avatars.githubusercontent.com/u/1714002?v=4" },
54+
{ username: "dbkegley", avatar: "https://avatars.githubusercontent.com/u/5727001?v=4" },
55+
{ username: "chrismalek", avatar: "https://avatars.githubusercontent.com/u/9403?v=4" },
56+
{ username: "KlimDos", avatar: "https://avatars.githubusercontent.com/u/17221993?v=4" },
57+
{ username: "maximilianmessing", avatar: "https://avatars.githubusercontent.com/u/7516094?v=4" },
58+
{ username: "levysantanna", avatar: "https://avatars.githubusercontent.com/u/1235238?v=4" },
59+
{ username: "falltodis", avatar: "https://avatars.githubusercontent.com/u/7006864?v=4" },
2160
];
2261

23-
function ProfileGrid({ profiles, cols }: { profiles: string[]; cols: 2 | 3 }) {
24-
const count = cols === 2 ? 4 : 6;
25-
return (
26-
<div className={`grid grid-cols-${cols} gap-1`}>
27-
{profiles.slice(0, count).map((avatar, idx) => (
28-
<div
29-
key={`profile-${idx}`}
30-
className="size-10 rounded-sm overflow-hidden border-2 border-neutral-200 bg-neutral-100"
31-
>
32-
<img
33-
src={avatar}
34-
alt="Contributor"
35-
className="w-full h-full object-cover"
36-
/>
37-
</div>
38-
))}
39-
</div>
40-
);
41-
}
42-
4362
function StatBadge({
4463
type,
4564
count,
@@ -50,59 +69,243 @@ function StatBadge({
5069
const renderCount = (n: number) => n > 1000 ? `${(n / 1000).toFixed(1)}k` : n;
5170

5271
return (
53-
<div className="flex flex-col gap-1 text-stone-600 h-24 items-center justify-center border border-neutral-200 rounded-sm px-4 bg-neutral-100">
54-
<p className="font-semibold font-serif">
72+
<div className="flex flex-col gap-1 text-stone-600 h-[84px] w-[84px] items-center justify-center border border-neutral-200 rounded-sm px-4 bg-neutral-100">
73+
<p className="font-semibold font-serif text-sm">
5574
{type === "stars" ? "Stars" : "Forks"}
5675
</p>
57-
<p className="text-sm font-medium text-center">
58-
{renderCount(count)}
76+
<p className="text-sm font-medium text-center">{renderCount(count)}</p>
77+
</div>
78+
);
79+
}
80+
81+
function OpenSourceButton({ showStars = false, starCount }: { showStars?: boolean; starCount?: number }) {
82+
const renderCount = (n: number) => n > 1000 ? `${(n / 1000).toFixed(1)}k` : n;
83+
84+
return (
85+
<div className="text-center space-y-4 w-full">
86+
<h2 className="text-3xl font-serif text-stone-600">Open source</h2>
87+
<p className="text-lg text-neutral-600">
88+
{"Hyprnote values privacy and community, so it's been transparent from day one"}
5989
</p>
90+
<a
91+
href={`https://github.com/${ORG_REPO}`}
92+
target="_blank"
93+
rel="noopener noreferrer"
94+
className={cn([
95+
"group px-6 h-12 inline-flex items-center justify-center gap-2",
96+
"bg-linear-to-t from-neutral-800 to-neutral-700 text-white rounded-full",
97+
"shadow-md hover:shadow-lg hover:scale-[102%] active:scale-[98%]",
98+
"transition-all cursor-pointer",
99+
])}
100+
>
101+
<Icon icon="mdi:github" className="text-xl" />
102+
View on GitHub
103+
{showStars && starCount && (
104+
<>
105+
<span className="text-neutral-400"></span>
106+
<div className="flex items-center gap-1">
107+
<Icon icon="mdi:star" className="text-lg" />
108+
<span>{renderCount(starCount)}</span>
109+
</div>
110+
</>
111+
)}
112+
</a>
60113
</div>
61114
);
62115
}
63116

117+
function Avatar({ username, avatar }: { username: string; avatar: string }) {
118+
return (
119+
<a
120+
href={`https://github.com/${username}`}
121+
target="_blank"
122+
rel="noopener noreferrer"
123+
className="size-10 rounded-sm overflow-hidden border-2 border-neutral-200 bg-neutral-100 shrink-0 hover:scale-110 hover:border-neutral-400 transition-all cursor-pointer"
124+
>
125+
<img src={avatar} alt={`${username}'s avatar`} className="w-full h-full object-cover" />
126+
</a>
127+
);
128+
}
129+
130+
function GridRow({
131+
children,
132+
}: {
133+
children: React.ReactNode;
134+
}) {
135+
return <div className="flex gap-1 items-center">{children}</div>;
136+
}
137+
64138
export function GitHubOpenSource() {
65139
const STARS_COUNT = 6419;
66140
const FORKS_COUNT = 396;
67141

68142
return (
69143
<section className="border-t border-neutral-100">
70-
<div className="py-16">
71-
<div className="grid grid-cols-1 lg:grid-cols-3 items-center max-w-6xl mx-auto">
72-
{/* Column 1: 2x2 grid + Stars badge + 3x2 grid */}
73-
<div className="flex items-center gap-1 justify-center bg-red-100">
74-
<ProfileGrid profiles={CURATED_PROFILES.slice(0, 4)} cols={2} />
75-
<StatBadge type="stars" count={STARS_COUNT} />
76-
<ProfileGrid profiles={CURATED_PROFILES.slice(4, 10)} cols={3} />
144+
<div className="py-16 px-4">
145+
{/* Small & Medium screens: button with star count */}
146+
<div className="lg:hidden max-w-4xl mx-auto">
147+
<OpenSourceButton showStars={true} starCount={STARS_COUNT} />
148+
</div>
149+
150+
{/* Large screen: 3-column layout with symmetric left and right sides */}
151+
<div className="hidden lg:flex justify-between max-w-7xl mx-auto items-center">
152+
{/* Left Side: 3 sub-columns with explicit widths */}
153+
<div className="flex gap-1">
154+
{/* Left Sub-column 1: 2x4 grid */}
155+
<div className="flex flex-col gap-1">
156+
{/* Row 1 */}
157+
<GridRow>
158+
{CURATED_PROFILES.slice(0, 2).map((profile) => (
159+
<Avatar key={`left-c1-r1-${profile.username}`} username={profile.username} avatar={profile.avatar} />
160+
))}
161+
</GridRow>
162+
{/* Row 2 */}
163+
<GridRow>
164+
{CURATED_PROFILES.slice(2, 4).map((profile) => (
165+
<Avatar key={`left-c1-r2-${profile.username}`} username={profile.username} avatar={profile.avatar} />
166+
))}
167+
</GridRow>
168+
{/* Row 3 */}
169+
<GridRow>
170+
{CURATED_PROFILES.slice(4, 6).map((profile) => (
171+
<Avatar key={`left-c1-r3-${profile.username}`} username={profile.username} avatar={profile.avatar} />
172+
))}
173+
</GridRow>
174+
{/* Row 4 */}
175+
<GridRow>
176+
{CURATED_PROFILES.slice(6, 8).map((profile) => (
177+
<Avatar key={`left-c1-r4-${profile.username}`} username={profile.username} avatar={profile.avatar} />
178+
))}
179+
</GridRow>
180+
</div>
181+
182+
{/* Left Sub-column 2: 2 avatars + Stars badge (spans 2 rows) + 2 avatars */}
183+
<div className="flex flex-col gap-1">
184+
{/* Row 1 */}
185+
<GridRow>
186+
{CURATED_PROFILES.slice(8, 10).map((profile) => (
187+
<Avatar key={`left-c2-r1-${profile.username}`} username={profile.username} avatar={profile.avatar} />
188+
))}
189+
</GridRow>
190+
{/* Rows 2-3: Stars badge - spans 2 rows (40px + 4px + 40px = 84px) */}
191+
<StatBadge type="stars" count={STARS_COUNT} />
192+
{/* Row 4 */}
193+
<GridRow>
194+
{CURATED_PROFILES.slice(10, 12).map((profile) => (
195+
<Avatar key={`left-c2-r4-${profile.username}`} username={profile.username} avatar={profile.avatar} />
196+
))}
197+
</GridRow>
198+
</div>
199+
200+
{/* Left Sub-column 3: 3x4 grid */}
201+
<div className="flex flex-col gap-1">
202+
{/* Row 1 */}
203+
<GridRow>
204+
{CURATED_PROFILES.slice(12, 15).map((profile) => (
205+
<Avatar key={`left-c3-r1-${profile.username}`} username={profile.username} avatar={profile.avatar} />
206+
))}
207+
</GridRow>
208+
{/* Row 2 */}
209+
<GridRow>
210+
{CURATED_PROFILES.slice(15, 18).map((profile) => (
211+
<Avatar key={`left-c3-r2-${profile.username}`} username={profile.username} avatar={profile.avatar} />
212+
))}
213+
</GridRow>
214+
{/* Row 3 */}
215+
<GridRow>
216+
{CURATED_PROFILES.slice(18, 21).map((profile) => (
217+
<Avatar key={`left-c3-r3-${profile.username}`} username={profile.username} avatar={profile.avatar} />
218+
))}
219+
</GridRow>
220+
{/* Row 4 */}
221+
<GridRow>
222+
{CURATED_PROFILES.slice(21, 24).map((profile) => (
223+
<Avatar key={`left-c3-r4-${profile.username}`} username={profile.username} avatar={profile.avatar} />
224+
))}
225+
</GridRow>
226+
</div>
77227
</div>
78228

79-
{/* Column 2: Text content */}
80-
<div className="text-center space-y-4">
81-
<h2 className="text-3xl font-serif text-stone-600">Open source</h2>
82-
<p className="text-lg text-neutral-600">
83-
{"Hyprnote values privacy and community, so it's been transparent from day one"}
84-
</p>
85-
<a
86-
href={`https://github.com/${ORG_REPO}`}
87-
target="_blank"
88-
rel="noopener noreferrer"
89-
className={cn([
90-
"group px-6 h-12 inline-flex items-center justify-center gap-2",
91-
"bg-linear-to-t from-neutral-800 to-neutral-700 text-white rounded-full",
92-
"shadow-md hover:shadow-lg hover:scale-[102%] active:scale-[98%]",
93-
"transition-all cursor-pointer",
94-
])}
95-
>
96-
<Icon icon="mdi:github" className="text-xl" />
97-
View on GitHub
98-
</a>
229+
{/* Center Column: Open Source Button */}
230+
<div className="flex items-center justify-center">
231+
<OpenSourceButton />
99232
</div>
100233

101-
{/* Column 3: 2x2 grid + Forks badge + 3x2 grid */}
102-
<div className="flex items-center gap-1 justify-center">
103-
<ProfileGrid profiles={CURATED_PROFILES.slice(4, 10)} cols={3} />
104-
<StatBadge type="forks" count={FORKS_COUNT} />
105-
<ProfileGrid profiles={CURATED_PROFILES.slice(0, 4)} cols={2} />
234+
{/* Right Side: 3 sub-columns with explicit widths (symmetric) */}
235+
<div className="flex gap-1">
236+
{/* Right Sub-column 1: 3x4 grid */}
237+
<div className="flex flex-col gap-1">
238+
{/* Row 1 */}
239+
<GridRow>
240+
{CURATED_PROFILES.slice(24, 27).map((profile) => (
241+
<Avatar key={`right-c1-r1-${profile.username}`} username={profile.username} avatar={profile.avatar} />
242+
))}
243+
</GridRow>
244+
{/* Row 2 */}
245+
<GridRow>
246+
{CURATED_PROFILES.slice(27, 30).map((profile) => (
247+
<Avatar key={`right-c1-r2-${profile.username}`} username={profile.username} avatar={profile.avatar} />
248+
))}
249+
</GridRow>
250+
{/* Row 3 */}
251+
<GridRow>
252+
{CURATED_PROFILES.slice(30, 33).map((profile) => (
253+
<Avatar key={`right-c1-r3-${profile.username}`} username={profile.username} avatar={profile.avatar} />
254+
))}
255+
</GridRow>
256+
{/* Row 4 */}
257+
<GridRow>
258+
{CURATED_PROFILES.slice(33, 36).map((profile) => (
259+
<Avatar key={`right-c1-r4-${profile.username}`} username={profile.username} avatar={profile.avatar} />
260+
))}
261+
</GridRow>
262+
</div>
263+
264+
{/* Right Sub-column 2: 2 avatars + Forks badge (spans 2 rows) + 2 avatars */}
265+
<div className="flex flex-col gap-1">
266+
{/* Row 1 */}
267+
<GridRow>
268+
{CURATED_PROFILES.slice(36, 38).map((profile) => (
269+
<Avatar key={`right-c2-r1-${profile.username}`} username={profile.username} avatar={profile.avatar} />
270+
))}
271+
</GridRow>
272+
{/* Rows 2-3: Forks badge - spans 2 rows (40px + 4px + 40px = 84px) */}
273+
<StatBadge type="forks" count={FORKS_COUNT} />
274+
{/* Row 4 */}
275+
<GridRow>
276+
{CURATED_PROFILES.slice(38, 40).map((profile) => (
277+
<Avatar key={`right-c2-r4-${profile.username}`} username={profile.username} avatar={profile.avatar} />
278+
))}
279+
</GridRow>
280+
</div>
281+
282+
{/* Right Sub-column 3: 2x4 grid */}
283+
<div className="flex flex-col gap-1">
284+
{/* Row 1 */}
285+
<GridRow>
286+
{CURATED_PROFILES.slice(40, 42).map((profile) => (
287+
<Avatar key={`right-c3-r1-${profile.username}`} username={profile.username} avatar={profile.avatar} />
288+
))}
289+
</GridRow>
290+
{/* Row 2 */}
291+
<GridRow>
292+
{CURATED_PROFILES.slice(42, 44).map((profile) => (
293+
<Avatar key={`right-c3-r2-${profile.username}`} username={profile.username} avatar={profile.avatar} />
294+
))}
295+
</GridRow>
296+
{/* Row 3 */}
297+
<GridRow>
298+
{CURATED_PROFILES.slice(44, 46).map((profile) => (
299+
<Avatar key={`right-c3-r3-${profile.username}`} username={profile.username} avatar={profile.avatar} />
300+
))}
301+
</GridRow>
302+
{/* Row 4 */}
303+
<GridRow>
304+
{CURATED_PROFILES.slice(46, 48).map((profile) => (
305+
<Avatar key={`right-c3-r4-${profile.username}`} username={profile.username} avatar={profile.avatar} />
306+
))}
307+
</GridRow>
308+
</div>
106309
</div>
107310
</div>
108311
</div>

0 commit comments

Comments
 (0)