diff --git a/app/components/ArrowRightIcon.tsx b/app/components/ArrowRightIcon.tsx
new file mode 100644
index 0000000..c506f3f
--- /dev/null
+++ b/app/components/ArrowRightIcon.tsx
@@ -0,0 +1,17 @@
+// Replaces the Unicode "→" (U+2192) previously used inline in CTA links
+// (Tools.tsx, Blog.tsx, ProductGrid.tsx) — that glyph isn't covered by the
+// site's custom web fonts, so browsers fall back to a system font just for
+// that one character. The fallback's vertical metrics differ enough by
+// platform (confirmed: sat visibly low relative to the label text on a
+// Samsung Galaxy S22/Android Chrome, not reproducible in desktop Chromium)
+// that centering it via flex `items-center` alone isn't reliable across
+// devices. An SVG has no font-fallback path — it renders identically
+// everywhere. `currentColor` stroke follows the parent Link's own
+// text/hover color, same as every other icon in this codebase.
+export function ArrowRightIcon() {
+ return (
+
+ );
+}
diff --git a/app/components/Blog.tsx b/app/components/Blog.tsx
index 7aed233..1fc1072 100644
--- a/app/components/Blog.tsx
+++ b/app/components/Blog.tsx
@@ -2,6 +2,7 @@ import Link from "next/link";
import Image from "next/image";
import { getBlogPosts } from "../lib/payload";
import { Reveal, RevealGroup, RevealItem } from "./Reveal";
+import { ArrowRightIcon } from "./ArrowRightIcon";
export async function Blog() {
const posts = await getBlogPosts(3);
@@ -69,7 +70,7 @@ export async function Blog() {
href={featured.href}
className="flex items-center gap-1 font-bold text-body text-text-primary whitespace-nowrap hover:text-brand transition-colors"
>
- →
+ Zum Beitrag
@@ -122,7 +123,7 @@ export async function Blog() {
href={post.href}
className="flex items-center gap-1 font-bold text-body whitespace-nowrap hover:text-brand transition-colors"
>
- →
+ Zum Beitrag
diff --git a/app/components/Tools.tsx b/app/components/Tools.tsx
index 05e7539..de9583d 100644
--- a/app/components/Tools.tsx
+++ b/app/components/Tools.tsx
@@ -1,6 +1,7 @@
import Link from "next/link";
import Image from "next/image";
import { Reveal, RevealGroup, RevealItem } from "./Reveal";
+import { ArrowRightIcon } from "./ArrowRightIcon";
import { getWerkzeugeCards } from "../lib/payload";
// Content now lives in Payload (WerkzeugeCards collection). Icons use a
@@ -87,17 +88,14 @@ export async function Tools() {
{tool.description}
- {/* flex items-center + arrow as its own span, not inline text
- — the → glyph sits low relative to the surrounding text's
- cap-height in the font used here, off-center against the
- label if it's just part of the same text node (fixed
- 2026-07-24, same pattern ProductGrid.tsx's "Mehr
- erfahren" link already uses). */}
+ {/* SVG arrow, not a Unicode "→" character — see
+ ArrowRightIcon.tsx's own comment on why (font-fallback
+ vertical-metrics mismatch, platform-dependent). */}
- →
+ {tool.ctaLabel}
diff --git a/app/konto/bestellungen/components/OrderFilters.tsx b/app/konto/bestellungen/components/OrderFilters.tsx
index 097795f..f0c0a68 100644
--- a/app/konto/bestellungen/components/OrderFilters.tsx
+++ b/app/konto/bestellungen/components/OrderFilters.tsx
@@ -1,5 +1,6 @@
"use client";
+import { useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { CustomSelect } from "../../../components/CustomSelect";
@@ -31,6 +32,16 @@ export function OrderFilters({
const paymentStatus = searchParams.get("paymentStatus") ?? "";
const year = searchParams.get("year") ?? "";
const hasAnyFilter = Boolean(status || paymentStatus || year);
+ const activeCount = [status, paymentStatus, year].filter(Boolean).length;
+
+ // Collapsed by default on mobile — with the account tab bar now also
+ // stacked above this (see KontoShell/AccountNav), 3 full-width dropdowns
+ // always visible left little room for the actual order list. Desktop
+ // (sm+) ignores this entirely and always shows the row inline, same as
+ // before. Starts expanded whenever a filter is already active (arriving
+ // via a shared/bookmarked filtered URL shouldn't hide what's applied) —
+ // a lazy initializer since it only needs to run once, on mount.
+ const [expanded, setExpanded] = useState(() => hasAnyFilter);
function setParam(key: string, value: string) {
const params = new URLSearchParams(searchParams.toString());
@@ -43,19 +54,38 @@ export function OrderFilters({
const yearOptions = years.map((y) => ({ value: y, label: y }));
return (
-
)}
- {/* self-center below sm — this row otherwise inherits the parent
- Reveal's items-start (left-aligned); centered here per its
- own request, but only the row itself (self-center keeps its
- shrink-to-fit content width, doesn't stretch it to the full
- parent width the way w-full/justify-center on the parent
- would). Back to left-aligned (matching the rest of the page)
- from sm up. */}
-
-
- Profil & Adresse
-
-
- Weiter einkaufen
-
-
-
-
-
-
- >
+
+
);
}
diff --git a/app/konto/components/AccountNav.tsx b/app/konto/components/AccountNav.tsx
new file mode 100644
index 0000000..2b5bd8a
--- /dev/null
+++ b/app/konto/components/AccountNav.tsx
@@ -0,0 +1,93 @@
+"use client";
+
+import Link from "next/link";
+import { usePathname, useRouter } from "next/navigation";
+import { clearCart } from "../../lib/cart";
+import { dispatchAuthChanged } from "../../lib/auth";
+
+// Persistent account navigation — a fixed sidebar (sm+) / horizontal tab
+// bar (below sm) shown on every logged-in /konto/* page via KontoShell.
+// Replaces the "Profil & Adresse / Weiter einkaufen / Abmelden" links that
+// used to live at the bottom of the orders page's content — those became
+// unreachable without scrolling past an arbitrarily long order list. Being
+// part of the shell now (not page content), reachability no longer depends
+// on how much is above it.
+const NAV_ITEMS = [
+ { href: "/konto/bestellungen", label: "Bestellungen", wishlistOnly: false },
+ { href: "/konto/merkliste", label: "Merkliste", wishlistOnly: true },
+ { href: "/konto/profil", label: "Profil", wishlistOnly: false },
+] as const;
+
+export function AccountNav({ wishlistEnabled }: { wishlistEnabled: boolean }) {
+ const pathname = usePathname();
+ const router = useRouter();
+ const items = NAV_ITEMS.filter((item) => !item.wishlistOnly || wishlistEnabled);
+
+ // Same sequence as the old LogoutButton (now folded in here, its one
+ // call site): clear the local cart (already mirrored server-side by
+ // CartSync, so safe to drop — the next login's mergeServerCartIntoLocal()
+ // restores it), tell already-mounted Client Components (Navbar's
+ // AccountLink) the auth state changed, leave the account area, then
+ // force a fresh Server Component render.
+ async function handleLogout() {
+ await fetch("/api/account/logout", { method: "POST" });
+ clearCart();
+ dispatchAuthChanged();
+ router.push("/");
+ router.refresh();
+ }
+
+ return (
+ <>
+ {/* Desktop/tablet — sidebar, sm+ (640px). Sits to the left of the
+ page content inside KontoShell's flex row. */}
+
+
+ {/* Mobile — horizontal tab bar, below sm. overflow-x-auto rather
+ than wrapping: 4 items at once already fits most phones, and a
+ scrollable single row reads clearly as "more tabs this way"
+ rather than a wrapped second line competing for attention with
+ the page content right below it. */}
+
+ >
+ );
+}
diff --git a/app/konto/components/KontoShell.tsx b/app/konto/components/KontoShell.tsx
new file mode 100644
index 0000000..170bd20
--- /dev/null
+++ b/app/konto/components/KontoShell.tsx
@@ -0,0 +1,29 @@
+import type { ReactNode } from "react";
+import { Footer } from "../../components/Footer";
+import { getWishlistEnabled } from "../../lib/payload";
+import { AccountNav } from "./AccountNav";
+
+// Shared shell for every logged-in /konto/* page (bestellungen, merkliste,
+// profil) — the /