Zwei neue Hase-Varianten (HasiGott, Mama Hase) + abwechselnde Foto/Text-Seite pro Familie; Hauptkategorie-Überschrift auf Kategorie-Seiten

- Zwei neue Unterkategorien unter "Hase" angelegt: "HasiGott" und "Mama
  Hase", jeweils mit eigenem Charaktertext, bereit für echte Produktfotos
  sobald VanVan sie im Adminbereich hinzufügt.
- Neue Logik: Innerhalb einer Tier-Familie (gleicher Slug-Präfix, z.B.
  "hase-*") wechselt die Vorstellungsbox jetzt automatisch die Seite ab —
  1. Mitglied Foto links/Text rechts (HasiDog, unverändert), 2. Mitglied
  Foto rechts/Text links (HasiGott), 3. wieder links (Mama Hase), und so
  weiter für alle künftigen Varianten.
- Kategorie-Seiten (z.B. "DIY Plushies") zeigen jetzt eine
  Hauptkategorie-Überschrift zwischen der Unterkategorien-Chip-Reihe und
  der eigentlichen Produktreihe darunter.

Verifiziert per Puppeteer gegen den echten Build: neue Seiten laden
fehlerfrei, HasiDog bleibt korrekt "nicht gespiegelt" (1. Familienmitglied),
Hauptkategorie-Überschrift sitzt an der richtigen Stelle.
This commit is contained in:
qcigano
2026-08-02 19:56:46 +02:00
parent 965434e2fc
commit 1c9bee94ca
10 changed files with 101 additions and 9 deletions
+30
View File
@@ -46,6 +46,36 @@
},
"mehrLink": "https://dogfather-universe.dogfather1608.workers.dev/hasidog.html"
},
{
"slug": "hase-hasigott",
"name": {
"de": "Hase „HasiGott”",
"en": "Bunny \"HasiGott\"",
"ch": "Hase „HasiGott”",
"fr": "Lapin « HasiGott »"
},
"vorstellung": {
"de": "Hallo, ich bin HasiGott.\nIch bin der Größte, Weiseste und Kuscheligste von allen zumindest behaupte ich das gerne selbst. Mit meinem selbstbewussten Lächeln bringe ich dich garantiert zum Schmunzeln.",
"en": "Hello, I'm HasiGott.\nI'm the biggest, wisest and cuddliest of them all — or at least that's what I like to say myself. With my confident smile, I'm bound to bring one to your face too.",
"ch": "Hoi, ig bi dr HasiGott.\nIg bi dr Gröscht, Wysischt und Kuschligscht vo allne zumindescht behaupte ig das gern sälber. Mit mim sälbstbewusste Lächle bring ig di garantiert zum Schmunzle.",
"fr": "Bonjour, je suis HasiGott.\nJe suis le plus grand, le plus sage et le plus câlin de tous — enfin, c'est ce que j'aime dire moi-même. Avec mon sourire plein d'assurance, je suis sûr de te faire sourire aussi."
}
},
{
"slug": "hase-mama",
"name": {
"de": "Hase „Mama Hase”",
"en": "Bunny \"Mama Hase\"",
"ch": "Hase „Mama Hase”",
"fr": "Lapin « Maman Lapine »"
},
"vorstellung": {
"de": "Hallo, ich bin Mama Hase.\nIch halte meine Familie mit ganz viel Liebe und einem offenen Herzen zusammen. Bei mir findest du immer ein warmes Plätzchen zum Ankuscheln.",
"en": "Hello, I'm Mama Hase.\nI hold my family together with lots of love and an open heart. With me, you'll always find a warm spot to cuddle up.",
"ch": "Hoi, ig bi d'Mama Hase.\nIg haltä mini Familie mit ganz viel Liebi und emenä offene Härz zäme. Bi mir findsch du immer es warms Plätzli zum Aakuschle.",
"fr": "Bonjour, je suis Maman Lapine.\nJe tiens ma famille unie avec beaucoup d'amour et un cœur ouvert. Avec moi, tu trouveras toujours un coin douillet pour te blottir."
}
},
{
"slug": "wal-sam",
"name": {
+3
View File
@@ -40,6 +40,9 @@ const items = productsByCategory(category.slug);
</div>
</>
)}
{items.length > 0 && (
<h2 class="kategorie-section-label">{category.icon} {category.name[lang]}</h2>
)}
{items.length > 0 ? (
<div class="grid grid-3">{items.map((p) => <ProductCard product={p} lang={lang} />)}</div>
) : (
@@ -21,6 +21,14 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
const featured = echtProdukte[0];
const weitere = echtProdukte.slice(1);
// 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;
---
<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">
@@ -37,7 +45,7 @@ const weitere = echtProdukte.slice(1);
{featured ? (
<section class="section-tight">
<div class="container grid grid-2 intro">
<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" />
@@ -45,7 +53,7 @@ const weitere = echtProdukte.slice(1);
<div class="img-placeholder" role="img" aria-label={featured.name[lang]}></div>
)}
</a>
<div>
<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>
+3
View File
@@ -40,6 +40,9 @@ const items = productsByCategory(category.slug);
</div>
</>
)}
{items.length > 0 && (
<h2 class="kategorie-section-label">{category.icon} {category.name[lang]}</h2>
)}
{items.length > 0 ? (
<div class="grid grid-3">{items.map((p) => <ProductCard product={p} lang={lang} />)}</div>
) : (
@@ -21,6 +21,14 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
const featured = echtProdukte[0];
const weitere = echtProdukte.slice(1);
// 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;
---
<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">
@@ -37,7 +45,7 @@ const weitere = echtProdukte.slice(1);
{featured ? (
<section class="section-tight">
<div class="container grid grid-2 intro">
<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" />
@@ -45,7 +53,7 @@ const weitere = echtProdukte.slice(1);
<div class="img-placeholder" role="img" aria-label={featured.name[lang]}></div>
)}
</a>
<div>
<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>
+3
View File
@@ -40,6 +40,9 @@ const items = productsByCategory(category.slug);
</div>
</>
)}
{items.length > 0 && (
<h2 class="kategorie-section-label">{category.icon} {category.name[lang]}</h2>
)}
{items.length > 0 ? (
<div class="grid grid-3">{items.map((p) => <ProductCard product={p} lang={lang} />)}</div>
) : (
@@ -21,6 +21,14 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
const featured = echtProdukte[0];
const weitere = echtProdukte.slice(1);
// 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;
---
<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">
@@ -37,7 +45,7 @@ const weitere = echtProdukte.slice(1);
{featured ? (
<section class="section-tight">
<div class="container grid grid-2 intro">
<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" />
@@ -45,7 +53,7 @@ const weitere = echtProdukte.slice(1);
<div class="img-placeholder" role="img" aria-label={featured.name[lang]}></div>
)}
</a>
<div>
<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>
+3
View File
@@ -40,6 +40,9 @@ const items = productsByCategory(category.slug);
</div>
</>
)}
{items.length > 0 && (
<h2 class="kategorie-section-label">{category.icon} {category.name[lang]}</h2>
)}
{items.length > 0 ? (
<div class="grid grid-3">{items.map((p) => <ProductCard product={p} lang={lang} />)}</div>
) : (
@@ -24,6 +24,14 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
const featured = echtProdukte[0];
const weitere = echtProdukte.slice(1);
// 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;
---
<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">
@@ -40,7 +48,7 @@ const weitere = echtProdukte.slice(1);
{featured ? (
<section class="section-tight">
<div class="container grid grid-2 intro">
<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" />
@@ -48,7 +56,7 @@ const weitere = echtProdukte.slice(1);
<div class="img-placeholder" role="img" aria-label={featured.name[lang]}></div>
)}
</a>
<div>
<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>
+19 -1
View File
@@ -608,7 +608,15 @@ a:focus-visible, button:focus-visible {
.intro h2 a { color: inherit; transition: color 0.2s var(--ease); }
.intro h2 a:hover { color: var(--c-accent); }
.intro .portrait-frame.portrait { display: block; }
@media (max-width: 800px) { .intro { grid-template-columns: 1fr; gap: 1.5rem; } }
/* Wechselt innerhalb einer Tier-Familie ab: Foto links/rechts, Text jeweils gegenüber — sorgt
für optische Abwechslung, sobald mehrere Varianten einer Serie eigene Seiten bekommen. */
.intro-reverse { grid-template-columns: 1.15fr 0.85fr; }
.intro-reverse .portrait-frame.portrait { order: 2; }
.intro-reverse .intro-text { order: 1; }
@media (max-width: 800px) {
.intro { grid-template-columns: 1fr; gap: 1.5rem; }
.intro-reverse .portrait-frame.portrait, .intro-reverse .intro-text { order: initial; }
}
/* Breiter als vorher (war 440px) + Seitenverhältnis nah am Originalfoto (900x1353 ≈ 2/3)
statt 3/4 — vorher wurde oben/unten so stark beschnitten, dass das Plushie neben VanVan
(Oktopus) nur noch als Ecke sichtbar war. So ist das ganze Foto inkl. beider Plushies zu sehen. */
@@ -744,6 +752,16 @@ a:focus-visible, button:focus-visible {
/* Überschrift über der "weitere Produkte"-Reihe auf Unterkategorie-Seiten — macht klar, dass da
noch mehr Produkte derselben Reihe kommen (Charakter-Familie), egal wie viele es am Ende sind. */
.more-variants-label { text-transform: uppercase; letter-spacing: 0.08em; color: var(--c-text-muted); margin-bottom: 0.8rem; }
/* Hauptkategorie-Überschrift zwischen den Unterkategorie-Chips und der eigentlichen
Produktreihe auf der Kategorie-Seite — macht klar, welche Hauptkategorie man gerade
durchstöbert, fällt bewusst etwas mehr auf als die stille "Unterkategorien"-Beschriftung. */
.kategorie-section-label {
text-align: center;
font-size: 1.3rem;
color: var(--c-accent);
margin: 2.2rem 0 1.3rem;
}
@media (max-width: 800px) { .shop-layout { grid-template-columns: 1fr; } }
/* Produktseite — großes Bild bekommt denselben edlen "Rahmen"-Look wie die Porträtfotos der