style(theme-engine): centralize CSS variables and add reactive light/dark mode support
This commit is contained in:
82
src/components/DenseBg.astro
Normal file
82
src/components/DenseBg.astro
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
import { animate } from 'motion';
|
||||
---
|
||||
<canvas id="dense-bg" class="dense-bg"></canvas>
|
||||
<script type="module">
|
||||
// Rocky Grund‑Setup
|
||||
const canvas = document.getElementById('dense-bg');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const TAU = Math.PI * 2;
|
||||
const DENSITY = 200; // Rocky Partikel‑Zahl
|
||||
const particles = [];
|
||||
// Rocky Größe anpassen
|
||||
function resize() {
|
||||
canvas.width = innerWidth;
|
||||
canvas.height = innerHeight;
|
||||
}
|
||||
resize();
|
||||
addEventListener('resize', resize);
|
||||
// Rocky Partikel‑Objekt
|
||||
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 Maus‑Tracking
|
||||
let mouse = { x: -9999, y: -9999 };
|
||||
addEventListener('mousemove', e => {
|
||||
mouse.x = e.clientX;
|
||||
mouse.y = e.clientY;
|
||||
});
|
||||
// Rocky Update‑Loop
|
||||
function update() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
particles.forEach(p => {
|
||||
// Rocky Bewegung
|
||||
p.x += p.vx;
|
||||
p.y += p.vy;
|
||||
// Rocky Rand‑Wrap
|
||||
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 Maus‑Anziehung
|
||||
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 Fade‑In/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>
|
||||
---
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
48
src/styles/glassmorphism.css
Normal file
48
src/styles/glassmorphism.css
Normal 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%);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user