Files
landingpage-haus-schleusingen/app/views/home/index.php
Hermes 4bc035b783 fix(i18n): map gallery + hero + floorplan images to real filenames; force-DE on legal pages
- home: map 9/12 gallery items to real filenames (Wohnzimmer-1 -> wohnzimmer2.png,
  Badezimmer-1 -> Bad.jpg, etc.); remove 3 items whose source images are missing
- home: hero-bg.jpg -> Außenansicht-2.webp (file exists)
- home: floorplan image -> /bilder/grundrisse/<name>.png (subdir + correct name)
- layout: og:image fallback Aussenansicht-2.webp (ASCII) -> Außenansicht-2.png (UTF-8)
- layout: hero-bg.jpg fallback -> Außenansicht-2.png (UTF-8)
- Controller::render(): add $forceLocale param for legal pages
- ImpressumController / DatenschutzController: force 'de' (TMG §5 / GDPR)
  so <html lang=de> is emitted regardless of cookie
2026-06-04 16:13:54 +00:00

356 lines
18 KiB
PHP

<?php
declare(strict_types=1);
/**
* Home page — page body only (nav/footer/lightbox live in layouts/main.php).
*
* @var string $locale
* @var array<string,mixed> $formData
* @var list<string> $formErrors Translation keys, resolved via t()
* @var bool $formSuccess
* @var array<string,string> $interestKeys ['visit' => 'form.interest.visit', ...]
* @var callable(string):string $escapeContactValue
* @var callable(string,array,string=):string $t
*/
$gridItems = [
// NOTE: image filenames reflect the actual files in public/bilder/ on the server.
// 3 items were removed (gästezimmer / wohnbereich / wohnbereich-detail)
// because no matching files exist in the image inventory.
['img' => 'bilder/Außenansicht-2.png', 'key' => 'gallery.exterior', 'alt' => 'gallery.alt.exterior', 'class' => 'span-2 row-2'],
['img' => 'bilder/wohnzimmer2.png', 'key' => 'gallery.living', 'alt' => 'gallery.alt.living', 'class' => 'span-2 row-1'],
['img' => 'bilder/Küche 1.jpg', 'key' => 'gallery.kitchen', 'alt' => 'gallery.alt.kitchen', 'class' => ''],
['img' => 'bilder/schlafzimmer.png', 'key' => 'gallery.bedroom', 'alt' => 'gallery.alt.bedroom', 'class' => ''],
['img' => 'bilder/Bad.jpg', 'key' => 'gallery.bath', 'alt' => 'gallery.alt.bath', 'class' => ''],
['img' => 'bilder/Kinderzimmer 2.jpg', 'key' => 'gallery.kid1', 'alt' => 'gallery.alt.kid1', 'class' => ''],
['img' => 'bilder/Kinderzimmer 3.jpg', 'key' => 'gallery.kid2', 'alt' => 'gallery.alt.kid2', 'class' => ''],
['img' => 'bilder/kinderzimmer 2 2.webp', 'key' => 'gallery.kid_detail', 'alt' => 'gallery.alt.kid_detail', 'class' => 'span-2 row-1'],
['img' => 'bilder/Außenansicht-2.png', 'key' => 'gallery.area3', 'alt' => 'gallery.alt.exterior', 'class' => 'span-2 row-1'],
];
?>
<header class="hero" id="hero">
<img src="/bilder/Außenansicht-2.webp" alt="" class="hero-bg" id="heroBg" loading="eager" decoding="async" fetchpriority="high">
<div class="hero-content" id="heroContent">
<span class="hero-tag"><?= htmlspecialchars($t('hero.tag'), ENT_QUOTES) ?></span>
<h1 class="hero-h1">
<span class="hero-line"><?= htmlspecialchars($t('hero.h1.line1'), ENT_QUOTES) ?></span>
<span class="hero-line accent"><?= htmlspecialchars($t('hero.h1.line2'), ENT_QUOTES) ?></span>
<span class="hero-line"><?= htmlspecialchars($t('hero.h1.line3'), ENT_QUOTES) ?></span>
</h1>
<ul class="hero-meta" aria-label="<?= htmlspecialchars($t('hero.address'), ENT_QUOTES) ?>">
<li class="hero-meta-item"><?= htmlspecialchars($t('hero.address'), ENT_QUOTES) ?></li>
<li class="hero-meta-item"><?= htmlspecialchars($t('hero.area'), ENT_QUOTES) ?></li>
<li class="hero-meta-item"><?= htmlspecialchars($t('hero.rooms'), ENT_QUOTES) ?></li>
<li class="hero-meta-item"><?= htmlspecialchars($t('hero.floors'), ENT_QUOTES) ?></li>
</ul>
<a class="hero-cta" href="#galerie"><?= htmlspecialchars($t('hero.discover'), ENT_QUOTES) ?> ↓</a>
</div>
</header>
<section class="facts-strip" aria-label="<?= htmlspecialchars($t('intro.stats.area'), ENT_QUOTES) ?>">
<div class="fact"><span class="fact-value">227</span><span class="fact-unit"><?= htmlspecialchars($t('facts.area'), ENT_QUOTES) ?></span></div>
<div class="fact"><span class="fact-value">6</span><span class="fact-unit"><?= htmlspecialchars($t('facts.rooms'), ENT_QUOTES) ?></span></div>
<div class="fact"><span class="fact-value">3</span><span class="fact-unit"><?= htmlspecialchars($t('facts.floors'), ENT_QUOTES) ?></span></div>
<div class="fact"><span class="fact-value">1.300</span><span class="fact-unit"><?= htmlspecialchars($t('facts.rent'), ENT_QUOTES) ?></span></div>
</section>
<section class="intro" id="intro">
<div class="intro-grid">
<div class="intro-text">
<span class="section-eyebrow"><?= htmlspecialchars($t('intro.eyebrow'), ENT_QUOTES) ?></span>
<h2><?= htmlspecialchars($t('intro.h2'), ENT_QUOTES) ?></h2>
<p><?= htmlspecialchars($t('intro.p1'), ENT_QUOTES) ?></p>
<p><?= htmlspecialchars($t('intro.p2'), ENT_QUOTES) ?></p>
</div>
<aside class="intro-stats">
<div class="stat">
<span class="stat-label"><?= htmlspecialchars($t('intro.stats.area'), ENT_QUOTES) ?></span>
<span class="stat-value">196,5 m²</span>
</div>
<div class="stat">
<span class="stat-label"><?= htmlspecialchars($t('intro.stats.terrace'), ENT_QUOTES) ?></span>
<span class="stat-value">35,8 m²</span>
</div>
<div class="stat">
<span class="stat-label"><?= htmlspecialchars($t('intro.stats.garage'), ENT_QUOTES) ?></span>
<span class="stat-value">2 PKW</span>
</div>
<span class="intro-badge"><?= htmlspecialchars($t('intro.badge'), ENT_QUOTES) ?></span>
</aside>
</div>
</section>
<section class="gallery-section" id="galerie" aria-label="<?= htmlspecialchars($t('gallery.aria'), ENT_QUOTES) ?>">
<div class="section-head">
<span class="section-eyebrow"><?= htmlspecialchars($t('gallery.eyebrow'), ENT_QUOTES) ?></span>
<h2><?= htmlspecialchars($t('gallery.h2'), ENT_QUOTES) ?></h2>
</div>
<div class="gallery-grid">
<?php foreach ($gridItems as $item): ?>
<button type="button" class="grid-item <?= htmlspecialchars($item['class'], ENT_QUOTES) ?>"
data-img="<?= htmlspecialchars($item['img'], ENT_QUOTES) ?>"
aria-label="<?= htmlspecialchars($t($item['key']) . $t('gallery.zoom'), ENT_QUOTES) ?>">
<img src="/<?= htmlspecialchars($item['img'], ENT_QUOTES) ?>" alt="<?= htmlspecialchars($t($item['alt']), ENT_QUOTES) ?>" loading="lazy" decoding="async">
<span class="grid-caption"><?= htmlspecialchars($t($item['key']), ENT_QUOTES) ?></span>
</button>
<?php endforeach; ?>
</div>
</section>
<section class="floors-section" id="grundriss" aria-label="<?= htmlspecialchars($t('floors.eyebrow'), ENT_QUOTES) ?>">
<div class="section-head">
<span class="section-eyebrow"><?= htmlspecialchars($t('floors.eyebrow'), ENT_QUOTES) ?></span>
<h2><?= htmlspecialchars($t('floors.h2'), ENT_QUOTES) ?></h2>
</div>
<?php
$floorImageMap = [
'eg' => 'bilder/grundrisse/EG.png',
'og1' => 'bilder/grundrisse/OG 1 2.png',
'og2' => 'bilder/grundrisse/OG 2 grundriss.png',
'attic' => 'bilder/grundrisse/Dachboden unten.png',
];
$floors = [
['id' => 'eg', 'titleKey' => 'floors.eg.title', 'areaKey' => 'floors.eg.area', 'altKey' => 'floors.alt.eg',
'rooms' => [
['key' => 'floors.room.hall', 'size' => '21,0'],
['key' => 'floors.room.wc', 'size' => '1,7'],
['key' => 'floors.room.garage', 'size' => '23,4'],
['key' => 'floors.room.storage1', 'size' => '5,5'],
['key' => 'floors.room.heating', 'size' => '11,2'],
['key' => 'floors.room.storage2', 'size' => '6,4'],
]],
['id' => 'og1', 'titleKey' => 'floors.og1.title', 'areaKey' => 'floors.og1.area', 'altKey' => 'floors.alt.og1',
'rooms' => [
['key' => 'floors.room.living', 'size' => '42,6'],
['key' => 'floors.room.kitchen', 'size' => '18,4'],
['key' => 'floors.room.guest', 'size' => '11,5'],
['key' => 'floors.room.bath', 'size' => '9,8'],
['key' => 'floors.room.storage1','size' => '3,4'],
['key' => 'floors.room.heating', 'size' => '8,0'],
]],
['id' => 'og2', 'titleKey' => 'floors.og2.title', 'areaKey' => 'floors.og2.area', 'altKey' => 'floors.alt.og2',
'rooms' => [
['key' => 'floors.room.bedroom', 'size' => '18,0'],
['key' => 'floors.room.kid1', 'size' => '21,7'],
['key' => 'floors.room.kid2', 'size' => '15,7'],
['key' => 'floors.room.bath', 'size' => '6,4'],
]],
['id' => 'attic','titleKey' => 'floors.attic.title', 'areaKey' => 'floors.attic.area', 'altKey' => 'floors.alt.attic',
'rooms' => [
['key' => 'floors.room.attic_low', 'size' => ''],
['key' => 'floors.room.attic_mid', 'size' => ''],
['key' => 'floors.room.attic_high', 'size' => ''],
]],
];
?>
<div class="floors-accordion">
<?php foreach ($floors as $floor): ?>
<details class="floor-item" id="floor-<?= htmlspecialchars($floor['id'], ENT_QUOTES) ?>">
<summary class="floor-header">
<span class="floor-title"><?= htmlspecialchars($t($floor['titleKey']), ENT_QUOTES) ?></span>
<span class="floor-area"><?= htmlspecialchars($t($floor['areaKey']), ENT_QUOTES) ?></span>
</summary>
<div class="floor-body">
<img src="/<?= htmlspecialchars($floorImageMap[$floor['id']] ?? 'bilder/grundrisse/EG.png', ENT_QUOTES) ?>"
alt="<?= htmlspecialchars($t($floor['altKey']), ENT_QUOTES) ?>"
loading="lazy" decoding="async"
class="floor-plan-img">
<ul class="room-list">
<?php foreach ($floor['rooms'] as $room): ?>
<li>
<span class="room-name"><?= htmlspecialchars($t($room['key']), ENT_QUOTES) ?></span>
<?php if ($room['size'] !== ''): ?>
<span class="room-size"><?= htmlspecialchars($room['size'], ENT_QUOTES) ?> m²</span>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>
</details>
<?php endforeach; ?>
</div>
</section>
<section class="pricing-section" id="miete" aria-label="<?= htmlspecialchars($t('rent.aria'), ENT_QUOTES) ?>">
<div class="section-head">
<span class="section-eyebrow"><?= htmlspecialchars($t('rent.eyebrow'), ENT_QUOTES) ?></span>
<h2><?= htmlspecialchars($t('rent.h2'), ENT_QUOTES) ?></h2>
</div>
<div class="pricing-grid">
<div class="price-card">
<span class="price-label"><?= htmlspecialchars($t('rent.cold'), ENT_QUOTES) ?></span>
<span class="price-value">1.300 €</span>
<span class="price-unit"><?= htmlspecialchars($t('rent.per_month'), ENT_QUOTES) ?></span>
</div>
<div class="price-card">
<span class="price-label"><?= htmlspecialchars($t('rent.warm'), ENT_QUOTES) ?></span>
<span class="price-value">1.600 €</span>
<span class="price-unit"><?= htmlspecialchars($t('rent.warm_includes'), ENT_QUOTES) ?></span>
</div>
<div class="price-card">
<span class="price-label"><?= htmlspecialchars($t('rent.deposit'), ENT_QUOTES) ?></span>
<span class="price-value">2.600 €</span>
<span class="price-unit"><?= htmlspecialchars($t('rent.deposit_months'), ENT_QUOTES) ?></span>
</div>
</div>
<dl class="rent-notes">
<dt><?= htmlspecialchars($t('rent.note.available'), ENT_QUOTES) ?></dt>
<dd><?= htmlspecialchars($t('rent.note.available_val'), ENT_QUOTES) ?></dd>
<dt><?= htmlspecialchars($t('rent.note.costs'), ENT_QUOTES) ?></dt>
<dd><?= htmlspecialchars($t('rent.note.costs_val'), ENT_QUOTES) ?></dd>
<dt><?= htmlspecialchars($t('rent.note.energy'), ENT_QUOTES) ?></dt>
<dd><?= htmlspecialchars($t('rent.note.energy_val'), ENT_QUOTES) ?></dd>
<dt><?= htmlspecialchars($t('rent.note.pets'), ENT_QUOTES) ?></dt>
<dd><?= htmlspecialchars($t('rent.note.pets_val'), ENT_QUOTES) ?></dd>
</dl>
</section>
<section class="lage-section" id="lage" aria-label="<?= htmlspecialchars($t('loc.eyebrow'), ENT_QUOTES) ?>">
<div class="section-head">
<span class="section-eyebrow"><?= htmlspecialchars($t('loc.eyebrow'), ENT_QUOTES) ?></span>
<h2><?= htmlspecialchars($t('loc.h2'), ENT_QUOTES) ?></h2>
</div>
<div class="lage-grid">
<ul class="lage-features">
<li>
<span class="lage-feature-title"><?= htmlspecialchars($t('loc.shopping'), ENT_QUOTES) ?></span>
<span class="lage-feature-desc"><?= htmlspecialchars($t('loc.shopping_desc'), ENT_QUOTES) ?></span>
</li>
<li>
<span class="lage-feature-title"><?= htmlspecialchars($t('loc.transport'), ENT_QUOTES) ?></span>
<span class="lage-feature-desc"><?= htmlspecialchars($t('loc.transport_desc'), ENT_QUOTES) ?></span>
</li>
<li>
<span class="lage-feature-title"><?= htmlspecialchars($t('loc.center'), ENT_QUOTES) ?></span>
<span class="lage-feature-desc"><?= htmlspecialchars($t('loc.center_desc'), ENT_QUOTES) ?></span>
</li>
</ul>
<div class="lage-map">
<iframe
title="<?= htmlspecialchars($t('loc.map_title'), ENT_QUOTES) ?>"
src="https://www.openstreetmap.org/export/embed.html?bbox=10.7535%2C50.5095%2C10.7705%2C50.5185&amp;layer=mapnik&amp;marker=50.5140%2C10.7620"
loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
<p class="lage-address">
<strong><?= htmlspecialchars($t('loc.address'), ENT_QUOTES) ?>:</strong><br>
<?= /* address HTML is XSS-safe — composed of trusted translations */ $t('loc.address_val') ?>
</p>
</div>
</div>
</section>
<section class="contact-section" id="kontakt" aria-label="<?= htmlspecialchars($t('contact.aria'), ENT_QUOTES) ?>">
<div class="section-head">
<span class="section-eyebrow"><?= htmlspecialchars($t('contact.eyebrow'), ENT_QUOTES) ?></span>
<h2><?= htmlspecialchars($t('contact.h2'), ENT_QUOTES) ?> <em><?= htmlspecialchars($t('contact.h2_em'), ENT_QUOTES) ?></em></h2>
<p class="contact-intro"><?= htmlspecialchars($t('contact.intro'), ENT_QUOTES) ?></p>
</div>
<div id="form-result" class="form-result" role="status" aria-live="polite">
<?php if ($formSuccess): ?>
<div class="form-success">
<strong><?= htmlspecialchars($t('contact.success'), ENT_QUOTES) ?></strong>
<p><?= htmlspecialchars($t('contact.success_sub'), ENT_QUOTES) ?></p>
</div>
<?php elseif (!empty($formErrors)): ?>
<div class="form-errors" role="alert">
<ul>
<?php foreach ($formErrors as $errKey): ?>
<li><?= htmlspecialchars($t($errKey), ENT_QUOTES) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
<form class="contact-form" method="post" action="/#kontakt" novalidate>
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token'] ?? '', ENT_QUOTES) ?>">
<input type="hidden" name="form_time" value="<?= htmlspecialchars((string) time(), ENT_QUOTES) ?>">
<div class="form-hp" aria-hidden="true">
<label for="website-hp"><?= htmlspecialchars($t('contact.hp_label'), ENT_QUOTES) ?></label>
<input type="text" id="website-hp" name="website" tabindex="-1" autocomplete="off">
</div>
<div class="form-row">
<div class="form-field">
<label for="fname"><?= htmlspecialchars($t('contact.fname'), ENT_QUOTES) ?></label>
<input type="text" id="fname" name="fname" required maxlength="80" autocomplete="given-name"
value="<?= $escapeContactValue($formData['fname'] ?? '') ?>"
<?= !empty($formFieldErrors['fname']) ? 'aria-invalid="true" aria-describedby="err-fname"' : '' ?>>
<?php if (!empty($formFieldErrors['fname'])): ?>
<p id="err-fname" class="form-field-error"><?= htmlspecialchars($t($formFieldErrors['fname'][0]), ENT_QUOTES) ?></p>
<?php endif; ?>
</div>
<div class="form-field">
<label for="lname"><?= htmlspecialchars($t('contact.lname'), ENT_QUOTES) ?></label>
<input type="text" id="lname" name="lname" required maxlength="80" autocomplete="family-name"
value="<?= $escapeContactValue($formData['lname'] ?? '') ?>"
<?= !empty($formFieldErrors['lname']) ? 'aria-invalid="true" aria-describedby="err-lname"' : '' ?>>
<?php if (!empty($formFieldErrors['lname'])): ?>
<p id="err-lname" class="form-field-error"><?= htmlspecialchars($t($formFieldErrors['lname'][0]), ENT_QUOTES) ?></p>
<?php endif; ?>
</div>
</div>
<div class="form-row">
<div class="form-field">
<label for="email"><?= htmlspecialchars($t('contact.email'), ENT_QUOTES) ?></label>
<input type="email" id="email" name="email" required maxlength="120" autocomplete="email"
value="<?= $escapeContactValue($formData['email'] ?? '') ?>"
<?= !empty($formFieldErrors['email']) ? 'aria-invalid="true" aria-describedby="err-email"' : '' ?>>
<?php if (!empty($formFieldErrors['email'])): ?>
<p id="err-email" class="form-field-error"><?= htmlspecialchars($t($formFieldErrors['email'][0]), ENT_QUOTES) ?></p>
<?php endif; ?>
</div>
<div class="form-field">
<label for="phone"><?= htmlspecialchars($t('contact.phone'), ENT_QUOTES) ?></label>
<input type="tel" id="phone" name="phone" maxlength="40" autocomplete="tel"
value="<?= $escapeContactValue($formData['phone'] ?? '') ?>">
</div>
</div>
<div class="form-field">
<label for="interest"><?= htmlspecialchars($t('contact.interest'), ENT_QUOTES) ?></label>
<select id="interest" name="interest" required>
<?php
$currentInterest = $formData['interest'] ?? 'visit';
$interestLabels = [
'visit' => 'contact.interest_visit',
'info' => 'contact.interest_info',
'apply' => 'contact.interest_apply',
];
foreach ($interestLabels as $value => $labelKey): ?>
<option value="<?= htmlspecialchars($value, ENT_QUOTES) ?>"
<?= $currentInterest === $value ? 'selected' : '' ?>>
<?= htmlspecialchars($t($labelKey), ENT_QUOTES) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-field">
<label for="message"><?= htmlspecialchars($t('contact.message'), ENT_QUOTES) ?></label>
<textarea id="message" name="message" required rows="6" maxlength="2000"
placeholder="<?= htmlspecialchars($t('contact.message'), ENT_QUOTES) ?>"
<?= !empty($formFieldErrors['message']) ? 'aria-invalid="true" aria-describedby="err-message"' : ''
?>><?= $escapeContactValue($formData['message'] ?? '') ?></textarea>
<?php if (!empty($formFieldErrors['message'])): ?>
<p id="err-message" class="form-field-error"><?= htmlspecialchars($t($formFieldErrors['message'][0]), ENT_QUOTES) ?></p>
<?php endif; ?>
</div>
<button type="submit" class="form-submit"><?= htmlspecialchars($t('contact.submit'), ENT_QUOTES) ?></button>
<p class="contact-direct"><?= htmlspecialchars($t('contact.direct'), ENT_QUOTES) ?>
<a href="mailto:mki@kies-media.de">mki@kies-media.de</a>
</p>
</form>
</section>