refactor: Umstellung auf Mini-MVC-Architektur (Issue #46)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s

- Front Controller Pattern mit public/index.php als Einstiegspunkt
- Eigenes Routing (App\Core\Router) ohne externes Framework
- Controller: HomeController, ImpressumController, DatenschutzController
- Views mit gemeinsamem Layout (app/views/layouts/main.php)
- PSR-4 Autoloading
- Statische Assets nach public/ verschoben
- Alte Dateien (index.php, impressum.html, datenschutz.html) geloescht
- 301-Redirects fuer alte URLs
- PHP 8.5 kompatibel
- Apache DocumentRoot auf public/ gesetzt
This commit is contained in:
2026-05-19 14:38:38 +00:00
parent 73635a5f03
commit 1aedcaf314
130 changed files with 1303 additions and 1179 deletions

15
public/.htaccess Normal file
View File

@@ -0,0 +1,15 @@
# Enable rewrite engine
RewriteEngine On
# Legacy redirects (301) must be before the catch-all
RewriteRule ^impressum\.html$ /impressum [R=301,L]
RewriteRule ^datenschutz\.html$ /datenschutz [R=301,L]
RewriteRule ^haus-schleusingen\.html$ / [R=301,L]
# Serve existing files/directories directly (css, js, images, fonts, etc.)
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Route everything else through the front controller
RewriteRule ^(.*)$ index.php [QSA,L]

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/bilder/Außenansicht-2.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

BIN
public/bilder/Bad-2-small.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
public/bilder/Bad-2-small.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/bilder/Bad-2.jpeg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

BIN
public/bilder/Bad-2.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

BIN
public/bilder/Bad-3-small.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
public/bilder/Bad-3-small.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/bilder/Bad-3.jpeg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

BIN
public/bilder/Bad-3.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

BIN
public/bilder/Bad-4-small.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
public/bilder/Bad-4-small.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
public/bilder/Bad-4.jpeg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
public/bilder/Bad-4.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
public/bilder/Bad-small.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
public/bilder/Bad-small.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/bilder/Bad.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

BIN
public/bilder/Bad.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
public/bilder/Kinderzimmer 2.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
public/bilder/Kinderzimmer 2.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
public/bilder/Kinderzimmer 3.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/bilder/Kinderzimmer 3.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/bilder/Kinderzimmer.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
public/bilder/Kinderzimmer.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
public/bilder/Küche 1-small.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/bilder/Küche 1.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

BIN
public/bilder/Küche 1.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
public/bilder/favicon/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,10 @@
{
"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: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/bilder/grundrisse/EG.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
public/bilder/grundrisse/EG.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/bilder/schlafzimmer.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 KiB

BIN
public/bilder/schlafzimmer.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/bilder/wohnzimmer2.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 KiB

BIN
public/bilder/wohnzimmer2.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

1317
public/css/haus-schleusingen.css Executable file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/fonts/DMSans-Light.ttf Executable file

Binary file not shown.

BIN
public/fonts/DMSans-Medium.ttf Executable file

Binary file not shown.

BIN
public/fonts/DMSans-Regular.ttf Executable file

Binary file not shown.

47
public/fonts/fonts.css Executable file
View File

@@ -0,0 +1,47 @@
@font-face {
font-family: "Cormorant Garamond";
font-style: normal;
font-weight: 300;
font-display: swap;
src: url("CormorantGaramond-Light.ttf") format("truetype");
}
@font-face {
font-family: "Cormorant Garamond";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("CormorantGaramond-Regular.ttf") format("truetype");
}
@font-face {
font-family: "Cormorant Garamond";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("CormorantGaramond-SemiBold.ttf") format("truetype");
}
@font-face {
font-family: "DM Sans";
font-style: normal;
font-weight: 300;
font-display: swap;
src: url("DMSans-Light.ttf") format("truetype");
}
@font-face {
font-family: "DM Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("DMSans-Regular.ttf") format("truetype");
}
@font-face {
font-family: "DM Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("DMSans-Medium.ttf") format("truetype");
}

44
public/index.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* Front Controller Single Entry Point
* All requests are routed through this file.
*/
// Autoloader (PSR-4 style, simple)
spl_autoload_register(function (string $class): void {
$prefix = 'App\\';
$baseDir = __DIR__ . '/../app/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relativeClass = substr($class, $len);
// Lowercase directory names, keep class name case
$parts = explode('\\', $relativeClass);
$className = array_pop($parts);
$parts = array_map('strtolower', $parts);
$parts[] = $className;
$file = $baseDir . implode('/', $parts) . '.php';
if (file_exists($file)) {
require $file;
}
});
use App\Core\Router;
$router = new Router();
// Define routes
$router->addRoute('/', \App\Controllers\HomeController::class, 'index');
$router->addRoute('/impressum', \App\Controllers\ImpressumController::class, 'index');
$router->addRoute('/datenschutz', \App\Controllers\DatenschutzController::class, 'index');
// Dispatch
$uri = $_SERVER['REQUEST_URI'] ?? '/';
$router->dispatch($uri);

259
public/js/haus-schleusingen.js Executable file
View File

@@ -0,0 +1,259 @@
document.addEventListener("DOMContentLoaded", function () {
// Navbar scroll
var navbar = document.getElementById("navbar");
window.addEventListener("scroll", function () {
if (window.scrollY > 60) navbar.classList.add("scrolled");
else navbar.classList.remove("scrolled");
});
// Hero animation on load
setTimeout(function () {
document.getElementById("heroContent").classList.add("visible");
document.getElementById("heroBg").classList.add("loaded");
}, 200);
// Scroll animations via IntersectionObserver
var animElements = document.querySelectorAll(".fact, [data-animate]");
animElements.forEach(function (el) {
el.style.opacity = "0";
el.style.transform = "translateY(30px)";
el.style.transition = "opacity 0.8s ease, transform 0.8s ease";
});
if ("IntersectionObserver" in window) {
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)";
});
}
// Floor accordion
// Floor accordion (vanilla JS + a11y)
document.querySelectorAll(".floor-header").forEach(function (header) {
header.addEventListener("click", function () {
var item = this.closest(".floor-item");
var isOpen = item.classList.contains("open");
var allItems = document.querySelectorAll(".floor-item");
// 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";
});
// 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();
}
});
});
// Lightbox track last focused element for focus return
var lightboxTrigger = null;
function openLightbox(src) {
lightboxTrigger = document.activeElement;
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 () {
document.getElementById("lightboxClose").focus();
}, 50);
}
function closeLightbox() {
document.getElementById("lightbox").classList.remove("open");
document.body.style.overflow = "";
// Return focus to trigger
if (lightboxTrigger) {
lightboxTrigger.focus();
lightboxTrigger = null;
}
}
// 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
document.querySelectorAll(".floor-plan img[data-img]").forEach(function (img) {
img.addEventListener("click", function () {
openLightbox(this.dataset.img);
});
});
document.getElementById("lightboxClose").addEventListener("click", closeLightbox);
document.getElementById("lightbox").addEventListener("click", function (e) {
if (e.target === this) closeLightbox();
});
document.addEventListener("keydown", function (e) {
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) {
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)
(function () {
var hamburger = document.querySelector(".nav-hamburger");
var nav = document.getElementById("navbar");
var overlay = document.querySelector(".nav-mobile-overlay");
var links = nav ? nav.querySelectorAll(".nav-links a") : [];
function toggleMenu() {
var isOpen = hamburger.classList.toggle("active");
nav.classList.toggle("mobile-open", isOpen);
if (overlay) overlay.classList.toggle("active", isOpen);
hamburger.setAttribute("aria-expanded", isOpen);
document.body.style.overflow = isOpen ? "hidden" : "";
}
function closeMenu() {
hamburger.classList.remove("active");
nav.classList.remove("mobile-open");
if (overlay) overlay.classList.remove("active");
hamburger.setAttribute("aria-expanded", "false");
document.body.style.overflow = "";
}
if (hamburger) {
hamburger.addEventListener("click", toggleMenu);
}
if (overlay) {
overlay.addEventListener("click", closeMenu);
}
links.forEach(function (link) {
link.addEventListener("click", closeMenu);
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") closeMenu();
});
// Close on resize to desktop
window.addEventListener("resize", function () {
if (window.innerWidth > 900) closeMenu();
});
})();

3
public/robots.txt Executable file
View File

@@ -0,0 +1,3 @@
User-agent: *
Allow: /
Sitemap: https://haus-schleusingen.de/haus-schleusingen.html