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

View File

@@ -0,0 +1,31 @@
<footer>
<div class="contact-block">
<div class="contact-item">
<i class="fas fa-envelope"></i>
<a href="mailto:kontakt@7sys.de">kontakt@7sys.de</a>
</div>
<div class="contact-item">
<i class="fas fa-phone"></i>
<a href="tel:+49123456789">+49 123 456789</a>
</div>
<div class="contact-item">
<i class="fab fa-signal"></i>
<a href="https://signal.me/7sys">@7sys</a>
</div>
</div>
<div class="social-links">
<a href="https://github.com/7sys" target="_blank" rel="noopener noreferrer">
<i class="fab fa-github"></i>
</a>
<a href="https://twitter.com/7sys" target="_blank" rel="noopener noreferrer">
<i class="fab fa-twitter"></i>
</a>
<a href="https://linkedin.com/company/7sys" target="_blank" rel="noopener noreferrer">
<i class="fab fa-linkedin"></i>
</a>
</div>
<div class="legal-links">
<a href="impressum.html">Impressum</a>
<a href="datenschutz.html">Datenschutz</a>
</div>
</footer>

View File

@@ -0,0 +1,22 @@
<header>
<div class="header-content">
<div class="logo-container">
<div class="logo-text">
<span>7</span><span>S</span><span>Y</span><span>S</span>
</div>
</div>
<nav class="nav-menu">
<a href="#intro">Start</a>
<a href="#services">Expertise</a>
<a href="#contact">Kontakt</a>
<button class="theme-toggle" aria-label="Theme Toggle">
<i class="fas fa-moon"></i>
</button>
</nav>
<button class="mobile-menu-toggle" aria-label="Toggle Menu">
<i class="fas fa-bars"></i>
</button>
</div>
</header>

View File

@@ -0,0 +1,11 @@
@keyframes fadeIn {
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes shinyEffect {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}

90
public-pre/css/base.css Normal file
View File

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

View File

@@ -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;
}

80
public-pre/css/footer.css Normal file
View File

@@ -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;
}
}

107
public-pre/css/header.css Normal file
View File

@@ -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;
}
}

24
public-pre/css/layout.css Normal file
View File

@@ -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;
}

View File

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

View File

@@ -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;
}

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Datenschutz - 7SYS</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&family=Open+Sans:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/layout.css">
<link rel="stylesheet" href="css/header.css">
<link rel="stylesheet" href="css/components.css">
<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>
<div class="container">
<section>
<h1>Datenschutzerklärung</h1>
<div class="card-base">
<h2>1. Datenschutz auf einen Blick</h2>
<h3>Allgemeine Hinweise</h3>
<p>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.</p>
<h3>Datenerfassung auf dieser Website</h3>
<p>Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten können Sie dem Impressum dieser Website entnehmen.</p>
<h2>2. Hosting</h2>
<p>Wir hosten die Inhalte unserer Website bei folgendem Anbieter:</p>
<p>Hetzner Online GmbH<br>
Industriestr. 25<br>
91710 Gunzenhausen<br>
Deutschland</p>
<h2>3. Allgemeine Hinweise und Pflichtinformationen</h2>
<h3>Datenschutz</h3>
<p>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.</p>
<h3>Hinweis zur verantwortlichen Stelle</h3>
<p>Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:</p>
<p>7SYS<br>
Musterstraße 123<br>
12345 Musterstadt<br>
Telefon: +49 123 456789<br>
E-Mail: kontakt@7sys.de</p>
</div>
</section>
</div>
</main>
</body>
</html>

50
public-pre/impressum.html Normal file
View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Impressum - 7SYS</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&family=Open+Sans:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/layout.css">
<link rel="stylesheet" href="css/header.css">
<link rel="stylesheet" href="css/components.css">
<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>
<div class="container">
<section>
<h1>Impressum</h1>
<div class="card-base">
<h2>Angaben gemäß § 5 TMG</h2>
<p>7SYS<br>
Musterstraße 123<br>
12345 Musterstadt</p>
<h3>Kontakt</h3>
<p>Telefon: +49 123 456789<br>
E-Mail: kontakt@7sys.de</p>
<h3>Umsatzsteuer-ID</h3>
<p>Umsatzsteuer-Identifikationsnummer gemäß § 27 a Umsatzsteuergesetz:<br>
DE123456789</p>
<h3>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV</h3>
<p>Max Mustermann<br>
Musterstraße 123<br>
12345 Musterstadt</p>
</div>
</section>
</div>
</main>
</body>
</html>

77
public-pre/index.html Normal file
View File

@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>7SYS - Persönliche IT-Lösungen</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&family=Open+Sans:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/layout.css">
<link rel="stylesheet" href="css/header.css">
<link rel="stylesheet" href="css/components.css">
<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>
<div class="container">
<section id="intro">
<h1 class="h1-hero">Ihr Partner für Digitale Souveränität</h1>
<p class="welcome-quote">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.</p>
<span class="logo-sub">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.</span>
<div class="mission-vision">
<div>
<h3>Vision</h3>
<p>Digitale Souveränität</p>
</div>
<div>
<h3>Mission</h3>
<p>Sichere und nachhaltige IT-Lösungen</p>
</div>
</div>
</section>
<section id="services">
<h2>Meine Expertise</h2>
<div class="service-card">
<h3>IT-Beratung</h3>
<p>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.</p>
</div>
<div class="service-card">
<h3>Systemadministration</h3>
<p>Professionelle Verwaltung und Wartung Ihrer IT-Systeme: Server-Management, Netzwerkadministration, Backup-Konzepte und Security-Lösungen für einen reibungslosen Betrieb.</p>
</div>
<div class="service-card">
<h3>Automatisierung</h3>
<p>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.</p>
</div>
<div class="service-card">
<h3>Cloud Services</h3>
<p>Maßgeschneiderte Cloud-Lösungen: Migration, Implementierung und Management Ihrer Cloud-Infrastruktur. Skalierbar, sicher und kosteneffizient optimal auf Ihre Bedürfnisse abgestimmt.</p>
</div>
</section>
<section id="contact">
<h2>Kontakt</h2>
<div class="contact-info">
<p>Ich freue mich darauf, Ihr Projekt kennenzulernen und Ihnen mein Wissen zu vermitteln.</p>
<a href="mailto:kontakt@7sys.de">E-Mail: kontakt@7sys.de</a>
<a href="tel:+49123456789">Tel: +49 123 456789</a>
<a href="https://signal.me/7sys">Signal: @7sys</a>
</div>
</section>
</div>
</main>
</body>
</html>

View File

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

64
public-pre/js/theme.js Normal file
View File

@@ -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';
}
});
});

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');
if (menuIcon) {
menuIcon.className = navMenu.classList.contains('active')
? 'fas fa-times'
: 'fas fa-bars';
}
});
// Close menu when clicking outside
@@ -46,14 +60,24 @@ 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', () => {
if (scrollTimeout) {
window.cancelAnimationFrame(scrollTimeout);
}
scrollTimeout = window.requestAnimationFrame(() => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
// Don't hide header when near top of page
@@ -70,6 +94,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
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');
(function() {
// Constants
const THEME_STORAGE_KEY = 'theme';
const DEFAULT_THEME = 'light';
const THEMES = {
LIGHT: 'light',
DARK: 'dark'
};
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
updateThemeIcon(savedTheme);
} else if (prefersDark) {
document.documentElement.setAttribute('data-theme', 'dark');
updateThemeIcon('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';
// 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', newTheme);
localStorage.setItem(THEME_STORAGE_KEY, newTheme);
updateThemeIcon(newTheme);
}
} catch (error) {
console.warn('Theme toggle error:', error);
}
}
function updateThemeIcon(theme) {
function updateThemeIcon(theme) {
try {
const themeIcon = document.querySelector('.theme-toggle i');
if (themeIcon) {
themeIcon.className = theme === 'dark'
themeIcon.className = theme === THEMES.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';
} 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);
}
}
})();