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:
1
public/README.md
Normal file
1
public/README.md
Normal 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.
|
||||
@@ -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>
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user