Fix cart-badge false pulse, blog hero-card misattribution, legal breadcrumb color
- Cart badge pulsed on every page load/reload, not just real cart changes — useCartCount's SSR snapshot is always 0, so hydration alone satisfied the "count increased" check. Skip the very first effect run. - WishlistButton: touch-device smaller/subtler sizing now applies regardless of revealOnHover (e.g. the homepage spotlight heart), not just the shop grid's hover-reveal mode. - /blog: the big featured-style hero card was applied to whatever post sorted first post-filter, even when that post isn't actually the `featured` one (a category filter can exclude the real featured post entirely). Now gated on the post's own `featured` flag. - Legal pages' "Startseite" breadcrumb link now matches the same text-brand/hover:underline treatment already used elsewhere on these pages (e.g. Impressum's Anbieter email link), instead of staying muted until hover. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018unaXmuzVA8ct1b6WoyP1U
This commit is contained in:
+1
-1
@@ -27,7 +27,7 @@ export default async function AgbPage() {
|
||||
<main className="flex flex-col flex-1 bg-bg-base">
|
||||
<Reveal className="flex flex-col gap-3 items-start pb-6 pt-10 px-[var(--layout-padding-x)] w-full">
|
||||
<p className="flex items-center gap-2 text-body-sm text-text-muted">
|
||||
<Link href="/" className="hover:text-brand transition-colors">Startseite</Link>
|
||||
<Link href="/" className="text-brand hover:underline">Startseite</Link>
|
||||
<span>›</span>
|
||||
<span className="text-text-primary">AGB</span>
|
||||
</p>
|
||||
|
||||
+10
-1
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -177,6 +177,12 @@ function CartLink() {
|
||||
const anchorRef = useRef<HTMLAnchorElement>(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);
|
||||
|
||||
@@ -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`}
|
||||
>
|
||||
<path
|
||||
d="M10 17S1 11.5 1 5.8C1 2.6 3.4 1 5.8 1c1.6 0 3.2.9 4.2 2.4C11 1.9 12.6 1 14.2 1 16.6 1 19 2.6 19 5.8 19 11.5 10 17 10 17Z"
|
||||
|
||||
@@ -26,7 +26,7 @@ export default async function DatenschutzPage() {
|
||||
<main className="flex flex-col flex-1 bg-bg-base">
|
||||
<Reveal className="flex flex-col gap-3 items-start pb-6 pt-10 px-[var(--layout-padding-x)] w-full">
|
||||
<p className="flex items-center gap-2 text-body-sm text-text-muted">
|
||||
<Link href="/" className="hover:text-brand transition-colors">Startseite</Link>
|
||||
<Link href="/" className="text-brand hover:underline">Startseite</Link>
|
||||
<span>›</span>
|
||||
<span className="text-text-primary">Datenschutz</span>
|
||||
</p>
|
||||
|
||||
@@ -29,7 +29,7 @@ export default async function ImpressumPage() {
|
||||
<main className="flex flex-col flex-1 bg-bg-base">
|
||||
<Reveal className="flex flex-col gap-3 items-start pb-6 pt-10 px-[var(--layout-padding-x)] w-full">
|
||||
<p className="flex items-center gap-2 text-body-sm text-text-muted">
|
||||
<Link href="/" className="hover:text-brand transition-colors">Startseite</Link>
|
||||
<Link href="/" className="text-brand hover:underline">Startseite</Link>
|
||||
<span>›</span>
|
||||
<span className="text-text-primary">Impressum</span>
|
||||
</p>
|
||||
|
||||
@@ -27,7 +27,7 @@ export default async function WiderrufPage() {
|
||||
<main className="flex flex-col flex-1 bg-bg-base">
|
||||
<Reveal className="flex flex-col gap-3 items-start pb-6 pt-10 px-[var(--layout-padding-x)] w-full">
|
||||
<p className="flex items-center gap-2 text-body-sm text-text-muted">
|
||||
<Link href="/" className="hover:text-brand transition-colors">Startseite</Link>
|
||||
<Link href="/" className="text-brand hover:underline">Startseite</Link>
|
||||
<span>›</span>
|
||||
<span className="text-text-primary">Widerruf</span>
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user