From d2ba994fadea9da0c1f4e85b2c35d53172077201 Mon Sep 17 00:00:00 2001 From: Matthias Meister Date: Wed, 6 May 2026 21:22:12 +0200 Subject: [PATCH] Add native cookie consent and move hero intro above CTA - Restore CookieConsent banner behavior with the new dependency - Reposition the right-panel intro above the Projekt anfragen button - Add focused tests and update build metadata --- ...k-2 - Animate-cookie-banner-transitions.md | 65 +++++++++++++++++++ ...-right-panel-intro-above-inquiry-button.md | 40 ++++++++++++ package.json | 1 + pnpm-lock.yaml | 8 +++ src/components/landing-hero-section.tsx | 8 +-- src/lib/cookie-banner-info.ts | 38 +++++++++++ src/pages/index.astro | 4 ++ tests/cookie-banner-info.test.mjs | 14 ++++ 8 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 backlog/tasks/task-2 - Animate-cookie-banner-transitions.md create mode 100644 backlog/tasks/task-3 - Move-right-panel-intro-above-inquiry-button.md create mode 100644 src/lib/cookie-banner-info.ts create mode 100644 tests/cookie-banner-info.test.mjs diff --git a/backlog/tasks/task-2 - Animate-cookie-banner-transitions.md b/backlog/tasks/task-2 - Animate-cookie-banner-transitions.md new file mode 100644 index 0000000..a381d52 --- /dev/null +++ b/backlog/tasks/task-2 - Animate-cookie-banner-transitions.md @@ -0,0 +1,65 @@ +--- +id: TASK-2 +title: Animate cookie banner transitions +status: In Progress +assignee: [] +created_date: '2026-05-06 18:52' +updated_date: '2026-05-06 19:10' +labels: + - frontend + - animation +dependencies: [] +priority: medium +--- + +## Description + + +Animate the privacy notice transition between the vanilla-cookieconsent banner and the floating cookie button in both directions using the existing Motion dependency, while preserving consent state and accessibility behavior. + + +## Acceptance Criteria + +- [ ] #1 The banner shrinks animatedly into the round cookie button. +- [ ] #2 The round cookie button opens animatedly back into the banner. +- [x] #3 prefers-reduced-motion receives a reduced fade-only variant. +- [x] #4 The production build completes successfully. + + +## Implementation Plan + + +1. Inspect existing cookie animation and test setup. +2. Add a focused failing test for the exported animation helpers/guards where practical. +3. Implement Motion-based bidirectional banner/FAB transitions with reduced-motion fallback. +4. Run focused verification and production build. +5. Check acceptance criteria and report for manual testing. + + +## Implementation Notes + + +Implemented Motion-based bidirectional transition in src/lib/cookie-banner-info.ts using a ghost element, transform/opacity animation, and a reduced-motion fade path. + +Verified focused cookie animation test passes with node --test tests/cookie-banner-info.test.mjs. + +Verified production build passes with pnpm build. + +Full node --test tests/*.mjs currently has an unrelated existing failure in tests/landing-content.test.mjs because the current landing content no longer contains the expected phrase "Projektbrief". + +Follow-up bugfix: native CookieConsent wrapper could remain briefly visible during the custom open animation, so the temporary ghost and the native modal appeared together. Fixed by suppressing both #cc-main .cm-wrapper and #cc-main .cm during transitions. + +Follow-up bugfix: the accept button listener used a one-shot binding, so after reopening the same modal could close with CookieConsent's native fade instead of the custom banner-to-FAB transition. Fixed by binding accept buttons through a WeakSet without once:true and rebinding after show(true). + +Verified follow-up with node --test tests/cookie-banner-info.test.mjs and pnpm build. + +Follow-up bugfix after manual report: clicking Verstanden could still leave no visible FAB if the Motion close sequence failed or did not resolve. Removed risky borderRadius/padding from the close Motion group, forced the ghost into layout before native modal suppression, and added an idempotent finalizeBannerToFab fallback timer so the FAB is always restored. + +Verified with node --test tests/cookie-banner-info.test.mjs and pnpm build. + +Rolled back the custom cookie circle and Motion transition work to the stable native CookieConsent behavior requested by the user: banner appears initially and clicking Verstanden lets CookieConsent close it normally. Removed custom FAB, ghost, Motion import, and event interception from src/lib/cookie-banner-info.ts. + +Updated the focused cookie test to assert the native CookieConsent close behavior and absence of the custom FAB/Motion code. + +Verified rollback with node --test tests/cookie-banner-info.test.mjs and pnpm build. + diff --git a/backlog/tasks/task-3 - Move-right-panel-intro-above-inquiry-button.md b/backlog/tasks/task-3 - Move-right-panel-intro-above-inquiry-button.md new file mode 100644 index 0000000..a1c13b6 --- /dev/null +++ b/backlog/tasks/task-3 - Move-right-panel-intro-above-inquiry-button.md @@ -0,0 +1,40 @@ +--- +id: TASK-3 +title: Move right-panel intro above inquiry button +status: In Progress +assignee: [] +created_date: '2026-05-06 19:20' +updated_date: '2026-05-06 19:21' +labels: [] +dependencies: [] +priority: medium +--- + +## Description + + +Reposition the red-panel 01 headline and supporting copy so it sits directly above the Projekt anfragen button. + + +## Acceptance Criteria + +- [x] #1 The 01 headline and supporting copy appear above the Projekt anfragen button on the right panel +- [x] #2 Responsive layout remains intact without overlapping content + + +## Implementation Plan + + +1. Locate the right-panel intro markup and layout rules +2. Reposition the intro block above the CTA while preserving responsive spacing +3. Run focused checks and report verification + + +## Implementation Notes + + +Moved the right-panel intro block out of vertical centering and placed it directly above the project inquiry CTA. Removed the CTA mt-auto so it follows the intro block naturally. +Verification: pnpm build passed. + +Additional verification: node --test tests/landing-content.test.mjs tests/cookie-banner-info.test.mjs fails because tests/landing-content.test.mjs still expects the removed/renamed phrase Projektbrief. This appears unrelated to the layout-only change; pnpm build remains passing. + diff --git a/package.json b/package.json index c0b0856..9b3a7b5 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.3", "tw-animate-css": "^1.4.0", + "vanilla-cookieconsent": "^3.1.0", "zod": "^4.3.6" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33472cc..a52fabf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: tw-animate-css: specifier: ^1.4.0 version: 1.4.0 + vanilla-cookieconsent: + specifier: ^3.1.0 + version: 3.1.0 zod: specifier: ^4.3.6 version: 4.3.6 @@ -3709,6 +3712,9 @@ packages: resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} engines: {node: ^20.17.0 || >=22.9.0} + vanilla-cookieconsent@3.1.0: + resolution: {integrity: sha512-/McNRtm/3IXzb9dhqMIcbquoU45SzbN2VB+To4jxEPqMmp7uVniP6BhGLjU8MC7ZCDsNQVOp27fhQTM/ruIXAA==} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -7794,6 +7800,8 @@ snapshots: validate-npm-package-name@7.0.2: {} + vanilla-cookieconsent@3.1.0: {} + vary@1.1.2: {} vfile-location@5.0.3: diff --git a/src/components/landing-hero-section.tsx b/src/components/landing-hero-section.tsx index 9588ae0..10ee515 100644 --- a/src/components/landing-hero-section.tsx +++ b/src/components/landing-hero-section.tsx @@ -76,7 +76,7 @@ const LandingHeroSection = () => {
- + ©2026
@@ -117,8 +117,8 @@ const LandingHeroSection = () => {
-
-
+
+

01 @@ -131,7 +131,7 @@ const LandingHeroSection = () => { Projekt anfragen diff --git a/src/lib/cookie-banner-info.ts b/src/lib/cookie-banner-info.ts new file mode 100644 index 0000000..2a66723 --- /dev/null +++ b/src/lib/cookie-banner-info.ts @@ -0,0 +1,38 @@ +import { run } from "vanilla-cookieconsent"; +import "vanilla-cookieconsent/dist/cookieconsent.css"; + +const bannerText = + "Diese Website setzt keine Cookies. Wir erfassen ausschließlich anonymisierte Analytics-Daten, die keinen Rückschluss auf einzelne Nutzer zulassen."; + +export function initCookieInfoBanner(): void { + if (typeof globalThis.window === "undefined") return; + + void run({ + hideFromBots: false, + categories: { + necessary: { + enabled: true, + readOnly: true, + }, + }, + cookie: { + name: "cc_notice", + useLocalStorage: true, + expiresAfterDays: 365, + }, + language: { + default: "de", + translations: { + de: { + consentModal: { + title: "Datenschutzhinweis", + description: bannerText, + acceptNecessaryBtn: "Verstanden", + }, + }, + }, + }, + }).catch((err) => { + console.error("[CookieBanner]", err); + }); +} diff --git a/src/pages/index.astro b/src/pages/index.astro index b701f08..3fdf79c 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -22,5 +22,9 @@ import "@/styles/global.css"; + diff --git a/tests/cookie-banner-info.test.mjs b/tests/cookie-banner-info.test.mjs new file mode 100644 index 0000000..f38cadf --- /dev/null +++ b/tests/cookie-banner-info.test.mjs @@ -0,0 +1,14 @@ +import { readFile } from "node:fs/promises"; +import test from "node:test"; +import assert from "node:assert/strict"; + +const sourcePath = new URL("../src/lib/cookie-banner-info.ts", import.meta.url); + +test("cookie banner uses the native CookieConsent close behavior", async () => { + const source = await readFile(sourcePath, "utf8"); + + assert.match(source, /from "vanilla-cookieconsent"/); + assert.match(source, /acceptNecessaryBtn: "Verstanden"/); + assert.doesNotMatch(source, /motion\/mini/); + assert.doesNotMatch(source, /cookie-info-fab/); +});