refactor(layout): switch index.astro to configurable section components driven by siteConfig
This commit is contained in:
36
src/components/sections/BentoGrid.astro
Normal file
36
src/components/sections/BentoGrid.astro
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
import type { SiteConfig } from '../../utils/configLoader';
|
||||
|
||||
interface Props {
|
||||
config: SiteConfig['bento'];
|
||||
}
|
||||
|
||||
const { config } = Astro.props;
|
||||
---
|
||||
|
||||
<section id="services" class="py-20 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto relative">
|
||||
<div class="text-center max-w-3xl mx-auto mb-16 space-y-3">
|
||||
<h2 class="text-3xl font-extrabold text-white tracking-tight uppercase">{config.sectionTitle}</h2>
|
||||
<p class="text-slate-400 text-xs sm:text-sm">{config.sectionSubtitle}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{config.cards.map((card) => (
|
||||
<div class={`relative glass-panel glass-panel-hover rounded-2xl p-8 flex flex-col justify-between overflow-hidden group ${card.colSpan === 2 ? 'md:col-span-2' : 'md:col-span-1'}`}>
|
||||
<div class="space-y-4 relative z-10">
|
||||
{card.badge && (
|
||||
<span class="inline-block text-[10px] font-mono uppercase tracking-wider text-sky-400 bg-sky-400/10 px-2.5 py-1 rounded border border-sky-400/20">
|
||||
{card.badge}
|
||||
</span>
|
||||
)}
|
||||
<h3 class="text-xl font-bold text-white tracking-tight">{card.title}</h3>
|
||||
<p class="text-slate-400 text-xs sm:text-sm leading-relaxed">{card.description}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex justify-end items-center opacity-40 group-hover:opacity-100 transition-opacity">
|
||||
<span class="text-xs font-mono text-sky-400">Details →</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
88
src/components/sections/ContactSection.astro
Normal file
88
src/components/sections/ContactSection.astro
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
import type { SiteConfig, SmtpConfig } from '../../utils/configLoader';
|
||||
|
||||
interface Props {
|
||||
contactConfig: SiteConfig['contact'];
|
||||
smtpConfig: SmtpConfig;
|
||||
}
|
||||
|
||||
const { contactConfig, smtpConfig } = Astro.props;
|
||||
---
|
||||
|
||||
<section id="contact" class="py-20 px-4 sm:px-6 lg:px-8 max-w-4xl mx-auto relative">
|
||||
<div class="glass-panel rounded-3xl p-8 sm:p-12 relative overflow-hidden">
|
||||
<div class="text-center space-y-3 mb-10">
|
||||
<h2 class="text-3xl font-extrabold text-white tracking-tight uppercase">{contactConfig.title}</h2>
|
||||
<p class="text-slate-400 text-xs sm:text-sm">{contactConfig.subtitle}</p>
|
||||
</div>
|
||||
|
||||
{!smtpConfig.isConfigured && (
|
||||
<div class="mb-6 p-4 rounded-xl bg-amber-500/10 border border-amber-500/20 text-amber-400 text-xs font-mono text-center">
|
||||
⚡ Hinweis: SMTP noch im Platzhalter-Modus. Formular sendet Test-Events an die Server-Log.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form id="waas-contact-form" class="space-y-5">
|
||||
<div>
|
||||
<label for="name" class="block text-[10px] font-bold uppercase tracking-wider text-slate-400 mb-1.5">Name</label>
|
||||
<input type="text" id="name" name="name" required class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400 transition-all" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-[10px] font-bold uppercase tracking-wider text-slate-400 mb-1.5">E-Mail Adresse</label>
|
||||
<input type="email" id="email" name="email" required class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400 transition-all" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="message" class="block text-[10px] font-bold uppercase tracking-wider text-slate-400 mb-1.5">Nachricht</label>
|
||||
<textarea id="message" name="message" rows="4" required class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400 transition-all resize-none"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="waas-submit-btn" class="w-full py-4 rounded-xl bg-sky-400 text-slate-950 font-bold text-xs uppercase tracking-wider hover:bg-white transition-all shadow-lg shadow-sky-400/20">
|
||||
Anfrage absenden
|
||||
</button>
|
||||
|
||||
<div id="waas-form-feedback" class="hidden text-center text-xs font-medium p-3 rounded-xl"></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script is:inline>
|
||||
const form = document.getElementById('waas-contact-form');
|
||||
const btn = document.getElementById('waas-submit-btn');
|
||||
const feedback = document.getElementById('waas-form-feedback');
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird gesendet...';
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const res = await fetch('/api/contact', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
feedback.classList.remove('hidden', 'bg-emerald-500/10', 'text-emerald-400', 'bg-red-500/10', 'text-red-400');
|
||||
if (data.success) {
|
||||
feedback.classList.add('bg-emerald-500/10', 'text-emerald-400');
|
||||
feedback.textContent = data.message;
|
||||
form.reset();
|
||||
} else {
|
||||
feedback.classList.add('bg-red-500/10', 'text-red-400');
|
||||
feedback.textContent = data.message || 'Fehler beim Senden.';
|
||||
}
|
||||
} catch (err) {
|
||||
feedback.classList.remove('hidden');
|
||||
feedback.classList.add('bg-red-500/10', 'text-red-400');
|
||||
feedback.textContent = 'Netzwerkfehler.';
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Anfrage absenden';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
13
src/components/sections/FallbackSection.astro
Normal file
13
src/components/sections/FallbackSection.astro
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
interface Props {
|
||||
sectionName: string;
|
||||
}
|
||||
|
||||
const { sectionName } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="py-12 px-4 max-w-4xl mx-auto text-center">
|
||||
<div class="glass-panel rounded-2xl p-8 text-slate-400 text-xs font-mono">
|
||||
<p>⚙️ Section <strong>{sectionName}</strong> ist noch in Bearbeitung oder Platzhalter.</p>
|
||||
</div>
|
||||
</section>
|
||||
76
src/components/sections/HeroSection.astro
Normal file
76
src/components/sections/HeroSection.astro
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
import type { SiteConfig, UIComponent } from '../../utils/configLoader';
|
||||
|
||||
interface Props {
|
||||
config: SiteConfig['hero'];
|
||||
}
|
||||
|
||||
const { config } = Astro.props;
|
||||
|
||||
const elements = config.elements || [];
|
||||
const badgeElements = elements.filter((el: UIComponent) => el.slotId === 'hero-badge-slot');
|
||||
const actionElements = elements.filter((el: UIComponent) => el.slotId === 'hero-actions-slot');
|
||||
const customElements = elements.filter((el: UIComponent) => !el.slotId || el.slotId === 'hero-custom-slot');
|
||||
---
|
||||
|
||||
<section class="relative py-24 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto overflow-hidden">
|
||||
<span class="aurora-glow aurora-glow-sky w-[500px] h-[500px] -top-32 -left-32"></span>
|
||||
<span class="aurora-glow aurora-glow-indigo w-[400px] h-[400px] bottom-0 right-0"></span>
|
||||
|
||||
<div class="relative z-10 text-center max-w-4xl mx-auto space-y-8">
|
||||
|
||||
<!-- Slot 1: Badge Slot -->
|
||||
<div id="hero-badge-slot" class="inline-flex flex-wrap items-center justify-center gap-2">
|
||||
{config.badge && (
|
||||
<div class="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full border border-sky-400/20 bg-sky-400/10 text-sky-400 text-xs font-mono tracking-wide backdrop-blur-md">
|
||||
<span>{config.badge}</span>
|
||||
</div>
|
||||
)}
|
||||
{badgeElements.map(el => (
|
||||
<span class="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full border border-sky-400/30 bg-sky-400/20 text-sky-300 text-xs font-mono">
|
||||
{el.content}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h1 class="text-4xl sm:text-6xl font-extrabold tracking-tight text-white leading-tight">
|
||||
{config.title}
|
||||
</h1>
|
||||
|
||||
<p class="text-slate-400 text-xs sm:text-lg max-w-2xl mx-auto leading-relaxed font-normal">
|
||||
{config.subtitle}
|
||||
</p>
|
||||
|
||||
<!-- Slot 2: Action Buttons Slot -->
|
||||
<div id="hero-actions-slot" class="flex flex-wrap items-center justify-center gap-4 pt-4">
|
||||
{config.ctaPrimaryText && (
|
||||
<a href={config.ctaPrimaryLink} class="px-8 py-3.5 rounded-xl bg-sky-400 text-slate-950 font-bold text-xs hover:bg-white transition-all duration-300 shadow-lg shadow-sky-400/20 active:scale-95">
|
||||
{config.ctaPrimaryText}
|
||||
</a>
|
||||
)}
|
||||
{config.ctaSecondaryText && (
|
||||
<a href={config.ctaSecondaryLink} class="px-8 py-3.5 rounded-xl glass-panel text-white font-semibold text-xs glass-panel-hover active:scale-95">
|
||||
{config.ctaSecondaryText}
|
||||
</a>
|
||||
)}
|
||||
{actionElements.map(el => (
|
||||
<a href={el.link || '#'} class={`px-8 py-3.5 rounded-xl font-bold text-xs transition-all active:scale-95 ${el.style === 'glass' ? 'glass-panel text-white' : 'bg-sky-400 text-slate-950 hover:bg-white'}`}>
|
||||
{el.content}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Slot 3: Custom Elements Slot -->
|
||||
{customElements.length > 0 && (
|
||||
<div id="hero-custom-slot" class="pt-6 flex flex-wrap justify-center gap-4">
|
||||
{customElements.map(el => (
|
||||
<div class="p-4 rounded-xl glass-panel text-xs text-slate-300">
|
||||
<span class="font-bold text-sky-400 block mb-1 uppercase text-[10px]">{el.type} [ID: {el.id}]</span>
|
||||
{el.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
@@ -2,56 +2,47 @@
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Navbar from '../components/Navbar.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import HeroAndLiquidGlass from '../components/HeroAndLiquidGlass.astro';
|
||||
import OnboardingWizard from '../components/OnboardingWizard.astro';
|
||||
import HeroSection from '../components/sections/HeroSection.astro';
|
||||
import BentoGrid from '../components/sections/BentoGrid.astro';
|
||||
import ContactSection from '../components/sections/ContactSection.astro';
|
||||
import FallbackSection from '../components/sections/FallbackSection.astro';
|
||||
import { loadWaasConfigs } from '../utils/configLoader';
|
||||
import { THEME_PRESETS } from '../utils/themePresets';
|
||||
|
||||
const { siteConfig, themeConfig, smtpConfig } = loadWaasConfigs();
|
||||
const activePreset = THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark;
|
||||
const colors = activePreset.colors;
|
||||
|
||||
const sectionComponents: Record<string, any> = {
|
||||
hero: HeroSection,
|
||||
bento: BentoGrid,
|
||||
services: BentoGrid,
|
||||
contact: ContactSection
|
||||
};
|
||||
---
|
||||
|
||||
<Layout title="N&D i-t SOLUTIONS | Webdesign & Entwicklung">
|
||||
<Navbar />
|
||||
<Layout title={siteConfig.siteName} description={siteConfig.metaDescription}>
|
||||
<div
|
||||
class="min-h-screen transition-colors duration-500 font-sans relative overflow-hidden"
|
||||
style={`background-color: ${themeConfig.customColors?.bg || colors.bg}; color: ${colors.text};`}
|
||||
>
|
||||
<Navbar />
|
||||
|
||||
<main>
|
||||
{/* Clean, modular Hero and Liquid Glass section */}
|
||||
<HeroAndLiquidGlass />
|
||||
<main>
|
||||
{siteConfig.sectionsOrder.map((sectionKey) => {
|
||||
if (sectionKey === 'hero') {
|
||||
return <HeroSection config={siteConfig.hero} />;
|
||||
}
|
||||
if (sectionKey === 'bento' || sectionKey === 'services') {
|
||||
return <BentoGrid config={siteConfig.bento} />;
|
||||
}
|
||||
if (sectionKey === 'contact') {
|
||||
return <ContactSection contactConfig={siteConfig.contact} smtpConfig={smtpConfig} />;
|
||||
}
|
||||
return <FallbackSection sectionName={sectionKey} />;
|
||||
})}
|
||||
</main>
|
||||
|
||||
<!-- Multi-step Onboarding Wizard -->
|
||||
<OnboardingWizard />
|
||||
|
||||
<!-- Contact section (separate, clean layout) -->
|
||||
<section id="contact" class="py-20 bg-slate-50 dark:bg-[#0B0F19] relative border-t border-slate-200 dark:border-white/5 transition-colors duration-300">
|
||||
<div class="absolute inset-0 opacity-[0.015] pointer-events-none bg-[radial-gradient(#000_1px,transparent_1px)] dark:bg-[radial-gradient(#fff_1px,transparent_1px)] [background-size:16px_16px] transition-opacity duration-300"></div>
|
||||
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
||||
<div class="bg-white/40 dark:bg-slate-900/40 backdrop-blur-md border border-slate-200 dark:border-white/5 rounded-2xl p-8 sm:p-10 transition-all duration-300 relative tactile-noise shadow-sm dark:shadow-none">
|
||||
<span class="absolute top-2.5 left-2.5 w-2 h-2 border-t border-l border-slate-300 dark:border-zinc-700/50"></span>
|
||||
<span class="absolute top-2.5 right-2.5 w-2 h-2 border-t border-r border-slate-300 dark:border-zinc-700/50"></span>
|
||||
<span class="absolute bottom-2.5 left-2.5 w-2 h-2 border-b border-l border-slate-300 dark:border-zinc-700/50"></span>
|
||||
<span class="absolute bottom-2.5 right-2.5 w-2 h-2 border-b border-r border-slate-300 dark:border-zinc-700/50"></span>
|
||||
|
||||
<h2 class="text-3xl font-extrabold text-slate-900 dark:text-white mb-2 uppercase tracking-tight transition-colors duration-300">Bereit für Ihr Projekt?</h2>
|
||||
<p class="text-slate-600 dark:text-zinc-400 text-xs mb-8 transition-colors duration-300">
|
||||
Schreiben Sie uns direkt. Wir melden uns innerhalb von 24 Stunden persönlich bei Ihnen.
|
||||
</p>
|
||||
|
||||
<form class="space-y-4" onsubmit="event.preventDefault()">
|
||||
<div>
|
||||
<label for="name" class="block text-[10px] font-bold text-slate-500 dark:text-zinc-400 uppercase mb-1">Name</label>
|
||||
<input type="text" id="name" required aria-required="true" 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 focus:ring-offset-2 focus:ring-offset-white dark:focus:ring-offset-slate-900 transition-all" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="email" class="block text-[10px] font-bold text-slate-500 dark:text-zinc-400 uppercase mb-1">E-Mail</label>
|
||||
<input type="email" id="email" required aria-required="true" 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 focus:ring-offset-2 focus:ring-offset-white dark:focus:ring-offset-slate-900 transition-all" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="message" class="block text-[10px] font-bold text-slate-500 dark:text-zinc-400 uppercase mb-1">Nachricht</label>
|
||||
<textarea id="message" rows="4" required aria-required="true" 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 focus:ring-offset-2 focus:ring-offset-white dark:focus:ring-offset-slate-900 transition-all resize-none"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="w-full py-3 bg-sky-600 hover:bg-sky-700 dark:bg-brand-arcticBlue text-white dark:text-zinc-950 font-bold text-xs uppercase tracking-wider rounded dark:hover:bg-white transition-colors focus:ring-2 focus:ring-offset-2 focus:ring-sky-500 focus:outline-none">
|
||||
Anfrage senden
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
<Footer />
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
Reference in New Issue
Block a user