// 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);
});