Skip to content

Commit 332fb05

Browse files
Pabl0ckscarletextechnophile-04
authored
Add 2025 recap page (#25)
Co-authored-by: Carlos Sánchez <oceanrdn@gmail.com> Co-authored-by: Shiv Bhonde <shivbhonde04@gmail.com>
1 parent 5edf596 commit 332fb05

File tree

17 files changed

+1278
-9
lines changed

17 files changed

+1278
-9
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { SLIDES, SlideId } from "./constants";
2+
3+
interface SideNavProps {
4+
activeSlide: string;
5+
onNavigate: (slideId: SlideId) => void;
6+
}
7+
8+
export const SideNav = ({ activeSlide, onNavigate }: SideNavProps) => (
9+
<nav className="fixed right-8 top-1/2 -translate-y-1/2 z-50 hidden lg:flex flex-col gap-3 bg-black/90 backdrop-blur-sm px-4 py-3 rounded-lg border border-neutral-content/10">
10+
{SLIDES.map(slide => (
11+
<button
12+
key={slide.id}
13+
onClick={() => onNavigate(slide.id)}
14+
className={`flex items-center gap-3 transition-all duration-200 text-sm ${
15+
activeSlide === slide.id ? "text-primary-content" : "text-neutral-content/40 hover:text-neutral-content/70"
16+
}`}
17+
>
18+
<span
19+
className={`block w-1.5 h-1.5 transition-all duration-200 ${
20+
activeSlide === slide.id ? "bg-primary scale-125" : "bg-neutral-content/30"
21+
}`}
22+
/>
23+
<span className={`transition-all duration-200 ${activeSlide === slide.id ? "font-medium" : ""}`}>
24+
{slide.title}
25+
</span>
26+
</button>
27+
))}
28+
</nav>
29+
);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { ReactNode } from "react";
2+
import { NOISE_TEXTURE_BG } from "./constants";
3+
4+
interface SlideSectionProps {
5+
id: string;
6+
trackName: string;
7+
children: ReactNode;
8+
}
9+
10+
export const SlideSection = ({ id, trackName, children }: SlideSectionProps) => (
11+
<section
12+
id={id}
13+
className="min-h-[80vh] py-24 flex items-start relative overflow-hidden border-t border-neutral-content/10 first:border-t-0"
14+
>
15+
{/* Background texture */}
16+
<div className="absolute inset-0 opacity-[0.14]">
17+
<div
18+
className="absolute inset-0"
19+
style={{
20+
backgroundImage: NOISE_TEXTURE_BG,
21+
}}
22+
/>
23+
</div>
24+
25+
<div className="container mx-auto px-6 lg:px-12 relative z-10">
26+
{/* Section indicator */}
27+
<div className="flex items-center gap-4 mb-8">
28+
<span className="text-sm text-white/80 uppercase tracking-[0.3em]">{trackName}</span>
29+
<span className="w-48 lg:w-64 h-px bg-neutral-content/20" />
30+
</div>
31+
32+
{children}
33+
</div>
34+
</section>
35+
);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
interface StatCardProps {
2+
value: string;
3+
label: string;
4+
growth?: string;
5+
}
6+
7+
export const StatCard = ({ value, label, growth }: StatCardProps) => (
8+
<div className="group">
9+
<div className="border border-neutral-content/20 px-4 py-2 transition-colors duration-200 hover:border-neutral-content/40">
10+
{/* Top border decoration */}
11+
<div className="flex items-center gap-2 text-neutral-content/30 text-xs">
12+
<span></span>
13+
<span className="flex-1 border-t border-neutral-content/20" />
14+
<span></span>
15+
</div>
16+
17+
{/* Content */}
18+
<div className="flex flex-col py-4 items-center text-center">
19+
<span className="text-3xl md:text-4xl font-bold text-primary-content tracking-tight">{value}</span>
20+
{growth && <span className="text-secondary text-sm mt-1">{growth}</span>}
21+
<span className="text-neutral-content/50 text-sm mt-2">{label}</span>
22+
</div>
23+
24+
{/* Bottom border decoration */}
25+
<div className="flex items-center gap-2 text-neutral-content/30 text-xs">
26+
<span></span>
27+
<span className="flex-1 border-t border-neutral-content/20" />
28+
<span></span>
29+
</div>
30+
</div>
31+
</div>
32+
);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ReactNode } from "react";
2+
3+
interface TaskListProps {
4+
tasks: ReactNode[];
5+
}
6+
7+
export const TaskList = ({ tasks }: TaskListProps) => (
8+
<div className="grid gap-2">
9+
{tasks.map((task, index) => (
10+
<div key={index} className="flex items-start gap-3 text-neutral-content/70">
11+
<span className="text-secondary text-xs"></span>
12+
<span className="text-sm leading-relaxed">{task}</span>
13+
</div>
14+
))}
15+
</div>
16+
);
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { ReactNode, useState } from "react";
2+
import { ChevronDownIcon } from "@heroicons/react/24/outline";
3+
4+
interface Tab {
5+
id: string;
6+
title: string;
7+
content: ReactNode;
8+
linkUrl?: string;
9+
linkLabel?: string;
10+
}
11+
12+
interface TerminalWindowProps {
13+
tabs: Tab[];
14+
defaultTab?: string;
15+
}
16+
17+
// Desktop version with tabs
18+
const DesktopTerminal = ({
19+
tabs,
20+
activeTab,
21+
setActiveTab,
22+
}: {
23+
tabs: Tab[];
24+
activeTab: string;
25+
setActiveTab: (id: string) => void;
26+
}) => {
27+
const activeContent = tabs.find(t => t.id === activeTab);
28+
29+
return (
30+
<div className="hidden md:block border border-neutral-600 rounded-lg overflow-hidden">
31+
{/* Title bar */}
32+
<div className="flex items-stretch bg-neutral-700 border-b border-neutral-600">
33+
{/* Window controls (decorative) */}
34+
<div className="flex items-center gap-2 px-3 py-2">
35+
<span className="w-3 h-3 rounded-full bg-red-500" />
36+
<span className="w-3 h-3 rounded-full bg-yellow-500" />
37+
<span className="w-3 h-3 rounded-full bg-green-500" />
38+
</div>
39+
40+
{/* Tab headers */}
41+
{tabs.map(tab => (
42+
<button
43+
key={tab.id}
44+
onClick={() => setActiveTab(tab.id)}
45+
className={`flex-1 min-w-0 flex items-center justify-center px-2 text-xs whitespace-nowrap border-l border-neutral-600 transition-all duration-200 ${
46+
activeTab === tab.id ? "text-white font-medium" : "text-neutral-400 hover:text-neutral-200"
47+
}`}
48+
style={{
49+
backgroundColor: activeTab === tab.id ? "#6b7280" : "transparent",
50+
}}
51+
title={tab.title}
52+
>
53+
{tab.title}
54+
</button>
55+
))}
56+
</div>
57+
58+
{/* Content area */}
59+
<div className="bg-black p-6">
60+
<div className="animate-fadeIn">
61+
{activeContent && (
62+
<div>
63+
<div className="flex items-center gap-3 mb-4">
64+
<div className="flex items-center gap-2 text-sm">
65+
<span className="text-primary">$</span>
66+
<span className="text-primary">cat {activeContent.title.toLowerCase().replace(/\s+/g, "-")}.md</span>
67+
</div>
68+
</div>
69+
{activeContent.content}
70+
{activeContent.linkUrl && (
71+
<div className="mt-6">
72+
<a
73+
href={activeContent.linkUrl}
74+
target="_blank"
75+
rel="noopener noreferrer"
76+
className="text-primary-content hover:text-primary text-sm"
77+
>
78+
{activeContent.linkLabel || "Link"}
79+
</a>
80+
</div>
81+
)}
82+
</div>
83+
)}
84+
</div>
85+
</div>
86+
</div>
87+
);
88+
};
89+
90+
// Mobile version with accordion inside single terminal frame
91+
const MobileTerminal = ({
92+
tabs,
93+
expandedTab,
94+
toggleTab,
95+
}: {
96+
tabs: Tab[];
97+
expandedTab: string | null;
98+
toggleTab: (id: string) => void;
99+
}) => {
100+
return (
101+
<div className="md:hidden border border-neutral-600 rounded-lg overflow-hidden">
102+
{/* Title bar with window controls - only once */}
103+
<div className="flex items-center gap-2 px-3 py-2 bg-neutral-700 border-b border-neutral-600">
104+
<span className="w-3 h-3 rounded-full bg-red-500" />
105+
<span className="w-3 h-3 rounded-full bg-yellow-500" />
106+
<span className="w-3 h-3 rounded-full bg-green-500" />
107+
</div>
108+
109+
{/* Accordion items */}
110+
<div className="bg-black">
111+
{tabs.map((tab, index) => {
112+
const isExpanded = expandedTab === tab.id;
113+
const isLast = index === tabs.length - 1;
114+
return (
115+
<div key={tab.id} className={!isLast ? "border-b border-neutral-700" : ""}>
116+
{/* Accordion header */}
117+
<button
118+
onClick={() => toggleTab(tab.id)}
119+
className="w-full flex items-center justify-between px-4 py-3 hover:bg-neutral-800/50 transition-colors"
120+
>
121+
<span className="text-sm text-white font-medium">{tab.title}</span>
122+
<ChevronDownIcon
123+
className={`w-5 h-5 text-neutral-400 transition-transform duration-200 ${
124+
isExpanded ? "rotate-180" : ""
125+
}`}
126+
/>
127+
</button>
128+
129+
{/* Accordion content */}
130+
<div
131+
className={`overflow-hidden transition-all duration-300 ease-in-out ${
132+
isExpanded ? "max-h-[600px] opacity-100" : "max-h-0 opacity-0"
133+
}`}
134+
>
135+
<div className="px-4 pb-4">
136+
<div className="flex items-center gap-2 text-xs mb-3">
137+
<span className="text-primary">$</span>
138+
<span className="text-primary">cat {tab.title.toLowerCase().replace(/\s+/g, "-")}.md</span>
139+
</div>
140+
{tab.linkUrl && (
141+
<a
142+
href={tab.linkUrl}
143+
target="_blank"
144+
rel="noopener noreferrer"
145+
className="text-primary-content hover:text-primary"
146+
>
147+
{tab.linkLabel || "Link"}
148+
</a>
149+
)}
150+
{tab.content}
151+
</div>
152+
</div>
153+
</div>
154+
);
155+
})}
156+
</div>
157+
</div>
158+
);
159+
};
160+
161+
export const TerminalWindow = ({ tabs, defaultTab }: TerminalWindowProps) => {
162+
const [activeTab, setActiveTab] = useState<string>(defaultTab || tabs[0]?.id);
163+
const [expandedTab, setExpandedTab] = useState<string | null>(defaultTab || tabs[0]?.id);
164+
165+
const toggleTab = (id: string) => {
166+
setExpandedTab(expandedTab === id ? null : id);
167+
};
168+
169+
return (
170+
<>
171+
<DesktopTerminal tabs={tabs} activeTab={activeTab} setActiveTab={setActiveTab} />
172+
<MobileTerminal tabs={tabs} expandedTab={expandedTab} toggleTab={toggleTab} />
173+
</>
174+
);
175+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Slide definitions for navigation
2+
export const SLIDES = [
3+
{
4+
id: "speedrun",
5+
title: "Speedrun Ethereum",
6+
shortTitle: "SRE",
7+
},
8+
{
9+
id: "scaffold-eth",
10+
title: "Scaffold-ETH 2",
11+
shortTitle: "SE2",
12+
},
13+
{
14+
id: "educational",
15+
title: "Educational Initiatives",
16+
shortTitle: "EDU",
17+
},
18+
{
19+
id: "ecosystem",
20+
title: "Ecosystem Collabs",
21+
shortTitle: "ECO",
22+
},
23+
{
24+
id: "misc",
25+
title: "Misc",
26+
shortTitle: "MISC",
27+
},
28+
] as const;
29+
30+
export type SlideId = (typeof SLIDES)[number]["id"];
31+
32+
// Shared noise texture background - extracted to avoid repetition
33+
export const NOISE_TEXTURE_BG = `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E")`;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export { StatCard } from "./StatCard";
2+
export { SideNav } from "./SideNav";
3+
export { TerminalWindow } from "./TerminalWindow";
4+
export { SlideSection } from "./SlideSection";
5+
export { TaskList } from "./TaskList";
6+
export { SLIDES, NOISE_TEXTURE_BG } from "./constants";
7+
export type { SlideId } from "./constants";
8+
9+
// Re-export slides
10+
export * from "./slides";

0 commit comments

Comments
 (0)