Complete carousel section for displaying available offers. Composes Badge Element, Typography Element, and Offer Card pattern with responsive carousel navigation (arrows + dots). Built with Alpine.js for reactive state management.
Full "Book Your Experience" section with badge label, centered header, and offer cards carousel (3 pages × 3 cards).
Choose from our seasonal experiences in the heart of Kampinos Forest. Each offer includes accommodation in one or both of our cozy houses.
Complete section markup using Alpine.js for state management. Composes Badge, Typography, and Offer Card patterns with carousel wrapper.
<section class="book-your-experience-section">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Header -->
<div class="text-center mb-12 lg:mb-16">
<span class="badge badge--primary mb-4">Book Your Experience</span>
<h2 class="t-h2 mb-4">Upcoming Available Offers</h2>
<p class="t-body t-muted max-w-2xl mx-auto">
Choose from our seasonal experiences in the heart of Kampinos Forest.
</p>
</div>
<!-- Carousel with Alpine.js -->
<div x-data="{
currentPage: 0,
totalPages: 3,
cardsPerPage: 3,
next() { this.currentPage = this.currentPage >= this.totalPages - 1 ? 0 : this.currentPage + 1 },
prev() { this.currentPage = this.currentPage <= 0 ? this.totalPages - 1 : this.currentPage - 1 },
getPageOffers() {
const start = this.currentPage * this.cardsPerPage;
return [start, start + 1, start + 2];
},
offers: [ /* 9 offer objects */ ]
}" class="relative">
<!-- Left Arrow -->
<button @click="prev()" class="hidden md:flex ...">...</button>
<!-- Cards Grid -->
<div class="flex-grow grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<template x-for="(offerIndex, i) in getPageOffers()" :key="offerIndex">
<div :class="{ 'hidden md:flex': i === 1, 'hidden lg:flex': i === 2 }">
<div class="offer-card w-full">
<!-- Card content with x-text binding -->
</div>
</div>
</template>
</div>
<!-- Right Arrow -->
<button @click="next()" class="hidden md:flex ...">...</button>
<!-- Mobile: Arrow Buttons Below -->
<div class="flex justify-center gap-4 md:hidden mt-6">
<button @click="prev()">...</button>
<button @click="next()">...</button>
</div>
<!-- Dots Pagination -->
<div class="flex justify-center gap-2 mt-8">
<template x-for="(dot, page) in totalPages" :key="page">
<button @click="currentPage = page" :class="{ 'bg-primary-600': currentPage === page }"></button>
</template>
</div>
</div>
</div>
</section>
<!-- Alpine.js via CDN -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script>
.badge .badge--primary)
for section label
.t-h2,
.t-lead) for heading/intro
x-data, x-for,
@click, :class directives
aria-label to all navigation buttons
This section composes three existing Design System patterns with Alpine.js reactive carousel state.
Section label using .badge .badge--primary from Badge
Element module.
Heading (.t-h2) and intro text (.t-lead)
using Typography Element classes.
Each carousel slide contains an .offer-card from the
Offer Card pattern.
Reactive state management for carousel navigation using
x-data, x-for, @click, and
:class directives.
CSS: /assets/css/ui-system.css
JS: Alpine.js CDN (v3.14.8)