70 Commits

Author SHA1 Message Date
e7f2875287 Merge pull request 'fix(#62): Correct PLZ 98533 → 98553 in Lage-Section' (#63) from feature/issue-62-fix-plz into main
Some checks failed
Lint / PHP Syntax Check (push) Successful in 32s
Lint / HTML Lint (htmlhint) (push) Has been cancelled
Lint / CSS Lint (stylelint) (push) Has been cancelled
2026-05-22 16:31:32 +02:00
2b5b0afd91 Merge pull request 'fix(#58): Remove broken getElementById form stub' (#61) from feature/issue-58-fix-formsuccess into main
Some checks failed
Lint / PHP Syntax Check (push) Successful in 31s
Lint / CSS Lint (stylelint) (push) Successful in 1m12s
Lint / HTML Lint (htmlhint) (push) Has been cancelled
2026-05-22 16:24:52 +02:00
e896831b36 fix(#62): correct PLZ from 98533 to 98553 in lage section
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 25s
Lint / PHP Syntax Check (push) Successful in 32s
Lint / CSS Lint (stylelint) (push) Successful in 1m17s
Lint / HTML Lint (htmlhint) (push) Successful in 1m7s
Lint / PHP Syntax Check (pull_request) Successful in 32s
Lint / CSS Lint (stylelint) (pull_request) Successful in 1m14s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m7s
The PLZ was incorrect in the Lage-Section view (98533 instead of 98553).
HomeController and meta description already used the correct 98553.

Closes #62
2026-05-22 14:23:07 +00:00
3db7dc8971 fix(#58): remove broken getElementById('contactForm') stub
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 26s
Lint / PHP Syntax Check (push) Successful in 35s
Lint / CSS Lint (stylelint) (push) Successful in 1m15s
Lint / HTML Lint (htmlhint) (push) Successful in 1m11s
Lint / PHP Syntax Check (pull_request) Successful in 39s
Lint / CSS Lint (stylelint) (pull_request) Successful in 1m13s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m12s
The JS had a dangling document.getElementById('contactForm') call that
did nothing - no event handler attached, no return value used.

Form handling is entirely server-side (PHP POST → redirect to #form-result).
No JS form intervention needed.

Closes #58
2026-05-22 14:14:23 +00:00
e30bc5704b Merge PR #59: remove old haus-schleusingen.html references (fixes #56)
All checks were successful
Lint / PHP Syntax Check (push) Successful in 36s
Lint / CSS Lint (stylelint) (push) Successful in 1m25s
Lint / HTML Lint (htmlhint) (push) Successful in 1m12s
2026-05-22 14:05:29 +00:00
25a48e9958 Merge PR #60: fix contact form mailto + formSuccess ID (fixes #57, #58)
Some checks failed
Lint / PHP Syntax Check (push) Successful in 34s
Lint / HTML Lint (htmlhint) (push) Has been cancelled
Lint / CSS Lint (stylelint) (push) Has been cancelled
2026-05-22 14:04:07 +00:00
a170afa7c0 fix: remove mailto handler and fix formSuccess element ID (refs #57, #58)
All checks were successful
Lint / PHP Syntax Check (pull_request) Manual approval
Lint / CSS Lint (stylelint) (pull_request) Manual approval
Lint / HTML Lint (htmlhint) (pull_request) Manual approval
Deploy Feature Branch to Test / deploy (push) Manual approval
Lint / PHP Syntax Check (push) Manual approval
Lint / CSS Lint (stylelint) (push) Manual approval
Lint / HTML Lint (htmlhint) (push) Successful in 1m12s
2026-05-22 13:57:21 +00:00
148b4849fd fix: remove all references to old haus-schleusingen.html (refs #56)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 25s
Lint / PHP Syntax Check (push) Successful in 32s
Lint / CSS Lint (stylelint) (push) Successful in 1m15s
Lint / HTML Lint (htmlhint) (push) Successful in 1m11s
Lint / PHP Syntax Check (pull_request) Successful in 34s
Lint / CSS Lint (stylelint) (pull_request) Successful in 1m15s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m11s
2026-05-22 13:38:33 +00:00
Claw (AI)
4d2393f436 docs: add pre-commit hook activation instructions to README (closes #54)
All checks were successful
Lint / PHP Syntax Check (push) Successful in 32s
Lint / CSS Lint (stylelint) (push) Successful in 1m10s
Lint / HTML Lint (htmlhint) (push) Successful in 1m7s
2026-05-22 13:16:54 +00:00
6b605bb961 Merge pull request 'Fix #48: Dateien an korrekte Orte im Projekt verschieben' (#52) from feature/issue-48-cleanup-files into main
All checks were successful
Lint / PHP Syntax Check (push) Successful in 32s
Lint / CSS Lint (stylelint) (push) Successful in 1m13s
Lint / HTML Lint (htmlhint) (push) Successful in 1m9s
Reviewed-on: #52
2026-05-22 08:56:43 +02:00
Claw
9c2c8324b0 refactor: clean up file locations (#48)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 25s
Lint / PHP Syntax Check (push) Successful in 32s
Lint / CSS Lint (stylelint) (push) Successful in 1m12s
Lint / HTML Lint (htmlhint) (push) Successful in 1m8s
Lint / PHP Syntax Check (pull_request) Successful in 31s
Lint / CSS Lint (stylelint) (pull_request) Successful in 1m14s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m9s
- Remove duplicate bilder/ directory from root (already in public/bilder/)
- Move screenshots (docker-preview, page-preview, screenshot-landingpage) to docs/
- Remove duplicate robots.txt from root (already in public/)
- Update README.md image references to docs/ path
- Update deploy workflow exclusions
2026-05-22 06:53:50 +00:00
344b0d8271 Merge pull request 'Fix #41: CSP und Security Headers implementieren' (#49) from feature/issue-41-csp-header into main
All checks were successful
Lint / PHP Syntax Check (push) Successful in 33s
Lint / CSS Lint (stylelint) (push) Successful in 1m13s
Lint / HTML Lint (htmlhint) (push) Successful in 1m9s
Reviewed-on: #49
2026-05-22 08:34:34 +02:00
9b92136048 Merge pull request 'Fix #42: CSRF-Schutz für Kontaktformular' (#50) from feature/issue-42-csrf-protection into main
Some checks failed
Lint / PHP Syntax Check (push) Successful in 32s
Lint / HTML Lint (htmlhint) (push) Has been cancelled
Lint / CSS Lint (stylelint) (push) Has been cancelled
Reviewed-on: #50
2026-05-22 08:33:41 +02:00
bd1407f8ab Merge pull request 'Fix #43: Offene Redirects via REQUEST_URI fixen' (#51) from feature/issue-43-open-redirect-fix into main
Some checks failed
Lint / PHP Syntax Check (push) Successful in 33s
Lint / HTML Lint (htmlhint) (push) Has been cancelled
Lint / CSS Lint (stylelint) (push) Has been cancelled
Reviewed-on: #51
2026-05-22 08:32:23 +02:00
d44fb337e2 fix(security): replace REQUEST_URI with fixed path in redirects (#43)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
Lint / PHP Syntax Check (push) Successful in 33s
Lint / CSS Lint (stylelint) (push) Successful in 1m14s
Lint / HTML Lint (htmlhint) (push) Successful in 1m8s
Lint / PHP Syntax Check (pull_request) Successful in 32s
Lint / CSS Lint (stylelint) (pull_request) Successful in 1m12s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m8s
- Replace all 3 occurrences of $_SERVER['REQUEST_URI'] with '/'
- Prevents potential open redirect via client-controlled REQUEST_URI
- Safe since contact form only exists on homepage

Fix #43
2026-05-21 23:06:19 +00:00
a919a392cc fix(security): add CSRF protection to contact form (#42)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 25s
Lint / PHP Syntax Check (push) Successful in 32s
Lint / CSS Lint (stylelint) (push) Successful in 1m13s
Lint / HTML Lint (htmlhint) (push) Successful in 1m9s
Lint / PHP Syntax Check (pull_request) Successful in 32s
Lint / CSS Lint (stylelint) (pull_request) Successful in 1m16s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m7s
- Generate CSRF token (32 bytes) on GET requests
- Add hidden csrf_token field to contact form
- Validate token with hash_equals() (timing-safe) on POST
- Reject invalid/missing tokens with user-friendly error

Fix #42
2026-05-21 23:05:51 +00:00
2d9f1838b6 fix(security): add CSP and security headers via .htaccess (#41)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 25s
Lint / PHP Syntax Check (push) Successful in 39s
Lint / CSS Lint (stylelint) (push) Successful in 1m25s
Lint / HTML Lint (htmlhint) (push) Successful in 1m10s
Lint / PHP Syntax Check (pull_request) Successful in 34s
Lint / CSS Lint (stylelint) (pull_request) Successful in 1m12s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m8s
- Content-Security-Policy: strict CSP for static landingpage
- X-Content-Type-Options: nosniff
- X-Frame-Options: SAMEORIGIN
- Referrer-Policy: strict-origin-when-cross-origin

Fix #41
2026-05-21 23:04:52 +00:00
36b5639801 Merge pull request 'Refactoring: Umstellung auf Mini-MVC-Architektur (Issue #46)' (#47) from feature/issue-46-mvc-refactoring into main
All checks were successful
Lint / PHP Syntax Check (push) Successful in 32s
Lint / CSS Lint (stylelint) (push) Successful in 1m14s
Lint / HTML Lint (htmlhint) (push) Successful in 1m10s
2026-05-21 14:05:07 +02:00
ffbf23a524 merge: resolve conflicts with main – remove old files (MVC has all changes)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 26s
Lint / PHP Syntax Check (push) Successful in 32s
Lint / CSS Lint (stylelint) (push) Successful in 1m12s
Lint / HTML Lint (htmlhint) (push) Successful in 1m10s
Lint / PHP Syntax Check (pull_request) Successful in 32s
Lint / CSS Lint (stylelint) (pull_request) Successful in 1m13s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m11s
2026-05-21 11:42:39 +00:00
1aedcaf314 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
2026-05-19 14:38:38 +00:00
7e3b89bf63 Merge pull request 'Fix #44: CI Pipeline mit PHP/CSS/HTML Linting' (#45) from feature/issue-44-ci-lint-pipeline into main
All checks were successful
Lint / PHP Syntax Check (push) Successful in 34s
Lint / CSS Lint (stylelint) (push) Successful in 1m13s
Lint / HTML Lint (htmlhint) (push) Successful in 1m15s
Reviewed-on: #45
2026-05-19 16:05:46 +02:00
Claw
afbf4ef80e fix(ci): run lint on all branches, not just main
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 25s
Lint / PHP Syntax Check (push) Successful in 32s
Lint / CSS Lint (stylelint) (push) Successful in 1m13s
Lint / HTML Lint (htmlhint) (push) Successful in 1m17s
Lint / PHP Syntax Check (pull_request) Successful in 34s
Lint / CSS Lint (stylelint) (pull_request) Successful in 1m13s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m9s
2026-05-19 14:04:59 +00:00
Claw
a0615d10e2 fix(css): kebab-case keyframe name and empty line before rule
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 32s
Lint / PHP Syntax Check (pull_request) Successful in 37s
Lint / CSS Lint (stylelint) (pull_request) Successful in 1m26s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m23s
fix(php): duplicate id 'form-result' → 'form-errors' for error container
2026-05-19 13:58:11 +00:00
Claw
a0d89a93a6 feat(ci): add lint pipeline for PHP, CSS and HTML (#44)
Some checks failed
Deploy Feature Branch to Test / deploy (push) Successful in 30s
Lint / PHP Syntax Check (pull_request) Successful in 34s
Lint / CSS Lint (stylelint) (pull_request) Failing after 1m24s
Lint / HTML Lint (htmlhint) (pull_request) Successful in 1m18s
2026-05-19 13:53:46 +00:00
6612a0207a Merge pull request 'Fix #17: Bildoptimierung – WebP, Lazy Loading, Caching' (#22) from feature/issue-17-bildoptimierung-webp into main 2026-05-19 15:29:04 +02:00
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
127faaffaf feat(favicon): use Außenansicht as favicon base
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
2026-05-15 08:43:47 +00:00
c6eda36750 feat(favicon): add favicon and browser icons for Issue #36
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 37s
- favicon.ico (16x16 + 32x32)
- favicon-32x32.png, favicon-16x16.png
- apple-touch-icon.png (180x180)
- site.webmanifest
- Linked in index.php head

Resolves #36
2026-05-15 08:40:16 +00:00
336fbc12a6 Merge pull request 'Fix #18: Accessibility – ARIA-Labels, Focus-Management, Skip-Navigation' (#24) from feature/issue-18-accessibility into main 2026-05-15 10:32:45 +02:00
Claw AI
8b73603293 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
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
2026-05-15 08:32:26 +00:00
d609175b3c Merge pull request 'Fix #19: Remove jQuery dependency, replace with vanilla JS' (#21) from feature/issue-19-remove-jquery-masonry into main 2026-05-15 10:29:56 +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
greggy
1fcdca95b7 refactor(js): remove jQuery dependency and replace with vanilla JS
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
- Rewrite haus-schleusingen.js entirely in vanilla JavaScript
- Use IntersectionObserver instead of scroll event for scroll animations
- Replace jQuery slideUp/slideDown with display toggle for accordion
- Replace jQuery fadeIn with CSS opacity transition for form success
- Remove jQuery CDN script tag from haus-schleusingen.html
- Delete unused masonry.pkgd.min.js
- Remove jquery globals from eslint.config.js

Ref #19
2026-05-15 07:57:01 +00:00
88ef7aa6ac Merge pull request 'Fix #34: Kontaktformular E-Mail-Versand via PHP' (#35) from feature/issue-34-contact-form-mail into main 2026-05-15 09:50:43 +02:00
bf53da13be Fix: Scroll to form result after submission (PRG pattern with anchor)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 23s
2026-05-14 22:38:27 +00:00
2307c379dc Revert to PHP mail() for portability, remove AgentMail API dependency
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
2026-05-14 22:25:24 +00:00
2c6ed749d5 Fix: Use AgentMail API instead of mail(), fix reply_to format
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
2026-05-14 22:20:59 +00:00
c2f2709790 feat(contact): server-side PHP mail handler for contact form
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
Fix #34: E-Mail-Versand via PHP

- PHP POST handler with server-side validation (name, email, message)
- mail() with From/Reply-To set to form email address
- Recipient: mki@kies-media.de
- Honeypot spam protection (hidden field)
- Minimum submit time check (3 seconds)
- Session-based rate limiting (60s between submissions)
- Header injection protection
- Error messages displayed above form
- Success message after successful send
- Form values preserved on validation errors
- Removed client-side mailto: JavaScript logic
- Added CSS for error display and honeypot hiding
2026-05-14 19:12:43 +00:00
69ca8efa47 Merge pull request 'Rename haus-schleusingen.html to index.php' (#33) from feature/rename-to-index-php into main 2026-05-14 20:55:38 +02:00
40001adbce Rename haus-schleusingen.html to index.php
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
2026-05-14 18:53:15 +00:00
158f07e374 Merge pull request 'Fix #27: Mobile Navigation – Hamburger-Menü implementieren' (#32) from feature/issue-27-hamburger-menu into main 2026-05-14 20:46:19 +02:00
76b1ec58c2 style(nav): remove duplicate display property in mobile nav links
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
2026-05-14 17:40:51 +00:00
565c8b304d feat(nav): add hamburger menu for mobile navigation (Fix #27)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
- Hamburger button with animated X toggle (CSS-only icon)
- Slide-down mobile nav on ≤900px with 44px+ tap targets
- Semi-transparent overlay when menu is open
- Escape key + outside click + link click closes menu
- Auto-close on resize to desktop
- Desktop navigation unchanged
- Pure vanilla JS toggle, no jQuery dependency
2026-05-14 17:40:20 +00:00
51d4f96b20 Merge pull request 'Fix #28: CTA-Button im Header auffälliger gestalten' (#30) from feature/issue-28-cta-button into main 2026-05-14 18:43:37 +02:00
5167634ee6 Merge pull request 'Fix #29: Impressum und Datenschutz als eigene Seiten' (#31) from feature/issue-29-impressum-datenschutz into main 2026-05-14 18:43:36 +02:00
0995684989 feat(legal): add impressum and datenschutz pages
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 23s
Fix #29: Create impressum.html with full §5 TMG details and
datenschutz.html with DSGVO-compliant privacy policy.
Fix footer links in main page. Both pages use landingpage design,
have meta robots noindex, and are responsive.
2026-05-14 16:33:33 +00:00
7706f11106 feat(cta): make header CTA button more prominent
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
Fix #28: Enlarge CTA button, increase font weight, add border-radius
and box-shadow for better visibility. Improve hover effect with
stronger lift and shadow. Touch target >= 44px.
2026-05-14 16:31:30 +00:00
143962a0fa Merge remote-tracking branch 'origin/feature/issue-16-seo-meta-schema' 2026-05-14 16:17:34 +00:00
04d570cb91 Merge pull request 'Fix #6: Datenschutz-Link im Footer aktualisieren' (#25) from feature/issue-6-datenschutz-link into main
Reviewed-on: #25
2026-05-14 11:23:54 +02:00
Claw AI
8706cb2b70 Merge branch 'main' into feature/issue-6-datenschutz-link
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 23s
2026-05-14 09:21:55 +00:00
Claw AI
3df40952c1 chore: remove pipeline test marker 2026-05-14 09:07:41 +00:00
Claw AI
c467f8cc1e ci: add index.html copy from main page
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 23s
2026-05-14 09:03:03 +00:00
Claw AI
d4a59ee306 ci: fix rsync dependency and permissions in deployment
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
2026-05-14 09:01:26 +00:00
Claw AI
7be0a2e9d5 test: add pipeline test marker
Some checks failed
Deploy Feature Branch to Test / deploy (push) Failing after 42s
2026-05-14 08:59:26 +00:00
Claw AI
10bc7c1d77 ci: fix volume mount for deployment target 2026-05-14 08:59:16 +00:00
Claw AI
e24db23888 ci: add deploy pipeline for feature branches to test environment 2026-05-14 08:58:55 +00:00
48df09df6c fix: Datenschutz-Link auf /datenschutz mit target=_blank (resolves #6) 2026-05-14 08:42:05 +00:00
Claw (AI)
3bbbe85599 feat(seo): add meta description, canonical, Open Graph and Schema.org JSON-LD
- Add meta description with property details
- Add canonical URL (haus-schleusingen.de placeholder, pending Martin's confirmation)
- Add Open Graph tags (og:type, og:title, og:description, og:image, og:url, og:locale, og:site_name)
- Add Schema.org RealEstateListing structured data (JSON-LD)
- Optimize title tag for SEO
- Create robots.txt with sitemap reference

Ref #16
2026-05-13 23:10:55 +00:00
88780c300a Merge pull request 'Fix #15: Kontaktformular – mailto:-Integration für echte Anfragen' (#20) from feature/issue-15-kontaktformular-backend into main
Reviewed-on: #20
2026-05-14 00:45:55 +02:00
Claw
cc7b2d8d70 fix(kontakt): replace simulated form submit with mailto: link
- Generate mailto: link with all form data on submit
- Opens user's email client with pre-filled email to mki@kies-media.de
- Subject includes interest type, body contains all form fields
- Update success message to reflect email client behavior
- Remove fake setTimeout simulation

Fix #15
2026-05-13 22:39:42 +00:00
ea85280cde Merge pull request 'Fix #11: Kontakt-Section mit Email hinzufügen' (#14) from feature/issue-11 into main
Reviewed-on: #14
2026-05-11 12:50:05 +02:00
958f52fd5d Fix #11: Kontakt-Section mit Email hinzufügen
- Email mki@kies-media.de als direkter Kontaktlink unter dem Formular
- Neues .contact-details CSS passend zum bestehenden Design
- Minimaler Eingriff: bestehendes Design bleibt erhalten
2026-05-11 06:38:28 +00:00
9c68365ab8 Merge pull request 'Fix #4: Impressum-Link im Footer hinzufügen' (#5) from feature/issue-4 into main
Reviewed-on: #5
2026-05-10 23:00:47 +02:00
5b304730fa feat: update Impressum-Link im Footer (Issue #4) 2026-05-10 21:22:14 +02:00
131 changed files with 1840 additions and 659 deletions

0
.continue/mcpServers/new-mcp-server.yaml Normal file → Executable file
View File

0
.dockerignore Normal file → Executable file
View File

View File

@@ -0,0 +1,67 @@
name: Deploy Feature Branch to Test
on:
push:
branches:
- "feature/**"
jobs:
deploy:
runs-on: ubuntu-latest
container:
volumes:
- /var/www/test/html:/deploy
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Show branch info
run: |
echo "=== Deploying branch: ${{ gitea.ref_name }} ==="
echo "=== Commit: ${{ gitea.sha }} ==="
echo "=== By: ${{ gitea.actor }} ==="
date
- name: Deploy to test environment
run: |
echo "Syncing files to test environment..."
apt-get update -qq && apt-get install -y -qq rsync > /dev/null 2>&1 || true
rsync -av --delete \
--exclude='.git' \
--exclude='.gitea' \
--exclude='.husky' \
--exclude='.prettierrc' \
--exclude='.prettierignore' \
--exclude='.stylelintrc.json' \
--exclude='.htmlhintrc' \
--exclude='.gitignore' \
--exclude='.dockerignore' \
--exclude='Dockerfile' \
--exclude='nginx.conf' \
--exclude='eslint.config.js' \
--exclude='package.json' \
--exclude='docs/' \
--exclude='AGENTS.md' \
--exclude='README.md' \
./ /deploy/
echo "✅ Deployment complete!"
- name: Set permissions
run: |
chown -R 33:33 /deploy/ 2>/dev/null || true
chmod -R 755 /deploy/ 2>/dev/null || true
echo "✅ Permissions set"
- name: Deployment summary
run: |
echo "=========================================="
echo " 🚀 Deployment Summary"
echo "=========================================="
echo " Branch: ${{ gitea.ref_name }}"
echo " Commit: ${{ gitea.sha }}"
echo " Target: http://178.104.150.0/"
echo " Time: $(date)"
echo "=========================================="

64
.gitea/workflows/lint.yml Normal file
View File

@@ -0,0 +1,64 @@
name: Lint
on:
push:
pull_request:
jobs:
lint-php:
name: PHP Syntax Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install PHP
run: |
apt-get update -qq && apt-get install -y -qq php-cli > /dev/null 2>&1
- name: PHP Lint
run: |
errors=0
while IFS= read -r file; do
if ! php -l "$file" > /dev/null 2>&1; then
echo "❌ Syntax error in $file"
php -l "$file"
errors=1
fi
done < <(find . -name "*.php" -not -path "./vendor/*")
if [ "$errors" -eq 1 ]; then
echo "::error::PHP lint check failed"
exit 1
fi
echo "✅ All PHP files pass syntax check"
lint-css:
name: CSS Lint (stylelint)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js & stylelint
run: |
apt-get update -qq && apt-get install -y -qq npm nodejs > /dev/null 2>&1
npm install -g stylelint stylelint-config-standard stylelint-prettier > /dev/null 2>&1
- name: CSS Lint
run: |
npx stylelint "**/*.css" --config .stylelintrc.json --allow-empty-input
echo "✅ All CSS files pass lint"
lint-html:
name: HTML Lint (htmlhint)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js & htmlhint
run: |
apt-get update -qq && apt-get install -y -qq npm nodejs > /dev/null 2>&1
npm install -g htmlhint > /dev/null 2>&1
- name: HTML Lint
run: |
npx htmlhint "**/*.html" --config .htmlhintrc
echo "✅ All HTML files pass lint"

0
.gitignore vendored Normal file → Executable file
View File

8
.htaccess Normal file
View File

@@ -0,0 +1,8 @@
# Legacy redirects for old URLs pointing to root
RewriteEngine On
RewriteRule ^impressum\.html$ /impressum [R=301,L]
RewriteRule ^datenschutz\.html$ /datenschutz [R=301,L]
RewriteRule ^haus-schleusingen\.html$ / [R=301,L]
# Everything else goes to public/
RewriteRule ^(.*)$ public/$1 [L]

0
.htmlhintrc Normal file → Executable file
View File

0
.husky/pre-commit Normal file → Executable file
View File

0
.prettierignore Normal file → Executable file
View File

0
.prettierrc Normal file → Executable file
View File

0
.stylelintrc.json Normal file → Executable file
View File

2
AGENTS.md Normal file → Executable file
View File

@@ -17,7 +17,7 @@
| Pfad | Beschreibung | | Pfad | Beschreibung |
| --------------------------- | -------------------------------------------- | | --------------------------- | -------------------------------------------- |
| `haus-schleusingen.html` | Einstiegsseite (einzige HTML-Datei) | | `public/index.php` | Einstiegsseite (PHP-Entry-Point) |
| `css/haus-schleusingen.css` | Hauptstylesheet | | `css/haus-schleusingen.css` | Hauptstylesheet |
| `js/haus-schleusingen.js` | Haupt-JavaScript | | `js/haus-schleusingen.js` | Haupt-JavaScript |
| `js/masonry.pkgd.min.js` | Masonry-Layout-Bibliothek (nicht bearbeiten) | | `js/masonry.pkgd.min.js` | Masonry-Layout-Bibliothek (nicht bearbeiten) |

0
Dockerfile Normal file → Executable file
View File

20
README.md Normal file → Executable file
View File

@@ -4,8 +4,8 @@ Statische Landingpage für **Haus Schleusingen**.
Das Projekt basiert auf reinem HTML, CSS und JavaScript und wird über einen Nginx-Container ausgeliefert. Das Projekt basiert auf reinem HTML, CSS und JavaScript und wird über einen Nginx-Container ausgeliefert.
<div align="right"> <div align="right">
<a href="screenshot-landingpage.png"> <a href="docs/screenshot-landingpage.png">
<img src="screenshot-landingpage-thumb.png" alt="Screenshot der Landingpage Haus Schleusingen" width="300" /> <img src="docs/screenshot-landingpage-thumb.png" alt="Screenshot der Landingpage Haus Schleusingen" width="300" />
</a> </a>
</div> </div>
@@ -44,7 +44,7 @@ Das Projekt basiert auf reinem HTML, CSS und JavaScript und wird über einen Ngi
## Projektstruktur ## Projektstruktur
``` ```
├── haus-schleusingen.html # Einstiegsseite (einzige HTML-Datei) ├── public/index.php # Einstiegsseite (PHP-Entry-Point)
├── css/ ├── css/
│ └── haus-schleusingen.css # Hauptstylesheet │ └── haus-schleusingen.css # Hauptstylesheet
├── js/ ├── js/
@@ -172,6 +172,20 @@ npm run lint
## Git-Workflow ## Git-Workflow
### Pre-Commit Hooks aktivieren
Die Pre-Commit Hooks (Husky + lint-staged) werden automatisch beim Installieren der Abhängigkeiten eingerichtet:
```bash
npm install
```
Der `prepare`-Script in `package.json` (`"prepare": "husky"`) sorgt dafür, dass Husky die Git Hooks im `.husky/`-Verzeichnis registriert. Nach `npm install` sind die Hooks aktiv kein manueller Schritt nötig.
> **Falls Hooks nicht laufen:** Prüfe ob `.husky/pre-commit` ausführbar ist (`chmod +x .husky/pre-commit`) und ob `core.hooksPath` nicht überschrieben wurde (`git config core.hooksPath`).
### Was wird beim Commit geprüft?
Beim Committen führt **Husky** automatisch den Pre-Commit Hook (`.husky/pre-commit`) aus, der **lint-staged** startet. Beim Committen führt **Husky** automatisch den Pre-Commit Hook (`.husky/pre-commit`) aus, der **lint-staged** startet.
### lint-staged prüft automatisch: ### lint-staged prüft automatisch:

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace App\Controllers;
use App\Core\View;
abstract class Controller
{
protected View $view;
public function __construct()
{
$this->view = new View();
}
protected function render(string $view, array $data = [], string $layout = 'main'): void
{
foreach ($data as $key => $value) {
$this->view->assign($key, $value);
}
$this->view->render($view, $layout);
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace App\Controllers;
class DatenschutzController extends Controller
{
public function index(): void
{
$this->render('datenschutz/index', [
'pageTitle' => 'Datenschutzerklärung Haus Schleusingen',
'pageDescription' => 'Datenschutzerklärung der Website haus-schleusingen.de',
'robots' => 'noindex',
'canonical' => 'https://haus-schleusingen.de/datenschutz',
]);
}
}

View File

@@ -0,0 +1,187 @@
<?php
declare(strict_types=1);
namespace App\Controllers;
class HomeController extends Controller
{
public function index(): void
{
session_start();
// --- Helper functions ---
$normalizeContactValue = function (string $value): string {
return trim($value);
};
$escapeContactValue = function (string $value): string {
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
};
$containsHeaderInjection = function (string $value): bool {
return (bool) preg_match('/[\r\n]/', $value);
};
// --- Form processing ---
$formErrors = [];
$formSuccess = false;
if (!empty($_SESSION['form_success'])) {
$formSuccess = true;
unset($_SESSION['form_success']);
}
if (!empty($_SESSION['form_errors'])) {
$formErrors = $_SESSION['form_errors'];
unset($_SESSION['form_errors']);
}
if (!empty($_SESSION['form_data'])) {
$formData = $_SESSION['form_data'];
unset($_SESSION['form_data']);
} else {
$formData = ['fname' => '', 'lname' => '', 'email' => '', 'phone' => '', 'interest' => 'Besichtigung anfragen', 'message' => ''];
}
// CSRF-Token generieren (nach Session-Start)
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// CSRF-Token validieren
$csrfToken = $_POST['csrf_token'] ?? '';
if (!hash_equals($_SESSION['csrf_token'] ?? '', $csrfToken)) {
header('Location: /#form-result');
$_SESSION['form_errors'] = ['Sicherheitsüberprüfung fehlgeschlagen. Bitte versuchen Sie es erneut.'];
exit;
}
$formData['fname'] = $normalizeContactValue((string) ($_POST['fname'] ?? ''));
$formData['lname'] = $normalizeContactValue((string) ($_POST['lname'] ?? ''));
$formData['email'] = $normalizeContactValue((string) ($_POST['email'] ?? ''));
$formData['phone'] = $normalizeContactValue((string) ($_POST['phone'] ?? ''));
$formData['interest'] = $normalizeContactValue((string) ($_POST['interest'] ?? ''));
$formData['message'] = $normalizeContactValue((string) ($_POST['message'] ?? ''));
$honeypot = $normalizeContactValue((string) ($_POST['website'] ?? ''));
if ($honeypot !== '') {
header('Location: /#form-result');
$_SESSION['form_success'] = true;
exit;
} else {
if ($formData['fname'] === '') {
$formErrors[] = 'Bitte geben Sie Ihren Vornamen an.';
}
if ($formData['lname'] === '') {
$formErrors[] = 'Bitte geben Sie Ihren Nachnamen an.';
}
if ($formData['email'] === '' || !filter_var($formData['email'], FILTER_VALIDATE_EMAIL)) {
$formErrors[] = 'Bitte geben Sie eine gültige E-Mail-Adresse an.';
}
if ($formData['message'] === '') {
$formErrors[] = 'Bitte geben Sie eine Nachricht ein.';
}
if ($containsHeaderInjection($formData['email']) || $containsHeaderInjection($formData['fname'] . ' ' . $formData['lname'])) {
$formErrors[] = 'Ungültige Zeichen in den Eingabefeldern.';
}
$formTime = isset($_POST['form_time']) ? (int) $_POST['form_time'] : 0;
if ($formTime > 0 && (time() - $formTime) < 3) {
$formErrors[] = 'Das Formular wurde zu schnell abgeschickt. Bitte versuchen Sie es erneut.';
}
$lastSubmit = $_SESSION['last_contact_submit'] ?? 0;
if ($lastSubmit && (time() - $lastSubmit) < 60) {
$formErrors[] = 'Bitte warten Sie einen Moment vor der nächsten Anfrage.';
}
if (empty($formErrors)) {
$to = 'mki@kies-media.de';
$subject = 'Kontaktanfrage: ' . $formData['interest'];
$body = "Von: {$formData['fname']} {$formData['lname']}\n"
. "E-Mail: {$formData['email']}\n";
if ($formData['phone'] !== '') {
$body .= "Telefon: {$formData['phone']}\n";
}
$body .= "Anliegen: {$formData['interest']}\n\n"
. $formData['message'];
$headers = "From: {$formData['email']}\r\n";
$headers .= "Reply-To: {$formData['email']}\r\n";
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
$mailSent = mail($to, $subject, $body, $headers);
if ($mailSent) {
$_SESSION['last_contact_submit'] = time();
header('Location: /#form-result');
$_SESSION['form_success'] = true;
exit;
} else {
$formErrors[] = 'Leider konnte die E-Mail nicht gesendet werden. Bitte versuchen Sie es später erneut oder schreiben Sie uns direkt an mki@kies-media.de.';
}
}
}
if (!empty($formErrors)) {
header('Location: /#form-result');
$_SESSION['form_errors'] = $formErrors;
$_SESSION['form_data'] = $formData;
exit;
}
}
$this->render('home/index', [
'formSuccess' => $formSuccess,
'formErrors' => $formErrors,
'formData' => $formData,
'escapeContactValue' => $escapeContactValue,
'pageTitle' => 'Einfamilienhaus mieten Schleusingen | 227 m², 6 Zimmer | 1.300 € Kaltmiete',
'pageDescription' => '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.',
'canonical' => 'https://haus-schleusingen.de/',
'openGraph' => [
'ogTitle' => 'Einfamilienhaus zur Miete in Schleusingen 227 m², 6 Zimmer',
'ogDescription' => 'Großzügiges Einfamilienhaus zur Langzeitmiete: 227 m², 6 Zimmer, 3 Etagen + Dachterrasse. Kaltmiete 1.300 €. Ab sofort verfügbar in Schleusingen.',
'ogImage' => 'https://haus-schleusingen.de/bilder/Außenansicht-2.png',
'ogUrl' => 'https://haus-schleusingen.de/',
],
'structuredData' => json_encode([
'@context' => 'https://schema.org',
'@type' => 'RealEstateListing',
'name' => 'Einfamilienhaus zur Miete in Schleusingen',
'description' => 'Großzügiges Einfamilienhaus zur Langzeitmiete: 227 m² Wohnfläche, 6 Zimmer, 3 Etagen mit Dachterrasse. Kaltmiete 1.300 €.',
'url' => 'https://haus-schleusingen.de/',
'image' => 'https://haus-schleusingen.de/bilder/Außenansicht-2.png',
'datePosted' => '2026-05-14',
'address' => [
'@type' => 'PostalAddress',
'streetAddress' => 'Bahnhofstraße 10',
'addressLocality' => 'Schleusingen',
'postalCode' => '98553',
'addressCountry' => 'DE',
],
'offers' => [
'@type' => 'Offer',
'price' => '1300',
'priceCurrency' => 'EUR',
'priceSpecification' => [
'@type' => 'UnitPriceSpecification',
'price' => '1300',
'priceCurrency' => 'EUR',
'unitCode' => 'MON',
'description' => 'Kaltmiete pro Monat',
],
],
'floorSize' => [
'@type' => 'QuantitativeValue',
'value' => '227',
'unitCode' => 'MTK',
],
'numberOfRooms' => [
'@type' => 'QuantitativeValue',
'value' => '6',
],
]),
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace App\Controllers;
class ImpressumController extends Controller
{
public function index(): void
{
$this->render('impressum/index', [
'pageTitle' => 'Impressum Haus Schleusingen',
'pageDescription' => 'Impressum der Website haus-schleusingen.de',
'robots' => 'noindex',
'canonical' => 'https://haus-schleusingen.de/impressum',
]);
}
}

60
app/core/Router.php Normal file
View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace App\Core;
class Router
{
private array $routes = [];
public function addRoute(string $path, string $controller, string $action = 'index'): void
{
$this->routes[$path] = [
'controller' => $controller,
'action' => $action,
];
}
public function dispatch(string $uri): void
{
// Normalize: strip query string and trailing slash
$path = parse_url($uri, PHP_URL_PATH);
$path = rtrim($path, '/') ?: '/';
// Direct match
if (isset($this->routes[$path])) {
$this->execute($this->routes[$path]);
return;
}
// Legacy .html redirect (301)
if (preg_match('#^/(impressum|datenschutz)\.html$#', $path, $m)) {
header('Location: /' . $m[1], true, 301);
exit;
}
// 404
http_response_code(404);
echo '<h1>404 Seite nicht gefunden</h1>';
echo '<p><a href="/">Zurück zur Startseite</a></p>';
}
private function execute(array $route): void
{
$controllerClass = $route['controller'];
$action = $route['action'];
if (!class_exists($controllerClass)) {
throw new \RuntimeException("Controller {$controllerClass} nicht gefunden.");
}
$controller = new $controllerClass();
if (!method_exists($controller, $action)) {
throw new \RuntimeException("Action {$action} in {$controllerClass} nicht gefunden.");
}
$controller->$action();
}
}

46
app/core/View.php Normal file
View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace App\Core;
class View
{
private string $viewsPath;
private array $data = [];
public function __construct(?string $viewsPath = null)
{
$this->viewsPath = $viewsPath ?? dirname(__DIR__) . '/views';
}
public function assign(string $key, mixed $value): void
{
$this->data[$key] = $value;
}
public function render(string $view, string $layout = 'main'): void
{
$viewFile = $this->viewsPath . '/' . $view . '.php';
$layoutFile = $this->viewsPath . '/layouts/' . $layout . '.php';
if (!file_exists($viewFile)) {
throw new \RuntimeException("View {$view} nicht gefunden: {$viewFile}");
}
if (!file_exists($layoutFile)) {
throw new \RuntimeException("Layout {$layout} nicht gefunden: {$layoutFile}");
}
// Extract data to variables for the view
extract($this->data, EXTR_SKIP);
// Capture view content
ob_start();
require $viewFile;
$content = ob_get_clean();
// Render layout with $content
require $layoutFile;
}
}

View File

@@ -0,0 +1,123 @@
<nav id="navbar" class="scrolled">
<div class="nav-logo">Bahnhofstraße 10</div>
<ul class="nav-links">
<li><a href="/#galerie">Galerie</a></li>
<li><a href="/#grundriss">Grundriss</a></li>
<li><a href="/#miete">Miete</a></li>
<li><a href="/#lage">Lage</a></li>
</ul>
<a href="/#kontakt" class="nav-cta" style="text-decoration:none;">Jetzt anfragen</a>
</nav>
<main class="legal-page">
<div class="section-eyebrow">Datenschutz</div>
<h1>Datenschutzerklärung</h1>
<h2>1. Verantwortliche Stelle</h2>
<address>
Martin Kiesewetter<br />
Am Schaftalsgraben 4<br />
98529 Suhl<br />
Telefon: 0176 45853923<br />
E-Mail: <a href="mailto:mki@kies-media.de">mki@kies-media.de</a>
</address>
<p>
Diese Datenschutzerklärung informiert Sie über die Art, den Umfang und den Zweck der Erhebung und Verwendung personenbezogener Daten auf dieser Website.
</p>
<hr class="legal-divider" />
<h2>2. Erhebung und Speicherung personenbezogener Daten</h2>
<h3>a) Beim Besuch der Website</h3>
<p>
Beim Aufrufen dieser Website werden durch den Hosting-Anbieter automatisch Informationen allgemeiner Natur erfasst. Diese Informationen (Server-Logfiles) beinhalten etwa die Art des Webbrowsers, das verwendete Betriebssystem, den Domainnamen Ihres Internet-Service-Providers, Ihre IP-Adresse und Ähnliches. Sie werden insbesondere zu einem sicheren und reibungslosen Betrieb der Website benötigt.
</p>
<ul>
<li>IP-Adresse</li>
<li>Datum und Uhrzeit der Anfrage</li>
<li>Zeitzonenunterschied zur Greenwich Mean Time (GMT)</li>
<li>Inhalt der Anforderung (konkrete Seite)</li>
<li>Zugriffsstatus/HTTP-Statuscode</li>
<li>Jeweils übertragene Datenmenge</li>
<li>Website, von der die Anforderung kommt (Referrer-URL)</li>
<li>Verwendeter Browser</li>
<li>Verwendetes Betriebssystem</li>
</ul>
<p>
Die Verarbeitung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Die Daten werden nicht mit anderen Datenquellen zusammengeführt und nach 30 Tagen automatisch gelöscht.
</p>
<h3>b) Kontakt per E-Mail</h3>
<p>
Auf dieser Website wird die Kontaktaufnahme über einen E-Mail-Link (mailto:) angeboten. Wenn Sie uns per E-Mail kontaktieren, werden Ihre Angaben (E-Mail-Adresse, ggf. Name und Nachricht) zwecks Bearbeitung Ihrer Anfrage gespeichert. Die Daten werden ausschließlich zur Beantwortung Ihrer Anfrage verwendet und nach Abschluss der Kommunikation gelöscht.
</p>
<p>
Rechtsgrundlage ist Art. 6 Abs. 1 lit. a DSGVO (Ihre Einwilligung) oder Art. 6 Abs. 1 lit. b DSGVO (zur Erfüllung eines Vertrags bzw. vorvertraglicher Maßnahmen).
</p>
<hr class="legal-divider" />
<h2>3. Cookies</h2>
<p>
Diese Website verwendet <strong>keine Cookies</strong>. Es werden keine Tracking-Cookies, Werbe-Cookies oder sonstige Cookies gesetzt.
</p>
<h2>4. Tracking &amp; Analyse</h2>
<p>
Diese Website setzt <strong>keine Tracking- oder Analyse-Tools</strong> ein. Es werden keine Besucherstatistiken erstellt, kein Google Analytics, kein Facebook Pixel und keine ähnlichen Dienste verwendet.
</p>
<h2>5. Social-Media-Plugins</h2>
<p>
Diese Website verwendet <strong>keine Social-Media-Plugins</strong> (Facebook, Twitter, Instagram etc.).
</p>
<hr class="legal-divider" />
<h2>6. Google Maps</h2>
<p>
Auf dieser Website wird ein Google Maps-Embed (Kartenansicht) eingebunden. Beim Laden der Karte werden Daten an Google übertragen, darunter möglicherweise Ihre IP-Adresse. Google Maps wird ausschließlich genutzt, um Ihnen die Lage des Mietobjekts anzuzeigen.
</p>
<p>
Weitere Informationen zum Datenschutz bei Google finden Sie unter: <a href="https://policies.google.com/privacy" target="_blank" rel="noopener">https://policies.google.com/privacy</a>
</p>
<p>
Rechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO (berechtigte Interessen an der Darstellung des Objektstandorts).
</p>
<hr class="legal-divider" />
<h2>7. SSL-Verschlüsselung</h2>
<p>
Diese Seite nutzt aus Sicherheitsgründen eine SSL-Verschlüsselung. Eine verschlüsselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von „http://" auf „https://" wechselt und an dem Schloss-Symbol in Ihrer Browserzeile.
</p>
<hr class="legal-divider" />
<h2>8. Ihre Rechte</h2>
<p>Sie haben gegenüber uns folgende Rechte hinsichtlich der Sie betreffenden personenbezogenen Daten:</p>
<ul>
<li><strong>Recht auf Auskunft</strong> (Art. 15 DSGVO)</li>
<li><strong>Recht auf Berichtigung</strong> (Art. 16 DSGVO)</li>
<li><strong>Recht auf Löschung</strong> (Art. 17 DSGVO)</li>
<li><strong>Recht auf Einschränkung der Verarbeitung</strong> (Art. 18 DSGVO)</li>
<li><strong>Recht auf Datenübertragbarkeit</strong> (Art. 20 DSGVO)</li>
<li><strong>Widerspruchsrecht</strong> (Art. 21 DSGVO)</li>
<li><strong>Recht auf Widerruf der Einwilligung</strong> (Art. 7 Abs. 3 DSGVO)</li>
<li><strong>Beschwerderecht bei einer Aufsichtsbehörde</strong> (Art. 77 DSGVO)</li>
</ul>
<p>
Zur Ausübung Ihrer Rechte wenden Sie sich bitte an: <a href="mailto:mki@kies-media.de">mki@kies-media.de</a>
</p>
<a href="/" class="legal-back"> Zurück zum Objekt</a>
</main>
<footer>
<div class="footer-logo">Bahnhofstraße 10 · Schleusingen</div>
<div class="footer-links">
<a href="/impressum">Impressum</a>
<a href="/datenschutz">Datenschutz</a>
</div>
</footer>

505
app/views/home/index.php Normal file
View File

@@ -0,0 +1,505 @@
<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>
</button>
<ul class="nav-links">
<li><a href="#galerie">Galerie</a></li>
<li><a href="#grundriss">Grundriss</a></li>
<li><a href="#miete">Miete</a></li>
<li><a href="#lage">Lage</a></li>
</ul>
<button
class="nav-cta"
onclick="$('html').animate({ scrollTop: $('#kontakt').offset().top }, 700)"
>
Jetzt anfragen
</button>
</nav>
<div class="nav-mobile-overlay" aria-hidden="true"></div>
<section class="hero" id="hero">
<div
class="hero-bg"
id="heroBg"
style="background-image: url(/bilder/Außenansicht-2.webp)"
></div>
<div class="hero-overlay"></div>
<div class="hero-content" id="heroContent">
<div class="hero-tag">Zur Langzeitmiete · Ab sofort verfügbar</div>
<h1>
Großzügiges
<br />
<em>Einfamilienhaus</em>
<br />
in Schleusingen
</h1>
<div class="hero-meta">
<span><strong>Schleusinger Bahnhofstraße 10</strong></span>
<span>227 Wohnfläche</span>
<span>6 Zimmer</span>
<span>3 Etagen + Dachterrasse</span>
</div>
</div>
<div class="hero-scroll">
<span>Entdecken</span>
<div class="scroll-line"></div>
</div>
</section>
<main id="main-content">
<div class="facts-strip">
<div class="fact">
<div class="fact-val">227</div>
<div class="fact-label"> Wohnfläche</div>
</div>
<div class="fact">
<div class="fact-val">6</div>
<div class="fact-label">Zimmer</div>
</div>
<div class="fact">
<div class="fact-val">3</div>
<div class="fact-label">Etagen</div>
</div>
<div class="fact">
<div class="fact-val">1.300</div>
<div class="fact-label"> Kaltmiete</div>
</div>
</div>
<section class="intro" id="intro">
<div class="intro-text" data-animate>
<div class="section-eyebrow">Das Objekt</div>
<h2>Wohnen mit Charakter und viel Raum</h2>
<p>
Vermietet wird ein vollständiges Einfamilienhaus in ruhiger Lage von Schleusingen. Das
Haus verbindet historischen Charme mit modernem Wohnkomfort auf drei großzügigen Etagen.
</p>
<p>
Garage für zwei Fahrzeuge, großzügige Dachterrasse mit 35,8 , vollausgestattete Küche,
Vollbad sowie Abstell- und Nutzräume machen das Haus zu einem außergewöhnlichen
Mietobjekt.
</p>
<div class="intro-stats">
<div>
<div class="istat-val">154,9 </div>
<div class="istat-label">Nutzfläche</div>
</div>
<div>
<div class="istat-val">35,8 </div>
<div class="istat-label">Dachterrasse</div>
</div>
<div>
<div class="istat-val">2 Stpl.</div>
<div class="istat-label">Garage</div>
</div>
</div>
</div>
<div class="intro-img" data-animate>
<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 </div>
</div>
</section>
<section id="galerie" class="gallery-section" aria-label="Fotogalerie">
<div class="gallery-header">
<div>
<div class="section-eyebrow">Fotogalerie</div>
<h2>Einblicke ins Haus</h2>
</div>
</div>
<div class="masonry-grid">
<div class="grid-sizer"></div>
<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.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 </span>
</div>
<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 </span>
</div>
<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 </span>
</div>
<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 </span>
</div>
<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 </span>
</div>
<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 </span>
</div>
<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.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 </span>
</div>
<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/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/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>
</section>
<section class="floors-section" id="grundriss">
<div class="section-eyebrow">Raumaufteilung</div>
<h2>Großzügig auf allen Etagen</h2>
<div class="floor-accordion">
<div class="floor-item">
<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 </span>
<div class="floor-icon">+</div>
</div>
</div>
<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<span class="room-chip-area">20,1 </span></div>
<div class="room-chip">WC<span class="room-chip-area">0,8 </span></div>
<div class="room-chip">Garage / Partykeller<span class="room-chip-area">42,6 </span></div>
<div class="room-chip">Abstellraum 1<span class="room-chip-area">9,9 </span></div>
<div class="room-chip">Abstellraum 2<span class="room-chip-area">7,8 </span></div>
<div class="room-chip">Heizungskeller<span class="room-chip-area">18,3 </span></div>
</div>
<div class="floor-plan floor-plan-multi">
<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>
<div class="floor-item">
<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 </span>
<div class="floor-icon">+</div>
</div>
</div>
<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<span class="room-chip-area">20,1 </span></div>
<div class="room-chip">Wohnzimmer<span class="room-chip-area">42,6 </span></div>
<div class="room-chip">Gästezimmer<span class="room-chip-area">11,5 </span></div>
<div class="room-chip">Badezimmer<span class="room-chip-area">9,8 </span></div>
<div class="room-chip">Küche<span class="room-chip-area">18,4 </span></div>
<div class="room-chip">Schlafzimmer<span class="room-chip-area">18,0 </span></div>
</div>
<div class="floor-plan floor-plan-multi">
<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>
<div class="floor-item">
<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 </span>
<div class="floor-icon">+</div>
</div>
</div>
<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<span class="room-chip-area">13,9 </span></div>
<div class="room-chip">Kinderzimmer 1<span class="room-chip-area">21,7 </span></div>
<div class="room-chip">Kinderzimmer 2<span class="room-chip-area">15,7 </span></div>
<div class="room-chip">Spielzimmer<span class="room-chip-area">6,3 </span></div>
<div class="room-chip">Ankleidezimmer<span class="room-chip-area">1,4 </span></div>
<div class="room-chip">Dachterrasse<span class="room-chip-area">9,0 </span> <small>(25% von 35,8 )</small></div>
</div>
<div class="floor-plan floor-plan-multi">
<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" 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" loading="lazy" data-img="/bilder/grundrisse/OG 2 3D.webp" />
</picture>
</div>
</div>
</div>
<div class="floor-item">
<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 Nutzfläche</span>
<div class="floor-icon">+</div>
</div>
</div>
<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)<span class="room-chip-area">52 </span></div>
<div class="room-chip">Dachboden Mitte (ungeheizt)<span class="room-chip-area">31 </span></div>
<div class="room-chip">Dachboden oben (ungeheizt)<span class="room-chip-area">11 </span></div>
</div>
<div class="floor-plan floor-plan-multi">
<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>
</div>
</section>
<section class="pricing-section" id="miete" aria-label="Mietkonditionen">
<div class="pricing-inner">
<div class="section-eyebrow">Mietkonditionen</div>
<h2>Transparente Preisgestaltung</h2>
<div class="price-cards">
<div class="price-card">
<div class="pc-label">Kaltmiete</div>
<div class="pc-val">1.300 </div>
<div class="pc-sub">pro Monat</div>
</div>
<div class="price-card highlight">
<div class="pc-label">Gesamtmiete warm</div>
<div class="pc-val">1.600 </div>
<div class="pc-sub">inkl. 300 Nebenkosten</div>
</div>
<div class="price-card">
<div class="pc-label">Kaution</div>
<div class="pc-val">2.600 </div>
<div class="pc-sub">2 Nettokaltmieten</div>
</div>
</div>
<div class="price-note">
<div class="pn-item">
<strong>Verfügbarkeit</strong>
Ab sofort · unbefristete Laufzeit
</div>
<div class="pn-item">
<strong>Nebenkosten</strong>
Vorauszahlung 300 /Monat, jährliche Abrechnung
</div>
<div class="pn-item">
<strong>Energieausweis</strong>
Wird bei Mietbeginn übergeben · Erdgasheizung
</div>
<div class="pn-item">
<strong>Haustiere</strong>
Auf Anfrage
</div>
</div>
</div>
</section>
<section class="lage-section" id="lage">
<div class="section-eyebrow">Standort</div>
<h2>Zentral und ruhig zugleich</h2>
<div class="lage-grid">
<div class="lage-item">
<div class="lage-icon">🛒</div>
<div>
<div class="lage-title">Einkaufen & Versorgung</div>
<div class="lage-desc">Supermärkte, Ärzte, Apotheken und Schulen sind fußläufig erreichbar</div>
</div>
</div>
<div class="lage-item">
<div class="lage-icon">🚌</div>
<div>
<div class="lage-title">Öffentlicher Nahverkehr</div>
<div class="lage-desc">Zentrale Bushaltestelle ca. 200 m entfernt direkte Verbindungen in die Region</div>
</div>
</div>
<div class="lage-item">
<div class="lage-icon">🏛</div>
<div>
<div class="lage-title">Innenstadt Schleusingen</div>
<div class="lage-desc">Wochenmarkt und Stadtmitte nur ca. 500 m entfernt</div>
</div>
</div>
<div class="lage-item">
<div class="lage-icon">📍</div>
<div>
<div class="lage-title">Genaue Adresse</div>
<div class="lage-desc">Schleusinger Bahnhofstraße 10<br />98553 Schleusingen, Thüringen</div>
</div>
</div>
</div>
<div class="lage-map-wrapper">
<iframe
src="https://maps.google.com/maps?q=50.5090045,10.7473859&t=&z=16&ie=UTF8&iwloc=&output=embed"
width="100%" height="450" style="border: 0" allowfullscreen="" loading="lazy"
referrerpolicy="no-referrer-when-downgrade"
title="Standort Bahnhofstraße 10, Schleusingen"
></iframe>
</div>
</section>
<section class="contact-section" id="kontakt" aria-label="Kontaktformular">
<div class="contact-inner">
<div class="section-eyebrow">Kontakt</div>
<h2>Interesse?<br /><em>Schreiben Sie uns.</em></h2>
<p>
Wir freuen uns über Ihre Anfrage und melden uns innerhalb von 24 Stunden.
Besichtigungstermine sind nach Absprache möglich. Bitte geben Sie bei Ihrer Anfrage ein
paar Terminvorschläge an.
</p>
<div class="contact-form">
<?php if ($formSuccess): ?>
<div id="form-result" class="form-success" style="display: block">
<p>Vielen Dank für Ihre Anfrage!</p>
<br />
<small>Wir haben Ihre Nachricht erhalten und melden uns innerhalb von 24 Stunden bei Ihnen.</small>
</div>
<?php else: ?>
<?php if (!empty($formErrors)): ?>
<div id="form-errors" class="form-errors">
<ul>
<?php foreach ($formErrors as $error): ?>
<li><?= $escapeContactValue($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form id="contactForm" method="post">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token'] ?? '') ?>" />
<div class="form-row">
<div class="form-field">
<label for="fname">Vorname</label>
<input type="text" id="fname" name="fname" placeholder="Max" required value="<?= $escapeContactValue($formData['fname']) ?>" />
</div>
<div class="form-field">
<label for="lname">Nachname</label>
<input type="text" id="lname" name="lname" placeholder="Mustermann" required value="<?= $escapeContactValue($formData['lname']) ?>" />
</div>
</div>
<div class="form-row">
<div class="form-field">
<label for="email">E-Mail</label>
<input type="email" id="email" name="email" placeholder="max@beispiel.de" required value="<?= $escapeContactValue($formData['email']) ?>" />
</div>
<div class="form-field">
<label for="phone">Telefon</label>
<input type="tel" id="phone" name="phone" placeholder="+49 ..." value="<?= $escapeContactValue($formData['phone']) ?>" />
</div>
</div>
<div class="form-row">
<div class="form-field full">
<label for="interest">Anliegen</label>
<select id="interest" name="interest">
<?php
$interestOptions = ['Besichtigung anfragen', 'Allgemeine Informationen', 'Mietbewerbung einreichen'];
foreach ($interestOptions as $opt):
$selected = ($formData['interest'] === $opt) ? ' selected' : '';
?>
<option<?= $selected ?>><?= $escapeContactValue($opt) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="form-row">
<div class="form-field full">
<label for="message">Nachricht</label>
<textarea id="message" name="message" rows="4" placeholder="Ihre Nachricht ..." required><?= $escapeContactValue($formData['message']) ?></textarea>
</div>
</div>
<div class="hp-field" aria-hidden="true">
<label for="website">Website</label>
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off" />
</div>
<input type="hidden" name="form_time" value="<?= time() ?>" />
<button type="submit" class="btn-submit">Anfrage absenden</button>
</form>
<?php endif; ?>
</div>
<div class="contact-details">
<p>Oder schreiben Sie uns direkt: <a href="mailto:mki@kies-media.de">mki@kies-media.de</a></p>
</div>
</div>
</section>
</main>
<footer role="contentinfo">
<div class="footer-logo">Bahnhofstraße 10 · Schleusingen</div>
<div class="footer-links">
<a href="/impressum">Impressum</a>
<a href="/datenschutz">Datenschutz</a>
</div>
</footer>
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Bildansicht">
<button class="lightbox-close" id="lightboxClose" aria-label="Bildansicht schließen">&times;</button>
<img src="" id="lightboxImg" alt="" />
</div>

View File

@@ -0,0 +1,85 @@
<nav id="navbar" class="scrolled">
<div class="nav-logo">Bahnhofstraße 10</div>
<ul class="nav-links">
<li><a href="/#galerie">Galerie</a></li>
<li><a href="/#grundriss">Grundriss</a></li>
<li><a href="/#miete">Miete</a></li>
<li><a href="/#lage">Lage</a></li>
</ul>
<a href="/#kontakt" class="nav-cta" style="text-decoration:none;">Jetzt anfragen</a>
</nav>
<main class="legal-page">
<div class="section-eyebrow">Pflichtangaben</div>
<h1>Impressum</h1>
<h2>Angaben gemäß § 5 TMG</h2>
<address>
Martin Kiesewetter<br />
Am Schaftalsgraben 4<br />
98529 Suhl<br />
Deutschland
</address>
<h3>Kontakt</h3>
<ul>
<li>Telefon: 0176 45853923</li>
<li>E-Mail: <a href="mailto:mki@kies-media.de">mki@kies-media.de</a></li>
</ul>
<hr class="legal-divider" />
<h2>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV</h2>
<p>
Martin Kiesewetter<br />
Am Schaftalsgraben 4<br />
98529 Suhl
</p>
<hr class="legal-divider" />
<h2>Streitschlichtung</h2>
<p>
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:
<a href="https://ec.europa.eu/consumers/odr/" target="_blank" rel="noopener">https://ec.europa.eu/consumers/odr/</a>
</p>
<p>
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.
</p>
<hr class="legal-divider" />
<h2>Haftung für Inhalte</h2>
<p>
Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.
</p>
<p>
Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen.
</p>
<h2>Haftung für Links</h2>
<p>
Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich.
</p>
<p>
Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend entfernen.
</p>
<h2>Urheberrecht</h2>
<p>
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet.
</p>
<p>
Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.
</p>
<a href="/" class="legal-back"> Zurück zum Objekt</a>
</main>
<footer>
<div class="footer-logo">Bahnhofstraße 10 · Schleusingen</div>
<div class="footer-links">
<a href="/impressum">Impressum</a>
<a href="/datenschutz">Datenschutz</a>
</div>
</footer>

View File

@@ -0,0 +1,51 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<?php if (!isset($pageTitle)) $pageTitle = 'Einfamilienhaus mieten Schleusingen | 227 m², 6 Zimmer | 1.300 € Kaltmiete'; ?>
<title><?= htmlspecialchars($pageTitle) ?></title>
<?php if (isset($pageDescription)): ?>
<meta name="description" content="<?= htmlspecialchars($pageDescription) ?>" />
<?php else: ?>
<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." />
<?php endif; ?>
<?php if (isset($robots)): ?>
<meta name="robots" content="<?= htmlspecialchars($robots) ?>" />
<?php endif; ?>
<?php if (isset($canonical)): ?>
<link rel="canonical" href="<?= htmlspecialchars($canonical) ?>" />
<?php endif; ?>
<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">
<?php if (isset($openGraph)): extract($openGraph); ?>
<meta property="og:type" content="website" />
<meta property="og:title" content="<?= htmlspecialchars($ogTitle ?? '') ?>" />
<meta property="og:description" content="<?= htmlspecialchars($ogDescription ?? '') ?>" />
<meta property="og:image" content="<?= htmlspecialchars($ogImage ?? '') ?>" />
<meta property="og:url" content="<?= htmlspecialchars($ogUrl ?? '') ?>" />
<meta property="og:locale" content="de_DE" />
<meta property="og:site_name" content="Haus Schleusingen" />
<?php endif; ?>
<?php if (isset($structuredData)): ?>
<script type="application/ld+json"><?= $structuredData ?></script>
<?php endif; ?>
<link rel="stylesheet" href="/fonts/fonts.css" />
<link rel="stylesheet" href="/css/haus-schleusingen.css" />
<?php if (isset($extraCss)): ?>
<style><?= $extraCss ?></style>
<?php endif; ?>
</head>
<body>
<?= $content ?>
<script src="/js/haus-schleusingen.js"></script>
</body>
</html>

0
docker-preview.png → docs/docker-preview.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

0
page-preview.png → docs/page-preview.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 338 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 2.8 MiB

1
eslint.config.js Normal file → Executable file
View File

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

View File

@@ -1,551 +0,0 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Einfamilienhaus zur Miete - Schleusingen</title>
<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" />
</head>
<body>
<nav id="navbar">
<div class="nav-logo">Bahnhofstraße 10</div>
<ul class="nav-links">
<li><a href="#galerie">Galerie</a></li>
<li><a href="#grundriss">Grundriss</a></li>
<li><a href="#miete">Miete</a></li>
<li><a href="#lage">Lage</a></li>
</ul>
<button
class="nav-cta"
onclick="$('html').animate({ scrollTop: $('#kontakt').offset().top }, 700)"
>
Jetzt anfragen
</button>
</nav>
<section class="hero" id="hero">
<div
class="hero-bg"
id="heroBg"
style="background-image: url(bilder/Außenansicht-2.png)"
></div>
<div class="hero-overlay"></div>
<div class="hero-content" id="heroContent">
<div class="hero-tag">Zur Langzeitmiete · Ab sofort verfügbar</div>
<h1>
Großzügiges
<br />
<em>Einfamilienhaus</em>
<br />
in Schleusingen
</h1>
<div class="hero-meta">
<span><strong>Schleusinger Bahnhofstraße 10</strong></span>
<span>227 m² Wohnfläche</span>
<span>6 Zimmer</span>
<span>3 Etagen + Dachterrasse</span>
</div>
</div>
<div class="hero-scroll">
<span>Entdecken</span>
<div class="scroll-line"></div>
</div>
</section>
<div class="facts-strip">
<div class="fact">
<div class="fact-val">227</div>
<div class="fact-label">m² Wohnfläche</div>
</div>
<div class="fact">
<div class="fact-val">6</div>
<div class="fact-label">Zimmer</div>
</div>
<div class="fact">
<div class="fact-val">3</div>
<div class="fact-label">Etagen</div>
</div>
<div class="fact">
<div class="fact-val">1.300</div>
<div class="fact-label">€ Kaltmiete</div>
</div>
</div>
<section class="intro" id="intro">
<div class="intro-text" data-animate>
<div class="section-eyebrow">Das Objekt</div>
<h2>Wohnen mit Charakter und viel Raum</h2>
<p>
Vermietet wird ein vollständiges Einfamilienhaus in ruhiger Lage von Schleusingen. Das
Haus verbindet historischen Charme mit modernem Wohnkomfort auf drei großzügigen Etagen.
</p>
<p>
Garage für zwei Fahrzeuge, großzügige Dachterrasse mit 35,8 m², vollausgestattete Küche,
Vollbad sowie Abstell- und Nutzräume machen das Haus zu einem außergewöhnlichen
Mietobjekt.
</p>
<div class="intro-stats">
<div>
<div class="istat-val">154,9 m²</div>
<div class="istat-label">Nutzfläche</div>
</div>
<div>
<div class="istat-val">35,8 m²</div>
<div class="istat-label">Dachterrasse</div>
</div>
<div>
<div class="istat-val">2 Stpl.</div>
<div class="istat-label">Garage</div>
</div>
</div>
</div>
<div class="intro-img" data-animate>
<img src="bilder/wohnzimmer2.png" alt="Wohnzimmer" />
<div class="intro-img-badge">Wohnzimmer · 42,6 m²</div>
</div>
</section>
<section id="galerie" class="gallery-section">
<div class="gallery-header">
<div>
<div class="section-eyebrow">Fotogalerie</div>
<h2>Einblicke ins Haus</h2>
</div>
</div>
<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" />
<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" />
<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" />
<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" />
<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" />
<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" />
<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" />
<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" />
<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" />
<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" />
<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" />
<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">
<img
src="bilder/WhatsApp Image 2026-03-30 at 07.50.42 (2).jpeg"
alt="Wohnbereich Detail 3"
/>
<span class="grid-item-label">Hausansicht</span>
</div>
</div>
</section>
<section class="floors-section" id="grundriss">
<div class="section-eyebrow">Raumaufteilung</div>
<h2>Großzügig auf allen Etagen</h2>
<div class="floor-accordion">
<div class="floor-item">
<div class="floor-header">
<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-rooms-grid">
<div class="room-chip">
Flur
<span class="room-chip-area">20,1 m²</span>
</div>
<div class="room-chip">
WC
<span class="room-chip-area">0,8 m²</span>
</div>
<div class="room-chip">
Garage / Partykeller
<span class="room-chip-area">42,6 m²</span>
</div>
<div class="room-chip">
Abstellraum 1
<span class="room-chip-area">9,9 m²</span>
</div>
<div class="room-chip">
Abstellraum 2
<span class="room-chip-area">7,8 m²</span>
</div>
<div class="room-chip">
Heizungskeller
<span class="room-chip-area">18,3 m²</span>
</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"
/>
</div>
</div>
</div>
<div class="floor-item">
<div class="floor-header">
<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-rooms-grid">
<div class="room-chip">
Flur
<span class="room-chip-area">20,1 m²</span>
</div>
<div class="room-chip">
Wohnzimmer
<span class="room-chip-area">42,6 m²</span>
</div>
<div class="room-chip">
Gästezimmer
<span class="room-chip-area">11,5 m²</span>
</div>
<div class="room-chip">
Badezimmer
<span class="room-chip-area">9,8 m²</span>
</div>
<div class="room-chip">
Küche
<span class="room-chip-area">18,4 m²</span>
</div>
<div class="room-chip">
Schlafzimmer
<span class="room-chip-area">18,0 m²</span>
</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"
/>
</div>
</div>
</div>
<div class="floor-item">
<div class="floor-header">
<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-rooms-grid">
<div class="room-chip">
Flur
<span class="room-chip-area">13,9 m²</span>
</div>
<div class="room-chip">
Kinderzimmer 1
<span class="room-chip-area">21,7 m²</span>
</div>
<div class="room-chip">
Kinderzimmer 2
<span class="room-chip-area">15,7 m²</span>
</div>
<div class="room-chip">
Spielzimmer
<span class="room-chip-area">6,3 m²</span>
</div>
<div class="room-chip">
Ankleidezimmer
<span class="room-chip-area">1,4 m²</span>
</div>
<div class="room-chip">
Dachterrasse
<span class="room-chip-area">9,0 m²</span> <small>(25% von 35,8 m²)</small>
</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"
/>
</div>
</div>
</div>
<div class="floor-item">
<div class="floor-header">
<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-rooms-grid">
<div class="room-chip">
Dachboden unten (ungeheizt)
<span class="room-chip-area">52 m²</span>
</div>
<div class="room-chip">
Dachboden Mitte (ungeheizt)
<span class="room-chip-area">31 m²</span>
</div>
<div class="room-chip">
Dachboden oben (ungeheizt)
<span class="room-chip-area">11 m²</span>
</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"
/>
</div>
</div>
</div>
</div>
</section>
<section class="pricing-section" id="miete">
<div class="pricing-inner">
<div class="section-eyebrow">Mietkonditionen</div>
<h2>Transparente Preisgestaltung</h2>
<div class="price-cards">
<div class="price-card">
<div class="pc-label">Kaltmiete</div>
<div class="pc-val">1.300 €</div>
<div class="pc-sub">pro Monat</div>
</div>
<div class="price-card highlight">
<div class="pc-label">Gesamtmiete warm</div>
<div class="pc-val">1.600 €</div>
<div class="pc-sub">inkl. 300 € Nebenkosten</div>
</div>
<div class="price-card">
<div class="pc-label">Kaution</div>
<div class="pc-val">2.600 €</div>
<div class="pc-sub">2 Nettokaltmieten</div>
</div>
</div>
<div class="price-note">
<div class="pn-item">
<strong>Verfügbarkeit</strong>
Ab sofort · unbefristete Laufzeit
</div>
<div class="pn-item">
<strong>Nebenkosten</strong>
Vorauszahlung 300 €/Monat, jährliche Abrechnung
</div>
<div class="pn-item">
<strong>Energieausweis</strong>
Wird bei Mietbeginn übergeben · Erdgasheizung
</div>
<div class="pn-item">
<strong>Haustiere</strong>
Auf Anfrage
</div>
</div>
</div>
</section>
<section class="lage-section" id="lage">
<div class="section-eyebrow">Standort</div>
<h2>Zentral und ruhig zugleich</h2>
<div class="lage-grid">
<div class="lage-item">
<div class="lage-icon">🛒</div>
<div>
<div class="lage-title">Einkaufen & Versorgung</div>
<div class="lage-desc">
Supermärkte, Ärzte, Apotheken und Schulen sind fußläufig erreichbar
</div>
</div>
</div>
<div class="lage-item">
<div class="lage-icon">🚌</div>
<div>
<div class="lage-title">Öffentlicher Nahverkehr</div>
<div class="lage-desc">
Zentrale Bushaltestelle ca. 200 m entfernt — direkte Verbindungen in die Region
</div>
</div>
</div>
<div class="lage-item">
<div class="lage-icon">🏛</div>
<div>
<div class="lage-title">Innenstadt Schleusingen</div>
<div class="lage-desc">Wochenmarkt und Stadtmitte nur ca. 500 m entfernt</div>
</div>
</div>
<div class="lage-item">
<div class="lage-icon">📍</div>
<div>
<div class="lage-title">Genaue Adresse</div>
<div class="lage-desc">
Schleusinger Bahnhofstraße 10
<br />
98533 Schleusingen, Thüringen
</div>
</div>
</div>
</div>
<div class="lage-map-wrapper">
<iframe
src="https://maps.google.com/maps?q=50.5090045,10.7473859&t=&z=16&ie=UTF8&iwloc=&output=embed"
width="100%"
height="450"
style="border: 0"
allowfullscreen=""
loading="lazy"
referrerpolicy="no-referrer-when-downgrade"
title="Standort Bahnhofstraße 10, Schleusingen"
></iframe>
</div>
</section>
<section class="contact-section" id="kontakt">
<div class="contact-inner">
<div class="section-eyebrow">Kontakt</div>
<h2>
Interesse?
<br />
<em>Schreiben Sie uns.</em>
</h2>
<p>
Wir freuen uns über Ihre Anfrage und melden uns innerhalb von 24 Stunden.
Besichtigungstermine sind nach Absprache möglich. Bitte geben Sie bei Ihrer Anfrage ein
paar Terminvorschläge an.
</p>
<div class="contact-form">
<form id="contactForm">
<div class="form-row">
<div class="form-field">
<label for="fname">Vorname</label>
<input type="text" id="fname" name="fname" placeholder="Max" required />
</div>
<div class="form-field">
<label for="lname">Nachname</label>
<input type="text" id="lname" name="lname" placeholder="Mustermann" required />
</div>
</div>
<div class="form-row">
<div class="form-field">
<label for="email">E-Mail</label>
<input
type="email"
id="email"
name="email"
placeholder="max@beispiel.de"
required
/>
</div>
<div class="form-field">
<label for="phone">Telefon</label>
<input type="tel" id="phone" name="phone" placeholder="+49 ..." />
</div>
</div>
<div class="form-row">
<div class="form-field full">
<label for="interest">Anliegen</label>
<select id="interest" name="interest">
<option>Besichtigung anfragen</option>
<option>Allgemeine Informationen</option>
<option>Mietbewerbung einreichen</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-field full">
<label for="message">Nachricht</label>
<textarea
id="message"
name="message"
rows="4"
placeholder="Ihre Nachricht ..."
></textarea>
</div>
</div>
<button type="submit" class="btn-submit">Anfrage absenden</button>
</form>
<div class="form-success" id="formSuccess">
<p>Vielen Dank für Ihre Anfrage!</p>
<br />
<small>Wir melden uns schnellstmöglich bei Ihnen.</small>
</div>
</div>
</div>
</section>
<footer>
<div class="footer-logo">Bahnhofstraße 10 · Schleusingen</div>
<div class="footer-links">
<a href="#">Impressum</a>
<a href="#">Datenschutz</a>
</div>
</footer>
<div class="lightbox" id="lightbox">
<button class="lightbox-close" id="lightboxClose">&times;</button>
<img src="" id="lightboxImg" alt="Vollbild" />
</div>
<script src="js/haus-schleusingen.js"></script>
</body>
</html>

View File

@@ -1,84 +0,0 @@
$(function () {
// Navbar scroll
$(window).on("scroll", function () {
if ($(this).scrollTop() > 60) $("#navbar").addClass("scrolled");
else $("#navbar").removeClass("scrolled");
});
// Hero animation on load
setTimeout(function () {
$("#heroContent").addClass("visible");
$("#heroBg").addClass("loaded");
}, 200);
// Scroll animations
function checkVisible() {
$(".fact, [data-animate]").each(function () {
var el = $(this);
var top = el.offset().top;
var windowBottom = $(window).scrollTop() + $(window).height();
if (windowBottom > top + 60) {
el.addClass("visible");
el.css({ opacity: 1, 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-header").on("click", function () {
var item = $(this).closest(".floor-item");
var isOpen = item.hasClass("open");
$(".floor-item").removeClass("open");
$(".floor-body").slideUp(300);
if (!isOpen) {
item.addClass("open");
item.find(".floor-body").slideDown(300);
}
});
// Lightbox gallery grid items
$(document).on("click", ".grid-item", function () {
var src = $(this).data("img") || $(this).find("img").attr("src");
$("#lightboxImg").attr("src", src);
$("#lightbox").addClass("open");
$("body").css("overflow", "hidden");
});
// Lightbox floor plan images in Raumaufteilung
$(document).on("click", ".floor-plan img[data-img]", function () {
var src = $(this).data("img");
$("#lightboxImg").attr("src", src);
$("#lightbox").addClass("open");
$("body").css("overflow", "hidden");
});
$("#lightboxClose, #lightbox").on("click", function (e) {
if (e.target === this) {
$("#lightbox").removeClass("open");
$("body").css("overflow", "");
}
});
$(document).on("keydown", function (e) {
if (e.key === "Escape") {
$("#lightbox").removeClass("open");
$("body").css("overflow", "");
}
});
// Form submit
$("#contactForm").on("submit", function (e) {
e.preventDefault();
var btn = $(this).find(".btn-submit");
btn.text("Wird gesendet...").prop("disabled", true);
setTimeout(function () {
$("#contactForm").hide();
$("#formSuccess").fadeIn(400);
}, 1200);
});
});

File diff suppressed because one or more lines are too long

19
nginx.conf Normal file → Executable file
View File

@@ -3,9 +3,22 @@ server {
server_name localhost; server_name localhost;
root /usr/share/nginx/html; root /usr/share/nginx/html;
index haus-schleusingen.html; index index.php;
# 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/ /index.php;
} }
}
# 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;
}
}

0
package.json Normal file → Executable file
View File

23
public/.htaccess Normal file
View File

@@ -0,0 +1,23 @@
# Enable rewrite engine
RewriteEngine On
# Security Headers
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-src https://www.google.com/ https://www.google.de/; connect-src 'self'"
</IfModule>
# 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]

View File

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

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

0
bilder/Bad-2.jpeg → public/bilder/Bad-2.jpeg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 259 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

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

0
bilder/Bad-3.jpeg → public/bilder/Bad-3.jpeg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 211 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

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

0
bilder/Bad-4.jpeg → public/bilder/Bad-4.jpeg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 220 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

0
bilder/Bad-small.jpg → public/bilder/Bad-small.jpg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

0
bilder/Bad.jpg → public/bilder/Bad.jpg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 456 KiB

BIN
public/bilder/Bad.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

View File

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 MiB

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

View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 MiB

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

View File

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
public/bilder/Kinderzimmer.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

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

0
bilder/Küche 1.jpg → public/bilder/Küche 1.jpg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 501 KiB

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"
}

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

Before

Width:  |  Height:  |  Size: 891 KiB

After

Width:  |  Height:  |  Size: 891 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 664 KiB

After

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 416 KiB

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

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

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 390 KiB

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Some files were not shown because too many files have changed in this diff Show More