From b04a0a515bc0a48c60ed97488156f828dd6a2dd3 Mon Sep 17 00:00:00 2001 From: Lumerel Deploy Date: Tue, 17 Feb 2026 03:21:33 +0000 Subject: [PATCH] Deploy from Lumerel --- .dockerignore | 3 + Dockerfile | 23 ++ index.html | 490 ++++++++++++++++++++++++++++++ script.js | 231 +++++++++++++++ styles.css | 804 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1551 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 index.html create mode 100644 script.js create mode 100644 styles.css diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..17896fe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +Dockerfile +.dockerignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dd9b2ed --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM webdevops/php-nginx:8.3-alpine +ENV WEB_DOCUMENT_ROOT=/app +ARG CACHE_BUST=1771298493 +COPY . /app +RUN echo "index index.php index.html index.htm;" > /opt/docker/etc/nginx/vhost.common.d/01-index.conf \ + && echo "add_header Cache-Control 'no-cache, no-store, must-revalidate';" > /opt/docker/etc/nginx/vhost.common.d/02-no-cache.conf +RUN set -e; if [ -f /app/composer.json ]; then \ + echo ">>> composer.json found, installing dependencies..."; \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer; \ + cd /app && composer install --no-dev --no-interaction --optimize-autoloader; \ + ls -la /app/vendor/autoload.php; \ + else \ + echo ">>> No composer.json found, skipping composer install"; \ + fi +RUN set -e; if [ -f /app/package.json ]; then \ + echo ">>> package.json found, installing node dependencies..."; \ + apk add --no-cache nodejs npm; \ + cd /app && npm install --production; \ + else \ + echo ">>> No package.json found, skipping npm install"; \ + fi + +RUN chown -R application:application /app \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..5d6dab3 --- /dev/null +++ b/index.html @@ -0,0 +1,490 @@ + + + + + + Todd Low Media - Web Development & Programming + + + + + + + + + + +
+
+
+
+

+ Building Digital Experiences
+ That Make a Difference +

+

+ Full-stack web development and programming solutions tailored to your needs +

+ +
+
+
+
</>
+
+
+
{ }
+
+
+
λ
+
+
+
+
+ Scroll to explore +
+
+
+ + +
+
+

What I Do

+

Comprehensive web development services to bring your vision to life

+ +
+
+
+ + + + +
+

Full-Stack Development

+

End-to-end web application development using modern technologies like React, Vue, Laravel, and Node.js

+
+ +
+
+ + + + +
+

Custom Web Applications

+

Tailored solutions designed specifically for your business needs and workflows

+
+ +
+
+ + + + +
+

API Development

+

RESTful APIs and backend services that power your applications efficiently and securely

+
+ +
+
+ + + + +
+

Responsive Design

+

Beautiful, mobile-first interfaces that work seamlessly across all devices and screen sizes

+
+ +
+
+ + + + +
+

Performance Optimization

+

Speed and efficiency improvements to ensure your site loads fast and runs smoothly

+
+ +
+
+ + + +
+

Maintenance & Support

+

Ongoing support and updates to keep your applications secure and up-to-date

+
+
+
+
+ + +
+
+

Featured Projects

+

A selection of recent work showcasing diverse solutions

+ +
+
+
+
+ E-Commerce Platform +
+
+
+

E-Commerce Platform

+

Full-featured online store with payment integration, inventory management, and admin dashboard

+
+ Laravel + Vue.js + MySQL +
+
+
+ +
+
+
+ Project Management Tool +
+
+
+

Project Management Tool

+

Collaborative workspace for teams with real-time updates, task tracking, and reporting

+
+ React + Node.js + WebSocket +
+
+
+ +
+
+
+ Analytics Dashboard +
+
+
+

Analytics Dashboard

+

Data visualization platform with interactive charts, custom reports, and export functionality

+
+ TypeScript + D3.js + PostgreSQL +
+
+
+ +
+
+
+ Booking System +
+
+
+

Booking System

+

Appointment scheduling platform with calendar integration, automated reminders, and payment processing

+
+ PHP + JavaScript + Stripe API +
+
+
+ +
+
+
+ Content Management +
+
+
+

Content Management System

+

Custom CMS with drag-and-drop page builder, media library, and multi-user permissions

+
+ Laravel + Alpine.js + Tailwind +
+
+
+ +
+
+
+ API Integration Hub +
+
+
+

API Integration Hub

+

Middleware service connecting multiple third-party APIs with unified authentication and data sync

+
+ Node.js + Express + Redis +
+
+
+
+
+
+ + +
+
+

Technical Skills

+

Technologies and tools I work with

+ +
+
+

Frontend

+
+
+ JavaScript / TypeScript +
+
+
+
+
+ React / Vue.js +
+
+
+
+
+ HTML5 / CSS3 +
+
+
+
+
+ Tailwind / Bootstrap +
+
+
+
+
+
+ +
+

Backend

+
+
+ PHP / Laravel +
+
+
+
+
+ Node.js / Express +
+
+
+
+
+ MySQL / PostgreSQL +
+
+
+
+
+ RESTful APIs +
+
+
+
+
+
+ +
+

Tools & DevOps

+
+
+ Git / GitHub +
+
+
+
+
+ Docker +
+
+
+
+
+ CI/CD +
+
+
+
+
+ AWS / Cloud Services +
+
+
+
+
+
+ +
+

Other

+
+
+ UI/UX Design +
+
+
+
+
+ Agile / Scrum +
+
+
+
+
+ Testing / QA +
+
+
+
+
+ Performance Optimization +
+
+
+
+
+
+
+
+
+ + +
+
+

Let's Work Together

+

Have a project in mind? I'd love to hear about it

+ +
+
+

Get In Touch

+

Whether you need a new website, a custom web application, or help with an existing project, I'm here to help bring your ideas to life.

+ +
+
+
+ + + + +
+ +
+ +
+
+ + + + +
+
+

Location

+

Available for remote work

+
+
+ +
+
+ + + + +
+
+

Response Time

+

Usually within 24 hours

+
+
+
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..d476f37 --- /dev/null +++ b/script.js @@ -0,0 +1,231 @@ +// Mobile Menu Toggle +const mobileToggle = document.getElementById('mobileToggle'); +const navMenu = document.getElementById('navMenu'); + +mobileToggle.addEventListener('click', () => { + navMenu.classList.toggle('active'); + + // Animate hamburger to X + const spans = mobileToggle.querySelectorAll('span'); + if (navMenu.classList.contains('active')) { + spans[0].style.transform = 'rotate(45deg) translate(5px, 5px)'; + spans[1].style.opacity = '0'; + spans[2].style.transform = 'rotate(-45deg) translate(7px, -6px)'; + } else { + spans[0].style.transform = 'none'; + spans[1].style.opacity = '1'; + spans[2].style.transform = 'none'; + } +}); + +// Close mobile menu when clicking on a link +const navLinks = document.querySelectorAll('.nav-link'); +navLinks.forEach(link => { + link.addEventListener('click', () => { + navMenu.classList.remove('active'); + const spans = mobileToggle.querySelectorAll('span'); + spans[0].style.transform = 'none'; + spans[1].style.opacity = '1'; + spans[2].style.transform = 'none'; + }); +}); + +// Navbar scroll effect +const nav = document.getElementById('nav'); +let lastScroll = 0; + +window.addEventListener('scroll', () => { + const currentScroll = window.pageYOffset; + + if (currentScroll > 50) { + nav.classList.add('scrolled'); + } else { + nav.classList.remove('scrolled'); + } + + lastScroll = currentScroll; +}); + +// Smooth scroll for anchor links +document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function (e) { + e.preventDefault(); + const target = document.querySelector(this.getAttribute('href')); + if (target) { + const offsetTop = target.offsetTop - 80; + window.scrollTo({ + top: offsetTop, + behavior: 'smooth' + }); + } + }); +}); + +// Animate on Scroll (AOS) implementation +function initAOS() { + const observerOptions = { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('aos-animate'); + } + }); + }, observerOptions); + + // Observe all elements with data-aos attribute + document.querySelectorAll('[data-aos]').forEach(element => { + observer.observe(element); + }); +} + +// Initialize AOS when DOM is loaded +document.addEventListener('DOMContentLoaded', initAOS); + +// Animate skill bars when they come into view +const skillObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const progressBars = entry.target.querySelectorAll('.skill-progress'); + progressBars.forEach(bar => { + const width = bar.style.width; + bar.style.width = '0'; + setTimeout(() => { + bar.style.width = width; + }, 100); + }); + skillObserver.unobserve(entry.target); + } + }); +}, { threshold: 0.5 }); + +const skillsSection = document.querySelector('.skills'); +if (skillsSection) { + skillObserver.observe(skillsSection); +} + +// Contact Form Handling +const contactForm = document.getElementById('contactForm'); + +contactForm.addEventListener('submit', (e) => { + e.preventDefault(); + + // Get form data + const formData = { + name: document.getElementById('name').value, + email: document.getElementById('email').value, + subject: document.getElementById('subject').value, + message: document.getElementById('message').value + }; + + // In a real application, you would send this data to a server + console.log('Form submitted:', formData); + + // Show success message + showNotification('Thank you for your message! I\'ll get back to you soon.', 'success'); + + // Reset form + contactForm.reset(); +}); + +// Notification function +function showNotification(message, type = 'info') { + const notification = document.createElement('div'); + notification.className = `notification notification-${type}`; + notification.textContent = message; + + notification.style.cssText = ` + position: fixed; + top: 100px; + right: 20px; + background: ${type === 'success' ? '#10b981' : '#3b82f6'}; + color: white; + padding: 1rem 1.5rem; + border-radius: 8px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); + z-index: 10000; + animation: slideIn 0.3s ease; + `; + + document.body.appendChild(notification); + + setTimeout(() => { + notification.style.animation = 'slideOut 0.3s ease'; + setTimeout(() => { + document.body.removeChild(notification); + }, 300); + }, 3000); +} + +// Add animation keyframes +const style = document.createElement('style'); +style.textContent = ` + @keyframes slideIn { + from { + transform: translateX(400px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } + } + + @keyframes slideOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(400px); + opacity: 0; + } + } +`; +document.head.appendChild(style); + +// Parallax effect for hero section +window.addEventListener('scroll', () => { + const scrolled = window.pageYOffset; + const hero = document.querySelector('.hero'); + + if (hero && scrolled < window.innerHeight) { + const parallaxElements = hero.querySelectorAll('.floating-card'); + parallaxElements.forEach((element, index) => { + const speed = 0.5 + (index * 0.1); + element.style.transform = `translateY(${scrolled * speed}px)`; + }); + } +}); + +// Add active state to navigation based on scroll position +window.addEventListener('scroll', () => { + const sections = document.querySelectorAll('section[id]'); + const scrollY = window.pageYOffset; + + sections.forEach(section => { + const sectionHeight = section.offsetHeight; + const sectionTop = section.offsetTop - 100; + const sectionId = section.getAttribute('id'); + const navLink = document.querySelector(`.nav-link[href="#${sectionId}"]`); + + if (navLink && scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) { + document.querySelectorAll('.nav-link').forEach(link => { + link.style.color = ''; + }); + navLink.style.color = 'var(--primary-color)'; + } + }); +}); + +// Preload animation for page load +window.addEventListener('load', () => { + document.body.style.opacity = '0'; + setTimeout(() => { + document.body.style.transition = 'opacity 0.5s ease'; + document.body.style.opacity = '1'; + }, 100); +}); \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..73cca3f --- /dev/null +++ b/styles.css @@ -0,0 +1,804 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --primary-color: #c026d3; + --primary-dark: #a21caf; + --primary-light: #e879f9; + --secondary-color: #6366f1; + --text-dark: #1f2937; + --text-light: #6b7280; + --bg-light: #f9fafb; + --bg-white: #ffffff; + --border-color: #e5e7eb; + --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + color: var(--text-dark); + line-height: 1.6; + overflow-x: hidden; +} + +html { + scroll-behavior: smooth; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* Navigation */ +.nav { + position: fixed; + top: 0; + left: 0; + right: 0; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + box-shadow: var(--shadow); + z-index: 1000; + transition: all 0.3s ease; +} + +.nav.scrolled { + background: rgba(255, 255, 255, 0.98); + box-shadow: var(--shadow-lg); +} + +.nav-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 20px; +} + +.logo-text { + font-size: 1.5rem; + font-weight: 700; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.nav-menu { + display: flex; + list-style: none; + gap: 2rem; +} + +.nav-link { + text-decoration: none; + color: var(--text-dark); + font-weight: 500; + transition: color 0.3s ease; + position: relative; +} + +.nav-link::after { + content: ''; + position: absolute; + bottom: -5px; + left: 0; + width: 0; + height: 2px; + background: var(--primary-color); + transition: width 0.3s ease; +} + +.nav-link:hover { + color: var(--primary-color); +} + +.nav-link:hover::after { + width: 100%; +} + +.mobile-toggle { + display: none; + flex-direction: column; + gap: 5px; + background: none; + border: none; + cursor: pointer; +} + +.mobile-toggle span { + width: 25px; + height: 3px; + background: var(--text-dark); + transition: all 0.3s ease; +} + +/* Hero Section */ +.hero { + position: relative; + min-height: 100vh; + display: flex; + align-items: center; + padding-top: 80px; + overflow: hidden; +} + +.hero-background { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); + opacity: 0.05; + z-index: -1; +} + +.hero-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4rem; + align-items: center; +} + +.hero-content { + z-index: 1; +} + +.hero-title { + font-size: 3.5rem; + font-weight: 700; + line-height: 1.2; + margin-bottom: 1.5rem; +} + +.gradient-text { + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-subtitle { + font-size: 1.25rem; + color: var(--text-light); + margin-bottom: 2rem; +} + +.hero-buttons { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.btn { + padding: 0.875rem 2rem; + border-radius: 8px; + text-decoration: none; + font-weight: 600; + transition: all 0.3s ease; + display: inline-block; + cursor: pointer; + border: none; + font-size: 1rem; +} + +.btn-primary { + background: linear-gradient(135deg, var(--primary-color), var(--primary-dark)); + color: white; + box-shadow: 0 4px 15px rgba(192, 38, 211, 0.3); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(192, 38, 211, 0.4); +} + +.btn-secondary { + background: white; + color: var(--primary-color); + border: 2px solid var(--primary-color); +} + +.btn-secondary:hover { + background: var(--primary-color); + color: white; + transform: translateY(-2px); +} + +.btn-full { + width: 100%; +} + +/* Hero Illustration */ +.hero-illustration { + position: relative; + height: 500px; +} + +.floating-card { + position: absolute; + background: white; + border-radius: 16px; + box-shadow: var(--shadow-lg); + padding: 2rem; + animation: float 6s ease-in-out infinite; +} + +.floating-card .code-symbol { + font-size: 4rem; + font-weight: 700; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.card-1 { + top: 10%; + left: 10%; + animation-delay: 0s; +} + +.card-2 { + top: 40%; + right: 10%; + animation-delay: 2s; +} + +.card-3 { + bottom: 10%; + left: 30%; + animation-delay: 4s; +} + +@keyframes float { + 0%, 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-20px); + } +} + +.scroll-indicator { + position: absolute; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: var(--text-light); + font-size: 0.875rem; +} + +.scroll-arrow { + width: 24px; + height: 24px; + border-left: 2px solid var(--text-light); + border-bottom: 2px solid var(--text-light); + transform: rotate(-45deg); + margin: 0.5rem auto; + animation: bounce 2s infinite; +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + transform: rotate(-45deg) translateY(0); + } + 40% { + transform: rotate(-45deg) translateY(-10px); + } + 60% { + transform: rotate(-45deg) translateY(-5px); + } +} + +/* Fade-in animations */ +.fade-in { + opacity: 0; + animation: fadeIn 1s ease forwards; +} + +.delay-1 { + animation-delay: 0.2s; +} + +.delay-2 { + animation-delay: 0.4s; +} + +.delay-3 { + animation-delay: 0.6s; +} + +@keyframes fadeIn { + to { + opacity: 1; + } +} + +/* Section Styles */ +section { + padding: 5rem 0; +} + +.section-title { + font-size: 2.5rem; + font-weight: 700; + text-align: center; + margin-bottom: 1rem; +} + +.section-subtitle { + text-align: center; + color: var(--text-light); + font-size: 1.125rem; + margin-bottom: 3rem; +} + +/* Services Section */ +.services { + background: var(--bg-light); +} + +.services-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; +} + +.service-card { + background: white; + padding: 2rem; + border-radius: 12px; + box-shadow: var(--shadow); + transition: all 0.3s ease; +} + +.service-card:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-lg); +} + +.service-icon { + width: 60px; + height: 60px; + background: linear-gradient(135deg, var(--primary-light), var(--primary-color)); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 1.5rem; +} + +.service-icon svg { + width: 32px; + height: 32px; + color: white; +} + +.service-card h3 { + font-size: 1.25rem; + margin-bottom: 1rem; + color: var(--text-dark); +} + +.service-card p { + color: var(--text-light); + line-height: 1.6; +} + +/* Portfolio Section */ +.portfolio-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 2rem; +} + +.portfolio-item { + background: white; + border-radius: 12px; + overflow: hidden; + box-shadow: var(--shadow); + transition: all 0.3s ease; +} + +.portfolio-item:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-lg); +} + +.portfolio-image { + width: 100%; + height: 250px; + overflow: hidden; +} + +.portfolio-placeholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 1.5rem; + font-weight: 600; + text-align: center; + padding: 2rem; +} + +.portfolio-content { + padding: 1.5rem; +} + +.portfolio-content h3 { + font-size: 1.25rem; + margin-bottom: 0.75rem; + color: var(--text-dark); +} + +.portfolio-content p { + color: var(--text-light); + margin-bottom: 1rem; + line-height: 1.6; +} + +.portfolio-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.portfolio-tags span { + padding: 0.25rem 0.75rem; + background: var(--bg-light); + border-radius: 20px; + font-size: 0.875rem; + color: var(--primary-color); + font-weight: 500; +} + +/* Skills Section */ +.skills { + background: var(--bg-light); +} + +.skills-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 2rem; +} + +.skill-category { + background: white; + padding: 2rem; + border-radius: 12px; + box-shadow: var(--shadow); +} + +.skill-category h3 { + font-size: 1.5rem; + margin-bottom: 1.5rem; + color: var(--text-dark); +} + +.skill-items { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.skill-item { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.skill-name { + font-weight: 500; + color: var(--text-dark); +} + +.skill-bar { + width: 100%; + height: 8px; + background: var(--bg-light); + border-radius: 10px; + overflow: hidden; +} + +.skill-progress { + height: 100%; + background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); + border-radius: 10px; + transition: width 1s ease; +} + +/* Contact Section */ +.contact-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4rem; + max-width: 1000px; + margin: 0 auto; +} + +.contact-info h3 { + font-size: 1.75rem; + margin-bottom: 1rem; +} + +.contact-info > p { + color: var(--text-light); + margin-bottom: 2rem; + line-height: 1.6; +} + +.contact-methods { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.contact-method { + display: flex; + gap: 1rem; + align-items: flex-start; +} + +.contact-icon { + width: 48px; + height: 48px; + background: linear-gradient(135deg, var(--primary-light), var(--primary-color)); + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.contact-icon svg { + width: 24px; + height: 24px; + color: white; +} + +.contact-method h4 { + font-size: 1rem; + margin-bottom: 0.25rem; + color: var(--text-dark); +} + +.contact-method p, +.contact-method a { + color: var(--text-light); + text-decoration: none; +} + +.contact-method a:hover { + color: var(--primary-color); +} + +/* Contact Form */ +.contact-form { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.form-group label { + font-weight: 500; + color: var(--text-dark); +} + +.form-group input, +.form-group textarea { + padding: 0.75rem; + border: 2px solid var(--border-color); + border-radius: 8px; + font-family: inherit; + font-size: 1rem; + transition: border-color 0.3s ease; +} + +.form-group input:focus, +.form-group textarea:focus { + outline: none; + border-color: var(--primary-color); +} + +.form-group textarea { + resize: vertical; +} + +/* Footer */ +.footer { + background: var(--text-dark); + color: white; + padding: 3rem 0 1.5rem; +} + +.footer-content { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + flex-wrap: wrap; + gap: 2rem; +} + +.footer-brand h3 { + font-size: 1.5rem; + margin-bottom: 0.5rem; +} + +.footer-brand p { + color: rgba(255, 255, 255, 0.7); +} + +.footer-links { + display: flex; + gap: 2rem; + flex-wrap: wrap; +} + +.footer-links a { + color: rgba(255, 255, 255, 0.7); + text-decoration: none; + transition: color 0.3s ease; +} + +.footer-links a:hover { + color: white; +} + +.footer-bottom { + text-align: center; + padding-top: 2rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.footer-bottom p { + color: rgba(255, 255, 255, 0.5); +} + +/* AOS Animation Classes */ +[data-aos] { + opacity: 0; + transition: opacity 0.6s ease, transform 0.6s ease; +} + +[data-aos].aos-animate { + opacity: 1; +} + +[data-aos="fade-up"] { + transform: translateY(30px); +} + +[data-aos="fade-up"].aos-animate { + transform: translateY(0); +} + +[data-aos="fade-right"] { + transform: translateX(-30px); +} + +[data-aos="fade-right"].aos-animate { + transform: translateX(0); +} + +[data-aos="fade-left"] { + transform: translateX(30px); +} + +[data-aos="fade-left"].aos-animate { + transform: translateX(0); +} + +[data-aos="zoom-in"] { + transform: scale(0.9); +} + +[data-aos="zoom-in"].aos-animate { + transform: scale(1); +} + +/* Responsive Design */ +@media (max-width: 968px) { + .hero-container { + grid-template-columns: 1fr; + text-align: center; + } + + .hero-illustration { + display: none; + } + + .hero-buttons { + justify-content: center; + } + + .contact-content { + grid-template-columns: 1fr; + gap: 3rem; + } + + .footer-content { + flex-direction: column; + text-align: center; + } + + .footer-links { + justify-content: center; + } +} + +@media (max-width: 768px) { + .mobile-toggle { + display: flex; + } + + .nav-menu { + position: fixed; + top: 70px; + left: -100%; + flex-direction: column; + background: white; + width: 100%; + padding: 2rem; + box-shadow: var(--shadow-lg); + transition: left 0.3s ease; + } + + .nav-menu.active { + left: 0; + } + + .hero-title { + font-size: 2.5rem; + } + + .section-title { + font-size: 2rem; + } + + .services-grid, + .portfolio-grid { + grid-template-columns: 1fr; + } + + .skills-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 480px) { + .hero-title { + font-size: 2rem; + } + + .hero-subtitle { + font-size: 1rem; + } + + .btn { + padding: 0.75rem 1.5rem; + font-size: 0.9rem; + } + + .section-title { + font-size: 1.75rem; + } +} \ No newline at end of file