/**
* ARCHI-TECH Premium JavaScript Features
*/
document.addEventListener("DOMContentLoaded", () => {
// 1. Before/After Image Slider Controller
const sliders = document.querySelectorAll('.ba-slider');
sliders.forEach(slider => {
const resize = slider.querySelector('.ba-resize');
const handle = slider.querySelector('.ba-handle');
const moveSlider = (clientX) => {
const rect = slider.getBoundingClientRect();
const position = clientX - rect.left;
let percentage = (position / rect.width) * 100;
// Boundary constraints
if (percentage < 0) percentage = 0;
if (percentage > 100) percentage = 100;
resize.style.width = `${percentage}%`;
handle.style.left = `${percentage}%`;
};
let isSliding = false;
slider.addEventListener('mousedown', (e) => {
isSliding = true;
moveSlider(e.clientX);
});
window.addEventListener('mouseup', () => {
isSliding = false;
});
slider.addEventListener('mousemove', (e) => {
if (!isSliding) return;
moveSlider(e.clientX);
});
// Touch support for mobiles
slider.addEventListener('touchstart', (e) => {
isSliding = true;
moveSlider(e.touches[0].clientX);
});
window.addEventListener('touchend', () => {
isSliding = false;
});
slider.addEventListener('touchmove', (e) => {
if (!isSliding) return;
moveSlider(e.touches[0].clientX);
});
});
// 2. Dynamic Numbers Counter Animation
const counters = document.querySelectorAll('.counter-number');
const animateCounters = () => {
counters.forEach(counter => {
const target = +counter.getAttribute('data-target');
let count = 0;
const speed = target / 40; // division increments
const updateCount = () => {
if (count < target) {
count += speed;
counter.innerText = Math.ceil(count);
setTimeout(updateCount, 30);
} else {
counter.innerText = target;
}
};
updateCount();
});
};
// Observer triggers counting once statistics enter viewport
const statsSection = document.querySelector('.statistics-section');
if (statsSection && counters.length > 0) {
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
animateCounters();
obs.unobserve(entry.target);
}
});
}, { threshold: 0.2 });
observer.observe(statsSection);
}
// 5. Interactive Sri Lanka Projects Map
const initProjectMap = (containerId) => {
const container = document.getElementById(containerId);
if (!container || typeof L === 'undefined') return;
// Sri Lanka Center coordinates
const map = L.map(containerId, {
scrollWheelZoom: false,
zoomControl: true
}).setView([7.8731, 80.7718], 7.5);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
attribution: '© OpenStreetMap contributors © CARTO'
}).addTo(map);
// Custom div-based pin builder using SVGs
const getCustomPin = (color) => {
return L.divIcon({
className: 'custom-leaflet-pin',
html: ``,
iconSize: [28, 28],
iconAnchor: [14, 28],
popupAnchor: [0, -25]
});
};
// Static project location tags mapping Completed/In Progress/Future statuses
const dataPoints = [
{ name: 'Colombo Heights', category: 'Luxury Residential', location: 'Colombo 03', status: 'Completed', color: '#1B263B', lat: 6.9150, lng: 79.8500 },
{ name: 'Zen Oasis Gardens', category: 'Landscape Design', location: 'Kandy', status: 'In Progress', color: '#B2BEB5', lat: 7.2906, lng: 80.6337 },
{ name: 'The Grand Horizon Villa', category: 'Architecture & Planning', location: 'Galle', status: 'Completed', color: '#1B263B', lat: 6.0535, lng: 80.2210 },
{ name: 'Trinco Heights Resort', category: 'Architecture & Design', location: 'Trincomalee', status: 'Future', color: '#555555', lat: 8.5873, lng: 81.2152 },
{ name: 'Heritage Conservation Center', category: 'Architecture & Planning', location: 'Anuradhapura', status: 'Completed', color: '#1B263B', lat: 8.3114, lng: 80.4037 },
{ name: 'Jaffna Library Annex', category: 'Construction Services', location: 'Jaffna', status: 'Completed', color: '#1B263B', lat: 9.6615, lng: 80.0255 },
{ name: 'Badulla Eco-Lodge', category: 'Urban Green Solutions', location: 'Badulla', status: 'Completed', color: '#1B263B', lat: 6.9934, lng: 81.0550 },
{ name: 'Tea Estate Luxury Villa', category: 'Interior Design', location: 'Nuwara Eliya', status: 'Completed', color: '#1B263B', lat: 6.9497, lng: 80.7891 },
{ name: 'Gemological Trade Center', category: 'Architecture & Planning', location: 'Ratnapura', status: 'Future', color: '#555555', lat: 6.6828, lng: 80.3992 },
{ name: 'Maritime Logistics Office', category: 'Construction Services', location: 'Hambantota', status: 'Completed', color: '#1B263B', lat: 6.1248, lng: 81.1185 }
];
// Also add the Studio itself as a distinct glowing pin!
dataPoints.push({
name: 'ARCHI-TECH Head Studio',
category: 'Main Design Office',
location: 'No 35B Wijerama Rd, Nugegoda',
status: 'Design Studio',
color: '#B2BEB5', // Ash Gray
lat: 6.8861,
lng: 79.8942,
isStudio: true
});
dataPoints.forEach(p => {
let statusBadge = `Status: ${p.status}`;
if (p.isStudio) {
statusBadge = `Headquarters`;
}
const popupHtml = `
${p.name}
${p.category}
${p.location}
${statusBadge}
`;
const marker = L.marker([p.lat, p.lng], { icon: getCustomPin(p.color) })
.bindPopup(popupHtml)
.addTo(map);
if (p.isStudio) {
marker.openPopup();
}
});
};
initProjectMap('projectMapIndex');
initProjectMap('projectMapContact');
});
/**
* 3. Premium Responsive Lightbox Module
*/
window.openLightbox = function(imageSrc, titleText = '') {
let lightboxModal = document.getElementById('customLightboxModal');
if (!lightboxModal) {
const modalHtml = `
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
lightboxModal = document.getElementById('customLightboxModal');
}
document.getElementById('lightboxImage').src = imageSrc;
document.getElementById('lightboxTitle').innerText = titleText;
const bsModal = new bootstrap.Modal(lightboxModal);
bsModal.show();
};
/**
* 4. Animated Grid Category Filtering
*/
window.filterGrid = function(category) {
const items = document.querySelectorAll('.filter-item');
const buttons = document.querySelectorAll('.filter-btn');
buttons.forEach(btn => {
if (btn.getAttribute('data-filter') === category) {
btn.classList.add('active');
btn.classList.remove('btn-outline-light');
btn.classList.add('btn-gold-filled');
} else {
btn.classList.remove('active');
btn.classList.add('btn-outline-light');
btn.classList.remove('btn-gold-filled');
}
});
items.forEach(item => {
const itemCat = item.getAttribute('data-category');
if (category === 'all' || (itemCat && itemCat.toLowerCase().trim() === category.toLowerCase().trim())) {
item.style.display = 'block';
setTimeout(() => {
item.style.opacity = '1';
item.style.transform = 'scale(1)';
}, 50);
} else {
item.style.opacity = '0';
item.style.transform = 'scale(0.8)';
setTimeout(() => {
item.style.display = 'none';
}, 300);
}
});
};
/**
* 5. Header Search Suggestions (Live AJAX Autocomplete)
*/
(function () {
const input = document.getElementById('header-search-input');
const dropdown = document.getElementById('header-search-suggestions');
const form = document.getElementById('header-search-form');
if (!input || !dropdown || !form) return;
let debounceTimer = null;
let activeIndex = -1;
let currentResults = [];
function escapeHtml(s) {
return s.replace(/&/g,'&').replace(//g,'>');
}
function escapeRegex(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function highlight(text, query) {
if (!query) return escapeHtml(text);
const re = new RegExp('(' + escapeRegex(query) + ')', 'gi');
return escapeHtml(text).replace(re, '$1');
}
function showSuggestions() { dropdown.classList.add('active'); }
function hideSuggestions() {
dropdown.classList.remove('active');
dropdown.innerHTML = '';
activeIndex = -1;
currentResults = [];
}
function setActiveItem(index) {
const items = dropdown.querySelectorAll('.search-suggestion-item');
items.forEach(el => el.classList.remove('keyboard-active'));
if (index >= 0 && index < items.length) {
items[index].classList.add('keyboard-active');
items[index].scrollIntoView({ block: 'nearest' });
}
activeIndex = index;
}
function renderSuggestions(results, query) {
dropdown.innerHTML = '';
activeIndex = -1;
currentResults = results;
if (!results.length) { hideSuggestions(); return; }
results.forEach(item => {
const li = document.createElement('li');
li.setAttribute('role', 'option');
const a = document.createElement('a');
a.className = 'search-suggestion-item';
// Build URL relative to current host
const base = window.location.pathname.replace(/\/[^/]+\.php.*$/, '/');
a.href = base + 'projects.php?search=' + encodeURIComponent(item.name);
const nameEl = document.createElement('span');
nameEl.className = 'suggest-name';
nameEl.innerHTML = highlight(item.name, query);
const metaEl = document.createElement('span');
metaEl.className = 'suggest-meta';
const catEl = document.createElement('span');
catEl.className = 'suggest-category';
catEl.textContent = item.category;
metaEl.appendChild(catEl);
if (item.location) {
const locEl = document.createElement('span');
locEl.className = 'suggest-location';
locEl.textContent = item.location;
metaEl.appendChild(locEl);
}
a.appendChild(nameEl);
a.appendChild(metaEl);
li.appendChild(a);
dropdown.appendChild(li);
});
const footer = document.createElement('div');
footer.className = 'search-suggestions-footer';
footer.textContent = 'Press Enter to see all results';
footer.addEventListener('click', () => form.submit());
dropdown.appendChild(footer);
showSuggestions();
}
function fetchSuggestions(query) {
const base = window.location.pathname.replace(/\/[^/]+\.php.*$/, '/');
fetch(base + 'search-suggestions.php?q=' + encodeURIComponent(query))
.then(res => res.json())
.then(data => {
if (input.value.trim() === query) renderSuggestions(data, query);
})
.catch(() => hideSuggestions());
}
input.addEventListener('input', () => {
clearTimeout(debounceTimer);
const q = input.value.trim();
if (q.length < 2) {
hideSuggestions();
return;
}
debounceTimer = setTimeout(() => fetchSuggestions(q), 220);
});
input.addEventListener('keydown', e => {
if (!dropdown.classList.contains('active')) return;
const items = dropdown.querySelectorAll('.search-suggestion-item');
if (e.key === 'ArrowDown') {
e.preventDefault();
setActiveItem(Math.min(activeIndex + 1, items.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setActiveItem(Math.max(activeIndex - 1, -1));
} else if (e.key === 'Enter' && activeIndex >= 0 && items[activeIndex]) {
e.preventDefault();
items[activeIndex].click();
} else if (e.key === 'Escape') {
hideSuggestions();
input.blur();
}
});
input.addEventListener('focus', () => {
const q = input.value.trim();
if (q.length >= 2 && currentResults.length > 0) renderSuggestions(currentResults, q);
});
document.addEventListener('click', e => {
if (!form.contains(e.target)) hideSuggestions();
});
})();