8 Commits

Author SHA1 Message Date
Claw
9c0a9a856a merge: resolve conflicts with main (WebP + vanilla JS + a11y)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 32s
2026-05-19 13:28:52 +00:00
4ca48a7445 Merge pull request 'fix: JavaScript doppelte Funktionen & toter Code (#39)' (#40) from feature/39-js-duplicate-functions-fix into main
Reviewed-on: #40
2026-05-19 15:13:29 +02:00
6b13b95102 fix: remove duplicate openLightbox/closeLightbox and dead code (#39)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 26s
- Consolidate openLightbox() and closeLightbox() to single vanilla JS definition
- Remove orphaned keyboard handler block that caused ReferenceError
- Refs: #39
2026-05-19 12:41:58 +00:00
9a8776412e Merge pull request 'Fix #36: Favicon erstellen und einbinden' (#37) from feature/issue-36-favicon into main 2026-05-15 10:45:31 +02:00
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
43 changed files with 166 additions and 102 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.

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

206
index.php
View File

@@ -215,7 +215,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<div
class="hero-bg"
id="heroBg"
style="background-image: url(bilder/Außenansicht-2.png)"
style="background-image: url(bilder/Außenansicht-2.webp)"
></div>
<div class="hero-overlay"></div>
<div class="hero-content" id="heroContent">
@@ -289,7 +289,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div>
</div>
<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>
</section>
@@ -304,55 +307,88 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<div class="masonry-grid">
<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">
<img src="bilder/Außenansicht-2-small.png" alt="Außenansicht des Einfamilienhauses" />
<div class="grid-item" data-img="bilder/Außenansicht-2.webp" role="button" tabindex="0" aria-label="Außenansicht Großansicht öffnen">
<picture>
<source srcset="bilder/Außenansicht-2-small.webp" type="image/webp">
<img src="bilder/Außenansicht-2-small.png" alt="Außenansicht des Einfamilienhauses" loading="lazy" />
</picture>
<span class="grid-item-label">Außenansicht</span>
</div>
<div class="grid-item" data-img="bilder/wohnzimmer2.png" role="button" tabindex="0" aria-label="Wohnzimmer Großansicht öffnen">
<img src="bilder/wohnzimmer2-small.png" alt="Wohnzimmer mit 42,6 m² Wohnfläche" />
<div class="grid-item" data-img="bilder/wohnzimmer2.webp" role="button" tabindex="0" aria-label="Wohnzimmer Großansicht öffnen">
<picture>
<source srcset="bilder/wohnzimmer2-small.webp" type="image/webp">
<img src="bilder/wohnzimmer2-small.png" alt="Wohnzimmer mit 42,6 m² Wohnfläche" loading="lazy" />
</picture>
<span class="grid-item-label">Wohnzimmer · 42,6 m²</span>
</div>
<div class="grid-item" data-img="bilder/Küche 1.jpg" role="button" tabindex="0" aria-label="Küche Großansicht öffnen">
<img src="bilder/Küche 1.jpg" alt="Küche mit 18,4 m²" />
<div class="grid-item" data-img="bilder/Küche 1.webp" role="button" tabindex="0" aria-label="Küche Großansicht öffnen">
<picture>
<source srcset="bilder/Küche 1-small.webp" type="image/webp">
<img src="bilder/Küche 1.jpg" alt="Küche mit 18,4 m²" loading="lazy" />
</picture>
<span class="grid-item-label">Küche · 18,4 m²</span>
</div>
<div class="grid-item" data-img="bilder/schlafzimmer.png" role="button" tabindex="0" aria-label="Schlafzimmer Großansicht öffnen">
<img src="bilder/schlafzimmer-small.png" alt="Schlafzimmer mit 18 m²" />
<div class="grid-item" data-img="bilder/schlafzimmer.webp" role="button" tabindex="0" aria-label="Schlafzimmer Großansicht öffnen">
<picture>
<source srcset="bilder/schlafzimmer-small.webp" type="image/webp">
<img src="bilder/schlafzimmer-small.png" alt="Schlafzimmer mit 18 m²" loading="lazy" />
</picture>
<span class="grid-item-label">Schlafzimmer · 18 m²</span>
</div>
<div class="grid-item" data-img="bilder/Bad.jpg" role="button" tabindex="0" aria-label="Badezimmer Großansicht öffnen">
<img src="bilder/Bad.jpg" alt="Badezimmer mit 9,8 m²" />
<div class="grid-item" data-img="bilder/Bad.webp" role="button" tabindex="0" aria-label="Badezimmer Großansicht öffnen">
<picture>
<source srcset="bilder/Bad-small.webp" type="image/webp">
<img src="bilder/Bad.jpg" alt="Badezimmer mit 9,8 m²" loading="lazy" />
</picture>
<span class="grid-item-label">Badezimmer · 9,8 m²</span>
</div>
<div class="grid-item" data-img="bilder/Kinderzimmer.png" role="button" tabindex="0" aria-label="Kinderzimmer 1 Großansicht öffnen">
<img src="bilder/Kinderzimmer-small.png" alt="Kinderzimmer 1 mit 21,7 m²" />
<div class="grid-item" data-img="bilder/Kinderzimmer.webp" role="button" tabindex="0" aria-label="Kinderzimmer 1 Großansicht öffnen">
<picture>
<source srcset="bilder/Kinderzimmer-small.webp" type="image/webp">
<img src="bilder/Kinderzimmer-small.png" alt="Kinderzimmer 1 mit 21,7 m²" loading="lazy" />
</picture>
<span class="grid-item-label">Kinderzimmer 1 · 21,7 m²</span>
</div>
<div class="grid-item" data-img="bilder/Kinderzimmer 2.jpg" role="button" tabindex="0" aria-label="Kinderzimmer 2 Großansicht öffnen">
<img src="bilder/Kinderzimmer 2-small.png" alt="Kinderzimmer 2 mit 15,7 m²" />
<div class="grid-item" data-img="bilder/Kinderzimmer 2.webp" role="button" tabindex="0" aria-label="Kinderzimmer 2 Großansicht öffnen">
<picture>
<source srcset="bilder/Kinderzimmer 2-small.webp" type="image/webp">
<img src="bilder/Kinderzimmer 2-small.png" alt="Kinderzimmer 2 mit 15,7 m²" loading="lazy" />
</picture>
<span class="grid-item-label">Kinderzimmer 2 · 15,7 m²</span>
</div>
<div class="grid-item" data-img="bilder/kinderzimmer 2 2.jpeg" role="button" tabindex="0" aria-label="Kinderzimmer Detail Großansicht öffnen">
<img src="bilder/kinderzimmer 2 2-small.png" alt="Detailansicht Kinderzimmer" />
<div class="grid-item" data-img="bilder/kinderzimmer 2 2.webp" role="button" tabindex="0" aria-label="Kinderzimmer Detail Großansicht öffnen">
<picture>
<source srcset="bilder/kinderzimmer 2 2-small.webp" type="image/webp">
<img src="bilder/kinderzimmer 2 2-small.png" alt="Detailansicht Kinderzimmer" loading="lazy" />
</picture>
<span class="grid-item-label">Kinderzimmer Detail</span>
</div>
<div class="grid-item" data-img="bilder/Kinderzimmer 3.jpg" role="button" tabindex="0" aria-label="Gästezimmer Großansicht öffnen">
<img src="bilder/Kinderzimmer 3-small.png" alt="Gästezimmer mit 11,5 m²" />
<div class="grid-item" data-img="bilder/Kinderzimmer 3.webp" role="button" tabindex="0" aria-label="Gästezimmer Großansicht öffnen">
<picture>
<source srcset="bilder/Kinderzimmer 3-small.webp" type="image/webp">
<img src="bilder/Kinderzimmer 3-small.png" alt="Gästezimmer mit 11,5 m²" loading="lazy" />
</picture>
<span class="grid-item-label">Gästezimmer · 11,5 m²</span>
</div>
<div class="grid-item" data-img="bilder/Bad-2.jpg" role="button" tabindex="0" aria-label="Zweites Bad Großansicht öffnen">
<img src="bilder/Bad-2-small.jpg" alt="Zweites Badezimmer im Haus" />
<div class="grid-item" data-img="bilder/Bad-2.webp" role="button" tabindex="0" aria-label="Zweites Bad Großansicht öffnen">
<picture>
<source srcset="bilder/Bad-2-small.webp" type="image/webp">
<img src="bilder/Bad-2-small.jpg" alt="Zweites Badezimmer im Haus" loading="lazy" />
</picture>
<span class="grid-item-label">Wohnbereich</span>
</div>
<div class="grid-item" data-img="bilder/bad3.jpg" role="button" tabindex="0" aria-label="Drittes Bad Großansicht öffnen">
<img src="bilder/Bad-3-small.jpg" alt="Drittes Badezimmer im Haus" />
<div class="grid-item" data-img="bilder/Bad-3.webp" role="button" tabindex="0" aria-label="Drittes Bad Großansicht öffnen">
<picture>
<source srcset="bilder/Bad-3-small.webp" type="image/webp">
<img src="bilder/Bad-3-small.jpg" alt="Drittes Badezimmer im Haus" loading="lazy" />
</picture>
<span class="grid-item-label">Wohnbereich Detail</span>
</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">
<img
src="bilder/WhatsApp Image 2026-03-30 at 07.50.42 (2).jpeg"
alt="Weitere Außenansicht des Einfamilienhauses"
/>
<div class="grid-item" data-img="bilder/Bad-4.webp" role="button" tabindex="0" aria-label="Wohnbereich Detail Großansicht öffnen">
<picture>
<source srcset="bilder/Bad-4-small.webp" type="image/webp">
<img src="bilder/Bad-4-small.jpg" alt="Wohnbereich Detail 3" loading="lazy" />
</picture>
<span class="grid-item-label">Hausansicht</span>
</div>
</div>
@@ -398,16 +434,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div>
</div>
<div class="floor-plan floor-plan-multi">
<img
src="bilder/grundrisse/EG-small.jpg"
alt="Grundriss Erdgeschoss"
data-img="bilder/grundrisse/EG.png"
/>
<img
src="bilder/grundrisse/EG 3D-small.jpg"
alt="Grundriss Erdgeschoss"
data-img="bilder/grundrisse/EG 3D.png"
/>
<picture>
<source srcset="bilder/grundrisse/EG-small.webp" type="image/webp">
<img
src="bilder/grundrisse/EG-small.jpg"
alt="Grundriss Erdgeschoss"
loading="lazy"
data-img="bilder/grundrisse/EG.webp"
/>
</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>
@@ -447,16 +491,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div>
</div>
<div class="floor-plan floor-plan-multi">
<img
src="bilder/grundrisse/OG 1 2-small.jpg"
alt="Grundriss 1. Obergeschoss"
data-img="bilder/grundrisse/OG 1 2.png"
/>
<img
src="bilder/grundrisse/OG 1 3D-small.jpg"
alt="Grundriss 1. Obergeschoss"
data-img="bilder/grundrisse/OG 1 3D.png"
/>
<picture>
<source srcset="bilder/grundrisse/OG 1 2-small.webp" type="image/webp">
<img
src="bilder/grundrisse/OG 1 2-small.jpg"
alt="Grundriss 1. Obergeschoss"
loading="lazy"
data-img="bilder/grundrisse/OG 1 2.webp"
/>
</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>
@@ -496,16 +548,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div>
</div>
<div class="floor-plan floor-plan-multi">
<img
src="bilder/grundrisse/OG 2 grundriss-small.jpg"
alt="Grundriss 2. Obergeschoss (1)"
data-img="bilder/grundrisse/OG 2 grundriss.png"
/>
<img
src="bilder/grundrisse/OG 2 3D-small.jpg"
alt="Grundriss 2. Obergeschoss (1)"
data-img="bilder/grundrisse/OG 2 3D.png"
/>
<picture>
<source srcset="bilder/grundrisse/OG 2 grundriss-small.webp" type="image/webp">
<img
src="bilder/grundrisse/OG 2 grundriss-small.jpg"
alt="Grundriss 2. Obergeschoss (1)"
loading="lazy"
data-img="bilder/grundrisse/OG 2 grundriss.webp"
/>
</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>
@@ -533,16 +593,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div>
</div>
<div class="floor-plan floor-plan-multi">
<img
src="bilder/grundrisse/Dachboden unten 2-small.jpg"
alt="Grundriss Dachboden"
data-img="bilder/grundrisse/Dachboden unten 2.png"
/>
<img
src="bilder/grundrisse/Dachboden unten-small.jpg"
alt="Grundriss Dachboden"
data-img="bilder/grundrisse/Dachboden unten.png"
/>
<picture>
<source srcset="bilder/grundrisse/Dachboden unten 2-small.webp" type="image/webp">
<img
src="bilder/grundrisse/Dachboden unten 2-small.jpg"
alt="Grundriss Dachboden"
loading="lazy"
data-img="bilder/grundrisse/Dachboden unten 2.webp"
/>
</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>

View File

@@ -81,10 +81,6 @@ document.addEventListener("DOMContentLoaded", function () {
this.click();
}
});
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
$(this).trigger("click");
}
});
// Lightbox track last focused element for focus return
@@ -92,27 +88,33 @@ document.addEventListener("DOMContentLoaded", function () {
function openLightbox(src) {
lightboxTrigger = document.activeElement;
$("#lightboxImg").attr("src", src).attr("alt", "");
$("#lightbox").addClass("open");
$("body").css("overflow", "hidden");
var img = document.getElementById("lightboxImg");
// WebP fallback: if .webp fails, try original format
img.onerror = function () {
if (img.getAttribute('src').endsWith('.webp')) {
img.setAttribute('src', src.replace(/\.webp$/, '.png'));
}
};
img.setAttribute("src", src);
img.setAttribute("alt", "");
document.getElementById("lightbox").classList.add("open");
document.body.style.overflow = "hidden";
// Set focus to close button
setTimeout(function () {
$("#lightboxClose").focus();
document.getElementById("lightboxClose").focus();
}, 50);
}
function closeLightbox() {
$("#lightbox").removeClass("open");
$("body").css("overflow", "");
document.getElementById("lightbox").classList.remove("open");
document.body.style.overflow = "";
// Return focus to trigger
if (lightboxTrigger) {
$(lightboxTrigger).focus();
lightboxTrigger.focus();
lightboxTrigger = null;
}
}
// Lightbox gallery grid items
// Lightbox gallery grid items
document.querySelectorAll(".grid-item").forEach(function (item) {
item.addEventListener("click", function () {
@@ -139,26 +141,7 @@ document.addEventListener("DOMContentLoaded", function () {
});
});
function openLightbox(src) {
lightboxTrigger = document.activeElement;
document.getElementById("lightboxImg").setAttribute("src", src);
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) {

View File

@@ -5,7 +5,20 @@ server {
root /usr/share/nginx/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 / {
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;
}
}