← back to Jill Website
public/js/main.js
389 lines
// Mobile Navigation Toggle
const hamburger = document.querySelector('.hamburger');
const navMenu = document.querySelector('.nav-menu');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active');
navMenu.classList.toggle('active');
});
// Mobile dropdown toggle
document.querySelectorAll('.nav-dropdown > .nav-link').forEach(dropdownLink => {
dropdownLink.addEventListener('click', (e) => {
if (window.innerWidth <= 768) {
e.preventDefault();
const dropdown = dropdownLink.parentElement;
dropdown.classList.toggle('active');
}
});
});
// Close mobile menu when clicking a link
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', () => {
if (!link.parentElement.classList.contains('nav-dropdown')) {
hamburger.classList.remove('active');
navMenu.classList.remove('active');
}
});
});
// Close dropdown when clicking dropdown item
document.querySelectorAll('.dropdown-menu a').forEach(link => {
link.addEventListener('click', () => {
hamburger.classList.remove('active');
navMenu.classList.remove('active');
document.querySelectorAll('.nav-dropdown').forEach(d => d.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', () => {
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;
});
// Google Maps Integration - Nosara Location
function initMap() {
// Coordinates for Nosara, Costa Rica
const nosaraLocation = { lat: 9.9759, lng: -85.6532 };
const map = new google.maps.Map(document.getElementById('nosara-map'), {
zoom: 12,
center: nosaraLocation,
styles: [
{
featureType: 'water',
elementType: 'geometry',
stylers: [{ color: '#667eea' }]
},
{
featureType: 'landscape',
elementType: 'geometry',
stylers: [{ color: '#f5f5f5' }]
}
]
});
// Add marker for property location
const propertyMarker = new google.maps.Marker({
position: nosaraLocation,
map: map,
title: 'Nosara Beachfront Rentals',
icon: {
url: 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="#2c5f8d">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
`),
scaledSize: new google.maps.Size(40, 40)
}
});
// Info window for property
const infoWindow = new google.maps.InfoWindow({
content: `
<div style="padding: 10px; font-family: 'Inter', sans-serif;">
<h3 style="margin: 0 0 5px 0; color: #2c5f8d;">Nosara Beachfront Rentals</h3>
<p style="margin: 0; color: #666;">Luxury villas at the mouth of Rio Nosara</p>
</div>
`
});
propertyMarker.addListener('click', () => {
infoWindow.open(map, propertyMarker);
});
// Add nearby points of interest
const pois = [
{ lat: 9.9641, lng: -85.6595, title: 'Playa Guiones', type: 'beach' },
{ lat: 9.9823, lng: -85.6482, title: 'La Luna Restaurant', type: 'restaurant' },
{ lat: 9.9785, lng: -85.6445, title: 'Hotel Lagarta Lodge', type: 'hotel' },
{ lat: 10.0156, lng: -85.6921, title: 'Playa Ostional', type: 'beach' }
];
const markerColors = {
restaurant: '#e74c3c',
hotel: '#3498db',
beach: '#f39c12',
activity: '#2ecc71'
};
pois.forEach(poi => {
const marker = new google.maps.Marker({
position: { lat: poi.lat, lng: poi.lng },
map: map,
title: poi.title,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 8,
fillColor: markerColors[poi.type],
fillOpacity: 0.8,
strokeWeight: 2,
strokeColor: '#ffffff'
}
});
const poiInfoWindow = new google.maps.InfoWindow({
content: `
<div style="padding: 5px; font-family: 'Inter', sans-serif;">
<strong style="color: ${markerColors[poi.type]};">${poi.title}</strong>
</div>
`
});
marker.addListener('click', () => {
poiInfoWindow.open(map, marker);
});
});
}
// Initialize map when Google Maps API is loaded
if (typeof google !== 'undefined' && google.maps) {
google.maps.event.addDomListener(window, 'load', initMap);
}
// Street View Integration
function initStreetView() {
const streetViewDiv = document.getElementById('street-view');
if (!streetViewDiv || typeof google === 'undefined') {
return;
}
// Coordinates for a scenic spot near Nosara beach
const panorama = new google.maps.StreetViewPanorama(
streetViewDiv,
{
position: { lat: 9.9759, lng: -85.6532 },
pov: { heading: 270, pitch: 0 },
zoom: 1,
addressControl: false,
fullscreenControl: true,
motionTracking: true,
motionTrackingControl: true
}
);
}
// Toggle Street View
const toggleStreetViewBtn = document.getElementById('toggle-street-view');
let streetViewActive = false;
if (toggleStreetViewBtn) {
toggleStreetViewBtn.addEventListener('click', () => {
streetViewActive = !streetViewActive;
if (streetViewActive && typeof google !== 'undefined') {
initStreetView();
toggleStreetViewBtn.innerHTML = '<i class="fas fa-times"></i> Close Tour';
} else {
const streetViewDiv = document.getElementById('street-view');
streetViewDiv.innerHTML = '';
toggleStreetViewBtn.innerHTML = '<i class="fas fa-street-view"></i> 360° Virtual Tour';
}
});
}
// 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 (Formspree handles submission)
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.checkin && data.checkout) {
const checkin = new Date(data.checkin);
const checkout = new Date(data.checkout);
const today = new Date();
today.setHours(0, 0, 0, 0);
if (checkin < today) {
alert('Check-in date must be today or later');
e.preventDefault();
return;
}
if (checkout <= checkin) {
alert('Check-out date must be after check-in date');
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();
currentYearElement.textContent = `© ${currentYear} Nosara Beachfront Rentals. All rights reserved.`;
}
// 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');
}
});
});
// Disable right-click on images (optional, for image protection)
// Uncomment if needed:
/*
document.querySelectorAll('img').forEach(img => {
img.addEventListener('contextmenu', (e) => {
e.preventDefault();
});
});
*/
console.log('🌴 Nosara Beachfront Rentals - Website Loaded Successfully');