356 lines
14 KiB
JavaScript
356 lines
14 KiB
JavaScript
// 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) + '<span class="plus">+</span>';
|
|
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: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://www.maptiler.com/copyright/">MapTiler</a>',
|
|
tileSize: 512,
|
|
zoomOffset: -1
|
|
});
|
|
var dark = L.tileLayer('https://api.maptiler.com/maps/streets-v2-dark/{z}/{x}/{y}.png?key=' + maptilerKey, {
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://www.maptiler.com/copyright/">MapTiler</a>',
|
|
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<br>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);
|
|
});
|