Skip to content

Commit 4927c0e

Browse files
7oSkaaacursoragent
andcommitted
Add session icons and portfolio link across the site.
Map each session to a distinct icon on card covers, add nav and footer links to the portfolio, and surface About Me in the hero. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 1498d10 commit 4927c0e

9 files changed

Lines changed: 334 additions & 103 deletions

File tree

website/scripts/generate-content.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ async function main() {
288288
youtubeChannelId: YOUTUBE_CHANNEL_ID,
289289
links: {
290290
templates: "https://7oskaaa.github.io/CP-Templates/",
291+
portfolio: "https://7oskaaa.github.io/Portfolio/",
291292
juniorSheet:
292293
"https://docs.google.com/spreadsheets/d/1iJZWP2nS_OB3kCTjq8L6TrJJ4o-5lhxDOyTaocSYc-k/edit#gid=84654839",
293294
juniorSheetAddon: "https://github.com/Waelahmed99/junior-sheet-add-on",

website/src/components/Hero.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import {
33
ArrowRight,
44
BookOpen,
55
ClipboardList,
6+
ExternalLink,
67
Play,
78
Sparkles,
89
Target,
10+
User,
911
} from "lucide-react";
1012
import siteData from "@/data/site-data.json";
1113
import type { SiteData } from "@/types";
@@ -35,8 +37,16 @@ export default function Hero() {
3537
</h1>
3638
<p className="mt-6 text-lg leading-relaxed text-slate-400">
3739
{data.config.description} Curated by{" "}
38-
<span className="text-slate-200">{data.config.author}</span>
39-
videos, problem sheets, articles, and everything you need to level up.
40+
<a
41+
href={data.config.links.portfolio}
42+
target="_blank"
43+
rel="noopener noreferrer"
44+
className="inline-flex items-center gap-1 font-medium text-slate-200 transition-colors hover:text-accent-light"
45+
>
46+
{data.config.author}
47+
<ExternalLink className="h-3.5 w-3.5 opacity-70" />
48+
</a>{" "}
49+
— videos, problem sheets, articles, and everything you need to level up.
4050
</p>
4151
<div className="mt-8 flex flex-wrap gap-4">
4252
<a href="#youtube-sessions" className="btn-primary">
@@ -50,6 +60,15 @@ export default function Hero() {
5060
<Link to="/youtube" className="btn-ghost">
5161
All Videos
5262
</Link>
63+
<a
64+
href={data.config.links.portfolio}
65+
target="_blank"
66+
rel="noopener noreferrer"
67+
className="btn-ghost"
68+
>
69+
<User className="h-4 w-4" />
70+
About Me
71+
</a>
5372
</div>
5473
</div>
5574

website/src/components/Layout.tsx

Lines changed: 124 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { Link, Outlet, useLocation } from "react-router-dom";
22
import {
3+
BookOpen,
4+
ExternalLink,
5+
FileCode2,
36
Github,
47
Menu,
8+
User,
59
X,
610
Youtube,
7-
ExternalLink,
811
} from "lucide-react";
912
import { useState } from "react";
1013
import ScrollRestoration from "@/components/ScrollRestoration";
@@ -14,9 +17,14 @@ import type { SiteData } from "@/types";
1417
const data = siteData as SiteData;
1518

1619
const NAV = [
17-
{ to: "/", label: "Sessions" },
18-
{ to: "/youtube", label: "YouTube" },
19-
];
20+
{ to: "/", label: "Sessions", icon: BookOpen },
21+
{ to: "/youtube", label: "YouTube", icon: Youtube },
22+
] as const;
23+
24+
const EXTERNAL_LINKS = [
25+
{ href: data.config.links.portfolio, label: "About Me", icon: User },
26+
{ href: data.config.links.templates, label: "Templates", icon: FileCode2 },
27+
] as const;
2028

2129
export default function Layout() {
2230
const [mobileOpen, setMobileOpen] = useState(false);
@@ -47,28 +55,33 @@ export default function Layout() {
4755
</Link>
4856

4957
<nav className="hidden items-center gap-1 md:flex">
50-
{NAV.map(({ to, label }) => (
58+
{NAV.map(({ to, label, icon: Icon }) => (
5159
<Link
5260
key={to}
5361
to={to}
54-
className={`rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
62+
className={`inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
5563
pathname === to
5664
? "bg-accent/20 text-accent-light"
5765
: "text-slate-300 hover:bg-white/5 hover:text-white"
5866
}`}
5967
>
68+
<Icon className="h-4 w-4" />
6069
{label}
6170
</Link>
6271
))}
63-
<a
64-
href={data.config.links.templates}
65-
target="_blank"
66-
rel="noopener noreferrer"
67-
className="ml-2 btn-ghost !py-2 !text-xs"
68-
>
69-
Templates
70-
<ExternalLink className="h-3.5 w-3.5" />
71-
</a>
72+
{EXTERNAL_LINKS.map(({ href, label, icon: Icon }) => (
73+
<a
74+
key={href}
75+
href={href}
76+
target="_blank"
77+
rel="noopener noreferrer"
78+
className="ml-1 inline-flex items-center gap-2 btn-ghost !py-2 !text-xs"
79+
>
80+
<Icon className="h-3.5 w-3.5" />
81+
{label}
82+
<ExternalLink className="h-3 w-3 opacity-60" />
83+
</a>
84+
))}
7285
</nav>
7386

7487
<button
@@ -83,16 +96,30 @@ export default function Layout() {
8396

8497
{mobileOpen && (
8598
<nav className="border-t border-white/5 px-4 py-4 md:hidden">
86-
{NAV.map(({ to, label }) => (
99+
{NAV.map(({ to, label, icon: Icon }) => (
87100
<Link
88101
key={to}
89102
to={to}
90103
onClick={() => setMobileOpen(false)}
91-
className="block rounded-lg px-4 py-3 text-sm font-medium text-slate-200 hover:bg-white/5"
104+
className="flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium text-slate-200 hover:bg-white/5"
92105
>
106+
<Icon className="h-4 w-4 text-slate-400" />
93107
{label}
94108
</Link>
95109
))}
110+
{EXTERNAL_LINKS.map(({ href, label, icon: Icon }) => (
111+
<a
112+
key={href}
113+
href={href}
114+
target="_blank"
115+
rel="noopener noreferrer"
116+
onClick={() => setMobileOpen(false)}
117+
className="flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium text-slate-200 hover:bg-white/5"
118+
>
119+
<Icon className="h-4 w-4 text-slate-400" />
120+
{label}
121+
</a>
122+
))}
96123
</nav>
97124
)}
98125
</header>
@@ -111,40 +138,64 @@ export default function Layout() {
111138
<p className="mt-2 text-sm text-slate-400">
112139
{data.config.description}
113140
</p>
141+
<a
142+
href={data.config.links.portfolio}
143+
target="_blank"
144+
rel="noopener noreferrer"
145+
className="mt-4 inline-flex items-center gap-2 text-sm font-medium text-accent-light transition-colors hover:text-white"
146+
>
147+
<User className="h-4 w-4" />
148+
View my portfolio
149+
<ExternalLink className="h-3.5 w-3.5 opacity-70" />
150+
</a>
114151
</div>
115152
<div>
116153
<h4 className="text-sm font-semibold uppercase tracking-wider text-slate-500">
117154
Resources
118155
</h4>
119156
<ul className="mt-3 space-y-2 text-sm">
120-
<FooterLink href={data.config.links.juniorSheet} label="Junior Sheet" />
121-
<FooterLink href={data.config.links.assiutSheet} label="Assiut Materials" />
122-
<FooterLink href={data.config.links.arabicCpChannel} label="Arabic CP Channel" />
157+
<FooterLink
158+
href={data.config.links.templates}
159+
label="CP Templates"
160+
icon={FileCode2}
161+
/>
162+
<FooterLink
163+
href={data.config.links.juniorSheet}
164+
label="Junior Sheet"
165+
/>
166+
<FooterLink
167+
href={data.config.links.assiutSheet}
168+
label="Assiut Materials"
169+
/>
170+
<FooterLink
171+
href={data.config.links.arabicCpChannel}
172+
label="Arabic CP Channel"
173+
/>
123174
</ul>
124175
</div>
125176
<div>
126177
<h4 className="text-sm font-semibold uppercase tracking-wider text-slate-500">
127178
Connect
128179
</h4>
129-
<div className="mt-3 flex gap-3">
130-
<a
180+
<div className="mt-3 flex flex-wrap gap-3">
181+
<SocialLink
182+
href={data.config.links.portfolio}
183+
label="Portfolio"
184+
icon={User}
185+
className="text-accent-light"
186+
/>
187+
<SocialLink
131188
href={data.config.youtubeChannel}
132-
target="_blank"
133-
rel="noopener noreferrer"
134-
className="glass-hover rounded-xl p-3 text-red-400"
135-
aria-label="YouTube"
136-
>
137-
<Youtube className="h-5 w-5" />
138-
</a>
139-
<a
189+
label="YouTube"
190+
icon={Youtube}
191+
className="text-red-400"
192+
/>
193+
<SocialLink
140194
href={data.config.links.github}
141-
target="_blank"
142-
rel="noopener noreferrer"
143-
className="glass-hover rounded-xl p-3 text-slate-300"
144-
aria-label="GitHub"
145-
>
146-
<Github className="h-5 w-5" />
147-
</a>
195+
label="GitHub"
196+
icon={Github}
197+
className="text-slate-300"
198+
/>
148199
</div>
149200
</div>
150201
</div>
@@ -163,17 +214,51 @@ export default function Layout() {
163214
);
164215
}
165216

166-
function FooterLink({ href, label }: { href: string; label: string }) {
217+
function FooterLink({
218+
href,
219+
label,
220+
icon: Icon,
221+
}: {
222+
href: string;
223+
label: string;
224+
icon?: typeof User;
225+
}) {
167226
return (
168227
<li>
169228
<a
170229
href={href}
171230
target="_blank"
172231
rel="noopener noreferrer"
173-
className="text-slate-400 transition-colors hover:text-accent-light"
232+
className="inline-flex items-center gap-2 text-slate-400 transition-colors hover:text-accent-light"
174233
>
234+
{Icon && <Icon className="h-3.5 w-3.5 shrink-0" />}
175235
{label}
176236
</a>
177237
</li>
178238
);
179239
}
240+
241+
function SocialLink({
242+
href,
243+
label,
244+
icon: Icon,
245+
className,
246+
}: {
247+
href: string;
248+
label: string;
249+
icon: typeof User;
250+
className?: string;
251+
}) {
252+
return (
253+
<a
254+
href={href}
255+
target="_blank"
256+
rel="noopener noreferrer"
257+
className={`glass-hover rounded-xl p-3 ${className ?? "text-slate-300"}`}
258+
aria-label={label}
259+
title={label}
260+
>
261+
<Icon className="h-5 w-5" />
262+
</a>
263+
);
264+
}

website/src/components/SessionCard.tsx

Lines changed: 7 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import { Link } from "react-router-dom";
2-
import { ArrowRight, Code2, FileText, Play } from "lucide-react";
2+
import { ArrowRight, FileText, Play } from "lucide-react";
33
import type { Session } from "@/types";
4-
import { CATEGORY_META } from "@/types";
54
import PracticeStats from "@/components/PracticeStats";
6-
import { getSessionThumbnail } from "@/lib/utils";
5+
import SessionCardCover from "@/components/SessionCardCover";
76

87
interface SessionCardProps {
98
session: Session;
109
}
1110

1211
export default function SessionCard({ session }: SessionCardProps) {
13-
const category = CATEGORY_META[session.category];
14-
const thumbnail = getSessionThumbnail(session);
1512
const hasYouTube =
1613
session.youtubeChannelVideos.length > 0 ||
1714
session.sessionRecordings.some((v) => v.type === "youtube");
@@ -25,51 +22,15 @@ export default function SessionCard({ session }: SessionCardProps) {
2522
to={`/sessions/${session.id}`}
2623
className="group glass-hover flex flex-col overflow-hidden rounded-2xl"
2724
>
28-
<div className="relative h-36 overflow-hidden">
29-
{thumbnail ? (
30-
<>
31-
<img
32-
src={thumbnail}
33-
alt=""
34-
loading="lazy"
35-
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
36-
/>
37-
<div className="absolute inset-0 bg-gradient-to-t from-surface-raised via-surface-raised/20 to-transparent" />
38-
</>
39-
) : (
40-
<div
41-
className={`flex h-full items-center justify-center bg-gradient-to-br ${category.color}`}
42-
>
43-
<div className="absolute inset-0 bg-grid-pattern bg-grid opacity-20" aria-hidden />
44-
<Code2 className="relative h-14 w-14 text-white/90 drop-shadow-lg" strokeWidth={1.5} />
45-
</div>
46-
)}
47-
<span
48-
className={`absolute left-4 top-4 inline-flex rounded-full bg-gradient-to-r ${category.color} px-3 py-1 text-xs font-semibold text-white shadow-lg`}
49-
>
50-
{category.label}
51-
</span>
52-
{hasYouTube && (
53-
<span className="absolute right-4 top-4 inline-flex items-center gap-1 rounded-full bg-black/50 px-2.5 py-1 text-[10px] font-medium text-white backdrop-blur-sm">
54-
<Play className="h-3 w-3 fill-current" />
55-
Video
56-
</span>
57-
)}
58-
</div>
25+
<SessionCardCover
26+
session={session}
27+
hasYouTube={hasYouTube}
28+
hasDrive={hasDrive}
29+
/>
5930

6031
<div className="flex flex-1 flex-col p-6">
6132
<div className="mb-3 flex flex-wrap gap-1.5">
6233
<PracticeStats stats={session.stats} variant="badges" />
63-
{hasDrive && (
64-
<span className="rounded-md bg-blue-500/10 px-2 py-0.5 text-[10px] font-medium text-blue-400">
65-
Drive
66-
</span>
67-
)}
68-
{session.folder.includes("ICPC SCU") && (
69-
<span className="rounded-md bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-400">
70-
ICPC SCU
71-
</span>
72-
)}
7334
{partCount > 1 && (
7435
<span className="rounded-md bg-violet-500/10 px-2 py-0.5 text-[10px] font-medium text-violet-400">
7536
{partCount} parts

0 commit comments

Comments
 (0)