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>
|
<span class="text-[9px] font-mono text-slate-400 dark:text-zinc-600 mt-6 tracking-wider">Abmahnsicher</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -121,13 +121,14 @@ import { FaBars, FaTimes } from 'react-icons/fa';
|
|||||||
updateToggleIcons();
|
updateToggleIcons();
|
||||||
|
|
||||||
themeToggleBtn?.addEventListener('click', () => {
|
themeToggleBtn?.addEventListener('click', () => {
|
||||||
// Toggle class
|
if (document.documentElement.classList.contains('light')) {
|
||||||
if (document.documentElement.classList.contains('dark')) {
|
document.documentElement.classList.remove('light');
|
||||||
document.documentElement.classList.remove('dark');
|
|
||||||
localStorage.setItem('theme', 'light');
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.add('dark');
|
document.documentElement.classList.add('dark');
|
||||||
localStorage.setItem('theme', 'dark');
|
localStorage.setItem('theme', 'dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
document.documentElement.classList.add('light');
|
||||||
|
localStorage.setItem('theme', 'light');
|
||||||
}
|
}
|
||||||
updateToggleIcons();
|
updateToggleIcons();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,13 +8,20 @@ import '@fontsource/space-grotesk/400.css';
|
|||||||
import '@fontsource/space-grotesk/500.css';
|
import '@fontsource/space-grotesk/500.css';
|
||||||
import '@fontsource/space-grotesk/600.css';
|
import '@fontsource/space-grotesk/600.css';
|
||||||
import '@fontsource/space-grotesk/700.css';
|
import '@fontsource/space-grotesk/700.css';
|
||||||
|
import '../styles/glassmorphism.css';
|
||||||
|
import { loadWaasConfigs } from '../utils/configLoader';
|
||||||
|
import { THEME_PRESETS } from '../utils/themePresets';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
description?: 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>
|
<!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" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>{title}</title>
|
<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>
|
<script is:inline>
|
||||||
(function() {
|
(function() {
|
||||||
const theme = localStorage.getItem('theme');
|
const theme = localStorage.getItem('theme');
|
||||||
if (theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
if (theme === 'light') {
|
||||||
document.documentElement.classList.add('dark');
|
document.documentElement.classList.add('light');
|
||||||
} else {
|
|
||||||
document.documentElement.classList.remove('dark');
|
document.documentElement.classList.remove('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
document.documentElement.classList.remove('light');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</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 />
|
<slot />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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