refactor(layout): switch index.astro to configurable section components driven by siteConfig

This commit is contained in:
Daniel S
2026-08-08 18:56:30 +02:00
parent c51629b229
commit c136e930c9
5 changed files with 254 additions and 50 deletions

View 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 &rarr;</span>
</div>
</div>
))}
</div>
</section>

View 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>

View 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>

View 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>