Fix orange dot/hero image visibility, arrow alignment, and add a mobile legal-page TOC
- PopIn (Home Hero's brand dot) switched from whileInView to animate — its translate-based entrance could push the element off-screen on a narrow phone before the IntersectionObserver ever saw it as visible, leaving it stuck invisible permanently. - Hero image: no longer wrapped in Reveal below lg: — whileInView's margin meant it stayed at opacity:0 (a white gap above the fold) on short mobile viewports until scrolled. Reveal's fade-in kept from lg: up. - "→ Label" CTA links (Tools.tsx, Blog.tsx) now use a flex row with the arrow as its own span instead of a literal inline "→" character, which doesn't reliably align to the surrounding text's cap-height. - Replaced icon-arrow-connector.svg with a new shared StepArrow component (inline SVG) across all three step sections — the old asset's color couldn't be overridden from outside the SVG file, so it could never actually become brand-orange. Bigger and better-shaped below the structural breakpoint per feedback. - Added MobileSectionTOC (SectionTOC.tsx) — a <details> accordion shown below lg: on Impressum/Datenschutz/AGB/Widerruf/Versand, which previously had no on-page navigation aid at all below lg: (the sidebar TOC is `hidden` entirely there). - Updated the figma-to-nextjs skill with 8 new dated Gotchas from this mobile-responsive pass, and expanded Step 6's verification checklist. - README: new "Mobile responsive pass" section summarizing the above.
This commit is contained in:
@@ -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 <nav> were sticky, the
|
||||
// card (a plain-flow sibling) would scroll away independently instead of
|
||||
// travelling with it. The caller wraps whatever the sidebar column
|
||||
// contains (this alone, or this + more) in `lg:sticky lg:top-32
|
||||
// lg:self-start` so the whole column moves as one unit.
|
||||
export function SectionTOC({ sections }: { sections: TOCSection[] }) {
|
||||
// Shared between SectionTOC (desktop sidebar nav) and MobileSectionTOC
|
||||
// (below lg: collapsible accordion, added 2026-07-24) — both need the same
|
||||
// scroll-spy "active" state and click-override handling, just render it
|
||||
// completely differently, so the logic lives here once instead of being
|
||||
// duplicated per component.
|
||||
function useActiveSection(sections: TOCSection[]) {
|
||||
const [active, setActive] = useState<string>(sections[0]?.id ?? "");
|
||||
// Not state — read inside the IntersectionObserver callback without
|
||||
// needing to re-subscribe it on every click, and cleared by its own
|
||||
@@ -67,6 +55,28 @@ export function SectionTOC({ sections }: { sections: TOCSection[] }) {
|
||||
}, CLICK_OVERRIDE_MS);
|
||||
}
|
||||
|
||||
return { active, handleClick };
|
||||
}
|
||||
|
||||
// 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 — every caller wraps this in its own `hidden lg:flex
|
||||
// ... lg:sticky lg:top-32 lg:self-start` div (Impressum/Datenschutz also
|
||||
// stack a second "Nachhaltigkeit" card below this in that same wrapper, so
|
||||
// the sticky behavior has to live on the wrapper for the two to travel
|
||||
// together as one unit — putting it on this <nav> instead would leave
|
||||
// that card behind as a plain-flow sibling scrolling past a now-fixed nav).
|
||||
export function SectionTOC({ sections }: { sections: TOCSection[] }) {
|
||||
const { active, handleClick } = useActiveSection(sections);
|
||||
if (sections.length === 0) return null;
|
||||
|
||||
return (
|
||||
@@ -92,3 +102,44 @@ export function SectionTOC({ sections }: { sections: TOCSection[] }) {
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
// Below lg: only — a collapsible accordion instead of the sidebar nav
|
||||
// (which is `hidden` entirely below lg:, see SectionTOC's own comment on
|
||||
// why a real 2-column split doesn't fit there). Added 2026-07-24: these
|
||||
// legal pages had no on-page navigation aid at all on Mobile/Tablet, which
|
||||
// is exactly where scanning a long legal document by scrolling is hardest.
|
||||
// Native <details>/<summary> — no extra open/close state needed, and it
|
||||
// stays open after a click so jumping between sections doesn't require
|
||||
// reopening it each time. Render this as its own element in the page
|
||||
// (typically right after the heading, before the two-column content row),
|
||||
// not nested inside a parent that's itself `hidden lg:...` — that would
|
||||
// hide this too regardless of its own lg:hidden class.
|
||||
export function MobileSectionTOC({ sections }: { sections: TOCSection[] }) {
|
||||
const { active, handleClick } = useActiveSection(sections);
|
||||
if (sections.length === 0) return null;
|
||||
|
||||
return (
|
||||
<details className="lg:hidden w-full bg-bg-base border border-border rounded-md p-4 open:pb-2">
|
||||
<summary className="text-label font-semibold text-text-muted uppercase tracking-wide cursor-pointer select-none">
|
||||
Inhaltsübersicht
|
||||
</summary>
|
||||
<div className="flex flex-col gap-1 mt-3">
|
||||
{sections.map(({ id, title }) => (
|
||||
<a
|
||||
key={id}
|
||||
href={`#${id}`}
|
||||
onClick={() => handleClick(id)}
|
||||
className={
|
||||
"px-3 py-2 rounded-sm text-body-sm transition-colors border-l-2 " +
|
||||
(active === id
|
||||
? "border-toc-active-border bg-bg-muted text-text-primary font-semibold"
|
||||
: "border-transparent text-text-muted hover:text-text-primary")
|
||||
}
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user