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:
+33
-2
@@ -97,7 +97,12 @@ collections:
|
|||||||
name: unterkategorie
|
name: unterkategorie
|
||||||
widget: string
|
widget: string
|
||||||
required: false
|
required: false
|
||||||
hint: "Interner Code der Unterkategorie eintragen, z.B. kuh-frieda (siehe Bereich Kategorien)"
|
hint: "Interner Code der Unterkategorie eintragen, z.B. hase (siehe Bereich Kategorien)"
|
||||||
|
- label: "Charakter innerhalb der Unterkategorie (optional)"
|
||||||
|
name: charakter
|
||||||
|
widget: string
|
||||||
|
required: false
|
||||||
|
hint: "Nur nötig, wenn die Unterkategorie mehrere Charaktere hat (z.B. 'hase' hat HasiDog, HasiGott, Mama Hase) — dann hier den Charakter-Code eintragen, z.B. hasidog (siehe Bereich Kategorien)."
|
||||||
- label: Auszeichnungen
|
- label: Auszeichnungen
|
||||||
name: badges
|
name: badges
|
||||||
widget: select
|
widget: select
|
||||||
@@ -174,7 +179,7 @@ collections:
|
|||||||
required: false
|
required: false
|
||||||
summary: "{{fields.name.de}}"
|
summary: "{{fields.name.de}}"
|
||||||
fields:
|
fields:
|
||||||
- { label: "Interner Code (URL)", name: slug, widget: string }
|
- { label: "Interner Code (URL)", name: slug, widget: string, hint: "z.B. hase — steht für die ganze Tier-Serie, nicht nur einen einzelnen Charakter." }
|
||||||
- label: Name
|
- label: Name
|
||||||
name: name
|
name: name
|
||||||
widget: object
|
widget: object
|
||||||
@@ -183,6 +188,32 @@ collections:
|
|||||||
- { label: Englisch, name: en, widget: string }
|
- { label: Englisch, name: en, widget: string }
|
||||||
- { label: "Schweizerdeutsch", name: ch, widget: string }
|
- { label: "Schweizerdeutsch", name: ch, widget: string }
|
||||||
- { label: Französisch, name: fr, widget: string }
|
- { label: Französisch, name: fr, widget: string }
|
||||||
|
- label: "Charaktere dieser Serie (z.B. mehrere Hasen-Varianten)"
|
||||||
|
name: characters
|
||||||
|
widget: list
|
||||||
|
required: false
|
||||||
|
summary: "{{fields.name.de}}"
|
||||||
|
hint: "Jeder Charakter bekommt auf der Unterkategorie-Seite seine eigene Box — die Foto-Seite wechselt automatisch ab (1. links, 2. rechts, 3. wieder links, usw.). Trage beim passenden Produkt weiter oben unter 'Charakter innerhalb der Unterkategorie' genau denselben Code ein, damit Foto und Text zusammenfinden."
|
||||||
|
fields:
|
||||||
|
- { label: "Interner Code", name: slug, widget: string, hint: "z.B. hasidog — muss beim Produkt unter 'Charakter' genau so eingetragen werden." }
|
||||||
|
- label: Name
|
||||||
|
name: name
|
||||||
|
widget: object
|
||||||
|
fields:
|
||||||
|
- { label: Deutsch, name: de, widget: string }
|
||||||
|
- { label: Englisch, name: en, widget: string }
|
||||||
|
- { label: "Schweizerdeutsch", name: ch, widget: string }
|
||||||
|
- { label: Französisch, name: fr, widget: string }
|
||||||
|
- label: "Persönliche Vorstellung (1. Zeile 'Hallo, ich bin ...', 2. Zeile Persönlichkeit)"
|
||||||
|
name: vorstellung
|
||||||
|
widget: object
|
||||||
|
required: false
|
||||||
|
fields:
|
||||||
|
- { label: Deutsch, name: de, widget: text }
|
||||||
|
- { label: Englisch, name: en, widget: text }
|
||||||
|
- { label: "Schweizerdeutsch", name: ch, widget: text }
|
||||||
|
- { label: Französisch, name: fr, widget: text }
|
||||||
|
- { label: "Link zu einer externen 'Mehr über mich'-Seite (optional)", name: mehrLink, widget: string, required: false }
|
||||||
- label: "Reihenfolge (0 = zuerst)"
|
- label: "Reihenfolge (0 = zuerst)"
|
||||||
name: order
|
name: order
|
||||||
widget: number
|
widget: number
|
||||||
|
|||||||
@@ -16,28 +16,28 @@
|
|||||||
"icon": "🧸",
|
"icon": "🧸",
|
||||||
"subcategories": [
|
"subcategories": [
|
||||||
{
|
{
|
||||||
"slug": "kuh-frieda",
|
"slug": "kuh",
|
||||||
"name": {
|
"name": { "de": "Kuh", "en": "Cow", "ch": "Kuh", "fr": "Vache" },
|
||||||
"de": "Kuh „Frieda”",
|
"characters": [
|
||||||
"en": "Cow \"Frieda\"",
|
{
|
||||||
"ch": "Kuh „Frieda”",
|
"slug": "frieda",
|
||||||
"fr": "Vache « Frieda »"
|
"name": { "de": "Frieda", "en": "Frieda", "ch": "Frieda", "fr": "Frieda" },
|
||||||
},
|
|
||||||
"vorstellung": {
|
"vorstellung": {
|
||||||
"de": "Hallo, ich bin Frieda.\nIch liebe blühende Wiesen und sammle jeden Tag die schönsten Glücksmomente. Mit meinem sanften Herzen erinnere ich dich daran, dass die schönsten Dinge oft ganz leise entstehen.",
|
"de": "Hallo, ich bin Frieda.\nIch liebe blühende Wiesen und sammle jeden Tag die schönsten Glücksmomente. Mit meinem sanften Herzen erinnere ich dich daran, dass die schönsten Dinge oft ganz leise entstehen.",
|
||||||
"en": "Hello, I'm Frieda.\nI love blooming meadows and collect the sweetest little moments of happiness every day. With my gentle heart, I remind you that the most beautiful things often happen quietly.",
|
"en": "Hello, I'm Frieda.\nI love blooming meadows and collect the sweetest little moments of happiness every day. With my gentle heart, I remind you that the most beautiful things often happen quietly.",
|
||||||
"ch": "Hoi, ig bi d'Frieda.\nIg liebe blüehendi Wise und sammle jede Tag die schönste Glücksmomänt. Mit mim sanfte Härz erinner ig di dra, dass die schönschte Sache oft ganz leis entstöh.",
|
"ch": "Hoi, ig bi d'Frieda.\nIg liebe blüehendi Wise und sammle jede Tag die schönste Glücksmomänt. Mit mim sanfte Härz erinner ig di dra, dass die schönschte Sache oft ganz leis entstöh.",
|
||||||
"fr": "Bonjour, je suis Frieda.\nJ'aime les prairies fleuries et je récolte chaque jour les plus beaux instants de bonheur. Avec mon cœur tendre, je te rappelle que les plus belles choses naissent souvent tout en douceur."
|
"fr": "Bonjour, je suis Frieda.\nJ'aime les prairies fleuries et je récolte chaque jour les plus beaux instants de bonheur. Avec mon cœur tendre, je te rappelle que les plus belles choses naissent souvent tout en douceur."
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "hase-hasidog",
|
"slug": "hase",
|
||||||
"name": {
|
"name": { "de": "Hase", "en": "Bunny", "ch": "Hase", "fr": "Lapin" },
|
||||||
"de": "Hase „HasiDog”",
|
"characters": [
|
||||||
"en": "Bunny \"HasiDog\"",
|
{
|
||||||
"ch": "Hase „HasiDog”",
|
"slug": "hasidog",
|
||||||
"fr": "Lapin « HasiDog »"
|
"name": { "de": "HasiDog", "en": "HasiDog", "ch": "HasiDog", "fr": "HasiDog" },
|
||||||
},
|
|
||||||
"vorstellung": {
|
"vorstellung": {
|
||||||
"de": "Hallo, ich bin HasiDog.\nIch bin klein, knuffig, wuschelig und verspielt, aber auch ein großer, frecher Abenteurer.",
|
"de": "Hallo, ich bin HasiDog.\nIch bin klein, knuffig, wuschelig und verspielt, aber auch ein großer, frecher Abenteurer.",
|
||||||
"en": "Hello, I'm HasiDog.\nI'm small, cuddly, fluffy and playful — but also a big, cheeky adventurer.",
|
"en": "Hello, I'm HasiDog.\nI'm small, cuddly, fluffy and playful — but also a big, cheeky adventurer.",
|
||||||
@@ -47,13 +47,8 @@
|
|||||||
"mehrLink": "https://dogfather-universe.dogfather1608.workers.dev/hasidog.html"
|
"mehrLink": "https://dogfather-universe.dogfather1608.workers.dev/hasidog.html"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "hase-hasigott",
|
"slug": "hasigott",
|
||||||
"name": {
|
"name": { "de": "HasiGott", "en": "HasiGott", "ch": "HasiGott", "fr": "HasiGott" },
|
||||||
"de": "Hase „HasiGott”",
|
|
||||||
"en": "Bunny \"HasiGott\"",
|
|
||||||
"ch": "Hase „HasiGott”",
|
|
||||||
"fr": "Lapin « HasiGott »"
|
|
||||||
},
|
|
||||||
"vorstellung": {
|
"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.",
|
"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.",
|
"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.",
|
||||||
@@ -62,73 +57,72 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "hase-mama",
|
"slug": "mama",
|
||||||
"name": {
|
"name": { "de": "Mama Hase", "en": "Mama Hase", "ch": "Mama Hase", "fr": "Maman Lapine" },
|
||||||
"de": "Hase „Mama Hase”",
|
|
||||||
"en": "Bunny \"Mama Hase\"",
|
|
||||||
"ch": "Hase „Mama Hase”",
|
|
||||||
"fr": "Lapin « Maman Lapine »"
|
|
||||||
},
|
|
||||||
"vorstellung": {
|
"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.",
|
"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.",
|
"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.",
|
"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."
|
"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",
|
"slug": "wal",
|
||||||
"name": {
|
"name": { "de": "Wal", "en": "Whale", "ch": "Wal", "fr": "Baleine" },
|
||||||
"de": "Wal „Sam”",
|
"characters": [
|
||||||
"en": "Whale \"Sam\"",
|
{
|
||||||
"ch": "Wal „Sam”",
|
"slug": "sam",
|
||||||
"fr": "Baleine « Sam »"
|
"name": { "de": "Sam", "en": "Sam", "ch": "Sam", "fr": "Sam" },
|
||||||
},
|
|
||||||
"vorstellung": {
|
"vorstellung": {
|
||||||
"de": "Hallo, ich bin Sam.\nIch träume von großen Abenteuern im tiefblauen Meer und trage dabei unzählige Geschichten in meinem Herzen. Egal wohin die Reise geht – ich schwimme als treuer Freund immer an deiner Seite.",
|
"de": "Hallo, ich bin Sam.\nIch träume von großen Abenteuern im tiefblauen Meer und trage dabei unzählige Geschichten in meinem Herzen. Egal wohin die Reise geht – ich schwimme als treuer Freund immer an deiner Seite.",
|
||||||
"en": "Hello, I'm Sam.\nI dream of great adventures in the deep blue sea, carrying countless stories in my heart. Wherever the journey leads, I'll always be swimming loyally by your side.",
|
"en": "Hello, I'm Sam.\nI dream of great adventures in the deep blue sea, carrying countless stories in my heart. Wherever the journey leads, I'll always be swimming loyally by your side.",
|
||||||
"ch": "Hoi, ig bi dr Sam.\nIg treume vo grosse Abentür im tiefblaue Meer und trage derbi zahllosi Gschichte i mim Härz. Wo o immer d'Reis higoht – ig schwimme als treue Fründ immer a dinere Site.",
|
"ch": "Hoi, ig bi dr Sam.\nIg treume vo grosse Abentür im tiefblaue Meer und trage derbi zahllosi Gschichte i mim Härz. Wo o immer d'Reis higoht – ig schwimme als treue Fründ immer a dinere Site.",
|
||||||
"fr": "Bonjour, je suis Sam.\nJe rêve de grandes aventures dans les profondeurs bleues de l'océan, portant d'innombrables histoires dans mon cœur. Où que le voyage te mène, je nage toujours fidèlement à tes côtés."
|
"fr": "Bonjour, je suis Sam.\nJe rêve de grandes aventures dans les profondeurs bleues de l'océan, portant d'innombrables histoires dans mon cœur. Où que le voyage te mène, je nage toujours fidèlement à tes côtés."
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "oktopus-siggi",
|
"slug": "oktopus",
|
||||||
"name": {
|
"name": { "de": "Oktopus", "en": "Octopus", "ch": "Oktopus", "fr": "Poulpe" },
|
||||||
"de": "Oktopus „Siggi”",
|
"characters": [
|
||||||
"en": "Octopus \"Siggi\"",
|
{
|
||||||
"ch": "Oktopus „Siggi”",
|
"slug": "siggi",
|
||||||
"fr": "Poulpe « Siggi »"
|
"name": { "de": "Siggi", "en": "Siggi", "ch": "Siggi", "fr": "Siggi" },
|
||||||
},
|
|
||||||
"vorstellung": {
|
"vorstellung": {
|
||||||
"de": "Hallo, ich bin Siggi.\nIch habe immer alle Arme voll zu tun, finde aber trotzdem Zeit für eine liebevolle Umarmung. Mit meiner fröhlichen Art zaubere ich dir selbst an grauen Tagen ein Lächeln ins Gesicht.",
|
"de": "Hallo, ich bin Siggi.\nIch habe immer alle Arme voll zu tun, finde aber trotzdem Zeit für eine liebevolle Umarmung. Mit meiner fröhlichen Art zaubere ich dir selbst an grauen Tagen ein Lächeln ins Gesicht.",
|
||||||
"en": "Hello, I'm Siggi.\nMy arms are always full, but I still find time for a loving hug. With my cheerful nature, I can bring a smile to your face even on grey days.",
|
"en": "Hello, I'm Siggi.\nMy arms are always full, but I still find time for a loving hug. With my cheerful nature, I can bring a smile to your face even on grey days.",
|
||||||
"ch": "Hoi, ig bi dr Siggi.\nIg ha immer alli Arm voll z'tue, find aber trotzdäm Zit für e liebevolli Umarmig. Mit minere fröhleche Art zauberi der sogar a graue Täg es Lächle is Gsicht.",
|
"ch": "Hoi, ig bi dr Siggi.\nIg ha immer alli Arm voll z'tue, find aber trotzdäm Zit für e liebevolli Umarmig. Mit minere fröhleche Art zauberi der sogar a graue Täg es Lächle is Gsicht.",
|
||||||
"fr": "Bonjour, je suis Siggi.\nJ'ai toujours les bras occupés, mais je trouve quand même le temps pour un câlin plein de tendresse. Avec ma bonne humeur, je fais naître un sourire même les jours gris."
|
"fr": "Bonjour, je suis Siggi.\nJ'ai toujours les bras occupés, mais je trouve quand même le temps pour un câlin plein de tendresse. Avec ma bonne humeur, je fais naître un sourire même les jours gris."
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "schildkroete-gaby",
|
"slug": "schildkroete",
|
||||||
"name": {
|
"name": { "de": "Schildkröte", "en": "Turtle", "ch": "Schildchröte", "fr": "Tortue" },
|
||||||
"de": "Schildkröte „Gaby”",
|
"characters": [
|
||||||
"en": "Turtle \"Gaby\"",
|
{
|
||||||
"ch": "Schildchröte „Gaby”",
|
"slug": "gaby",
|
||||||
"fr": "Tortue « Gaby »"
|
"name": { "de": "Gaby", "en": "Gaby", "ch": "Gaby", "fr": "Gaby" },
|
||||||
},
|
|
||||||
"vorstellung": {
|
"vorstellung": {
|
||||||
"de": "Hallo, ich bin Gaby.\nIch zeige, dass man nichts überstürzen muss, denn jedes Ziel erreicht man Schritt für Schritt. Mit meiner ruhigen Art schenke ich Geborgenheit und Gelassenheit.",
|
"de": "Hallo, ich bin Gaby.\nIch zeige, dass man nichts überstürzen muss, denn jedes Ziel erreicht man Schritt für Schritt. Mit meiner ruhigen Art schenke ich Geborgenheit und Gelassenheit.",
|
||||||
"en": "Hello, I'm Gaby.\nI show that there's no need to rush — every goal is reached step by step. With my calm nature, I bring comfort and ease.",
|
"en": "Hello, I'm Gaby.\nI show that there's no need to rush — every goal is reached step by step. With my calm nature, I bring comfort and ease.",
|
||||||
"ch": "Hoi, ig bi d'Gaby.\nIg zeig, dass me nüt überstürze muess, wills jedes Ziel Schritt für Schritt erreicht wird. Mit minere ruhige Art schänke ig Gborgeheit und Gelassehei.",
|
"ch": "Hoi, ig bi d'Gaby.\nIg zeig, dass me nüt überstürze muess, wills jedes Ziel Schritt für Schritt erreicht wird. Mit minere ruhige Art schänke ig Gborgeheit und Gelassehei.",
|
||||||
"fr": "Bonjour, je suis Gaby.\nJe montre qu'il ne faut rien précipiter, car chaque but s'atteint pas à pas. Avec mon calme, j'apporte réconfort et sérénité."
|
"fr": "Bonjour, je suis Gaby.\nJe montre qu'il ne faut rien précipiter, car chaque but s'atteint pas à pas. Avec mon calme, j'apporte réconfort et sérénité."
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "pinguin-pingi",
|
"slug": "pinguin",
|
||||||
"name": {
|
"name": { "de": "Pinguin", "en": "Penguin", "ch": "Pinguin", "fr": "Pingouin" },
|
||||||
"de": "Pinguin „Pingi”",
|
"characters": [
|
||||||
"en": "Penguin \"Pingi\"",
|
{
|
||||||
"ch": "Pinguin „Pingi”",
|
"slug": "pingi",
|
||||||
"fr": "Pingouin « Pingi »"
|
"name": { "de": "Pingi", "en": "Pingi", "ch": "Pingi", "fr": "Pingi" },
|
||||||
},
|
|
||||||
"vorstellung": {
|
"vorstellung": {
|
||||||
"de": "Hallo, ich bin Pingi.\nIch liebe Schnee, Eis und warme Umarmungen gleichermaßen. Ich beweise jeden Tag, dass ein großes Herz auch im kältesten Winter für Wärme sorgen kann.",
|
"de": "Hallo, ich bin Pingi.\nIch liebe Schnee, Eis und warme Umarmungen gleichermaßen. Ich beweise jeden Tag, dass ein großes Herz auch im kältesten Winter für Wärme sorgen kann.",
|
||||||
"en": "Hello, I'm Pingi.\nI love snow, ice, and warm hugs in equal measure. Every day I prove that a big heart can bring warmth, even in the coldest winter.",
|
"en": "Hello, I'm Pingi.\nI love snow, ice, and warm hugs in equal measure. Every day I prove that a big heart can bring warmth, even in the coldest winter.",
|
||||||
@@ -138,3 +132,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
},
|
},
|
||||||
"artikelnummer": "BAS-050",
|
"artikelnummer": "BAS-050",
|
||||||
"kategorie": "diy-plushies",
|
"kategorie": "diy-plushies",
|
||||||
"unterkategorie": "hase-hasidog",
|
"unterkategorie": "hase",
|
||||||
|
"charakter": "hasidog",
|
||||||
"preis": 25,
|
"preis": 25,
|
||||||
"preisAlt": 42,
|
"preisAlt": 42,
|
||||||
"bestand": 2,
|
"bestand": 2,
|
||||||
|
|||||||
+15
-3
@@ -8,16 +8,28 @@ import type { Locale } from "../i18n/config";
|
|||||||
|
|
||||||
type LocalizedText = Record<Locale, string>;
|
type LocalizedText = Record<Locale, string>;
|
||||||
|
|
||||||
export interface Subcategory {
|
// Ein einzelner Charakter innerhalb einer Tier-Familie (z.B. "HasiDog" innerhalb der
|
||||||
|
// Unterkategorie "Hase"). Jede Familie kann beliebig viele Charaktere haben — die Seite
|
||||||
|
// "wächst" automatisch mit, wenn VanVan hier neue Charaktere ergänzt.
|
||||||
|
export interface Character {
|
||||||
slug: string;
|
slug: string;
|
||||||
name: LocalizedText;
|
name: LocalizedText;
|
||||||
// Persönliche Vorstellung des Charakters (z.B. bei DIY-Plushies-Unterkategorien) — optional,
|
// Persönliche Vorstellung des Charakters — wird als eigene Box mit abwechselnder
|
||||||
// wird auf der Unterkategorie-Seite als eigene Box unter der Überschrift angezeigt.
|
// Foto-/Text-Seite auf der Unterkategorie-Seite angezeigt.
|
||||||
vorstellung?: LocalizedText;
|
vorstellung?: LocalizedText;
|
||||||
// Optionales Ziel für einen "Mehr über ..."-Button (z.B. HasiDog hat eine eigene Seite).
|
// Optionales Ziel für einen "Mehr über ..."-Button (z.B. HasiDog hat eine eigene Seite).
|
||||||
mehrLink?: string;
|
mehrLink?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Subcategory {
|
||||||
|
slug: string;
|
||||||
|
name: LocalizedText;
|
||||||
|
// Ein oder mehrere Charaktere dieser Tier-Familie — jeder bekommt seine eigene Box auf der
|
||||||
|
// Unterkategorie-Seite (Foto/Text abwechselnd links/rechts), verknüpft mit dem passenden
|
||||||
|
// Produkt über Product.charakter (siehe products.ts).
|
||||||
|
characters?: Character[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface Category {
|
export interface Category {
|
||||||
slug: string;
|
slug: string;
|
||||||
order: number;
|
order: number;
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ export interface Product {
|
|||||||
name: LocalizedText;
|
name: LocalizedText;
|
||||||
artikelnummer?: string;
|
artikelnummer?: string;
|
||||||
kategorie: string; // slug aus categories.ts
|
kategorie: string; // slug aus categories.ts
|
||||||
unterkategorie?: string; // optionaler Unterkategorie-Slug aus categories.ts
|
unterkategorie?: string; // optionaler Unterkategorie-Slug aus categories.ts (z.B. "hase")
|
||||||
|
// Ordnet das Produkt einem bestimmten Charakter innerhalb der Unterkategorie zu (z.B. "hasidog"
|
||||||
|
// innerhalb der Unterkategorie "hase") — so weiß die Seite, welches Foto/welcher Text zu
|
||||||
|
// welchem Charakter gehört. Leer lassen, wenn die Unterkategorie nur einen Charakter hat.
|
||||||
|
charakter?: string;
|
||||||
preis: number; // in Euro
|
preis: number; // in Euro
|
||||||
preisAlt?: number; // falls Sale
|
preisAlt?: number; // falls Sale
|
||||||
bestand: number; // 0 = ausverkauft, bleibt trotzdem sichtbar
|
bestand: number; // 0 = ausverkauft, bleibt trotzdem sichtbar
|
||||||
|
|||||||
@@ -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 category = getCategory(kategorie!)!;
|
||||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||||
|
|
||||||
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
|
||||||
const featured = echtProdukte[0];
|
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||||
const weitere = echtProdukte.slice(1);
|
|
||||||
|
|
||||||
// Innerhalb einer Tier-Familie (gleicher Slug-Präfix vor dem ersten "-", z.B. "hase") wechselt
|
// Jeder Charakter dieser Tier-Familie bekommt seine eigene Box, abwechselnd Foto links/rechts —
|
||||||
// die Foto-/Text-Seite ab: 1. Mitglied Foto links, 2. Foto rechts, 3. wieder links, usw. — sorgt
|
// 1. Charakter links, 2. rechts, 3. wieder links, usw. Sobald VanVan im Adminbereich neue
|
||||||
// für optische Abwechslung, sobald mehrere Varianten einer Serie eigene Seiten bekommen.
|
// Charaktere UND passende Produkte ergänzt, wächst diese Liste automatisch mit. Fehlt einem
|
||||||
const familyPrefix = sub.slug.split("-")[0];
|
// Charakter noch ein echtes Produkt, zeigt seine Box trotzdem schon den Charaktertext (nur ohne
|
||||||
const familyMembers = category.subcategories?.filter((s) => s.slug.split("-")[0] === familyPrefix) ?? [];
|
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
|
||||||
const familyIndex = familyMembers.findIndex((s) => s.slug === sub.slug);
|
const characters = sub.characters ?? [];
|
||||||
const introReverse = familyIndex % 2 === 1;
|
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}/`}>
|
<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">
|
<div class="container">
|
||||||
@@ -43,43 +50,40 @@ const introReverse = familyIndex % 2 === 1;
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{featured ? (
|
{characterEntries.length > 0 ? (
|
||||||
|
characterEntries.map(({ char, product, reverse }) => (
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class={`container grid grid-2 intro ${introReverse ? "intro-reverse" : ""}`}>
|
<div class={`container grid grid-2 intro ${reverse ? "intro-reverse" : ""}`}>
|
||||||
<a class="portrait-frame portrait" href={`/ch/produkt/${featured.slug}/`} aria-label={featured.name[lang]}>
|
{product ? (
|
||||||
{featured.bilder && featured.bilder.length > 0 ? (
|
<a class="portrait-frame portrait" href={`/ch/produkt/${product.slug}/`} aria-label={product.name[lang]}>
|
||||||
<img class="product-photo" src={featured.bilder[0]} alt={featured.name[lang]} loading="lazy" />
|
{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={featured.name[lang]}></div>
|
<div class="img-placeholder" role="img" aria-label={product.name[lang]}></div>
|
||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
|
) : (
|
||||||
|
<div class="portrait-frame portrait">
|
||||||
|
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div class="intro-text">
|
<div class="intro-text">
|
||||||
{sub.vorstellung && <span class="eyebrow">{sub.vorstellung[lang].split("\n")[0]}</span>}
|
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
|
||||||
<h2><a href={`/ch/produkt/${featured.slug}/`}>{featured.name[lang]}</a></h2>
|
<h2>{product ? <a href={`/ch/produkt/${product.slug}/`}>{product.name[lang]}</a> : char.name[lang]}</h2>
|
||||||
<p class="lead">{sub.vorstellung ? sub.vorstellung[lang].split("\n")[1] : featured.beschreibung[lang]}</p>
|
<p class="lead">{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : (product ? product.beschreibung[lang] : t.shop.subcategoryEmpty)}</p>
|
||||||
{sub.mehrLink && (
|
{char.mehrLink && (
|
||||||
<div class="character-more">
|
<div class="character-more">
|
||||||
<p>Wenn du meh über mich wüsse witt, de klick</p>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
))
|
||||||
) : (
|
) : (
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{sub.vorstellung && (
|
|
||||||
<div class="character-intro">
|
|
||||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</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>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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 category = getCategory(kategorie!)!;
|
||||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||||
|
|
||||||
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
|
||||||
const featured = echtProdukte[0];
|
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||||
const weitere = echtProdukte.slice(1);
|
|
||||||
|
|
||||||
// Innerhalb einer Tier-Familie (gleicher Slug-Präfix vor dem ersten "-", z.B. "hase") wechselt
|
// Jeder Charakter dieser Tier-Familie bekommt seine eigene Box, abwechselnd Foto links/rechts —
|
||||||
// die Foto-/Text-Seite ab: 1. Mitglied Foto links, 2. Foto rechts, 3. wieder links, usw. — sorgt
|
// 1. Charakter links, 2. rechts, 3. wieder links, usw. Sobald VanVan im Adminbereich neue
|
||||||
// für optische Abwechslung, sobald mehrere Varianten einer Serie eigene Seiten bekommen.
|
// Charaktere UND passende Produkte ergänzt, wächst diese Liste automatisch mit. Fehlt einem
|
||||||
const familyPrefix = sub.slug.split("-")[0];
|
// Charakter noch ein echtes Produkt, zeigt seine Box trotzdem schon den Charaktertext (nur ohne
|
||||||
const familyMembers = category.subcategories?.filter((s) => s.slug.split("-")[0] === familyPrefix) ?? [];
|
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
|
||||||
const familyIndex = familyMembers.findIndex((s) => s.slug === sub.slug);
|
const characters = sub.characters ?? [];
|
||||||
const introReverse = familyIndex % 2 === 1;
|
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}/`}>
|
<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">
|
<div class="container">
|
||||||
@@ -43,43 +50,40 @@ const introReverse = familyIndex % 2 === 1;
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{featured ? (
|
{characterEntries.length > 0 ? (
|
||||||
|
characterEntries.map(({ char, product, reverse }) => (
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class={`container grid grid-2 intro ${introReverse ? "intro-reverse" : ""}`}>
|
<div class={`container grid grid-2 intro ${reverse ? "intro-reverse" : ""}`}>
|
||||||
<a class="portrait-frame portrait" href={`/en/produkt/${featured.slug}/`} aria-label={featured.name[lang]}>
|
{product ? (
|
||||||
{featured.bilder && featured.bilder.length > 0 ? (
|
<a class="portrait-frame portrait" href={`/en/produkt/${product.slug}/`} aria-label={product.name[lang]}>
|
||||||
<img class="product-photo" src={featured.bilder[0]} alt={featured.name[lang]} loading="lazy" />
|
{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={featured.name[lang]}></div>
|
<div class="img-placeholder" role="img" aria-label={product.name[lang]}></div>
|
||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
|
) : (
|
||||||
|
<div class="portrait-frame portrait">
|
||||||
|
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div class="intro-text">
|
<div class="intro-text">
|
||||||
{sub.vorstellung && <span class="eyebrow">{sub.vorstellung[lang].split("\n")[0]}</span>}
|
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
|
||||||
<h2><a href={`/en/produkt/${featured.slug}/`}>{featured.name[lang]}</a></h2>
|
<h2>{product ? <a href={`/en/produkt/${product.slug}/`}>{product.name[lang]}</a> : char.name[lang]}</h2>
|
||||||
<p class="lead">{sub.vorstellung ? sub.vorstellung[lang].split("\n")[1] : featured.beschreibung[lang]}</p>
|
<p class="lead">{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : (product ? product.beschreibung[lang] : t.shop.subcategoryEmpty)}</p>
|
||||||
{sub.mehrLink && (
|
{char.mehrLink && (
|
||||||
<div class="character-more">
|
<div class="character-more">
|
||||||
<p>If you'd like to learn more about me, just click</p>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
))
|
||||||
) : (
|
) : (
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{sub.vorstellung && (
|
|
||||||
<div class="character-intro">
|
|
||||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</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>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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 category = getCategory(kategorie!)!;
|
||||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||||
|
|
||||||
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
|
||||||
const featured = echtProdukte[0];
|
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||||
const weitere = echtProdukte.slice(1);
|
|
||||||
|
|
||||||
// Innerhalb einer Tier-Familie (gleicher Slug-Präfix vor dem ersten "-", z.B. "hase") wechselt
|
// Jeder Charakter dieser Tier-Familie bekommt seine eigene Box, abwechselnd Foto links/rechts —
|
||||||
// die Foto-/Text-Seite ab: 1. Mitglied Foto links, 2. Foto rechts, 3. wieder links, usw. — sorgt
|
// 1. Charakter links, 2. rechts, 3. wieder links, usw. Sobald VanVan im Adminbereich neue
|
||||||
// für optische Abwechslung, sobald mehrere Varianten einer Serie eigene Seiten bekommen.
|
// Charaktere UND passende Produkte ergänzt, wächst diese Liste automatisch mit. Fehlt einem
|
||||||
const familyPrefix = sub.slug.split("-")[0];
|
// Charakter noch ein echtes Produkt, zeigt seine Box trotzdem schon den Charaktertext (nur ohne
|
||||||
const familyMembers = category.subcategories?.filter((s) => s.slug.split("-")[0] === familyPrefix) ?? [];
|
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
|
||||||
const familyIndex = familyMembers.findIndex((s) => s.slug === sub.slug);
|
const characters = sub.characters ?? [];
|
||||||
const introReverse = familyIndex % 2 === 1;
|
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}/`}>
|
<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">
|
<div class="container">
|
||||||
@@ -43,43 +50,40 @@ const introReverse = familyIndex % 2 === 1;
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{featured ? (
|
{characterEntries.length > 0 ? (
|
||||||
|
characterEntries.map(({ char, product, reverse }) => (
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class={`container grid grid-2 intro ${introReverse ? "intro-reverse" : ""}`}>
|
<div class={`container grid grid-2 intro ${reverse ? "intro-reverse" : ""}`}>
|
||||||
<a class="portrait-frame portrait" href={`/fr/produkt/${featured.slug}/`} aria-label={featured.name[lang]}>
|
{product ? (
|
||||||
{featured.bilder && featured.bilder.length > 0 ? (
|
<a class="portrait-frame portrait" href={`/fr/produkt/${product.slug}/`} aria-label={product.name[lang]}>
|
||||||
<img class="product-photo" src={featured.bilder[0]} alt={featured.name[lang]} loading="lazy" />
|
{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={featured.name[lang]}></div>
|
<div class="img-placeholder" role="img" aria-label={product.name[lang]}></div>
|
||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
|
) : (
|
||||||
|
<div class="portrait-frame portrait">
|
||||||
|
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div class="intro-text">
|
<div class="intro-text">
|
||||||
{sub.vorstellung && <span class="eyebrow">{sub.vorstellung[lang].split("\n")[0]}</span>}
|
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
|
||||||
<h2><a href={`/fr/produkt/${featured.slug}/`}>{featured.name[lang]}</a></h2>
|
<h2>{product ? <a href={`/fr/produkt/${product.slug}/`}>{product.name[lang]}</a> : char.name[lang]}</h2>
|
||||||
<p class="lead">{sub.vorstellung ? sub.vorstellung[lang].split("\n")[1] : featured.beschreibung[lang]}</p>
|
<p class="lead">{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : (product ? product.beschreibung[lang] : t.shop.subcategoryEmpty)}</p>
|
||||||
{sub.mehrLink && (
|
{char.mehrLink && (
|
||||||
<div class="character-more">
|
<div class="character-more">
|
||||||
<p>Si tu veux en savoir plus sur moi, clique</p>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
))
|
||||||
) : (
|
) : (
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{sub.vorstellung && (
|
|
||||||
<div class="character-intro">
|
|
||||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</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>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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 category = getCategory(kategorie!)!;
|
||||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||||
|
|
||||||
// Sobald VanVan im Adminbereich ein echtes Produkt dieser Unterkategorie zuordnet, wird es
|
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
|
||||||
// hier automatisch featured angezeigt (echtes Foto + ihr echter Text) statt des generischen
|
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
|
||||||
// Platzhaltertexts.
|
|
||||||
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
|
// Jeder Charakter dieser Tier-Familie bekommt seine eigene Box, abwechselnd Foto links/rechts —
|
||||||
// die Foto-/Text-Seite ab: 1. Mitglied Foto links, 2. Foto rechts, 3. wieder links, usw. — sorgt
|
// 1. Charakter links, 2. rechts, 3. wieder links, usw. Sobald VanVan im Adminbereich neue
|
||||||
// für optische Abwechslung, sobald mehrere Varianten einer Serie eigene Seiten bekommen.
|
// Charaktere UND passende Produkte ergänzt, wächst diese Liste automatisch mit. Fehlt einem
|
||||||
const familyPrefix = sub.slug.split("-")[0];
|
// Charakter noch ein echtes Produkt, zeigt seine Box trotzdem schon den Charaktertext (nur ohne
|
||||||
const familyMembers = category.subcategories?.filter((s) => s.slug.split("-")[0] === familyPrefix) ?? [];
|
// Foto) — die Seite muss nicht auf das erste Foto warten, um zu existieren.
|
||||||
const familyIndex = familyMembers.findIndex((s) => s.slug === sub.slug);
|
const characters = sub.characters ?? [];
|
||||||
const introReverse = familyIndex % 2 === 1;
|
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}/`}>
|
<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">
|
<div class="container">
|
||||||
@@ -46,43 +50,40 @@ const introReverse = familyIndex % 2 === 1;
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{featured ? (
|
{characterEntries.length > 0 ? (
|
||||||
|
characterEntries.map(({ char, product, reverse }) => (
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class={`container grid grid-2 intro ${introReverse ? "intro-reverse" : ""}`}>
|
<div class={`container grid grid-2 intro ${reverse ? "intro-reverse" : ""}`}>
|
||||||
<a class="portrait-frame portrait" href={`/produkt/${featured.slug}/`} aria-label={featured.name[lang]}>
|
{product ? (
|
||||||
{featured.bilder && featured.bilder.length > 0 ? (
|
<a class="portrait-frame portrait" href={`/produkt/${product.slug}/`} aria-label={product.name[lang]}>
|
||||||
<img class="product-photo" src={featured.bilder[0]} alt={featured.name[lang]} loading="lazy" />
|
{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={featured.name[lang]}></div>
|
<div class="img-placeholder" role="img" aria-label={product.name[lang]}></div>
|
||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
|
) : (
|
||||||
|
<div class="portrait-frame portrait">
|
||||||
|
<div class="img-placeholder" role="img" aria-label={char.name[lang]}></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div class="intro-text">
|
<div class="intro-text">
|
||||||
{sub.vorstellung && <span class="eyebrow">{sub.vorstellung[lang].split("\n")[0]}</span>}
|
{char.vorstellung && <span class="eyebrow">{char.vorstellung[lang].split("\n")[0]}</span>}
|
||||||
<h2><a href={`/produkt/${featured.slug}/`}>{featured.name[lang]}</a></h2>
|
<h2>{product ? <a href={`/produkt/${product.slug}/`}>{product.name[lang]}</a> : char.name[lang]}</h2>
|
||||||
<p class="lead">{sub.vorstellung ? sub.vorstellung[lang].split("\n")[1] : featured.beschreibung[lang]}</p>
|
<p class="lead">{char.vorstellung ? char.vorstellung[lang].split("\n")[1] : (product ? product.beschreibung[lang] : t.shop.subcategoryEmpty)}</p>
|
||||||
{sub.mehrLink && (
|
{char.mehrLink && (
|
||||||
<div class="character-more">
|
<div class="character-more">
|
||||||
<p>Wenn du mehr über mich erfahren möchtest, dann klicke</p>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
))
|
||||||
) : (
|
) : (
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{sub.vorstellung && (
|
|
||||||
<div class="character-intro">
|
|
||||||
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</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>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user