Tier-Familien-Seiten: nur noch Charaktere mit echtem Admin-Produkt sichtbar

Wichtige Verhaltensänderung: ein Charakter (aus dem Kategorien-Bereich im
Admin) ist ab jetzt nur noch eine VORBEREITETE Definition. Er erscheint
auf der Seite NUR, wenn ihm im Admin tatsächlich ein Produkt mit genau
diesem Charakter-Code zugeordnet wurde (das Feld "Charakter" am
Produkt = seine "Rolle"). Ohne zugeordnetes Produkt taucht er nirgends
mehr auf — auch nicht mehr als Platzhalter-Karte mit seinem Namen.
Konkret: HasiGott und Mama Hase verschwinden von der Hase-Seite, weil
für sie (noch) keine Produkte im Admin angelegt wurden. Sobald VanVan
ein Produkt mit charakter: hasigott anlegt, erscheint er automatisch.

Familien ganz ohne Produkte (Kuh, Wal, Oktopus, Schildkröte, Pinguin)
zeigen weiterhin die bereits vorbereiteten, namenlosen "Kommt bald"-
Karten (unverändert) — nur die NAMENTLICHE Vorab-Anzeige einzelner
Charaktere ohne Produkt entfällt.

Zusätzlich: der "Mehr über mich"-Button in der Story-Box hatte keinen
erklärenden Text — man wusste nicht, wohin/wozu er führt. Jetzt steht
davor "Wenn du mehr über mich erfahren möchtest, dann klicke [hier →]"
in allen 4 Sprachen.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
qcigano
2026-08-03 00:47:25 +02:00
co-authored by Claude Sonnet 5
parent b28475c26a
commit 8700e2c588
6 changed files with 103 additions and 140 deletions
@@ -21,19 +21,18 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
// Jeder Charakter dieser Tier-Familie bekommt seine eigene kompakte Karte (wie die Produktkarten
// im Shop-Grid), alle nebeneinander in einer Reihe. Sobald VanVan im Adminbereich neue
// Charaktere UND passende Produkte ergänzt, wächst diese Reihe automatisch mit. Fehlt einem
// Charakter noch ein echtes Produkt, zeigt seine Karte trotzdem schon den Charaktertext (nur ohne
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
// Ein Charakter (aus dem Kategorien-Bereich im Admin) ist erstmal nur eine VORBEREITETE
// Definition — er erscheint auf der Seite NUR, wenn ihm im Admin auch wirklich ein Produkt mit
// genau diesem Charakter-Code zugeordnet wurde (das Feld "Charakter" am Produkt = seine "Rolle").
// Ohne zugeordnetes Produkt taucht er hier NICHT auf, auch nicht als Platzhalter — nur echte,
// im Admin angelegte Artikel dürfen auf der Seite erscheinen.
const characters = sub.characters ?? [];
const characterEntries = characters.map((char) => ({
char,
product: alleProdukte.find((p) => p.charakter === char.slug),
}));
const characterEntries = characters
.map((char) => ({ char, product: alleProdukte.find((p) => p.charakter === char.slug) }))
.filter((e) => e.product);
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product?.slug).filter(Boolean));
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product!.slug));
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
const hatCharaktere = characterEntries.length > 0;
const hatWeitere = weitere.length > 0;
@@ -55,34 +54,23 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
{hatCharaktere && (
<section class="section-tight">
<div class="container">
{characterEntries.some(({ product }) => product) && (
<div class="character-stories">
{characterEntries.filter(({ product }) => product).map(({ char }) => (
<div class="card character-story">
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
<p>{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : ""}</p>
{char.mehrLink && (
<div class="character-stories">
{characterEntries.map(({ char }) => (
<div class="card character-story">
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
<p>{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : ""}</p>
{char.mehrLink && (
<p class="character-more-lead">
{t.shop.moreAboutCharacterLead}{" "}
<a class="btn btn-outline btn-sm" href={char.mehrLink} target="_blank" rel="noopener">da →</a>
)}
</div>
))}
</div>
)}
</p>
)}
</div>
))}
</div>
<div class="grid grid-3">
{characterEntries.map(({ char, product }) =>
product ? (
<ProductCard product={product} lang={lang} />
) : (
<div class="card character-card">
<div class="product-media">
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
</div>
<h3>{char.name[lang]}</h3>
<p class="character-card-desc">{t.shop.subcategoryEmpty}</p>
</div>
)
)}
{characterEntries.map(({ product }) => <ProductCard product={product!} lang={lang} />)}
</div>
</div>
</section>
@@ -21,19 +21,18 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
// Jeder Charakter dieser Tier-Familie bekommt seine eigene kompakte Karte (wie die Produktkarten
// im Shop-Grid), alle nebeneinander in einer Reihe. Sobald VanVan im Adminbereich neue
// Charaktere UND passende Produkte ergänzt, wächst diese Reihe automatisch mit. Fehlt einem
// Charakter noch ein echtes Produkt, zeigt seine Karte trotzdem schon den Charaktertext (nur ohne
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
// Ein Charakter (aus dem Kategorien-Bereich im Admin) ist erstmal nur eine VORBEREITETE
// Definition — er erscheint auf der Seite NUR, wenn ihm im Admin auch wirklich ein Produkt mit
// genau diesem Charakter-Code zugeordnet wurde (das Feld "Charakter" am Produkt = seine "Rolle").
// Ohne zugeordnetes Produkt taucht er hier NICHT auf, auch nicht als Platzhalter — nur echte,
// im Admin angelegte Artikel dürfen auf der Seite erscheinen.
const characters = sub.characters ?? [];
const characterEntries = characters.map((char) => ({
char,
product: alleProdukte.find((p) => p.charakter === char.slug),
}));
const characterEntries = characters
.map((char) => ({ char, product: alleProdukte.find((p) => p.charakter === char.slug) }))
.filter((e) => e.product);
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product?.slug).filter(Boolean));
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product!.slug));
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
const hatCharaktere = characterEntries.length > 0;
const hatWeitere = weitere.length > 0;
@@ -55,34 +54,23 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
{hatCharaktere && (
<section class="section-tight">
<div class="container">
{characterEntries.some(({ product }) => product) && (
<div class="character-stories">
{characterEntries.filter(({ product }) => product).map(({ char }) => (
<div class="card character-story">
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
<p>{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : ""}</p>
{char.mehrLink && (
<div class="character-stories">
{characterEntries.map(({ char }) => (
<div class="card character-story">
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
<p>{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : ""}</p>
{char.mehrLink && (
<p class="character-more-lead">
{t.shop.moreAboutCharacterLead}{" "}
<a class="btn btn-outline btn-sm" href={char.mehrLink} target="_blank" rel="noopener">here →</a>
)}
</div>
))}
</div>
)}
</p>
)}
</div>
))}
</div>
<div class="grid grid-3">
{characterEntries.map(({ char, product }) =>
product ? (
<ProductCard product={product} lang={lang} />
) : (
<div class="card character-card">
<div class="product-media">
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
</div>
<h3>{char.name[lang]}</h3>
<p class="character-card-desc">{t.shop.subcategoryEmpty}</p>
</div>
)
)}
{characterEntries.map(({ product }) => <ProductCard product={product!} lang={lang} />)}
</div>
</div>
</section>
@@ -21,19 +21,18 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
// Jeder Charakter dieser Tier-Familie bekommt seine eigene kompakte Karte (wie die Produktkarten
// im Shop-Grid), alle nebeneinander in einer Reihe. Sobald VanVan im Adminbereich neue
// Charaktere UND passende Produkte ergänzt, wächst diese Reihe automatisch mit. Fehlt einem
// Charakter noch ein echtes Produkt, zeigt seine Karte trotzdem schon den Charaktertext (nur ohne
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
// Ein Charakter (aus dem Kategorien-Bereich im Admin) ist erstmal nur eine VORBEREITETE
// Definition — er erscheint auf der Seite NUR, wenn ihm im Admin auch wirklich ein Produkt mit
// genau diesem Charakter-Code zugeordnet wurde (das Feld "Charakter" am Produkt = seine "Rolle").
// Ohne zugeordnetes Produkt taucht er hier NICHT auf, auch nicht als Platzhalter — nur echte,
// im Admin angelegte Artikel dürfen auf der Seite erscheinen.
const characters = sub.characters ?? [];
const characterEntries = characters.map((char) => ({
char,
product: alleProdukte.find((p) => p.charakter === char.slug),
}));
const characterEntries = characters
.map((char) => ({ char, product: alleProdukte.find((p) => p.charakter === char.slug) }))
.filter((e) => e.product);
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product?.slug).filter(Boolean));
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product!.slug));
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
const hatCharaktere = characterEntries.length > 0;
const hatWeitere = weitere.length > 0;
@@ -55,34 +54,23 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
{hatCharaktere && (
<section class="section-tight">
<div class="container">
{characterEntries.some(({ product }) => product) && (
<div class="character-stories">
{characterEntries.filter(({ product }) => product).map(({ char }) => (
<div class="card character-story">
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
<p>{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : ""}</p>
{char.mehrLink && (
<div class="character-stories">
{characterEntries.map(({ char }) => (
<div class="card character-story">
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
<p>{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : ""}</p>
{char.mehrLink && (
<p class="character-more-lead">
{t.shop.moreAboutCharacterLead}{" "}
<a class="btn btn-outline btn-sm" href={char.mehrLink} target="_blank" rel="noopener">ici →</a>
)}
</div>
))}
</div>
)}
</p>
)}
</div>
))}
</div>
<div class="grid grid-3">
{characterEntries.map(({ char, product }) =>
product ? (
<ProductCard product={product} lang={lang} />
) : (
<div class="card character-card">
<div class="product-media">
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
</div>
<h3>{char.name[lang]}</h3>
<p class="character-card-desc">{t.shop.subcategoryEmpty}</p>
</div>
)
)}
{characterEntries.map(({ product }) => <ProductCard product={product!} lang={lang} />)}
</div>
</div>
</section>
@@ -21,19 +21,18 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
// Jeder Charakter dieser Tier-Familie bekommt seine eigene kompakte Karte (wie die Produktkarten
// im Shop-Grid), alle nebeneinander in einer Reihe. Sobald VanVan im Adminbereich neue
// Charaktere UND passende Produkte ergänzt, wächst diese Reihe automatisch mit. Fehlt einem
// Charakter noch ein echtes Produkt, zeigt seine Karte trotzdem schon den Charaktertext (nur ohne
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
// Ein Charakter (aus dem Kategorien-Bereich im Admin) ist erstmal nur eine VORBEREITETE
// Definition — er erscheint auf der Seite NUR, wenn ihm im Admin auch wirklich ein Produkt mit
// genau diesem Charakter-Code zugeordnet wurde (das Feld "Charakter" am Produkt = seine "Rolle").
// Ohne zugeordnetes Produkt taucht er hier NICHT auf, auch nicht als Platzhalter — nur echte,
// im Admin angelegte Artikel dürfen auf der Seite erscheinen.
const characters = sub.characters ?? [];
const characterEntries = characters.map((char) => ({
char,
product: alleProdukte.find((p) => p.charakter === char.slug),
}));
const characterEntries = characters
.map((char) => ({ char, product: alleProdukte.find((p) => p.charakter === char.slug) }))
.filter((e) => e.product);
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product?.slug).filter(Boolean));
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product!.slug));
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
const hatCharaktere = characterEntries.length > 0;
const hatWeitere = weitere.length > 0;
@@ -61,35 +60,24 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
<div class="container">
{/* Die persönlichen Charaktergeschichten stehen als eigene, volle Breite Boxen ÜBER der
Kartenreihe — getrennt von den "Shop"-Karten darunter, die nur Foto/Preis/Infos zeigen. */}
{characterEntries.some(({ product }) => product) && (
<div class="character-stories">
{characterEntries.filter(({ product }) => product).map(({ char }) => (
<div class="card character-story">
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
<p>{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : ""}</p>
{char.mehrLink && (
<div class="character-stories">
{characterEntries.map(({ char }) => (
<div class="card character-story">
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
<p>{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : ""}</p>
{char.mehrLink && (
<p class="character-more-lead">
{t.shop.moreAboutCharacterLead}{" "}
<a class="btn btn-outline btn-sm" href={char.mehrLink} target="_blank" rel="noopener">hier →</a>
)}
</div>
))}
</div>
)}
</p>
)}
</div>
))}
</div>
{/* Kartenreihe wie im Shop-Grid — nur Foto, Preis und Kaufinfos, keine Geschichten. */}
<div class="grid grid-3">
{characterEntries.map(({ char, product }) =>
product ? (
<ProductCard product={product} lang={lang} />
) : (
<div class="card character-card">
<div class="product-media">
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
</div>
<h3>{char.name[lang]}</h3>
<p class="character-card-desc">{t.shop.subcategoryEmpty}</p>
</div>
)
)}
{characterEntries.map(({ product }) => <ProductCard product={product!} lang={lang} />)}
</div>
</div>
</section>