Skip to content

Commit ae6df9a

Browse files
Gallery section added
1 parent 12c2a99 commit ae6df9a

10 files changed

Lines changed: 215 additions & 9 deletions

app/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Footer } from "@/components/footer"
66
import { HeroSection } from "@/components/hero-section"
77
import { Navbar } from "@/components/navbar"
88
import { ProjectsSection } from "@/components/projects-section"
9+
import { GallerySection } from "@/components/gallery-section"
910
import { SkillsSection } from "@/components/skills-section"
1011

1112
export default function Home() {
@@ -17,8 +18,9 @@ export default function Home() {
1718
<AboutSection />
1819
<ExperienceSection />
1920
<ProjectsSection />
20-
<SkillsSection />
2121
<EducationSection />
22+
<SkillsSection />
23+
<GallerySection />
2224
<ContactSection />
2325
</main>
2426
<Footer />

components/about-section.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ export function AboutSection() {
4242
<div className="mt-4 grid gap-8 md:grid-cols-[2fr_1fr]">
4343
<div>
4444
<p className="text-dark-foreground mb-4">
45-
Computer Science student in my final semester at PUCP, focused on software development. I'm motivated to
46-
build efficient solutions with modern technologies like Java, React, SQL, and .NET. Self-taught and
47-
passionate about participating in projects that pose real technical challenges.
45+
Computer Science student in my final semester at PUCP{" "}
46+
<span className="text-dark-accent">
47+
(No. 1 university in Peru for 13 consecutive years, Ranking QS).
48+
</span>{" "}
49+
I'm focused on software development and motivated to build efficient solutions with modern technologies like Java, React, SQL, and .NET. Self-taught and passionate about participating in projects that pose real technical challenges.
4850
</p>
4951
<p className="text-dark-foreground mb-4">
5052
Currently developing my thesis on healthcare systems interoperability, learning about standards like HL7
@@ -57,7 +59,7 @@ export function AboutSection() {
5759
approach.
5860
</p>
5961
<h3 className="text-xl font-semibold mb-2 text-dark-accent">Languages</h3>
60-
<ul className="space-y-1 text-dark-secondary mb-6">
62+
<ul className="list-disc pl-5 space-y-1 text-dark-secondary mb-6">
6163
<li>English: Intermediate-Advanced (Level B2 – First Certificate in English, Cambridge)</li>
6264
<li>Spanish: Native</li>
6365
</ul>
@@ -111,7 +113,7 @@ export function AboutSection() {
111113
<a href="mailto:johan.amador@pucp.edu.pe" className="text-dark-accent hover:underline">
112114
johan.amador@pucp.edu.pe
113115
</a>
114-
</li>
116+
</li>
115117
</ul>
116118
</div>
117119
</div>

components/contact-section.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export function ContactSection() {
9090
</div>
9191
</div>
9292
<div className="bg-dark-background p-6 rounded-lg border border-dark-border">
93-
<h3 className="text-xl font-semibold mb-4">Send me a message</h3>
93+
<h3 className="text-xl font-semibold mb-2">Send me a message</h3>
9494
<form className="space-y-4">
9595
<div>
9696
<input

components/gallery-section.tsx

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
"use client"
2+
3+
import { useEffect, useRef, useState } from "react"
4+
5+
const images = [
6+
{
7+
src: "./gallery/conectaton-ips-2025.jpg",
8+
alt: "Conectatón IPS Perú 2025",
9+
description: "At IPS Perú 2025 with teammates, representing Hospital Santa Clotilde & university.",
10+
},
11+
{
12+
src: "./gallery/hl7-peru-reunion.png",
13+
alt: "Meeting with HL7 Peru members",
14+
description: "SIH SALUS team meeting with HL7 Perú members.",
15+
},
16+
{
17+
src: "./gallery/health-minister.jpg",
18+
alt: "With Dr. César Vásquez (Minister of Health) and José Pérez Lu (General Director of IT, MINSA)",
19+
description: "With Dr. César Vásquez, Minister of Health, and José Pérez Lu, General Director of IT at MINSA.",
20+
},
21+
{
22+
src: "./gallery/diresa-huanuco-sanmartin.jpg",
23+
alt: "With DIRESA Huánuco and San Martín members",
24+
description: "With members of DIRESA Huánuco and San Martín during a regional health digitalization meeting.",
25+
},
26+
]
27+
28+
const VISIBLE_COUNT_DESKTOP = 3
29+
const VISIBLE_COUNT_MOBILE = 1
30+
const IMAGE_RATIO = "aspect-[16/9]" // Para fotos horizontales
31+
32+
const getMobile = () => typeof window !== 'undefined' && window.innerWidth < 768;
33+
34+
export function GallerySection() {
35+
const sectionRef = useRef<HTMLElement>(null)
36+
const [start, setStart] = useState(0)
37+
const [isHovered, setIsHovered] = useState(false)
38+
const [visibleCount, setVisibleCount] = useState(VISIBLE_COUNT_DESKTOP)
39+
const [isMobile, setIsMobile] = useState(getMobile());
40+
41+
useEffect(() => {
42+
const observer = new IntersectionObserver(
43+
(entries) => {
44+
entries.forEach((entry) => {
45+
if (entry.isIntersecting) {
46+
entry.target.classList.add("is-visible")
47+
}
48+
})
49+
},
50+
{ threshold: 0.1 },
51+
)
52+
const section = sectionRef.current
53+
if (section) {
54+
observer.observe(section)
55+
}
56+
return () => {
57+
if (section) {
58+
observer.unobserve(section)
59+
}
60+
}
61+
}, [])
62+
63+
useEffect(() => {
64+
if (isHovered) return
65+
const interval = setInterval(() => {
66+
setStart((prev) => (prev + 1) % images.length)
67+
}, 3500)
68+
return () => clearInterval(interval)
69+
}, [isHovered])
70+
71+
useEffect(() => {
72+
function handleResize() {
73+
setIsMobile(getMobile());
74+
setVisibleCount(getMobile() ? VISIBLE_COUNT_MOBILE : VISIBLE_COUNT_DESKTOP);
75+
}
76+
handleResize();
77+
window.addEventListener("resize", handleResize);
78+
return () => window.removeEventListener("resize", handleResize);
79+
}, [])
80+
81+
// Calcula las imágenes visibles en el carrusel
82+
const visibleImages = Array.from({ length: visibleCount }, (_, i) => {
83+
return images[(start + i) % images.length]
84+
})
85+
86+
// Efecto de desplazamiento natural
87+
const trackRef = useRef<HTMLDivElement>(null)
88+
useEffect(() => {
89+
if (!trackRef.current) return
90+
trackRef.current.style.transition =
91+
"transform 0.8s cubic-bezier(0.4,0,0.2,1)"
92+
trackRef.current.style.transform = `translateX(-${(100 / visibleCount) * start
93+
}%)`
94+
}, [start, visibleCount])
95+
96+
return (
97+
<section
98+
id="gallery"
99+
ref={sectionRef}
100+
className="py-12 md:py-20 fade-in-section"
101+
>
102+
<div className="container px-2 md:px-6">
103+
<div className="mx-auto max-w-[58rem]">
104+
<h2 className="text-3xl font-bold leading-tight tracking-tighter md:text-4xl mb-8">
105+
<span className="text-dark-accent">#</span> Gallery
106+
</h2>
107+
<div
108+
className={`mb-8 relative flex-1 flex items-center justify-center overflow-hidden ${isMobile ? 'w-full' : 'w-screen left-1/2 right-1/2 -mx-[50vw]'} px-0`}
109+
style={{ maxWidth: "100vw" }}
110+
onMouseEnter={() => setIsHovered(true)}
111+
onMouseLeave={() => setIsHovered(false)}
112+
>
113+
{isMobile ? (
114+
<div
115+
ref={trackRef}
116+
className="flex"
117+
style={{
118+
width: "100%",
119+
minHeight: "56vw",
120+
maxHeight: "70vh",
121+
transition: "transform 0.8s cubic-bezier(0.4,0,0.2,1)",
122+
}}
123+
>
124+
<div
125+
className="flex flex-col items-center justify-start w-full"
126+
style={{ minWidth: "100%", maxWidth: "100%", height: "auto", maxHeight: "none" }}
127+
>
128+
<div className="w-full h-0 pb-[56.25%] relative flex items-center justify-center bg-dark-surface rounded-2xl overflow-hidden shadow-lg">
129+
<img
130+
src={images[start].src}
131+
alt={images[start].alt}
132+
className="border border-dark-border absolute top-0 left-0 w-full h-full object-cover select-none"
133+
draggable={false}
134+
style={{ objectFit: "cover", width: "100%", height: "100%", display: "block" }}
135+
/>
136+
</div>
137+
<div className="mt-2 w-full break-words text-center text-dark-secondary text-sm md:text-base transition-colors min-h-[2.5rem] flex items-center justify-center whitespace-normal overflow-visible px-2">
138+
<span style={{ wordBreak: 'break-word', overflowWrap: 'break-word', whiteSpace: 'normal', width: '100%' }}>
139+
{images[start].description}
140+
</span>
141+
</div>
142+
</div>
143+
</div>
144+
) : (
145+
<div
146+
ref={trackRef}
147+
className="flex gap-2 md:gap-8"
148+
style={{
149+
width: `${(images.length / visibleCount) * 100}%`,
150+
minHeight: "21vw",
151+
maxHeight: "70vh",
152+
transition: "transform 0.8s cubic-bezier(0.4,0,0.2,1)",
153+
}}
154+
>
155+
{images.concat(images.slice(0, visibleCount)).map((img, idx) => (
156+
<div
157+
key={img.src + idx}
158+
className="flex flex-col items-center justify-start w-full"
159+
style={{
160+
minWidth: `calc(100vw / ${visibleCount})`,
161+
maxWidth: `calc(100vw / ${visibleCount})`,
162+
height: "auto",
163+
maxHeight: "none",
164+
}}
165+
>
166+
<div className="w-full h-0 pb-[56.25%] relative flex items-center justify-center bg-dark-surface rounded-2xl overflow-hidden shadow-lg">
167+
<img
168+
src={img.src}
169+
alt={img.alt}
170+
className="border border-dark-border absolute top-0 left-0 w-full h-full object-cover select-none"
171+
draggable={false}
172+
style={{ objectFit: "cover", width: "100%", height: "100%", display: "block" }}
173+
/>
174+
</div>
175+
<div className="mt-2 w-full break-words text-center text-dark-secondary text-sm md:text-base transition-colors min-h-[2.5rem] flex items-center justify-center whitespace-normal overflow-visible px-2">
176+
<span style={{ wordBreak: 'break-word', overflowWrap: 'break-word', whiteSpace: 'normal', width: '100%' }}>
177+
{img.description}
178+
</span>
179+
</div>
180+
</div>
181+
))}
182+
</div>
183+
)}
184+
</div>
185+
<div className="flex gap-2 mt-2 justify-center">
186+
{images.map((_, idx) => (
187+
<button
188+
key={idx}
189+
className={`w-3 h-3 rounded-full border border-dark-accent transition-all ${idx === start ? "bg-dark-accent" : "bg-transparent"}`}
190+
onClick={() => setStart(idx)}
191+
aria-label={`Ver imagen ${idx + 1}`}
192+
tabIndex={0}
193+
style={isMobile ? { minWidth: 12, minHeight: 12 } : {}}
194+
/>
195+
))}
196+
</div>
197+
</div>
198+
</div>
199+
</section>
200+
)
201+
}

components/navbar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ const navItems = [
99
{ name: "About", href: "#about" },
1010
{ name: "Experience", href: "#experience" },
1111
{ name: "Projects", href: "#projects" },
12-
{ name: "Skills", href: "#skills" },
1312
{ name: "Education", href: "#education" },
13+
{ name: "Skills", href: "#skills" },
14+
{ name: "Gallery", href: "#gallery" },
1415
{ name: "Contact", href: "#contact" },
1516
]
1617

components/projects-section.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function ProjectsSection() {
6363
}, [])
6464

6565
return (
66-
<section id="projects" ref={sectionRef} className="py-12 md:py-20 fade-in-section">
66+
<section id="projects" ref={sectionRef} className="py-12 md:py-20 bg-dark-surface/50 fade-in-section">
6767
<div className="container px-4 md:px-6">
6868
<div className="mx-auto max-w-[72rem]">
6969
<h2 className="text-3xl font-bold leading-tight tracking-tighter md:text-4xl mb-8">
98.4 KB
Loading
118 KB
Loading

public/gallery/health-minister.jpg

130 KB
Loading
927 KB
Loading

0 commit comments

Comments
 (0)