feat(a11y): ARIA labels, focus management, skip-nav, keyboard nav, contrast fix
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 23s
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 23s
Accessibility improvements per WCAG 2.1 AA: - Skip-to-content link (TA-1) - ARIA landmarks and roles for nav, main, sections, footer (TA-2) - Accordion keyboard navigation + aria-expanded (TA-3) - Lightbox focus trap + focus management + dialog role (TA-4) - Gallery grid items keyboard accessible (TA-5) - Improved alt texts for all images (TA-6) - Focus-visible styles for all interactive elements (TA-7) - Darker --stone color for WCAG AA contrast compliance (TA-8) Fix #18
This commit is contained in:
@@ -1,7 +1,55 @@
|
||||
/* 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 {
|
||||
--cream: #f5f0e8;
|
||||
--warm: #e8dfd0;
|
||||
--stone: #9e9485;
|
||||
--stone: #7a7062;
|
||||
--dark: #1c1a17;
|
||||
--charcoal: #2e2b26;
|
||||
--accent: #8b6914;
|
||||
|
||||
83
index.php
83
index.php
@@ -185,7 +185,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<link rel="stylesheet" href="css/haus-schleusingen.css" />
|
||||
</head>
|
||||
<body>
|
||||
<nav id="navbar">
|
||||
<a href="#main-content" class="skip-link">Zum Inhalt springen</a>
|
||||
<nav id="navbar" role="navigation" aria-label="Hauptnavigation">
|
||||
<div class="nav-logo">Bahnhofstraße 10</div>
|
||||
<button class="nav-hamburger" aria-label="Navigation öffnen" aria-expanded="false">
|
||||
<span></span>
|
||||
@@ -234,6 +235,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<main id="main-content">
|
||||
<div class="facts-strip">
|
||||
<div class="fact">
|
||||
<div class="fact-val">227</div>
|
||||
@@ -287,7 +289,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="galerie" class="gallery-section">
|
||||
<section id="galerie" class="gallery-section" aria-label="Fotogalerie">
|
||||
<div class="gallery-header">
|
||||
<div>
|
||||
<div class="section-eyebrow">Fotogalerie</div>
|
||||
@@ -297,54 +299,54 @@ 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">
|
||||
<img src="bilder/Außenansicht-2-small.png" alt="Außenansicht" />
|
||||
<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" />
|
||||
<span class="grid-item-label">Außenansicht</span>
|
||||
</div>
|
||||
<div class="grid-item" data-img="bilder/wohnzimmer2.png">
|
||||
<img src="bilder/wohnzimmer2-small.png" alt="Wohnzimmer" />
|
||||
<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" />
|
||||
<span class="grid-item-label">Wohnzimmer · 42,6 m²</span>
|
||||
</div>
|
||||
<div class="grid-item" data-img="bilder/Küche 1.jpg">
|
||||
<img src="bilder/Küche 1.jpg" alt="Küche" />
|
||||
<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²" />
|
||||
<span class="grid-item-label">Küche · 18,4 m²</span>
|
||||
</div>
|
||||
<div class="grid-item" data-img="bilder/schlafzimmer.png">
|
||||
<img src="bilder/schlafzimmer-small.png" alt="Schlafzimmer" />
|
||||
<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²" />
|
||||
<span class="grid-item-label">Schlafzimmer · 18 m²</span>
|
||||
</div>
|
||||
<div class="grid-item" data-img="bilder/Bad.jpg">
|
||||
<img src="bilder/Bad.jpg" alt="Badezimmer" />
|
||||
<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²" />
|
||||
<span class="grid-item-label">Badezimmer · 9,8 m²</span>
|
||||
</div>
|
||||
<div class="grid-item" data-img="bilder/Kinderzimmer.png">
|
||||
<img src="bilder/Kinderzimmer-small.png" alt="Kinderzimmer 1" />
|
||||
<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²" />
|
||||
<span class="grid-item-label">Kinderzimmer 1 · 21,7 m²</span>
|
||||
</div>
|
||||
<div class="grid-item" data-img="bilder/Kinderzimmer 2.jpg">
|
||||
<img src="bilder/Kinderzimmer 2-small.png" alt="Kinderzimmer 2" />
|
||||
<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²" />
|
||||
<span class="grid-item-label">Kinderzimmer 2 · 15,7 m²</span>
|
||||
</div>
|
||||
<div class="grid-item" data-img="bilder/kinderzimmer 2 2.jpeg">
|
||||
<img src="bilder/kinderzimmer 2 2-small.png" alt="Kinderzimmer Detail" />
|
||||
<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" />
|
||||
<span class="grid-item-label">Kinderzimmer Detail</span>
|
||||
</div>
|
||||
<div class="grid-item" data-img="bilder/Kinderzimmer 3.jpg">
|
||||
<img src="bilder/Kinderzimmer 3-small.png" alt="Kinderzimmer 3" />
|
||||
<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²" />
|
||||
<span class="grid-item-label">Gästezimmer · 11,5 m²</span>
|
||||
</div>
|
||||
<div class="grid-item" data-img="bilder/Bad-2.jpg">
|
||||
<img src="bilder/Bad-2-small.jpg" alt="Wohnbereich Detail 1" />
|
||||
<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" />
|
||||
<span class="grid-item-label">Wohnbereich</span>
|
||||
</div>
|
||||
<div class="grid-item" data-img="bilder/bad3.jpg">
|
||||
<img src="bilder/Bad-3-small.jpg" alt="Wohnbereich Detail 2" />
|
||||
<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" />
|
||||
<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">
|
||||
<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="Wohnbereich Detail 3"
|
||||
alt="Weitere Außenansicht des Einfamilienhauses"
|
||||
/>
|
||||
<span class="grid-item-label">Hausansicht</span>
|
||||
</div>
|
||||
@@ -356,14 +358,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<h2>Großzügig auf allen Etagen</h2>
|
||||
<div class="floor-accordion">
|
||||
<div class="floor-item">
|
||||
<div class="floor-header">
|
||||
<div class="floor-header" role="button" tabindex="0" aria-expanded="false" aria-controls="floor-body-0" id="floor-title-0">
|
||||
<span class="floor-title">Erdgeschoss</span>
|
||||
<div class="floor-size">
|
||||
<span>99,5 m²</span>
|
||||
<div class="floor-icon">+</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="floor-body">
|
||||
<div class="floor-body" id="floor-body-0" role="region" aria-labelledby="floor-title-0">
|
||||
<div class="floor-rooms-grid">
|
||||
<div class="room-chip">
|
||||
Flur
|
||||
@@ -405,14 +407,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
</div>
|
||||
<div class="floor-item">
|
||||
<div class="floor-header">
|
||||
<div class="floor-header" role="button" tabindex="0" aria-expanded="false" aria-controls="floor-body-1" id="floor-title-1">
|
||||
<span class="floor-title">1. Obergeschoss</span>
|
||||
<div class="floor-size">
|
||||
<span>120,4 m²</span>
|
||||
<div class="floor-icon">+</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="floor-body">
|
||||
<div class="floor-body" id="floor-body-1" role="region" aria-labelledby="floor-title-1">
|
||||
<div class="floor-rooms-grid">
|
||||
<div class="room-chip">
|
||||
Flur
|
||||
@@ -454,14 +456,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
</div>
|
||||
<div class="floor-item">
|
||||
<div class="floor-header">
|
||||
<div class="floor-header" role="button" tabindex="0" aria-expanded="false" aria-controls="floor-body-2" id="floor-title-2">
|
||||
<span class="floor-title">2. Obergeschoss</span>
|
||||
<div class="floor-size">
|
||||
<span>68 m²</span>
|
||||
<div class="floor-icon">+</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="floor-body">
|
||||
<div class="floor-body" id="floor-body-2" role="region" aria-labelledby="floor-title-2">
|
||||
<div class="floor-rooms-grid">
|
||||
<div class="room-chip">
|
||||
Flur
|
||||
@@ -503,14 +505,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
</div>
|
||||
<div class="floor-item">
|
||||
<div class="floor-header">
|
||||
<div class="floor-header" role="button" tabindex="0" aria-expanded="false" aria-controls="floor-body-3" id="floor-title-3">
|
||||
<span class="floor-title">Dachboden</span>
|
||||
<div class="floor-size">
|
||||
<span>94 m² Nutzfläche</span>
|
||||
<div class="floor-icon">+</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="floor-body">
|
||||
<div class="floor-body" id="floor-body-3" role="region" aria-labelledby="floor-title-3">
|
||||
<div class="floor-rooms-grid">
|
||||
<div class="room-chip">
|
||||
Dachboden unten (ungeheizt)
|
||||
@@ -542,7 +544,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pricing-section" id="miete">
|
||||
<section class="pricing-section" id="miete" aria-label="Mietkonditionen">
|
||||
<div class="pricing-inner">
|
||||
<div class="section-eyebrow">Mietkonditionen</div>
|
||||
<h2>Transparente Preisgestaltung</h2>
|
||||
@@ -640,7 +642,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="contact-section" id="kontakt">
|
||||
<section class="contact-section" id="kontakt" aria-label="Kontaktformular">
|
||||
<div class="contact-inner">
|
||||
<div class="section-eyebrow">Kontakt</div>
|
||||
<h2>
|
||||
@@ -741,7 +743,8 @@ foreach ($interestOptions as $opt):
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
</main>
|
||||
<footer role="contentinfo">
|
||||
<div class="footer-logo">Bahnhofstraße 10 · Schleusingen</div>
|
||||
<div class="footer-links">
|
||||
<a href="impressum.html">Impressum</a>
|
||||
@@ -749,9 +752,9 @@ foreach ($interestOptions as $opt):
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="lightbox" id="lightbox">
|
||||
<button class="lightbox-close" id="lightboxClose">×</button>
|
||||
<img src="" id="lightboxImg" alt="Vollbild" />
|
||||
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Bildansicht">
|
||||
<button class="lightbox-close" id="lightboxClose" aria-label="Bildansicht schließen">×</button>
|
||||
<img src="" id="lightboxImg" alt="" />
|
||||
</div>
|
||||
|
||||
<script src="js/haus-schleusingen.js"></script>
|
||||
|
||||
@@ -47,6 +47,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
}
|
||||
|
||||
// Floor accordion
|
||||
// Floor accordion (vanilla JS + a11y)
|
||||
document.querySelectorAll(".floor-header").forEach(function (header) {
|
||||
header.addEventListener("click", function () {
|
||||
var item = this.closest(".floor-item");
|
||||
@@ -56,6 +57,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
// Close all
|
||||
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";
|
||||
});
|
||||
@@ -63,12 +66,53 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
// 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
|
||||
var lightboxTrigger = null;
|
||||
|
||||
function openLightbox(src) {
|
||||
lightboxTrigger = document.activeElement;
|
||||
$("#lightboxImg").attr("src", src).attr("alt", "");
|
||||
$("#lightbox").addClass("open");
|
||||
$("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 () {
|
||||
@@ -77,6 +121,17 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
});
|
||||
});
|
||||
|
||||
// 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
|
||||
document.querySelectorAll(".floor-plan img[data-img]").forEach(function (img) {
|
||||
img.addEventListener("click", function () {
|
||||
@@ -85,14 +140,24 @@ 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);
|
||||
@@ -103,6 +168,30 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
if (e.key === "Escape") closeLightbox();
|
||||
});
|
||||
|
||||
// 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 – opens email client with pre-filled mailto: link
|
||||
document.getElementById("contactForm").addEventListener("submit", function (e) {
|
||||
|
||||
Reference in New Issue
Block a user