Anpassungen am bestehenden Webshop laut PDF-Vorgabe
- Persönliche Vorstellung jedes DIY-Plushies-Charakters auf der jeweiligen Unterkategorie-Seite (dezente Box im Kartenstil), inkl. eigener HasiDog-Seite mit "Mehr über HasiDog"-Button - Beschreibungstexte "DIY Plushies" und "Häkelzubehör" angepasst (Startseite + Kategorieseiten, gemeinsame Datenquelle) - Shop-Filter erweitert: Unterkategorien jetzt gruppiert unter ihrer Hauptkategorie wählbar, inkl. Filterlogik - Shop-Bannerbild jetzt auch auf allen Kategorie- und Unterkategorie-Seiten, gleiche Größe/Abstände/Responsivität wie im Shop - Keine Design-Änderung: bestehende Farben, Typografie, Karten, Buttons, Abstände vollständig erhalten, alles fügt sich nahtlos ins bestehende Layout ein - Alle Änderungen in DE/EN/CH/FR umgesetzt, Link-Audit durchgeführt
This commit is contained in:
@@ -45,6 +45,11 @@ collections:
|
|||||||
search_fields: ["name.de"]
|
search_fields: ["name.de"]
|
||||||
value_field: "slug"
|
value_field: "slug"
|
||||||
display_fields: ["name.de"]
|
display_fields: ["name.de"]
|
||||||
|
- label: "Unterkategorie (optional)"
|
||||||
|
name: unterkategorie
|
||||||
|
widget: string
|
||||||
|
required: false
|
||||||
|
hint: "Interner Code der Unterkategorie eintragen, z.B. kuh-frieda (siehe Kategorien-Bereich)"
|
||||||
- { label: "Preis (in Euro)", name: preis, widget: number, value_type: float, step: 0.01 }
|
- { label: "Preis (in Euro)", name: preis, widget: number, value_type: float, step: 0.01 }
|
||||||
- label: "Alter Preis (nur ausfüllen, wenn im Sale)"
|
- label: "Alter Preis (nur ausfüllen, wenn im Sale)"
|
||||||
name: preisAlt
|
name: preisAlt
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
"fr": "Peluches DIY"
|
"fr": "Peluches DIY"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"de": "Handgenähte Kuscheltiere und Plüschfiguren – jedes Stück ein Unikat.",
|
"de": "Handgefertigte Kuscheltiere und Plüschfiguren – jedes Stück ein Unikat.",
|
||||||
"en": "Hand-sewn stuffed animals and plush figures – every piece a one-of-a-kind.",
|
"en": "Handcrafted stuffed animals and plush figures – every piece a one-of-a-kind.",
|
||||||
"ch": "Vo Hang gnähti Kuscheltier und Plüschfigure – jedes Sächeli es Unikat.",
|
"ch": "Vo Hang gmachti Kuscheltier und Plüschfigure – jedes Sächeli es Unikat.",
|
||||||
"fr": "Peluches et figurines cousues à la main – chaque pièce est unique."
|
"fr": "Peluches et figurines fabriquées à la main – chaque pièce est unique."
|
||||||
},
|
},
|
||||||
"icon": "🧸",
|
"icon": "🧸",
|
||||||
"subcategories": [
|
"subcategories": [
|
||||||
@@ -22,6 +22,12 @@
|
|||||||
"en": "Cow \"Frieda\"",
|
"en": "Cow \"Frieda\"",
|
||||||
"ch": "Kuh „Frieda”",
|
"ch": "Kuh „Frieda”",
|
||||||
"fr": "Vache « Frieda »"
|
"fr": "Vache « Frieda »"
|
||||||
|
},
|
||||||
|
"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.",
|
||||||
|
"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.",
|
||||||
|
"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."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -31,7 +37,14 @@
|
|||||||
"en": "Bunny \"HasiDog\"",
|
"en": "Bunny \"HasiDog\"",
|
||||||
"ch": "Hase „HasiDog”",
|
"ch": "Hase „HasiDog”",
|
||||||
"fr": "Lapin « HasiDog »"
|
"fr": "Lapin « HasiDog »"
|
||||||
}
|
},
|
||||||
|
"vorstellung": {
|
||||||
|
"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.",
|
||||||
|
"ch": "Hoi, ig bi dr HasiDog.\nIg bi chli, härzig, wuschelig und verspielt, aber o en grosse, freche Abentürer.",
|
||||||
|
"fr": "Bonjour, je suis HasiDog.\nJe suis petit, tout doux, ébouriffé et joueur — mais aussi un grand aventurier un brin coquin."
|
||||||
|
},
|
||||||
|
"mehrLink": "/hasidog/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "wal-sam",
|
"slug": "wal-sam",
|
||||||
@@ -40,6 +53,12 @@
|
|||||||
"en": "Whale \"Sam\"",
|
"en": "Whale \"Sam\"",
|
||||||
"ch": "Wal „Sam”",
|
"ch": "Wal „Sam”",
|
||||||
"fr": "Baleine « Sam »"
|
"fr": "Baleine « Sam »"
|
||||||
|
},
|
||||||
|
"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.",
|
||||||
|
"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.",
|
||||||
|
"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."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -49,6 +68,12 @@
|
|||||||
"en": "Octopus \"Siggi\"",
|
"en": "Octopus \"Siggi\"",
|
||||||
"ch": "Oktopus „Siggi”",
|
"ch": "Oktopus „Siggi”",
|
||||||
"fr": "Poulpe « Siggi »"
|
"fr": "Poulpe « Siggi »"
|
||||||
|
},
|
||||||
|
"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.",
|
||||||
|
"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.",
|
||||||
|
"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."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -58,6 +83,12 @@
|
|||||||
"en": "Turtle \"Gaby\"",
|
"en": "Turtle \"Gaby\"",
|
||||||
"ch": "Schildchröte „Gaby”",
|
"ch": "Schildchröte „Gaby”",
|
||||||
"fr": "Tortue « Gaby »"
|
"fr": "Tortue « Gaby »"
|
||||||
|
},
|
||||||
|
"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.",
|
||||||
|
"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.",
|
||||||
|
"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é."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,6 +98,12 @@
|
|||||||
"en": "Penguin \"Pingi\"",
|
"en": "Penguin \"Pingi\"",
|
||||||
"ch": "Pinguin „Pingi”",
|
"ch": "Pinguin „Pingi”",
|
||||||
"fr": "Pingouin « Pingi »"
|
"fr": "Pingouin « Pingi »"
|
||||||
|
},
|
||||||
|
"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.",
|
||||||
|
"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.",
|
||||||
|
"ch": "Hoi, ig bi dr Pingi.\nIg lieb Schnee, Eis und warmi Umarmige glich gärn. Ig bewis jede Tag, dass es grosses Härz o im chalteschte Winter für Wärmi sorge cha.",
|
||||||
|
"fr": "Bonjour, je suis Pingi.\nJ'aime autant la neige et la glace que les câlins tout doux. Chaque jour, je prouve qu'un grand cœur peut réchauffer même l'hiver le plus froid."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
"fr": "Fournitures de crochet"
|
"fr": "Fournitures de crochet"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"de": "Nadeln, Garne und alles, was dein nächstes Häkelprojekt braucht.",
|
"de": "Maschenmarkierer und vieles, was dein nächstes Häkelprojekt benötigt.",
|
||||||
"en": "Hooks, yarn and everything your next crochet project needs.",
|
"en": "Stitch markers and everything else your next crochet project needs.",
|
||||||
"ch": "Nadle, Garn und alles, wo's für dis nächschte Häkelprojekt bruucht.",
|
"ch": "Masche-Markierer und viel, wo's für dis nächschte Häkelprojekt bruucht.",
|
||||||
"fr": "Crochets, fils et tout ce qu'il faut pour votre prochain projet."
|
"fr": "Marqueurs de mailles et tout ce qu'il faut pour votre prochain projet au crochet."
|
||||||
},
|
},
|
||||||
"icon": "🪡",
|
"icon": "🪡",
|
||||||
"subcategories": [
|
"subcategories": [
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ type LocalizedText = Record<Locale, string>;
|
|||||||
export interface Subcategory {
|
export interface Subcategory {
|
||||||
slug: string;
|
slug: string;
|
||||||
name: LocalizedText;
|
name: LocalizedText;
|
||||||
|
// Persönliche Vorstellung des Charakters (z.B. bei DIY-Plushies-Unterkategorien) — optional,
|
||||||
|
// wird auf der Unterkategorie-Seite als eigene Box unter der Überschrift angezeigt.
|
||||||
|
vorstellung?: LocalizedText;
|
||||||
|
// Optionales Ziel für einen "Mehr über ..."-Button (z.B. HasiDog hat eine eigene Seite).
|
||||||
|
mehrLink?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Category {
|
export interface Category {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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
|
||||||
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
|
||||||
|
|||||||
+4
-4
@@ -41,7 +41,7 @@ export const ui = {
|
|||||||
sortNew: "Neu", sortBestseller: "Bestseller", sortPriceAsc: "Preis aufsteigend", sortPriceDesc: "Preis absteigend",
|
sortNew: "Neu", sortBestseller: "Bestseller", sortPriceAsc: "Preis aufsteigend", sortPriceDesc: "Preis absteigend",
|
||||||
resultCount: (n: number) => `${n} Produkt${n === 1 ? "" : "e"}`, noResults: "Keine Produkte gefunden – bitte Filter anpassen.",
|
resultCount: (n: number) => `${n} Produkt${n === 1 ? "" : "e"}`, noResults: "Keine Produkte gefunden – bitte Filter anpassen.",
|
||||||
breadcrumbShop: "Shop", categoryEmpty: "In dieser Kategorie sind aktuell noch keine Produkte hinterlegt – schau bald wieder vorbei.",
|
breadcrumbShop: "Shop", categoryEmpty: "In dieser Kategorie sind aktuell noch keine Produkte hinterlegt – schau bald wieder vorbei.",
|
||||||
categoryLabel: "Kategorie", productSingular: "Produkt", productPlural: "Produkte", sortLabel: "Sortierung", subcategoriesLabel: "Unterkategorien", subcategoryEmpty: "Für diesen Bereich stellt VanVan bald die ersten Produkte ein – schau bald wieder vorbei.",
|
categoryLabel: "Kategorie", productSingular: "Produkt", productPlural: "Produkte", sortLabel: "Sortierung", subcategoriesLabel: "Unterkategorien", subcategoryEmpty: "Für diesen Bereich stellt VanVan bald die ersten Produkte ein – schau bald wieder vorbei.", moreAbout: "Mehr über",
|
||||||
},
|
},
|
||||||
sale: {
|
sale: {
|
||||||
eyebrow: "% Sale", title: "Aktuelle Angebote", lead: "Zeitlich begrenzte Rabatte auf ausgewählte handgemachte Stücke.",
|
eyebrow: "% Sale", title: "Aktuelle Angebote", lead: "Zeitlich begrenzte Rabatte auf ausgewählte handgemachte Stücke.",
|
||||||
@@ -210,7 +210,7 @@ export const ui = {
|
|||||||
sortNew: "New", sortBestseller: "Bestseller", sortPriceAsc: "Price: low to high", sortPriceDesc: "Price: high to low",
|
sortNew: "New", sortBestseller: "Bestseller", sortPriceAsc: "Price: low to high", sortPriceDesc: "Price: high to low",
|
||||||
resultCount: (n: number) => `${n} product${n === 1 ? "" : "s"}`, noResults: "No products found – please adjust your filters.",
|
resultCount: (n: number) => `${n} product${n === 1 ? "" : "s"}`, noResults: "No products found – please adjust your filters.",
|
||||||
breadcrumbShop: "Shop", categoryEmpty: "No products in this category yet – check back soon.",
|
breadcrumbShop: "Shop", categoryEmpty: "No products in this category yet – check back soon.",
|
||||||
categoryLabel: "Category", productSingular: "product", productPlural: "products", sortLabel: "Sort by", subcategoriesLabel: "Subcategories", subcategoryEmpty: "VanVan will be adding the first products here soon – check back shortly.",
|
categoryLabel: "Category", productSingular: "product", productPlural: "products", sortLabel: "Sort by", subcategoriesLabel: "Subcategories", subcategoryEmpty: "VanVan will be adding the first products here soon – check back shortly.", moreAbout: "More about",
|
||||||
},
|
},
|
||||||
sale: {
|
sale: {
|
||||||
eyebrow: "% Sale", title: "Current offers", lead: "Time-limited discounts on selected handmade pieces.",
|
eyebrow: "% Sale", title: "Current offers", lead: "Time-limited discounts on selected handmade pieces.",
|
||||||
@@ -379,7 +379,7 @@ export const ui = {
|
|||||||
sortNew: "Neu", sortBestseller: "Bestseller", sortPriceAsc: "Pris ufsteigend", sortPriceDesc: "Pris absteigend",
|
sortNew: "Neu", sortBestseller: "Bestseller", sortPriceAsc: "Pris ufsteigend", sortPriceDesc: "Pris absteigend",
|
||||||
resultCount: (n: number) => `${n} Produkt`, noResults: "Kei Produkt gfunde – bitte Filter apasse.",
|
resultCount: (n: number) => `${n} Produkt`, noResults: "Kei Produkt gfunde – bitte Filter apasse.",
|
||||||
breadcrumbShop: "Shop", categoryEmpty: "In däre Kategorie sind grad no kei Produkt hinterlegt – lueg spöter nomal verbi.",
|
breadcrumbShop: "Shop", categoryEmpty: "In däre Kategorie sind grad no kei Produkt hinterlegt – lueg spöter nomal verbi.",
|
||||||
categoryLabel: "Kategorie", productSingular: "Produkt", productPlural: "Produkt", sortLabel: "Sortiere", subcategoriesLabel: "Unterkategorie", subcategoryEmpty: "Für das da stellt VanVan bald die erschte Produkt i – lueg bald wieder verbi.",
|
categoryLabel: "Kategorie", productSingular: "Produkt", productPlural: "Produkt", sortLabel: "Sortiere", subcategoriesLabel: "Unterkategorie", subcategoryEmpty: "Für das da stellt VanVan bald die erschte Produkt i – lueg bald wieder verbi.", moreAbout: "Meh über",
|
||||||
},
|
},
|
||||||
sale: {
|
sale: {
|
||||||
eyebrow: "% Aktion", title: "Aktuelli Aktion", lead: "Zytlich begrenzti Rabatt uf uswählti, vo Hang gmachti Sächeli.",
|
eyebrow: "% Aktion", title: "Aktuelli Aktion", lead: "Zytlich begrenzti Rabatt uf uswählti, vo Hang gmachti Sächeli.",
|
||||||
@@ -548,7 +548,7 @@ export const ui = {
|
|||||||
sortNew: "Nouveautés", sortBestseller: "Best-sellers", sortPriceAsc: "Prix croissant", sortPriceDesc: "Prix décroissant",
|
sortNew: "Nouveautés", sortBestseller: "Best-sellers", sortPriceAsc: "Prix croissant", sortPriceDesc: "Prix décroissant",
|
||||||
resultCount: (n: number) => `${n} produit${n === 1 ? "" : "s"}`, noResults: "Aucun produit trouvé – veuillez ajuster les filtres.",
|
resultCount: (n: number) => `${n} produit${n === 1 ? "" : "s"}`, noResults: "Aucun produit trouvé – veuillez ajuster les filtres.",
|
||||||
breadcrumbShop: "Boutique", categoryEmpty: "Aucun produit dans cette catégorie pour le moment – repassez bientôt.",
|
breadcrumbShop: "Boutique", categoryEmpty: "Aucun produit dans cette catégorie pour le moment – repassez bientôt.",
|
||||||
categoryLabel: "Catégorie", productSingular: "produit", productPlural: "produits", sortLabel: "Trier par", subcategoriesLabel: "Sous-catégories", subcategoryEmpty: "VanVan ajoutera bientôt les premiers produits ici – repassez bientôt.",
|
categoryLabel: "Catégorie", productSingular: "produit", productPlural: "produits", sortLabel: "Trier par", subcategoriesLabel: "Sous-catégories", subcategoryEmpty: "VanVan ajoutera bientôt les premiers produits ici – repassez bientôt.", moreAbout: "En savoir plus sur",
|
||||||
},
|
},
|
||||||
sale: {
|
sale: {
|
||||||
eyebrow: "% Soldes", title: "Offres actuelles", lead: "Réductions limitées dans le temps sur une sélection de pièces faites main.",
|
eyebrow: "% Soldes", title: "Offres actuelles", lead: "Réductions limitées dans le temps sur une sélection de pièces faites main.",
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
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>
|
||||||
@@ -17,14 +17,16 @@ const category = getCategory(kategorie!)!;
|
|||||||
const items = productsByCategory(category.slug);
|
const items = productsByCategory(category.slug);
|
||||||
---
|
---
|
||||||
<Layout title={category.name[lang]} description={category.description[lang]} lang={lang} path={`/shop/${category.slug}/`}>
|
<Layout title={category.name[lang]} description={category.description[lang]} lang={lang} path={`/shop/${category.slug}/`}>
|
||||||
<section class="section-tight">
|
<div class="container">
|
||||||
<div class="container">
|
<p class="small breadcrumb" style="margin-top:1.5rem;"><a href="/ch/shop/">{t.shop.breadcrumbShop}</a> / {category.name[lang]}</p>
|
||||||
<p class="small breadcrumb"><a href="/ch/shop/">{t.shop.breadcrumbShop}</a> / {category.name[lang]}</p>
|
<section class="page-banner" style="background-image:url('/img/banner-shop.webp'); --banner-ratio: 1600 / 729;">
|
||||||
<span class="eyebrow" aria-hidden="true">{category.icon}</span>
|
<div class="page-banner-content">
|
||||||
<h1>{category.name[lang]}</h1>
|
<span class="eyebrow" aria-hidden="true">{category.icon} {t.shop.breadcrumbShop}</span>
|
||||||
<p class="lead">{category.description[lang]}</p>
|
<h1>{category.name[lang]}</h1>
|
||||||
</div>
|
<p class="lead">{category.description[lang]}</p>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -17,22 +17,30 @@ const category = getCategory(kategorie!)!;
|
|||||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||||
---
|
---
|
||||||
<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}/`}>
|
||||||
<section class="section-tight">
|
<div class="container">
|
||||||
<div class="container">
|
<p class="small breadcrumb" style="margin-top:1.5rem;">
|
||||||
<p class="small breadcrumb">
|
<a href="/ch/shop/">{t.shop.breadcrumbShop}</a> / <a href={`/ch/shop/${category.slug}/`}>{category.name[lang]}</a> / {sub.name[lang]}
|
||||||
<a href="/ch/shop/">{t.shop.breadcrumbShop}</a> / <a href={`/ch/shop/${category.slug}/`}>{category.name[lang]}</a> / {sub.name[lang]}
|
</p>
|
||||||
</p>
|
<section class="page-banner" style="background-image:url('/img/banner-shop.webp'); --banner-ratio: 1600 / 729;">
|
||||||
<span class="eyebrow" aria-hidden="true">{category.icon}</span>
|
<div class="page-banner-content">
|
||||||
<h1>{sub.name[lang]}</h1>
|
<span class="eyebrow" aria-hidden="true">{category.icon} {category.name[lang]}</span>
|
||||||
</div>
|
<h1>{sub.name[lang]}</h1>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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>)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
|
{sub.mehrLink && <a class="btn btn-primary" href={`/ch${sub.mehrLink}`}>{t.shop.moreAbout} {sub.name[lang]} →</a>}
|
||||||
<a class="btn btn-outline" href={`/ch/shop/${category.slug}/`}>← {category.name[lang]}</a>
|
<a class="btn btn-outline" href={`/ch/shop/${category.slug}/`}>← {category.name[lang]}</a>
|
||||||
<a class="btn btn-primary" href="/ch/shop/">{t.common.toShop}</a>
|
<a class="btn btn-outline" href="/ch/shop/">{t.common.toShop}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -28,7 +28,16 @@ const t = useTranslations(lang);
|
|||||||
<label for="f-kategorie">{t.shop.categoryLabel}</label>
|
<label for="f-kategorie">{t.shop.categoryLabel}</label>
|
||||||
<select id="f-kategorie">
|
<select id="f-kategorie">
|
||||||
<option value="">{t.shop.allCategories}</option>
|
<option value="">{t.shop.allCategories}</option>
|
||||||
{categories.map((c) => <option value={c.slug}>{c.name[lang]}</option>)}
|
{categories.map((c) => (
|
||||||
|
c.subcategories && c.subcategories.length > 0 ? (
|
||||||
|
<optgroup label={c.name[lang]}>
|
||||||
|
<option value={c.slug}>{c.name[lang]} ({t.shop.allCategories})</option>
|
||||||
|
{c.subcategories.map((s) => <option value={`sub:${s.slug}`}>— {s.name[lang]}</option>)}
|
||||||
|
</optgroup>
|
||||||
|
) : (
|
||||||
|
<option value={c.slug}>{c.name[lang]}</option>
|
||||||
|
)
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
@@ -58,6 +67,7 @@ const t = useTranslations(lang);
|
|||||||
<div
|
<div
|
||||||
class="product-slot"
|
class="product-slot"
|
||||||
data-kategorie={p.kategorie}
|
data-kategorie={p.kategorie}
|
||||||
|
data-unterkategorie={p.unterkategorie ?? ""}
|
||||||
data-preis={p.preis}
|
data-preis={p.preis}
|
||||||
data-bestand={p.bestand}
|
data-bestand={p.bestand}
|
||||||
data-neu={p.badges.includes("neu") ? 1 : 0}
|
data-neu={p.badges.includes("neu") ? 1 : 0}
|
||||||
@@ -98,7 +108,11 @@ const t = useTranslations(lang);
|
|||||||
|
|
||||||
let visible = 0;
|
let visible = 0;
|
||||||
slots.forEach((slot) => {
|
slots.forEach((slot) => {
|
||||||
const matchKat = !kat || slot.dataset.kategorie === kat;
|
const matchKat = !kat
|
||||||
|
? true
|
||||||
|
: kat.startsWith("sub:")
|
||||||
|
? slot.dataset.unterkategorie === kat.slice(4)
|
||||||
|
: slot.dataset.kategorie === kat;
|
||||||
const matchPreis = Number(slot.dataset.preis) <= maxPreis;
|
const matchPreis = Number(slot.dataset.preis) <= maxPreis;
|
||||||
const matchVerfuegbar = !nurVerfuegbar || Number(slot.dataset.bestand) > 0;
|
const matchVerfuegbar = !nurVerfuegbar || Number(slot.dataset.bestand) > 0;
|
||||||
const show = matchKat && matchPreis && matchVerfuegbar;
|
const show = matchKat && matchPreis && matchVerfuegbar;
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
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>
|
||||||
@@ -17,14 +17,16 @@ const category = getCategory(kategorie!)!;
|
|||||||
const items = productsByCategory(category.slug);
|
const items = productsByCategory(category.slug);
|
||||||
---
|
---
|
||||||
<Layout title={category.name[lang]} description={category.description[lang]} lang={lang} path={`/shop/${category.slug}/`}>
|
<Layout title={category.name[lang]} description={category.description[lang]} lang={lang} path={`/shop/${category.slug}/`}>
|
||||||
<section class="section-tight">
|
<div class="container">
|
||||||
<div class="container">
|
<p class="small breadcrumb" style="margin-top:1.5rem;"><a href="/en/shop/">{t.shop.breadcrumbShop}</a> / {category.name[lang]}</p>
|
||||||
<p class="small breadcrumb"><a href="/en/shop/">{t.shop.breadcrumbShop}</a> / {category.name[lang]}</p>
|
<section class="page-banner" style="background-image:url('/img/banner-shop.webp'); --banner-ratio: 1600 / 729;">
|
||||||
<span class="eyebrow" aria-hidden="true">{category.icon}</span>
|
<div class="page-banner-content">
|
||||||
<h1>{category.name[lang]}</h1>
|
<span class="eyebrow" aria-hidden="true">{category.icon} {t.shop.breadcrumbShop}</span>
|
||||||
<p class="lead">{category.description[lang]}</p>
|
<h1>{category.name[lang]}</h1>
|
||||||
</div>
|
<p class="lead">{category.description[lang]}</p>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -17,22 +17,30 @@ const category = getCategory(kategorie!)!;
|
|||||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||||
---
|
---
|
||||||
<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}/`}>
|
||||||
<section class="section-tight">
|
<div class="container">
|
||||||
<div class="container">
|
<p class="small breadcrumb" style="margin-top:1.5rem;">
|
||||||
<p class="small breadcrumb">
|
<a href="/en/shop/">{t.shop.breadcrumbShop}</a> / <a href={`/en/shop/${category.slug}/`}>{category.name[lang]}</a> / {sub.name[lang]}
|
||||||
<a href="/en/shop/">{t.shop.breadcrumbShop}</a> / <a href={`/en/shop/${category.slug}/`}>{category.name[lang]}</a> / {sub.name[lang]}
|
</p>
|
||||||
</p>
|
<section class="page-banner" style="background-image:url('/img/banner-shop.webp'); --banner-ratio: 1600 / 729;">
|
||||||
<span class="eyebrow" aria-hidden="true">{category.icon}</span>
|
<div class="page-banner-content">
|
||||||
<h1>{sub.name[lang]}</h1>
|
<span class="eyebrow" aria-hidden="true">{category.icon} {category.name[lang]}</span>
|
||||||
</div>
|
<h1>{sub.name[lang]}</h1>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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>)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
|
{sub.mehrLink && <a class="btn btn-primary" href={`/en${sub.mehrLink}`}>{t.shop.moreAbout} {sub.name[lang]} →</a>}
|
||||||
<a class="btn btn-outline" href={`/en/shop/${category.slug}/`}>← {category.name[lang]}</a>
|
<a class="btn btn-outline" href={`/en/shop/${category.slug}/`}>← {category.name[lang]}</a>
|
||||||
<a class="btn btn-primary" href="/en/shop/">{t.common.toShop}</a>
|
<a class="btn btn-outline" href="/en/shop/">{t.common.toShop}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -28,7 +28,16 @@ const t = useTranslations(lang);
|
|||||||
<label for="f-kategorie">{t.shop.categoryLabel}</label>
|
<label for="f-kategorie">{t.shop.categoryLabel}</label>
|
||||||
<select id="f-kategorie">
|
<select id="f-kategorie">
|
||||||
<option value="">{t.shop.allCategories}</option>
|
<option value="">{t.shop.allCategories}</option>
|
||||||
{categories.map((c) => <option value={c.slug}>{c.name[lang]}</option>)}
|
{categories.map((c) => (
|
||||||
|
c.subcategories && c.subcategories.length > 0 ? (
|
||||||
|
<optgroup label={c.name[lang]}>
|
||||||
|
<option value={c.slug}>{c.name[lang]} ({t.shop.allCategories})</option>
|
||||||
|
{c.subcategories.map((s) => <option value={`sub:${s.slug}`}>— {s.name[lang]}</option>)}
|
||||||
|
</optgroup>
|
||||||
|
) : (
|
||||||
|
<option value={c.slug}>{c.name[lang]}</option>
|
||||||
|
)
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
@@ -58,6 +67,7 @@ const t = useTranslations(lang);
|
|||||||
<div
|
<div
|
||||||
class="product-slot"
|
class="product-slot"
|
||||||
data-kategorie={p.kategorie}
|
data-kategorie={p.kategorie}
|
||||||
|
data-unterkategorie={p.unterkategorie ?? ""}
|
||||||
data-preis={p.preis}
|
data-preis={p.preis}
|
||||||
data-bestand={p.bestand}
|
data-bestand={p.bestand}
|
||||||
data-neu={p.badges.includes("neu") ? 1 : 0}
|
data-neu={p.badges.includes("neu") ? 1 : 0}
|
||||||
@@ -98,7 +108,11 @@ const t = useTranslations(lang);
|
|||||||
|
|
||||||
let visible = 0;
|
let visible = 0;
|
||||||
slots.forEach((slot) => {
|
slots.forEach((slot) => {
|
||||||
const matchKat = !kat || slot.dataset.kategorie === kat;
|
const matchKat = !kat
|
||||||
|
? true
|
||||||
|
: kat.startsWith("sub:")
|
||||||
|
? slot.dataset.unterkategorie === kat.slice(4)
|
||||||
|
: slot.dataset.kategorie === kat;
|
||||||
const matchPreis = Number(slot.dataset.preis) <= maxPreis;
|
const matchPreis = Number(slot.dataset.preis) <= maxPreis;
|
||||||
const matchVerfuegbar = !nurVerfuegbar || Number(slot.dataset.bestand) > 0;
|
const matchVerfuegbar = !nurVerfuegbar || Number(slot.dataset.bestand) > 0;
|
||||||
const show = matchKat && matchPreis && matchVerfuegbar;
|
const show = matchKat && matchPreis && matchVerfuegbar;
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
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>
|
||||||
@@ -17,14 +17,16 @@ const category = getCategory(kategorie!)!;
|
|||||||
const items = productsByCategory(category.slug);
|
const items = productsByCategory(category.slug);
|
||||||
---
|
---
|
||||||
<Layout title={category.name[lang]} description={category.description[lang]} lang={lang} path={`/shop/${category.slug}/`}>
|
<Layout title={category.name[lang]} description={category.description[lang]} lang={lang} path={`/shop/${category.slug}/`}>
|
||||||
<section class="section-tight">
|
<div class="container">
|
||||||
<div class="container">
|
<p class="small breadcrumb" style="margin-top:1.5rem;"><a href="/fr/shop/">{t.shop.breadcrumbShop}</a> / {category.name[lang]}</p>
|
||||||
<p class="small breadcrumb"><a href="/fr/shop/">{t.shop.breadcrumbShop}</a> / {category.name[lang]}</p>
|
<section class="page-banner" style="background-image:url('/img/banner-shop.webp'); --banner-ratio: 1600 / 729;">
|
||||||
<span class="eyebrow" aria-hidden="true">{category.icon}</span>
|
<div class="page-banner-content">
|
||||||
<h1>{category.name[lang]}</h1>
|
<span class="eyebrow" aria-hidden="true">{category.icon} {t.shop.breadcrumbShop}</span>
|
||||||
<p class="lead">{category.description[lang]}</p>
|
<h1>{category.name[lang]}</h1>
|
||||||
</div>
|
<p class="lead">{category.description[lang]}</p>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -17,22 +17,30 @@ const category = getCategory(kategorie!)!;
|
|||||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||||
---
|
---
|
||||||
<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}/`}>
|
||||||
<section class="section-tight">
|
<div class="container">
|
||||||
<div class="container">
|
<p class="small breadcrumb" style="margin-top:1.5rem;">
|
||||||
<p class="small breadcrumb">
|
<a href="/fr/shop/">{t.shop.breadcrumbShop}</a> / <a href={`/fr/shop/${category.slug}/`}>{category.name[lang]}</a> / {sub.name[lang]}
|
||||||
<a href="/fr/shop/">{t.shop.breadcrumbShop}</a> / <a href={`/fr/shop/${category.slug}/`}>{category.name[lang]}</a> / {sub.name[lang]}
|
</p>
|
||||||
</p>
|
<section class="page-banner" style="background-image:url('/img/banner-shop.webp'); --banner-ratio: 1600 / 729;">
|
||||||
<span class="eyebrow" aria-hidden="true">{category.icon}</span>
|
<div class="page-banner-content">
|
||||||
<h1>{sub.name[lang]}</h1>
|
<span class="eyebrow" aria-hidden="true">{category.icon} {category.name[lang]}</span>
|
||||||
</div>
|
<h1>{sub.name[lang]}</h1>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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>)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
|
{sub.mehrLink && <a class="btn btn-primary" href={`/fr${sub.mehrLink}`}>{t.shop.moreAbout} {sub.name[lang]} →</a>}
|
||||||
<a class="btn btn-outline" href={`/fr/shop/${category.slug}/`}>← {category.name[lang]}</a>
|
<a class="btn btn-outline" href={`/fr/shop/${category.slug}/`}>← {category.name[lang]}</a>
|
||||||
<a class="btn btn-primary" href="/fr/shop/">{t.common.toShop}</a>
|
<a class="btn btn-outline" href="/fr/shop/">{t.common.toShop}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -28,7 +28,16 @@ const t = useTranslations(lang);
|
|||||||
<label for="f-kategorie">{t.shop.categoryLabel}</label>
|
<label for="f-kategorie">{t.shop.categoryLabel}</label>
|
||||||
<select id="f-kategorie">
|
<select id="f-kategorie">
|
||||||
<option value="">{t.shop.allCategories}</option>
|
<option value="">{t.shop.allCategories}</option>
|
||||||
{categories.map((c) => <option value={c.slug}>{c.name[lang]}</option>)}
|
{categories.map((c) => (
|
||||||
|
c.subcategories && c.subcategories.length > 0 ? (
|
||||||
|
<optgroup label={c.name[lang]}>
|
||||||
|
<option value={c.slug}>{c.name[lang]} ({t.shop.allCategories})</option>
|
||||||
|
{c.subcategories.map((s) => <option value={`sub:${s.slug}`}>— {s.name[lang]}</option>)}
|
||||||
|
</optgroup>
|
||||||
|
) : (
|
||||||
|
<option value={c.slug}>{c.name[lang]}</option>
|
||||||
|
)
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
@@ -58,6 +67,7 @@ const t = useTranslations(lang);
|
|||||||
<div
|
<div
|
||||||
class="product-slot"
|
class="product-slot"
|
||||||
data-kategorie={p.kategorie}
|
data-kategorie={p.kategorie}
|
||||||
|
data-unterkategorie={p.unterkategorie ?? ""}
|
||||||
data-preis={p.preis}
|
data-preis={p.preis}
|
||||||
data-bestand={p.bestand}
|
data-bestand={p.bestand}
|
||||||
data-neu={p.badges.includes("neu") ? 1 : 0}
|
data-neu={p.badges.includes("neu") ? 1 : 0}
|
||||||
@@ -98,7 +108,11 @@ const t = useTranslations(lang);
|
|||||||
|
|
||||||
let visible = 0;
|
let visible = 0;
|
||||||
slots.forEach((slot) => {
|
slots.forEach((slot) => {
|
||||||
const matchKat = !kat || slot.dataset.kategorie === kat;
|
const matchKat = !kat
|
||||||
|
? true
|
||||||
|
: kat.startsWith("sub:")
|
||||||
|
? slot.dataset.unterkategorie === kat.slice(4)
|
||||||
|
: slot.dataset.kategorie === kat;
|
||||||
const matchPreis = Number(slot.dataset.preis) <= maxPreis;
|
const matchPreis = Number(slot.dataset.preis) <= maxPreis;
|
||||||
const matchVerfuegbar = !nurVerfuegbar || Number(slot.dataset.bestand) > 0;
|
const matchVerfuegbar = !nurVerfuegbar || Number(slot.dataset.bestand) > 0;
|
||||||
const show = matchKat && matchPreis && matchVerfuegbar;
|
const show = matchKat && matchPreis && matchVerfuegbar;
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
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>
|
||||||
@@ -17,14 +17,16 @@ const category = getCategory(kategorie!)!;
|
|||||||
const items = productsByCategory(category.slug);
|
const items = productsByCategory(category.slug);
|
||||||
---
|
---
|
||||||
<Layout title={category.name[lang]} description={category.description[lang]} lang={lang} path={`/shop/${category.slug}/`}>
|
<Layout title={category.name[lang]} description={category.description[lang]} lang={lang} path={`/shop/${category.slug}/`}>
|
||||||
<section class="section-tight">
|
<div class="container">
|
||||||
<div class="container">
|
<p class="small breadcrumb" style="margin-top:1.5rem;"><a href="/shop/">{t.shop.breadcrumbShop}</a> / {category.name[lang]}</p>
|
||||||
<p class="small breadcrumb"><a href="/shop/">{t.shop.breadcrumbShop}</a> / {category.name[lang]}</p>
|
<section class="page-banner" style="background-image:url('/img/banner-shop.webp'); --banner-ratio: 1600 / 729;">
|
||||||
<span class="eyebrow" aria-hidden="true">{category.icon}</span>
|
<div class="page-banner-content">
|
||||||
<h1>{category.name[lang]}</h1>
|
<span class="eyebrow" aria-hidden="true">{category.icon} {t.shop.breadcrumbShop}</span>
|
||||||
<p class="lead">{category.description[lang]}</p>
|
<h1>{category.name[lang]}</h1>
|
||||||
</div>
|
<p class="lead">{category.description[lang]}</p>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -17,22 +17,30 @@ const category = getCategory(kategorie!)!;
|
|||||||
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
const sub = getSubcategory(kategorie!, unterkategorie!)!;
|
||||||
---
|
---
|
||||||
<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}/`}>
|
||||||
<section class="section-tight">
|
<div class="container">
|
||||||
<div class="container">
|
<p class="small breadcrumb" style="margin-top:1.5rem;">
|
||||||
<p class="small breadcrumb">
|
<a href="/shop/">{t.shop.breadcrumbShop}</a> / <a href={`/shop/${category.slug}/`}>{category.name[lang]}</a> / {sub.name[lang]}
|
||||||
<a href="/shop/">{t.shop.breadcrumbShop}</a> / <a href={`/shop/${category.slug}/`}>{category.name[lang]}</a> / {sub.name[lang]}
|
</p>
|
||||||
</p>
|
<section class="page-banner" style="background-image:url('/img/banner-shop.webp'); --banner-ratio: 1600 / 729;">
|
||||||
<span class="eyebrow" aria-hidden="true">{category.icon}</span>
|
<div class="page-banner-content">
|
||||||
<h1>{sub.name[lang]}</h1>
|
<span class="eyebrow" aria-hidden="true">{category.icon} {category.name[lang]}</span>
|
||||||
</div>
|
<h1>{sub.name[lang]}</h1>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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>)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<p class="small">{t.shop.subcategoryEmpty}</p>
|
<p class="small">{t.shop.subcategoryEmpty}</p>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
|
{sub.mehrLink && <a class="btn btn-primary" href={sub.mehrLink}>{t.shop.moreAbout} {sub.name[lang]} →</a>}
|
||||||
<a class="btn btn-outline" href={`/shop/${category.slug}/`}>← {category.name[lang]}</a>
|
<a class="btn btn-outline" href={`/shop/${category.slug}/`}>← {category.name[lang]}</a>
|
||||||
<a class="btn btn-primary" href="/shop/">{t.common.toShop}</a>
|
<a class="btn btn-outline" href="/shop/">{t.common.toShop}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -28,7 +28,16 @@ const t = useTranslations(lang);
|
|||||||
<label for="f-kategorie">{t.shop.categoryLabel}</label>
|
<label for="f-kategorie">{t.shop.categoryLabel}</label>
|
||||||
<select id="f-kategorie">
|
<select id="f-kategorie">
|
||||||
<option value="">{t.shop.allCategories}</option>
|
<option value="">{t.shop.allCategories}</option>
|
||||||
{categories.map((c) => <option value={c.slug}>{c.name[lang]}</option>)}
|
{categories.map((c) => (
|
||||||
|
c.subcategories && c.subcategories.length > 0 ? (
|
||||||
|
<optgroup label={c.name[lang]}>
|
||||||
|
<option value={c.slug}>{c.name[lang]} ({t.shop.allCategories})</option>
|
||||||
|
{c.subcategories.map((s) => <option value={`sub:${s.slug}`}>— {s.name[lang]}</option>)}
|
||||||
|
</optgroup>
|
||||||
|
) : (
|
||||||
|
<option value={c.slug}>{c.name[lang]}</option>
|
||||||
|
)
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
@@ -58,6 +67,7 @@ const t = useTranslations(lang);
|
|||||||
<div
|
<div
|
||||||
class="product-slot"
|
class="product-slot"
|
||||||
data-kategorie={p.kategorie}
|
data-kategorie={p.kategorie}
|
||||||
|
data-unterkategorie={p.unterkategorie ?? ""}
|
||||||
data-preis={p.preis}
|
data-preis={p.preis}
|
||||||
data-bestand={p.bestand}
|
data-bestand={p.bestand}
|
||||||
data-neu={p.badges.includes("neu") ? 1 : 0}
|
data-neu={p.badges.includes("neu") ? 1 : 0}
|
||||||
@@ -98,7 +108,11 @@ const t = useTranslations(lang);
|
|||||||
|
|
||||||
let visible = 0;
|
let visible = 0;
|
||||||
slots.forEach((slot) => {
|
slots.forEach((slot) => {
|
||||||
const matchKat = !kat || slot.dataset.kategorie === kat;
|
const matchKat = !kat
|
||||||
|
? true
|
||||||
|
: kat.startsWith("sub:")
|
||||||
|
? slot.dataset.unterkategorie === kat.slice(4)
|
||||||
|
: slot.dataset.kategorie === kat;
|
||||||
const matchPreis = Number(slot.dataset.preis) <= maxPreis;
|
const matchPreis = Number(slot.dataset.preis) <= maxPreis;
|
||||||
const matchVerfuegbar = !nurVerfuegbar || Number(slot.dataset.bestand) > 0;
|
const matchVerfuegbar = !nurVerfuegbar || Number(slot.dataset.bestand) > 0;
|
||||||
const show = matchKat && matchPreis && matchVerfuegbar;
|
const show = matchKat && matchPreis && matchVerfuegbar;
|
||||||
|
|||||||
@@ -647,6 +647,20 @@ a:focus-visible, button:focus-visible {
|
|||||||
}
|
}
|
||||||
.subcategory-chip:hover { color: var(--c-accent); border-color: color-mix(in srgb, var(--c-accent) 55%, transparent); background: color-mix(in srgb, var(--c-accent) 8%, var(--c-anthracite)); }
|
.subcategory-chip:hover { color: var(--c-accent); border-color: color-mix(in srgb, var(--c-accent) 55%, transparent); background: color-mix(in srgb, var(--c-accent) 8%, var(--c-anthracite)); }
|
||||||
|
|
||||||
|
/* Persönliche Vorstellung eines Plushie-Charakters auf seiner Unterkategorie-Seite (z.B.
|
||||||
|
"Hallo, ich bin Frieda ..."). Dezente Box im Kartenstil des Shops, kein neues Designelement. */
|
||||||
|
.character-intro {
|
||||||
|
max-width: 640px;
|
||||||
|
margin: 1.6rem 0 2rem;
|
||||||
|
padding: 1.4rem 1.6rem;
|
||||||
|
border-radius: var(--radius-l);
|
||||||
|
background: var(--c-anthracite);
|
||||||
|
border: 1px solid rgba(244, 241, 247, 0.1);
|
||||||
|
border-left: 3px solid var(--c-accent);
|
||||||
|
}
|
||||||
|
.character-intro p { margin: 0; white-space: pre-line; }
|
||||||
|
.character-intro p:first-child { font-family: var(--font-head); font-weight: 600; color: var(--c-text); margin-bottom: 0.5em; }
|
||||||
|
|
||||||
/* Seiten-Banner (Shop, Sale, ...): echtes Foto/Grafik als Kopfbild mit Verlauf-Überblendung
|
/* Seiten-Banner (Shop, Sale, ...): echtes Foto/Grafik als Kopfbild mit Verlauf-Überblendung
|
||||||
unten, damit Titel/Text darauf lesbar bleiben — ersetzt die reine Text-Kopfzeile durch ein
|
unten, damit Titel/Text darauf lesbar bleiben — ersetzt die reine Text-Kopfzeile durch ein
|
||||||
echtes visuelles Signal, wie im Pflichtenheft für "Bannerplätze" gefordert. */
|
echtes visuelles Signal, wie im Pflichtenheft für "Bannerplätze" gefordert. */
|
||||||
|
|||||||
Reference in New Issue
Block a user