diff --git a/app/agb/page.tsx b/app/agb/page.tsx index 294d8b4..b4972c2 100644 --- a/app/agb/page.tsx +++ b/app/agb/page.tsx @@ -27,7 +27,7 @@ export default async function AgbPage() {

- Startseite + Startseite AGB

diff --git a/app/blog/page.tsx b/app/blog/page.tsx index d090b06..a8eea35 100644 --- a/app/blog/page.tsx +++ b/app/blog/page.tsx @@ -47,7 +47,16 @@ export default async function BlogOverviewPage({ const allCategories = blogFilterEnabled ? distinctCategories(allPosts) : []; const posts = activeCategories.length === 0 ? allPosts : allPosts.filter((post) => post.categories.some((c) => activeCategories.includes(c))); - const [featured, ...rest] = posts; + // getBlogPosts sorts "-featured,-publishedAt", so index 0 IS the actual + // featured post when the unfiltered list is shown — but a category + // filter can (and often will) exclude it entirely, in which case index 0 + // is just the newest matching post, not an editorially featured one. It + // still doesn't deserve the big hero-card treatment (implies "the + // featured post", not "whatever sorted first"), so that layout is gated + // on the post's own `featured` flag, not on array position. + const [first, ...restAll] = posts; + const featured = first?.featured ? first : undefined; + const rest = featured ? restAll : posts; return ( <> diff --git a/app/components/Navbar.tsx b/app/components/Navbar.tsx index 7056779..31a2e44 100644 --- a/app/components/Navbar.tsx +++ b/app/components/Navbar.tsx @@ -177,6 +177,12 @@ function CartLink() { const anchorRef = useRef(null); const [pulse, setPulse] = useState(false); const prevVisibleRef = useRef(0); + // useCartCount's getServerSnapshot is always 0 (see cart.ts) so hydration + // always transitions 0 -> the real count on first render — without this + // guard that transition alone satisfied "visibleCount > prevVisibleRef" + // and pulsed the badge on every single page load/reload, not just an + // actual in-session cart change. + const hasMountedRef = useRef(false); const pathname = usePathname(); // Held back by pendingCount while a ball is mid-flight, so the badge only @@ -191,6 +197,11 @@ function CartLink() { }, [registerCartIcon]); useEffect(() => { + if (!hasMountedRef.current) { + hasMountedRef.current = true; + prevVisibleRef.current = visibleCount; + return; + } if (visibleCount > prevVisibleRef.current) { setPulse(true); const t = setTimeout(() => setPulse(false), 350); diff --git a/app/components/WishlistButton.tsx b/app/components/WishlistButton.tsx index bb6bb3d..8ad4ea3 100644 --- a/app/components/WishlistButton.tsx +++ b/app/components/WishlistButton.tsx @@ -39,7 +39,11 @@ export function WishlistButton({ * but visually quieter than the full-size default. Applies to * already-wishlisted hearts too (not just the hover-revealed ones) — * consistent sizing across every heart on a touch device, rather than - * only the not-yet-wishlisted ones shrinking. */ + * only the not-yet-wishlisted ones shrinking. This smaller touch sizing + * is applied regardless of `revealOnHover` (see the className below) — + * a heart reading visually "quieter" on a touch screen is a general + * touch-device trait, not specific to the multi-card-grid use case it + * was first built for. */ revealOnHover?: boolean; }) { const { isWishlisted, toggle } = useWishlist(); @@ -70,9 +74,7 @@ export function WishlistButton({ aria-label={wishlisted ? "Von der Merkliste entfernen" : "Zur Merkliste hinzufügen"} aria-pressed={wishlisted} disabled={pending} - className={`flex h-9 w-9 items-center justify-center rounded-full bg-bg-base/90 backdrop-blur-sm transition-all active:scale-90 disabled:opacity-60 ${ - revealOnHover ? "pointer-coarse:h-7 pointer-coarse:w-7 pointer-coarse:bg-bg-base/60" : "" - } ${ + className={`flex h-9 w-9 items-center justify-center rounded-full bg-bg-base/90 backdrop-blur-sm transition-all active:scale-90 disabled:opacity-60 pointer-coarse:h-7 pointer-coarse:w-7 pointer-coarse:bg-bg-base/60 ${ revealOnHover && !wishlisted ? "opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100 pointer-coarse:opacity-100" : "" @@ -83,7 +85,7 @@ export function WishlistButton({ height="18" viewBox="0 0 20 18" fill={wishlisted ? "currentColor" : "none"} - className={`${wishlisted ? "text-brand" : "text-text-primary"} ${revealOnHover ? "pointer-coarse:scale-75" : ""}`} + className={`${wishlisted ? "text-brand" : "text-text-primary"} pointer-coarse:scale-75`} >

- Startseite + Startseite Datenschutz

diff --git a/app/impressum/page.tsx b/app/impressum/page.tsx index f505e75..c30a345 100644 --- a/app/impressum/page.tsx +++ b/app/impressum/page.tsx @@ -29,7 +29,7 @@ export default async function ImpressumPage() {

- Startseite + Startseite Impressum

diff --git a/app/widerruf/page.tsx b/app/widerruf/page.tsx index ff1e9f1..e2f9d8b 100644 --- a/app/widerruf/page.tsx +++ b/app/widerruf/page.tsx @@ -27,7 +27,7 @@ export default async function WiderrufPage() {

- Startseite + Startseite Widerruf