diff --git a/README.md b/README.md
index ac306d2..eabc1dc 100644
--- a/README.md
+++ b/README.md
@@ -1408,6 +1408,39 @@ session (low-stock digest, stale-unverified-accounts report, weekly revenue
report, expired-discount-code cleanup) needed no monitor changes of their
own; see the Payload README's "Jobs Queue" section.
+## Mobile responsive pass (2026-07-24)
+
+A full-site pass fixing concrete Mobile-width bugs across the Home Hero,
+Navbar, Newsletter, cart, `/challenge`, `/todo-cards`, blog detail, and the
+legal pages — found by testing on a real phone rather than just resizing a
+desktop browser to 768px. The detailed technical lessons (aspect-ratio
+distortion from mixing fluid/fixed sizing, `preserveAspectRatio="none"` SVGs,
+`whileInView` failing to trigger on short/narrow viewports, etc.) are written
+up as dated Gotchas in the `figma-to-nextjs` skill
+(`~/.claude/skills/figma-to-nextjs/SKILL.md`) rather than duplicated here —
+that's now the reference for "why" on any of this. Two changes are worth
+calling out specifically since they add new shared components:
+
+- **`app/components/StepArrow.tsx`** — a small inline-SVG connector arrow,
+ replacing `/icon-arrow-connector.svg` across all three "how it works" step
+ sections (`/challenge`, `/todo-cards`, `/newsletter`). The old asset's
+ color lived in a CSS custom property scoped to the SVG file itself, so it
+ could never actually become the brand orange used everywhere else once
+ loaded via `next/image` — converting it to a real component was the only
+ fix, and having one shared component means a future color/shape/size
+ tweak is one edit instead of three.
+- **`app/components/SectionTOC.tsx`'s `MobileSectionTOC`** — the
+ Impressum/Datenschutz/AGB/Widerruf/Versand table-of-contents sidebar was
+ `hidden` entirely below `lg:` (a 360px sidebar genuinely doesn't fit next
+ to a readable content column below that), which meant Mobile/Tablet had no
+ on-page navigation aid at all on these often-long pages — exactly where
+ scanning a long legal document by scrolling is hardest. `MobileSectionTOC`
+ is a `/` accordion (no extra JS state needed) sharing the
+ same scroll-spy "active section" logic (extracted into a `useActiveSection`
+ hook) as the desktop sidebar nav, rendered as its own element right after
+ each page's heading — not nested inside the sidebar's `hidden lg:...`
+ wrapper, which would hide it too regardless of its own `lg:hidden` class.
+
## Tests
`npm run test:unit` (Vitest, `node` environment, no jsdom/Next.js runtime
diff --git a/app/agb/page.tsx b/app/agb/page.tsx
index 8e18924..e840792 100644
--- a/app/agb/page.tsx
+++ b/app/agb/page.tsx
@@ -7,7 +7,7 @@ import { Footer } from "../components/Footer";
import { TrustRow } from "../components/TrustRow";
import { RichText, extractHeadings } from "../components/RichText";
import { LiveRichText } from "../components/LiveRichText";
-import { SectionTOC } from "../components/SectionTOC";
+import { SectionTOC, MobileSectionTOC } from "../components/SectionTOC";
import { getLegalPage } from "../lib/payload";
export const metadata: Metadata = {
@@ -39,6 +39,13 @@ export default async function AgbPage() {
Stand: Juli 2026
+ {/* MobileSectionTOC — below lg: only, see SectionTOC.tsx's own
+ comment. Outside the sidebar's `hidden lg:flex` wrapper below
+ (that wrapper's `hidden` would hide this too otherwise). */}
+
+
+
+
diff --git a/app/challenge/page.tsx b/app/challenge/page.tsx
index 3306957..b7d938d 100644
--- a/app/challenge/page.tsx
+++ b/app/challenge/page.tsx
@@ -4,6 +4,7 @@ import Image from "next/image";
import { draftMode } from "next/headers";
import { Footer } from "../components/Footer";
import { Reveal, RevealGroup, RevealItem } from "../components/Reveal";
+import { StepArrow } from "../components/StepArrow";
import { TestimonialsGrid } from "../components/TestimonialsGrid";
import { LiveTestimonialsGrid } from "../components/LiveTestimonialsGrid";
import { getTestimonials } from "../lib/payload";
@@ -251,23 +252,14 @@ export default async function ChallengePage() {
,
i < steps.length - 1 ? (
- // Same /icon-arrow-connector.svg asset and rotate-on-stack
- // pattern as todo-cards/newsletter's HowItWorks — this
- // used to be its own hand-drawn SVG arrow, inconsistent
- // with those two. Always visible (rotated 90° while
- // stacked below lg, this page's own structural
- // breakpoint) rather than hidden below lg like before.
+ // Shared StepArrow component (see its own file) — same
+ // rotate-on-stack pattern as todo-cards/newsletter's
+ // HowItWorks. Always visible (rotated 90° while stacked
+ // below lg, this page's own structural breakpoint) rather
+ // than hidden below lg like before. Bigger below lg:
+ // (w-8 h-8, was w-6 h-6) per explicit feedback.
- {/* object-contain — same preserveAspectRatio="none" fix
- as todo-cards/newsletter's HowItWorks; the square
- mobile box was stretching this into a blob. */}
-
+
+ {/* flex + arrow as its own span — see Tools.tsx's comment on
+ this same fix (→'s glyph baseline sits low next to text). */}
- → Zum Beitrag
+ →
+ Zum Beitrag
@@ -109,11 +112,14 @@ export async function Blog() {
+ {/* flex + arrow as its own span — see the featured post's
+ own Link above / Tools.tsx's comment on this same fix. */}
- → Zum Beitrag
+ →
+ Zum Beitrag
diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx
index 133ac20..1a4dfc6 100644
--- a/app/components/Hero.tsx
+++ b/app/components/Hero.tsx
@@ -2,6 +2,29 @@ import Link from "next/link";
import Image from "next/image";
import { PopIn, Reveal } from "./Reveal";
+// Shared between the plain (below lg:) and Reveal-wrapped (lg:+) render —
+// see the two call sites' own comment on why this needs two wrappers.
+function HeroImage() {
+ return (
+
+ );
+}
+
export function Hero() {
return (
@@ -113,26 +136,23 @@ export function Hero() {
plain box-shadow reads as a hard rectangular edge against the
existing corner/right/bottom mask-gradient fade below, which
looked worse than no shadow at all — tried and reverted. */}
+ {/* No Reveal (fade-in-on-scroll) below lg: — whileInView's -80px
+ viewport margin means the image doesn't fade in until scrolled
+ that much further into view; on a short mobile viewport this
+ image sits right at the initial fold, so it stayed at
+ opacity:0 (a white gap, matching the section's own bg-bg-base)
+ above the fold until the user scrolled (reported 2026-07-24).
+ Plain, always-visible image below lg: instead; Reveal's fade
+ kept from lg: up, where the image is beside the text with
+ plenty of room and this was never an issue. */}
+
+
+
-
+
diff --git a/app/components/Reveal.tsx b/app/components/Reveal.tsx
index 500e9fc..01376f7 100644
--- a/app/components/Reveal.tsx
+++ b/app/components/Reveal.tsx
@@ -120,8 +120,17 @@ export function PopIn({ children, className, delay = 0 }: RevealProps) {
diff --git a/app/components/SectionTOC.tsx b/app/components/SectionTOC.tsx
index d1ed7c5..b869a0f 100644
--- a/app/components/SectionTOC.tsx
+++ b/app/components/SectionTOC.tsx
@@ -10,24 +10,12 @@ export type TOCSection = { id: string; title: string };
// active state away from what was actually clicked.
const CLICK_OVERRIDE_MS = 1000;
-// lg:-only sidebar — same "wide fixed-width block next to content" shape
-// as the cart's order-summary sidebar (see figma-to-nextjs skill Gotcha
-// #5): a 360px TOC card plus a readable content column already exceeds
-// the 768px Tablet floor, so md: wouldn't leave room for a real 2-column
-// split at Tablet widths.
-//
-// Generic over `sections` — originally written just for /versand
-// (VersandTOC), generalized once /datenschutz needed the identical
-// scroll-spy sidebar but driven by CMS-authored headings instead of a
-// hardcoded array. Any future long legal/content page reuses this too.
-//
-// Not sticky itself — Impressum/Datenschutz put an extra card below this
-// in the same sidebar column, and if only this