Files
shop/pages/index.vue
libretech 61a74e649b
build / build (push) Successful in 1m38s
feat(selection): lock single-option boxes, drop the about link
Recovered work in progress that sat uncommitted since 2026-05: a
SelectionBox renders as a plain div instead of a NuxtLink when locked,
so a variant with only one choice reads as decided rather than
clickable, and it reports aria-checked in that state. useProductContent
gained the content lookups the details and index pages now use, the
cookie banner says Schließen instead of Akzeptieren, the Über uns
header entry is gone, and ProductCard's hover shadow no longer relies
on a short-circuit that could yield a non-array.
2026-09-23 22:09:52 +02:00

242 lines
8.2 KiB
Vue
Raw Permalink 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.
<template>
<main class="pt-0">
<h1 class="sr-only">MUELLERPRINTS — Handgefertigte Notizbücher aus Stuttgart</h1>
<Hero />
<!-- First cover section -->
<section v-if="covers[0]" class="my-20 lg:container mx-auto px-6">
<Heading v-if="covers[0]?.name" :level="2" html-tag="h2">{{ covers[0].name }}</Heading>
<p v-if="covers[0]?.copyText?.details" class="leading-6 tracking-tight xl:w-2/3" v-html="covers[0].copyText.details" />
<div class="mt-8">
<ClientOnly>
<LazyCarousel>
<Slide v-for="(item, j) in covers[0]?.products" :key="j">
<ProductCard :product="item" />
</Slide>
</LazyCarousel>
</ClientOnly>
</div>
</section>
<!-- Storytelling: Handwerk aus Stuttgart -->
<FeatureModule
eyebrow="Handwerk aus Stuttgart"
headline="Jedes Notizbuch ein Unikat"
subtitle="Tradition trifft Nachhaltigkeit"
:body="`
In unserer Werkstatt verbinden wir <strong>traditionelle Buchbindekunst</strong> mit modernem Design.
Jedes Notizbuch wird von Hand gebunden – mit Fadenheftung, die ein flaches Aufschlagen ermöglicht.
Wir verwenden ausschließlich <strong>100% Recyclingpapier</strong> aus deutscher Produktion.
`"
:image="productionImage01"
class="bg-gray-50"
/>
<!-- Remaining cover sections with alternating backgrounds -->
<template v-for="(coverData, i) in covers.slice(1)" :key="i">
<section :class="['my-20 lg:container mx-auto px-6']">
<Heading v-if="coverData?.name" :level="2" html-tag="h2">{{ coverData.name }}</Heading>
<p v-if="coverData?.copyText?.details" class="leading-6 tracking-tight xl:w-2/3" v-html="coverData.copyText.details" />
<div class="mt-8">
<ClientOnly>
<LazyCarousel>
<Slide v-for="(item, j) in coverData?.products" :key="j">
<ProductCard :product="item" />
</Slide>
</LazyCarousel>
</ClientOnly>
</div>
</section>
</template>
<!-- Promo bands for standalone products (e.g. Journaling Booklet) -->
<section
v-for="promo in promoCovers"
:key="promo.id"
class="py-16 lg:py-24 bg-gradient-to-br from-violet-50 via-white to-rose-50"
>
<div class="container mx-auto px-6 flex flex-col-reverse lg:flex-row items-center gap-10 lg:gap-16">
<div class="lg:w-1/2">
<p class="text-sm uppercase tracking-widest opacity-60 mb-3">Neu im Sortiment</p>
<Heading :level="2" html-tag="h2">{{ promo.name }}</Heading>
<p v-if="promo.details" class="mt-4 text-lg leading-relaxed opacity-80 xl:w-5/6" v-html="promo.details" />
<NuxtLink
:to="`/details/${promo.productSlug}`"
class="mt-8 inline-block px-8 py-3 rounded-full font-medium bg-gray-900 text-white hover:bg-gray-700 transition-all duration-200"
@click="trackEvent('promo-cta-clicked', { product: promo.productSlug })"
>
{{ promo.name }} entdecken
</NuxtLink>
</div>
<NuxtLink :to="`/details/${promo.productSlug}`" class="lg:w-1/2 block">
<img v-if="promo.image" :src="promo.image" :alt="promo.name" loading="lazy" class="w-full rounded-lg shadow-xl" />
</NuxtLink>
</div>
</section>
<!-- Testimonials (hidden for now)
<section v-if="testimonials?.length > 0" class="px-6 lg:container mx-auto mt-20 mb-32">
<div class="text-center mb-10">
<h3 class="text-2xl font-bold mb-3">Was unsere Kunden sagen</h3>
<p class="text-gray-600 max-w-2xl mx-auto">Erfahrungen und Bewertungen von zufriedenen Kunden</p>
</div>
<LazyTestimonial
:testimonials="testimonials"
:show-title="false"
:show-rating="true"
:show-avatar="false"
:show-quote-icon="true"
primary-color="#374151"
star-color="#374151"
/>
</section>
-->
</main>
</template>
<script setup lang="ts">
import { Slide } from "vue3-carousel";
import productionImage01 from "~/assets/production/01.png";
import { trackEvent } from "~/utils/trackEvent";
import { scrollProgress } from "~/utils/scrollProgress";
import type { Testimonial } from "~/types";
const shopApi = useShopApi();
// Fetch covers with their products
const { data: coversData } = await useAsyncData("landing-covers", async () => {
try {
const { data } = await shopApi.getProductCovers();
// Standalone covers (e.g. Journaling Booklet) are excluded from the landing
// grid — they get a dedicated promo placement / direct link, not a row here.
const sellable = data.filter((cover: any) => !cover.attributes?.standalone);
const covers = await Promise.all(
sellable.map(async (cover: any) => {
const productsResponse = await shopApi.getCheapestProducts(cover.id, 1, 10);
return {
id: cover.id,
name: cover.attributes?.name,
sort: cover.attributes?.sort ?? 0,
copyText: cover.attributes?.copyText,
products: productsResponse?.data ?? []
};
})
);
return covers
.filter((c) => c.products.length > 0)
.sort((a, b) => a.sort - b.sort);
} catch (error) {
console.error("Error fetching covers:", error);
return [];
}
});
const covers = computed(() => coversData.value ?? []);
// Standalone covers (e.g. Journaling Booklet) → dedicated promo band with a
// direct link to the first product of that category. Link resolves by SLUG
// (never hardcoded ids — prod ids differ from dev).
const { data: promoData } = await useAsyncData("landing-promos", async () => {
try {
const { data } = await shopApi.getProductCovers();
const standalone = data.filter((c: any) => c.attributes?.standalone);
const promos = await Promise.all(
standalone.map(async (cover: any) => {
const productsResponse = await shopApi.getCheapestProducts(cover.id, 1, 1);
const first = productsResponse?.data?.[0] as any;
const img = first?.images?.images?.[0];
return {
id: cover.id,
name: first?.name ?? cover.attributes?.name,
details: cover.attributes?.copyText?.details,
sort: cover.attributes?.sort ?? 0,
productSlug: first?.slug,
image: img?.formats?.medium?.url ?? img?.formats?.small?.url ?? img?.url
};
})
);
return promos.filter((p) => p.productSlug).sort((a, b) => a.sort - b.sort);
} catch (error) {
console.error("Error fetching promos:", error);
return [];
}
});
const promoCovers = computed(() => promoData.value ?? []);
// Testimonials
const testimonials: Testimonial[] = [
{
name: "Maria S.",
purpose: "Skizzenbuch",
comment: "Endlich ein Papier, das meine Aquarellstifte aushält. Kein Durchdrücken, nichts wellt sich. Hab schon das dritte bestellt.",
rating: 5
},
{
name: "Thomas W.",
purpose: "Tagebuch",
comment: "Schreibe seit Jahren Tagebuch und das hier ist mit Abstand das beste Notizbuch, das ich hatte. Liegt super in der Hand.",
rating: 5
},
{
name: "Julia B.",
purpose: "Studium",
comment: "Benutze es für Vorlesungsmitschriften. Das Papier ist top, mein Füller schmiert nicht.",
rating: 4
},
{
name: "Sophia K.",
purpose: "Bullet Journal",
comment: "Die Bindung ist echt gut, das Buch bleibt flach liegen beim Schreiben. Und ich mag, dass es in Deutschland hergestellt wird.",
rating: 5
}
];
// Scroll tracking
const hasScrolled50 = ref(false);
const hasScrolled100 = ref(false);
function trackScrolling() {
if (hasScrolled100.value) return;
const progress = scrollProgress();
if (progress >= 50 && !hasScrolled50.value) {
trackEvent("start-page-scrolled-50");
hasScrolled50.value = true;
}
if (progress === 100) {
trackEvent("start-page-scrolled-100");
hasScrolled100.value = true;
}
}
// Toggle dark mode for hero section
const toggleDarkMode = inject<(value: boolean) => void>("toggleDarkMode");
onMounted(() => {
toggleDarkMode?.(true);
window.addEventListener("scroll", trackScrolling);
// Track start page view
trackEvent("start-page-viewed", {
coverCount: covers.value.length
});
});
onUnmounted(() => {
toggleDarkMode?.(false);
window.removeEventListener("scroll", trackScrolling);
});
// SEO Meta
useSeoMeta({
title: "MUELLERPRINTS - Handgefertigte Notizbücher aus Stuttgart",
description: "Handgefertigte Notizbücher mit 100% Recyclingpapier, traditioneller Fadenheftung und einzigartigen Einbanddesigns. Made in Stuttgart.",
ogTitle: "MUELLERPRINTS - Handgefertigte Notizbücher",
ogDescription: "Handgefertigte Notizbücher mit 100% Recyclingpapier, traditioneller Fadenheftung und einzigartigen Einbanddesigns.",
ogType: "website"
});
</script>