Fix more mobile responsive issues: Hero, arrows, cart button, checkmarks, modal
- Newsletter card icon: fixed an aspect-ratio distortion bug (w-16 maps to this project's fluid --spacing-16, which floors to 40px below 768px, paired with a fixed 55px height — squished the icon on mobile). Same root cause existed in NewsletterModal's envelope icon; that one's now just hidden below md: instead per feedback. - Hero: subtitle sized down and CTA checkmark icon scaled to match below lg:, social proof text always wraps below the avatars there instead of only when it doesn't fit. - Werkzeuge connector arrows (todo-cards/newsletter/challenge HowItWorks): object-contain added — the SVG has preserveAspectRatio="none" and was stretching to fill the square mobile box instead of keeping its thin-arrow shape. - Challenge bottom CTA: icon now stacks above the copy, centered, below lg: to match the Home Newsletter card's pattern. - Homepage Werkzeuge icons: smaller below md: to match the (fluid-floor) text size next to them. - Blog detail "Passend dazu" card: the fixed w-[19rem] title column plus "Entdecken" sharing its row overflowed on mobile; stacked below sm: instead, with a little top margin on "Entdecken". - Todo-Karten checklist checkmarks: recolored brand-orange (icon-check .svg's fill lives in an internal CSS var that can't be overridden from outside an <img>-loaded SVG, so switched to an inline SVG) and top-aligned instead of vertically centered. - AddToCartButton: added whitespace-nowrap to the stacked-label grid (used to reserve button width across all possible label texts) — one of those labels wrapping to two lines on a narrow w-full button was inflating the row height for whichever label is actually showing, leaving a tall empty gap under "Ausverkauft".
This commit is contained in:
@@ -120,27 +120,34 @@ export default async function BlogDetailPage({
|
||||
{post.relatedProduct?.href && (
|
||||
<Link
|
||||
href={post.relatedProduct.href}
|
||||
className="group flex items-center gap-6 border border-border rounded-md px-9 py-7 hover:border-brand transition-colors"
|
||||
className="group flex items-center gap-4 sm:gap-6 border border-border rounded-md px-5 py-5 sm:px-9 sm:py-7 hover:border-brand transition-colors"
|
||||
>
|
||||
<div className="relative w-16 h-[4.6875rem] shrink-0 rounded-sm overflow-hidden">
|
||||
<Image alt="" src={post.relatedProduct.image} fill sizes="64px" className="object-cover" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 flex flex-col gap-2.5">
|
||||
<p className="font-bold text-[0.8125rem] text-brand">Passend dazu:</p>
|
||||
<div className="flex items-end justify-between gap-4 w-full">
|
||||
{/* w-[19rem] (305px) — matches Figma's title-col exactly,
|
||||
so the description wraps at the same point instead of
|
||||
stretching out to fill the space before "Entdecken". */}
|
||||
<div className="flex flex-col gap-2 items-start w-[19rem] shrink-0">
|
||||
{/* Stacked below sm: — the fixed w-[19rem] title column plus
|
||||
"Entdecken" on the same row overflowed a mobile-width
|
||||
card (fixed 2026-07-24). "Entdecken" wraps to its own
|
||||
line with a little space above it; back to the
|
||||
side-by-side row (matching Figma) from sm: up, where
|
||||
there's room for both. */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 w-full">
|
||||
{/* w-[19rem] (305px) only from sm: — matches Figma's
|
||||
title-col exactly there, so the description wraps at
|
||||
the same point instead of stretching out to fill the
|
||||
space before "Entdecken"; full width below sm:. */}
|
||||
<div className="flex flex-col gap-2 items-start w-full sm:w-[19rem] sm:shrink-0">
|
||||
<p
|
||||
className="font-semibold text-[1.375rem] text-text-primary whitespace-nowrap"
|
||||
className="font-semibold text-[1.375rem] text-text-primary sm:whitespace-nowrap"
|
||||
style={{ fontFamily: "var(--font-lora)" }}
|
||||
>
|
||||
{post.relatedProduct.name}
|
||||
</p>
|
||||
<p className="text-[0.9375rem] text-text-muted leading-[1.45]">{post.relatedProduct.description}</p>
|
||||
</div>
|
||||
<span className="flex items-center gap-1.5 font-bold text-[0.875rem] text-text-primary whitespace-nowrap">
|
||||
<span className="flex items-center gap-1.5 font-bold text-[0.875rem] text-text-primary whitespace-nowrap mt-1 sm:mt-0">
|
||||
Entdecken
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
|
||||
@@ -258,12 +258,15 @@ export default async function ChallengePage() {
|
||||
// stacked below lg, this page's own structural
|
||||
// breakpoint) rather than hidden below lg like before.
|
||||
<div key={`arrow-${i}`} className="flex items-center justify-center shrink-0 lg:mt-5">
|
||||
{/* object-contain — same preserveAspectRatio="none" fix
|
||||
as todo-cards/newsletter's HowItWorks; the square
|
||||
mobile box was stretching this into a blob. */}
|
||||
<Image
|
||||
alt=""
|
||||
src="/icon-arrow-connector.svg"
|
||||
width={24}
|
||||
height={24}
|
||||
className="w-6 h-6 rotate-90 lg:w-10 lg:h-3 lg:rotate-0"
|
||||
className="w-6 h-6 object-contain rotate-90 lg:w-10 lg:h-3 lg:rotate-0"
|
||||
/>
|
||||
</div>
|
||||
) : null,
|
||||
@@ -334,8 +337,11 @@ export default async function ChallengePage() {
|
||||
<div className="px-8 lg:px-[5rem] max-w-[1280px] mx-auto">
|
||||
<Reveal className="bg-[#f8f3ec] rounded-xl flex flex-col lg:flex-row gap-8 lg:gap-[3.5rem] items-start lg:items-center px-6 lg:px-10 py-8">
|
||||
|
||||
{/* Left: icon + copy */}
|
||||
<div className="flex gap-5 items-start flex-1 min-w-0">
|
||||
{/* Left: icon + copy — icon above text, centered, below lg:
|
||||
(matches the Home Newsletter card's icon-above-text
|
||||
pattern), row layout again from lg: up alongside the
|
||||
outer Reveal's own flex-col -> lg:flex-row switch. */}
|
||||
<div className="flex flex-col items-center text-center gap-5 lg:flex-row lg:items-start lg:text-left flex-1 min-w-0">
|
||||
<div className="shrink-0 -rotate-4">
|
||||
<svg width="52" height="44" viewBox="0 0 52 44" fill="none">
|
||||
<rect x="2" y="2" width="48" height="40" rx="3" stroke="#f6a701" strokeWidth="2" />
|
||||
|
||||
@@ -128,7 +128,14 @@ export function AddToCartButton({
|
||||
also reserves space for "Ausverkauft"/"Maximale Menge im
|
||||
Warenkorb" — the widest of the four wins regardless of which is
|
||||
showing. */}
|
||||
<span className="relative grid">
|
||||
{/* whitespace-nowrap — inherited by every stacked span below. On a
|
||||
w-full button (e.g. this page's mobile layout), "Maximale Menge
|
||||
im Warenkorb" is long enough to wrap to two lines without this,
|
||||
and since every stacked span shares the same grid cell, that
|
||||
inflated the row height for whichever text is actually showing
|
||||
too — "Ausverkauft" rendered with a tall empty gap underneath it
|
||||
(fixed 2026-07-24). */}
|
||||
<span className="relative grid whitespace-nowrap">
|
||||
<span className="invisible [grid-area:1/1]" aria-hidden="true">
|
||||
{label}
|
||||
</span>
|
||||
|
||||
+13
-10
@@ -47,8 +47,10 @@ export function Hero() {
|
||||
</PopIn>
|
||||
</p>
|
||||
|
||||
{/* Subheading */}
|
||||
<p className="font-semibold leading-[2.375rem] min-w-full shrink-0 text-text-primary text-h-emphasis w-[min-content] [word-break:break-word] not-italic">
|
||||
{/* Subheading — smaller fixed size below lg:, text-h-emphasis's
|
||||
own 20px floor read too large next to the now-smaller mobile
|
||||
CTA text. leading shrinks to match, not just font-size. */}
|
||||
<p className="font-semibold leading-[1.75rem] lg:leading-[2.375rem] min-w-full shrink-0 text-text-primary text-[1rem] lg:text-h-emphasis w-[min-content] [word-break:break-word] not-italic">
|
||||
Für Menschen mit Familie, Verantwortung und zu wenig Zeit
|
||||
</p>
|
||||
|
||||
@@ -67,17 +69,18 @@ export function Hero() {
|
||||
<span className="font-semibold leading-[2.375rem] text-text-primary text-[0.8125rem] lg:text-h3 whitespace-nowrap not-italic">
|
||||
Starte mit der 7-Tage-Challenge
|
||||
</span>
|
||||
<div className="relative h-[1.1875rem] w-[1.5625rem] shrink-0">
|
||||
<Image alt="" src="/icon-check.svg" fill sizes="26px" />
|
||||
{/* Scaled down to match the smaller mobile CTA text (same
|
||||
~0.76 aspect ratio as the lg: size), full size again from
|
||||
lg: up alongside text-h3. */}
|
||||
<div className="relative h-[0.8125rem] w-[1.0625rem] lg:h-[1.1875rem] lg:w-[1.5625rem] shrink-0">
|
||||
<Image alt="" src="/icon-check.svg" fill sizes="(min-width: 1024px) 26px, 17px" />
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{/* Social proof — flex-wrap + justify-center rather than a fixed
|
||||
breakpoint: at narrow widths avatars+text don't fit on one
|
||||
row, so the text wraps to its own line and both lines end up
|
||||
centered; from lg: up (real column width) it still fits on
|
||||
one row exactly as before, so nothing changes there. */}
|
||||
<div className="flex flex-wrap gap-3 items-center justify-center overflow-clip shrink-0 w-full">
|
||||
{/* Social proof — always avatars-then-text on two lines below
|
||||
lg: (not just when it happens to overflow), single row again
|
||||
from lg: up where the real column width fits it fine. */}
|
||||
<div className="flex flex-col lg:flex-row gap-3 items-center justify-center overflow-clip shrink-0 w-full">
|
||||
{/* Avatars — gap 2px, not overlapping */}
|
||||
<div className="flex gap-[0.125rem] items-center shrink-0">
|
||||
{["/avatar-1.jpg", "/avatar-2.jpg", "/avatar-3.jpg"].map((src, i) => (
|
||||
|
||||
@@ -55,7 +55,14 @@ export function Newsletter({
|
||||
width leaves real room. */}
|
||||
<div className="flex flex-col items-center gap-4 text-center w-full md:flex-row md:items-start md:gap-8 md:text-left md:w-[var(--newsletter-copy-width)] md:py-4 md:shrink-0">
|
||||
|
||||
{/* Decorative envelope icon, tilted -4° as per design */}
|
||||
{/* Decorative envelope icon, tilted -4° as per design.
|
||||
w-[4rem], not w-16 — this project's --spacing-16 is a
|
||||
fluid token (floors to 40px below 768px, see globals.css),
|
||||
so pairing w-16 with the fixed h-[3.438rem] squished the
|
||||
icon to a 40:55 box on mobile instead of the SVG's native
|
||||
64:55.0096 (it has preserveAspectRatio="none", so it
|
||||
actually stretches to whatever box it's given — fixed
|
||||
2026-07-24). */}
|
||||
<div className="flex items-center justify-center shrink-0 w-[4.23rem] h-[3.71rem]">
|
||||
<div className="-rotate-4 -scale-y-100">
|
||||
<Image
|
||||
@@ -63,7 +70,7 @@ export function Newsletter({
|
||||
src="/newsletter-icon.svg"
|
||||
width={64}
|
||||
height={55}
|
||||
className="w-16 h-[3.438rem] block"
|
||||
className="w-[4rem] h-[3.438rem] block"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -172,8 +172,8 @@ export function NewsletterModal({ open, onClose }: { open: boolean; onClose: ()
|
||||
{/* -scale-y-100 is required, not just -rotate-4 — the SVG
|
||||
itself is authored upside-down (matches how Newsletter.tsx
|
||||
uses this exact same asset); without it the icon renders
|
||||
flipped. */}
|
||||
<div className="w-16 h-14 -rotate-4 -scale-y-100">
|
||||
flipped. Hidden below md: — removed on mobile 2026-07-24. */}
|
||||
<div className="hidden md:block w-16 h-14 -rotate-4 -scale-y-100">
|
||||
<Image alt="" src="/newsletter-icon.svg" width={64} height={56} className="w-full h-full" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -38,9 +38,13 @@ export async function Tools() {
|
||||
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 */}
|
||||
<div className="relative flex items-center justify-center shrink-0 size-14">
|
||||
<Image alt="" src={tool.icon} fill sizes="56px" className="object-contain" />
|
||||
{/* 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
|
||||
|
||||
@@ -76,12 +76,16 @@ export function HowItWorks() {
|
||||
// technique as Challenge's step connector and todo-cards'
|
||||
// identical HowItWorks, not just the same icon asset.
|
||||
<div className="flex items-center justify-center shrink-0 md:mt-[1.625rem]">
|
||||
{/* object-contain — see todo-cards/HowItWorks.tsx's own
|
||||
comment on this same fix: the SVG's preserveAspectRatio
|
||||
="none" stretched it to fill the square mobile box
|
||||
instead of keeping its thin-arrow ratio. */}
|
||||
<Image
|
||||
alt=""
|
||||
src="/icon-arrow-connector.svg"
|
||||
width={24}
|
||||
height={24}
|
||||
className="w-6 h-6 rotate-90 md:w-10 md:h-3 md:rotate-0"
|
||||
className="w-6 h-6 object-contain rotate-90 md:w-10 md:h-3 md:rotate-0"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -9,6 +9,17 @@ const bullets = [
|
||||
"Inklusive Mini-Anleitung mit Tipps für den Start",
|
||||
];
|
||||
|
||||
// Inline, brand-orange stroke — same fix/reasoning as TodoKartenHero.tsx's
|
||||
// own IconCheck (icon-check.svg's fill can't be recolored from outside
|
||||
// the SVG when loaded via <img src>/next/image).
|
||||
function IconCheck() {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" className="size-5 shrink-0 mt-0.5">
|
||||
<path d="M4 10.5l4.5 4.5L16 5.5" stroke="#f6a701" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Focus() {
|
||||
return (
|
||||
<section className="w-full bg-bg-base flex flex-col lg:flex-row gap-10 lg:gap-16 items-center py-12 md:py-16 px-[var(--layout-padding-x)]">
|
||||
@@ -33,8 +44,8 @@ export function Focus() {
|
||||
</p>
|
||||
<ul className="flex flex-col gap-3 items-start w-full">
|
||||
{bullets.map((b) => (
|
||||
<li key={b} className="flex gap-[0.625rem] items-center w-full">
|
||||
<Image alt="" src="/icon-check.svg" width={20} height={20} className="size-5 shrink-0" />
|
||||
<li key={b} className="flex gap-[0.625rem] items-start w-full">
|
||||
<IconCheck />
|
||||
<span className="flex-1 font-semibold text-body text-text-primary">{b}</span>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -83,12 +83,18 @@ export function HowItWorks() {
|
||||
// — same margin-based centering technique as Challenge's
|
||||
// step connector, not just the same icon asset.
|
||||
<div className="flex items-center justify-center shrink-0 md:mt-[1.625rem]">
|
||||
{/* object-contain — the SVG has preserveAspectRatio="none"
|
||||
(viewBox 40x12) and stretches to fill whatever box
|
||||
it's given; the square w-6 h-6 mobile box squished it
|
||||
into a blob instead of a thin arrow. contain keeps its
|
||||
real ratio, letterboxed inside the (still square, so
|
||||
rotate-90 doesn't shift its footprint) box. */}
|
||||
<Image
|
||||
alt=""
|
||||
src="/icon-arrow-connector.svg"
|
||||
width={24}
|
||||
height={24}
|
||||
className="w-6 h-6 rotate-90 md:w-10 md:h-3 md:rotate-0"
|
||||
className="w-6 h-6 object-contain rotate-90 md:w-10 md:h-3 md:rotate-0"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -12,6 +12,19 @@ const checklist = [
|
||||
"Minimalistisch, analog, effektiv",
|
||||
];
|
||||
|
||||
// Inline, brand-orange stroke — icon-check.svg's fill is hardcoded to
|
||||
// #222221 via an internal CSS var that only resolves inside the SVG's own
|
||||
// document, so it can't be recolored from the host page when loaded via
|
||||
// <img src>/next/image. Same simple checkmark path as Challenge's own
|
||||
// Check() component, for the same orange-checkmark look (fixed 2026-07-24).
|
||||
function IconCheck() {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" className="size-5 shrink-0 mt-0.5">
|
||||
<path d="M4 10.5l4.5 4.5L16 5.5" stroke="#f6a701" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
// Same "todo-karten" product Pricing.tsx reads further down the page —
|
||||
// this is just a compact early teaser so the hero's CTA isn't asking for
|
||||
// a click without saying what it costs. Delivery time is repeated here too
|
||||
@@ -94,8 +107,8 @@ export async function TodoKartenHero() {
|
||||
|
||||
<ul className="flex flex-col gap-3 items-start w-full">
|
||||
{checklist.map((item) => (
|
||||
<li key={item} className="flex gap-[0.625rem] items-center w-full">
|
||||
<Image alt="" src="/icon-check.svg" width={20} height={20} className="size-5 shrink-0" />
|
||||
<li key={item} className="flex gap-[0.625rem] items-start w-full">
|
||||
<IconCheck />
|
||||
<span className="flex-1 text-body text-text-primary">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user