Initial commit: Van's DIY & Bastelbedarf Astro shop

This commit is contained in:
qcigano
2026-08-02 13:22:55 +02:00
commit 2e340f7a8a
92 changed files with 11022 additions and 0 deletions
+313
View File
@@ -0,0 +1,313 @@
---
import "../styles/global.css";
import { categories } from "../data/categories";
import { defaultLocale, localeMeta, localePrefix, type Locale } from "../i18n/config";
import { useTranslations } from "../i18n/ui";
import LanguageSwitcher from "../components/LanguageSwitcher.astro";
interface Props {
title: string;
description?: string;
lang?: Locale;
/** Sprachneutraler Pfad der aktuellen Seite (ohne Sprach-Prefix), z.B. "/shop/". Für den Sprachumschalter. */
path?: string;
/** Seite existiert nur auf Deutsch (Rechtstexte) — siehe LanguageSwitcher. */
legalOnly?: boolean;
}
const {
title,
description = "Van's DIY & Bastelbedarf handgemachte Häkelwerke, Plushies, Schmuck und Bastelzubehör mit Liebe gefertigt.",
lang = defaultLocale,
path = "/",
legalOnly = false,
} = Astro.props;
const t = useTranslations(lang);
const p = (href: string) => `${localePrefix(lang)}${href}`;
---
<!doctype html>
<html lang={localeMeta[lang].htmlLang}>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title} · Van's DIY & Bastelbedarf</title>
<meta name="description" content={description} />
<link rel="canonical" href={new URL(Astro.url.pathname, Astro.site ?? "https://vans-diy-bastelbedarf.de").toString()} />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Van's DIY & Bastelbedarf" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta name="theme-color" content="#0a0910" />
<!-- TODO Phase 2: Fonts self-hosten statt Google-Fonts-CDN (Performance/DSGVO) -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,[email protected],300..700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<style>
:root { --font-head-override: "Fraunces", serif; }
</style>
</head>
<body>
<div class="topbar">🩵 {t.topbar}</div>
<header class="site-header">
<div class="container header-inner">
<a href={p("/")} class="brand">
<img src="/logo.png" alt="Van's DIY & Bastelbedarf Logo" width="42" height="42" />
<span>Van's DIY &amp; Bastelbedarf</span>
</a>
<nav class="main-nav" aria-label="Hauptnavigation">
<a href={p("/shop/")}>{t.nav.shop}</a>
<a href={p("/sale/")}>{t.nav.sale}</a>
<a href={p("/ueber-die-kreative/")}>{t.nav.creative}</a>
<a href={p("/faq/")}>{t.nav.faq}</a>
<a href={p("/kontakt/")}>{t.nav.contact}</a>
</nav>
<div class="header-actions">
<LanguageSwitcher lang={lang} path={path} legalOnly={legalOnly} />
<a href={p("/konto/")} class="account-btn">
<span class="account-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 12a5 5 0 1 0-5-5 5 5 0 0 0 5 5Zm0 2c-4.4 0-9 2.2-9 5v2h18v-2c0-2.8-4.6-5-9-5Z"/></svg>
</span>
<span class="account-label">{t.nav.login}</span>
</a>
<a href={p("/warenkorb/")} class="icon-link cart-link" aria-label={t.nav.account}>
🧺 <span class="cart-count" id="cart-count">0</span>
</a>
<button class="hamburger" id="hamburger" aria-label="Menü" aria-expanded="false">☰</button>
</div>
</div>
<nav class="mobile-nav" id="mobile-nav" aria-label="Mobile Navigation">
<a href={p("/shop/")}>{t.nav.shop}</a>
<a href={p("/sale/")}>{t.nav.sale}</a>
<a href={p("/ueber-die-kreative/")}>{t.nav.creative}</a>
<a href={p("/faq/")}>{t.nav.faq}</a>
<a href={p("/kontakt/")}>{t.nav.contact}</a>
<a href={p("/konto/")}>{t.nav.account}</a>
<span class="mobile-nav-divider">{t.shop.categoryLabel}</span>
{categories.map((c) => <a href={p(`/shop/${c.slug}/`)}>{c.icon} {c.name[lang]}</a>)}
</nav>
</header>
<nav class="sub-nav" aria-label="Kategorien-Schnellzugriff">
<div class="container sub-nav-inner">
{categories.map((c) => (
<a href={p(`/shop/${c.slug}/`)}>{c.name[lang]}</a>
))}
</div>
</nav>
<main>
<slot />
</main>
<footer class="site-footer">
<div class="container footer-grid">
<div>
<a href={p("/")} class="brand">
<img src="/logo.png" alt="Van's DIY & Bastelbedarf Logo" width="36" height="36" />
<span>Van's DIY &amp; Bastelbedarf</span>
</a>
<p class="small">{t.footer.tagline}</p>
</div>
<div>
<h3>{t.footer.shopHeading}</h3>
<ul class="footer-list">
{categories.map((c) => <li><a href={p(`/shop/${c.slug}/`)}>{c.name[lang]}</a></li>)}
</ul>
</div>
<div>
<h3>{t.footer.serviceHeading}</h3>
<ul class="footer-list">
<li><a href={p("/versand-zahlung/")}>{t.footer.shipping}</a></li>
<li><a href={p("/faq/")}>{t.nav.faq}</a></li>
<li><a href={p("/kontakt/")}>{t.nav.contact}</a></li>
<li><a href={p("/konto/")}>{t.footer.account}</a></li>
</ul>
</div>
<div>
<h3>{t.footer.legalHeading}</h3>
<ul class="footer-list">
<li><a href="/impressum/">Impressum</a></li>
<li><a href="/datenschutz/">Datenschutz</a></li>
<li><a href="/agb/">AGB</a></li>
<li><a href="/widerruf/">Widerrufsbelehrung</a></li>
<li><a href="/muster-widerrufsformular/">Muster-Widerrufsformular</a></li>
</ul>
{lang !== "de" && <p class="small legal-note-footer" title={t.footer.legalNoteTitle}>{t.footer.legalNote}</p>}
</div>
</div>
<div class="container">
<hr class="divider" />
<p class="small">© {new Date().getFullYear()} Van's DIY & Bastelbedarf · {t.footer.copyright}</p>
</div>
</footer>
<script>
import { cartCount } from "../scripts/cart";
function updateCount() {
const el = document.getElementById("cart-count");
if (el) el.textContent = String(cartCount());
}
updateCount();
window.addEventListener("cart:changed", updateCount);
window.addEventListener("storage", updateCount);
const hamburger = document.getElementById("hamburger");
const mobileNav = document.getElementById("mobile-nav");
hamburger?.addEventListener("click", () => {
const open = mobileNav?.classList.toggle("open");
hamburger.setAttribute("aria-expanded", String(!!open));
});
</script>
<style is:global>
body { font-family: var(--font-body); }
h1, h2, h3, h4 { font-family: var(--font-head-override, var(--font-head)); }
.topbar {
background: linear-gradient(90deg, var(--c-purple), var(--c-purple-deep));
color: var(--c-text);
text-align: center;
font-size: 0.82rem;
padding: 0.5rem 1rem;
letter-spacing: 0.01em;
}
.site-header {
position: sticky;
top: 0;
z-index: 40;
background: rgba(10, 9, 16, 0.85);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(244, 241, 247, 0.06);
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding-block: 0.8rem;
}
.brand {
display: flex;
align-items: center;
gap: 0.6rem;
font-family: var(--font-head);
font-weight: 600;
font-size: 1.05rem;
}
.brand img { border-radius: 50%; }
.main-nav { display: flex; gap: 1.6rem; }
.main-nav a {
font-size: 0.92rem;
color: var(--c-text-muted);
transition: color 0.2s var(--ease);
}
.main-nav a:hover { color: var(--c-accent); }
.header-actions { display: flex; align-items: center; gap: 0.8rem; }
.icon-link { font-size: 1.1rem; position: relative; display: inline-flex; align-items: center; gap: 0.3rem; }
.cart-count {
background: var(--c-accent);
color: #08181c;
font-size: 0.7rem;
font-weight: 700;
border-radius: 999px;
min-width: 1.2em;
padding: 0 0.35em;
text-align: center;
}
.hamburger {
display: none;
background: none;
border: none;
color: var(--c-text);
font-size: 1.4rem;
cursor: pointer;
}
.mobile-nav {
display: none;
flex-direction: column;
padding: 0.5rem 1.5rem 1.2rem;
gap: 0.9rem;
border-top: 1px solid rgba(244,241,247,0.06);
}
.mobile-nav.open { display: flex; }
.mobile-nav-divider {
margin-top: 0.4rem;
padding-top: 0.9rem;
border-top: 1px solid rgba(244, 241, 247, 0.08);
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--c-accent);
}
@media (max-width: 860px) {
.main-nav { display: none; }
.hamburger { display: inline-block; }
}
/* Zweite Navigationsleiste: Schnellzugriff auf die 5 Hauptkategorien, direkt unter der
Hauptnav — gleicher Designstil (dunkler Anthrazit-Streifen, Akzent-Unterstrich bei Hover). */
.sub-nav {
background: var(--c-anthracite);
border-bottom: 1px solid rgba(244, 241, 247, 0.06);
}
.sub-nav-inner {
display: flex;
gap: clamp(1.1rem, 3vw, 2rem);
overflow-x: auto;
padding-block: 0.6rem;
scrollbar-width: none;
}
.sub-nav-inner::-webkit-scrollbar { display: none; }
.sub-nav-inner a {
position: relative;
font-size: 0.83rem;
font-weight: 600;
color: var(--c-text-muted);
white-space: nowrap;
padding-bottom: 3px;
transition: color 0.2s var(--ease);
}
.sub-nav-inner a::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -1px;
height: 2px;
background: var(--c-accent);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.2s var(--ease);
}
.sub-nav-inner a:hover { color: var(--c-accent); }
.sub-nav-inner a:hover::after { transform: scaleX(1); }
@media (max-width: 860px) { .sub-nav { display: none; } }
.site-footer {
background: var(--c-anthracite);
margin-top: 4rem;
padding-top: 3rem;
padding-bottom: 2rem;
}
.footer-grid {
display: grid;
grid-template-columns: 1.4fr 1fr 1fr 1fr;
gap: 2rem;
margin-bottom: 1rem;
}
.footer-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.55rem; }
.footer-list a { color: var(--c-text-muted); font-size: 0.9rem; }
.footer-list a:hover { color: var(--c-accent); }
.legal-note-footer { margin-top: 0.8rem; font-size: 0.78rem; line-height: 1.5; }
@media (max-width: 760px) { .footer-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px) { .footer-grid { grid-template-columns: 1fr; } }
</style>
</body>
</html>