Files
kaiser-natron/src/api/products.js
Dorian 15c951c2a0 feat: shop 4-section category banners + brand-feedback pass
Shop page rebuilt into four brand colour-coded use-group sections (Küche/lime,
Reinigung/grapefruit, Wäsche/plum, Pflege/care/orange): colour banners with diagonal
dividers, per-section grid titles, crimson 'add to cart' + white-outline 'learn more'
CTAs, plain-name eyebrows, first-fold halved + thin white band. Split old Haushalt
group into clean+wash.

Also in this checkpoint (brand-owner feedback items):
- L11: Kaiser-Natron® on every visible mention, hyphenated (de+en, products, logo, nav)
- L4: product-hero headline reworded to the brand versatility voice
- L5/L6: faint 'AI Edited' badge on AI bundle images (data-driven aiEdited flag)
- L10: Revitalization animation row + early-access CTA removed
- U2: membership 'become a member' button removed
- New category tokens (cat-kitchen/clean/wash/care) + Hero/WaveDivider tones
- New /kueche category page (route + copy) — not yet linked (see STYLE-CHANGES caveat)
- Homepage reverted to original ProductTeaser (banners belong on /shop)

dist/ rebuilt and included for the Portainer/gitea deploy. Open decisions tracked
in STYLE-CHANGES.md §20 and memory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 12:35:52 +01:00

388 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Product catalog — frontend fixture until the PHP / MySQL backend takes over.
// Shape and helper are the same surface the API module will eventually expose.
export const products = [
{
id: 'kaiser-natron-pulver-50-g-beutel',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Pulver',
size: '50 g Beutel',
category: 'Pulver',
price: 1.29,
image: '/products/kaiser-natron-pulver-50-g-beutel.webp',
href: '/shop/kaiser-natron-pulver-50-g-beutel',
inStock: true,
keywords: ['natron', 'backsoda', 'bicarbonat', 'natriumhydrogencarbonat', 'baking soda'],
properties: ['glutenfrei', 'laktosefrei', 'vegan', 'neutralisiert-saeuren', 'enthaertet', 'mild-alkalisch'],
},
{
id: 'kaiser-natron-pulver-250-g-grosspackung',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Pulver',
size: '250 g Großpackung',
category: 'Pulver',
price: 4.49,
image: '/products/kaiser-natron-pulver-250-g-grosspackung.webp',
href: '/shop/kaiser-natron-pulver-250-g-grosspackung',
inStock: true,
keywords: ['natron', 'backsoda', 'großpackung', 'bicarbonat', 'baking soda', 'vorrat'],
properties: ['glutenfrei', 'laktosefrei', 'vegan', 'neutralisiert-saeuren', 'enthaertet', 'mild-alkalisch'],
},
{
id: 'kaiser-natron-pulver-3490-g-eimer',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Pulver',
size: '3.490 g Eimer',
category: 'Pulver',
price: 29.9,
image: '/products/kaiser-natron-pulver-3.490-g-eimer.webp',
href: '/shop/kaiser-natron-pulver-3490-g-eimer',
inStock: true,
keywords: ['natron', 'eimer', 'großgebinde', 'gastro', 'gewerbe', 'baking soda'],
properties: ['glutenfrei', 'laktosefrei', 'vegan', 'neutralisiert-saeuren', 'enthaertet', 'mild-alkalisch'],
},
{
id: 'kaiser-natron-tabletten-100-g-dose',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Tabletten',
size: '100 g Dose',
category: 'Tabletten',
price: 3.49,
image: '/products/kaiser-natron-tabletten-100-g-dose.webp',
href: '/shop/kaiser-natron-tabletten-100-g-dose',
inStock: true,
keywords: ['natron', 'tabletten', 'magen', 'sodbrennen', 'verdauung'],
properties: ['glutenfrei', 'laktosefrei', 'vegan', 'fuer-kueche-und-haushalt'],
},
{
id: 'kaiser-natron-bad-500-g',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Bad',
size: '500 g',
category: 'Körperpflege',
price: 5.49,
image: '/products/kaiser-natron-bad-500-g.webp',
href: '/shop/kaiser-natron-bad-500-g',
inStock: true,
keywords: ['basenbad', 'wellness', 'entspannung', 'haut', 'wanne'],
properties: ['mit-entspannendem-apfelsinenduft', 'ohne-konservierungsstoffe', 'ohne-mineraloel', 'frei-von-tierischen-substanzen'],
},
{
id: 'kaiser-natron-fussbad-500-g',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Fußbad',
size: '500 g',
category: 'Körperpflege',
price: 5.49,
image: '/products/kaiser-natron-fussbad-500-g.webp',
href: '/shop/kaiser-natron-fussbad-500-g',
inStock: true,
keywords: ['fusspflege', 'basenbad', 'wellness', 'entspannung'],
properties: ['mit-dem-duft-der-latschenkiefer', 'wohltuend-und-erfrischend', 'ohne-konservierungsstoffe', 'ohne-mineraloel', 'frei-von-tierischen-substanzen'],
},
{
id: 'kaiser-natron-daunenwasch-250-ml',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Daunenwasch',
size: '250 ml',
category: 'Wäsche',
price: 6.9,
image: '/products/kaiser-natron-daunenwasch-250-ml.webp',
href: '/shop/kaiser-natron-daunenwasch-250-ml',
inStock: true,
keywords: ['daunen', 'wäsche', 'bettdecke', 'kissen', 'pflege'],
properties: ['mit-pflegendem-lanolin', 'frei-von-mikroplastik'],
},
{
id: 'kaiser-natron-sport-profi-250-ml',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Sport Profi',
size: '250 ml',
category: 'Sport',
price: 7.9,
image: '/products/kaiser-natron-sport-profi-250-ml.webp',
href: '/shop/kaiser-natron-sport-profi-250-ml',
inStock: true,
keywords: ['sport', 'wäsche', 'funktionskleidung', 'geruch'],
properties: ['frei-von-mikroplastik'],
},
{
id: 'kaiser-natron-spuelmittel-500-ml',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Spülmittel',
size: '500 ml',
category: 'Reinigung',
price: 3.9,
image: '/products/kaiser-natron-spuelmittel-500-ml.webp',
href: '/shop/kaiser-natron-spuelmittel-500-ml',
inStock: true,
keywords: ['geschirr', 'spülen', 'küche', 'reinigung'],
properties: ['mit-pampelmusenduft', 'hautvertraeglich-dermatologisch-bestaetigt', 'frei-von-mikroplastik'],
},
{
id: 'kaiser-natron-allzweck-reiniger-750-ml',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Allzweck-Reiniger',
size: '750 ml',
category: 'Reinigung',
price: 4.49,
image: '/products/kaiser-natron-allzweck-reiniger-750-ml.webp',
href: '/shop/kaiser-natron-allzweck-reiniger-750-ml',
inStock: true,
keywords: ['reinigung', 'allzweck', 'universal', 'haushalt'],
properties: ['hygienische-sauberkeit', 'fuer-boeden-und-oberflaechen', 'mit-pampelmusenduft', 'vegan', 'ohne-konservierungsstoffe', 'frei-von-mikroplastik'],
},
{
id: 'kaiser-natron-allzweck-spray-500-ml',
brand: 'Kaiser-Natron®',
title: 'Kaiser-Natron® Allzweck-Spray',
size: '500 ml',
category: 'Reinigung',
price: 4.9,
image: '/products/kaiser-natron-allzweck-spray-500-ml.webp',
href: '/shop/kaiser-natron-allzweck-spray-500-ml',
inStock: true,
keywords: ['reinigung', 'allzweck', 'spray', 'haushalt'],
properties: ['nachhaltig-durch-tenside-nachwachsenden-ursprungs', 'ohne-konservierungsstoffe', 'mit-natuerlicher-seife', 'frei-von-mikroplastik'],
},
{
id: 'holste-wasch-soda-500-g-beutel',
brand: 'Holste',
title: 'Holste Wasch-Soda',
size: '500 g Beutel',
category: 'Wäsche',
price: 2.9,
image: '/products/holste-wasch-soda-500-g-beutel.webp',
href: '/shop/holste-wasch-soda-500-g-beutel',
inStock: true,
keywords: ['soda', 'wasch-soda', 'waschen', 'natriumcarbonat'],
},
{
id: 'holste-handwaschpaste-500-ml',
brand: 'Holste',
title: 'Holste Handwaschpaste',
size: '500 ml',
category: 'Reinigung',
price: 6.9,
image: '/products/holste-handwaschpaste-500-ml.webp',
href: '/shop/holste-handwaschpaste-500-ml',
inStock: true,
keywords: ['hände', 'werkstatt', 'arbeit', 'grobe verschmutzung'],
},
{
id: 'holste-kalk-und-urinsteinloeser-750-ml',
brand: 'Holste',
title: 'Holste Kalk- und Urinsteinlöser',
size: '750 ml',
category: 'Reinigung',
price: 5.9,
image: '/products/holste-kalk--und-urinsteinloeser-750-ml.webp',
href: '/shop/holste-kalk-und-urinsteinloeser-750-ml',
inStock: true,
keywords: ['kalk', 'urinstein', 'bad', 'wc', 'toilette', 'entkalker'],
},
{
id: 'holste-reisstaerke-250-g-faltschachtel',
brand: 'Holste',
title: 'Holste Reisstärke',
size: '250 g Faltschachtel',
category: 'Wäsche',
price: 3.9,
image: '/products/holste-reisstaerke-250-g-faltschachtel.webp',
href: '/shop/holste-reisstaerke-250-g-faltschachtel',
inStock: true,
keywords: ['stärke', 'reisstärke', 'wäsche', 'bügeln'],
},
{
id: 'holste-schmierseife-fluessig-1-l-flasche',
brand: 'Holste',
title: 'Holste Schmierseife flüssig',
size: '1 l Flasche',
category: 'Reinigung',
price: 6.49,
image: '/products/holste-schmierseife-fluessig-1-l-flasche.webp',
href: '/shop/holste-schmierseife-fluessig-1-l-flasche',
inStock: true,
keywords: ['schmierseife', 'naturseife', 'boden', 'reinigung'],
},
{
id: 'holste-zitronensaeure-entkalker-fluessig-500-ml',
brand: 'Holste',
title: 'Holste Zitronensäure-Entkalker flüssig',
size: '500 ml',
category: 'Reinigung',
price: 4.9,
image: '/products/holste-zitronensaeure-entkalker-fluessig-500-ml.webp',
href: '/shop/holste-zitronensaeure-entkalker-fluessig-500-ml',
inStock: true,
keywords: ['zitronensäure', 'entkalker', 'kalk', 'haushalt', 'küche'],
},
{
id: 'gazelle-waeschestaerke-1000-ml-flasche',
brand: 'Gazelle',
title: 'Gazelle Wäschestärke',
size: '1.000 ml Flasche',
category: 'Wäsche',
price: 5.9,
image: '/products/gazelle-waeschestaerke-1000-ml-flasche.webp',
href: '/shop/gazelle-waeschestaerke-1000-ml-flasche',
inStock: true,
keywords: ['wäschestärke', 'stärke', 'bügeln', 'tischwäsche'],
},
{
id: 'gruene-tante-mit-quarzmehl-500-ml-dose',
brand: 'Grüne Tante',
title: 'Grüne Tante mit Quarzmehl',
size: '500 ml Dose',
category: 'Reinigung',
price: 7.9,
image: '/products/gruene-tante-mit-quarzmehl-500-ml-dose.webp',
href: '/shop/gruene-tante-mit-quarzmehl-500-ml-dose',
inStock: true,
keywords: ['handreiniger', 'werkstatt', 'quarzmehl', 'grobe verschmutzung'],
},
{
id: 'linda-fleckenweg-200-ml-tube',
brand: 'Linda',
title: 'Linda Fleckenweg',
size: '200 ml Tube',
category: 'Wäsche',
price: 4.9,
image: '/products/linda-fleckenweg-200-ml-tube.webp',
href: '/shop/linda-fleckenweg-200-ml-tube',
inStock: true,
keywords: ['fleck', 'fleckenentferner', 'vorbehandlung', 'wäsche'],
},
{
id: 'linda-handreiniger-der-kraftvolle-200-g-tube',
brand: 'Linda',
title: 'Linda Handreiniger Der Kraftvolle',
size: '200 g Tube',
category: 'Reinigung',
price: 5.9,
image: '/products/linda-fleckenweg-200-ml-tube.webp',
href: '/shop/linda-handreiniger-der-kraftvolle-200-g-tube',
inStock: true,
keywords: ['handreiniger', 'werkstatt', 'öl', 'fett'],
},
{
id: 'linda-neutral-375-ml-dose',
brand: 'Linda',
title: 'Linda Neutral',
size: '375 ml Dose',
category: 'Reinigung',
price: 5.49,
image: '/products/linda-neutral-375-ml-dose.webp',
href: '/shop/linda-neutral-375-ml-dose',
inStock: true,
keywords: ['handreiniger', 'neutral', 'haut', 'empfindlich'],
},
]
// Normalize for search: lowercase + fold German diacritics so "Spülmittel"
// matches "spulmittel" and "großpackung" matches "grosspackung".
function normalize(input) {
if (!input) return ''
return String(input)
.toLowerCase()
.replace(/ß/g, 'ss')
.replace(/ä/g, 'ae')
.replace(/ö/g, 'oe')
.replace(/ü/g, 'ue')
.normalize('NFD')
.replace(/[̀-ͯ]/g, '')
}
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
// Ranked match: every query term must hit at least one field; fields have
// weights so a title hit outranks a keyword hit. Exact > startsWith >
// word-boundary > substring. Terms AND together (all must match something).
export function searchProducts(query, list = products, limit = 8) {
const q = normalize(query).trim()
if (!q) return []
const terms = q.split(/\s+/).filter(Boolean)
const scored = []
for (const product of list) {
const haystacks = [
{ text: normalize(product.title), weight: 3 },
{ text: normalize(product.brand), weight: 2.2 },
{ text: normalize(product.category), weight: 1.4 },
{ text: normalize(product.size), weight: 1.2 },
{ text: normalize((product.keywords || []).join(' ')), weight: 1 },
]
let total = 0
let allTermsMatched = true
for (const term of terms) {
const boundary = new RegExp(`\\b${escapeRegExp(term)}`)
let best = 0
for (const { text, weight } of haystacks) {
if (!text) continue
let s = 0
if (text === term) s = 100
else if (text.startsWith(term)) s = 80
else if (boundary.test(text)) s = 55
else if (text.includes(term)) s = 25
if (s * weight > best) best = s * weight
}
if (best === 0) {
allTermsMatched = false
break
}
total += best
}
if (allTermsMatched) scored.push({ product, score: total })
}
scored.sort((a, b) => b.score - a.score)
return scored.slice(0, limit).map((s) => s.product)
}
export function formatPrice(amount, currency = '€') {
const num = typeof amount === 'number' ? amount : Number(amount)
if (!Number.isFinite(num)) return ''
return `${currency} ${num.toFixed(2).replace('.', ',')}`
}
/**
* Shop use-cases — the four top-level bands on the Shop page, each in
* its own brand colour (kaiser-natron.de palette). Product-level
* `category` strings are fine-grained (Pulver, Reinigung, Wäsche,
* Körperpflege, Sport, Tabletten); this table rolls them up into the
* four shop-facing groups so the mapping is declared in one place.
*
* cook — Küche: kitchen & digestion (baking, gastro) — lime
* clean — Reinigung: household cleaning — grapefruit
* wash — Wäsche: laundry & textile care — plum
* care — Pflege: bath, body and sport — orange
*/
export const USE_CASES = ['cook', 'clean', 'wash', 'care']
const USE_CASE_BY_CATEGORY = {
Pulver: 'cook',
Tabletten: 'cook',
Reinigung: 'clean',
Wäsche: 'wash',
Körperpflege: 'care',
Sport: 'care',
}
export function useCaseFor(product) {
return USE_CASE_BY_CATEGORY[product.category] || 'clean'
}
export function productsByUseCase(list = products) {
return list.reduce(
(acc, p) => {
acc[useCaseFor(p)].push(p)
return acc
},
{ cook: [], clean: [], wash: [], care: [] },
)
}