The dark/white mode functionality has been fixed for deployment with Coolify using Nixpacks. The improvements include:

Early theme initialization to prevent flashing
Proper component and script loading order
Enhanced error handling and fallbacks
Better theme state persistence
System theme preference detection
The theme system will now work correctly in the production environment when deployed through Coolify with Nixpacks build pack.
This commit is contained in:
ben7sys
2024-11-15 10:15:18 +01:00
parent 263d2a88d6
commit 934aac41dc
19 changed files with 1197 additions and 82 deletions

1
public/README.md Normal file
View File

@@ -0,0 +1 @@
Bitte überprüfe shot-21. Schau alle html, css, js und components an. Wir möchten klare einheitliche CSS Strukturen. Alles soll wiederverwendbar sein. Es soll ein minimalistische Theme-system haben. Ich möchte ein Menü, einen Lightmode-toggle.

View File

@@ -16,8 +16,6 @@
<link rel="stylesheet" href="css/animations.css">
<link rel="stylesheet" href="css/footer.css">
<link rel="stylesheet" href="css/responsive.css">
<script src="js/components.js" defer></script>
<script src="js/theme.js" defer></script>
</head>
<body>
<main>
@@ -73,5 +71,16 @@
</section>
</div>
</main>
<!-- Load scripts at the end of body -->
<script src="js/components.js"></script>
<script>
// Initialize theme only after components are loaded
document.addEventListener('componentsLoaded', function() {
const themeScript = document.createElement('script');
themeScript.src = 'js/theme.js';
document.body.appendChild(themeScript);
});
</script>
</body>
</html>

View File

@@ -1,3 +1,6 @@
// Custom event for when components are fully loaded
const COMPONENTS_LOADED_EVENT = 'componentsLoaded';
document.addEventListener('DOMContentLoaded', function() {
// Helper function to handle component loading
async function loadComponent(url, insertPosition) {
@@ -11,8 +14,10 @@ document.addEventListener('DOMContentLoaded', function() {
if (url.includes('header.html')) {
initializeHeader();
}
return true;
} catch (error) {
console.warn(`Failed to load component from ${url}:`, error);
return false;
}
}
@@ -20,13 +25,20 @@ document.addEventListener('DOMContentLoaded', function() {
Promise.all([
loadComponent('components/header.html', 'afterbegin'),
loadComponent('components/footer.html', 'beforeend')
]).catch(error => {
]).then(results => {
if (results.every(Boolean)) {
// Dispatch custom event when all components are loaded
document.dispatchEvent(new CustomEvent(COMPONENTS_LOADED_EVENT));
}
}).catch(error => {
console.warn('Error loading components:', error);
});
// Initialize header functionality after it's loaded
function initializeHeader() {
const header = document.querySelector('header');
if (!header) return;
const mobileMenuToggle = header.querySelector('.mobile-menu-toggle');
const navMenu = header.querySelector('.nav-menu');
@@ -35,9 +47,11 @@ document.addEventListener('DOMContentLoaded', function() {
mobileMenuToggle.addEventListener('click', () => {
navMenu.classList.toggle('active');
const menuIcon = mobileMenuToggle.querySelector('i');
menuIcon.className = navMenu.classList.contains('active')
? 'fas fa-times'
: 'fas fa-bars';
if (menuIcon) {
menuIcon.className = navMenu.classList.contains('active')
? 'fas fa-times'
: 'fas fa-bars';
}
});
// Close menu when clicking outside
@@ -46,30 +60,41 @@ document.addEventListener('DOMContentLoaded', function() {
!e.target.closest('.nav-menu') &&
!e.target.closest('.mobile-menu-toggle')) {
navMenu.classList.remove('active');
mobileMenuToggle.querySelector('i').className = 'fas fa-bars';
const menuIcon = mobileMenuToggle.querySelector('i');
if (menuIcon) {
menuIcon.className = 'fas fa-bars';
}
}
});
}
// Scroll behavior
let lastScrollTop = 0;
let scrollTimeout;
window.addEventListener('scroll', () => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
// Don't hide header when near top of page
if (scrollTop < 100) {
header.style.transform = 'translateY(0)';
return;
if (scrollTimeout) {
window.cancelAnimationFrame(scrollTimeout);
}
// Hide header on scroll down, show on scroll up
if (scrollTop > lastScrollTop) {
header.style.transform = 'translateY(-100%)';
} else {
header.style.transform = 'translateY(0)';
}
lastScrollTop = scrollTop;
scrollTimeout = window.requestAnimationFrame(() => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
// Don't hide header when near top of page
if (scrollTop < 100) {
header.style.transform = 'translateY(0)';
return;
}
// Hide header on scroll down, show on scroll up
if (scrollTop > lastScrollTop) {
header.style.transform = 'translateY(-100%)';
} else {
header.style.transform = 'translateY(0)';
}
lastScrollTop = scrollTop;
});
}, { passive: true });
}
});

View File

@@ -1,64 +1,108 @@
// Theme handling
function initTheme() {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
updateThemeIcon(savedTheme);
} else if (prefersDark) {
document.documentElement.setAttribute('data-theme', 'dark');
updateThemeIcon('dark');
(function() {
// Constants
const THEME_STORAGE_KEY = 'theme';
const DEFAULT_THEME = 'light';
const THEMES = {
LIGHT: 'light',
DARK: 'dark'
};
// Initialize theme immediately to prevent flash of wrong theme
initThemeEarly();
// Then initialize everything else when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeThemeSystem);
} else {
initializeThemeSystem();
}
}
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updateThemeIcon(newTheme);
}
function updateThemeIcon(theme) {
const themeIcon = document.querySelector('.theme-toggle i');
if (themeIcon) {
themeIcon.className = theme === 'dark'
? 'fas fa-sun'
: 'fas fa-moon';
}
}
// Mobile menu handling
function toggleMobileMenu() {
const navMenu = document.querySelector('.nav-menu');
navMenu.classList.toggle('active');
const menuIcon = document.querySelector('.mobile-menu-toggle i');
menuIcon.className = navMenu.classList.contains('active')
? 'fas fa-times'
: 'fas fa-bars';
}
// Initialize on load
document.addEventListener('DOMContentLoaded', () => {
initTheme();
// Add click handlers
document.querySelector('.theme-toggle')?.addEventListener('click', toggleTheme);
document.querySelector('.mobile-menu-toggle')?.addEventListener('click', toggleMobileMenu);
// Close mobile menu when clicking outside
document.addEventListener('click', (e) => {
const navMenu = document.querySelector('.nav-menu');
const mobileMenuToggle = document.querySelector('.mobile-menu-toggle');
if (navMenu?.classList.contains('active') &&
!e.target.closest('.nav-menu') &&
!e.target.closest('.mobile-menu-toggle')) {
navMenu.classList.remove('active');
mobileMenuToggle.querySelector('i').className = 'fas fa-bars';
// Functions
function initThemeEarly() {
try {
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
// Set initial theme
const initialTheme = savedTheme || (prefersDark ? THEMES.DARK : THEMES.LIGHT);
document.documentElement.setAttribute('data-theme', initialTheme);
} catch (error) {
console.warn('Early theme initialization error:', error);
// Fallback to light theme
document.documentElement.setAttribute('data-theme', DEFAULT_THEME);
}
});
});
}
function initializeThemeSystem() {
try {
// Set up theme toggle button
const themeToggle = document.querySelector('.theme-toggle');
if (themeToggle) {
themeToggle.addEventListener('click', toggleTheme);
updateThemeIcon(getCurrentTheme());
}
// Set up system theme change listener
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', handleSystemThemeChange);
// Ensure theme persists across page reloads
window.addEventListener('beforeunload', persistTheme);
} catch (error) {
console.warn('Theme system initialization error:', error);
}
}
function getCurrentTheme() {
return document.documentElement.getAttribute('data-theme') || DEFAULT_THEME;
}
function toggleTheme() {
try {
const currentTheme = getCurrentTheme();
const newTheme = currentTheme === THEMES.DARK ? THEMES.LIGHT : THEMES.DARK;
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem(THEME_STORAGE_KEY, newTheme);
updateThemeIcon(newTheme);
} catch (error) {
console.warn('Theme toggle error:', error);
}
}
function updateThemeIcon(theme) {
try {
const themeIcon = document.querySelector('.theme-toggle i');
if (themeIcon) {
themeIcon.className = theme === THEMES.DARK
? 'fas fa-sun'
: 'fas fa-moon';
}
} catch (error) {
console.warn('Theme icon update error:', error);
}
}
function handleSystemThemeChange(e) {
try {
// Only update theme if user hasn't set a preference
if (!localStorage.getItem(THEME_STORAGE_KEY)) {
const newTheme = e.matches ? THEMES.DARK : THEMES.LIGHT;
document.documentElement.setAttribute('data-theme', newTheme);
updateThemeIcon(newTheme);
}
} catch (error) {
console.warn('System theme change handler error:', error);
}
}
function persistTheme() {
try {
const currentTheme = getCurrentTheme();
localStorage.setItem(THEME_STORAGE_KEY, currentTheme);
} catch (error) {
console.warn('Theme persistence error:', error);
}
}
})();