From 934aac41dcb51f9883e134723543fc4d276564a4 Mon Sep 17 00:00:00 2001 From: ben7sys Date: Fri, 15 Nov 2024 10:15:18 +0100 Subject: [PATCH] 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. --- public-pre/components/footer.html | 31 ++++++ public-pre/components/header.html | 22 ++++ public-pre/css/animations.css | 11 ++ public-pre/css/base.css | 90 ++++++++++++++++ public-pre/css/components.css | 139 ++++++++++++++++++++++++ public-pre/css/footer.css | 80 ++++++++++++++ public-pre/css/header.css | 107 +++++++++++++++++++ public-pre/css/layout.css | 24 +++++ public-pre/css/responsive.css | 172 ++++++++++++++++++++++++++++++ public-pre/css/variables.css | 36 +++++++ public-pre/datenschutz.html | 58 ++++++++++ public-pre/impressum.html | 50 +++++++++ public-pre/index.html | 77 +++++++++++++ public-pre/js/components.js | 75 +++++++++++++ public-pre/js/theme.js | 64 +++++++++++ public/README.md | 1 + public/index.html | 13 ++- public/js/components.js | 65 +++++++---- public/js/theme.js | 164 +++++++++++++++++----------- 19 files changed, 1197 insertions(+), 82 deletions(-) create mode 100644 public-pre/components/footer.html create mode 100644 public-pre/components/header.html create mode 100644 public-pre/css/animations.css create mode 100644 public-pre/css/base.css create mode 100644 public-pre/css/components.css create mode 100644 public-pre/css/footer.css create mode 100644 public-pre/css/header.css create mode 100644 public-pre/css/layout.css create mode 100644 public-pre/css/responsive.css create mode 100644 public-pre/css/variables.css create mode 100644 public-pre/datenschutz.html create mode 100644 public-pre/impressum.html create mode 100644 public-pre/index.html create mode 100644 public-pre/js/components.js create mode 100644 public-pre/js/theme.js create mode 100644 public/README.md diff --git a/public-pre/components/footer.html b/public-pre/components/footer.html new file mode 100644 index 0000000..aa29293 --- /dev/null +++ b/public-pre/components/footer.html @@ -0,0 +1,31 @@ + diff --git a/public-pre/components/header.html b/public-pre/components/header.html new file mode 100644 index 0000000..5ae002d --- /dev/null +++ b/public-pre/components/header.html @@ -0,0 +1,22 @@ +
+
+
+
+ 7SYS +
+
+ + + + +
+
diff --git a/public-pre/css/animations.css b/public-pre/css/animations.css new file mode 100644 index 0000000..685e6c2 --- /dev/null +++ b/public-pre/css/animations.css @@ -0,0 +1,11 @@ +@keyframes fadeIn { + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes shinyEffect { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} diff --git a/public-pre/css/base.css b/public-pre/css/base.css new file mode 100644 index 0000000..1bc269b --- /dev/null +++ b/public-pre/css/base.css @@ -0,0 +1,90 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + transition: color var(--transition-speed) ease, + background-color var(--transition-speed) ease; +} + +body { + font-family: 'Montserrat', 'Open Sans', -apple-system, BlinkMacSystemFont, system-ui, sans-serif; + background: var(--bg-main); + color: var(--text); + line-height: 1.6; + min-height: 100vh; +} + +h1 { + font-size: 2rem; + margin-bottom: var(--spacing-lg); + color: var(--text); + opacity: 0.85; + font-weight: 600; +} + +h2 { + font-size: 2rem; + margin-bottom: var(--spacing-lg); + color: var(--text); +} + +h3 { + color: var(--text); + margin-bottom: var(--spacing-md); +} + +h1.h1-hero { + font-size: 2.5rem; + margin: var(--spacing-xl); + padding: var(--spacing-xl); + color: var(--text); + opacity: 0.85; + font-weight: 100; + text-align: center; +} + +p { + margin-bottom: var(--spacing-md); +} + +a { + color: var(--primary); + text-decoration: none; + transition: opacity var(--transition-speed) ease; +} + +a:hover { + opacity: 0.8; +} + +main { + padding-top: var(--header-height); +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--spacing-md); +} + +section { + padding: var(--spacing-xl) 0; +} + +/* Utility classes */ +.text-center { + text-align: center; +} + +.mb-1 { margin-bottom: var(--spacing-sm); } +.mb-2 { margin-bottom: var(--spacing-md); } +.mb-3 { margin-bottom: var(--spacing-lg); } +.mb-4 { margin-bottom: var(--spacing-xl); } + +.mt-1 { margin-top: var(--spacing-sm); } +.mt-2 { margin-top: var(--spacing-md); } +.mt-3 { margin-top: var(--spacing-lg); } +.mt-4 { margin-top: var(--spacing-xl); } diff --git a/public-pre/css/components.css b/public-pre/css/components.css new file mode 100644 index 0000000..6db0782 --- /dev/null +++ b/public-pre/css/components.css @@ -0,0 +1,139 @@ +/* Card Base Styles */ +.card-base { + background: var(--bg-card); + padding: var(--spacing-xl); + border-radius: var(--border-radius); + border: 1px solid var(--border-color); + transition: transform var(--transition-speed) ease, + border-color var(--transition-speed) ease; +} + +.card-base:hover { + border-color: var(--primary); +} + +/* Mission Vision Cards */ +.mission-vision { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--spacing-lg); + margin: var(--spacing-xl) 0; +} + +.mission-vision div { + background: var(--bg-card); + padding: var(--spacing-xl); + border-radius: var(--border-radius); + border: 1px solid var(--border-color); + text-align: center; +} + +.mission-vision h3 { + color: var(--primary); + margin-bottom: var(--spacing-md); +} + +/* Service Cards */ +.service-card { + background: var(--bg-card); + padding: var(--spacing-xl); + border-radius: var(--border-radius); + border: 1px solid var(--border-color); + margin: var(--spacing-lg) 0; + transition: transform var(--transition-speed) ease, + border-color var(--transition-speed) ease; +} + +.service-card:hover { + transform: translateY(-5px); + border-color: var(--primary); +} + +.service-card h3 { + color: var(--primary); + margin-bottom: var(--spacing-md); +} + +/* Contact Info Card */ +.contact-info { + background: var(--bg-card); + padding: var(--spacing-xl); + border-radius: var(--border-radius); + border: 1px solid var(--border-color); + margin: var(--spacing-xl) 0; +} + +.contact-info a { + color: var(--primary); + text-decoration: none; + display: block; + margin: var(--spacing-sm) 0; + transition: opacity var(--transition-speed) ease; +} + +.contact-info a:hover { + opacity: 0.8; +} + +/* Quote Styles */ +.quote { + font-size: 1.2rem; + line-height: 1.6; + padding: var(--spacing-xl); + margin: var(--spacing-xl) 0; + background: var(--bg-card); + border-left: 4px solid var(--primary); + border-radius: 0 var(--border-radius) var(--border-radius) 0; + color: var(--text); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +.hero-quote { + font-size: 1.3rem; + line-height: 1.7; + color: var(--text); + max-width: 900px; + margin: var(--spacing-xl) auto; + padding: var(--spacing-md) 0; + position: relative; + font-weight: 300; + opacity: 0.9; +} + +.hero-quote::before { + content: '"'; + position: absolute; + top: -30px; + left: -10px; + font-size: 3rem; + color: var(--primary); + opacity: 0.3; +} + +.welcome-quote { + font-size: 1.1rem; + text-align: center; + padding: var(--spacing-xl); +} + +.welcome-quote:before { + content: '°'; + position: absolute; + top: -10px; + left: 0; + font-size: 1rem; + color: var(--primary); + opacity: 0.2; +} + +/* Logo Sub Text */ +.logo-sub { + display: block; + text-align: center; + font-size: 0.9rem; + color: var(--text-muted); + margin: var(--spacing-xl) auto; + max-width: 800px; + line-height: 1.4; + font-style: italic; +} diff --git a/public-pre/css/footer.css b/public-pre/css/footer.css new file mode 100644 index 0000000..f10c571 --- /dev/null +++ b/public-pre/css/footer.css @@ -0,0 +1,80 @@ +footer { + background: var(--bg-card); + padding: var(--spacing-xl) var(--spacing-md); + margin-top: calc(2 * var(--spacing-xl)); + border-top: 1px solid var(--border-color); + display: flex; + flex-direction: column; + align-items: center; + gap: var(--spacing-lg); +} + +.contact-block { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--spacing-lg); + margin-bottom: var(--spacing-sm); +} + +.contact-item { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.contact-item i { + color: var(--primary); + font-size: 1.2rem; +} + +.contact-item a { + color: var(--text-muted); + text-decoration: none; + transition: color var(--transition-speed) ease; +} + +.contact-item a:hover { + color: var(--primary); +} + +.social-links { + display: flex; + gap: var(--spacing-lg); + justify-content: center; +} + +.social-links a { + color: var(--text-muted); + text-decoration: none; + transition: color var(--transition-speed) ease; + font-size: 1.5rem; +} + +.social-links a:hover { + color: var(--primary); +} + +.legal-links { + text-align: center; + font-size: 0.9rem; + color: var(--text-muted); +} + +.legal-links a { + color: var(--text-muted); + text-decoration: none; + margin: 0 var(--spacing-sm); + transition: color var(--transition-speed) ease; +} + +.legal-links a:hover { + color: var(--primary); +} + +@media (max-width: 768px) { + .contact-block { + flex-direction: column; + align-items: center; + } +} diff --git a/public-pre/css/header.css b/public-pre/css/header.css new file mode 100644 index 0000000..eb2d131 --- /dev/null +++ b/public-pre/css/header.css @@ -0,0 +1,107 @@ +header { + position: fixed; + top: 0; + width: 100%; + background: linear-gradient(350deg, var(--header-gradient-1), var(--header-gradient-2)); + backdrop-filter: blur(10px); + padding: var(--spacing-lg) var(--spacing-md); + z-index: 1000; + border-bottom: 1px solid var(--border-color); + left: 0; + right: 0; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + transition: background-color 0.7s ease, transform 0.7s ease, box-shadow 0.7s ease; + max-height: var(--header-height); +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--spacing-md); +} + +.logo-container { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.logo-text { + font-size: 2.2rem; + font-weight: bold; + text-align: center; + line-height: 1; +} + +.logo-text span:nth-child(1) { color: var(--primary-gradient-1); } +.logo-text span:nth-child(2) { color: var(--primary-gradient-2); } +.logo-text span:nth-child(3) { color: var(--primary-gradient-3); } +.logo-text span:nth-child(4) { color: var(--primary-gradient-4); } + +.nav-menu { + display: flex; + gap: var(--spacing-lg); + align-items: center; +} + +.nav-menu a { + color: var(--text); + text-decoration: none; + font-size: 1rem; + transition: color var(--transition-speed) ease; +} + +.nav-menu a:hover { + color: var(--primary); +} + +.theme-toggle { + background: none; + border: none; + color: var(--text); + cursor: pointer; + padding: var(--spacing-sm); + display: flex; + align-items: center; + justify-content: center; + transition: color var(--transition-speed) ease; +} + +.theme-toggle:hover { + color: var(--primary); +} + +.mobile-menu-toggle { + display: none; + background: none; + border: none; + color: var(--text); + cursor: pointer; + padding: var(--spacing-sm); +} + +@media (max-width: 768px) { + .mobile-menu-toggle { + display: block; + } + + .nav-menu { + display: none; + position: absolute; + top: var(--header-height); + left: 0; + right: 0; + background: var(--bg-card); + padding: var(--spacing-md); + flex-direction: column; + align-items: center; + border-bottom: 1px solid var(--border-color); + } + + .nav-menu.active { + display: flex; + } +} diff --git a/public-pre/css/layout.css b/public-pre/css/layout.css new file mode 100644 index 0000000..613450b --- /dev/null +++ b/public-pre/css/layout.css @@ -0,0 +1,24 @@ +.container { + max-width: 800px; + margin: 0 auto; + padding: 0 1rem; +} + +main { + margin-top: 2rem; + padding: 2rem 0; +} + +section { + margin: 4rem 0; + opacity: 0; + transform: translateY(20px); + animation: fadeIn 0.6s ease forwards; +} + +.mission-vision { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + margin: 2rem 0; +} diff --git a/public-pre/css/responsive.css b/public-pre/css/responsive.css new file mode 100644 index 0000000..d54cd41 --- /dev/null +++ b/public-pre/css/responsive.css @@ -0,0 +1,172 @@ +/* Tablet Devices */ +@media (max-width: 768px) { + .container { + padding: 0 1.5rem; + } + + header { + padding: 1rem; + } + + .logo-text { + font-size: 1.8rem; + } + + h1 { + font-size: 2rem; + } + + h2 { + font-size: 1.5rem; + } + + .h1-hero { + font-size: 2rem !important; + margin: 1rem !important; + padding: 1rem !important; + } + + .mission-vision { + grid-template-columns: 1fr; + gap: 1rem; + } + + .service-card { + padding: 1.5rem; + } + + .welcome-quote { + font-size: 1rem; + padding: 1rem; + } + + .logo-sub { + margin: 1.5rem; + font-size: 0.85rem; + margin: 0; + padding: 1.5rem 1.5rem; + } +} + +/* Mobile Devices */ +@media (max-width: 480px) { + .container { + padding: 0 1rem; + } + + header { + padding: 0.75rem; + max-height: 120px; + } + + .logo-text { + font-size: 1.6rem; + } + + main { + margin-top: 1rem; + padding: 1rem 0; + } + + section { + margin: 2rem 0; + } + + h1.h1-hero { + font-size: 1.75rem !important; + margin: 0.5rem !important; + padding: 0.5rem !important; + } + + .welcome-quote { + font-size: 0.95rem; + padding: 0.5rem; + line-height: 1.5; + } + + .service-card { + padding: 1.25rem; + margin: 1rem 0; + } + + .service-card h3 { + font-size: 1.2rem; + } + + .contact-info { + padding: 1.5rem; + margin: 1rem 0; + } + + .contact-info a { + padding: 0.5rem 0; + font-size: 0.95rem; + } + + .logo-sub { + font-size: 0.8rem; + line-height: 1.4; + margin: 0; + padding: 1.5rem 1.5rem; + } +} + +/* Small Mobile Devices */ +@media (max-width: 320px) { + header { + padding: 0.5rem; + max-height: 100px; + } + + .logo-text { + font-size: 1.4rem; + } + + h1.h1-hero { + font-size: 1.5rem !important; + } + + .service-card { + padding: 1rem; + } + + .contact-info { + padding: 1rem; + } + + .welcome-quote { + font-size: 0.9rem; + } + + .logo-sub { + font-size: 0.75rem; + margin: 0.75rem; + } +} + +/* Ensure touch targets are large enough on mobile */ +@media (hover: none) { + .contact-info a { + padding: 0.75rem 0; + } + + .service-card { + transition: none; + } + + .service-card:hover { + transform: none; + } + + header:hover { + transform: none; + } +} + +/* Fix for notched phones */ +@supports (padding: max(0px)) { + header { + padding-left: max(1rem, env(safe-area-inset-left)); + padding-right: max(1rem, env(safe-area-inset-right)); + } +} diff --git a/public-pre/css/variables.css b/public-pre/css/variables.css new file mode 100644 index 0000000..1623a97 --- /dev/null +++ b/public-pre/css/variables.css @@ -0,0 +1,36 @@ +:root { + /* Light theme (default) */ + --primary: #3a7be0; + --primary-gradient-1: #112fcf; + --primary-gradient-2: #3b3bc8; + --primary-gradient-3: #5743ed; + --primary-gradient-4: #784dd9; + + --bg-main: #ffffff; + --bg-card: #f5f5f5; + --text: #333333; + --text-muted: #666666; + --header-gradient-1: rgba(245, 245, 245, 0.9); + --header-gradient-2: rgba(235, 235, 235, 0.9); + --border-color: rgba(58, 123, 224, 0.1); +} + +[data-theme="dark"] { + --bg-main: #0a0a0a; + --bg-card: #141414; + --text: #e0e0e0; + --text-muted: #888888; + --header-gradient-1: rgba(14, 14, 14, 0.9); + --header-gradient-2: rgba(41, 41, 41, 0.9); +} + +/* Common tokens */ +:root { + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + --border-radius: 8px; + --transition-speed: 0.3s; + --header-height: 140px; +} diff --git a/public-pre/datenschutz.html b/public-pre/datenschutz.html new file mode 100644 index 0000000..57e1b3a --- /dev/null +++ b/public-pre/datenschutz.html @@ -0,0 +1,58 @@ + + + + + + Datenschutz - 7SYS + + + + + + + + + + + + + + + + +
+
+
+

Datenschutzerklärung

+
+

1. Datenschutz auf einen Blick

+

Allgemeine Hinweise

+

Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können.

+ +

Datenerfassung auf dieser Website

+

Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten können Sie dem Impressum dieser Website entnehmen.

+ +

2. Hosting

+

Wir hosten die Inhalte unserer Website bei folgendem Anbieter:

+

Hetzner Online GmbH
+ Industriestr. 25
+ 91710 Gunzenhausen
+ Deutschland

+ +

3. Allgemeine Hinweise und Pflichtinformationen

+

Datenschutz

+

Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.

+ +

Hinweis zur verantwortlichen Stelle

+

Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:

+

7SYS
+ Musterstraße 123
+ 12345 Musterstadt
+ Telefon: +49 123 456789
+ E-Mail: kontakt@7sys.de

+
+
+
+
+ + diff --git a/public-pre/impressum.html b/public-pre/impressum.html new file mode 100644 index 0000000..0aa0187 --- /dev/null +++ b/public-pre/impressum.html @@ -0,0 +1,50 @@ + + + + + + Impressum - 7SYS + + + + + + + + + + + + + + + + +
+
+
+

Impressum

+
+

Angaben gemäß § 5 TMG

+

7SYS
+ Musterstraße 123
+ 12345 Musterstadt

+ +

Kontakt

+

Telefon: +49 123 456789
+ E-Mail: kontakt@7sys.de

+ +

Umsatzsteuer-ID

+

Umsatzsteuer-Identifikationsnummer gemäß § 27 a Umsatzsteuergesetz:
+ DE123456789

+ +

Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV

+

Max Mustermann
+ Musterstraße 123
+ 12345 Musterstadt

+
+
+
+
+ + diff --git a/public-pre/index.html b/public-pre/index.html new file mode 100644 index 0000000..191b628 --- /dev/null +++ b/public-pre/index.html @@ -0,0 +1,77 @@ + + + + + + 7SYS - Persönliche IT-Lösungen + + + + + + + + + + + + + + + + +
+
+
+

Ihr Partner für Digitale Souveränität

+

Als Ihr persönlicher IT-Experte entwickle ich maßgeschneiderte und zukunftssichere Systemarchitekturen. Mit Expertise und Leidenschaft gestalte ich IT-Lösungen, die Ihr Unternehmen nachhaltig stärken und schützen.

+ Simplicity is a great virtue but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better. +
+
+

Vision

+

Digitale Souveränität

+
+
+

Mission

+

Sichere und nachhaltige IT-Lösungen

+
+
+
+ +
+

Meine Expertise

+ +
+

IT-Beratung

+

Strategische IT-Beratung für Ihren Erfolg: Von der Analyse Ihrer bestehenden Systeme bis zur Entwicklung zukunftssicherer IT-Strategien. Ich begleite Sie persönlich bei jedem Schritt.

+
+ +
+

Systemadministration

+

Professionelle Verwaltung und Wartung Ihrer IT-Systeme: Server-Management, Netzwerkadministration, Backup-Konzepte und Security-Lösungen für einen reibungslosen Betrieb.

+
+ +
+

Automatisierung

+

Clevere automatisierte Lösungen für ausgewählte Prozesse, die Sie in Ihrer Organisation nutzen können, um Arbeiten sicher, effizient und konsistent zu gestalten.

+
+ +
+

Cloud Services

+

Maßgeschneiderte Cloud-Lösungen: Migration, Implementierung und Management Ihrer Cloud-Infrastruktur. Skalierbar, sicher und kosteneffizient – optimal auf Ihre Bedürfnisse abgestimmt.

+
+
+ +
+

Kontakt

+
+

Ich freue mich darauf, Ihr Projekt kennenzulernen und Ihnen mein Wissen zu vermitteln.

+ E-Mail: kontakt@7sys.de + Tel: +49 123 456789 + Signal: @7sys +
+
+
+
+ + diff --git a/public-pre/js/components.js b/public-pre/js/components.js new file mode 100644 index 0000000..8e167da --- /dev/null +++ b/public-pre/js/components.js @@ -0,0 +1,75 @@ +document.addEventListener('DOMContentLoaded', function() { + // Helper function to handle component loading + async function loadComponent(url, insertPosition) { + try { + const response = await fetch(url); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + const data = await response.text(); + document.body.insertAdjacentHTML(insertPosition, data); + + // After header is loaded, initialize its event listeners + if (url.includes('header.html')) { + initializeHeader(); + } + } catch (error) { + console.warn(`Failed to load component from ${url}:`, error); + } + } + + // Load components with error handling + Promise.all([ + loadComponent('components/header.html', 'afterbegin'), + loadComponent('components/footer.html', 'beforeend') + ]).catch(error => { + console.warn('Error loading components:', error); + }); + + // Initialize header functionality after it's loaded + function initializeHeader() { + const header = document.querySelector('header'); + const mobileMenuToggle = header.querySelector('.mobile-menu-toggle'); + const navMenu = header.querySelector('.nav-menu'); + + // Mobile menu toggle + if (mobileMenuToggle && navMenu) { + mobileMenuToggle.addEventListener('click', () => { + navMenu.classList.toggle('active'); + const menuIcon = mobileMenuToggle.querySelector('i'); + menuIcon.className = navMenu.classList.contains('active') + ? 'fas fa-times' + : 'fas fa-bars'; + }); + + // Close menu when clicking outside + document.addEventListener('click', (e) => { + 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'; + } + }); + } + + // Scroll behavior + let lastScrollTop = 0; + 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; + } + + // 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 }); + } +}); diff --git a/public-pre/js/theme.js b/public-pre/js/theme.js new file mode 100644 index 0000000..f0d332b --- /dev/null +++ b/public-pre/js/theme.js @@ -0,0 +1,64 @@ +// 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 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'; + } + }); +}); diff --git a/public/README.md b/public/README.md new file mode 100644 index 0000000..f023908 --- /dev/null +++ b/public/README.md @@ -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. \ No newline at end of file diff --git a/public/index.html b/public/index.html index 191b628..56707aa 100644 --- a/public/index.html +++ b/public/index.html @@ -16,8 +16,6 @@ - -
@@ -73,5 +71,16 @@
+ + + + diff --git a/public/js/components.js b/public/js/components.js index 8e167da..28ac318 100644 --- a/public/js/components.js +++ b/public/js/components.js @@ -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 }); } }); diff --git a/public/js/theme.js b/public/js/theme.js index f0d332b..0095c3b 100644 --- a/public/js/theme.js +++ b/public/js/theme.js @@ -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); + } + } +})();