// Theme toggle logic function setTheme(theme) { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); var icon = document.getElementById('theme-icon'); var iconDesktop = document.getElementById('theme-icon-desktop'); if (icon) icon.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; if (iconDesktop) iconDesktop.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; } function toggleTheme() { const current = document.documentElement.getAttribute('data-theme') || 'light'; setTheme(current === 'light' ? 'dark' : 'light'); } var themeToggle = document.getElementById('theme-toggle'); if (themeToggle) themeToggle.addEventListener('click', toggleTheme); var themeToggleDesktop = document.getElementById('theme-toggle-desktop'); if (themeToggleDesktop) themeToggleDesktop.addEventListener('click', toggleTheme); // On load: set theme from localStorage or system (function() { let theme = localStorage.getItem('theme'); if (!theme) { theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } setTheme(theme); })(); // Page fade-in effect window.addEventListener('DOMContentLoaded', function() { document.body.classList.add('page-loaded'); // Staggered fade-in for .fade-in-up elements var fadeEls = document.querySelectorAll('.fade-in-up'); fadeEls.forEach(function(el, i) { setTimeout(function() { el.classList.add('visible'); }, 200 + i * 180); }); // Counter animation for about page const counters = document.querySelectorAll('.counter'); const animateCounters = () => { counters.forEach(counter => { const target = parseInt(counter.getAttribute('data-target')); const count = parseInt(counter.innerText); const increment = target / 100; if (count < target) { counter.innerText = Math.ceil(count + increment) + '+'; setTimeout(animateCounters, 20); } else { counter.innerText = target; } }); }; // Intersection Observer for counter animation const observerOptions = { threshold: 0.5, rootMargin: '0px 0px -100px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { animateCounters(); observer.unobserve(entry.target); } }); }, observerOptions); const aboutStats = document.querySelector('.about-stats'); if (aboutStats) { observer.observe(aboutStats); } }); // MapTiler + Leaflet integration for contact page map document.addEventListener('DOMContentLoaded', function() { var mapDiv = document.getElementById('map'); if (mapDiv) { var map = L.map('map', { center: [50.77989716345206, 4.048368809494087], // Blijkheerstraat 92, 1755 Pajottegem zoom: 13, zoomControl: true, attributionControl: false }); var maptilerKey = 'fyG4fxsIMwJnhJ9hQ1wj'; var light = L.tileLayer('https://api.maptiler.com/maps/streets-v2-pastel/{z}/{x}/{y}.png?key=' + maptilerKey, { attribution: '© OpenStreetMap contributors © MapTiler', tileSize: 512, zoomOffset: -1 }); var dark = L.tileLayer('https://api.maptiler.com/maps/streets-v2-dark/{z}/{x}/{y}.png?key=' + maptilerKey, { attribution: '© OpenStreetMap contributors © MapTiler', tileSize: 512, zoomOffset: -1 }); // Initial theme var isDark = document.documentElement.getAttribute('data-theme') === 'dark'; var currentLayer = isDark ? dark : light; currentLayer.addTo(map); // Marker L.marker([50.77989716345206, 4.048368809494087]).addTo(map) .bindPopup('Kobelly Web Solutions
Blijkheerstraat 92, 1755 Pajottegem') .openPopup(); // Listen for theme changes var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') { var newTheme = document.documentElement.getAttribute('data-theme'); if (newTheme === 'dark') { map.removeLayer(light); dark.addTo(map); } else { map.removeLayer(dark); light.addTo(map); } } }); }); observer.observe(document.documentElement, { attributes: true }); } }); // Dynamic Stats Calculation document.addEventListener('DOMContentLoaded', function() { // Calculate stats since January 2016 var startDate = new Date('2014-01-01'); var now = new Date(); // Calculate years of experience var years = now.getFullYear() - startDate.getFullYear(); // If before Jan 1 this year, subtract 1 if ( now.getMonth() < startDate.getMonth() || (now.getMonth() === startDate.getMonth() && now.getDate() < startDate.getDate()) ) { years--; } // Calculate hours coded (assuming ~2000 hours per year) var hoursCoded = years * 2000; // Update all stats sections across the website var statItems = document.querySelectorAll('.stat-item h3'); statItems.forEach(function(statEl) { var statText = statEl.textContent; // Update years experience if (statText.includes('Years Experience') || statText.includes('8+') || statText.includes('5+') || statText.includes('10+')) { statEl.textContent = years + '+'; } // Update hours coded if (statText.includes('Hours Coded') || statText.includes('15,000+') || statText.includes('1000+')) { statEl.textContent = hoursCoded.toLocaleString() + '+'; } }); // Also update any elements with specific IDs (for backward compatibility) var yearsEl = document.getElementById('years-experience'); var hoursEl = document.getElementById('hours-coded'); if (yearsEl) yearsEl.textContent = years + '+'; if (hoursEl) hoursEl.textContent = hoursCoded.toLocaleString() + '+'; }); // Interactive Design Showcase document.addEventListener('DOMContentLoaded', function() { const designCards = document.querySelectorAll('.design-card'); const resetButton = document.getElementById('reset-theme'); const previewContainer = document.getElementById('theme-preview'); const previewContent = document.querySelector('.theme-preview-content'); // Theme configurations const themes = { 'modern-minimal': { name: 'Modern Minimal', description: 'Clean, spacious design with subtle animations and modern typography.', colors: { primary: '#6c757d', secondary: '#f8f9fa', accent: '#e9ecef' }, features: ['Clean Typography', 'Subtle Animations', 'Minimal Layout'] }, 'bold-vibrant': { name: 'Bold & Vibrant', description: 'Eye-catching design with bold colors and dynamic elements.', colors: { primary: '#ff6b6b', secondary: '#4ecdc4', accent: '#45b7d1' }, features: ['Bold Colors', 'Dynamic Elements', 'High Contrast'] }, 'elegant-professional': { name: 'Elegant Professional', description: 'Sophisticated design with premium styling and professional appeal.', colors: { primary: '#2c3e50', secondary: '#34495e', accent: '#95a5a6' }, features: ['Premium Styling', 'Professional Appeal', 'Sophisticated Layout'] }, 'tech-startup': { name: 'Tech Startup', description: 'Modern tech-focused design with innovative layouts and animations.', colors: { primary: '#3498db', secondary: '#2ecc71', accent: '#f39c12' }, features: ['Innovative Layout', 'Tech-focused', 'Modern Animations'] }, 'creative-agency': { name: 'Creative Agency', description: 'Artistic design with creative layouts and expressive visual elements.', colors: { primary: '#9b59b6', secondary: '#e74c3c', accent: '#f1c40f' }, features: ['Creative Layout', 'Artistic Elements', 'Expressive Design'] }, 'ecommerce': { name: 'E-commerce', description: 'Optimized design for online stores with product-focused layouts.', colors: { primary: '#e67e22', secondary: '#27ae60', accent: '#8e44ad' }, features: ['Product-focused', 'Shopping Optimized', 'Conversion-driven'] } }; // Apply theme to the mini website only function applyTheme(themeName) { const theme = themes[themeName]; if (!theme) return; const miniWebsite = document.getElementById('mini-website'); const previewContainer = document.querySelector('.theme-preview-container'); if (!miniWebsite) return; // Remove all existing theme classes from mini website miniWebsite.classList.remove('theme-modern-minimal', 'theme-bold-vibrant', 'theme-elegant-professional', 'theme-tech-startup', 'theme-creative-agency', 'theme-ecommerce'); // Add new theme class to mini website miniWebsite.classList.add(`theme-${themeName}`); // Update CSS custom properties for mini website only miniWebsite.style.setProperty('--bs-primary', theme.colors.primary); miniWebsite.style.setProperty('--bs-primary-rgb', hexToRgb(theme.colors.primary)); // Update preview container data-theme attribute for border styling if (previewContainer) { previewContainer.setAttribute('data-theme', themeName); } // Update active card designCards.forEach(card => card.classList.remove('active')); const activeCard = document.querySelector(`[data-theme="${themeName}"]`); if (activeCard) activeCard.classList.add('active'); // Activate preview container previewContainer.classList.add('active'); // Scroll to preview container with smooth animation if (previewContainer) { const previewTop = previewContainer.offsetTop; window.scrollTo({ top: previewTop - 100, behavior: 'smooth' }); } } // Reset to default theme function resetTheme() { const miniWebsite = document.getElementById('mini-website'); const previewContainer = document.querySelector('.theme-preview-container'); if (miniWebsite) { miniWebsite.classList.remove('theme-modern-minimal', 'theme-bold-vibrant', 'theme-elegant-professional', 'theme-tech-startup', 'theme-creative-agency', 'theme-ecommerce'); miniWebsite.style.removeProperty('--bs-primary'); miniWebsite.style.removeProperty('--bs-primary-rgb'); } // Remove data-theme attribute from preview container if (previewContainer) { previewContainer.removeAttribute('data-theme'); } designCards.forEach(card => card.classList.remove('active')); previewContainer.classList.remove('active'); } // Helper function to convert hex to RGB function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}` : '0, 0, 0'; } // Event listeners designCards.forEach(card => { card.addEventListener('click', function() { const themeName = this.getAttribute('data-theme'); applyTheme(themeName); }); }); if (resetButton) { resetButton.addEventListener('click', resetTheme); } // Add color swatch styles const style = document.createElement('style'); style.textContent = ` .color-swatch { width: 30px; height: 30px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .preview-mockup { width: 200px; height: 150px; border-radius: 10px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); margin: 0 auto; } .mockup-header { height: 30px; } .mockup-content { padding: 20px; height: 120px; } .mockup-title { height: 20px; border-radius: 4px; margin-bottom: 15px; width: 80%; } .mockup-text { height: 12px; background: rgba(255,255,255,0.3); border-radius: 4px; margin-bottom: 8px; width: 100%; } .mockup-text:last-child { width: 60%; } `; document.head.appendChild(style); });