style(theme-engine): centralize CSS variables and add reactive light/dark mode support

This commit is contained in:
Daniel S
2026-08-08 18:56:26 +02:00
parent be3a8a82ab
commit c51629b229
5 changed files with 175 additions and 64 deletions

View File

@@ -0,0 +1,82 @@
---
import { animate } from 'motion';
---
<canvas id="dense-bg" class="dense-bg"></canvas>
<script type="module">
// Rocky GrundSetup
const canvas = document.getElementById('dense-bg');
const ctx = canvas.getContext('2d');
const TAU = Math.PI * 2;
const DENSITY = 200; // Rocky PartikelZahl
const particles = [];
// Rocky Größe anpassen
function resize() {
canvas.width = innerWidth;
canvas.height = innerHeight;
}
resize();
addEventListener('resize', resize);
// Rocky PartikelObjekt
function createParticle() {
return {
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
r: 1 + Math.random() * 2,
vx: (Math.random() - 0.5) * 0.5,
vy: (Math.random() - 0.5) * 0.5,
alpha: Math.random() * 0.5 + 0.2,
};
}
// Rocky Initialisierung
for (let i = 0; i < DENSITY; i++) particles.push(createParticle());
// Rocky MausTracking
let mouse = { x: -9999, y: -9999 };
addEventListener('mousemove', e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
// Rocky UpdateLoop
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(p => {
// Rocky Bewegung
p.x += p.vx;
p.y += p.vy;
// Rocky RandWrap
if (p.x < 0) p.x = canvas.width;
if (p.x > canvas.width) p.x = 0;
if (p.y < 0) p.y = canvas.height;
if (p.y > canvas.height) p.y = 0;
// Rocky MausAnziehung
const dx = mouse.x - p.x;
const dy = mouse.y - p.y;
const dist = Math.hypot(dx, dy);
if (dist < 100) {
const pull = (100 - dist) / 100;
p.vx += dx * 0.001 * pull;
p.vy += dy * 0.001 * pull;
}
// Rocky FadeIn/Out
p.alpha += (Math.random() - 0.5) * 0.005;
p.alpha = Math.min(Math.max(p.alpha, 0.1), 0.8);
// Rocky Zeichen
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, TAU);
ctx.fillStyle = `rgba(255,255,255,${p.alpha})`;
ctx.fill();
});
requestAnimationFrame(update);
}
// Rocky Start
update();
</script>
<style>
.dense-bg {
position: fixed;
inset: 0;
pointer-events: none;
z-index: -1;
background: #0b0f19; /* Rocky dunkler Hintergrund */
}
</style>
---

View File

@@ -254,59 +254,7 @@ const heroTemplates = [
<span class="text-[9px] font-mono text-slate-400 dark:text-zinc-600 mt-6 tracking-wider">Abmahnsicher</span>
</div>
<!-- Card 4: ROI-Rechner (Breit: col-span-2) -->
<div class="relative md:col-span-2 rounded-2xl border border-white/80 dark:border-white/[0.06] bg-white/40 dark:bg-slate-900/10 p-8 backdrop-blur-xl hover:border-sky-300 dark:hover:border-sky-500/20 hover:bg-white/60 dark:hover:bg-slate-950/20 transition-all duration-300 group flex flex-col justify-between min-h-[380px] shadow-sm hover:shadow-md reveal">
<span class="absolute top-3 left-3 w-1.5 h-1.5 border-t border-l border-slate-300 dark:border-zinc-700/50"></span>
<span class="absolute top-3 right-3 w-1.5 h-1.5 border-t border-r border-slate-300 dark:border-zinc-700/50"></span>
<div>
<h3 class="text-xl font-bold text-slate-950 dark:text-white mb-4 uppercase tracking-tight">Einfacher Spar-Rechner</h3>
<p class="text-slate-600 dark:text-zinc-400 text-xs mb-6 leading-relaxed">
Sehen Sie selbst, wie viel Zeit und Geld Sie sparen, wenn wir die Arbeit für Sie übernehmen.
</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div class="space-y-4">
<div>
<label class="block text-[10px] font-bold text-slate-500 dark:text-zinc-400 uppercase mb-1" for="roi-cost">Bisherige Webseiten-Kosten (pro Jahr in €):</label>
<input type="number" id="roi-cost" value="600" class="w-full bg-white dark:bg-zinc-950 border border-slate-200 dark:border-brand-darkBorder rounded p-2.5 text-xs text-slate-800 dark:text-white focus:outline-none focus:ring-2 focus:ring-sky-500" />
</div>
<div>
<label class="block text-[10px] font-bold text-slate-500 dark:text-zinc-400 uppercase mb-1" for="roi-hours">Eigene Arbeitszeit an der Seite (Stunden pro Monat):</label>
<input type="number" id="roi-hours" value="4" class="w-full bg-white dark:bg-zinc-950 border border-slate-200 dark:border-brand-darkBorder rounded p-2.5 text-xs text-slate-800 dark:text-white focus:outline-none focus:ring-2 focus:ring-sky-500" />
</div>
</div>
<div class="bg-sky-500/5 border border-sky-500/10 dark:border-sky-500/5 p-6 rounded-xl flex flex-col justify-center">
<span class="text-[10px] font-mono text-slate-400 dark:text-zinc-500 uppercase tracking-wider">Ihre Geld-Ersparnis:</span>
<p class="text-3xl font-extrabold text-sky-600 dark:text-sky-400 mt-2" id="roi-result">0 € / Jahr</p>
<p class="text-xs text-emerald-600 dark:text-emerald-400 mt-1 font-medium" id="roi-time">0 Stunden gerettete Freizeit im Jahr</p>
</div>
</div>
</div>
<script is:inline>
(function() {
const costInput = document.getElementById('roi-cost');
const hoursInput = document.getElementById('roi-hours');
const resultText = document.getElementById('roi-result');
const timeText = document.getElementById('roi-time');
function calc() {
const cost = parseFloat(costInput.value) || 0;
const hours = parseFloat(hoursInput.value) || 0;
const waasYear = 99 * 12;
const currentTotalYear = cost + (hours * 50 * 12);
const saving = Math.max(0, currentTotalYear - waasYear);
resultText.textContent = `${saving.toLocaleString('de-DE')} € / Jahr`;
timeText.textContent = `${hours * 12} Stunden Lebenszeit zurück / Jahr`;
}
if (costInput && hoursInput && resultText && timeText) {
costInput.addEventListener('input', calc);
hoursInput.addEventListener('input', calc);
calc();
}
})();
</script>
</div>
</div>

View File

@@ -121,13 +121,14 @@ import { FaBars, FaTimes } from 'react-icons/fa';
updateToggleIcons();
themeToggleBtn?.addEventListener('click', () => {
// Toggle class
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
} else {
if (document.documentElement.classList.contains('light')) {
document.documentElement.classList.remove('light');
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
localStorage.setItem('theme', 'light');
}
updateToggleIcons();
});

View File

@@ -8,13 +8,20 @@ import '@fontsource/space-grotesk/400.css';
import '@fontsource/space-grotesk/500.css';
import '@fontsource/space-grotesk/600.css';
import '@fontsource/space-grotesk/700.css';
import '../styles/glassmorphism.css';
import { loadWaasConfigs } from '../utils/configLoader';
import { THEME_PRESETS } from '../utils/themePresets';
interface Props {
title: string;
description?: string;
}
const { title, description = "Professionelles Webdesign & moderne Websites für Kleinbetriebe, Handwerker und Selbstständige. N&D i-t SOLUTIONS erstellt Ihre Homepage bezahlbar und schnell." } = Astro.props;
const { siteConfig, themeConfig } = loadWaasConfigs();
const activePreset = THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark;
const colors = activePreset.colors;
const { title, description = "Professionelles Webdesign & moderne Websites." } = Astro.props;
---
<!doctype html>
@@ -26,19 +33,44 @@ const { title, description = "Professionelles Webdesign & moderne Websites für
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
<!-- Inline script to set dark/light mode before page renders -->
<style is:global define:vars={{
themeBg: colors.bg,
themeCardBg: colors.cardBg,
themeText: colors.text,
themeAccent: colors.accent,
themeBorder: colors.border
}}>
:root {
--theme-bg: var(--themeBg);
--theme-card-bg: var(--themeCardBg);
--theme-text: var(--themeText);
--theme-accent: var(--themeAccent);
--theme-border: var(--themeBorder);
}
html.light {
--theme-bg: #F8FAFC;
--theme-card-bg: rgba(255, 255, 255, 0.7);
--theme-text: #0F172A;
--theme-border: rgba(0, 0, 0, 0.1);
}
</style>
<!-- Inline theme initialization to prevent FOUC -->
<script is:inline>
(function() {
const theme = localStorage.getItem('theme');
if (theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
if (theme === 'light') {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
}
})();
</script>
</head>
<body class="bg-slate-50 dark:bg-brand-darkBg text-slate-800 dark:text-brand-lightText font-sans transition-colors duration-300 antialiased selection:bg-brand-arcticBlue/20 selection:text-brand-arcticBlue">
<body class="font-sans transition-colors duration-500 antialiased">
<slot />
</body>
</html>

View File

@@ -0,0 +1,48 @@
/* Central Color Pattern & WaaS Utility Token System */
:root {
--theme-bg: #0B0F19;
--theme-card-bg: rgba(15, 23, 42, 0.4);
--theme-text: #F8FAFC;
--theme-accent: #38BDF8;
--theme-border: rgba(255, 255, 255, 0.1);
}
body {
background-color: var(--theme-bg) !important;
color: var(--theme-text) !important;
}
.glass-panel {
background: var(--theme-card-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--theme-border);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
}
.glass-panel-hover {
transition: all 0.3s ease;
}
.glass-panel-hover:hover {
border-color: var(--theme-accent);
box-shadow: 0 0 25px -5px var(--theme-accent);
}
.aurora-glow {
position: absolute;
border-radius: 50%;
filter: blur(80px);
pointer-events: none;
opacity: 0.2;
z-index: 0;
}
.aurora-glow-sky {
background: radial-gradient(circle, var(--theme-accent) 0%, transparent 70%);
}
.aurora-glow-indigo {
background: radial-gradient(circle, var(--theme-accent) 0%, transparent 70%);
}