Skip to content

Commit f03cfd9

Browse files
committed
feat: add markdown rendered pages
Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>
1 parent 8d9f9b1 commit f03cfd9

37 files changed

+1559
-68
lines changed

bun.lockb

52 KB
Binary file not shown.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"build": "next build",
88
"start": "next start",
99
"prod": "next build && next start",
10+
"format": "biome format src --write",
1011
"lint": "biome lint src"
1112
},
1213
"dependencies": {
@@ -17,10 +18,11 @@
1718
"@radix-ui/react-slot": "^1.1.1",
1819
"class-variance-authority": "^0.7.1",
1920
"clsx": "^2.1.1",
20-
"framer-motion": "^11.16.1",
21+
"framer-motion": "^11.17.0",
2122
"lucide-react": "^0.469.0",
22-
"motion": "^11.16.1",
23+
"motion": "^11.17.0",
2324
"next": "15.1.4",
25+
"next-mdx-remote": "^5.0.0",
2426
"react": "19.0.0",
2527
"react-dom": "19.0.0",
2628
"tailwind-merge": "^2.6.0",

public/team/kelvin.jpg

335 KB
Loading

public/team/monica.jpg

259 KB
Loading

public/team/prisca.jpg

488 KB
Loading

public/team/rodney.jpg

432 KB
Loading

public/team/stephany.jpg

1.51 MB
Loading

public/team/team.jpg

1.33 MB
Loading
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export default function HeroSection() {
2+
return (
3+
<div className="relative overflow-hidden py-24 lg:py-32">
4+
<div className="container mx-auto max-w-7xl px-8">
5+
<div className="max-w-2xl text-center mx-auto">
6+
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
7+
Welcome to PyCon Kenya 2025
8+
</h1>
9+
<p className="my-6 text-xl text-muted-foreground">
10+
Africa's largest Python conference, held in Nairobi, Kenya. Join us
11+
for a weekend of inspiring talks, informative workshops, and fun
12+
networking events.
13+
</p>
14+
</div>
15+
<div className="mt-10 relative max-w-7xl mx-auto">
16+
<img
17+
src="/gallery/DSC_0004-4.jpg"
18+
className="rounded-xl"
19+
alt="PyConKE 2022"
20+
/>
21+
<div className="absolute bottom-12 -start-20 -z-[1] w-48 h-48 bg-gradient-to-b from-primary-foreground via-primary-foreground to-background p-px rounded-lg">
22+
<div className="w-48 h-48 rounded-lg bg-background/10" />
23+
</div>
24+
<div className="absolute -top-12 -end-20 -z-[1] w-48 h-48 bg-gradient-to-t from-primary-foreground via-primary-foreground to-background p-px rounded-full">
25+
<div className="w-48 h-48 rounded-full bg-background/10" />
26+
</div>
27+
</div>
28+
</div>
29+
</div>
30+
);
31+
}
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
"use client";
2+
import Image from "next/image";
3+
import React, {
4+
type RefObject,
5+
useEffect,
6+
useId,
7+
useRef,
8+
useState,
9+
} from "react";
10+
import { AnimatePresence, motion } from "framer-motion";
11+
import { useOutsideClick } from "@/hooks/use-outside-click";
12+
import { X } from "lucide-react";
13+
14+
const team = [
15+
{
16+
description: "Kelvin Ndemo",
17+
id: "kelvin",
18+
title: "Chairman",
19+
src: "/team/kelvin.jpg",
20+
ctaText: "Visit",
21+
ctaLink: "#",
22+
content: () => {
23+
return (
24+
<p>
25+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
26+
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
27+
minim veniam, quis nostrud exercitation ullamco laboris nisi ut
28+
aliquip ex ea commodo consequat.
29+
<br /> <br /> Duis aute irure dolor in reprehenderit in voluptate
30+
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
31+
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
32+
mollit anim id est laborum.
33+
</p>
34+
);
35+
},
36+
},
37+
{
38+
description: "Monica Oyugi",
39+
id: "monica",
40+
title: "Co-Chair",
41+
src: "/team/monica.jpg",
42+
ctaText: "Visit",
43+
ctaLink: "#",
44+
content: () => {
45+
return (
46+
<p>
47+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
48+
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
49+
minim veniam, quis nostrud exercitation ullamco laboris nisi ut
50+
aliquip ex ea commodo consequat.
51+
<br /> <br /> Duis aute irure dolor in reprehenderit in voluptate
52+
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
53+
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
54+
mollit anim id est laborum.
55+
</p>
56+
);
57+
},
58+
},
59+
60+
{
61+
description: "Stephany Owino",
62+
id: "stephany",
63+
title: "Event Manager",
64+
src: "/team/stephany.jpg",
65+
ctaText: "Visit",
66+
ctaLink: "#",
67+
content: () => {
68+
return (
69+
<p>
70+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
71+
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
72+
minim veniam, quis nostrud exercitation ullamco laboris nisi ut
73+
aliquip ex ea commodo consequat.
74+
<br /> <br /> Duis aute irure dolor in reprehenderit in voluptate
75+
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
76+
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
77+
mollit anim id est laborum.
78+
</p>
79+
);
80+
},
81+
},
82+
{
83+
description: "Prisca Akinyi",
84+
id: "prisca",
85+
title: "Co - Event Manager",
86+
src: "/team/prisca.jpg",
87+
ctaText: "Visit",
88+
ctaLink: "#",
89+
content: () => {
90+
return (
91+
<p>
92+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
93+
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
94+
minim veniam, quis nostrud exercitation ullamco laboris nisi ut
95+
aliquip ex ea commodo consequat.
96+
<br /> <br /> Duis aute irure dolor in reprehenderit in voluptate
97+
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
98+
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
99+
mollit anim id est laborum.
100+
</p>
101+
);
102+
},
103+
},
104+
{
105+
description: "Rodney Osodo",
106+
id: "rodney",
107+
title: "Program Director",
108+
src: "/team/rodney.jpg",
109+
ctaText: "Visit",
110+
ctaLink: "#",
111+
content: () => {
112+
return (
113+
<p>
114+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
115+
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
116+
minim veniam, quis nostrud exercitation ullamco laboris nisi ut
117+
aliquip ex ea commodo consequat.
118+
<br /> <br /> Duis aute irure dolor in reprehenderit in voluptate
119+
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
120+
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
121+
mollit anim id est laborum.
122+
</p>
123+
);
124+
},
125+
},
126+
];
127+
128+
export function OrganisingTeam() {
129+
const [active, setActive] = useState<(typeof team)[number] | boolean | null>(
130+
null,
131+
);
132+
const id = useId();
133+
const ref = useRef<HTMLDivElement>(null);
134+
135+
useEffect(() => {
136+
function onKeyDown(event: KeyboardEvent) {
137+
if (event.key === "Escape") {
138+
setActive(false);
139+
}
140+
}
141+
142+
if (active && typeof active === "object") {
143+
document.body.style.overflow = "hidden";
144+
} else {
145+
document.body.style.overflow = "auto";
146+
}
147+
148+
window.addEventListener("keydown", onKeyDown);
149+
return () => window.removeEventListener("keydown", onKeyDown);
150+
}, [active]);
151+
152+
useOutsideClick(ref as RefObject<HTMLDivElement>, () => setActive(null));
153+
154+
return (
155+
<div className="border-t py-12 lg:py-20 relative overflow-hidden">
156+
<AnimatePresence>
157+
{active && typeof active === "object" && (
158+
<motion.div
159+
initial={{ opacity: 0 }}
160+
animate={{ opacity: 1 }}
161+
exit={{ opacity: 0 }}
162+
className="fixed inset-0 bg-black/20 h-full w-full z-10"
163+
/>
164+
)}
165+
</AnimatePresence>
166+
<AnimatePresence>
167+
{active && typeof active === "object" ? (
168+
<div className="fixed inset-0 grid place-items-center z-[100]">
169+
<motion.button
170+
key={`button-${active.title}-${id}`}
171+
layout={true}
172+
initial={{
173+
opacity: 0,
174+
}}
175+
animate={{
176+
opacity: 1,
177+
}}
178+
exit={{
179+
opacity: 0,
180+
transition: {
181+
duration: 0.05,
182+
},
183+
}}
184+
className="flex absolute top-2 right-2 lg:hidden items-center justify-center bg-white rounded-full h-6 w-6"
185+
onClick={() => setActive(null)}
186+
>
187+
<X />
188+
</motion.button>
189+
<motion.div
190+
layoutId={`card-${active.title}-${id}`}
191+
ref={ref}
192+
className="w-full max-w-[500px] h-full md:h-fit md:max-h-[90%] flex flex-col bg-white dark:bg-neutral-900 sm:rounded-3xl overflow-hidden"
193+
>
194+
<motion.div layoutId={`image-${active.title}-${id}`}>
195+
<Image
196+
priority={true}
197+
width={1000}
198+
height={1000}
199+
src={active.src}
200+
alt={active.title}
201+
className="w-full h-80 lg:h-80 sm:rounded-tr-lg sm:rounded-tl-lg object-cover object-top"
202+
/>
203+
</motion.div>
204+
205+
<div>
206+
<div className="flex justify-between items-start p-4">
207+
<div className="">
208+
<motion.h3
209+
layoutId={`title-${active.title}-${id}`}
210+
className="font-medium text-neutral-700 dark:text-neutral-200 text-base"
211+
>
212+
{active.title}
213+
</motion.h3>
214+
<motion.p
215+
layoutId={`description-${active.description}-${id}`}
216+
className="text-neutral-600 dark:text-neutral-400 text-base"
217+
>
218+
{active.description}
219+
</motion.p>
220+
</div>
221+
222+
<motion.a
223+
layout={true}
224+
initial={{ opacity: 0 }}
225+
animate={{ opacity: 1 }}
226+
exit={{ opacity: 0 }}
227+
href={active.ctaLink}
228+
target="_blank"
229+
rel="noreferrer"
230+
className="px-4 py-3 text-sm rounded-full font-bold bg-green-500 text-white"
231+
>
232+
{active.ctaText}
233+
</motion.a>
234+
</div>
235+
<div className="pt-4 relative px-4">
236+
<motion.div
237+
layout={true}
238+
initial={{ opacity: 0 }}
239+
animate={{ opacity: 1 }}
240+
exit={{ opacity: 0 }}
241+
className="text-neutral-600 text-xs md:text-sm lg:text-base h-40 md:h-fit pb-10 flex flex-col items-start gap-4 overflow-auto dark:text-neutral-400 [mask:linear-gradient(to_bottom,white,white,transparent)] [scrollbar-width:none] [-ms-overflow-style:none] [-webkit-overflow-scrolling:touch]"
242+
>
243+
{typeof active.content === "function"
244+
? active.content()
245+
: active.content}
246+
</motion.div>
247+
</div>
248+
</div>
249+
</motion.div>
250+
</div>
251+
) : null}
252+
</AnimatePresence>
253+
<div className="max-w-2xl mx-auto w-full">
254+
<h2 className="text-3xl font-bold lg:text-4xl pb-10">Team</h2>
255+
<p className="mt-3 text-muted-foreground">
256+
The PyCon KE team is made up of a diverse group of individuals with a
257+
wide range of experiences and skills. We are committed to providing a
258+
supportive and inclusive environment for all attendees, speakers,
259+
sponsors, and volunteers.
260+
</p>
261+
</div>
262+
<div className="my-10 relative max-w-7xl mx-auto">
263+
<img src="/team/team.jpg" className="rounded-xl" alt="PyConKE 2022" />
264+
</div>
265+
<ul className="max-w-2xl mx-auto w-full grid grid-cols-1 md:grid-cols-2 items-start gap-4">
266+
{team.map((card) => (
267+
<motion.div
268+
layoutId={`card-${card.title}-${id}`}
269+
key={card.title}
270+
onClick={() => setActive(card)}
271+
className="p-4 flex flex-col hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-xl cursor-pointer"
272+
>
273+
<div className="flex gap-4 flex-col w-full">
274+
<motion.div layoutId={`image-${card.title}-${id}`}>
275+
<Image
276+
width={1000}
277+
height={1000}
278+
src={card.src}
279+
alt={card.title}
280+
className="h-60 w-full rounded-lg object-cover object-top"
281+
/>
282+
</motion.div>
283+
<div className="flex justify-center items-center flex-col">
284+
<motion.h3
285+
layoutId={`title-${card.title}-${id}`}
286+
className="font-medium text-neutral-800 dark:text-neutral-200 text-center md:text-left text-base"
287+
>
288+
{card.title}
289+
</motion.h3>
290+
<motion.p
291+
layoutId={`description-${card.description}-${id}`}
292+
className="text-neutral-600 dark:text-neutral-400 text-center md:text-left text-base"
293+
>
294+
{card.description}
295+
</motion.p>
296+
</div>
297+
</div>
298+
</motion.div>
299+
))}
300+
</ul>
301+
</div>
302+
);
303+
}

0 commit comments

Comments
 (0)