Skip to content

Commit 4be7e55

Browse files
Initial commit
0 parents  commit 4be7e55

35 files changed

+6746
-0
lines changed

.eslintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": ["next/core-web-vitals", "next/typescript"]
3+
}

.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
# or
14+
bun dev
15+
```
16+
17+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18+
19+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20+
21+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22+
23+
## Learn More
24+
25+
To learn more about Next.js, take a look at the following resources:
26+
27+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29+
30+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31+
32+
## Deploy on Vercel
33+
34+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35+
36+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

app/components/about-section.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { personalData } from "../data/cv";
2+
3+
export function AboutSection() {
4+
return (
5+
<section id="about" className="py-8 md:py-12 animate-slide-up">
6+
<div className="container mx-auto px-4">
7+
<h2 className="section-title">ABOUT ME</h2>
8+
<div className="bg-card rounded-lg p-6 shadow-sm border border-border">
9+
<p className="text-foreground leading-relaxed">
10+
{personalData.about}
11+
</p>
12+
</div>
13+
</div>
14+
</section>
15+
);
16+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { personalData } from "../data/cv";
2+
3+
export function CertificatesSection() {
4+
return (
5+
<section id="certificates" className="py-8 md:py-12 animate-slide-up">
6+
<div className="container mx-auto px-4">
7+
<h2 className="section-title">CERTIFICATES</h2>
8+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
9+
{personalData.certificates.map((cert, index) => (
10+
<div key={index} className="certificate-item">
11+
<svg
12+
xmlns="http://www.w3.org/2000/svg"
13+
width="20"
14+
height="20"
15+
fill="currentColor"
16+
className="bi bi-award text-primary flex-shrink-0"
17+
viewBox="0 0 16 16"
18+
>
19+
<path d="M9.669.864 8 0 6.331.864l-1.858.282-.842 1.68-1.337 1.32L2.6 6l-.306 1.854 1.337 1.32.842 1.68 1.858.282L8 12l1.669-.864 1.858-.282.842-1.68 1.337-1.32L13.4 6l.306-1.854-1.337-1.32-.842-1.68zm1.196 1.193.684 1.365 1.086 1.072L12.387 6l.248 1.506-1.086 1.072-.684 1.365-1.51.229L8 10.874l-1.355-.702-1.51-.229-.684-1.365-1.086-1.072L3.614 6l-.25-1.506 1.087-1.072.684-1.365 1.51-.229L8 1.126l1.356.702z" />
20+
<path d="M4 11.794V16l4-1 4 1v-4.206l-2.018.306L8 13.126 6.018 12.1z" />
21+
</svg>
22+
<div>
23+
<h3 className="font-medium">{cert.name}</h3>
24+
<p className="text-sm text-muted-foreground">{cert.year}</p>
25+
</div>
26+
</div>
27+
))}
28+
</div>
29+
</div>
30+
</section>
31+
);
32+
}

app/components/download-button.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { personalData } from "../data/cv";
5+
6+
export function DownloadButton() {
7+
const [mounted, setMounted] = useState(false);
8+
9+
// Avoid hydration mismatch
10+
useEffect(() => {
11+
setMounted(true);
12+
}, []);
13+
14+
if (!mounted) {
15+
return null;
16+
}
17+
18+
const handleDownload = () => {
19+
// In a real implementation, this would generate a PDF
20+
// For this example, we'll just trigger the print dialog
21+
// which allows saving as PDF
22+
window.print();
23+
};
24+
25+
return (
26+
<button
27+
onClick={handleDownload}
28+
className="flex items-center gap-2 px-3 py-2 rounded-md bg-accent text-accent-foreground hover:bg-accent/90 transition-colors"
29+
title="Download CV as PDF"
30+
>
31+
<svg
32+
xmlns="http://www.w3.org/2000/svg"
33+
width="16"
34+
height="16"
35+
fill="currentColor"
36+
className="bi bi-download"
37+
viewBox="0 0 16 16"
38+
>
39+
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" />
40+
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z" />
41+
</svg>
42+
<span className="hidden sm:inline">Download PDF</span>
43+
</button>
44+
);
45+
}

app/components/education-section.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { personalData } from "../data/cv";
2+
3+
export function EducationSection() {
4+
return (
5+
<section id="education" className="py-8 md:py-12 animate-slide-up">
6+
<div className="container mx-auto px-4">
7+
<h2 className="section-title">EDUCATION</h2>
8+
<div className="space-y-6">
9+
{personalData.education.map((edu, index) => (
10+
<div key={index} className="timeline-item">
11+
<div className="mb-2">
12+
<h3 className="text-xl font-bold text-primary">
13+
{edu.degree}
14+
</h3>
15+
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-4 text-muted-foreground">
16+
<span className="font-medium">{edu.institution}</span>
17+
<span className="hidden sm:inline"></span>
18+
<span>{edu.period}</span>
19+
</div>
20+
</div>
21+
</div>
22+
))}
23+
</div>
24+
</div>
25+
</section>
26+
);
27+
}

app/components/experience-section.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { personalData } from "../data/cv";
2+
3+
export function ExperienceSection() {
4+
return (
5+
<section id="experience" className="py-8 md:py-12 animate-slide-up">
6+
<div className="container mx-auto px-4">
7+
<h2 className="section-title">EXPERIENCE</h2>
8+
<div className="space-y-6">
9+
{personalData.experience.map((job, index) => (
10+
<div key={index} className="timeline-item">
11+
<div className="mb-2">
12+
<h3 className="text-xl font-bold text-primary">
13+
{job.position}
14+
</h3>
15+
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-4 text-muted-foreground">
16+
<span className="font-medium">{job.company}</span>
17+
<span className="hidden sm:inline"></span>
18+
<span>{job.period}</span>
19+
</div>
20+
</div>
21+
<ul className="mt-4 space-y-2">
22+
{job.responsibilities.map((responsibility, idx) => (
23+
<li key={idx} className="flex items-start gap-2">
24+
<svg
25+
xmlns="http://www.w3.org/2000/svg"
26+
width="16"
27+
height="16"
28+
fill="currentColor"
29+
className="bi bi-arrow-right text-primary mt-1 flex-shrink-0"
30+
viewBox="0 0 16 16"
31+
>
32+
<path
33+
fillRule="evenodd"
34+
d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8"
35+
/>
36+
</svg>
37+
<span>{responsibility}</span>
38+
</li>
39+
))}
40+
</ul>
41+
</div>
42+
))}
43+
</div>
44+
</div>
45+
</section>
46+
);
47+
}

app/components/floating-nav.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
5+
export function FloatingNav() {
6+
const [activeSection, setActiveSection] = useState("about");
7+
const [mounted, setMounted] = useState(false);
8+
9+
// Avoid hydration mismatch
10+
useEffect(() => {
11+
setMounted(true);
12+
}, []);
13+
14+
useEffect(() => {
15+
if (!mounted) return;
16+
17+
const handleScroll = () => {
18+
const sections = document.querySelectorAll("section[id]");
19+
let currentActiveSection = "about";
20+
21+
sections.forEach((section) => {
22+
const sectionTop = section.getBoundingClientRect().top;
23+
if (sectionTop <= 100) {
24+
currentActiveSection = section.id;
25+
}
26+
});
27+
28+
setActiveSection(currentActiveSection);
29+
};
30+
31+
window.addEventListener("scroll", handleScroll);
32+
return () => window.removeEventListener("scroll", handleScroll);
33+
}, [mounted]);
34+
35+
if (!mounted) {
36+
return null;
37+
}
38+
39+
const navItems = [
40+
{ id: "about", label: "About" },
41+
{ id: "skills", label: "Skills" },
42+
{ id: "experience", label: "Experience" },
43+
{ id: "education", label: "Education" },
44+
{ id: "certificates", label: "Certificates" },
45+
];
46+
47+
const scrollToSection = (id: string) => {
48+
const element = document.getElementById(id);
49+
if (element) {
50+
window.scrollTo({
51+
top: element.offsetTop - 80,
52+
behavior: "smooth",
53+
});
54+
}
55+
};
56+
57+
return (
58+
<nav className="floating-nav">
59+
{navItems.map((item) => (
60+
<button
61+
key={item.id}
62+
onClick={() => scrollToSection(item.id)}
63+
className={`nav-item text-sm ${
64+
activeSection === item.id ? "active" : ""
65+
}`}
66+
aria-current={activeSection === item.id ? "page" : undefined}
67+
>
68+
{item.label}
69+
</button>
70+
))}
71+
</nav>
72+
);
73+
}

0 commit comments

Comments
 (0)