Files
vans-diy-bastelbedarf/src/layouts/Layout.astro
T
qciganoandClaude Sonnet 5 45f64b95ee Echtes Logo statt generiertem Buchstaben-Icon (Browser-Tab + Installation als App)
Bisher wurde als Icon nur Astros Standard-Platzhalter-SVG (ein Pfeil-Symbol,
kein eigenes Branding) verlinkt. Weil kein Web-App-Manifest mit Icons
hinterlegt war, generierten Browser beim "Auf PC installieren" automatisch
einen Buchstaben-Kreis ("V") als App-Icon statt des echten Logos.

Aus dem vorhandenen public/logo.png automatisch alle nötigen Icon-Größen
erzeugt (favicon.ico mit 16/32/48px, Apple-Touch-Icon 180px, sowie 192px/
512px normal + eigene "maskable"-Varianten mit Weißraum-Sicherheitszone,
damit runde/eckige OS-Zuschnitte beim Installieren nicht den Rand des
Logos abschneiden). Neues manifest.webmanifest verlinkt alle Icons plus
Name/Theme-Farbe für die Installation. Layout.astro und der
Admin-Bereich verlinken jetzt überall das echte Logo statt des alten
Platzhalters.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-02 23:11:11 +02:00

320 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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" />
<!-- Eigenes Logo als Icon — überall (Browser-Tab, Lesezeichen, Installation als App/PWA auf
PC & Handy) statt eines automatisch generierten Buchstaben-Icons. -->
<link rel="icon" type="image/x-icon" href="/favicon.ico" sizes="any" />
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.webmanifest" />
<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>