Grundlegend umgebaut: eine Seite pro Tier-Familie mit mehreren Charakteren statt einer Seite pro Charakter
Bisher war jeder Charakter (HasiDog, Frieda, Sam, ...) eine eigene Unterkategorie mit eigener Seite. Das passte nicht mehr, sobald eine Familie mehrere Charaktere bekommt (HasiDog, HasiGott, Mama Hase) — die sollten alle auf EINER Seite erscheinen, nicht auf drei getrennten. Neues Modell: - Unterkategorie = die ganze Tier-Familie (z.B. "hase", "kuh", "wal"), nicht mehr ein einzelner Charakter. - Jede Familie hat jetzt ein "characters"-Array (categories.ts, config.yml) — beliebig viele Charaktere, jeder mit eigenem Namen, Vorstellungstext und optionalem "Mehr über mich"-Link. - Produkte bekommen ein neues optionales Feld "charakter", das sie einem bestimmten Charakter innerhalb der Unterkategorie zuordnet (Product. charakter in products.ts + Adminbereich-Feld). - Die Unterkategorie-Seite zeigt jetzt JEDEN Charakter als eigene Box untereinander, mit automatisch abwechselnder Foto-/Text-Seite (1. links, 2. rechts, 3. wieder links, ...). Ein Charakter ohne zugeordnetes Produkt zeigt trotzdem schon seine Box (nur ohne Foto) — die Seite "wächst" mit, sobald neue Charaktere/Produkte dazukommen, für jede Tier-Familie (Hase, Kuh, Wal, Oktopus, Schildkröte, Pinguin). - "Hase" hat jetzt drei Charaktere (HasiDog mit echtem Produkt, HasiGott und Mama Hase als Platzhalter-Boxen). - Unterkategorien-Chips zeigen jetzt sauber die Familiennamen (Kuh, Hase, Wal, ...) statt "Kuh „Frieda"" etc. - Alte, verwaiste /hasidog/-Seite entfernt (war durch die Umstellung kaputt und nirgends mehr verlinkt). Verifiziert per Puppeteer gegen den echten Build: /shop/diy-plushies/hase/ zeigt alle drei Charaktere korrekt alternierend, Ein-Charakter-Familien (Kuh etc.) funktionieren unverändert, Adminbereich lädt ohne Konfigurationsfehler, alte Hase-URLs geben korrekt 404.
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
---
|
||||
import Layout from "../../../layouts/Layout.astro";
|
||||
import { getCategory, getSubcategory } from "../../../data/categories";
|
||||
import type { Locale } from "../../../i18n/config";
|
||||
import { useTranslations } from "../../../i18n/ui";
|
||||
|
||||
const lang: Locale = "ch";
|
||||
const t = useTranslations(lang);
|
||||
const category = getCategory("diy-plushies")!;
|
||||
const sub = getSubcategory("diy-plushies", "hase-hasidog")!;
|
||||
---
|
||||
<Layout title={sub.name[lang]} description="Lern dr HasiDog kenne – dr chlii, wuschelig Abentürer vo Van's DIY & Bastelbedarf." lang={lang} path="/hasidog/">
|
||||
<section class="section-tight">
|
||||
<div class="container grid grid-2 intro">
|
||||
<div class="portrait-frame portrait">
|
||||
<img src="/img/hero-plushie-bunny.webp" alt="Ghäklets Hase-Plushie HasiDog i blaue Latzhose, im Gras" width="900" height="1200" loading="eager" />
|
||||
<span class="portrait-badge">{t.common.handmadeBadge}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="eyebrow">{category.icon} {category.name[lang]}</span>
|
||||
<h1>{sub.name[lang]}</h1>
|
||||
{sub.vorstellung && (
|
||||
<div class="character-intro">
|
||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</p>)}
|
||||
</div>
|
||||
)}
|
||||
<div class="btn-row">
|
||||
<a class="btn btn-outline" href={`/ch/shop/${category.slug}/${sub.slug}/`}>← {sub.name[lang]}</a>
|
||||
<a class="btn btn-primary" href="/ch/shop/">{t.common.toShop}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
@@ -18,17 +18,24 @@ const { kategorie, unterkategorie } = Astro.params;
|
||||
const category = getCategory(kategorie!)!;
|
||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||
|
||||
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||
const featured = echtProdukte[0];
|
||||
const weitere = echtProdukte.slice(1);
|
||||
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
|
||||
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||
|
||||
// Innerhalb einer Tier-Familie (gleicher Slug-Präfix vor dem ersten "-", z.B. "hase") wechselt
|
||||
// die Foto-/Text-Seite ab: 1. Mitglied Foto links, 2. Foto rechts, 3. wieder links, usw. — sorgt
|
||||
// für optische Abwechslung, sobald mehrere Varianten einer Serie eigene Seiten bekommen.
|
||||
const familyPrefix = sub.slug.split("-")[0];
|
||||
const familyMembers = category.subcategories?.filter((s) => s.slug.split("-")[0] === familyPrefix) ?? [];
|
||||
const familyIndex = familyMembers.findIndex((s) => s.slug === sub.slug);
|
||||
const introReverse = familyIndex % 2 === 1;
|
||||
// Jeder Charakter dieser Tier-Familie bekommt seine eigene Box, abwechselnd Foto links/rechts —
|
||||
// 1. Charakter links, 2. rechts, 3. wieder links, usw. Sobald VanVan im Adminbereich neue
|
||||
// Charaktere UND passende Produkte ergänzt, wächst diese Liste automatisch mit. Fehlt einem
|
||||
// Charakter noch ein echtes Produkt, zeigt seine Box trotzdem schon den Charaktertext (nur ohne
|
||||
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
|
||||
const characters = sub.characters ?? [];
|
||||
const characterEntries = characters.map((char, index) => ({
|
||||
char,
|
||||
product: alleProdukte.find((p) => p.charakter === char.slug),
|
||||
reverse: index % 2 === 1,
|
||||
}));
|
||||
|
||||
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
|
||||
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product?.slug).filter(Boolean));
|
||||
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
|
||||
---
|
||||
<Layout title={`${sub.name[lang]} – ${category.name[lang]}`} description={`${sub.name[lang]} bi Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}>
|
||||
<div class="container">
|
||||
@@ -43,43 +50,40 @@ const introReverse = familyIndex % 2 === 1;
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{featured ? (
|
||||
<section class="section-tight">
|
||||
<div class={`container grid grid-2 intro ${introReverse ? "intro-reverse" : ""}`}>
|
||||
<a class="portrait-frame portrait" href={`/ch/produkt/${featured.slug}/`} aria-label={featured.name[lang]}>
|
||||
{featured.bilder && featured.bilder.length > 0 ? (
|
||||
<img class="product-photo" src={featured.bilder[0]} alt={featured.name[lang]} loading="lazy" />
|
||||
{characterEntries.length > 0 ? (
|
||||
characterEntries.map(({ char, product, reverse }) => (
|
||||
<section class="section-tight">
|
||||
<div class={`container grid grid-2 intro ${reverse ? "intro-reverse" : ""}`}>
|
||||
{product ? (
|
||||
<a class="portrait-frame portrait" href={`/ch/produkt/${product.slug}/`} aria-label={product.name[lang]}>
|
||||
{product.bilder && product.bilder.length > 0 ? (
|
||||
<img class="product-photo" src={product.bilder[0]} alt={product.name[lang]} loading="lazy" />
|
||||
) : (
|
||||
<div class="img-placeholder" role="img" aria-label={product.name[lang]}></div>
|
||||
)}
|
||||
</a>
|
||||
) : (
|
||||
<div class="img-placeholder" role="img" aria-label={featured.name[lang]}></div>
|
||||
)}
|
||||
</a>
|
||||
<div class="intro-text">
|
||||
{sub.vorstellung && <span class="eyebrow">{sub.vorstellung[lang].split("\n")[0]}</span>}
|
||||
<h2><a href={`/ch/produkt/${featured.slug}/`}>{featured.name[lang]}</a></h2>
|
||||
<p class="lead">{sub.vorstellung ? sub.vorstellung[lang].split("\n")[1] : featured.beschreibung[lang]}</p>
|
||||
{sub.mehrLink && (
|
||||
<div class="character-more">
|
||||
<p>Wenn du meh über mich wüsse witt, de klick</p>
|
||||
<a class="btn btn-outline" href={sub.mehrLink} target="_blank" rel="noopener">da →</a>
|
||||
<div class="portrait-frame portrait">
|
||||
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section class="section-tight">
|
||||
<div class="container">
|
||||
{sub.vorstellung && (
|
||||
<div class="character-intro">
|
||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</p>)}
|
||||
{sub.mehrLink && (
|
||||
<div class="intro-text">
|
||||
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
|
||||
<h2>{product ? <a href={`/ch/produkt/${product.slug}/`}>{product.name[lang]}</a> : char.name[lang]}</h2>
|
||||
<p class="lead">{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : (product ? product.beschreibung[lang] : t.shop.subcategoryEmpty)}</p>
|
||||
{char.mehrLink && (
|
||||
<div class="character-more">
|
||||
<p>Wenn du meh über mich wüsse witt, de klick</p>
|
||||
<a class="btn btn-outline" href={sub.mehrLink} target="_blank" rel="noopener">da →</a>
|
||||
<a class="btn btn-outline" href={char.mehrLink} target="_blank" rel="noopener">da →</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
))
|
||||
) : (
|
||||
<section class="section-tight">
|
||||
<div class="container">
|
||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
import Layout from "../../../layouts/Layout.astro";
|
||||
import { getCategory, getSubcategory } from "../../../data/categories";
|
||||
import type { Locale } from "../../../i18n/config";
|
||||
import { useTranslations } from "../../../i18n/ui";
|
||||
|
||||
const lang: Locale = "en";
|
||||
const t = useTranslations(lang);
|
||||
const category = getCategory("diy-plushies")!;
|
||||
const sub = getSubcategory("diy-plushies", "hase-hasidog")!;
|
||||
---
|
||||
<Layout title={sub.name[lang]} description="Meet HasiDog – the small, fluffy adventurer from Van's DIY & Bastelbedarf." lang={lang} path="/hasidog/">
|
||||
<section class="section-tight">
|
||||
<div class="container grid grid-2 intro">
|
||||
<div class="portrait-frame portrait">
|
||||
<img src="/img/hero-plushie-bunny.webp" alt="Crocheted bunny plushie HasiDog in blue overalls, in the grass" width="900" height="1200" loading="eager" />
|
||||
<span class="portrait-badge">{t.common.handmadeBadge}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="eyebrow">{category.icon} {category.name[lang]}</span>
|
||||
<h1>{sub.name[lang]}</h1>
|
||||
{sub.vorstellung && (
|
||||
<div class="character-intro">
|
||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</p>)}
|
||||
</div>
|
||||
)}
|
||||
<div class="btn-row">
|
||||
<a class="btn btn-outline" href={`/en/shop/${category.slug}/${sub.slug}/`}>← {sub.name[lang]}</a>
|
||||
<a class="btn btn-primary" href="/en/shop/">{t.common.toShop}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
@@ -18,17 +18,24 @@ const { kategorie, unterkategorie } = Astro.params;
|
||||
const category = getCategory(kategorie!)!;
|
||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||
|
||||
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||
const featured = echtProdukte[0];
|
||||
const weitere = echtProdukte.slice(1);
|
||||
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
|
||||
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||
|
||||
// Innerhalb einer Tier-Familie (gleicher Slug-Präfix vor dem ersten "-", z.B. "hase") wechselt
|
||||
// die Foto-/Text-Seite ab: 1. Mitglied Foto links, 2. Foto rechts, 3. wieder links, usw. — sorgt
|
||||
// für optische Abwechslung, sobald mehrere Varianten einer Serie eigene Seiten bekommen.
|
||||
const familyPrefix = sub.slug.split("-")[0];
|
||||
const familyMembers = category.subcategories?.filter((s) => s.slug.split("-")[0] === familyPrefix) ?? [];
|
||||
const familyIndex = familyMembers.findIndex((s) => s.slug === sub.slug);
|
||||
const introReverse = familyIndex % 2 === 1;
|
||||
// Jeder Charakter dieser Tier-Familie bekommt seine eigene Box, abwechselnd Foto links/rechts —
|
||||
// 1. Charakter links, 2. rechts, 3. wieder links, usw. Sobald VanVan im Adminbereich neue
|
||||
// Charaktere UND passende Produkte ergänzt, wächst diese Liste automatisch mit. Fehlt einem
|
||||
// Charakter noch ein echtes Produkt, zeigt seine Box trotzdem schon den Charaktertext (nur ohne
|
||||
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
|
||||
const characters = sub.characters ?? [];
|
||||
const characterEntries = characters.map((char, index) => ({
|
||||
char,
|
||||
product: alleProdukte.find((p) => p.charakter === char.slug),
|
||||
reverse: index % 2 === 1,
|
||||
}));
|
||||
|
||||
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
|
||||
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product?.slug).filter(Boolean));
|
||||
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
|
||||
---
|
||||
<Layout title={`${sub.name[lang]} – ${category.name[lang]}`} description={`${sub.name[lang]} at Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}>
|
||||
<div class="container">
|
||||
@@ -43,43 +50,40 @@ const introReverse = familyIndex % 2 === 1;
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{featured ? (
|
||||
<section class="section-tight">
|
||||
<div class={`container grid grid-2 intro ${introReverse ? "intro-reverse" : ""}`}>
|
||||
<a class="portrait-frame portrait" href={`/en/produkt/${featured.slug}/`} aria-label={featured.name[lang]}>
|
||||
{featured.bilder && featured.bilder.length > 0 ? (
|
||||
<img class="product-photo" src={featured.bilder[0]} alt={featured.name[lang]} loading="lazy" />
|
||||
{characterEntries.length > 0 ? (
|
||||
characterEntries.map(({ char, product, reverse }) => (
|
||||
<section class="section-tight">
|
||||
<div class={`container grid grid-2 intro ${reverse ? "intro-reverse" : ""}`}>
|
||||
{product ? (
|
||||
<a class="portrait-frame portrait" href={`/en/produkt/${product.slug}/`} aria-label={product.name[lang]}>
|
||||
{product.bilder && product.bilder.length > 0 ? (
|
||||
<img class="product-photo" src={product.bilder[0]} alt={product.name[lang]} loading="lazy" />
|
||||
) : (
|
||||
<div class="img-placeholder" role="img" aria-label={product.name[lang]}></div>
|
||||
)}
|
||||
</a>
|
||||
) : (
|
||||
<div class="img-placeholder" role="img" aria-label={featured.name[lang]}></div>
|
||||
)}
|
||||
</a>
|
||||
<div class="intro-text">
|
||||
{sub.vorstellung && <span class="eyebrow">{sub.vorstellung[lang].split("\n")[0]}</span>}
|
||||
<h2><a href={`/en/produkt/${featured.slug}/`}>{featured.name[lang]}</a></h2>
|
||||
<p class="lead">{sub.vorstellung ? sub.vorstellung[lang].split("\n")[1] : featured.beschreibung[lang]}</p>
|
||||
{sub.mehrLink && (
|
||||
<div class="character-more">
|
||||
<p>If you'd like to learn more about me, just click</p>
|
||||
<a class="btn btn-outline" href={sub.mehrLink} target="_blank" rel="noopener">here →</a>
|
||||
<div class="portrait-frame portrait">
|
||||
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section class="section-tight">
|
||||
<div class="container">
|
||||
{sub.vorstellung && (
|
||||
<div class="character-intro">
|
||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</p>)}
|
||||
{sub.mehrLink && (
|
||||
<div class="intro-text">
|
||||
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
|
||||
<h2>{product ? <a href={`/en/produkt/${product.slug}/`}>{product.name[lang]}</a> : char.name[lang]}</h2>
|
||||
<p class="lead">{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : (product ? product.beschreibung[lang] : t.shop.subcategoryEmpty)}</p>
|
||||
{char.mehrLink && (
|
||||
<div class="character-more">
|
||||
<p>If you'd like to learn more about me, just click</p>
|
||||
<a class="btn btn-outline" href={sub.mehrLink} target="_blank" rel="noopener">here →</a>
|
||||
<a class="btn btn-outline" href={char.mehrLink} target="_blank" rel="noopener">here →</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
))
|
||||
) : (
|
||||
<section class="section-tight">
|
||||
<div class="container">
|
||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
import Layout from "../../../layouts/Layout.astro";
|
||||
import { getCategory, getSubcategory } from "../../../data/categories";
|
||||
import type { Locale } from "../../../i18n/config";
|
||||
import { useTranslations } from "../../../i18n/ui";
|
||||
|
||||
const lang: Locale = "fr";
|
||||
const t = useTranslations(lang);
|
||||
const category = getCategory("diy-plushies")!;
|
||||
const sub = getSubcategory("diy-plushies", "hase-hasidog")!;
|
||||
---
|
||||
<Layout title={sub.name[lang]} description="Découvre HasiDog – le petit aventurier tout doux de Van's DIY & Bastelbedarf." lang={lang} path="/hasidog/">
|
||||
<section class="section-tight">
|
||||
<div class="container grid grid-2 intro">
|
||||
<div class="portrait-frame portrait">
|
||||
<img src="/img/hero-plushie-bunny.webp" alt="Peluche lapin au crochet HasiDog en salopette bleue, dans l'herbe" width="900" height="1200" loading="eager" />
|
||||
<span class="portrait-badge">{t.common.handmadeBadge}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="eyebrow">{category.icon} {category.name[lang]}</span>
|
||||
<h1>{sub.name[lang]}</h1>
|
||||
{sub.vorstellung && (
|
||||
<div class="character-intro">
|
||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</p>)}
|
||||
</div>
|
||||
)}
|
||||
<div class="btn-row">
|
||||
<a class="btn btn-outline" href={`/fr/shop/${category.slug}/${sub.slug}/`}>← {sub.name[lang]}</a>
|
||||
<a class="btn btn-primary" href="/fr/shop/">{t.common.toShop}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
@@ -18,17 +18,24 @@ const { kategorie, unterkategorie } = Astro.params;
|
||||
const category = getCategory(kategorie!)!;
|
||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||
|
||||
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||
const featured = echtProdukte[0];
|
||||
const weitere = echtProdukte.slice(1);
|
||||
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
|
||||
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||
|
||||
// Innerhalb einer Tier-Familie (gleicher Slug-Präfix vor dem ersten "-", z.B. "hase") wechselt
|
||||
// die Foto-/Text-Seite ab: 1. Mitglied Foto links, 2. Foto rechts, 3. wieder links, usw. — sorgt
|
||||
// für optische Abwechslung, sobald mehrere Varianten einer Serie eigene Seiten bekommen.
|
||||
const familyPrefix = sub.slug.split("-")[0];
|
||||
const familyMembers = category.subcategories?.filter((s) => s.slug.split("-")[0] === familyPrefix) ?? [];
|
||||
const familyIndex = familyMembers.findIndex((s) => s.slug === sub.slug);
|
||||
const introReverse = familyIndex % 2 === 1;
|
||||
// Jeder Charakter dieser Tier-Familie bekommt seine eigene Box, abwechselnd Foto links/rechts —
|
||||
// 1. Charakter links, 2. rechts, 3. wieder links, usw. Sobald VanVan im Adminbereich neue
|
||||
// Charaktere UND passende Produkte ergänzt, wächst diese Liste automatisch mit. Fehlt einem
|
||||
// Charakter noch ein echtes Produkt, zeigt seine Box trotzdem schon den Charaktertext (nur ohne
|
||||
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
|
||||
const characters = sub.characters ?? [];
|
||||
const characterEntries = characters.map((char, index) => ({
|
||||
char,
|
||||
product: alleProdukte.find((p) => p.charakter === char.slug),
|
||||
reverse: index % 2 === 1,
|
||||
}));
|
||||
|
||||
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
|
||||
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product?.slug).filter(Boolean));
|
||||
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
|
||||
---
|
||||
<Layout title={`${sub.name[lang]} – ${category.name[lang]}`} description={`${sub.name[lang]} chez Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}>
|
||||
<div class="container">
|
||||
@@ -43,43 +50,40 @@ const introReverse = familyIndex % 2 === 1;
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{featured ? (
|
||||
<section class="section-tight">
|
||||
<div class={`container grid grid-2 intro ${introReverse ? "intro-reverse" : ""}`}>
|
||||
<a class="portrait-frame portrait" href={`/fr/produkt/${featured.slug}/`} aria-label={featured.name[lang]}>
|
||||
{featured.bilder && featured.bilder.length > 0 ? (
|
||||
<img class="product-photo" src={featured.bilder[0]} alt={featured.name[lang]} loading="lazy" />
|
||||
{characterEntries.length > 0 ? (
|
||||
characterEntries.map(({ char, product, reverse }) => (
|
||||
<section class="section-tight">
|
||||
<div class={`container grid grid-2 intro ${reverse ? "intro-reverse" : ""}`}>
|
||||
{product ? (
|
||||
<a class="portrait-frame portrait" href={`/fr/produkt/${product.slug}/`} aria-label={product.name[lang]}>
|
||||
{product.bilder && product.bilder.length > 0 ? (
|
||||
<img class="product-photo" src={product.bilder[0]} alt={product.name[lang]} loading="lazy" />
|
||||
) : (
|
||||
<div class="img-placeholder" role="img" aria-label={product.name[lang]}></div>
|
||||
)}
|
||||
</a>
|
||||
) : (
|
||||
<div class="img-placeholder" role="img" aria-label={featured.name[lang]}></div>
|
||||
)}
|
||||
</a>
|
||||
<div class="intro-text">
|
||||
{sub.vorstellung && <span class="eyebrow">{sub.vorstellung[lang].split("\n")[0]}</span>}
|
||||
<h2><a href={`/fr/produkt/${featured.slug}/`}>{featured.name[lang]}</a></h2>
|
||||
<p class="lead">{sub.vorstellung ? sub.vorstellung[lang].split("\n")[1] : featured.beschreibung[lang]}</p>
|
||||
{sub.mehrLink && (
|
||||
<div class="character-more">
|
||||
<p>Si tu veux en savoir plus sur moi, clique</p>
|
||||
<a class="btn btn-outline" href={sub.mehrLink} target="_blank" rel="noopener">ici →</a>
|
||||
<div class="portrait-frame portrait">
|
||||
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section class="section-tight">
|
||||
<div class="container">
|
||||
{sub.vorstellung && (
|
||||
<div class="character-intro">
|
||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</p>)}
|
||||
{sub.mehrLink && (
|
||||
<div class="intro-text">
|
||||
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
|
||||
<h2>{product ? <a href={`/fr/produkt/${product.slug}/`}>{product.name[lang]}</a> : char.name[lang]}</h2>
|
||||
<p class="lead">{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : (product ? product.beschreibung[lang] : t.shop.subcategoryEmpty)}</p>
|
||||
{char.mehrLink && (
|
||||
<div class="character-more">
|
||||
<p>Si tu veux en savoir plus sur moi, clique</p>
|
||||
<a class="btn btn-outline" href={sub.mehrLink} target="_blank" rel="noopener">ici →</a>
|
||||
<a class="btn btn-outline" href={char.mehrLink} target="_blank" rel="noopener">ici →</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
))
|
||||
) : (
|
||||
<section class="section-tight">
|
||||
<div class="container">
|
||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import { getCategory, getSubcategory } from "../../data/categories";
|
||||
import type { Locale } from "../../i18n/config";
|
||||
import { useTranslations } from "../../i18n/ui";
|
||||
|
||||
const lang: Locale = "de";
|
||||
const t = useTranslations(lang);
|
||||
const category = getCategory("diy-plushies")!;
|
||||
const sub = getSubcategory("diy-plushies", "hase-hasidog")!;
|
||||
---
|
||||
<Layout title={sub.name[lang]} description="Lerne HasiDog kennen – den kleinen, wuscheligen Abenteurer von Van's DIY & Bastelbedarf." lang={lang} path="/hasidog/">
|
||||
<section class="section-tight">
|
||||
<div class="container grid grid-2 intro">
|
||||
<div class="portrait-frame portrait">
|
||||
<img src="/img/hero-plushie-bunny.webp" alt="Gehäkeltes Hasen-Plushie HasiDog in blauer Latzhose, im Gras" width="900" height="1200" loading="eager" />
|
||||
<span class="portrait-badge">{t.common.handmadeBadge}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="eyebrow">{category.icon} {category.name[lang]}</span>
|
||||
<h1>{sub.name[lang]}</h1>
|
||||
{sub.vorstellung && (
|
||||
<div class="character-intro">
|
||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</p>)}
|
||||
</div>
|
||||
)}
|
||||
<div class="btn-row">
|
||||
<a class="btn btn-outline" href={`/shop/${category.slug}/${sub.slug}/`}>← {sub.name[lang]}</a>
|
||||
<a class="btn btn-primary" href="/shop/">{t.common.toShop}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
@@ -18,20 +18,24 @@ const { kategorie, unterkategorie } = Astro.params;
|
||||
const category = getCategory(kategorie!)!;
|
||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||
|
||||
// Sobald VanVan im Adminbereich ein echtes Produkt dieser Unterkategorie zuordnet, wird es
|
||||
// hier automatisch featured angezeigt (echtes Foto + ihr echter Text) statt des generischen
|
||||
// Platzhaltertexts.
|
||||
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||
const featured = echtProdukte[0];
|
||||
const weitere = echtProdukte.slice(1);
|
||||
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
|
||||
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||
|
||||
// Innerhalb einer Tier-Familie (gleicher Slug-Präfix vor dem ersten "-", z.B. "hase") wechselt
|
||||
// die Foto-/Text-Seite ab: 1. Mitglied Foto links, 2. Foto rechts, 3. wieder links, usw. — sorgt
|
||||
// für optische Abwechslung, sobald mehrere Varianten einer Serie eigene Seiten bekommen.
|
||||
const familyPrefix = sub.slug.split("-")[0];
|
||||
const familyMembers = category.subcategories?.filter((s) => s.slug.split("-")[0] === familyPrefix) ?? [];
|
||||
const familyIndex = familyMembers.findIndex((s) => s.slug === sub.slug);
|
||||
const introReverse = familyIndex % 2 === 1;
|
||||
// Jeder Charakter dieser Tier-Familie bekommt seine eigene Box, abwechselnd Foto links/rechts —
|
||||
// 1. Charakter links, 2. rechts, 3. wieder links, usw. Sobald VanVan im Adminbereich neue
|
||||
// Charaktere UND passende Produkte ergänzt, wächst diese Liste automatisch mit. Fehlt einem
|
||||
// Charakter noch ein echtes Produkt, zeigt seine Box trotzdem schon den Charaktertext (nur ohne
|
||||
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
|
||||
const characters = sub.characters ?? [];
|
||||
const characterEntries = characters.map((char, index) => ({
|
||||
char,
|
||||
product: alleProdukte.find((p) => p.charakter === char.slug),
|
||||
reverse: index % 2 === 1,
|
||||
}));
|
||||
|
||||
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
|
||||
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product?.slug).filter(Boolean));
|
||||
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
|
||||
---
|
||||
<Layout title={`${sub.name[lang]} – ${category.name[lang]}`} description={`${sub.name[lang]} bei Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}>
|
||||
<div class="container">
|
||||
@@ -46,43 +50,40 @@ const introReverse = familyIndex % 2 === 1;
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{featured ? (
|
||||
<section class="section-tight">
|
||||
<div class={`container grid grid-2 intro ${introReverse ? "intro-reverse" : ""}`}>
|
||||
<a class="portrait-frame portrait" href={`/produkt/${featured.slug}/`} aria-label={featured.name[lang]}>
|
||||
{featured.bilder && featured.bilder.length > 0 ? (
|
||||
<img class="product-photo" src={featured.bilder[0]} alt={featured.name[lang]} loading="lazy" />
|
||||
{characterEntries.length > 0 ? (
|
||||
characterEntries.map(({ char, product, reverse }) => (
|
||||
<section class="section-tight">
|
||||
<div class={`container grid grid-2 intro ${reverse ? "intro-reverse" : ""}`}>
|
||||
{product ? (
|
||||
<a class="portrait-frame portrait" href={`/produkt/${product.slug}/`} aria-label={product.name[lang]}>
|
||||
{product.bilder && product.bilder.length > 0 ? (
|
||||
<img class="product-photo" src={product.bilder[0]} alt={product.name[lang]} loading="lazy" />
|
||||
) : (
|
||||
<div class="img-placeholder" role="img" aria-label={product.name[lang]}></div>
|
||||
)}
|
||||
</a>
|
||||
) : (
|
||||
<div class="img-placeholder" role="img" aria-label={featured.name[lang]}></div>
|
||||
)}
|
||||
</a>
|
||||
<div class="intro-text">
|
||||
{sub.vorstellung && <span class="eyebrow">{sub.vorstellung[lang].split("\n")[0]}</span>}
|
||||
<h2><a href={`/produkt/${featured.slug}/`}>{featured.name[lang]}</a></h2>
|
||||
<p class="lead">{sub.vorstellung ? sub.vorstellung[lang].split("\n")[1] : featured.beschreibung[lang]}</p>
|
||||
{sub.mehrLink && (
|
||||
<div class="character-more">
|
||||
<p>Wenn du mehr über mich erfahren möchtest, dann klicke</p>
|
||||
<a class="btn btn-outline" href={sub.mehrLink} target="_blank" rel="noopener">hier →</a>
|
||||
<div class="portrait-frame portrait">
|
||||
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section class="section-tight">
|
||||
<div class="container">
|
||||
{sub.vorstellung && (
|
||||
<div class="character-intro">
|
||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</p>)}
|
||||
{sub.mehrLink && (
|
||||
<div class="intro-text">
|
||||
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
|
||||
<h2>{product ? <a href={`/produkt/${product.slug}/`}>{product.name[lang]}</a> : char.name[lang]}</h2>
|
||||
<p class="lead">{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : (product ? product.beschreibung[lang] : t.shop.subcategoryEmpty)}</p>
|
||||
{char.mehrLink && (
|
||||
<div class="character-more">
|
||||
<p>Wenn du mehr über mich erfahren möchtest, dann klicke</p>
|
||||
<a class="btn btn-outline" href={sub.mehrLink} target="_blank" rel="noopener">hier →</a>
|
||||
<a class="btn btn-outline" href={char.mehrLink} target="_blank" rel="noopener">hier →</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
))
|
||||
) : (
|
||||
<section class="section-tight">
|
||||
<div class="container">
|
||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user