Full-page template for the public experience-focused offer detail view. Composes the Welcome Section (hero) with a glassy Contact Form (name, email, phone, message CTA), and a main content section with a two-column layout: left column holds description, gallery, upcoming offers table, and right column holds amenities, optional extras, FAQ and cancellation policy links. Rental options are House A, House B, or Both.
This is a standard-context public page (not the
glassy app shell). The booking widget uses
form-card--glassy
placed over the hero background โ its glassy surface is scoped to the
hero section only. All sections below the hero use standard DS classes
on light/neutral surfaces.
<!-- __('key') -->
comments for PL/EN localisation.
Two-column main content: left column (description + gallery + upcoming offers), right column (amenities + extras + FAQ/cancellation). Footer is not rendered here โ compose from navigations/footer.html.
๐ Navbar is composed separately โ see navigations/navbar.html.
Celebrate the long May weekend surrounded by blooming nature. BBQ, campfire evenings, and forest walks await your family or group.
Get in Touch
Interested in this offer? Send us a message and we'll get back to you shortly.
Escape the city and immerse yourself in the tranquility of Kampinos National Park. Our seasonal May weekend retreat gives you exclusive access to our forest property surrounded by pristine wilderness, wild flowers, and the gentle sounds of nature.
Whether you choose House A, House B, or book both for a larger group, you will enjoy dedicated outdoor spaces, a communal BBQ terrace, fire pit evenings, and direct access to forest trails starting from the property gate.
Future dates only ยท 6 per page
May 2026
1 โ 4
3 nights
Jun 2026
5 โ 8
3 nights
Jun 2026
15 โ 22
7 nights
Jul 2026
1 โ 7
6 nights
Aug 2026
10 โ 17
7 nights
โ
Sep 2026
5 โ 8
3 nights
BBQ Area
Covered BBQ terrace with grill and garden seating
Garden & Fire Pit
Shared outdoor space with campfire area
Free Wi-Fi
High-speed internet available in both houses
Pet Friendly
Pets welcome (prior notification required)
Free Parking
Private parking on property grounds
Private Bathroom
Each house has its own full bathroom
Bike Rental
Per bike per day โ book in advance
Breakfast Basket
Fresh local produce delivered to your door each morning
Firewood Bundle
Ready-to-use firewood for fire pit evenings
Optional extras can be requested in the inquiry message or confirmed with the host after booking.
๐ Footer is composed separately โ see navigations/footer.html.
Spaced card-row design. Each offer is a horizontal flex card with a date accent strip on the left. Status badge (Available / Last Minute / Sold Out), House A/B availability pills, price from, and two CTA buttons (Reserve / Ask Question). Shows 6 future offers per page with Prev/Next pagination.
Future dates only ยท 6 per page
May 2026
1 โ 4
3 nights
Jun 2026
15 โ 22
7 nights
Aug 2026
10 โ 17
7 nights
โ
Copy-ready page template. Replace placeholder content with dynamic PHP output. Compose Navbar and Footer from their respective DS pages. Translation key comments mark all visible labels. Always include a CSRF token in the inquiry form.
<!-- Navbar: compose from navigations/navbar.html (Guest variant) -->
<!-- HERO: Offer Header + Booking Widget -->
<section class="welcome-section">
<div class="welcome-section__bg">
<img
src="<?= htmlspecialchars($offer['hero_image_url']) ?>"
alt="<?= htmlspecialchars($offer['title']) ?>"
class="welcome-section__bg-image"
loading="eager"
/>
<div class="welcome-section__bg-overlay"></div>
</div>
<div class="welcome-section__content">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
<!-- Left: Offer Info -->
<div>
<span class="badge badge--primary mb-4"><?= __('offer.seasonal_badge') ?></span>
<span class="badge badge--overlay mb-6 inline-block"><?= __('offer.status.available') ?></span>
<h1 class="t-display-glass mb-4"><?= htmlspecialchars($offer['title']) ?></h1>
<p class="t-lead-glass mb-6"><?= htmlspecialchars($offer['lead']) ?></p>
<div class="badge-group mb-8">
<span class="badge badge--muted">๐ <?= htmlspecialchars($offer['dates_label']) ?></span>
<span class="badge badge--muted"><?= __('offer.min_nights', ['n' => $offer['min_nights']]) ?></span>
<span class="badge badge--muted">๐ฅ <?= __('offer.max_guests', ['n' => $offer['max_guests']]) ?></span>
<?php foreach ($offer['feature_badges'] as $badge): ?>
<span class="badge badge--muted"><?= htmlspecialchars($badge) ?></span>
<?php endforeach; ?>
</div>
</div>
<!-- Right: Contact Form (Glassy) -->
<div class="form-card form-card--glassy">
<div class="form-card__header">
<p class="t-lead-glass"><?= __('contact.widget.title') ?></p>
<p class="t-small-glass"><?= __('contact.widget.subtitle') ?></p>
</div>
<form class="form-card__body" action="/<?= $lang ?>/contact" method="post">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>" />
<input type="hidden" name="offer_ref" value="<?= htmlspecialchars($offer['slug'] ?? '') ?>" />
<div class="form-layout">
<!-- Message -->
<div class="field field--glass">
<label class="field__label"><?= __('contact.message') ?></label>
<div class="field__control">
<textarea class="input input--glass" name="message" rows="4"><?= htmlspecialchars($_POST['message'] ?? '') ?></textarea>
</div>
</div>
<!-- Email + Phone -->
<div class="form-grid">
<div class="field field--glass">
<label class="field__label"><?= __('contact.email') ?></label>
<div class="field__control">
<input type="email" class="input input--glass" name="email" required
value="<?= htmlspecialchars($_POST['email'] ?? '') ?>" />
</div>
</div>
<div class="field field--glass">
<label class="field__label"><?= __('contact.phone') ?> <span class="t-small t-muted"><?= __('form.optional') ?></span></label>
<div class="field__control">
<input type="tel" class="input input--glass" name="phone"
value="<?= htmlspecialchars($_POST['phone'] ?? '') ?>" />
</div>
</div>
</div>
</div><!-- /form-layout -->
<!-- Copy checkbox + Disclaimer -->
<div class="field field--glass mt-4">
<div class="field__control">
<label class="form-field__option">
<input type="checkbox" class="checkbox" name="send_copy" value="1"
<?= !empty($_POST['send_copy']) ? 'checked' : '' ?> />
<span class="t-body"><?= __('contact.send_copy') ?></span>
</label>
</div>
<p class="field__hint"><?= __('contact.disclaimer') ?></p>
</div>
<div class="form-card__footer">
<button type="submit" class="btn btn--primary btn--full">
<?= __('contact.cta_send') ?>
</button>
</div>
</form>
</div><!-- /contact form -->
</div>
</div>
</section>
<!-- MAIN CONTENT: Description + Gallery + Amenities -->
<section class="bg-white dark:bg-neutral-900 py-16">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-12">
<!-- Description + Gallery (2 of 3 cols) -->
<div class="lg:col-span-2 space-y-12">
<div>
<span class="badge badge--primary mb-4"><?= __('offer.section.experience') ?></span>
<h2 class="t-h2 mb-4"><?= htmlspecialchars($offer['description_heading']) ?></h2>
<?php foreach ($offer['description_paragraphs'] as $para): ?>
<p class="t-body t-muted mb-4"><?= htmlspecialchars($para) ?></p>
<?php endforeach; ?>
</div>
<div>
<h3 class="t-h4 mb-4"><?= __('offer.gallery.heading') ?></h3>
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
<?php foreach ($offer['gallery_images'] as $idx => $img): ?>
<div class="aspect-video rounded-lg overflow-hidden">
<img
src="<?= htmlspecialchars($img['url']) ?>"
alt="<?= htmlspecialchars($img['alt']) ?>"
class="w-full h-full object-cover"
loading="<?= $idx === 0 ? 'eager' : 'lazy' ?>"
/>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Upcoming Offers -->
<div>
<div class="flex flex-col sm:flex-row sm:items-end sm:justify-between mb-4 gap-2">
<div>
<span class="badge badge--primary mb-2"><?= __('offer.upcoming.badge') ?></span>
<h3 class="t-h3"><?= __('offer.upcoming.heading') ?></h3>
</div>
<p class="t-small t-muted"><?= __('offer.upcoming.subtext') ?></p>
</div>
<div class="space-y-3">
<?php foreach ($upcoming_offers as $upcoming): ?>
<div class="flex flex-col sm:flex-row items-stretch bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border <?= $upcoming['status'] === 'last_minute' ? 'border-amber-200 dark:border-amber-700/50' : 'border-neutral-200 dark:border-neutral-700' ?> hover:shadow-md transition-shadow overflow-hidden <?= $upcoming['status'] === 'sold_out' ? 'opacity-55' : '' ?>">
<div class="sm:w-44 flex-shrink-0 <?= $upcoming['status'] === 'last_minute' ? 'bg-amber-50 dark:bg-amber-900/20' : ($upcoming['status'] === 'sold_out' ? 'bg-neutral-100 dark:bg-neutral-700/30' : 'bg-primary-50 dark:bg-primary-900/30') ?> flex flex-col items-center justify-center p-4 border-r <?= $upcoming['status'] === 'last_minute' ? 'border-amber-100 dark:border-amber-700/40' : 'border-neutral-100 dark:border-neutral-700' ?>">
<p class="text-xs font-semibold uppercase tracking-wider"><?= htmlspecialchars($upcoming['month_label']) ?></p>
<p class="text-2xl font-bold leading-tight"><?= htmlspecialchars($upcoming['day_range']) ?></p>
<p class="text-xs text-neutral-500 dark:text-neutral-400 mt-1"><?= __('offer.nights', ['n' => $upcoming['nights']]) ?></p>
</div>
<div class="flex flex-1 flex-col sm:flex-row items-start sm:items-center gap-4 p-4 sm:px-6">
<div class="flex-1">
<?php if ($upcoming['status'] === 'available'): ?>
<span class="badge badge--overlay mb-2"><?= __('status.available') ?></span>
<?php elseif ($upcoming['status'] === 'last_minute'): ?>
<span class="badge badge--accent mb-2"><?= __('status.last_minute') ?></span>
<?php else: ?>
<span class="badge badge--muted mb-2"><?= __('status.sold_out') ?></span>
<?php endif; ?>
<div class="flex gap-3 mt-2">
<span class="inline-flex items-center gap-1 text-xs font-medium <?= $upcoming['house_a'] ? 'text-green-700 dark:text-green-400 bg-green-50 dark:bg-green-900/30' : 'text-neutral-400 bg-neutral-100 dark:bg-neutral-700' ?> px-2 py-1 rounded-full">
๐ <?= __('col.house_a') ?> <span class="font-bold"><?= $upcoming['house_a'] ? 'โ' : 'โ' ?></span>
</span>
<span class="inline-flex items-center gap-1 text-xs font-medium <?= $upcoming['house_b'] ? 'text-green-700 dark:text-green-400 bg-green-50 dark:bg-green-900/30' : 'text-neutral-400 bg-neutral-100 dark:bg-neutral-700' ?> px-2 py-1 rounded-full">
๐ <?= __('col.house_b') ?> <span class="font-bold"><?= $upcoming['house_b'] ? 'โ' : 'โ' ?></span>
</span>
</div>
</div>
<div class="text-right flex-shrink-0">
<?php if ($upcoming['price_from']): ?>
<p class="text-xs text-neutral-400"><?= __('price.from') ?></p>
<p class="text-xl font-bold text-primary-700 dark:text-primary-400 leading-tight"><?= htmlspecialchars($upcoming['price_from']) ?></p>
<p class="text-xs text-neutral-400"><?= __('price.per_night') ?></p>
<?php else: ?>
<p class="text-xl font-bold text-neutral-400">โ</p>
<?php endif; ?>
</div>
<div class="flex sm:flex-col gap-2 flex-shrink-0">
<?php if ($upcoming['status'] !== 'sold_out'): ?>
<a href="/<?= $lang ?>/inquiry?offer=<?= (int)$upcoming['id'] ?>" class="btn btn--primary btn--sm"><?= __('cta.reserve') ?></a>
<a href="/<?= $lang ?>/contact?offer=<?= (int)$upcoming['id'] ?>" class="btn btn--secondary btn--sm"><?= __('cta.ask') ?></a>
<?php else: ?>
<span class="btn btn--secondary btn--sm cursor-not-allowed"><?= __('status.sold_out') ?></span>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- Pagination -->
<div class="flex items-center justify-between mt-6">
<?php if ($pagination['prev_page']): ?>
<a href="?page=<?= $pagination['prev_page'] ?>" class="btn btn--secondary btn--sm"><?= __('pagination.prev') ?></a>
<?php else: ?>
<span class="btn btn--secondary btn--sm opacity-40 cursor-not-allowed"><?= __('pagination.prev') ?></span>
<?php endif; ?>
<p class="t-small t-muted"><?= __('pagination.info', ['current' => $pagination['current'], 'total' => $pagination['total'], 'count' => $pagination['count']]) ?></p>
<?php if ($pagination['next_page']): ?>
<a href="?page=<?= $pagination['next_page'] ?>" class="btn btn--secondary btn--sm"><?= __('pagination.next') ?></a>
<?php else: ?>
<span class="btn btn--secondary btn--sm opacity-40 cursor-not-allowed"><?= __('pagination.next') ?></span>
<?php endif; ?>
</div>
</div>
</div>
<!-- Amenities Sidebar (1 of 3 cols) -->
<div>
<span class="badge badge--primary mb-4"><?= __('offer.section.amenities') ?></span>
<h3 class="t-h4 mb-6"><?= __('offer.amenities.heading') ?></h3>
<ul class="space-y-4">
<?php foreach ($offer['amenities'] as $amenity): ?>
<li class="flex items-start gap-3">
<span class="text-lg leading-none mt-0.5"><?= htmlspecialchars($amenity['icon']) ?></span>
<div>
<p class="t-body t-bold"><?= htmlspecialchars($amenity['label']) ?></p>
<p class="t-small t-muted"><?= htmlspecialchars($amenity['description']) ?></p>
</div>
</li>
<?php endforeach; ?>
</ul>
<!-- Optional / Extra Amenities (paid) -->
<?php if (!empty($offer['extras'])): ?>
<div class="mt-8 pt-6 border-t border-neutral-200 dark:border-neutral-700">
<span class="badge badge--accent mb-4"><?= __('offer.section.extras') ?></span>
<h4 class="t-h5 mb-4"><?= __('offer.extras.heading') ?></h4>
<ul class="space-y-4">
<?php foreach ($offer['extras'] as $extra): ?>
<li class="flex items-start gap-3">
<span class="text-lg leading-none mt-0.5"><?= htmlspecialchars($extra['icon']) ?></span>
<div>
<p class="t-body t-bold"><?= htmlspecialchars($extra['label']) ?></p>
<p class="t-small t-muted"><?= htmlspecialchars($extra['description']) ?></p>
</div>
</li>
<?php endforeach; ?>
</ul>
<p class="t-small t-muted mt-4"><?= __('offer.extras.note') ?></p>
</div>
<?php endif; ?>
<!-- Trust Signals: FAQ + Cancellation -->
<div class="mt-8 pt-6 border-t border-neutral-200 dark:border-neutral-700 space-y-3">
<span class="badge badge--primary mb-2"><?= __('offer.section.trust') ?></span>
<a href="/<?= $lang ?>/faq" class="flex items-start gap-3 p-3 bg-neutral-50 dark:bg-neutral-700/50 border border-neutral-200 dark:border-neutral-600 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-700 transition">
<span class="text-xl leading-none">โ</span>
<div>
<p class="t-small t-bold"><?= __('trust.faq.title') ?></p>
<span class="t-small text-primary-600 dark:text-primary-400"><?= __('trust.faq.cta') ?></span>
</div>
</a>
<a href="/<?= $lang ?>/cancellation-policy" class="flex items-start gap-3 p-3 bg-neutral-50 dark:bg-neutral-700/50 border border-neutral-200 dark:border-neutral-600 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-700 transition">
<span class="text-xl leading-none">๐</span>
<div>
<p class="t-small t-bold"><?= __('trust.cancellation.title') ?></p>
<p class="t-small t-muted"><?= __('trust.cancellation.summary') ?></p>
<span class="t-small text-primary-600 dark:text-primary-400"><?= __('trust.cancellation.cta') ?></span>
</div>
</a>
</div>
</div>
</div>
</div>
</section>
<!-- Footer: compose from navigations/footer.html -->
<!-- Footer: compose from navigations/footer.html -->
loading="eager". All gallery images below the fold must use
loading="lazy".
Guest navigation bar โ compose with this template
Marketing footer โ compose with this template
Full-height hero classes used for the offer header
Glassy form card used for the booking widget
Date inputs, selects, and radio buttons in glassy context