← back to Billy Website

js/main.js

289 lines

// Mobile Navigation Toggle
const hamburger = document.querySelector('.hamburger');
const navMenu = document.querySelector('.nav-menu');

if (hamburger && navMenu) {
    hamburger.addEventListener('click', () => {
        hamburger.classList.toggle('active');
        navMenu.classList.toggle('active');
    });
}

// Close mobile menu when clicking a link
document.querySelectorAll('.nav-link').forEach(link => {
    link.addEventListener('click', () => {
        if (hamburger) hamburger.classList.remove('active');
        if (navMenu) navMenu.classList.remove('active');
    });
});

// Smooth Scrolling
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
    anchor.addEventListener('click', function(e) {
        e.preventDefault();
        const target = document.querySelector(this.getAttribute('href'));
        if (target) {
            const offset = 70; // navbar height
            const targetPosition = target.offsetTop - offset;
            window.scrollTo({
                top: targetPosition,
                behavior: 'smooth'
            });
        }
    });
});

// Navbar scroll effect
let lastScroll = 0;
const navbar = document.querySelector('.navbar');

window.addEventListener('scroll', () => {
    if (!navbar) return;

    const currentScroll = window.pageYOffset;

    if (currentScroll <= 0) {
        navbar.style.boxShadow = '0 5px 20px rgba(0,0,0,0.1)';
        return;
    }

    if (currentScroll > lastScroll && currentScroll > 100) {
        // Scrolling down
        navbar.style.transform = 'translateY(-100%)';
    } else {
        // Scrolling up
        navbar.style.transform = 'translateY(0)';
        navbar.style.boxShadow = '0 5px 30px rgba(0,0,0,0.15)';
    }

    lastScroll = currentScroll;
});

// Leaflet Map Integration - Laurel Canyon Location
function initMap() {
    // Coordinates for 2755 Carmar Drive, Los Angeles, CA 90046
    const propertyLocation = [34.1144, -118.3683];

    // Initialize map
    const map = L.map('nosara-map').setView(propertyLocation, 13);

    // Add OpenStreetMap tile layer (free, no API key needed)
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
        maxZoom: 19
    }).addTo(map);

    // Custom icon for main property
    const propertyIcon = L.divIcon({
        className: 'custom-property-marker',
        html: '<div style="background: #8B4513; color: white; width: 30px; height: 30px; border-radius: 50% 50% 50% 0; transform: rotate(-45deg); display: flex; align-items: center; justify-content: center; border: 3px solid white; box-shadow: 0 3px 10px rgba(0,0,0,0.3);"><i class="fas fa-home" style="transform: rotate(45deg); font-size: 14px;"></i></div>',
        iconSize: [30, 30],
        iconAnchor: [15, 30],
        popupAnchor: [0, -30]
    });

    // Add marker for property location
    const propertyMarker = L.marker(propertyLocation, { icon: propertyIcon }).addTo(map);
    propertyMarker.bindPopup(`
        <div style="padding: 10px; font-family: 'Inter', sans-serif; min-width: 200px;">
            <h3 style="margin: 0 0 5px 0; color: #8B4513; font-size: 16px;">2755 Carmar Drive</h3>
            <p style="margin: 0; color: #666; font-size: 14px;">Luxury rental in historic Laurel Canyon</p>
        </div>
    `).openPopup();

    // Add nearby points of interest
    const pois = [
        { lat: 34.1016, lng: -118.3465, title: 'Hollywood Bowl', type: 'hotel', icon: 'theater-masks' },
        { lat: 34.0983, lng: -118.3701, title: 'Sunset Strip', type: 'restaurant', icon: 'music' },
        { lat: 34.1097, lng: -118.3544, title: 'Runyon Canyon Park', type: 'beach', icon: 'hiking' },
        { lat: 34.1169, lng: -118.3583, title: 'Canyon Country Store', type: 'restaurant', icon: 'shopping-bag' },
        { lat: 34.1314, lng: -118.3688, title: 'Mulholland Drive Viewpoint', type: 'activity', icon: 'mountain' }
    ];

    const markerColors = {
        restaurant: '#e74c3c',
        hotel: '#3498db',
        beach: '#2ecc71',
        activity: '#f39c12'
    };

    pois.forEach(poi => {
        const poiIcon = L.divIcon({
            className: 'custom-poi-marker',
            html: `<div style="background: ${markerColors[poi.type]}; color: white; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; border: 2px solid white; box-shadow: 0 2px 5px rgba(0,0,0,0.3);"><i class="fas fa-${poi.icon}" style="font-size: 10px;"></i></div>`,
            iconSize: [24, 24],
            iconAnchor: [12, 12],
            popupAnchor: [0, -12]
        });

        const marker = L.marker([poi.lat, poi.lng], { icon: poiIcon }).addTo(map);
        marker.bindPopup(`
            <div style="padding: 5px; font-family: 'Inter', sans-serif;">
                <strong style="color: ${markerColors[poi.type]}; font-size: 14px;">${poi.title}</strong>
            </div>
        `);
    });
}

// Initialize map when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
    if (document.getElementById('nosara-map')) {
        initMap();
    }
});

// Scroll 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.addEventListener('DOMContentLoaded', () => {
    const animateElements = document.querySelectorAll('.villa-card, .season-card, .rec-item, .heritage-item, .timeline-item');

    animateElements.forEach(el => {
        el.style.opacity = '0';
        el.style.transform = 'translateY(30px)';
        el.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
        observer.observe(el);
    });
});

// Stats Counter Animation
function animateCounter(element, target, duration = 2000) {
    const start = 0;
    const increment = target / (duration / 16);
    let current = start;

    const timer = setInterval(() => {
        current += increment;
        if (current >= target) {
            element.textContent = target;
            clearInterval(timer);
        } else {
            element.textContent = Math.floor(current);
        }
    }, 16);
}

// Trigger counter animation when stats section is visible
const statsSection = document.querySelector('.stats');
let statsAnimated = false;

const statsObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting && !statsAnimated) {
            const statNumbers = document.querySelectorAll('.stat-item h3');
            statNumbers.forEach(stat => {
                const target = parseInt(stat.textContent);
                if (!isNaN(target)) {
                    animateCounter(stat, target);
                }
            });
            statsAnimated = true;
        }
    });
}, { threshold: 0.5 });

if (statsSection) {
    statsObserver.observe(statsSection);
}

// Form Validation
const contactForm = document.querySelector('.contact-form');

if (contactForm) {
    contactForm.addEventListener('submit', (e) => {
        // Get form values
        const formData = new FormData(contactForm);
        const data = Object.fromEntries(formData);

        // Date validation
        if (data['preferred-date']) {
            const preferredDate = new Date(data['preferred-date']);
            const today = new Date();
            today.setHours(0, 0, 0, 0);

            if (preferredDate < today) {
                alert('Preferred viewing date must be today or later');
                e.preventDefault();
                return;
            }
        }

        // Form will submit to Formspree
    });
}

// Parallax Effect for Hero Section
window.addEventListener('scroll', () => {
    const scrolled = window.pageYOffset;
    const heroText = document.querySelector('.hero-text');

    if (heroText && scrolled < window.innerHeight) {
        heroText.style.transform = `translateY(${scrolled * 0.5}px)`;
        heroText.style.opacity = 1 - (scrolled / 500);
    }
});

// Lazy Loading for Images
if ('IntersectionObserver' in window) {
    const imageObserver = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                const img = entry.target;
                img.src = img.dataset.src;
                img.classList.add('loaded');
                observer.unobserve(img);
            }
        });
    });

    document.querySelectorAll('img[data-src]').forEach(img => {
        imageObserver.observe(img);
    });
}

// Current year for footer
const currentYearElement = document.querySelector('.footer-bottom p');
if (currentYearElement) {
    const currentYear = new Date().getFullYear();
    const footerText = currentYearElement.textContent;
    if (footerText && footerText.includes('2025')) {
        currentYearElement.textContent = footerText.replace('2025', currentYear);
    }
}

// Add active class to navigation based on scroll position
window.addEventListener('scroll', () => {
    let current = '';
    const sections = document.querySelectorAll('section[id]');

    sections.forEach(section => {
        const sectionTop = section.offsetTop;
        const sectionHeight = section.clientHeight;
        if (pageYOffset >= (sectionTop - 100)) {
            current = section.getAttribute('id');
        }
    });

    document.querySelectorAll('.nav-link').forEach(link => {
        link.classList.remove('active');
        if (link.getAttribute('href') === `#${current}`) {
            link.classList.add('active');
        }
    });
});

console.log('🎸 2755 Carmar Drive - Laurel Canyon Luxury Rental - Website Loaded Successfully');