Files
kaiser-natron/src/pages/HomePage.vue
2026-04-22 11:23:38 +01:00

164 lines
5.3 KiB
Vue

<script setup>
import { ref, onMounted } from 'vue'
import Navbar from '@/design-system/components/Navbar.vue'
import Hero from '@/design-system/components/Hero.vue'
import CartDrawer from '@/design-system/components/CartDrawer.vue'
import {
products,
fetchCart,
addToCart,
updateCartItem,
removeFromCart,
} from '@/api/index.js'
import { useCartStore } from '@/stores/cart.js'
import { useI18n } from '@/i18n/index.js'
const { t } = useI18n()
const cart = useCartStore()
const cartOpen = ref(false)
const imgPulver250 =
'/products/cutouts/kaiser-natron-pulver-250-g-gro%C3%9Fpackung-removebg-preview-2.png'
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)
}
async function onRemove(productId) {
await removeFromCart(productId)
}
// Picking a product from the navbar search drops it into the cart and
// reveals the drawer so the user sees the update.
async function onSearchSelect(product) {
await addToCart(product.id, 1)
cartOpen.value = true
}
// Hydrate from the API on mount — today this reads the local store, later
// it will hit `GET /api/cart`. Either way the drawer has data to show.
onMounted(() => {
fetchCart()
})
</script>
<template>
<!-- First fold on md+ the wrapper is exactly one viewport tall and the
centering row below the navbar vertically centres the hero inside
the remaining space. On mobile the Hero's stacked split layout
(image + copy + CTAs) is taller than a phone viewport, so we drop
the height cap and let the section flow at its natural height;
otherwise `overflow-hidden` clips the CTAs, which is what the
mobile screenshot showed. -->
<div class="flex flex-col bg-brand md:h-svh md:overflow-hidden">
<Navbar
variant="brand"
layout="standard"
:cart-count="cart.count"
:products="products"
@cart="cartOpen = true"
@search="onSearchSelect"
/>
<div class="md:flex-1 md:flex md:items-center">
<Hero
class="w-full"
variant="split"
tone="brand"
:eyebrow="t('ds.hero.eyebrow')"
:subheadline="t('ds.hero.sub')"
:image="imgPulver250"
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="onHeroAdd"
>
<template #headline>
{{ t('ds.hero.headline.a') }}
<em class="italic font-light text-accent-soft">{{ t('ds.hero.headline.em') }}</em>
{{ t('ds.hero.headline.b') }}
</template>
</Hero>
</div>
<!-- Wave divider from brand-green → cream. The SVG is fully opaque:
a cream rect fills the whole viewBox so the SVG's bottom row is
solid cream (matches the banner below no seam), and a green
path paints the top portion (matches the bg-brand parent above
no seam). The earlier version left the top half transparent,
which caused browsers to anti-alias the path's top/bottom
edges against the parent and produce hairline artifacts. -->
<svg
aria-hidden="true"
class="block w-full h-12 md:h-16 shrink-0"
viewBox="0 0 1440 64"
preserveAspectRatio="none"
>
<rect width="1440" height="64" fill="var(--color-cream)" />
<path
d="M0,0 L0,40 C320,4 520,60 720,32 C920,4 1120,60 1440,24 L1440,0 Z"
fill="var(--color-brand)"
/>
</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"
:subtotal="cart.subtotal"
:count="cart.count"
@update-quantity="onQty"
@remove="onRemove"
@checkout="cartOpen = false"
/>
</template>