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
This commit is contained in:
65
backlog/tasks/task-2 - Animate-cookie-banner-transitions.md
Normal file
65
backlog/tasks/task-2 - Animate-cookie-banner-transitions.md
Normal file
@@ -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
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
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.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #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.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
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.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
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.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
@@ -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
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Reposition the red-panel 01 headline and supporting copy so it sits directly above the Projekt anfragen button.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [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
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
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
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
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.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
@@ -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": {
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -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:
|
||||
|
||||
@@ -76,7 +76,7 @@ const LandingHeroSection = () => {
|
||||
|
||||
<div className="relative z-10 flex min-h-[520px] flex-1 flex-col lg:min-h-0">
|
||||
<div className="flex shrink-0 items-start justify-between border-b border-primary-foreground/30 pb-5 text-xs uppercase tracking-[0.28em]">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span>©2026</span>
|
||||
</div>
|
||||
|
||||
@@ -117,8 +117,8 @@ const LandingHeroSection = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="relative flex min-h-0 flex-1 flex-col justify-center py-10 lg:py-14">
|
||||
<div className="pointer-events-none absolute bottom-8 right-0 h-36 w-36 border border-primary-foreground/35 sm:bottom-10 sm:right-2 lg:bottom-16 lg:right-10" />
|
||||
<div className="relative mt-auto pt-16 pb-8 lg:pt-24 lg:pb-10">
|
||||
<div className="pointer-events-none absolute bottom-0 right-0 h-36 w-36 border border-primary-foreground/35 sm:right-2 lg:right-10" />
|
||||
<div className="relative max-w-xl">
|
||||
<p className="text-[clamp(4rem,10vw,9rem)] font-black uppercase leading-[0.75] tracking-normal">
|
||||
01
|
||||
@@ -131,7 +131,7 @@ const LandingHeroSection = () => {
|
||||
|
||||
<a
|
||||
href="#kontakt"
|
||||
className="group relative z-10 mt-auto inline-flex w-fit shrink-0 items-center gap-3 border border-primary-foreground px-5 py-4 text-sm font-semibold uppercase tracking-[0.18em] transition hover:bg-primary-foreground hover:text-primary"
|
||||
className="group relative z-10 inline-flex w-fit shrink-0 items-center gap-3 border border-primary-foreground px-5 py-4 text-sm font-semibold uppercase tracking-[0.18em] transition hover:bg-primary-foreground hover:text-primary"
|
||||
>
|
||||
Projekt anfragen
|
||||
<ArrowUpRight className="size-5 transition group-hover:-translate-y-0.5 group-hover:translate-x-0.5" />
|
||||
|
||||
38
src/lib/cookie-banner-info.ts
Normal file
38
src/lib/cookie-banner-info.ts
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -22,5 +22,9 @@ import "@/styles/global.css";
|
||||
<LandingHeroSection client:media="(min-width: 1024px)" />
|
||||
<LandingRest />
|
||||
</main>
|
||||
<script>
|
||||
import { initCookieInfoBanner } from "@/lib/cookie-banner-info";
|
||||
initCookieInfoBanner();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
14
tests/cookie-banner-info.test.mjs
Normal file
14
tests/cookie-banner-info.test.mjs
Normal file
@@ -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/);
|
||||
});
|
||||
Reference in New Issue
Block a user