Files
einfach-produktiv/app/components/Tools.tsx
T
Marco 6a6d50abdb 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.
2026-07-24 19:17:56 +00:00

95 lines
4.7 KiB
TypeScript

import Link from "next/link";
import Image from "next/image";
import { Reveal, RevealGroup, RevealItem } from "./Reveal";
import { getWerkzeugeCards } from "../lib/payload";
// Content now lives in Payload (WerkzeugeCards collection). Icons use a
// uniform box here (object-contain) rather than the previous hardcoded
// per-card hand-tuned width/height/rotation — that only made sense for a
// fixed, known set of 3 SVGs authored upside-down for a specific
// hand-drawn look, which doesn't generalize to a real CMS field. The 3
// original icons were re-exported pre-flipped/rotated as PNGs so they
// still display correctly with plain object-contain.
export async function Tools() {
const tools = await getWerkzeugeCards();
if (tools.length === 0) return null;
return (
<div id="werkzeuge" className="flex flex-col gap-12 items-start pb-16 pt-8 w-full bg-bg-base">
{/* Section header */}
<Reveal className="flex flex-col gap-2 items-start px-[var(--layout-padding-x)] w-full">
<p className="font-bold text-brand text-h-small">
Meine Werkzeuge
</p>
<p
className="font-semibold text-text-primary text-h-section"
style={{ fontFamily: "var(--font-lora)" }}
>
Werkzeuge für einen leichteren Alltag.
</p>
</Reveal>
{/* Tools grid — 3 cols md+, stacked below md; internal icon+text layout
stays horizontal at every size, only the outer span changes */}
<RevealGroup className="grid grid-cols-1 md:grid-cols-12 gap-10 md:gap-[var(--layout-grid-gap)] px-[var(--layout-padding-x)] w-full">
{tools.map((tool) => (
<RevealItem
key={tool.id}
className="md:col-span-4 flex gap-8 items-start rounded-md transition-transform duration-300 hover:-translate-y-1"
>
{/* Icon — uniform box, pre-flipped/rotated source asset.
size-14 (56px) is a fixed value at every width (14 isn't
one of this project's fluid spacing-scale steps) — smaller
below md: so it doesn't dwarf the title/description text,
which does shrink toward its own fluid floor there. */}
<div className="relative flex items-center justify-center shrink-0 size-11 md:size-14">
<Image alt="" src={tool.icon} fill sizes="(min-width: 768px) 56px, 44px" className="object-contain" />
</div>
{/* Card content — self-stretch + h-full + justify-between so
the CTA always sits at the same bottom edge across all 3
cards, regardless of how many lines the description wraps
to. The parent RevealItem's items-start (needed to keep the
icon top-aligned, not stretched) otherwise stops this one
child from filling the grid-equalized row height — matches
the Figma card-content spec (h-full items-start
justify-between), which the earlier implementation had
dropped. gap-4 alongside justify-between guarantees a
minimum text→CTA gap even for the tallest card (the one
that defines the row height, and so has ~zero leftover
space for justify-between to distribute on its own). */}
<div className="flex flex-1 flex-col self-stretch h-full justify-between items-start gap-4 min-w-0 text-text-primary [word-break:break-word]">
<div className="flex flex-col gap-4 items-start w-full">
<p
className="font-semibold leading-normal text-h-section w-full"
style={{ fontFamily: "var(--font-lora)" }}
>
{tool.title}
</p>
<p className="font-normal leading-[1.5rem] text-body w-full">
{tool.description}
</p>
</div>
{/* 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). */}
<Link
href={tool.ctaHref}
className="flex items-center gap-1 font-bold leading-normal text-body whitespace-nowrap hover:text-brand transition-colors"
>
<span aria-hidden></span>
<span>{tool.ctaLabel}</span>
</Link>
</div>
</RevealItem>
))}
</RevealGroup>
</div>
);
}