Initial commit: iOS developer portfolio with bio-digital aesthetic

Features:
- Hero section with animated circuit traces and botanical SVG decorations
- Pulsing circuit animations with fading comet tails connecting plant elements
- Mobile-focused About section highlighting Swift and Flutter expertise
- Technologies & Skills showcase with properly padded badges
- Contact form with validation and EmailJS integration
- Responsive design with Tailwind CSS v4 and custom sage/charcoal color palette
- Smooth scroll animations and fade-in effects
- Centered layout with explicit inline styles for Tailwind v4 compatibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-24 15:23:27 -06:00
commit 39e61fc617
18 changed files with 4713 additions and 0 deletions

21
src/App.jsx Normal file
View File

@@ -0,0 +1,21 @@
import Header from './components/layout/Header';
import Footer from './components/layout/Footer';
import Hero from './components/sections/Hero';
import About from './components/sections/About';
import Contact from './components/sections/Contact';
function App() {
return (
<div className="min-h-screen w-full overflow-x-hidden">
<Header />
<main className="w-full">
<Hero />
<About />
<Contact />
</main>
<Footer />
</div>
);
}
export default App;

1
src/assets/react.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,57 @@
import { Github, Linkedin, Mail, ArrowUp } from 'lucide-react';
const Footer = () => {
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const socialLinks = [
{ icon: Github, href: '#', label: 'GitHub' },
{ icon: Linkedin, href: '#', label: 'LinkedIn' },
{ icon: Mail, href: '#contact', label: 'Email' },
];
return (
<footer className="bg-white border-t border-sage-200">
<div className="max-w-6xl mx-auto px-6 py-8">
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
{/* Social Links */}
<div className="flex items-center gap-4">
{socialLinks.map((link) => {
const Icon = link.icon;
return (
<a
key={link.label}
href={link.href}
className="text-charcoal-600 hover:text-sage-600 transition-colors"
aria-label={link.label}
target={link.href.startsWith('http') ? '_blank' : undefined}
rel={link.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
<Icon size={20} />
</a>
);
})}
</div>
{/* Copyright */}
<p className="text-charcoal-600 text-sm">
© {new Date().getFullYear()} All rights reserved
</p>
{/* Back to Top */}
<button
onClick={scrollToTop}
className="text-charcoal-600 hover:text-sage-600 transition-colors flex items-center gap-2"
aria-label="Back to top"
>
<span className="text-sm">Back to top</span>
<ArrowUp size={16} />
</button>
</div>
</div>
</footer>
);
};
export default Footer;

View File

@@ -0,0 +1,69 @@
import { useState } from 'react';
import { Menu, X } from 'lucide-react';
const Header = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const scrollToSection = (sectionId) => {
const element = document.getElementById(sectionId);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
setIsMenuOpen(false);
}
};
const navItems = [
{ label: 'Home', id: 'hero' },
{ label: 'About', id: 'about' },
{ label: 'Contact', id: 'contact' },
];
return (
<header className="fixed top-0 left-0 right-0 bg-white/80 backdrop-blur-md shadow-sm z-50">
<nav className="max-w-6xl mx-auto px-6 py-4">
<div className="flex items-center justify-center md:justify-center">
{/* Desktop Navigation */}
<ul className="hidden md:flex items-center gap-8">
{navItems.map((item) => (
<li key={item.id}>
<button
onClick={() => scrollToSection(item.id)}
className="text-charcoal-700 hover:text-sage-600 transition-colors font-medium"
>
{item.label}
</button>
</li>
))}
</ul>
{/* Mobile Menu Button */}
<button
onClick={() => setIsMenuOpen(!isMenuOpen)}
className="md:hidden absolute right-6 text-charcoal-700 hover:text-sage-600 transition-colors"
aria-label="Toggle menu"
>
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
{/* Mobile Navigation */}
{isMenuOpen && (
<ul className="md:hidden mt-4 py-4 space-y-4 border-t border-sage-200">
{navItems.map((item) => (
<li key={item.id}>
<button
onClick={() => scrollToSection(item.id)}
className="block w-full text-left text-charcoal-700 hover:text-sage-600 transition-colors font-medium"
>
{item.label}
</button>
</li>
))}
</ul>
)}
</nav>
</header>
);
};
export default Header;

View File

@@ -0,0 +1,135 @@
import { Code, Smartphone, Zap, Globe } from 'lucide-react';
const About = () => {
const skills = [
'Swift',
'Flutter',
'Dart',
'iOS Development',
'UIKit',
'SwiftUI',
'Xcode',
'Firebase',
'REST APIs',
'Git',
'Core Data',
'Cross-platform Development',
];
const highlights = [
{
icon: Code,
title: 'Clean Code',
description: 'Writing maintainable, scalable, and efficient code',
},
{
icon: Zap,
title: 'Fast Learner',
description: 'Quickly adapting to new technologies and frameworks',
},
{
icon: Smartphone,
title: 'Mobile Specialist',
description: 'iOS expertise with Swift and cross-platform Flutter development',
},
{
icon: Globe,
title: 'Collaborative',
description: 'Working effectively in team environments',
},
];
return (
<section id="about" className="py-20 bg-gradient-to-b from-sage-50 via-white to-white relative w-full">
{/* Decorative top wave transition */}
<div className="absolute top-0 left-0 right-0 h-24 bg-gradient-to-b from-white/0 to-white/100 -mt-24 pointer-events-none"></div>
<div className="max-w-6xl mx-auto px-6" style={{marginLeft: 'auto', marginRight: 'auto'}}>
{/* Section Header */}
<div className="text-center mb-16 opacity-0 animate-fadeInUp">
<h2 className="text-4xl md:text-5xl font-bold text-charcoal-900 mb-4">
About Me
</h2>
<div className="w-20 h-1 bg-sage-600 mb-8" style={{marginLeft: 'auto', marginRight: 'auto'}}></div>
<p className="text-lg text-charcoal-600 max-w-2xl" style={{marginLeft: 'auto', marginRight: 'auto', textAlign: 'center'}}>
I'm an iOS developer passionate about creating innovative mobile experiences and building
user-friendly applications. With expertise in Swift and Flutter, I specialize in crafting
elegant native and cross-platform solutions. I enjoy tackling challenging problems and
continuously learning new technologies.
</p>
</div>
{/* Highlights Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-16">
{highlights.map((highlight, index) => {
const Icon = highlight.icon;
const delayClass = `delay-${(index + 1) * 100}`;
return (
<div
key={highlight.title}
className={`bg-sage-50 rounded-xl p-6 hover:shadow-lg transition-shadow border border-sage-100 opacity-0 animate-fadeInUp ${delayClass}`}
>
<div className="w-12 h-12 bg-sage-200 rounded-lg flex items-center justify-center mb-4">
<Icon className="text-sage-700" size={24} />
</div>
<h3 className="text-lg font-semibold text-charcoal-900 mb-2">
{highlight.title}
</h3>
<p className="text-sm text-charcoal-600">
{highlight.description}
</p>
</div>
);
})}
</div>
{/* Skills Section */}
<div className="bg-gradient-to-br from-sage-50 to-white rounded-2xl p-8 md:p-12 opacity-0 animate-fadeIn delay-400">
<h3 className="text-2xl font-bold text-charcoal-900 mb-6 text-center">
Technologies & Skills
</h3>
<div className="flex flex-wrap justify-center gap-3">
{skills.map((skill) => (
<span
key={skill}
className="py-4 bg-white text-charcoal-700 rounded-lg font-medium shadow-sm hover:shadow-md transition-shadow border border-sage-200"
style={{paddingLeft: '3rem', paddingRight: '3rem'}}
>
{skill}
</span>
))}
</div>
</div>
{/* Resume Download */}
<div className="text-center mt-12">
<a
href="#"
className="inline-flex items-center gap-2 px-6 py-3 bg-sage-600 text-white rounded-lg font-medium hover:bg-sage-700 transition-colors shadow-md hover:shadow-lg"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
Download Resume
</a>
<p className="text-sm text-charcoal-500 mt-2">
{/* TODO: Add your resume link */}
(Link your resume PDF here)
</p>
</div>
</div>
</section>
);
};
export default About;

View File

@@ -0,0 +1,206 @@
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import emailjs from '@emailjs/browser';
import { Mail, User, MessageSquare, Send, CheckCircle, AlertCircle } from 'lucide-react';
const Contact = () => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitStatus, setSubmitStatus] = useState(null); // 'success' | 'error' | null
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm();
const onSubmit = async (data) => {
setIsSubmitting(true);
setSubmitStatus(null);
try {
// TODO: Set up EmailJS account and replace these values
// 1. Go to https://www.emailjs.com/ and create a free account
// 2. Add an email service (Gmail, Outlook, etc.)
// 3. Create an email template
// 4. Replace the values below with your actual IDs
const EMAILJS_SERVICE_ID = 'YOUR_SERVICE_ID';
const EMAILJS_TEMPLATE_ID = 'YOUR_TEMPLATE_ID';
const EMAILJS_PUBLIC_KEY = 'YOUR_PUBLIC_KEY';
await emailjs.send(
EMAILJS_SERVICE_ID,
EMAILJS_TEMPLATE_ID,
{
from_name: data.name,
from_email: data.email,
message: data.message,
},
EMAILJS_PUBLIC_KEY
);
setSubmitStatus('success');
reset();
} catch (error) {
console.error('Failed to send email:', error);
setSubmitStatus('error');
} finally {
setIsSubmitting(false);
}
};
return (
<section id="contact" className="py-20 bg-gradient-to-br from-sage-50 to-white w-full">
<div className="max-w-4xl mx-auto px-6" style={{marginLeft: 'auto', marginRight: 'auto'}}>
{/* Section Header */}
<div className="text-center mb-12">
<h2 className="text-4xl md:text-5xl font-bold text-charcoal-900 mb-4">
Get In Touch
</h2>
<div className="w-20 h-1 bg-sage-600 mx-auto mb-8"></div>
<p className="text-lg text-charcoal-600 max-w-2xl mx-auto">
Have a question or want to work together? Feel free to reach out!
</p>
</div>
<div className="bg-white rounded-2xl shadow-xl p-8 md:p-12">
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
{/* Name Field */}
<div>
<label htmlFor="name" className="block text-sm font-medium text-charcoal-700 mb-2">
<User className="inline-block w-4 h-4 mr-2" />
Your Name
</label>
<input
id="name"
type="text"
{...register('name', {
required: 'Name is required',
minLength: { value: 2, message: 'Name must be at least 2 characters' }
})}
className={`w-full px-4 py-3 rounded-lg border ${
errors.name ? 'border-red-500' : 'border-sage-300'
} focus:outline-none focus:ring-2 focus:ring-sage-500 transition-colors`}
placeholder="John Doe"
/>
{errors.name && (
<p className="mt-1 text-sm text-red-600 flex items-center gap-1">
<AlertCircle size={14} />
{errors.name.message}
</p>
)}
</div>
{/* Email Field */}
<div>
<label htmlFor="email" className="block text-sm font-medium text-charcoal-700 mb-2">
<Mail className="inline-block w-4 h-4 mr-2" />
Email Address
</label>
<input
id="email"
type="email"
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email address',
},
})}
className={`w-full px-4 py-3 rounded-lg border ${
errors.email ? 'border-red-500' : 'border-sage-300'
} focus:outline-none focus:ring-2 focus:ring-sage-500 transition-colors`}
placeholder="john@example.com"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600 flex items-center gap-1">
<AlertCircle size={14} />
{errors.email.message}
</p>
)}
</div>
{/* Message Field */}
<div>
<label htmlFor="message" className="block text-sm font-medium text-charcoal-700 mb-2">
<MessageSquare className="inline-block w-4 h-4 mr-2" />
Message
</label>
<textarea
id="message"
rows={6}
{...register('message', {
required: 'Message is required',
minLength: { value: 10, message: 'Message must be at least 10 characters' },
})}
className={`w-full px-4 py-3 rounded-lg border ${
errors.message ? 'border-red-500' : 'border-sage-300'
} focus:outline-none focus:ring-2 focus:ring-sage-500 transition-colors resize-none`}
placeholder="Tell me about your project or just say hi!"
/>
{errors.message && (
<p className="mt-1 text-sm text-red-600 flex items-center gap-1">
<AlertCircle size={14} />
{errors.message.message}
</p>
)}
</div>
{/* Submit Button */}
<button
type="submit"
disabled={isSubmitting}
className="w-full px-8 py-4 bg-sage-600 text-white rounded-lg font-medium hover:bg-sage-700 transition-colors shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{isSubmitting ? (
<>
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
Sending...
</>
) : (
<>
<Send size={20} />
Send Message
</>
)}
</button>
{/* Status Messages */}
{submitStatus === 'success' && (
<div className="p-4 bg-green-50 border border-green-200 rounded-lg flex items-center gap-2 text-green-800">
<CheckCircle size={20} />
<p>Message sent successfully! I'll get back to you soon.</p>
</div>
)}
{submitStatus === 'error' && (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg flex items-center gap-2 text-red-800">
<AlertCircle size={20} />
<p>Failed to send message. Please try again or contact me directly at [your-email@example.com]</p>
</div>
)}
</form>
{/* Alternative Contact Info */}
<div className="mt-8 pt-8 border-t border-sage-200">
<p className="text-center text-charcoal-600 mb-4">
Or reach out directly:
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 text-sm">
<a
href="mailto:your-email@example.com"
className="flex items-center gap-2 text-sage-600 hover:text-sage-700 transition-colors"
>
<Mail size={16} />
your-email@example.com
</a>
</div>
</div>
</div>
</div>
</section>
);
};
export default Contact;

View File

@@ -0,0 +1,375 @@
import { ArrowDown } from 'lucide-react';
const Hero = () => {
const scrollToAbout = () => {
document.getElementById('about')?.scrollIntoView({ behavior: 'smooth' });
};
const scrollToContact = () => {
document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' });
};
return (
<section id="hero" className="min-h-screen flex items-center justify-center relative bg-gradient-to-br from-sage-50 to-white overflow-hidden">
{/* Animated circuit connections */}
<svg className="absolute inset-0 w-full h-full pointer-events-none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none" viewBox="0 0 1920 1080">
<defs>
{/* Radial gradients for pulse glow effect */}
<radialGradient id="pulseGlow1">
<stop offset="0%" stopColor="#7fb069" stopOpacity="1"/>
<stop offset="50%" stopColor="#7fb069" stopOpacity="0.6"/>
<stop offset="100%" stopColor="#7fb069" stopOpacity="0"/>
</radialGradient>
<radialGradient id="pulseGlow2">
<stop offset="0%" stopColor="#5a9650" stopOpacity="1"/>
<stop offset="50%" stopColor="#5a9650" stopOpacity="0.6"/>
<stop offset="100%" stopColor="#5a9650" stopOpacity="0"/>
</radialGradient>
{/* Circuit pulse animation */}
<style>
{`
@keyframes pulse-node {
0%, 100% { opacity: 0.3; r: 6; }
50% { opacity: 0.8; r: 10; }
}
.circuit-node {
animation: pulse-node 2s ease-in-out infinite;
}
`}
</style>
</defs>
{/* Solid circuit paths */}
{/* Path 1: Top left to middle left */}
<path id="path1" d="M 90 90 L 90 300 L 160 300 L 160 540"
stroke="#7fb069"
strokeWidth="2"
fill="none"
opacity="0.3" />
{/* Path 2: Middle left to bottom left */}
<path id="path2" d="M 160 540 L 160 850 L 90 850"
stroke="#5a9650"
strokeWidth="2"
fill="none"
opacity="0.3" />
{/* Path 3: Bottom left to bottom right */}
<path id="path3" d="M 90 850 L 90 950 L 1800 950 L 1800 900"
stroke="#5a9650"
strokeWidth="2"
fill="none"
opacity="0.3" />
{/* Path 4: Bottom right to middle right */}
<path id="path4" d="M 1800 900 L 1830 900 L 1830 360 L 1760 360"
stroke="#4a7c42"
strokeWidth="2"
fill="none"
opacity="0.3" />
{/* Path 5: Middle right to top right */}
<path id="path5" d="M 1760 360 L 1776 360 L 1776 180"
stroke="#7fb069"
strokeWidth="2"
fill="none"
opacity="0.3" />
{/* Path 6: Top right to top left */}
<path id="path6" d="M 1776 180 L 1776 40 L 90 40 L 90 90"
stroke="#7fb069"
strokeWidth="2"
fill="none"
opacity="0.3" />
{/* Additional center-crossing circuit traces */}
{/* Horizontal center traces */}
<path d="M 300 540 L 1620 540"
stroke="#7fb069"
strokeWidth="1.5"
fill="none"
opacity="0.2" />
<path d="M 400 700 L 1520 700"
stroke="#5a9650"
strokeWidth="1.5"
fill="none"
opacity="0.2" />
<path d="M 350 380 L 1570 380"
stroke="#4a7c42"
strokeWidth="1.5"
fill="none"
opacity="0.2" />
{/* Vertical center traces */}
<path d="M 960 200 L 960 880"
stroke="#7fb069"
strokeWidth="1.5"
fill="none"
opacity="0.2" />
{/* Diagonal traces */}
<path d="M 400 300 L 700 500"
stroke="#5a9650"
strokeWidth="1.5"
fill="none"
opacity="0.15" />
<path d="M 1220 500 L 1520 300"
stroke="#5a9650"
strokeWidth="1.5"
fill="none"
opacity="0.15" />
<path d="M 500 780 L 800 600"
stroke="#4a7c42"
strokeWidth="1.5"
fill="none"
opacity="0.15" />
<path d="M 1120 600 L 1420 780"
stroke="#4a7c42"
strokeWidth="1.5"
fill="none"
opacity="0.15" />
{/* Small circuit nodes at intersections */}
<circle cx="960" cy="540" r="3" fill="#7fb069" opacity="0.4" />
<circle cx="400" cy="700" r="3" fill="#5a9650" opacity="0.4" />
<circle cx="1520" cy="700" r="3" fill="#5a9650" opacity="0.4" />
<circle cx="350" cy="380" r="3" fill="#4a7c42" opacity="0.4" />
<circle cx="1570" cy="380" r="3" fill="#4a7c42" opacity="0.4" />
<circle cx="960" cy="200" r="3" fill="#7fb069" opacity="0.4" />
<circle cx="960" cy="880" r="3" fill="#7fb069" opacity="0.4" />
<circle cx="700" cy="500" r="3" fill="#5a9650" opacity="0.4" />
<circle cx="1220" cy="500" r="3" fill="#5a9650" opacity="0.4" />
{/* Complete circuit path for continuous pulse */}
<path id="fullCircuit" d="M 90 90 L 90 300 L 160 300 L 160 540 L 160 850 L 90 850 L 90 950 L 1800 950 L 1800 900 L 1830 900 L 1830 360 L 1760 360 L 1776 360 L 1776 180 L 1776 40 L 90 40 L 90 90"
fill="none"
opacity="0"/>
{/* Animated pulse with tails - First pulse */}
{/* Tail segments (fading trail behind main pulse) */}
<circle r="14" fill="url(#pulseGlow1)" opacity="0">
<animateMotion dur="12s" repeatCount="indefinite" begin="0.15s">
<mpath href="#fullCircuit"/>
</animateMotion>
<animate attributeName="opacity" values="0;0.3;0.3;0.3;0.3;0.3;0.3;0.3;0.3;0" dur="12s" repeatCount="indefinite" begin="0.15s"/>
</circle>
<circle r="16" fill="url(#pulseGlow1)" opacity="0">
<animateMotion dur="12s" repeatCount="indefinite" begin="0.3s">
<mpath href="#fullCircuit"/>
</animateMotion>
<animate attributeName="opacity" values="0;0.2;0.2;0.2;0.2;0.2;0.2;0.2;0.2;0" dur="12s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle r="18" fill="url(#pulseGlow1)" opacity="0">
<animateMotion dur="12s" repeatCount="indefinite" begin="0.45s">
<mpath href="#fullCircuit"/>
</animateMotion>
<animate attributeName="opacity" values="0;0.1;0.1;0.1;0.1;0.1;0.1;0.1;0.1;0" dur="12s" repeatCount="indefinite" begin="0.45s"/>
</circle>
{/* Main pulse head */}
<circle r="12" fill="url(#pulseGlow1)" opacity="0">
<animateMotion dur="12s" repeatCount="indefinite" begin="0s">
<mpath href="#fullCircuit"/>
</animateMotion>
<animate attributeName="opacity" values="0;1;1;1;1;1;1;1;1;0" dur="12s" repeatCount="indefinite" begin="0s"/>
</circle>
{/* Animated pulse with tails - Second pulse */}
{/* Tail segments */}
<circle r="14" fill="url(#pulseGlow2)" opacity="0">
<animateMotion dur="12s" repeatCount="indefinite" begin="6.15s">
<mpath href="#fullCircuit"/>
</animateMotion>
<animate attributeName="opacity" values="0;0.3;0.3;0.3;0.3;0.3;0.3;0.3;0.3;0" dur="12s" repeatCount="indefinite" begin="6.15s"/>
</circle>
<circle r="16" fill="url(#pulseGlow2)" opacity="0">
<animateMotion dur="12s" repeatCount="indefinite" begin="6.3s">
<mpath href="#fullCircuit"/>
</animateMotion>
<animate attributeName="opacity" values="0;0.2;0.2;0.2;0.2;0.2;0.2;0.2;0.2;0" dur="12s" repeatCount="indefinite" begin="6.3s"/>
</circle>
<circle r="18" fill="url(#pulseGlow2)" opacity="0">
<animateMotion dur="12s" repeatCount="indefinite" begin="6.45s">
<mpath href="#fullCircuit"/>
</animateMotion>
<animate attributeName="opacity" values="0;0.1;0.1;0.1;0.1;0.1;0.1;0.1;0.1;0" dur="12s" repeatCount="indefinite" begin="6.45s"/>
</circle>
{/* Main pulse head */}
<circle r="12" fill="url(#pulseGlow2)" opacity="0">
<animateMotion dur="12s" repeatCount="indefinite" begin="6s">
<mpath href="#fullCircuit"/>
</animateMotion>
<animate attributeName="opacity" values="0;1;1;1;1;1;1;1;1;0" dur="12s" repeatCount="indefinite" begin="6s"/>
</circle>
{/* Static circuit nodes at plant centers */}
<circle cx="90" cy="90" r="6" fill="#7fb069" className="circuit-node" />
<circle cx="1776" cy="180" r="6" fill="#7fb069" className="circuit-node" style={{animationDelay: '0.3s'}} />
<circle cx="1800" cy="900" r="6" fill="#5a9650" className="circuit-node" style={{animationDelay: '0.6s'}} />
<circle cx="90" cy="850" r="6" fill="#5a9650" className="circuit-node" style={{animationDelay: '0.9s'}} />
<circle cx="160" cy="540" r="6" fill="#4a7c42" className="circuit-node" style={{animationDelay: '1.2s'}} />
<circle cx="1760" cy="360" r="6" fill="#4a7c42" className="circuit-node" style={{animationDelay: '1.5s'}} />
</svg>
{/* Decorative plant elements */}
{/* Top left - stem with branches */}
<div className="absolute top-10 left-10 w-32 h-32 opacity-20">
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
<path
d="M100 20C100 20 80 60 80 100C80 140 100 180 100 180"
stroke="currentColor"
strokeWidth="3"
className="text-sage-600"
/>
<path
d="M100 50C100 50 130 70 140 100C150 130 130 150 130 150"
stroke="currentColor"
strokeWidth="2"
className="text-sage-500"
/>
<path
d="M100 50C100 50 70 70 60 100C50 130 70 150 70 150"
stroke="currentColor"
strokeWidth="2"
className="text-sage-500"
/>
</svg>
</div>
{/* Bottom right - plant with leaves */}
<div className="absolute bottom-20 right-20 w-40 h-40 opacity-20">
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
<circle cx="100" cy="100" r="5" fill="currentColor" className="text-sage-600" />
<path
d="M100 95C100 95 110 70 120 50"
stroke="currentColor"
strokeWidth="2"
className="text-sage-500"
/>
<ellipse cx="125" cy="40" rx="8" ry="12" fill="currentColor" className="text-sage-400" />
<path
d="M100 95C100 95 85 70 70 55"
stroke="currentColor"
strokeWidth="2"
className="text-sage-500"
/>
<ellipse cx="65" cy="48" rx="7" ry="10" fill="currentColor" className="text-sage-400" />
</svg>
</div>
{/* Top right - fern-like plant */}
<div className="absolute top-32 right-16 w-36 h-36 opacity-20">
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
<path
d="M100 180C100 180 100 100 100 20"
stroke="currentColor"
strokeWidth="2"
className="text-sage-600"
/>
<ellipse cx="80" cy="60" rx="20" ry="8" fill="currentColor" className="text-sage-400" />
<ellipse cx="120" cy="80" rx="22" ry="9" fill="currentColor" className="text-sage-400" />
<ellipse cx="75" cy="100" rx="24" ry="10" fill="currentColor" className="text-sage-500" />
<ellipse cx="125" cy="120" rx="26" ry="11" fill="currentColor" className="text-sage-500" />
<ellipse cx="70" cy="140" rx="20" ry="8" fill="currentColor" className="text-sage-400" />
</svg>
</div>
{/* Bottom left - monstera-style leaf */}
<div className="absolute bottom-32 left-16 w-28 h-28 opacity-20">
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
<path
d="M100 160C100 160 60 140 50 100C40 60 70 30 100 40C130 30 160 60 150 100C140 140 100 160 100 160Z"
fill="currentColor"
className="text-sage-400"
/>
<path
d="M100 160L100 40"
stroke="white"
strokeWidth="4"
className="opacity-50"
/>
<path
d="M80 100C80 100 90 90 100 90C110 90 120 100 120 100"
stroke="white"
strokeWidth="3"
className="opacity-50"
/>
</svg>
</div>
{/* Middle left - small sprout */}
<div className="absolute top-1/2 left-32 w-20 h-20 opacity-20">
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
<path
d="M100 150C100 150 100 120 100 80"
stroke="currentColor"
strokeWidth="2"
className="text-sage-600"
/>
<ellipse cx="85" cy="70" rx="15" ry="20" fill="currentColor" className="text-sage-500" transform="rotate(-20 85 70)" />
<ellipse cx="115" cy="70" rx="15" ry="20" fill="currentColor" className="text-sage-500" transform="rotate(20 115 70)" />
</svg>
</div>
{/* Middle right - circular leaf pattern */}
<div className="absolute top-1/3 right-32 w-24 h-24 opacity-20">
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
<circle cx="100" cy="100" r="8" fill="currentColor" className="text-sage-600" />
<ellipse cx="100" cy="60" rx="12" ry="25" fill="currentColor" className="text-sage-400" />
<ellipse cx="130" cy="85" rx="12" ry="25" fill="currentColor" className="text-sage-400" transform="rotate(60 130 85)" />
<ellipse cx="130" cy="115" rx="12" ry="25" fill="currentColor" className="text-sage-400" transform="rotate(120 130 115)" />
<ellipse cx="100" cy="140" rx="12" ry="25" fill="currentColor" className="text-sage-400" transform="rotate(180 100 140)" />
<ellipse cx="70" cy="115" rx="12" ry="25" fill="currentColor" className="text-sage-400" transform="rotate(240 70 115)" />
<ellipse cx="70" cy="85" rx="12" ry="25" fill="currentColor" className="text-sage-400" transform="rotate(300 70 85)" />
</svg>
</div>
{/* Content */}
<div className="max-w-4xl mx-auto px-6 text-center z-10">
<h1 className="text-5xl md:text-7xl font-bold text-charcoal-900 mb-6">
Hi, I'm <span className="text-sage-600">Michael Simard</span>
</h1>
<p className="text-xl md:text-2xl text-charcoal-600 mb-8 max-w-2xl" style={{marginLeft: 'auto', marginRight: 'auto', textAlign: 'center'}}>
iOS developer crafting elegant mobile experiences with Swift and Flutter
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 mb-12">
<button
onClick={scrollToAbout}
className="py-3 bg-sage-600 text-white rounded-lg font-medium hover:bg-sage-700 transition-colors shadow-md hover:shadow-lg"
style={{paddingLeft: '3rem', paddingRight: '3rem'}}
>
Learn More About Me
</button>
<button
onClick={scrollToContact}
className="py-3 bg-white text-sage-600 border-2 border-sage-600 rounded-lg font-medium hover:bg-sage-50 transition-colors"
style={{paddingLeft: '3rem', paddingRight: '3rem'}}
>
Get In Touch
</button>
</div>
{/* Scroll indicator */}
<div className="animate-bounce">
<ArrowDown className="mx-auto text-sage-600" size={32} />
</div>
</div>
</section>
);
};
export default Hero;

99
src/index.css Normal file
View File

@@ -0,0 +1,99 @@
@import "tailwindcss";
@theme {
/* Custom color palette - plant aesthetic */
--color-sage-50: #f5f9f6;
--color-sage-100: #e8f3eb;
--color-sage-200: #c8e6d0;
--color-sage-300: #a4d4b4;
--color-sage-400: #7fb069;
--color-sage-500: #5a9650;
--color-sage-600: #4a7c42;
--color-sage-700: #3a6235;
--color-sage-800: #2d4d2a;
--color-sage-900: #1f3520;
/* Neutral colors */
--color-charcoal-50: #f7f8f8;
--color-charcoal-100: #e8eaeb;
--color-charcoal-200: #d1d5d8;
--color-charcoal-300: #a8b1b7;
--color-charcoal-400: #7d8a93;
--color-charcoal-500: #636e72;
--color-charcoal-600: #525c61;
--color-charcoal-700: #444d51;
--color-charcoal-800: #3a4145;
--color-charcoal-900: #2d3436;
/* Font family */
--font-family-sans: 'Inter', system-ui, -apple-system, sans-serif;
}
/* Base styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #fafafa;
color: var(--color-charcoal-900);
margin: 0 auto;
max-width: 100vw;
overflow-x: hidden;
}
html {
scroll-behavior: smooth;
overflow-x: hidden;
}
/* Scroll animations */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.animate-fadeInUp {
animation: fadeInUp 0.8s ease-out forwards;
}
.animate-fadeIn {
animation: fadeIn 0.6s ease-out forwards;
}
/* Staggered animation delays */
.delay-100 {
animation-delay: 0.1s;
}
.delay-200 {
animation-delay: 0.2s;
}
.delay-300 {
animation-delay: 0.3s;
}
.delay-400 {
animation-delay: 0.4s;
}

10
src/main.jsx Normal file
View File

@@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)