Hero Section My name: Vedant Kale Tagline: "Pass...
Erstellt am: 11. September 2025
Erstellt am: 11. September 2025
Hero Section
My name: Vedant Kale
Tagline: "Passionate BTech CSE Student | Full-Stack Web Developer | Aspiring Big Tech Engineer"
Short intro: "I’m a 2nd-year BTech CSE student at Pimpri Chinchwad University with strong skills in JavaScript, Node.js, Express.js, Java, Python, HTML, CSS, and DSA. I love building real-world projects and aim to join a big tech company as a skilled software engineer."
A 3D animated background (like rotating shapes, floating spheres, or coding-inspired 3D objects).
CTA buttons: View Projects and Contact Me.
About Me Section
Brief description of my journey as a developer.
Mention that I’m also learning DSA (Striver’s A2Z), full-stack web dev, and JavaScript frameworks.
Add a 3D model (like a laptop, desk setup, or animated avatar).
Projects Section
Showcase with interactive 3D project cards:
Weather API App – Real-time weather info using API.
Smart Solar Tracking System – Arduino Uno–based IoT project.
Google Gemini Chatbot Clone – Built with modern JS/Node.js.
Smart Traffic Light System – Java + Swing GUI + animations.
Smart Farm Simulation – Java IoT sensors + JDBC logging + GUI.
Each project card should have: Title, description, tech stack, GitHub link, and live demo (if available).
Skills Section
Show interactive skill spheres or 3D tags for: JavaScript, React, Node.js, Express.js, MongoDB, HTML, CSS, Java, Python, DSA.
Maybe include a skill bar or circular animation.
Experience / Education Section
Pimpri Chinchwad University | CGPA: 7.4 | 2nd-Year BTech CSE
Add internships/projects once done.
Contact Section
Email: [email protected]
LinkedIn: www.linkedin.com/in/Vedant-Kale-Developer
GitHub: https://github.com/VedantKale244
A contact form (using EmailJS) with name, email, and message.
Design Guidelines
Use a dark mode theme with neon highlights.
Smooth scroll animations and hover effects.
Make it responsive for mobile and desktop.
Keep a 3D background animation (like stars, cubes, or an interactive 3D scene).
Extras
Include a download resume button (PDF).
Add a scroll-to-top button.
SEO friendly (title: "Vedant Kale | Developer Portfolio").
Optimized for performance.
Awesome brief, Vedant! Here’s a sleek, performant, dark-neon, one-page portfolio you can drop onto any static host (GitHub Pages, Netlify, Vercel). It includes a lightweight 3D background (Three.js), smooth scrolling, interactive sections, EmailJS form, SEO, and responsive styles.
Below is a complete project you can copy-paste. File tree:
text/vedant-portfolio ├─ index.html ├─ styles.css ├─ main.js ├─ three-bg.js ├─ projects.js └─ assets/ └─ resume.pdf ← put your PDF here (rename if different)
index.htmlhtml<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Vedant Kale | Developer Portfolio</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="description" content="Vedant Kale — BTech CSE student, full-stack developer, aspiring big tech engineer. Projects, skills, and contact." /> <link rel="canonical" href="https://your-domain.com/" /> <!-- Open Graph --> <meta property="og:title" content="Vedant Kale | Developer Portfolio" /> <meta property="og:description" content="Projects, skills, and contact." /> <meta property="og:type" content="website" /> <meta property="og:url" content="https://your-domain.com/" /> <meta property="og:image" content="https://your-domain.com/og-image.png" /> <!-- Twitter --> <meta name="twitter:card" content="summary_large_image" /> <!-- JSON-LD --> <script type="application/ld+json"> { "@context":"https://schema.org", "@type":"Person", "name":"Vedant Kale", "jobTitle":"Full-Stack Web Developer", "email":"mailto:[email protected]", "url":"https://your-domain.com/", "sameAs":[ "https://www.linkedin.com/in/Vedant-Kale-Developer", "https://github.com/VedantKale244" ] } </script> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://unpkg.com" crossorigin> <link rel="stylesheet" href="styles.css" /> </head> <body> <!-- 3D background --> <canvas id="bg-canvas" aria-hidden="true"></canvas> <!-- Skip link --> <a class="skip-link" href="#hero">Skip to content</a> <!-- Header / Nav --> <header class="site-header"> <a class="logo" href="#hero">VK</a> <nav> <button class="nav-toggle" aria-expanded="false" aria-controls="nav-menu">☰</button> <ul id="nav-menu" class="nav-list"> <li><a href="#about">About</a></li> <li><a href="#projects">Projects</a></li> <li><a href="#skills">Skills</a></li> <li><a href="#education">Education</a></li> <li><a href="#contact">Contact</a></li> <li><a class="btn btn-outline" href="assets/resume.pdf" download>Download Résumé</a></li> </ul> </nav> </header> <!-- Hero --> <section id="hero" class="section hero"> <div class="hero-content"> <p class="eyebrow">Hi, I’m</p> <h1 class="glow">Vedant Kale</h1> <p class="tagline">Passionate BTech CSE Student | Full-Stack Web Developer | Aspiring Big Tech Engineer</p> <p class="intro"> I’m a 2nd-year BTech CSE student at Pimpri Chinchwad University with strong skills in JavaScript, Node.js, Express.js, Java, Python, HTML, CSS, and DSA. I love building real-world projects and aim to join a big tech company as a skilled software engineer. </p> <div class="cta-row"> <a href="#projects" class="btn btn-primary">View Projects</a> <a href="#contact" class="btn btn-secondary">Contact Me</a> </div> </div> <div class="hero-3d-hint">Interactive 3D background — move your mouse ✨</div> </section> <!-- About --> <section id="about" class="section about"> <div class="container"> <h2 class="section-title">About Me</h2> <p> I started my developer journey by tinkering with web technologies and quickly fell in love with building things end-to-end. I’m actively learning <strong>DSA (Striver’s A2Z)</strong>, <strong>full-stack web development</strong>, and modern <strong>JavaScript frameworks</strong>. I enjoy converting ideas into performant, real-world applications. </p> <div class="about-3d"> <!-- Decorative 3D figure (CSS) as a lightweight “laptop” silhouette --> <div class="laptop3d" aria-hidden="true"> <div class="lid"></div> <div class="base"></div> </div> </div> </div> </section> <!-- Projects --> <section id="projects" class="section projects"> <div class="container"> <h2 class="section-title">Projects</h2> <div id="projects-grid" class="grid"></div> </div> </section> <!-- Skills --> <section id="skills" class="section skills"> <div class="container"> <h2 class="section-title">Skills</h2> <div class="skills-cloud" role="list" aria-label="Skill tags"> <!-- Filled by JS --> </div> <div class="skill-bars"> <div class="skill"> <span>JavaScript</span> <div class="bar"><i style="width:88%"></i></div> </div> <div class="skill"> <span>Node.js / Express</span> <div class="bar"><i style="width:82%"></i></div> </div> <div class="skill"> <span>React</span> <div class="bar"><i style="width:75%"></i></div> </div> <div class="skill"> <span>Java / DSA</span> <div class="bar"><i style="width:70%"></i></div> </div> <div class="skill"> <span>Python</span> <div class="bar"><i style="width:68%"></i></div> </div> </div> </div> </section> <!-- Education --> <section id="education" class="section education"> <div class="container"> <h2 class="section-title">Experience & Education</h2> <div class="edu-card"> <h3>Pimpri Chinchwad University</h3> <p><strong>BTech CSE — 2nd Year</strong></p> <p>CGPA: 7.4</p> <p class="muted">Internships/projects will be added here as they’re completed.</p> </div> </div> </section> <!-- Contact --> <section id="contact" class="section contact"> <div class="container"> <h2 class="section-title">Contact</h2> <div class="contact-cards"> <a class="contact-card" href="mailto:[email protected]"> <h3>Email</h3> <p>[email protected]</p> </a> <a class="contact-card" href="https://www.linkedin.com/in/Vedant-Kale-Developer" target="_blank" rel="noopener"> <h3>LinkedIn</h3> <p>linkedin.com/in/Vedant-Kale-Developer</p> </a> <a class="contact-card" href="https://github.com/VedantKale244" target="_blank" rel="noopener"> <h3>GitHub</h3> <p>github.com/VedantKale244</p> </a> </div> <form id="contact-form" class="contact-form" aria-label="Contact form"> <div class="field"> <label for="name">Name</label> <input id="name" name="name" type="text" placeholder="Your name" required /> </div> <div class="field"> <label for="email">Email</label> <input id="email" name="email" type="email" placeholder="[email protected]" required /> </div> <div class="field"> <label for="message">Message</label> <textarea id="message" name="message" rows="5" placeholder="How can I help?" required></textarea> </div> <button class="btn btn-primary" type="submit">Send Message</button> <p id="form-status" class="form-status" role="status" aria-live="polite"></p> </form> </div> </section> <button id="to-top" class="to-top" aria-label="Scroll to top">↑</button> <!-- Scripts --> <script defer src="https://unpkg.com/[email protected]/build/three.min.js"></script> <script defer src="projects.js"></script> <script defer src="three-bg.js"></script> <script defer src="main.js"></script> </body> </html>
styles.csscss:root { --bg: #0b0f17; --bg-soft: #0f1422; --text: #e5f0ff; --muted: #9cb3d1; --neon: #43ffd2; --neon-2: #7b5cff; --accent: #00e5ff; --card: #121826; --ring: rgba(67,255,210,0.35); --shadow: 0 10px 30px rgba(0,0,0,.45); } * { box-sizing: border-box; } html { scroll-behavior: smooth; } body { margin: 0; font-family: ui-sans-serif, system-ui, Segoe UI, Roboto, Helvetica, Arial, Apple Color Emoji, Segoe UI Emoji; background: radial-gradient(1200px 800px at 20% -10%, rgba(123,92,255,.15), transparent), radial-gradient(900px 600px at 110% 10%, rgba(0,229,255,.12), transparent), var(--bg); color: var(--text); line-height: 1.6; } /* Accessibility */ .skip-link { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; } .skip-link:focus { left: 1rem; top: 1rem; width: auto; height: auto; z-index: 1000; } /* Header */ .site-header { position: sticky; top: 0; z-index: 50; backdrop-filter: blur(8px); background: linear-gradient(180deg, rgba(15,20,34,.75), rgba(15,20,34,.3)); border-bottom: 1px solid rgba(255,255,255,.06); display: flex; align-items: center; justify-content: space-between; padding: .6rem 1rem; } .logo { font-weight: 800; letter-spacing: .08em; text-decoration: none; color: var(--text); border: 2px solid var(--neon); padding: .2rem .5rem; border-radius: 8px; box-shadow: 0 0 16px rgba(67,255,210,.25), inset 0 0 16px rgba(67,255,210,.15); } .nav-list { display: flex; gap: .5rem; list-style: none; margin: 0; padding: 0; } .nav-list a { color: var(--text); text-decoration: none; padding: .5rem .75rem; border-radius: 8px; } .nav-list a:hover { background: rgba(255,255,255,.06); } .nav-toggle { display:none; } .btn { display:inline-block; border:none; cursor:pointer; text-decoration:none; border-radius:12px; padding:.8rem 1.1rem; font-weight:700; transition: transform .2s ease, box-shadow .2s ease; box-shadow: var(--shadow); } .btn:hover { transform: translateY(-2px); } .btn-primary { background: linear-gradient(90deg, var(--neon-2), var(--accent)); color:#05121a; } .btn-secondary { background:#1a2236; color:var(--text); border:1px solid rgba(255,255,255,.12); } .btn-outline { border:1px solid var(--neon); color:var(--text); background: transparent; } /* Hero */ #bg-canvas { position: fixed; inset: 0; z-index: -1; } .section { padding: 4.5rem 1rem; } .hero { min-height: 80vh; display: grid; place-items: center; text-align: center; } .eyebrow { opacity:.8; letter-spacing:.25em; text-transform:uppercase; } .glow { font-size: clamp(2.4rem, 7vw, 5rem); line-height: 1.05; margin: .2rem 0 .5rem; text-shadow: 0 0 24px rgba(67,255,210,.35), 0 0 40px rgba(123,92,255,.25); } .tagline { font-size: clamp(1rem, 2.2vw, 1.2rem); color: var(--muted); } .intro { max-width: 70ch; margin: .9rem auto 1.3rem; } .cta-row { display:flex; gap:.8rem; justify-content:center; flex-wrap:wrap; } .hero-3d-hint { font-size:.9rem; color:var(--muted); margin-top:1rem; } /* Generic containers */ .container { max-width: 1100px; margin: 0 auto; } .section-title { font-size: 1.9rem; margin-bottom: 1.2rem; position: relative; } .section-title::after { content:""; position:absolute; left:0; bottom:-8px; width:72px; height:2px; background: linear-gradient(90deg, var(--neon), transparent); } /* About “3D laptop” */ .about { position: relative; } .about-3d { display:flex; justify-content:center; margin-top: 1.2rem; } .laptop3d { width: 240px; height: 150px; position: relative; perspective: 800px; } .laptop3d .lid, .laptop3d .base { position:absolute; left:50%; transform: translateX(-50%); border-radius: 10px; box-shadow: inset 0 0 20px rgba(123,92,255,.25), 0 10px 25px rgba(0,0,0,.45); } .lid { width: 240px; height: 140px; top: 0; transform-origin: bottom; background: #0d1528; border:1px solid rgba(255,255,255,.08); } .base { width: 260px; height: 22px; bottom: 0; background: #0f172a; border:1px solid rgba(255,255,255,.08); } .laptop3d:hover .lid { transform: translateX(-50%) rotateX(15deg); } /* Projects */ .projects .grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem; } .card { background: linear-gradient(180deg, rgba(26,34,54,.9), rgba(18,24,38,.95)); border: 1px solid rgba(255,255,255,.06); border-radius: 16px; padding: 1rem; box-shadow: var(--shadow); transform-style: preserve-3d; perspective: 800px; position: relative; overflow: hidden; } .card::before { content:""; position:absolute; inset: -2px; background: radial-gradient(600px 200px at var(--mx,50%) -10%, rgba(67,255,210,.16), transparent); pointer-events: none; } .card:hover { transform: translateY(-4px); } .card h3 { margin:.2rem 0 .4rem; } .card p { color: var(--muted); } .tech { display:flex; flex-wrap: wrap; gap:.35rem; margin:.6rem 0; } .tech span { font-size:.78rem; padding:.25rem .5rem; border-radius:999px; background: #0f1629; border:1px solid rgba(255,255,255,.08); } .card .links { display:flex; gap:.5rem; margin-top:.6rem; } .card .links a { font-size:.9rem; } /* Skills */ .skills-cloud { position: relative; display:flex; gap:.5rem; flex-wrap:wrap; margin-bottom: 1rem; } .skill-tag { padding:.45rem .7rem; border-radius: 999px; border:1px solid rgba(255,255,255,.08); background: linear-gradient(180deg, #0f1629, #0b1222); box-shadow: inset 0 0 16px rgba(67,255,210,.08); } .skill-bars .skill { margin:.6rem 0; } .skill-bars .bar { background:#0e1322; border-radius: 999px; height:10px; overflow:hidden; border:1px solid rgba(255,255,255,.06); } .skill-bars .bar i { display:block; height:100%; background: linear-gradient(90deg, var(--neon-2), var(--accent)); } /* Education */ .edu-card { background: var(--card); border:1px solid rgba(255,255,255,.06); border-radius:14px; padding: 1rem; box-shadow: var(--shadow); max-width: 720px; } /* Contact */ .contact-cards { display:grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap:.8rem; margin-bottom: 1rem; } .contact-card { background: var(--card); border:1px solid rgba(255,255,255,.06); border-radius:12px; padding:.9rem; color:var(--text); text-decoration:none; } .contact-card:hover { border-color: var(--neon); box-shadow: 0 0 22px var(--ring); } .contact-form { display:grid; gap:.75rem; max-width: 700px; } .field { display:grid; gap:.35rem; } input, textarea { width:100%; padding:.8rem; border-radius:10px; border:1px solid rgba(255,255,255,.12); color:var(--text); background: #0e1424; } input:focus, textarea:focus { outline: 2px solid var(--neon); box-shadow: 0 0 0 6px var(--ring); } .form-status { min-height: 1.2em; color: var(--muted); } /* Scroll to top */ .to-top { position: fixed; right: 1rem; bottom: 1rem; width: 42px; height: 42px; border-radius: 999px; border: 1px solid rgba(255,255,255,.12); background: #0f1629; color: var(--text); display: grid; place-items: center; opacity: 0; pointer-events: none; transition: opacity .2s ease, transform .2s ease; } .to-top.show { opacity: 1; pointer-events: auto; } .to-top:hover { transform: translateY(-3px); } /* Mobile Nav */ @media (max-width: 900px) { .nav-toggle { display:block; background:transparent; color:var(--text); border:1px solid rgba(255,255,255,.2); border-radius:8px; padding:.35rem .6rem; } .nav-list { position:absolute; right:1rem; top:58px; background:#0f1629; border:1px solid rgba(255,255,255,.12); border-radius:12px; padding:.6rem; display:none; flex-direction:column; min-width: 220px; } .nav-list.open { display:flex; } } /* Reduced motion */ @media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
projects.jsjs// Centralized project data so you can add/update easily. window.PROJECTS = [ { title: "Weather API App", description: "Real-time weather dashboard with search, temperature, humidity, and forecast.", tech: ["HTML", "CSS", "JavaScript", "OpenWeather API"], github: "https://github.com/VedantKale244", // replace with repo demo: "#" }, { title: "Smart Solar Tracking System", description: "Arduino Uno–based IoT system that tracks sun position to maximize panel efficiency.", tech: ["Arduino", "C/C++", "Sensors", "IoT"], github: "https://github.com/VedantKale244", demo: "#" }, { title: "Google Gemini Chatbot Clone", description: "A modern chatbot UI/UX with Node.js backend and streaming responses.", tech: ["Node.js", "Express.js", "JavaScript", "WebSockets"], github: "https://github.com/VedantKale244", demo: "#" }, { title: "Smart Traffic Light System", description: "Desktop simulation with Java + Swing GUI and basic traffic algorithms.", tech: ["Java", "Swing", "Animations"], github: "https://github.com/VedantKale244", demo: "#" }, { title: "Smart Farm Simulation", description: "Java-based sensor simulation with JDBC logging and a control panel GUI.", tech: ["Java", "JDBC", "GUI"], github: "https://github.com/VedantKale244", demo: "#" } ]; window.SKILLS = [ "JavaScript","React","Node.js","Express.js","MongoDB","HTML","CSS","Java","Python","DSA" ];
three-bg.jsjs/* Lightweight Three.js scene: floating spheres & rotating wireframe torus. Optimized for performance, pauses on hidden tab, reacts to mouse. */ (() => { const canvas = document.getElementById('bg-canvas'); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 200); camera.position.z = 28; const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setSize(window.innerWidth, window.innerHeight); // Lights const light1 = new THREE.PointLight(0x7b5cff, 1.2, 0); light1.position.set(10, 10, 10); scene.add(light1); const light2 = new THREE.PointLight(0x00e5ff, 1.1, 0); light2.position.set(-10, -6, -10); scene.add(light2); scene.add(new THREE.AmbientLight(0x334455, 0.5)); // Torus (wireframe) const torus = new THREE.Mesh( new THREE.TorusKnotGeometry(5, 1.2, 150, 20), new THREE.MeshPhongMaterial({ color: 0x43ffd2, wireframe: true, transparent: true, opacity: 0.25 }) ); scene.add(torus); // Floating spheres const sphereGeo = new THREE.SphereGeometry(0.55, 24, 24); const spheres = []; const mat1 = new THREE.MeshStandardMaterial({ color: 0x7b5cff, metalness: 0.6, roughness: 0.25 }); const mat2 = new THREE.MeshStandardMaterial({ color: 0x00e5ff, metalness: 0.6, roughness: 0.25 }); for (let i = 0; i < 36; i++) { const m = i % 2 ? mat1 : mat2; const s = new THREE.Mesh(sphereGeo, m); s.position.set((Math.random() - 0.5) * 36, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 28); scene.add(s); spheres.push(s); } // Mouse parallax const targetRot = { x: 0, y: 0 }; window.addEventListener('pointermove', (e) => { const nx = (e.clientX / window.innerWidth) * 2 - 1; const ny = (e.clientY / window.innerHeight) * 2 - 1; targetRot.x = ny * 0.15; targetRot.y = nx * 0.25; }); // Resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); // Respect reduced motion const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; let raf, last = 0; function animate(t = 0) { const dt = (t - last) / 1000; last = t; // Smoothly approach mouse target rotation torus.rotation.x += (targetRot.x - torus.rotation.x) * 0.05; torus.rotation.y += (targetRot.y - torus.rotation.y) * 0.05; if (!reduced) { torus.rotation.z += 0.08 * dt; spheres.forEach((s, i) => { s.position.y += Math.sin(t * 0.001 + i) * 0.003; s.rotation.y += 0.3 * dt; }); } renderer.render(scene, camera); raf = requestAnimationFrame(animate); } // Pause when tab is hidden to save power document.addEventListener('visibilitychange', () => { if (document.hidden) cancelAnimationFrame(raf); else { last = 0; raf = requestAnimationFrame(animate); } }); raf = requestAnimationFrame(animate); })();
main.jsjs// Mobile nav const navToggle = document.querySelector('.nav-toggle'); const navList = document.querySelector('.nav-list'); navToggle?.addEventListener('click', () => { const open = navList.classList.toggle('open'); navToggle.setAttribute('aria-expanded', String(open)); }); // Project cards (interactive “3D-ish”) const grid = document.getElementById('projects-grid'); const setCardGlow = (card, e) => { const r = card.getBoundingClientRect(); const mx = ((e.clientX - r.left) / r.width) * 100; card.style.setProperty('--mx', `${mx}%`); }; if (grid && window.PROJECTS) { for (const p of window.PROJECTS) { const el = document.createElement('article'); el.className = 'card'; el.innerHTML = ` <h3>${p.title}</h3> <p>${p.description}</p> <div class="tech">${p.tech.map(t=>`<span>${t}</span>`).join('')}</div> <div class="links"> <a class="btn btn-secondary" href="${p.github}" target="_blank" rel="noopener">GitHub</a> ${p.demo && p.demo !== "#" ? `<a class="btn btn-primary" href="${p.demo}" target="_blank" rel="noopener">Live Demo</a>` : `<a class="btn btn-outline" href="#" aria-disabled="true" onclick="return false;">Demo Soon</a>`} </div> `; el.addEventListener('mousemove', (e)=>setCardGlow(el,e)); grid.appendChild(el); } } // Skills cloud const cloud = document.querySelector('.skills-cloud'); if (cloud && window.SKILLS) { window.SKILLS.forEach((s, idx) => { const tag = document.createElement('span'); tag.className = 'skill-tag'; tag.textContent = s; tag.style.transform = `translateZ(0)`; tag.style.animation = `float${idx%3} 6s ease-in-out ${idx*0.15}s infinite`; cloud.appendChild(tag); }); // Keyframes injected for soft float const style = document.createElement('style'); style.textContent = ` @keyframes float0 { 0%,100%{ transform: translateY(0)} 50%{ transform: translateY(-6px)} } @keyframes float1 { 0%,100%{ transform: translateY(0)} 50%{ transform: translateY(-10px)} } @keyframes float2 { 0%,100%{ transform: translateY(0)} 50%{ transform: translateY(-4px)} }`; document.head.appendChild(style); } // Scroll-to-top const toTop = document.getElementById('to-top'); const onScroll = () => { if (window.scrollY > 500) toTop.classList.add('show'); else toTop.classList.remove('show'); }; window.addEventListener('scroll', onScroll, { passive: true }); toTop?.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' })); // EmailJS (client-side) const form = document.getElementById('contact-form'); const statusEl = document.getElementById('form-status'); // Initialize EmailJS (replace with your public key) const EMAILJS_PUBLIC_KEY = "YOUR_EMAILJS_PUBLIC_KEY"; // e.g. "9BmdXXXXXXX" const EMAILJS_SERVICE_ID = "YOUR_SERVICE_ID"; const EMAILJS_TEMPLATE_ID = "YOUR_TEMPLATE_ID"; // Lazy load EmailJS lib only when needed let emailjsLoaded = false; async function ensureEmailJS() { if (emailjsLoaded) return; await new Promise((resolve, reject) => { const s = document.createElement('script'); s.src = "https://cdn.jsdelivr.net/npm/@emailjs/browser@4/dist/email.min.js"; s.async = true; s.onload = resolve; s.onerror = reject; document.body.appendChild(s); }); // global emailjs available window.emailjs?.init({ publicKey: EMAILJS_PUBLIC_KEY }); emailjsLoaded = true; } form?.addEventListener('submit', async (e) => { e.preventDefault(); statusEl.textContent = "Sending…"; const data = { from_name: form.name.value, reply_to: form.email.value, message: form.message.value }; try { await ensureEmailJS(); if (!EMAILJS_PUBLIC_KEY || EMAILJS_PUBLIC_KEY.startsWith("YOUR_")) { throw new Error("Configure EmailJS keys in main.js"); } await window.emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, data); statusEl.textContent = "Thanks! Your message has been sent."; form.reset(); } catch (err) { console.error(err); statusEl.textContent = "Couldn’t send right now. Please email me directly at [email protected]."; } });
assets/resume.pdf (or update the link in the navbar).projects.js to add real GitHub and demos.from_name, reply_to, message.YOUR_EMAILJS_PUBLIC_KEY, YOUR_SERVICE_ID, YOUR_TEMPLATE_ID in main.js.your-domain.com with your actual domain (or GitHub Pages URL).devicePixelRatio <= 2.prefers-reduced-motion.<model-viewer> (adds ~100–200 KB).IntersectionObserver for subtle fades.If you’d like, I can also turn this into a React + Vite project with componentized sections and routing, or wire the projects to auto-pull pinned repos from your GitHub.