// Linear Portfolio Template JavaScript
document.addEventListener('DOMContentLoaded', function() {
// Mobile menu toggle
const mobileMenuButton = document.querySelector('.mobile-menu-button');
const mobileMenu = document.querySelector('.mobile-menu');
if (mobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', function() {
mobileMenu.classList.toggle('hidden');
});
}
// Smooth scrolling for navigation links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
// Close mobile menu if open
if (mobileMenu && !mobileMenu.classList.contains('hidden')) {
mobileMenu.classList.add('hidden');
}
}
});
});
// Form submission handling
const contactForm = document.querySelector('#contact form');
if (contactForm) {
contactForm.addEventListener('submit', function(e) {
e.preventDefault();
// Get form data
const formData = new FormData(this);
const name = formData.get('name');
const email = formData.get('email');
const company = formData.get('company');
const project = formData.get('project');
// Simple validation
if (!name || !email || !project) {
showNotification('Please fill in all required fields.', 'error');
return;
}
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
showNotification('Please enter a valid email address.', 'error');
return;
}
// Simulate form submission
showNotification('Sending message...', 'info');
setTimeout(() => {
showNotification(`Thanks ${name}! Your message has been sent.`, 'success');
this.reset();
}, 2000);
});
}
// Notification system
function showNotification(message, type = 'info') {
// Remove existing notification
const existingNotification = document.querySelector('.notification');
if (existingNotification) {
existingNotification.remove();
}
// Create notification element
const notification = document.createElement('div');
notification.className = 'notification';
// Set colors based on type
let bgColor, borderColor;
switch (type) {
case 'success':
bgColor = '#f0fdf4';
borderColor = '#bbf7d0';
break;
case 'error':
bgColor = '#fef2f2';
borderColor = '#fecaca';
break;
default:
bgColor = '#f8fafc';
borderColor = '#e2e8f0';
}
notification.style.background = bgColor;
notification.style.border = `1px solid ${borderColor}`;
notification.innerHTML = `
${message}
`;
// Add to page
document.body.appendChild(notification);
// Show notification
setTimeout(() => notification.classList.add('show'), 100);
// Auto hide after 5 seconds
setTimeout(() => {
if (notification && notification.parentElement) {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}
}, 5000);
// Initialize feather icons in notification
if (typeof feather !== 'undefined') {
feather.replace();
}
}
// Intersection Observer for animations
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
});
}, observerOptions);
// Observe elements for animation
document.querySelectorAll('section').forEach(section => {
section.style.opacity = '0';
section.style.transform = 'translateY(30px)';
section.style.transition = 'opacity 0.8s ease-out, transform 0.8s ease-out';
observer.observe(section);
});
// Header scroll effect
const header = document.querySelector('header');
let lastScrollTop = 0;
window.addEventListener('scroll', function() {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
if (scrollTop > 100) {
header.classList.add('bg-white/95', 'backdrop-blur-sm', 'shadow-sm');
} else {
header.classList.remove('shadow-sm');
}
lastScrollTop = scrollTop;
});
// Typing animation for hero text
function typeWriter(element, text, speed = 50) {
let i = 0;
element.innerHTML = '';
function type() {
if (i < text.length) {
element.innerHTML += text.charAt(i);
i++;
setTimeout(type, speed);
}
}
type();
}
// Initialize typing animation on hero section
const heroTitle = document.querySelector('#home h1');
if (heroTitle) {
const originalText = heroTitle.innerHTML;
const spans = heroTitle.querySelectorAll('span');
if (spans.length > 0) {
typeWriter(spans[0], spans[0].textContent, 100);
}
}
// Lazy loading for images
const images = document.querySelectorAll('img');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.setAttribute('loaded', '');
observer.unobserve(img);
}
});
});
images.forEach(img => {
img.setAttribute('loading', '');
imageObserver.observe(img);
});
// Keyboard navigation support
document.addEventListener('keydown', function(e) {
// ESC key closes mobile menu
if (e.key === 'Escape' && mobileMenu && !mobileMenu.classList.contains('hidden')) {
mobileMenu.classList.add('hidden');
}
});
// Performance optimization: Debounce scroll events
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Apply debounce to scroll handler
const debouncedScrollHandler = debounce(function() {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
if (scrollTop > 100) {
header.classList.add('shadow-sm');
} else {
header.classList.remove('shadow-sm');
}
}, 10);
window.addEventListener('scroll', debouncedScrollHandler);
// Initialize feather icons
if (typeof feather !== 'undefined') {
feather.replace();
}
console.log('Linear Portfolio Template initialized successfully!');
});