From cc935420ec20cdf3891029afe8abfd0db417914e Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 29 Jul 2026 10:49:56 +0000 Subject: [PATCH] Move structural breakpoint to 640px, shorten Hero heading Tablet layout (768-1023px) hit the md: grid switch exactly where the fluid clamp() tokens were already at their floor, leaving no room to shrink. Moves the fluid floor and structural breakpoint down together to sm: (640px) so real tablets always get the fluid, desktop-like structure; consolidates the ad-hoc md:+lg: patchwork in Hero/About/ Newsletter/Footer/Tools back onto one line, leaving documented lg: exceptions where content genuinely doesn't fit yet. Also shortens the Hero heading to a single sentence with no trailing period (the brand's orange dot already renders one, animated). --- README.md | 50 ++++++++ .../components/BestellbestaetigungContent.tsx | 14 ++- app/blog/page.tsx | 18 +-- app/cart/components/CartContent.tsx | 7 +- app/cart/components/RelatedProducts.tsx | 10 +- app/components/About.tsx | 54 +++++---- app/components/Blog.tsx | 26 ++--- app/components/Footer.tsx | 9 +- app/components/Hero.tsx | 80 +++++++------ app/components/Navbar.tsx | 12 +- app/components/Newsletter.tsx | 42 ++++--- app/components/NewsletterModal.tsx | 14 +-- app/components/ProductSpotlight.tsx | 8 +- app/components/SectionTOC.tsx | 5 +- app/components/TestimonialsGrid.tsx | 6 +- app/components/Tools.tsx | 24 ++-- app/components/TrustRow.tsx | 6 +- app/globals.css | 110 +++++++++--------- app/lib/fluid.ts | 2 +- app/newsletter/components/HowItWorks.tsx | 18 +-- .../components/WeeklyImpulsesHero.tsx | 7 +- app/not-found.tsx | 12 +- app/shop/components/ProductGrid.tsx | 6 +- app/todo-cards/components/HowItWorks.tsx | 16 +-- app/todo-cards/components/TodoKartenHero.tsx | 3 +- 25 files changed, 327 insertions(+), 232 deletions(-) diff --git a/README.md b/README.md index b442787..e32ccc8 100644 --- a/README.md +++ b/README.md @@ -1855,6 +1855,56 @@ Verified locally by curling each page and grepping the rendered Google's Rich Results Test (no live Stripe-style external validation step for this), so worth a manual check there once deployed. +## Structural breakpoint moved to 640px (2026-07-29) + +The Tablet layout (768-1023px) was still not landing right after the +2026-07-24 fixes above: real tablets (768px+) hit the structural `md:` +grid switch at the exact viewport width where the fluid `clamp()` tokens +(`app/lib/fluid.ts`) were already at their smallest (floor) value, leaving +no room to shrink — cramped images/text in 2-column sections. Fixed by +moving both the fluid floor and the structural switch down together, from +768px to Tailwind's `sm:` (640px): + +- **`app/lib/fluid.ts`**: `MIN_VW` 768 → 640. All `clamp()` values in + `app/globals.css`'s `@theme`/`:root` blocks are hand-authored (no build + step calls `fluid()`), so they were individually regenerated for the new + 640→1440 range, not just re-anchored by editing the constant. +- **Hero/About/Newsletter/Footer/Tools** — the `md:`+`lg:` two-tier + patchwork from the 2026-07-24 fixes above collapsed back onto a single + `sm:` structural line (grid/flex switches now happen at 640px, aligned + with the new fluid floor). Content-sizing exceptions that still don't + fit at 640px (Hero/Tools' fixed-size CTA/text/icon swaps, Newsletter's + input+button row, About's inner quote/bio row) stay gated behind `lg:`, + documented in each file as a deliberate exception, not a leftover. +- Every other component with a plain `md:` structural switch (TrustRow, + Blog, TestimonialsGrid, ProductSpotlight, ProductGrid, RelatedProducts, + NewsletterModal, `/blog`, `/not-found`, both `HowItWorks` components) + renamed mechanically to `sm:`. +- Components with a genuine fixed-width constraint (Cart/Checkout's + two-column split, `SectionTOC.tsx` + the 5 legal pages, the newsletter/ + todo-cards hero/benefits sections, `/challenge`) stay on `lg:` — the + math still doesn't fit at 640px either, only their code comments' + "768px floor" language was updated. +- **`Navbar.tsx`** is a deliberate, documented exception to the whole + migration — its `md:`/`lg:` 3-tier scheme (hamburger-only, hamburger+ + inline-CTAs, full-inline) is about horizontal nav-link overflow, a + different failure mode than the vertical grid/flex reflows everywhere + else, with no fluid token that would make a full inline nav fit at + 640px. + +No browser/screenshot tool is available in this environment, so this +migration is verified by typecheck/build/lint/`test:unit` only — actual +visual confirmation at 640/768/1024/1440px is still owed by the user. + +Same session: **`Hero.tsx`'s heading** shortened from two sentences +("Verliere dich nicht im Mehr. Finde heraus, was wichtig ist.") to one +("Finde heraus, was wichtig ist") with no trailing period, since the +brand's orange dot (`PopIn`) already renders one, animated, right after +it. The homepage's **"3x3-System" werkzeuge-card** description was also +lengthened to match the other two cards' length (see the Payload repo's +own README for the `fix-3x3-copy-length.ts` one-off that changed the +live content). + ## Tests `npm run test:unit` (Vitest, `node` environment, no jsdom/Next.js runtime diff --git a/app/bestellbestaetigung/components/BestellbestaetigungContent.tsx b/app/bestellbestaetigung/components/BestellbestaetigungContent.tsx index ab82171..b88c21b 100644 --- a/app/bestellbestaetigung/components/BestellbestaetigungContent.tsx +++ b/app/bestellbestaetigung/components/BestellbestaetigungContent.tsx @@ -183,6 +183,12 @@ export function BestellbestaetigungContent({ defaultTaxRate }: { defaultTaxRate: {!productsLoading && ( + // Outer card+sidebar split stays lg: (fixed 18rem delivery-status + // sidebar, L297 below — same fixed-width-block category as Tier C + // exceptions elsewhere), while the inner order-meta/items row + // already switches at sm: — intentional, not an inconsistent + // leftover: the inner row has no fixed-width sidebar to fight for + // room against, just two flexible columns. -
+ +
{/* -inset-1, not inset-0 — this section fades in via Reveal's y:28→0 transform; a plain inset-0 image can leave a hairline gap at the top edge while that's still settling @@ -343,10 +349,10 @@ export function BestellbestaetigungContent({ defaultTaxRate }: { defaultTaxRate: src="/bestellbestaetigung-testimonial-photo.jpg" width={366} height={126} - sizes="(min-width: 768px) 45vw, 100vw" + sizes="(min-width: 640px) 45vw, 100vw" className="absolute -inset-1 w-[calc(100%+0.5rem)] h-[calc(100%+0.5rem)] object-cover" /> -
+

-

+ +

-
- -
+
+ +
@@ -56,14 +56,14 @@ export default async function BlogOverviewPage() { -
+
{featured.thumbnail && ( - + )}
-
+
{featured.category} diff --git a/app/cart/components/CartContent.tsx b/app/cart/components/CartContent.tsx index 1b6c28a..a82ea0b 100644 --- a/app/cart/components/CartContent.tsx +++ b/app/cart/components/CartContent.tsx @@ -176,9 +176,10 @@ export function CartContent({ {/* Cart card — lg:-only split from the sidebar (same "wide content next to sidebar" shape as the Hero's image/text split, see figma-to-nextjs skill Gotcha #5: Figma's 830px card alone - already exceeds the 768px Tablet floor, so md: would never - have had room for a real 2-column layout at Tablet widths - anyway). */} + already exceeds even the site's 640px structural floor, so + sm: would never have had room for a real 2-column layout at + Tablet widths anyway — a deliberate exception to the site-wide + sm: consolidation, not a leftover of it). */} {items.map(({ entry, product }, i) => { const discount = discountPercent(product.price, product.compareAtPrice); diff --git a/app/cart/components/RelatedProducts.tsx b/app/cart/components/RelatedProducts.tsx index afa5a17..cd8bac0 100644 --- a/app/cart/components/RelatedProducts.tsx +++ b/app/cart/components/RelatedProducts.tsx @@ -123,7 +123,7 @@ export function RelatedProducts({ defaultTaxRate, kleinunternehmer }: { defaultT (opacity: 0) — invisible. Not worth chasing a fix for a scroll-reveal nicety on a list that mutates; a static grid renders correctly with no animation risk. */} -
+
{displayProducts.map((product, i) => { const discount = discountPercent(product.price, product.compareAtPrice); const taxRate = effectiveTaxRate(product, defaultTaxRate); @@ -134,7 +134,7 @@ export function RelatedProducts({ defaultTaxRate, kleinunternehmer }: { defaultT
{/* Same top-left pill pattern as ProductGrid.tsx/ diff --git a/app/components/About.tsx b/app/components/About.tsx index bcbbed0..a1f76bd 100644 --- a/app/components/About.tsx +++ b/app/components/About.tsx @@ -3,19 +3,20 @@ import { Reveal } from "./Reveal"; export function About() { return ( -
+
{/* Text content — relative + z-10 so it renders above the overlapping - photo at lg+. Comes first in DOM at every breakpoint (no reorder - here — unlike Hero, there's no conversion CTA at stake). - md:flex-[1.4_0_0] lg:flex-[1_0_0] — at Tablet the text column got - the narrower 1:1.4 share meant for Desktop's overlap layout, - leaving it too cramped for the fixed-width statement + quote/bio - row. Widened at Tablet (text gets the bigger share, image the - smaller one, no overlap yet) and reverted to the original ratio - from lg: up, where the overlap trick actually needs the image to - have more room. */} - + photo from sm+. Comes first in DOM at every breakpoint (no + reorder here — unlike Hero, there's no conversion CTA at stake). + Used to be md:flex-[1.4_0_0] lg:flex-[1_0_0]: a two-step ratio + where Tablet got a wider, non-overlap-ready share and only + reverted to the real 1:1.4 Desktop ratio at lg: — that in-between + step existed purely to compensate for the grid arriving (at the + old md: 768px) before the fluid tokens had room to shrink. + Now the grid itself starts at sm: (640px), aligned with the + fluid floor, so the single real ratio applies from sm: up + directly — no intermediate step needed. */} + {/* Large serif statement — width-constrained as per design */}

{/* Quote row: script quote / divider / author bio — side-by-side - from lg: (was md:) — even with the text column's wider Tablet - share above, quote + divider + the whitespace-nowrap bio ("Gründer - von einfach-produktiv.") together still needed more room than - Tablet's ~384px column has. Stacked with a horizontal divider - through the whole Tablet range instead, side-by-side (vertical + only from lg: (1024px), a deliberate exception to the site's + sm: (640px) structural consolidation: quote + divider + the + whitespace-nowrap bio ("Gründer von einfach-produktiv.") + together need more room than the sm:-anchored text column has + through the whole 640-1023px range, independent of the outer + text/image ratio above. Stacked with a horizontal divider + through that whole range instead, side-by-side (vertical divider) only once there's real room at lg:. */}

@@ -80,13 +83,14 @@ export function About() {
- {/* Author photo — overlaps the text column via -ml-48 from lg+ only - (that overlap trick has nothing to blend into once stacked, and - at Tablet it would eat back into the extra width the text column - above just gained); plain full-width photo below the text on - Mobile, plain side-by-side (no overlap) at Tablet. */} + {/* Author photo — overlaps the text column via -ml-48 from sm+ now + (moved down together with the ratio collapse above — the overlap + trick and the 1:1.4 share are the same Desktop-style treatment, + previously gated behind the extra lg: step for the same reason + the ratio was, so both move together); plain full-width photo + below the text on true Mobile only. */} @@ -94,11 +98,11 @@ export function About() { alt="Björn" src="/about-author.jpg" fill - sizes="(min-width: 1024px) 58vw, (min-width: 768px) 42vw, 100vw" + sizes="(min-width: 640px) 58vw, 100vw" className="object-cover object-center pointer-events-none" /> - {/* Left gradient: wide enough to cover the text-column overlap — lg+ only */} -
+ {/* Left gradient: wide enough to cover the text-column overlap — moved to sm+ with the overlap itself */} +
diff --git a/app/components/Blog.tsx b/app/components/Blog.tsx index 2bc14e6..99bff0d 100644 --- a/app/components/Blog.tsx +++ b/app/components/Blog.tsx @@ -31,20 +31,20 @@ export async function Blog() { {/* Posts grid */} - {/* Featured post — image on top on Mobile, side-by-side from md+ */} - -
+ {/* Featured post — image on top on Mobile, side-by-side from sm+ */} + +
{featured.thumbnail && ( )}
-
+
{featured.category} @@ -58,7 +58,7 @@ export async function Blog() { > {featured.title}

-

+

{featured.excerpt}

@@ -75,25 +75,25 @@ export async function Blog() {
- {/* Secondary posts — image on top on Mobile, side-by-side from md+ */} -
+ {/* Secondary posts — image on top on Mobile, side-by-side from sm+ */} +
{secondary.map((post) => ( -
+
{post.thumbnail && ( )}
-
+
{post.category} @@ -107,7 +107,7 @@ export async function Blog() { > {post.title}

-

+

{post.excerpt}

diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index 3dee45e..e2c5a33 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -19,11 +19,10 @@ export function Footer() {
{/* Three groups: logo | @handle | links — stacked + centered below - lg: (was md:). Logo + handle + 5 legal links all side by side - with justify-between read too cramped on Tablet — stacked - through that range instead, side by side again once there's - real room at lg:. */} -
+ sm: (640px, moved down from the old md:/lg: skip-pattern now + that grid/flex structure across the site aligns with the fluid + floor). Side by side again once there's real room at sm:. */} +
{/* Logo: "einfach produktiv" white + "." gold */}
diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx index 497e9de..88a6e03 100644 --- a/app/components/Hero.tsx +++ b/app/components/Hero.tsx @@ -11,7 +11,7 @@ function HeroImage() { alt="" fill priority - sizes="(min-width: 768px) 58vw, 100vw" + sizes="(min-width: 640px) 58vw, 100vw" className="object-cover" style={{ WebkitMaskImage: @@ -28,44 +28,52 @@ function HeroImage() { export function Hero() { return (
- {/* Structural breakpoint is md: (768px) for the GRID only — the text - column stays ~283-320px wide through the whole 768-1023px Tablet + {/* Structural breakpoint is sm: (640px, moved down from the old md: + 768px so the grid arrives exactly where the fluid token floor + also sits — see fluid.ts/globals.css) for the GRID only — the + text column stays narrow through the whole 640-1023px Tablet range regardless. Below, every piece of *content* inside the text - column (heading/subtitle/CTA/social-proof) keeps its smaller, - fixed-below-lg: sizing all the way through Tablet too, not just - true Mobile — reusing the full fluid-token sizes at md: (as a - first pass 2026-07-24 briefly did) put the original ~19-44px - fluid floors right back in that narrow column, recreating the - exact 3-line-wrap problem the old `lg:` structural exception - existed to avoid. Splitting "grid at md:" from "full-size content - at lg:" gets both: Tablet shows the real 5/7 grid, but with - content sized for its column's actual width, not the column - width `lg:` was designed for. */} -
+ column (heading/subtitle/CTA/social-proof) deliberately keeps its + smaller, fixed-below-lg: sizing all the way through Tablet too, + not just true Mobile — reusing the full fluid-token sizes that + early put the original ~19-44px fluid floors right back in that + narrow column, recreating the exact 3-line-wrap problem the old + `lg:` structural exception existed to avoid. This is a narrower, + intentional exception to the site-wide sm: consolidation — the + column's real width at sm: hasn't been visually verified yet + (no browser in this environment), so full-size content stays + gated behind lg: as a fast-follow rather than a guess. Splitting + "grid at sm:" from "full-size content at lg:" gets both: Tablet + shows the real 5/7 grid, but with content sized for its column's + actual width, not the column width `lg:` was designed for. */} +
{/* Text content — first in DOM/visual order at every breakpoint so the CTA stays above the fold on Mobile (deliberate exception to the "keep DOM order" default, see Hero decision in the plan). Reveal fires ~immediately since Hero is already in the initial viewport — this doubles as the page's entrance animation. */} - + {/* Heading — smaller fixed-ish size below lg: (text-h1, still a real paired font-size+line-height token, not an arbitrary value) — text-display's own 44px floor wraps very heavily in a ~283-320px Tablet column (even a single word can approach - that width). Forced break after "darf" only in the sm-md - tablet range (natural wrap there landed awkwardly); removed - at true mobile widths (below sm:) 2026-07-24 — narrower - still, natural wrap reads fine there, and the forced break - made "darf" the whole first line. Full text-display only - from lg: up, where the column has real room again. */} + that width). The old forced break after "darf" only existed + for a "wide single-column, not yet grid" band (640-767px + under the old md: 768px grid switch) that no longer exists + now the grid itself starts at sm: (640px) — below sm: the + stack is narrower than that old band ever was, where natural + wrap already reads fine (confirmed 2026-07-24), so the break + is removed rather than re-anchored to a new range. Full + text-display only from lg: up, where the column has real + room again. */}

- Verliere dich nicht
im mehr. Finde heraus, was wichtig ist. + Finde heraus, was wichtig ist
{/* Brand's signature orange dot (also in the logo/footer) — bouncy pop-in once the heading scrolls into view, timed to @@ -133,29 +141,29 @@ export function Hero() { {/* Image — bleeds to the true edge at every breakpoint (never - padded). Below md: (stacked layout) the full 887:583 aspect - ratio at 100vw would make the image ~600-900px tall and - dominate the page, so height is capped and object-cover crops - it into a supporting banner instead; at md:+ (grid, image only - 58% width) the full aspect ratio looks right again, so the cap - is lifted. No shadow: a 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 md: — whileInView's -80px + padded). Below sm: (stacked layout, moved down from the old + md: 768px) the full 887:583 aspect ratio at 100vw would make + the image ~600-900px tall and dominate the page, so height is + capped and object-cover crops it into a supporting banner + instead; at sm:+ (grid, image only 58% width) the full aspect + ratio looks right again, so the cap is lifted. No shadow: a + 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 sm: — 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 md: instead; Reveal's fade - kept from md: up, where the image is beside the text with + Plain, always-visible image below sm: instead; Reveal's fade + kept from sm: up, where the image is beside the text with plenty of room and this was never an issue. */} -

+
diff --git a/app/components/Navbar.tsx b/app/components/Navbar.tsx index 6a7d41d..dcbba1a 100644 --- a/app/components/Navbar.tsx +++ b/app/components/Navbar.tsx @@ -428,7 +428,17 @@ export function Navbar({ singleActiveProduct }: { singleActiveProduct: boolean } Figma's Navbar-Mobile component set exactly, see the plan. Matches the Figma NavLink component's hover: text never changes color, an underline (brand, 2px x 40px) fades in on - hover and stays on for the active section. */} + hover and stays on for the active section. + + Deliberate exception to the site-wide sm: (640px) structural + consolidation (see the 640px-breakpoint plan): this md:/lg: + 3-tier scheme (hamburger-only <768, hamburger+inline-CTAs + 768-1023, full-inline ≥1024) is untouched by that migration. + Horizontal nav-link overflow is a different failure mode than + the vertical grid/flex reflows the rest of the site has — + there's no fluid token that shrinks link text to make a full + inline nav fit at 640px, and nothing here was ever tied to + the fluid token scale the way Hero/About/etc. were. */}