├── .eslintrc.cjs
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
├── projects
│ ├── ai-saas.png
│ ├── social-media.png
│ └── stopwatch.png
└── vite.svg
├── src
├── App.css
├── App.jsx
├── assets
│ └── react.svg
├── components
│ ├── Contact.jsx
│ ├── Hero.jsx
│ ├── Navbar.jsx
│ └── Projects.jsx
├── index.css
└── main.jsx
└── vite.config.js
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react/jsx-no-target-blank': 'off',
16 | 'react-refresh/only-export-components': [
17 | 'warn',
18 | { allowConstantExport: true },
19 | ],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Build a Developer Portfolio with Vite & Framer Motion
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |

15 |

16 |

17 |

18 |

19 |
20 |
Craft a Stunning Personal Portfolio with Vite, React & Framer Motion
21 |
22 | Follow the full video tutorial on
23 |
YouTube
24 |
25 |
26 |
27 |
28 | ## 📋 Table of Contents
29 |
30 | 1. [Introduction](#-introduction)
31 | 2. [Tech Stack](#-tech-stack)
32 | 3. [Features](#-features)
33 | 4. [Quick Start](#-quick-start)
34 | 5. [Screenshots](#-screenshots)
35 | 6. [Deployment](#-deployment)
36 |
37 | ---
38 |
39 | ## 🚀 Introduction
40 |
41 | In this comprehensive tutorial, you’ll build a modern **Developer Portfolio** from scratch using **Vite**, **React**, and **Framer Motion**. You’ll implement an animated navbar, glitch title effect, floating cards, responsive hero, projects grid, a fully working contact form powered by EmailJS, and an animated footer—then deploy to Vercel.
42 |
43 | **Timestamps at a glance:**
44 |
45 | * 00:00 Intro & What We’ll Build
46 | * 12:04 Animated Navbar with Framer Motion
47 | * 31:49 Hero Section Entrance Animation
48 | * 40:49 Glitch Title Effect
49 | * 55:12 Syntax‑Highlighted Code Block
50 | * 57:05 Floating Card Animation
51 | * 1:14:03 Projects Grid Layout
52 | * 1:36:04 Contact Section UI
53 | * 1:50:47 EmailJS Configuration
54 | * 2:09:38 Animated Footer
55 | * 2:13:02 Deploying to Vercel
56 |
57 | 🎥 Watch the full tutorial: [YouTube](https://youtu.be/KSQOPRea-P4)
58 |
59 | ---
60 |
61 | ## ⚙️ Tech Stack
62 |
63 | * **Vite** – Next‑generation frontend tooling
64 | * **React 18** – Component‑based UI
65 | * **Framer Motion** – Fluid animations & gestures
66 | * **TailwindCSS** – Utility‑first styling
67 | * **EmailJS** – Client‑side email sending
68 | * **TypeScript** (optional) – Static type checking
69 |
70 | ---
71 |
72 | ## ⚡️ Features
73 |
74 | * ✨ **Animated Navbar**
75 | Smooth entrance and hover states built with Framer Motion.
76 |
77 | * 🚀 **Hero Section**
78 | Engaging entrance animation, glitch title effect & call‑to‑action buttons.
79 |
80 | * 🃏 **Floating Card**
81 | Interactive profile card floating on scroll.
82 |
83 | * 💻 **Embedded Code Block**
84 | Syntax-highlighted code snippet in the hero.
85 |
86 | * 🎨 **Projects Showcase**
87 | Responsive grid of project cards with glassy hover effects.
88 |
89 | * 📱 **Mobile Responsiveness**
90 | Tweaks for a perfect experience on all screen sizes.
91 |
92 | * 📨 **Contact Form**
93 | Styled form with glassy submit button, integrated with EmailJS.
94 |
95 | * 🎬 **Animated Footer**
96 | Subtle motion effects to wrap up the page.
97 |
98 | * ☁️ **One‑Click Deployment**
99 | Deploy your portfolio to Vercel in seconds.
100 |
101 | ---
102 |
103 | ## 👌 Quick Start
104 |
105 | ### Prerequisites
106 |
107 | * [Node.js](https://nodejs.org/) (v14+)
108 | * [EmailJS Account](https://www.emailjs.com/)
109 |
110 | ### Clone and Run
111 |
112 | ```bash
113 | git clone https://github.com/yourusername/portfolio-vite-framer.git
114 | cd portfolio-vite-framer
115 | npm install
116 | ```
117 |
118 | 1. Copy `.env.example` to `.env.local` and add your EmailJS service ID, template ID & public key.
119 | 2. Start the development server:
120 |
121 | ```bash
122 | npm run dev
123 | ```
124 | 3. Open [http://localhost:5173](http://localhost:5173) in your browser.
125 |
126 | ---
127 |
128 | ## 🖼️ Screenshots
129 |
130 |
131 |
132 |
133 |
134 | ---
135 |
136 | ## ☁️ Deployment
137 |
138 | ### Deploy on Vercel
139 |
140 | 1. Push your code to GitHub
141 | 2. Go to [Vercel](https://vercel.com/) and import your repo
142 | 3. Add your EmailJS keys as Environment Variables in Vercel dashboard
143 | 4. Click **Deploy** — your live portfolio will be online in moments!
144 |
145 | ---
146 |
147 | ## 🔗 Useful Links
148 |
149 | * [Vite Docs](https://vitejs.dev/)
150 | * [React Docs](https://reactjs.org/docs)
151 | * [Framer Motion Docs](https://www.framer.com/motion/)
152 | * [Tailwind CSS Docs](https://tailwindcss.com/docs)
153 | * [EmailJS Docs](https://www.emailjs.com/docs/)
154 | * [Vercel](https://vercel.com/)
155 |
156 | ---
157 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cool-animation-portfolio",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@emailjs/browser": "^4.4.1",
14 | "framer-motion": "^12.16.0",
15 | "react": "^18.3.1",
16 | "react-dom": "^18.3.1",
17 | "react-syntax-highlighter": "^15.6.1"
18 | },
19 | "devDependencies": {
20 | "@types/react": "^18.3.3",
21 | "@types/react-dom": "^18.3.0",
22 | "@vitejs/plugin-react": "^4.3.1",
23 | "eslint": "^8.57.0",
24 | "eslint-plugin-react": "^7.34.3",
25 | "eslint-plugin-react-hooks": "^4.6.2",
26 | "eslint-plugin-react-refresh": "^0.4.7",
27 | "vite": "^5.3.4"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/public/projects/ai-saas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/machadop1407/animation-personal-portfolio/a89ff47b98bda9d4636cf416f4c45d8060c126a8/public/projects/ai-saas.png
--------------------------------------------------------------------------------
/public/projects/social-media.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/machadop1407/animation-personal-portfolio/a89ff47b98bda9d4636cf416f4c45d8060c126a8/public/projects/social-media.png
--------------------------------------------------------------------------------
/public/projects/stopwatch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/machadop1407/animation-personal-portfolio/a89ff47b98bda9d4636cf416f4c45d8060c126a8/public/projects/stopwatch.png
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | --primary-color: #7c3aed;
9 | --primary-dark: #5b21b6;
10 | --secondary-color: #db2777;
11 | --accent-color: #f472b6;
12 | --text-color: #e2e8f0;
13 | --light-text: #94a3b8;
14 | --background: #0f172a;
15 | --section-bg: #1e293b;
16 | --card-bg: #1e293b;
17 | --gradient-start: #7c3aed;
18 | --gradient-end: #db2777;
19 | --nav-bg: rgba(15, 23, 42, 0.8);
20 | --card-border: rgba(148, 163, 184, 0.1);
21 | }
22 |
23 | body {
24 | line-height: 1.6;
25 | color: var(--text-color);
26 | background: var(--background);
27 | }
28 |
29 | .app {
30 | opacity: 0;
31 | transform: translateY(20px);
32 | transition: opacity 0.6s ease-out, transform 0.6s ease-out;
33 | width: 100%;
34 | min-height: 100vh;
35 | }
36 |
37 | .app.loaded {
38 | opacity: 1;
39 | transform: translateY(0);
40 | }
41 |
42 | .navbar {
43 | position: fixed;
44 | top: 0;
45 | left: 0;
46 | right: 0;
47 | display: flex;
48 | justify-content: space-between;
49 | align-items: center;
50 | padding: 1.5rem 5%;
51 | background: var(--nav-bg);
52 | backdrop-filter: blur(10px);
53 | z-index: 1000;
54 | box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
55 | border-bottom: 1px solid var(--card-border);
56 | }
57 |
58 | .logo {
59 | font-size: 1.5rem;
60 | font-weight: 700;
61 | color: var(--accent-color);
62 | text-shadow: 0 0 10px rgba(244, 114, 182, 0.3);
63 | }
64 |
65 | .nav-links {
66 | display: flex;
67 | gap: 2rem;
68 | list-style: none;
69 | }
70 |
71 | .nav-links a {
72 | text-decoration: none;
73 | color: var(--text-color);
74 | font-weight: 500;
75 | transition: all 0.3 ease;
76 | position: relative;
77 | cursor: pointer;
78 | }
79 |
80 | .nav-links a::after {
81 | content: "";
82 | position: absolute;
83 | bottom: -5px;
84 | left: 0;
85 | width: 0;
86 | height: 2px;
87 | background: var(--accent-color);
88 | transition: width 0.3s ease;
89 | }
90 |
91 | .nav-links a:hover {
92 | color: var(--accent-color);
93 | }
94 |
95 | .nav-links a:hover::after {
96 | width: 100%;
97 | }
98 |
99 | /* Hero Section */
100 |
101 | .hero {
102 | min-height: 100vh;
103 | background: linear-gradient(
104 | 135deg,
105 | var(--gradient-start) 0%,
106 | var(--gradient-end) 100%
107 | );
108 |
109 | display: flex;
110 | align-items: center;
111 | padding: 0 5%;
112 | position: relative;
113 | overflow: hidden;
114 | }
115 |
116 | .hero-container {
117 | display: flex;
118 | justify-content: space-between;
119 | align-items: center;
120 | gap: 4rem;
121 | max-width: 1400px;
122 | margin: 0 auto;
123 | width: 100%;
124 | padding-top: 100px;
125 | }
126 |
127 | .hero-content {
128 | flex: 1;
129 | text-align: left;
130 | max-width: 600px;
131 | position: relative;
132 | z-index: 2;
133 | }
134 |
135 | .hero-badge {
136 | display: inline-block;
137 | padding: 0.5rem 1rem;
138 | background: rgba(255, 255, 255, 0.1);
139 | border-radius: 50px;
140 | margin-bottom: 1.5rem;
141 | backdrop-filter: blur(5px);
142 | border: 1px solid rgba(255, 255, 255, 0.2);
143 | }
144 |
145 | .glitch {
146 | font-size: 4.5rem;
147 | font-weight: 800;
148 | text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
149 | position: relative;
150 | margin-bottom: 1rem;
151 | line-height: 1.1;
152 | }
153 |
154 | .hero-subtitle {
155 | font-size: 2rem;
156 | color: var(--accent-color);
157 | margin-bottom: 1.5rem;
158 | font-weight: 600;
159 | }
160 |
161 | .hero-description {
162 | font-size: 1.1rem;
163 | margin-bottom: 2rem;
164 | line-height: 1.7;
165 | }
166 |
167 | .cta-buttons {
168 | display: flex;
169 | gap: 1rem;
170 | margin-bottom: 2rem;
171 | }
172 |
173 | .cta-primary,
174 | .cta-secondary {
175 | padding: 0.8rem 2rem;
176 | border-radius: 50px;
177 | font-weight: 600;
178 | font-size: 1rem;
179 | text-decoration: none;
180 | transition: all 0.3s ease;
181 | position: relative;
182 | overflow: hidden;
183 | display: inline-flex;
184 | align-items: center;
185 | justify-content: center;
186 | min-width: 160px;
187 | }
188 |
189 | .cta-primary {
190 | background: white;
191 | color: var(--primary-color);
192 | box-shadow: 0 4px 15px rgba(255, 255, 255, 0.2);
193 | border: 2px solid white;
194 | }
195 |
196 | .cta-secondary {
197 | background: transparent;
198 | color: white;
199 | box-shadow: 0 4px 15px rgba(255, 255, 255, 0.2);
200 | border: 2px solid white;
201 | backdrop-filter: blur(5px);
202 | }
203 |
204 | .cta-secondary:hover {
205 | background: rgba(255, 255, 255, 0.1);
206 | border-color: rgba(255, 255, 255, 0.3);
207 | transform: translateY(-2px);
208 | }
209 |
210 | .cta-secondary:active {
211 | transform: translateY(0);
212 | }
213 |
214 | .cta-secondary::before {
215 | content: "";
216 | position: absolute;
217 | top: 0;
218 | left: -100%;
219 | width: 100%;
220 | height: 100%;
221 | background: linear-gradient(
222 | 90deg,
223 | transparent,
224 | rgba(255, 255, 255, 0.2),
225 | transparent
226 | );
227 | transition: 0.5s;
228 | }
229 |
230 | .cta-secondary:hover::before {
231 | left: 100%;
232 | }
233 |
234 | .social-links {
235 | display: flex;
236 | gap: 1.5rem;
237 | }
238 |
239 | .social-links a {
240 | color: white;
241 | font-size: 1.5rem;
242 | transition: all 0.3s ease;
243 | }
244 |
245 | .social-links a:hover {
246 | color: var(--accent-color);
247 | }
248 |
249 | .floating-card {
250 | position: absolute;
251 | bottom: 2rem;
252 | right: -2rem;
253 | background: rgba(255, 255, 255, 0.1);
254 | backdrop-filter: blur(10px);
255 | padding: 1rem 1.5rem;
256 | border-radius: 15px;
257 | border: 1px solid rgba(255, 255, 255, 0.2);
258 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
259 | }
260 |
261 | .card-content {
262 | display: flex;
263 | align-items: center;
264 | gap: 1rem;
265 | }
266 |
267 | .card-text {
268 | color: white;
269 | font-size: 0.9rem;
270 | font-weight: 500;
271 | }
272 |
273 | .card-icon {
274 | font-size: 1.5rem;
275 | }
276 |
277 | .hero-image-container {
278 | flex: 1;
279 | position: relative;
280 | max-width: 600px;
281 | }
282 |
283 | .code-display {
284 | width: 100%;
285 | }
286 |
287 | /* Hero Media Queries */
288 |
289 | @media (max-width: 1024px) {
290 | .hero-container {
291 | flex-direction: column;
292 | text-align: center;
293 | gap: 3rem;
294 | padding-top: 120px;
295 | }
296 |
297 | .hero-content {
298 | text-align: center;
299 | max-width: 100%;
300 | }
301 |
302 | .cta-buttons {
303 | justify-content: center;
304 | }
305 |
306 | .social-links {
307 | justify-content: center;
308 | }
309 | }
310 |
311 | @media (max-width: 768px) {
312 | .hero-image-container {
313 | display: none;
314 | }
315 | }
316 |
317 | /* Projects Sections */
318 |
319 | .projects {
320 | padding: 6rem 5%;
321 | background-color: var(--background);
322 | position: relative;
323 | }
324 |
325 | .projects h2 {
326 | text-align: center;
327 | font-size: 2.5rem;
328 | margin-bottom: 3rem;
329 | }
330 |
331 | .project-grid {
332 | display: grid;
333 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
334 | gap: 2rem;
335 | max-width: 1200px;
336 | margin: 0 auto;
337 | }
338 |
339 | .project-card {
340 | background: var(--card-bg);
341 | border-radius: 1rem;
342 | overflow: hidden;
343 | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
344 | transition: all 0.3 ease;
345 | border: 1px solid var(--card-border);
346 | position: relative;
347 | display: flex;
348 | flex-direction: column;
349 | }
350 |
351 | .project-card::before {
352 | content: "";
353 | position: absolute;
354 | top: 0;
355 | left: 0;
356 | right: 0;
357 | bottom: 0;
358 | background: linear-gradient(
359 | 135deg,
360 | var(--gradient-start) 0%,
361 | var(--gradient-end) 100%
362 | );
363 |
364 | opacity: 0;
365 | transition: opacity 0.3s ease;
366 | z-index: 1;
367 | }
368 |
369 | .project-card:hover::before {
370 | opacity: 0.1;
371 | }
372 |
373 | .project-card:hover {
374 | transform: translateY(-10px) scale(1.02);
375 | box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
376 | border-color: var(--accent-color);
377 | }
378 |
379 | .project-image {
380 | height: 240px;
381 | background-size: cover;
382 | background-position: center;
383 | position: relative;
384 | overflow: hidden;
385 | }
386 |
387 | .project-image::after {
388 | content: "";
389 | position: absolute;
390 | top: 0;
391 | left: 0;
392 | right: 0;
393 | bottom: 0;
394 | background: linear-gradient(
395 | 45deg,
396 | transparent 0%,
397 | rgba(255, 255, 255, 0.1) 50%,
398 | transparent 100%
399 | );
400 | transform: translateX(-100%);
401 | transition: transform 0.6s ease;
402 | }
403 |
404 | .project-card:hover .project-image::after {
405 | transform: translateX(100%);
406 | }
407 |
408 | .project-card h3 {
409 | padding: 1.5rem 1.5rem 0.5rem;
410 | font-size: 1.5rem;
411 | color: var(--text-color);
412 | }
413 |
414 | .project-card p {
415 | padding: 0 1.5rem 1rem;
416 | color: var(--light-text);
417 | flex-grow: 1;
418 | }
419 |
420 | .project-tech {
421 | padding: 0 1.5rem 1.5rem;
422 | display: flex;
423 | gap: 0.5rem;
424 | flex-wrap: wrap;
425 | }
426 |
427 | .project-tech span {
428 | padding: 0.3rem 0.8rem;
429 | background: rgba(255, 255, 255, 0.1);
430 | border-radius: 50px;
431 | font-size: 0.8rem;
432 | color: var(--accent-color);
433 | border: 1px solid rgba(244, 114, 182, 0.2);
434 | }
435 |
436 | /* contact section */
437 |
438 | .contact {
439 | padding: 6rem 5%;
440 | background: var(--section-bg);
441 | position: relative;
442 | }
443 |
444 | .contact::before {
445 | content: "";
446 | position: absolute;
447 | top: 0;
448 | left: 0;
449 | right: 0;
450 | height: 1px;
451 | background: linear-gradient(
452 | 90deg,
453 | transparent,
454 | var(--accent-color),
455 | transparent
456 | );
457 | }
458 |
459 | .contact h2 {
460 | text-align: center;
461 | font-size: 2.5rem;
462 | margin-bottom: 3rem;
463 | }
464 |
465 | .contact-content {
466 | max-width: 600px;
467 | margin: 0 auto;
468 | }
469 |
470 | .contact-form {
471 | display: flex;
472 | flex-direction: column;
473 | gap: 1rem;
474 | }
475 |
476 | .contact-form input,
477 | .contact-form textarea {
478 | padding: 1rem;
479 | border: 2px solid var(--card-border);
480 | border-radius: 0.5rem;
481 | font-size: 1rem;
482 | transition: all 0.3s ease;
483 | background: var(--card-bg);
484 | color: var(--text-color);
485 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
486 | }
487 |
488 | .contact-form input:focus,
489 | .contact-form textarea:focus {
490 | outline: none;
491 | border-color: var(--accent-color);
492 | box-shadow: 0 4px 12px rgba(244, 144, 182, 0.2);
493 | }
494 |
495 | .contact-form textarea {
496 | min-height: 150px;
497 | resize: vertical;
498 | }
499 |
500 | .submit-btn {
501 | padding: 1rem;
502 | background: var(--primary-color);
503 | color: white;
504 | border: none;
505 | border-radius: 0.5rem;
506 | font-size: 1rem;
507 | font-weight: 600;
508 | cursor: pointer;
509 | transition: all 0.3s ease;
510 | box-shadow: 0 4px 12px rgba(124, 58, 237, 0.3);
511 | position: relative;
512 | overflow: hidden;
513 | }
514 |
515 | .submit-btn::before {
516 | content: "";
517 | position: absolute;
518 | top: 0;
519 | left: -100%;
520 | width: 100%;
521 | height: 100%;
522 | background: linear-gradient(
523 | 90deg,
524 | transparent,
525 | rgba(255, 255, 255, 0.2),
526 | transparent
527 | );
528 | transition: 0.5s;
529 | }
530 |
531 | .submit-btn:hover::before {
532 | left: 100%;
533 | }
534 |
535 | .form-status {
536 | margin-top: 1rem;
537 | padding: 1rem;
538 | border-radius: 0.5rem;
539 | text-align: center;
540 | font-weight: 500;
541 | }
542 |
543 | .form-status.success {
544 | background: rgba(34, 197, 94, 0.1);
545 | border: 1px solid rgba(34, 197, 94, 0.2);
546 | color: #4ade80;
547 | }
548 |
549 | .form-status.error {
550 | background: rgba(239, 68, 68, 0.1);
551 | border: 1px solid rgba(239, 68, 68, 0.2);
552 | color: #f87171;
553 | }
554 |
555 | .footer {
556 | text-align: center;
557 | padding: 2rem;
558 | background: var(--background);
559 | color: var(--text-color);
560 | border-top: 1px solid var(--card-border);
561 | }
562 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import { Navbar } from "./components/Navbar";
3 | import { Hero } from "./components/Hero";
4 | import { Projects } from "./components/Projects";
5 | import { Contact } from "./components/Contact";
6 | import { useEffect, useState } from "react";
7 | import emailjs from "@emailjs/browser";
8 | import { motion } from "framer-motion";
9 |
10 | function App() {
11 | const [isLoaded, setIsLoaded] = useState(false);
12 |
13 | useEffect(() => {
14 | setIsLoaded(true);
15 | emailjs.init(import.meta.env.VITE_EMAILJS_PUBLIC_KEY);
16 | }, []);
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
33 | © 2025 PedroTech. All rights reserved.
34 |
35 |
36 | );
37 | }
38 |
39 | export default App;
40 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Contact.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import emailjs from "@emailjs/browser";
3 | import { useState } from "react";
4 |
5 | const fadeInUp = {
6 | initial: { opacity: 0, y: 20 },
7 | animate: { opacity: 1, y: 0 },
8 | transition: { duration: 0.6 },
9 | };
10 |
11 | const staggerContainer = {
12 | animate: {
13 | transition: {
14 | staggerChildren: 0.1,
15 | },
16 | },
17 | };
18 |
19 | export const Contact = () => {
20 | const [formData, setFormData] = useState({
21 | name: "",
22 | email: "",
23 | message: "",
24 | });
25 |
26 | const [formStatus, setFormStatus] = useState({
27 | submitting: false,
28 | success: false,
29 | error: false,
30 | message: "",
31 | });
32 |
33 | const handleInputChange = (e) => {
34 | const { name, value } = e.target;
35 | setFormData((prev) => ({
36 | ...prev,
37 | [name]: value,
38 | }));
39 | };
40 |
41 | const handleSubmit = async (e) => {
42 | e.preventDefault();
43 |
44 | setFormStatus({
45 | submitting: true,
46 | success: false,
47 | error: false,
48 | message: "",
49 | });
50 |
51 | try {
52 | await emailjs.send(
53 | import.meta.env.VITE_EMAILJS_SERVICE_ID,
54 | import.meta.env.VITE_EMAILJS_TEMPLATE_ID,
55 | {
56 | name: formData.name,
57 | email: formData.email,
58 | message: formData.message,
59 | }
60 | );
61 |
62 | setFormStatus({
63 | submitting: false,
64 | success: true,
65 | error: false,
66 | message: "Message sent successfully!",
67 | });
68 |
69 | setFormData({
70 | name: "",
71 | email: "",
72 | message: "",
73 | });
74 | } catch (error) {
75 | setFormStatus({
76 | submitting: false,
77 | success: false,
78 | error: true,
79 | message: "Failed to send message. Please try again.",
80 | });
81 | }
82 | };
83 |
84 | return (
85 |
93 |
99 | Get in Touch
100 |
101 |
102 |
103 |
104 |
112 |
120 |
127 |
128 |
135 | {formStatus.submitting ? "Sending..." : "Send Message"}
136 |
137 |
138 | {formStatus.message && (
139 |
144 | {formStatus.message}
145 |
146 | )}
147 |
148 |
149 |
150 | );
151 | };
152 |
--------------------------------------------------------------------------------
/src/components/Hero.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
3 | import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
4 |
5 | const fadeInUp = {
6 | initial: { opacity: 0, y: 20 },
7 | animate: { opacity: 1, y: 0 },
8 | transition: { duration: 0.6 },
9 | };
10 |
11 | const staggerContainer = {
12 | animate: {
13 | transition: {
14 | staggerChildren: 0.1,
15 | },
16 | },
17 | };
18 |
19 | export const Hero = () => {
20 | return (
21 |
28 |
29 |
35 |
36 | 👋 Hello, I'm
37 |
38 |
43 | PedroTech
44 |
45 |
46 | {" "}
47 | Creative Developer & Designer
48 |
49 |
50 | I craft beautiful digital experiences that combine stunning design
51 | with powerful functionality. Specializing in modern web applications
52 | and interactive user interfaces.
53 |
54 |
55 |
56 |
62 | {" "}
63 | View My Work
64 |
65 |
71 | Contact Me
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
93 |
94 |
107 | {`const aboutMe: DeveloperProfile = {
108 | codename: "PedroTech",
109 | origin: "🌍 Somewhere between a coffee shop and a terminal",
110 | role: "Fullstack Web Sorcerer",
111 | stack: {
112 | languages: ["JavaScript", "TypeScript", "SQL"],
113 | frameworks: ["React", "Next.js", "TailwindCSS", "Supabase"],
114 | },
115 | traits: [
116 | "pixel-perfectionist",
117 | "API whisperer",
118 | "dark mode advocate",
119 | "terminal aesthetic enthusiast",
120 | ],
121 | missionStatement:
122 | "Turning ideas into interfaces and bugs into feature",
123 | availability: "Available for hire",
124 | };`}
125 |
126 |
127 |
128 |
133 |
134 | 💻
135 |
136 | {" "}
137 | Currently working on something awesome!
138 |
139 |
140 |
141 |
142 |
143 |
144 | );
145 | };
146 |
--------------------------------------------------------------------------------
/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 |
3 | const fadeInUp = {
4 | initial: { opacity: 0, y: 20 },
5 | animate: { opacity: 1, y: 0 },
6 | transition: { duration: 0.6 },
7 | };
8 |
9 | const staggerContainer = {
10 | animate: {
11 | transition: {
12 | staggerChildren: 0.1,
13 | },
14 | },
15 | };
16 |
17 | export const Navbar = () => {
18 | return (
19 |
25 |
30 | Portfolio
31 |
32 |
33 |
39 |
44 | Home
45 |
46 |
51 | Projects
52 |
53 |
58 | Contact
59 |
60 |
61 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/src/components/Projects.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 |
3 | const fadeInUp = {
4 | initial: { opacity: 0, y: 20 },
5 | animate: { opacity: 1, y: 0 },
6 | transition: { duration: 0.6 },
7 | };
8 |
9 | const staggerContainer = {
10 | animate: {
11 | transition: {
12 | staggerChildren: 0.1,
13 | },
14 | },
15 | };
16 |
17 | export const Projects = () => {
18 | return (
19 |
27 |
33 | My Projects
34 |
35 |
42 |
47 |
52 | AI SaaS Platform
53 |
54 | A modern SaaS platform built with Next.js and OpenAI integration,
55 | featuring real-time AI-powered content generation and analytics.
56 |
57 |
58 | Next.js
59 | OpenAI
60 | TailwindCSS
61 |
62 |
63 |
64 |
69 |
77 | Social Media Dashboard
78 |
79 | A comprehensive social media management dashboard with analytics,
80 | scheduling, and engagement tracking features.
81 |
82 |
83 | React
84 | Node.js
85 | MongoDB
86 |
87 |
88 |
89 |
94 |
102 | Productivity Timer
103 |
104 | A sleek productivity timer application with customizable work
105 | sessions, statistics tracking, and dark mode support.
106 |
107 |
108 | React
109 | TypeScript
110 | TailwindCSS
111 |
112 |
113 |
114 |
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap");
2 |
3 | :root {
4 | font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
5 | sans-serif;
6 | line-height: 1.5;
7 | font-weight: 400;
8 | color-scheme: dark;
9 | color: rgba(255, 255, 255, 0.87);
10 | background-color: #242424;
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | }
16 |
17 | html {
18 | scroll-behavior: smooth;
19 | width: 100%;
20 | }
21 |
22 | body {
23 | margin: 0;
24 | min-width: 100%;
25 | min-height: 100vh;
26 | width: 100%;
27 | }
28 |
29 | /* Custom scrollbar */
30 | ::-webkit-scrollbar {
31 | width: 10px;
32 | }
33 |
34 | ::-webkit-scrollbar-track {
35 | background: #f1f1f1;
36 | }
37 |
38 | ::-webkit-scrollbar-thumb {
39 | background: #888;
40 | border-radius: 5px;
41 | }
42 |
43 | ::-webkit-scrollbar-thumb:hover {
44 | background: #555;
45 | }
46 |
47 | /* Selection color */
48 | ::selection {
49 | background: var(--primary-color);
50 | color: white;
51 | }
52 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.jsx'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------