Refactor footer component and integrate into landing page

- Remove unnecessary elements and simplify the footer layout
- Update copyright notice and styling for consistency
- Add Footer27 component to the landing page
- Enhance tests to verify footer rendering and legal links
This commit is contained in:
2026-05-06 22:06:43 +02:00
parent d2ba994fad
commit 3440508bac
6 changed files with 326 additions and 45 deletions

View File

@@ -0,0 +1,55 @@
---
id: TASK-4
title: Add legal footer and pages
status: In Progress
assignee: []
created_date: '2026-05-06 19:43'
updated_date: '2026-05-06 19:51'
labels: []
dependencies: []
modified_files:
- src/pages/index.astro
- src/components/footer27.tsx
- src/pages/impressum.astro
- src/pages/datenschutz.astro
- tests/landing-content.test.mjs
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Bind the existing footer into the landing page and add real Impressum and Datenschutz routes so the legal links resolve correctly.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Landing page renders the footer component below the main landing content
- [x] #2 Footer links point to /impressum and /datenschutz
- [x] #3 /impressum contains the provided business and VAT ID information
- [x] #4 /datenschutz states that no cookies are used and documents the current Rybbit analytics usage
- [x] #5 Automated content tests and Astro build pass
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing content tests for footer rendering and legal pages
2. Implement footer wiring and legal Astro pages
3. Run automated tests and build
4. Check off verified acceptance criteria and leave task In Progress for user confirmation
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Implemented footer rendering on the landing page and added /impressum plus /datenschutz Astro routes.
Verified with node --test tests/*.mjs and env CI=true pnpm build. The first sandboxed build attempt failed because pnpm needed network access to recreate node_modules; the escalated build completed successfully.
Task remains In Progress pending explicit user confirmation before moving to Done.
Adjusted footer per visual feedback: removed the CTA/contact block and kept only the compact copyright, analytics note, Impressum, and Datenschutz row. Footer now uses the same horizontal page padding as the landing sections instead of max-w-6xl centering.
Re-verified after the footer adjustment with node --test tests/*.mjs and env CI=true pnpm build.
<!-- SECTION:NOTES:END -->

View File

@@ -1,6 +1,3 @@
import { ArrowRight, Mail, Phone } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
interface Footer27Props { interface Footer27Props {
@@ -9,48 +6,11 @@ interface Footer27Props {
const Footer27 = ({ className }: Footer27Props) => { const Footer27 = ({ className }: Footer27Props) => {
return ( return (
<footer className={cn("px-4 pb-10 sm:px-6 lg:px-8", className)}> <footer className={cn("px-5 pb-10 sm:px-8 lg:px-12", className)}>
<div className="mx-auto max-w-6xl border-t border-border/80 pt-8"> <div className="border-t border-border/80 pt-6">
<div className="grid gap-8 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-start"> <div className="flex flex-col gap-4 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
<div>
<h2 className="max-w-2xl text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
Bereit für eine Website, die Ihr Unternehmen klarer erklärt?
</h2>
<p className="mt-4 max-w-xl text-base leading-7 text-muted-foreground">
Eine kurze Nachricht reicht. Ich prüfe, welcher Weg sinnvoll ist,
und melde mich mit einer ehrlichen Einschätzung.
</p>
<Button asChild size="lg" className="mt-6 h-11 rounded-md px-5">
<a href="#kontakt">
Kostenloses Angebot anfordern
<ArrowRight className="shrink-0" aria-hidden />
</a>
</Button>
</div>
<address className="not-italic">
<div className="space-y-3 text-sm text-muted-foreground">
<a
href="mailto:info@matthias-meister-webdesign.de"
className="flex items-center gap-2 transition-colors hover:text-foreground"
>
<Mail className="size-4" aria-hidden />
info@matthias-meister-webdesign.de
</a>
<a
href="tel:037627984400"
className="flex items-center gap-2 transition-colors hover:text-foreground"
>
<Phone className="size-4" aria-hidden />
03762 798 4400
</a>
</div>
</address>
</div>
<div className="mt-10 flex flex-col gap-4 border-t border-border/80 pt-6 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
<div className="space-y-1"> <div className="space-y-1">
<p>© 2026 Matthias Meister Webdesign Crimmitschau</p> <p>© 2026 Matthias Meister Webdesign</p>
<p className="text-xs text-muted-foreground/90"> <p className="text-xs text-muted-foreground/90">
Diese Website misst Nutzung anonym und cookiefrei (Rybbit). Diese Website misst Nutzung anonym und cookiefrei (Rybbit).
</p> </p>

132
src/pages/datenschutz.astro Normal file
View File

@@ -0,0 +1,132 @@
---
import "@/styles/global.css";
---
<html lang="de">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Datenschutz | Matthias Meister Softwareentwicklung</title>
<script
src="https://rybbit.matthias.lol/api/script.js"
data-site-id="60abc81e438a"
defer></script>
</head>
<body>
<main class="min-h-screen bg-background px-5 py-8 text-foreground sm:px-8 lg:px-12">
<a
href="/"
class="text-sm font-semibold uppercase tracking-[0.24em] text-muted-foreground transition hover:text-foreground"
>
Matthias Meister
</a>
<section class="mx-auto grid max-w-5xl gap-12 py-16 lg:grid-cols-[0.42fr_0.58fr] lg:py-24">
<div>
<p class="text-sm uppercase tracking-[0.3em] text-primary">
Datenschutz
</p>
<h1 class="mt-6 max-w-[9ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
Ihre Daten
</h1>
</div>
<div class="space-y-10 text-lg leading-8 text-foreground/85">
<section>
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
Verantwortlicher
</h2>
<div class="mt-4 space-y-1 text-muted-foreground">
<p>Matthias Meister Softwareentwicklung</p>
<p>Inhaber: Matthias Meister</p>
<p>Karl-Marx-Str. 22</p>
<p>08451 Crimmitschau</p>
<p>
E-Mail:
<a
class="underline underline-offset-4 transition hover:text-foreground"
href="mailto:hallo@matthias-meister.com"
>
hallo@matthias-meister.com
</a>
</p>
</div>
</section>
<section>
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
Keine Cookies
</h2>
<p class="mt-4 text-muted-foreground">
Diese Website setzt keine Cookies. Es gibt keine Nutzerkonten,
keinen Newsletter, keine Zahlungsabwicklung und keine eingebetteten
Drittanbieter-Medien.
</p>
</section>
<section>
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
Kontakt per E-Mail
</h2>
<p class="mt-4 text-muted-foreground">
Wenn Sie über einen mailto-Link Kontakt aufnehmen, werden die von
Ihnen per E-Mail übermittelten Angaben zur Bearbeitung Ihrer
Anfrage verarbeitet. Auf dieser Website wird dafür kein
Kontaktformular gespeichert.
</p>
</section>
<section>
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
Reichweitenmessung mit Rybbit
</h2>
<p class="mt-4 text-muted-foreground">
Diese Website nutzt Rybbit Analytics über
https://rybbit.matthias.lol/api/script.js, um anonymisierte und
aggregierte Statistiken zur Nutzung der Website zu erhalten.
Rybbit arbeitet cookielos und verwendet nach Anbieterangaben keine
Cookies oder local storage für das Tracking.
</p>
<p class="mt-4 text-muted-foreground">
Dabei können technische Besuchsdaten wie aufgerufene Seiten,
Referrer, Browser- und Geräteinformationen sowie aus der
IP-Adresse abgeleitete ungefähre Standortdaten verarbeitet werden.
Die IP-Adresse wird dabei nur vorübergehend zur Verarbeitung
genutzt und nicht als Klartext in der Analysedatenbank gespeichert.
</p>
</section>
<section>
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
Ihre Rechte
</h2>
<p class="mt-4 text-muted-foreground">
Sie haben im Rahmen der gesetzlichen Vorgaben insbesondere Rechte
auf Auskunft, Berichtigung, Löschung, Einschränkung der
Verarbeitung und Widerspruch gegen die Verarbeitung Ihrer
personenbezogenen Daten. Bitte wenden Sie sich dafür an die oben
genannte Kontaktadresse.
</p>
</section>
<p class="border-t border-border pt-8 text-sm text-muted-foreground">
Hinweis: Dieser Text beschreibt die technische Umsetzung dieser
Website und ersetzt keine anwaltliche Prüfung.
</p>
<nav class="flex flex-wrap gap-5 text-sm text-muted-foreground">
<a class="underline underline-offset-4 transition hover:text-foreground" href="/">
Startseite
</a>
<a class="underline underline-offset-4 transition hover:text-foreground" href="/impressum">
Impressum
</a>
</nav>
</div>
</section>
</main>
</body>
</html>

86
src/pages/impressum.astro Normal file
View File

@@ -0,0 +1,86 @@
---
import "@/styles/global.css";
---
<html lang="de">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Impressum | Matthias Meister Softwareentwicklung</title>
<script
src="https://rybbit.matthias.lol/api/script.js"
data-site-id="60abc81e438a"
defer></script>
</head>
<body>
<main class="min-h-screen bg-background px-5 py-8 text-foreground sm:px-8 lg:px-12">
<a
href="/"
class="text-sm font-semibold uppercase tracking-[0.24em] text-muted-foreground transition hover:text-foreground"
>
Matthias Meister
</a>
<section class="mx-auto grid max-w-5xl gap-12 py-16 lg:grid-cols-[0.42fr_0.58fr] lg:py-24">
<div>
<p class="text-sm uppercase tracking-[0.3em] text-primary">
Rechtliches
</p>
<h1 class="mt-6 max-w-[8ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
Impressum
</h1>
</div>
<div class="space-y-10 text-lg leading-8 text-foreground/85">
<section>
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
Angaben gemäß § 5 TMG
</h2>
<div class="mt-4 space-y-1 text-muted-foreground">
<p>Matthias Meister Softwareentwicklung</p>
<p>Inhaber: Matthias Meister</p>
<p>Karl-Marx-Str. 22</p>
<p>08451 Crimmitschau</p>
</div>
</section>
<section>
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
Umsatzsteuer
</h2>
<p class="mt-4 text-muted-foreground">
Umsatzsteuer-Identifikationsnummer: DE460155697
</p>
</section>
<section>
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
Kontakt
</h2>
<p class="mt-4 text-muted-foreground">
E-Mail:
<a
class="underline underline-offset-4 transition hover:text-foreground"
href="mailto:hallo@matthias-meister.com"
>
hallo@matthias-meister.com
</a>
</p>
</section>
<nav class="flex flex-wrap gap-5 border-t border-border pt-8 text-sm text-muted-foreground">
<a class="underline underline-offset-4 transition hover:text-foreground" href="/">
Startseite
</a>
<a class="underline underline-offset-4 transition hover:text-foreground" href="/datenschutz">
Datenschutz
</a>
</nav>
</div>
</section>
</main>
</body>
</html>

View File

@@ -1,6 +1,7 @@
--- ---
import { LandingHeroSection } from "@/components/landing-hero-section"; import { LandingHeroSection } from "@/components/landing-hero-section";
import { LandingRest } from "@/components/landing"; import { LandingRest } from "@/components/landing";
import { Footer27 } from "@/components/footer27";
import "@/styles/global.css"; import "@/styles/global.css";
--- ---
@@ -21,6 +22,7 @@ import "@/styles/global.css";
<main class="min-h-screen overflow-hidden bg-background text-foreground"> <main class="min-h-screen overflow-hidden bg-background text-foreground">
<LandingHeroSection client:media="(min-width: 1024px)" /> <LandingHeroSection client:media="(min-width: 1024px)" />
<LandingRest /> <LandingRest />
<Footer27 />
</main> </main>
<script> <script>
import { initCookieInfoBanner } from "@/lib/cookie-banner-info"; import { initCookieInfoBanner } from "@/lib/cookie-banner-info";

View File

@@ -7,12 +7,17 @@ const sourcePaths = [
new URL("../src/components/landing-hero-section.tsx", import.meta.url), new URL("../src/components/landing-hero-section.tsx", import.meta.url),
]; ];
const footerPath = new URL("../src/components/footer27.tsx", import.meta.url);
const indexPath = new URL("../src/pages/index.astro", import.meta.url);
const impressumPath = new URL("../src/pages/impressum.astro", import.meta.url);
const datenschutzPath = new URL("../src/pages/datenschutz.astro", import.meta.url);
test("Landing component contains the core brief anchors", async () => { test("Landing component contains the core brief anchors", async () => {
const source = ( const source = (
await Promise.all(sourcePaths.map((p) => readFile(p, "utf8"))) await Promise.all(sourcePaths.map((p) => readFile(p, "utf8")))
).join("\n"); ).join("\n");
for (const phrase of ["Projektbrief", "01", "Website", "Kontakt", "für", "müssen", "Änderungen"]) { for (const phrase of ["Online Fertig Passt", "01", "Website", "Kontakt", "für", "müssen", "Änderungen"]) {
assert.match(source, new RegExp(phrase)); assert.match(source, new RegExp(phrase));
} }
}); });
@@ -37,3 +42,44 @@ test("Landing component uses real German umlauts in visible copy", async () => {
assert.doesNotMatch(source, new RegExp(asciiFallback)); assert.doesNotMatch(source, new RegExp(asciiFallback));
} }
}); });
test("Landing page renders the footer with legal links", async () => {
const [indexSource, footerSource] = await Promise.all([
readFile(indexPath, "utf8"),
readFile(footerPath, "utf8"),
]);
assert.match(indexSource, /import\s+\{\s*Footer27\s*\}/);
assert.match(indexSource, /<Footer27\s*\/>/);
assert.match(footerSource, /href="\/impressum"/);
assert.match(footerSource, /href="\/datenschutz"/);
assert.match(footerSource, /© 2026 Matthias Meister Webdesign/);
assert.doesNotMatch(footerSource, /Bereit für eine Website/);
assert.doesNotMatch(footerSource, /Kostenloses Angebot anfordern/);
assert.doesNotMatch(footerSource, /max-w-6xl/);
});
test("Legal pages contain required business and privacy information", async () => {
const [impressumSource, datenschutzSource] = await Promise.all([
readFile(impressumPath, "utf8"),
readFile(datenschutzPath, "utf8"),
]);
for (const phrase of [
"Matthias Meister Softwareentwicklung",
"Karl-Marx-Str. 22",
"08451 Crimmitschau",
"DE460155697",
]) {
assert.match(impressumSource, new RegExp(phrase));
}
for (const phrase of [
"keine Cookies",
"mailto",
"Rybbit",
"rybbit.matthias.lol",
]) {
assert.match(datenschutzSource, new RegExp(phrase));
}
});