addded banner placeholder

This commit is contained in:
Dorian
2026-04-22 10:45:44 +01:00
parent f27624478c
commit 6b92eb5474
41 changed files with 145 additions and 44 deletions

View File

@@ -7,7 +7,11 @@ html {
}
body {
background: var(--color-surface);
/* Match the last visible section on the home page (cream) so browser
overscroll + any empty space below content blends with the banner
instead of showing a seam. Design-system pages override with
`bg-surface` on their own root wrapper, so they're unaffected. */
background: var(--color-cream);
color: var(--color-ink);
font-family: var(--font-sans);
line-height: 1.55;

View File

@@ -30,6 +30,10 @@ const props = defineProps({
default: 'cream',
validator: (t) => ['cream', 'paper', 'brand'].includes(t),
},
/** Only affects the split variant. When true, the image sits on the left
* and copy on the right from `lg` up. Mobile order is unchanged (image
* on top, copy below) to preserve reading order. */
reverse: { type: Boolean, default: false },
})
defineEmits(['cta', 'secondary'])
@@ -76,11 +80,17 @@ const layout = computed(() => {
// `order-*` flips the stacking order below lg without changing DOM order.
// On lg, let the grid stretch items to the row height and then `justify-center`
// the copy column so its headline/CTA block sits at the image's vertical midpoint.
// `reverse` swaps the desktop columns so the image sits on the left.
return {
root: 'grid gap-10 md:gap-14 lg:grid-cols-[1.05fr_1fr]',
copy: 'order-2 lg:order-1 max-w-xl mx-auto lg:mx-0 items-center text-center lg:items-start lg:text-left lg:justify-center',
root: props.reverse
? 'grid gap-10 md:gap-14 lg:grid-cols-[1fr_1.05fr]'
: 'grid gap-10 md:gap-14 lg:grid-cols-[1.05fr_1fr]',
copy: [
'order-2 max-w-xl mx-auto lg:mx-0 items-center text-center lg:justify-center',
props.reverse ? 'lg:order-2 lg:items-start lg:text-left' : 'lg:order-1 lg:items-start lg:text-left',
].join(' '),
actions: 'justify-center lg:justify-start',
media: 'order-1 lg:order-2',
media: props.reverse ? 'order-1 lg:order-1' : 'order-1 lg:order-2',
mediaSize: 'w-[221px] sm:w-[289px] md:w-[357px] lg:w-full lg:max-w-[442px]',
}
})

View File

@@ -230,6 +230,13 @@ const de = {
'ds.hero.headline.b': 'was glänzen soll.',
'ds.hero.sub': 'Reinigt, backt und neutralisiert Gerüche. Die 250-g-Großpackung für den Haushalt, der sich auf das Original verlässt.',
// Home page second-fold banner
'home.banner.eyebrow': 'Vielseitig einsetzbar',
'home.banner.headline.a': 'Ein Pulver,',
'home.banner.headline.em': 'hundert Anwendungen',
'home.banner.headline.b': 'im Haushalt.',
'home.banner.sub': 'Vom Kuchen über das Bad bis zur Wäsche Kaiser-Natron leistet überall still und zuverlässig seine Arbeit.',
// Search section
'ds.search.title': 'Produktsuche',
'ds.search.description': 'Suchknopf rechts in der Navbar (links der Sprachwahl) und links unten als Floating-Button auf dem Telefon. Klick öffnet ein Full-Screen-Overlay auf Mobilgeräten bzw. ein zentriertes Panel auf dem Desktop. Eingabefeld ist autofokussiert, Ergebnisliste aktualisiert sich beim Tippen, Pfeiltasten / Enter / Esc werden unterstützt.',
@@ -507,6 +514,13 @@ const en = {
'ds.hero.headline.b': 'that should shine.',
'ds.hero.sub': 'Cleans, bakes, and neutralises odours. The 250 g large pack for the household that trusts the original.',
// Home page second-fold banner
'home.banner.eyebrow': 'Truly versatile',
'home.banner.headline.a': 'One powder,',
'home.banner.headline.em': 'a hundred uses',
'home.banner.headline.b': 'around the home.',
'home.banner.sub': 'From baking to bathing to laundry — Kaiser Natron quietly does its work wherever you need it.',
'ds.search.title': 'Product search',
'ds.search.description': 'Search trigger on the right of the navbar (left of the language switcher) and bottom-left as a floating button on mobile. Tapping it opens a full-screen overlay on mobile and a centered panel on desktop. The input auto-focuses, the results list updates as you type, and arrow keys / Enter / Esc are supported.',
'ds.search.demo.label': 'Open demo',

View File

@@ -22,11 +22,21 @@ const imgPulver250 =
const heroProductId = 'kaiser-natron-pulver-250-g-grosspackung'
// Second-fold banner — cream tone, image-left split, alternate cutout.
const imgBanner =
'/products/cutouts/kaiser-natron-pulver-250-g-gro%C3%9Fpackung-removebg-preview%20%281%29.png'
const bannerProductId = 'kaiser-natron-pulver-250-g-grosspackung'
async function onHeroAdd() {
await addToCart(heroProductId, 1)
cartOpen.value = true
}
async function onBannerAdd() {
await addToCart(bannerProductId, 1)
cartOpen.value = true
}
async function onQty({ productId, quantity }) {
await updateCartItem(productId, quantity)
}
@@ -84,8 +94,57 @@ onMounted(() => {
</template>
</Hero>
</div>
<!-- Wave divider from brand-green cream. Fill uses the next
section's background so the curve reads as the bottom edge of
the green fold. `preserveAspectRatio="none"` lets the SVG
stretch across any viewport width while keeping a consistent
vertical amplitude. -->
<svg
aria-hidden="true"
class="block w-full h-12 md:h-16 shrink-0"
viewBox="0 0 1440 64"
preserveAspectRatio="none"
>
<path
d="M0,40 C320,4 520,60 720,32 C920,4 1120,60 1440,24 L1440,64 L0,64 Z"
fill="var(--color-cream)"
/>
</svg>
</div>
<!-- Second-fold product banner — same Hero component, cream surface,
split layout reversed so the product sits on the left. -->
<Hero
variant="split"
tone="cream"
reverse
:eyebrow="t('home.banner.eyebrow')"
:subheadline="t('home.banner.sub')"
:image="imgBanner"
image-alt="Kaiser-Natron Pulver 250 g Großpackung"
:cta-label="t('ds.buttons.addToCart')"
:secondary-label="t('ds.buttons.learnMore')"
secondary-href="/anwendungen"
@cta="onBannerAdd"
>
<template #headline>
{{ t('home.banner.headline.a') }}
<em class="italic font-light text-brand-soft">{{ t('home.banner.headline.em') }}</em>
{{ t('home.banner.headline.b') }}
</template>
</Hero>
<!-- Bottom clearance for the mobile floating cluster (search / cart / menu).
Cluster sits at bottom-5 (20px) + safe-area, is 56px tall, and needs a
24px breathing gap above it: 20 + 56 + 24 = 100px, plus the device's
safe-area inset. Desktop hides the cluster, so no spacer there. -->
<div
aria-hidden="true"
class="md:hidden"
style="height: calc(100px + env(safe-area-inset-bottom));"
/>
<CartDrawer
v-model="cartOpen"
:items="cart.items"

View File

@@ -14,10 +14,16 @@ const variant = computed(() =>
const tone = computed(() =>
['cream', 'paper', 'brand'].includes(route.query.tone) ? route.query.tone : 'cream',
)
const reverse = computed(() => route.query.reverse === '1')
const navVariant = computed(() => (tone.value === 'brand' ? 'brand' : tone.value))
const imgPulver250 =
'/products/cutouts/kaiser-natron-pulver-250-g-gro%C3%9Fpackung-removebg-preview-2.png'
const imgBad = '/products/kaiser-natron-bad-500-g.jpg'
const image = computed(() => (reverse.value ? imgBad : imgPulver250))
const imageAlt = computed(() =>
reverse.value ? 'Kaiser-Natron Bad 500 g' : 'Kaiser-Natron Pulver 250 g Großpackung',
)
</script>
<template>
@@ -26,18 +32,26 @@ const imgPulver250 =
<Hero
:variant="variant"
:tone="tone"
:eyebrow="t('ds.hero.eyebrow')"
:subheadline="t('ds.hero.sub')"
:image="imgPulver250"
image-alt="Kaiser-Natron Pulver 250 g Großpackung"
:badge="t('ds.badges.featured')"
:reverse="reverse"
:eyebrow="reverse ? t('home.banner.eyebrow') : t('ds.hero.eyebrow')"
:subheadline="reverse ? t('home.banner.sub') : t('ds.hero.sub')"
:image="image"
:image-alt="imageAlt"
:badge="reverse ? '' : t('ds.badges.featured')"
:cta-label="t('ds.buttons.addToCart')"
:secondary-label="t('ds.buttons.learnMore')"
>
<template #headline>
{{ t('ds.hero.headline.a') }}
<em class="italic font-light text-brand-soft">{{ t('ds.hero.headline.em') }}</em>
{{ t('ds.hero.headline.b') }}
<template v-if="reverse">
{{ t('home.banner.headline.a') }}
<em class="italic font-light text-brand-soft">{{ t('home.banner.headline.em') }}</em>
{{ t('home.banner.headline.b') }}
</template>
<template v-else>
{{ t('ds.hero.headline.a') }}
<em class="italic font-light text-brand-soft">{{ t('ds.hero.headline.em') }}</em>
{{ t('ds.hero.headline.b') }}
</template>
</template>
</Hero>
</div>