├── .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 | Copy of Copy of Copy of Copy of Copy of Copy of Copy of Copy of Copy of Copy of Copy of Copy of Copy of Copy of Copy of 10,000 REACT COMPONENTS (3) 10 | 11 | 12 |
13 |
14 | Vite 15 | React 16 | Framer Motion 17 | Tailwind CSS 18 | EmailJS 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 | Hero Animation 131 | Projects Grid 132 | Contact Form 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 | --------------------------------------------------------------------------------