4 Commits

Author SHA1 Message Date
Claw (AI)
73635a5f03 fix(js): improve lightbox WebP fallback error handler
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
- Use .off('error') to prevent stacking error handlers
- Simplify fallback logic: only replace .webp → .png on error
- Prevents infinite error loops
2026-05-15 07:57:09 +00:00
Claw (AI)
b237cb6315 fix(images): remove unused masonry.js and fix broken references
- Delete js/masonry.pkgd.min.js (24 KB, never referenced in HTML)
- Fix bad3.jpg → Bad-3.webp reference (was 404)
- Fix WhatsApp Image reference → replaced with Bad-4.webp (existing image)
- Update data-img attributes to use WebP paths
2026-05-15 07:57:09 +00:00
Claw (AI)
98cb53df09 fix(images): update nginx with gzip and 30d cache headers
- Enable gzip for CSS, JS, SVG, JSON, XML
- Add 30-day cache headers for static assets (images, CSS, JS, fonts)
- Set Cache-Control: public, immutable for static files
2026-05-15 07:57:09 +00:00
Claw (AI)
8666bc1eec feat(images): convert all images to WebP with 87% size reduction
- Convert 34 images (PNG/JPG) to WebP at quality 80
- Total savings: 21.6 MB → 2.8 MB (87% reduction)
- Add <picture> elements with WebP source + original fallback
- Add loading=lazy to all below-the-fold images
- Update lightbox to serve WebP images with error fallback
2026-05-15 07:57:09 +00:00
50 changed files with 228 additions and 356 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
bilder/Außenansicht-2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

BIN
bilder/Bad-2-small.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
bilder/Bad-2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

BIN
bilder/Bad-3-small.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
bilder/Bad-3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

BIN
bilder/Bad-4-small.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
bilder/Bad-4.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
bilder/Bad-small.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
bilder/Bad.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
bilder/Kinderzimmer 2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
bilder/Kinderzimmer 3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
bilder/Kinderzimmer.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
bilder/Küche 1-small.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
bilder/Küche 1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1,10 +0,0 @@
{
"name": "Haus Schleusingen",
"short_name": "HS",
"icons": [
{ "src": "/bilder/favicon/favicon-32x32.png", "sizes": "32x32", "type": "image/png" },
{ "src": "/bilder/favicon/favicon-16x16.png", "sizes": "16x16", "type": "image/png" }
],
"theme_color": "#1c1917",
"background_color": "#fafaf9"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
bilder/grundrisse/EG.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
bilder/schlafzimmer.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
bilder/wohnzimmer2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -1,55 +1,7 @@
/* SKIP LINK */
.skip-link {
position: absolute;
left: -9999px;
top: 0;
background: var(--accent);
color: var(--white);
padding: 0.75rem 1.5rem;
font-size: 0.85rem;
font-weight: 500;
z-index: 200;
text-decoration: none;
transition: none;
}
.skip-link:focus {
left: 0;
outline: 2px solid var(--white);
outline-offset: 2px;
}
/* FOCUS VISIBLE */
*:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.lightbox-close:focus-visible {
outline: 2px solid var(--white);
outline-offset: 2px;
}
button:focus-visible,
a:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.grid-item:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.floor-header:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
:root { :root {
--cream: #f5f0e8; --cream: #f5f0e8;
--warm: #e8dfd0; --warm: #e8dfd0;
--stone: #7a7062; --stone: #9e9485;
--dark: #1c1a17; --dark: #1c1a17;
--charcoal: #2e2b26; --charcoal: #2e2b26;
--accent: #8b6914; --accent: #8b6914;

View File

@@ -13,6 +13,7 @@ module.exports = [
sourceType: "script", sourceType: "script",
globals: { globals: {
...globals.browser, ...globals.browser,
...globals.jquery,
}, },
}, },
plugins: { plugins: {

247
index.php
View File

@@ -126,11 +126,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/png" sizes="32x32" href="/bilder/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/bilder/favicon/favicon-16x16.png">
<link rel="icon" type="image/x-icon" href="/bilder/favicon/favicon.ico">
<link rel="apple-touch-icon" sizes="180x180" href="/bilder/favicon/apple-touch-icon.png">
<link rel="manifest" href="/bilder/favicon/site.webmanifest">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Einfamilienhaus mieten Schleusingen | 227 m², 6 Zimmer | 1.300 € Kaltmiete</title> <title>Einfamilienhaus mieten Schleusingen | 227 m², 6 Zimmer | 1.300 € Kaltmiete</title>
<meta name="description" content="Einfamilienhaus zur Langzeitmiete in Schleusingen: 227 m² Wohnfläche, 6 Zimmer, 3 Etagen mit Dachterrasse. Kaltmiete 1.300 €. Bahnhofstraße 10, 98553 Schleusingen. Ab sofort verfügbar." /> <meta name="description" content="Einfamilienhaus zur Langzeitmiete in Schleusingen: 227 m² Wohnfläche, 6 Zimmer, 3 Etagen mit Dachterrasse. Kaltmiete 1.300 €. Bahnhofstraße 10, 98553 Schleusingen. Ab sofort verfügbar." />
@@ -187,11 +182,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</script> </script>
<link rel="stylesheet" href="fonts/fonts.css" /> <link rel="stylesheet" href="fonts/fonts.css" />
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<link rel="stylesheet" href="css/haus-schleusingen.css" /> <link rel="stylesheet" href="css/haus-schleusingen.css" />
</head> </head>
<body> <body>
<a href="#main-content" class="skip-link">Zum Inhalt springen</a> <nav id="navbar">
<nav id="navbar" role="navigation" aria-label="Hauptnavigation">
<div class="nav-logo">Bahnhofstraße 10</div> <div class="nav-logo">Bahnhofstraße 10</div>
<button class="nav-hamburger" aria-label="Navigation öffnen" aria-expanded="false"> <button class="nav-hamburger" aria-label="Navigation öffnen" aria-expanded="false">
<span></span> <span></span>
@@ -215,7 +210,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<div <div
class="hero-bg" class="hero-bg"
id="heroBg" id="heroBg"
style="background-image: url(bilder/Außenansicht-2.png)" style="background-image: url(bilder/Außenansicht-2.webp)"
></div> ></div>
<div class="hero-overlay"></div> <div class="hero-overlay"></div>
<div class="hero-content" id="heroContent"> <div class="hero-content" id="heroContent">
@@ -240,7 +235,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div> </div>
</section> </section>
<main id="main-content">
<div class="facts-strip"> <div class="facts-strip">
<div class="fact"> <div class="fact">
<div class="fact-val">227</div> <div class="fact-val">227</div>
@@ -289,12 +283,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div> </div>
</div> </div>
<div class="intro-img" data-animate> <div class="intro-img" data-animate>
<img src="bilder/wohnzimmer2.png" alt="Wohnzimmer" /> <picture>
<source srcset="bilder/wohnzimmer2.webp" type="image/webp">
<img src="bilder/wohnzimmer2.png" alt="Wohnzimmer" loading="lazy" />
</picture>
<div class="intro-img-badge">Wohnzimmer · 42,6 m²</div> <div class="intro-img-badge">Wohnzimmer · 42,6 m²</div>
</div> </div>
</section> </section>
<section id="galerie" class="gallery-section" aria-label="Fotogalerie"> <section id="galerie" class="gallery-section">
<div class="gallery-header"> <div class="gallery-header">
<div> <div>
<div class="section-eyebrow">Fotogalerie</div> <div class="section-eyebrow">Fotogalerie</div>
@@ -304,55 +301,88 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<div class="masonry-grid"> <div class="masonry-grid">
<div class="grid-sizer"></div> <div class="grid-sizer"></div>
<div class="grid-item" data-img="bilder/Außenansicht-2.png" role="button" tabindex="0" aria-label="Außenansicht Großansicht öffnen"> <div class="grid-item" data-img="bilder/Außenansicht-2.webp">
<img src="bilder/Außenansicht-2-small.png" alt="Außenansicht des Einfamilienhauses" /> <picture>
<source srcset="bilder/Außenansicht-2-small.webp" type="image/webp">
<img src="bilder/Außenansicht-2-small.png" alt="Außenansicht" loading="lazy" />
</picture>
<span class="grid-item-label">Außenansicht</span> <span class="grid-item-label">Außenansicht</span>
</div> </div>
<div class="grid-item" data-img="bilder/wohnzimmer2.png" role="button" tabindex="0" aria-label="Wohnzimmer Großansicht öffnen"> <div class="grid-item" data-img="bilder/wohnzimmer2.webp">
<img src="bilder/wohnzimmer2-small.png" alt="Wohnzimmer mit 42,6 m² Wohnfläche" /> <picture>
<source srcset="bilder/wohnzimmer2-small.webp" type="image/webp">
<img src="bilder/wohnzimmer2-small.png" alt="Wohnzimmer" loading="lazy" />
</picture>
<span class="grid-item-label">Wohnzimmer · 42,6 m²</span> <span class="grid-item-label">Wohnzimmer · 42,6 m²</span>
</div> </div>
<div class="grid-item" data-img="bilder/Küche 1.jpg" role="button" tabindex="0" aria-label="Küche Großansicht öffnen"> <div class="grid-item" data-img="bilder/Küche 1.webp">
<img src="bilder/Küche 1.jpg" alt="Küche mit 18,4 m²" /> <picture>
<source srcset="bilder/Küche 1-small.webp" type="image/webp">
<img src="bilder/Küche 1.jpg" alt="Küche" loading="lazy" />
</picture>
<span class="grid-item-label">Küche · 18,4 m²</span> <span class="grid-item-label">Küche · 18,4 m²</span>
</div> </div>
<div class="grid-item" data-img="bilder/schlafzimmer.png" role="button" tabindex="0" aria-label="Schlafzimmer Großansicht öffnen"> <div class="grid-item" data-img="bilder/schlafzimmer.webp">
<img src="bilder/schlafzimmer-small.png" alt="Schlafzimmer mit 18 m²" /> <picture>
<source srcset="bilder/schlafzimmer-small.webp" type="image/webp">
<img src="bilder/schlafzimmer-small.png" alt="Schlafzimmer" loading="lazy" />
</picture>
<span class="grid-item-label">Schlafzimmer · 18 m²</span> <span class="grid-item-label">Schlafzimmer · 18 m²</span>
</div> </div>
<div class="grid-item" data-img="bilder/Bad.jpg" role="button" tabindex="0" aria-label="Badezimmer Großansicht öffnen"> <div class="grid-item" data-img="bilder/Bad.webp">
<img src="bilder/Bad.jpg" alt="Badezimmer mit 9,8 m²" /> <picture>
<source srcset="bilder/Bad-small.webp" type="image/webp">
<img src="bilder/Bad.jpg" alt="Badezimmer" loading="lazy" />
</picture>
<span class="grid-item-label">Badezimmer · 9,8 m²</span> <span class="grid-item-label">Badezimmer · 9,8 m²</span>
</div> </div>
<div class="grid-item" data-img="bilder/Kinderzimmer.png" role="button" tabindex="0" aria-label="Kinderzimmer 1 Großansicht öffnen"> <div class="grid-item" data-img="bilder/Kinderzimmer.webp">
<img src="bilder/Kinderzimmer-small.png" alt="Kinderzimmer 1 mit 21,7 m²" /> <picture>
<source srcset="bilder/Kinderzimmer-small.webp" type="image/webp">
<img src="bilder/Kinderzimmer-small.png" alt="Kinderzimmer 1" loading="lazy" />
</picture>
<span class="grid-item-label">Kinderzimmer 1 · 21,7 m²</span> <span class="grid-item-label">Kinderzimmer 1 · 21,7 m²</span>
</div> </div>
<div class="grid-item" data-img="bilder/Kinderzimmer 2.jpg" role="button" tabindex="0" aria-label="Kinderzimmer 2 Großansicht öffnen"> <div class="grid-item" data-img="bilder/Kinderzimmer 2.webp">
<img src="bilder/Kinderzimmer 2-small.png" alt="Kinderzimmer 2 mit 15,7 m²" /> <picture>
<source srcset="bilder/Kinderzimmer 2-small.webp" type="image/webp">
<img src="bilder/Kinderzimmer 2-small.png" alt="Kinderzimmer 2" loading="lazy" />
</picture>
<span class="grid-item-label">Kinderzimmer 2 · 15,7 m²</span> <span class="grid-item-label">Kinderzimmer 2 · 15,7 m²</span>
</div> </div>
<div class="grid-item" data-img="bilder/kinderzimmer 2 2.jpeg" role="button" tabindex="0" aria-label="Kinderzimmer Detail Großansicht öffnen"> <div class="grid-item" data-img="bilder/kinderzimmer 2 2.webp">
<img src="bilder/kinderzimmer 2 2-small.png" alt="Detailansicht Kinderzimmer" /> <picture>
<source srcset="bilder/kinderzimmer 2 2-small.webp" type="image/webp">
<img src="bilder/kinderzimmer 2 2-small.png" alt="Kinderzimmer Detail" loading="lazy" />
</picture>
<span class="grid-item-label">Kinderzimmer Detail</span> <span class="grid-item-label">Kinderzimmer Detail</span>
</div> </div>
<div class="grid-item" data-img="bilder/Kinderzimmer 3.jpg" role="button" tabindex="0" aria-label="Gästezimmer Großansicht öffnen"> <div class="grid-item" data-img="bilder/Kinderzimmer 3.webp">
<img src="bilder/Kinderzimmer 3-small.png" alt="Gästezimmer mit 11,5 m²" /> <picture>
<source srcset="bilder/Kinderzimmer 3-small.webp" type="image/webp">
<img src="bilder/Kinderzimmer 3-small.png" alt="Kinderzimmer 3" loading="lazy" />
</picture>
<span class="grid-item-label">Gästezimmer · 11,5 m²</span> <span class="grid-item-label">Gästezimmer · 11,5 m²</span>
</div> </div>
<div class="grid-item" data-img="bilder/Bad-2.jpg" role="button" tabindex="0" aria-label="Zweites Bad Großansicht öffnen"> <div class="grid-item" data-img="bilder/Bad-2.webp">
<img src="bilder/Bad-2-small.jpg" alt="Zweites Badezimmer im Haus" /> <picture>
<source srcset="bilder/Bad-2-small.webp" type="image/webp">
<img src="bilder/Bad-2-small.jpg" alt="Wohnbereich Detail 1" loading="lazy" />
</picture>
<span class="grid-item-label">Wohnbereich</span> <span class="grid-item-label">Wohnbereich</span>
</div> </div>
<div class="grid-item" data-img="bilder/bad3.jpg" role="button" tabindex="0" aria-label="Drittes Bad Großansicht öffnen"> <div class="grid-item" data-img="bilder/Bad-3.webp">
<img src="bilder/Bad-3-small.jpg" alt="Drittes Badezimmer im Haus" /> <picture>
<source srcset="bilder/Bad-3-small.webp" type="image/webp">
<img src="bilder/Bad-3-small.jpg" alt="Wohnbereich Detail 2" loading="lazy" />
</picture>
<span class="grid-item-label">Wohnbereich Detail</span> <span class="grid-item-label">Wohnbereich Detail</span>
</div> </div>
<div class="grid-item" data-img="bilder/WhatsApp Image 2026-03-30 at 07.50.42 (2).jpeg" role="button" tabindex="0" aria-label="Hausansicht Großansicht öffnen"> <div class="grid-item" data-img="bilder/Bad-4.webp">
<img <picture>
src="bilder/WhatsApp Image 2026-03-30 at 07.50.42 (2).jpeg" <source srcset="bilder/Bad-4-small.webp" type="image/webp">
alt="Weitere Außenansicht des Einfamilienhauses" <img src="bilder/Bad-4-small.jpg" alt="Wohnbereich Detail 3" loading="lazy" />
/> </picture>
<span class="grid-item-label">Hausansicht</span> <span class="grid-item-label">Hausansicht</span>
</div> </div>
</div> </div>
@@ -363,14 +393,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<h2>Großzügig auf allen Etagen</h2> <h2>Großzügig auf allen Etagen</h2>
<div class="floor-accordion"> <div class="floor-accordion">
<div class="floor-item"> <div class="floor-item">
<div class="floor-header" role="button" tabindex="0" aria-expanded="false" aria-controls="floor-body-0" id="floor-title-0"> <div class="floor-header">
<span class="floor-title">Erdgeschoss</span> <span class="floor-title">Erdgeschoss</span>
<div class="floor-size"> <div class="floor-size">
<span>99,5 m²</span> <span>99,5 m²</span>
<div class="floor-icon">+</div> <div class="floor-icon">+</div>
</div> </div>
</div> </div>
<div class="floor-body" id="floor-body-0" role="region" aria-labelledby="floor-title-0"> <div class="floor-body">
<div class="floor-rooms-grid"> <div class="floor-rooms-grid">
<div class="room-chip"> <div class="room-chip">
Flur Flur
@@ -398,28 +428,36 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div> </div>
</div> </div>
<div class="floor-plan floor-plan-multi"> <div class="floor-plan floor-plan-multi">
<img <picture>
src="bilder/grundrisse/EG-small.jpg" <source srcset="bilder/grundrisse/EG-small.webp" type="image/webp">
alt="Grundriss Erdgeschoss" <img
data-img="bilder/grundrisse/EG.png" src="bilder/grundrisse/EG-small.jpg"
/> alt="Grundriss Erdgeschoss"
<img loading="lazy"
src="bilder/grundrisse/EG 3D-small.jpg" data-img="bilder/grundrisse/EG.webp"
alt="Grundriss Erdgeschoss" />
data-img="bilder/grundrisse/EG 3D.png" </picture>
/> <picture>
<source srcset="bilder/grundrisse/EG 3D-small.webp" type="image/webp">
<img
src="bilder/grundrisse/EG 3D-small.jpg"
alt="Grundriss Erdgeschoss"
loading="lazy"
data-img="bilder/grundrisse/EG 3D.webp"
/>
</picture>
</div> </div>
</div> </div>
</div> </div>
<div class="floor-item"> <div class="floor-item">
<div class="floor-header" role="button" tabindex="0" aria-expanded="false" aria-controls="floor-body-1" id="floor-title-1"> <div class="floor-header">
<span class="floor-title">1. Obergeschoss</span> <span class="floor-title">1. Obergeschoss</span>
<div class="floor-size"> <div class="floor-size">
<span>120,4 m²</span> <span>120,4 m²</span>
<div class="floor-icon">+</div> <div class="floor-icon">+</div>
</div> </div>
</div> </div>
<div class="floor-body" id="floor-body-1" role="region" aria-labelledby="floor-title-1"> <div class="floor-body">
<div class="floor-rooms-grid"> <div class="floor-rooms-grid">
<div class="room-chip"> <div class="room-chip">
Flur Flur
@@ -447,28 +485,36 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div> </div>
</div> </div>
<div class="floor-plan floor-plan-multi"> <div class="floor-plan floor-plan-multi">
<img <picture>
src="bilder/grundrisse/OG 1 2-small.jpg" <source srcset="bilder/grundrisse/OG 1 2-small.webp" type="image/webp">
alt="Grundriss 1. Obergeschoss" <img
data-img="bilder/grundrisse/OG 1 2.png" src="bilder/grundrisse/OG 1 2-small.jpg"
/> alt="Grundriss 1. Obergeschoss"
<img loading="lazy"
src="bilder/grundrisse/OG 1 3D-small.jpg" data-img="bilder/grundrisse/OG 1 2.webp"
alt="Grundriss 1. Obergeschoss" />
data-img="bilder/grundrisse/OG 1 3D.png" </picture>
/> <picture>
<source srcset="bilder/grundrisse/OG 1 3D-small.webp" type="image/webp">
<img
src="bilder/grundrisse/OG 1 3D-small.jpg"
alt="Grundriss 1. Obergeschoss"
loading="lazy"
data-img="bilder/grundrisse/OG 1 3D.webp"
/>
</picture>
</div> </div>
</div> </div>
</div> </div>
<div class="floor-item"> <div class="floor-item">
<div class="floor-header" role="button" tabindex="0" aria-expanded="false" aria-controls="floor-body-2" id="floor-title-2"> <div class="floor-header">
<span class="floor-title">2. Obergeschoss</span> <span class="floor-title">2. Obergeschoss</span>
<div class="floor-size"> <div class="floor-size">
<span>68 m²</span> <span>68 m²</span>
<div class="floor-icon">+</div> <div class="floor-icon">+</div>
</div> </div>
</div> </div>
<div class="floor-body" id="floor-body-2" role="region" aria-labelledby="floor-title-2"> <div class="floor-body">
<div class="floor-rooms-grid"> <div class="floor-rooms-grid">
<div class="room-chip"> <div class="room-chip">
Flur Flur
@@ -496,28 +542,36 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div> </div>
</div> </div>
<div class="floor-plan floor-plan-multi"> <div class="floor-plan floor-plan-multi">
<img <picture>
src="bilder/grundrisse/OG 2 grundriss-small.jpg" <source srcset="bilder/grundrisse/OG 2 grundriss-small.webp" type="image/webp">
alt="Grundriss 2. Obergeschoss (1)" <img
data-img="bilder/grundrisse/OG 2 grundriss.png" src="bilder/grundrisse/OG 2 grundriss-small.jpg"
/> alt="Grundriss 2. Obergeschoss (1)"
<img loading="lazy"
src="bilder/grundrisse/OG 2 3D-small.jpg" data-img="bilder/grundrisse/OG 2 grundriss.webp"
alt="Grundriss 2. Obergeschoss (1)" />
data-img="bilder/grundrisse/OG 2 3D.png" </picture>
/> <picture>
<source srcset="bilder/grundrisse/OG 2 3D-small.webp" type="image/webp">
<img
src="bilder/grundrisse/OG 2 3D-small.jpg"
alt="Grundriss 2. Obergeschoss (1)"
loading="lazy"
data-img="bilder/grundrisse/OG 2 3D.webp"
/>
</picture>
</div> </div>
</div> </div>
</div> </div>
<div class="floor-item"> <div class="floor-item">
<div class="floor-header" role="button" tabindex="0" aria-expanded="false" aria-controls="floor-body-3" id="floor-title-3"> <div class="floor-header">
<span class="floor-title">Dachboden</span> <span class="floor-title">Dachboden</span>
<div class="floor-size"> <div class="floor-size">
<span>94 m² Nutzfläche</span> <span>94 m² Nutzfläche</span>
<div class="floor-icon">+</div> <div class="floor-icon">+</div>
</div> </div>
</div> </div>
<div class="floor-body" id="floor-body-3" role="region" aria-labelledby="floor-title-3"> <div class="floor-body">
<div class="floor-rooms-grid"> <div class="floor-rooms-grid">
<div class="room-chip"> <div class="room-chip">
Dachboden unten (ungeheizt) Dachboden unten (ungeheizt)
@@ -533,23 +587,31 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div> </div>
</div> </div>
<div class="floor-plan floor-plan-multi"> <div class="floor-plan floor-plan-multi">
<img <picture>
src="bilder/grundrisse/Dachboden unten 2-small.jpg" <source srcset="bilder/grundrisse/Dachboden unten 2-small.webp" type="image/webp">
alt="Grundriss Dachboden" <img
data-img="bilder/grundrisse/Dachboden unten 2.png" src="bilder/grundrisse/Dachboden unten 2-small.jpg"
/> alt="Grundriss Dachboden"
<img loading="lazy"
src="bilder/grundrisse/Dachboden unten-small.jpg" data-img="bilder/grundrisse/Dachboden unten 2.webp"
alt="Grundriss Dachboden" />
data-img="bilder/grundrisse/Dachboden unten.png" </picture>
/> <picture>
<source srcset="bilder/grundrisse/Dachboden unten-small.webp" type="image/webp">
<img
src="bilder/grundrisse/Dachboden unten-small.jpg"
alt="Grundriss Dachboden"
loading="lazy"
data-img="bilder/grundrisse/Dachboden unten.webp"
/>
</picture>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<section class="pricing-section" id="miete" aria-label="Mietkonditionen"> <section class="pricing-section" id="miete">
<div class="pricing-inner"> <div class="pricing-inner">
<div class="section-eyebrow">Mietkonditionen</div> <div class="section-eyebrow">Mietkonditionen</div>
<h2>Transparente Preisgestaltung</h2> <h2>Transparente Preisgestaltung</h2>
@@ -647,7 +709,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div> </div>
</section> </section>
<section class="contact-section" id="kontakt" aria-label="Kontaktformular"> <section class="contact-section" id="kontakt">
<div class="contact-inner"> <div class="contact-inner">
<div class="section-eyebrow">Kontakt</div> <div class="section-eyebrow">Kontakt</div>
<h2> <h2>
@@ -748,8 +810,7 @@ foreach ($interestOptions as $opt):
</div> </div>
</section> </section>
</main> <footer>
<footer role="contentinfo">
<div class="footer-logo">Bahnhofstraße 10 · Schleusingen</div> <div class="footer-logo">Bahnhofstraße 10 · Schleusingen</div>
<div class="footer-links"> <div class="footer-links">
<a href="impressum.html">Impressum</a> <a href="impressum.html">Impressum</a>
@@ -757,9 +818,9 @@ foreach ($interestOptions as $opt):
</div> </div>
</footer> </footer>
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Bildansicht"> <div class="lightbox" id="lightbox">
<button class="lightbox-close" id="lightboxClose" aria-label="Bildansicht schließen">&times;</button> <button class="lightbox-close" id="lightboxClose">&times;</button>
<img src="" id="lightboxImg" alt="" /> <img src="" id="lightboxImg" alt="Vollbild" />
</div> </div>
<script src="js/haus-schleusingen.js"></script> <script src="js/haus-schleusingen.js"></script>

View File

@@ -1,233 +1,88 @@
document.addEventListener("DOMContentLoaded", function () { $(function () {
// Navbar scroll // Navbar scroll
var navbar = document.getElementById("navbar"); $(window).on("scroll", function () {
window.addEventListener("scroll", function () { if ($(this).scrollTop() > 60) $("#navbar").addClass("scrolled");
if (window.scrollY > 60) navbar.classList.add("scrolled"); else $("#navbar").removeClass("scrolled");
else navbar.classList.remove("scrolled");
}); });
// Hero animation on load // Hero animation on load
setTimeout(function () { setTimeout(function () {
document.getElementById("heroContent").classList.add("visible"); $("#heroContent").addClass("visible");
document.getElementById("heroBg").classList.add("loaded"); $("#heroBg").addClass("loaded");
}, 200); }, 200);
// Scroll animations via IntersectionObserver // Scroll animations
var animElements = document.querySelectorAll(".fact, [data-animate]"); function checkVisible() {
animElements.forEach(function (el) { $(".fact, [data-animate]").each(function () {
el.style.opacity = "0"; var el = $(this);
el.style.transform = "translateY(30px)"; var top = el.offset().top;
el.style.transition = "opacity 0.8s ease, transform 0.8s ease"; var windowBottom = $(window).scrollTop() + $(window).height();
}); if (windowBottom > top + 60) {
el.addClass("visible");
if ("IntersectionObserver" in window) { el.css({ opacity: 1, transform: "translateY(0)" });
var observer = new IntersectionObserver( }
function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
entry.target.style.opacity = "1";
entry.target.style.transform = "translateY(0)";
observer.unobserve(entry.target);
}
});
},
{ rootMargin: "0px 0px -60px 0px" }
);
animElements.forEach(function (el) {
observer.observe(el);
});
} else {
// Fallback: show all immediately
animElements.forEach(function (el) {
el.classList.add("visible");
el.style.opacity = "1";
el.style.transform = "translateY(0)";
}); });
} }
$("[data-animate]").css({
opacity: 0,
transform: "translateY(30px)",
transition: "opacity 0.8s ease, transform 0.8s ease",
});
$(window).on("scroll", checkVisible);
checkVisible();
// Floor accordion // Floor accordion
// Floor accordion (vanilla JS + a11y) $(".floor-header").on("click", function () {
document.querySelectorAll(".floor-header").forEach(function (header) { var item = $(this).closest(".floor-item");
header.addEventListener("click", function () { var isOpen = item.hasClass("open");
var item = this.closest(".floor-item"); $(".floor-item").removeClass("open");
var isOpen = item.classList.contains("open"); $(".floor-body").slideUp(300);
var allItems = document.querySelectorAll(".floor-item"); if (!isOpen) {
item.addClass("open");
// Close all item.find(".floor-body").slideDown(300);
allItems.forEach(function (fi) {
fi.classList.remove("open");
var hdr = fi.querySelector(".floor-header");
if (hdr) hdr.setAttribute("aria-expanded", "false");
var body = fi.querySelector(".floor-body");
if (body) body.style.display = "none";
});
// Open clicked if it was closed
if (!isOpen) {
item.classList.add("open");
this.setAttribute("aria-expanded", "true");
var body = item.querySelector(".floor-body");
if (body) body.style.display = "block";
}
});
});
// Accordion keyboard handler (Enter/Space)
document.querySelectorAll(".floor-header").forEach(function (header) {
header.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
this.click();
}
});
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
$(this).trigger("click");
} }
}); });
// Lightbox track last focused element for focus return // Lightbox gallery grid items
var lightboxTrigger = null; $(document).on("click", ".grid-item", function () {
var src = $(this).data("img") || $(this).find("img").attr("src");
function openLightbox(src) { $("#lightboxImg").off("error").on("error", function () {
lightboxTrigger = document.activeElement; // WebP fallback: try original format
$("#lightboxImg").attr("src", src).attr("alt", ""); if ($(this).attr('src').endsWith('.webp')) {
$(this).attr('src', src.replace(/\.webp$/, '.png'));
}
});
$("#lightboxImg").attr("src", src);
$("#lightbox").addClass("open"); $("#lightbox").addClass("open");
$("body").css("overflow", "hidden"); $("body").css("overflow", "hidden");
// Set focus to close button
setTimeout(function () {
$("#lightboxClose").focus();
}, 50);
}
function closeLightbox() {
$("#lightbox").removeClass("open");
$("body").css("overflow", "");
// Return focus to trigger
if (lightboxTrigger) {
$(lightboxTrigger).focus();
lightboxTrigger = null;
}
}
// Lightbox gallery grid items
// Lightbox gallery grid items
document.querySelectorAll(".grid-item").forEach(function (item) {
item.addEventListener("click", function () {
var src = this.dataset.img || this.querySelector("img").getAttribute("src");
openLightbox(src);
});
});
// Gallery keyboard handler (Enter/Space)
document.querySelectorAll(".grid-item").forEach(function (item) {
item.setAttribute("tabindex", "0");
item.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
this.click();
}
});
}); });
// Lightbox floor plan images in Raumaufteilung // Lightbox floor plan images in Raumaufteilung
document.querySelectorAll(".floor-plan img[data-img]").forEach(function (img) { $(document).on("click", ".floor-plan img[data-img]", function () {
img.addEventListener("click", function () { var src = $(this).data("img");
openLightbox(this.dataset.img); $("#lightboxImg").off("error").on("error", function () {
if ($(this).attr('src').endsWith('.webp')) {
$(this).attr('src', src.replace(/\.webp$/, '.png'));
}
}); });
$("#lightboxImg").attr("src", src);
$("#lightbox").addClass("open");
$("body").css("overflow", "hidden");
}); });
$("#lightboxClose, #lightbox").on("click", function (e) {
function openLightbox(src) { if (e.target === this) {
lightboxTrigger = document.activeElement; $("#lightbox").removeClass("open");
document.getElementById("lightboxImg").setAttribute("src", src); $("body").css("overflow", "");
document.getElementById("lightbox").classList.add("open");
document.body.style.overflow = "hidden";
// Focus close button
setTimeout(function () {
document.getElementById("lightboxClose").focus();
}, 100);
}
function closeLightbox() {
document.getElementById("lightbox").classList.remove("open");
document.body.style.overflow = "";
// Return focus to trigger
if (lightboxTrigger) {
lightboxTrigger.focus();
lightboxTrigger = null;
} }
}
document.getElementById("lightboxClose").addEventListener("click", closeLightbox);
document.getElementById("lightbox").addEventListener("click", function (e) {
if (e.target === this) closeLightbox();
}); });
document.addEventListener("keydown", function (e) { $(document).on("keydown", function (e) {
if (e.key === "Escape") closeLightbox(); if (e.key === "Escape") {
}); $("#lightbox").removeClass("open");
$("body").css("overflow", "");
// Focus trap for lightbox
document.getElementById("lightbox").addEventListener("keydown", function (e) {
if (e.key !== "Tab") return;
var focusable = this.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])");
focusable = Array.from(focusable).filter(function (el) { return el.offsetParent !== null; });
if (focusable.length === 0) return;
var first = focusable[0];
var last = focusable[focusable.length - 1];
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
} }
}); });
// Form submit is handled server-side by PHP no JS intervention needed. // Form submit is handled server-side by PHP no JS intervention needed.
// Form submit opens email client with pre-filled mailto: link
document.getElementById("contactForm").addEventListener("submit", function (e) {
e.preventDefault();
var fname = document.getElementById("fname").value.trim();
var lname = document.getElementById("lname").value.trim();
var email = document.getElementById("email").value.trim();
var phone = document.getElementById("phone").value.trim();
var interest = document.getElementById("interest").value;
var message = document.getElementById("message").value.trim();
var subject = "Kontaktanfrage: " + interest;
var body = "Von: " + fname + " " + lname + "\n";
body += "E-Mail: " + email + "\n";
if (phone) body += "Telefon: " + phone + "\n";
body += "Anliegen: " + interest + "\n\n";
body += message;
var mailto =
"mailto:mki@kies-media.de" +
"?subject=" + encodeURIComponent(subject) +
"&body=" + encodeURIComponent(body);
window.location.href = mailto;
// Show success message
this.style.display = "none";
var success = document.getElementById("formSuccess");
success.style.display = "block";
success.style.opacity = "0";
success.style.transition = "opacity 0.4s ease";
requestAnimationFrame(function () {
success.style.opacity = "1";
});
});
}); });
// Mobile hamburger menu (vanilla JS) // Mobile hamburger menu (vanilla JS)

View File

@@ -5,7 +5,20 @@ server {
root /usr/share/nginx/html; root /usr/share/nginx/html;
index haus-schleusingen.html; index haus-schleusingen.html;
# Gzip aktivieren
gzip on;
gzip_types text/css application/javascript image/svg+xml application/json text/xml;
gzip_min_length 256;
gzip_vary on;
location / { location / {
try_files $uri $uri/ /haus-schleusingen.html; try_files $uri $uri/ /haus-schleusingen.html;
} }
# Lange Cache-Dauer für Bilder und statische Assets
location ~* \.(jpg|jpeg|png|webp|gif|ico|svg|css|js|woff2?)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
} }