Fix several mobile responsive issues across Hero, Newsletter, cart, and Challenge

- Hero CTA: back to a single line below lg: (wrapping put the icon
  beside two lines and looked broken), sized down instead so the full
  phrase fits.
- Hero heading: dropped the forced <br> after "darf" at true mobile
  widths, kept it only for the sm-lg tablet range it was added for.
- Hero social proof: avatars+text now wrap and center instead of
  cramming onto one row (flex-wrap + justify-center, no fixed
  breakpoint needed).
- Newsletter card icon: stacked above the copy and centered below md:
  instead of squeezed into a row beside it.
- TrustRow: left-aligned below md: instead of centered (shop, cart, and
  every other page using this component).
- Cart line item image: full width below sm: instead of a fixed 150px
  square.
- Challenge EmailCapture (top + bottom CTA, same shared component):
  input and button stack full-width below sm: instead of squeezing
  into one row.
- Challenge steps: icon above text and centered at every breakpoint
  (previously only from lg: up), connector arrows centered to match.
This commit is contained in:
Marco
2026-07-24 18:48:18 +00:00
parent f20a02dfa2
commit 3da8b75395
6 changed files with 64 additions and 31 deletions
+14 -2
View File
@@ -214,8 +214,20 @@ export function CartContent({
<div key={lineKey} className="w-full">
{i > 0 && <div className="h-px bg-border w-full mb-6" />}
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6 items-start sm:items-center w-full">
<div className="relative size-[9.375rem] shrink-0 rounded-sm overflow-hidden">
<Image src={product.image} alt={product.name} fill sizes="150px" className="object-cover" />
{/* Full-width on mobile (stacked layout) instead of the
fixed 150px square — a small square floating above
the text looked cramped on a narrow column that has
the width to spare; fixed 150px square again from
sm: once the row layout kicks in and the image sits
beside the text instead. */}
<div className="relative w-full aspect-square sm:size-[9.375rem] sm:shrink-0 rounded-sm overflow-hidden">
<Image
src={product.image}
alt={product.name}
fill
sizes="(min-width: 640px) 150px, 100vw"
className="object-cover"
/>
{discount !== null && (
<span className="absolute top-2 left-2 rounded-full bg-brand px-2 py-0.5 text-label font-bold text-text-primary">
-{discount}%
+7 -1
View File
@@ -22,7 +22,13 @@ export function EmailCapture({ buttonLabel = "Challenge starten" }: { buttonLabe
return (
<form onSubmit={handleSubmit} className="flex flex-col gap-2 w-full">
<div className="flex gap-3 w-full">
{/* Stacked full-width below sm: — side by side, the button's own
content width plus the input's min-w-0 squeeze left it cramped
on a narrow phone. Default align-items: stretch in flex-col
mode is what makes both the input and the button (shrink-0,
fixed to its label's width) fill the row once stacked, no
explicit w-full needed on either. */}
<div className="flex flex-col sm:flex-row gap-3 w-full">
<input
ref={emailRef}
type="email"
+11 -3
View File
@@ -231,13 +231,21 @@ export default async function ChallengePage() {
<p className="text-[1rem] text-[#666]">Jeden Tag ein Impuls. In nur wenigen Minuten.</p>
</Reveal>
<RevealGroup className="flex flex-col lg:flex-row items-start lg:items-start gap-8 lg:gap-2 w-full">
{/* items-center below lg: (was items-start) — the step blocks
are centered columns now (see RevealItem below), so the
connector arrows between them need to be centered too,
not flush against the left edge. */}
<RevealGroup className="flex flex-col lg:flex-row items-center lg:items-start gap-8 lg:gap-2 w-full">
{steps.flatMap((step, i) => [
<RevealItem key={step.title} className="group flex lg:flex-col items-start lg:items-center gap-4 lg:gap-5 flex-1 min-w-0">
// Icon-above-text, centered, at every breakpoint now
// (previously a left-aligned icon+text row below lg: —
// fixed 2026-07-24 to match the lg: layout instead of
// diverging from it).
<RevealItem key={step.title} className="group flex flex-col items-center gap-4 lg:gap-5 flex-1 min-w-0">
<div className="flex items-center justify-center w-16 h-14 shrink-0 transition-transform duration-300 group-hover:scale-110">
{step.icon}
</div>
<div className="flex flex-col gap-1 lg:text-center">
<div className="flex flex-col gap-1 text-center">
<p className="font-semibold text-[#222221] text-[1rem]">{step.title}</p>
<p className="text-[0.875rem] text-[#666] leading-[1.5]">{step.desc}</p>
</div>
+23 -22
View File
@@ -22,19 +22,19 @@ export function Hero() {
viewport — this doubles as the page's entrance animation. */}
<Reveal className="order-1 lg:order-none lg:col-span-5 flex flex-col gap-7 items-start pl-[var(--layout-padding-x)] pr-10 lg:pr-0">
{/* Heading — forced break after "darf" below lg: (1024px),
natural wrap from lg: up. Below lg: the Hero is stacked
full-width and narrower per-viewport, where natural wrap
produced an awkward break — force it after "darf" there via
a responsive <br/> (visible by default, turned off at lg:+).
From lg: up the 5/12 grid's text column wraps fine on its
own, no forced break needed. */}
{/* Heading — forced break after "darf" only in the sm-lg tablet
range (Hero stacked full-width, natural wrap there landed
awkwardly). Removed at true mobile widths (below sm:) 2026-07-24
— narrower still, natural wrap there reads fine, and the
forced break made "darf" the whole first line, leaving very
little text above the CTA. Natural wrap from lg: up too, once
the 5/12 grid gives the column real width again. */}
<p
className="font-semibold leading-[0] shrink-0 text-[0px] text-text-primary"
style={{ fontFamily: "var(--font-playfair)" }}
>
<span className="text-display">
Produktivität darf<br className="lg:hidden" /> sich leicht anfühlen
Produktivität darf<br className="hidden sm:inline lg:hidden" /> sich leicht anfühlen
</span>
{/* Brand's signature orange dot (also in the logo/footer) —
bouncy pop-in once the heading scrolls into view, timed to
@@ -57,17 +57,14 @@ export function Hero() {
href="/challenge"
className="flex gap-4 items-center justify-center overflow-clip px-6 py-3 rounded-sm shrink-0 max-w-full bg-brand hover:brightness-95 active:scale-[0.97] transition-all"
>
{/* whitespace-nowrap only from lg: up, where the 5-col text
column has room for the full phrase on one line. Below
lg: (stacked, narrower column) nowrap forced this button
wider than the column itself, overflowing on mobile —
fixed 2026-07-24 by letting it wrap to two lines instead
(leading-[2.375rem] was already sized for that). max-w-full
on the Link above is what actually makes the wrap happen —
without it, a flex item with items-start siblings just
grows to fit the unwrapped text instead of respecting the
column width. */}
<span className="font-semibold leading-[2.375rem] text-text-primary text-h3 lg:whitespace-nowrap not-italic">
{/* Letting this wrap to two lines below lg: (tried 2026-07-24)
put the icon beside a two-line text block, which read as
broken rather than intentional. Smaller fixed size below
lg: instead, so the full phrase fits on one line within
the stacked mobile column's width — text-h3's own 19px
floor was still too wide for that at ~236px of available
text budget on a 375px phone. */}
<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">
@@ -75,8 +72,12 @@ export function Hero() {
</div>
</Link>
{/* Social proof */}
<div className="flex gap-3 items-start overflow-clip shrink-0 w-full">
{/* 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">
{/* 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) => (
@@ -89,7 +90,7 @@ export function Hero() {
</div>
))}
</div>
<p className="flex-[1_0_0] font-normal leading-[1.5rem] text-text-primary text-body [word-break:break-word]">
<p className="flex-[1_0_0] font-normal leading-[1.5rem] text-text-primary text-body text-center [word-break:break-word]">
10.000+ Menschen vertrauen <span className="whitespace-nowrap">einfach-produktiv</span>
</p>
</div>
+8 -2
View File
@@ -46,8 +46,14 @@ export function Newsletter({
{/* Rounded card: cream bg, stacks below md */}
<Reveal className="bg-bg-muted flex flex-col md:flex-row gap-8 md:gap-12 items-center px-8 py-8 md:py-0 rounded-md w-full">
{/* Left: copy — fixed width from md+ so the form always gets the remaining space */}
<div className="flex gap-8 items-start w-full md:w-[var(--newsletter-copy-width)] md:py-4 md:shrink-0">
{/* Left: copy — fixed width from md+ so the form always gets the remaining space.
Icon+text stacked (icon on top, centered) below md: — side by
side they squeezed the text into a ~164px column on a 375px
phone (icon width + gap eating most of the card's inner
width), wrapping awkwardly. Row layout with the icon beside
the text is fine again from md+, where the fixed copy-column
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 */}
<div className="flex items-center justify-center shrink-0 w-[4.23rem] h-[3.71rem]">
+1 -1
View File
@@ -11,7 +11,7 @@ export async function TrustRow() {
if (items.length === 0) return null;
return (
<div className="w-full bg-bg-base flex flex-col md:flex-row gap-6 md:gap-12 items-center justify-center py-8 px-[var(--layout-padding-x)]">
<div className="w-full bg-bg-base flex flex-col md:flex-row gap-6 md:gap-12 items-start md:items-center justify-center py-8 px-[var(--layout-padding-x)]">
{items.map((item, i) => (
<div key={item.id} className="flex items-center gap-6 md:gap-12">
{i > 0 && <div className="hidden md:block h-10 w-px bg-border" />}