"use client"; import { useEffect, useRef } from "react"; import Image from "next/image"; import { AnimatePresence, motion } from "motion/react"; const features = [ { icon: "/icon-sparkle-wrapper.svg", title: "7 Tage. Ein Fokus.", desc: "Tägliche Impulse für mehr Klarheit und weniger Reibung.", }, { icon: "/icon-checklist.png", title: "Praktisch & umsetzbar.", desc: "Direkt anwendbare Methoden für deinen Alltag.", }, { icon: "/icon-heart.png", title: "Kein Spam. Versprochen.", desc: "Nur wertvolle Inhalte, wenn du sie brauchst.", }, ]; /** * The Navbar's "Newsletter" CTA opens this modal rather than navigating — * matches the original Figma prototype (Newsletter-Button → Overlay * newsletter-overlay), unlike the Werkzeuge "Impulse & Tipps" card's * "Anmelden" link, which goes to the full /newsletter detail page instead. * Different entry points, different weight: a quick-access nav CTA gets a * lightweight in-place signup, a content card gets the full page. */ export function NewsletterModal({ open, onClose }: { open: boolean; onClose: () => void }) { const dialogRef = useRef(null); const closeButtonRef = useRef(null); // Body scroll lock while open. useEffect(() => { if (!open) return; const prevOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = prevOverflow; }; }, [open]); // Focus management: move focus into the modal on open, trap Tab within // it, close + return focus on Escape — same pattern as the Navbar's // mobile drawer. useEffect(() => { if (!open) return; closeButtonRef.current?.focus(); const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { e.preventDefault(); onClose(); return; } if (e.key !== "Tab") return; const focusables = dialogRef.current?.querySelectorAll( 'a[href], button:not([disabled]), input:not([disabled])' ); if (!focusables || focusables.length === 0) return; const first = focusables[0]; const last = focusables[focusables.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } }; document.addEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown); }, [open, onClose]); return ( // AnimatePresence, not a plain `if (!open) return null` — that would // unmount the modal instantly on close with no way to play an exit // animation first. Keeping it mounted (conditionally rendering the // child) lets Framer Motion finish the fade-out before removing it. {open && ( { if (e.target === e.currentTarget) onClose(); }} > {/* modal-top: photo + copy/form, stacked below md */}
Notizbuch mit Kaffee und Stift
{/* -scale-y-100 is required, not just -rotate-4 — the SVG itself is authored upside-down (matches how Newsletter.tsx uses this exact same asset); without it the icon renders flipped. */}

Starte mit einer Woche voller Klarheit

Melde dich zum Newsletter an und erhalte die 7-Tage-Challenge, mit der du durch mehr Struktur weniger Stress spürst.

{/* modal-bottom: 3 feature cards. Figma's export had items-end on this row, but that's a byproduct of nested -scale-y-100 flip-wrappers Figma uses for baseline-grid tricks (visually cancels out to top-alignment in the actual design) — taken literally it bottom-aligned the icon with the last line of description text instead of the title, which read as broken. items-start + a fixed icon bounding box (icons have different native proportions, e.g. the sparkle glyph isn't square) fixes it without needing the flip trick. */}
{features.map((f) => (

{f.title}

{f.desc}

))}
)}
); }