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).
This commit is contained in:
+29
-25
@@ -3,19 +3,20 @@ import { Reveal } from "./Reveal";
|
||||
|
||||
export function About() {
|
||||
return (
|
||||
<section id="ueber-bjoern" className="bg-bg-dark flex flex-col md:flex-row md:items-stretch w-full">
|
||||
<section id="ueber-bjoern" className="bg-bg-dark flex flex-col sm:flex-row sm:items-stretch w-full">
|
||||
|
||||
{/* 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. */}
|
||||
<Reveal className="flex flex-col gap-4 justify-center px-[var(--layout-padding-x)] py-8 md:flex-[1.4_0_0] lg:flex-[1_0_0] min-w-0 relative z-10">
|
||||
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. */}
|
||||
<Reveal className="flex flex-col gap-4 justify-center px-[var(--layout-padding-x)] py-8 sm:flex-[1_0_0] min-w-0 relative z-10">
|
||||
|
||||
{/* Large serif statement — width-constrained as per design */}
|
||||
<p
|
||||
@@ -26,11 +27,13 @@ export function About() {
|
||||
</p>
|
||||
|
||||
{/* 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:. */}
|
||||
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-6 lg:gap-0 w-full">
|
||||
|
||||
@@ -80,13 +83,14 @@ export function About() {
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
{/* 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. */}
|
||||
<Reveal
|
||||
className="relative overflow-hidden w-full md:flex-[1_0_0] lg:flex-[1.4_0_0] lg:-ml-48"
|
||||
className="relative overflow-hidden w-full sm:flex-[1.4_0_0] sm:-ml-48"
|
||||
style={{ minHeight: "14rem" }}
|
||||
delay={0.15}
|
||||
>
|
||||
@@ -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 */}
|
||||
<div className="hidden lg:block absolute inset-y-0 left-0 w-72 bg-gradient-to-r from-bg-dark to-transparent pointer-events-none" />
|
||||
{/* Left gradient: wide enough to cover the text-column overlap — moved to sm+ with the overlap itself */}
|
||||
<div className="hidden sm:block absolute inset-y-0 left-0 w-72 bg-gradient-to-r from-bg-dark to-transparent pointer-events-none" />
|
||||
</Reveal>
|
||||
|
||||
</section>
|
||||
|
||||
+13
-13
@@ -31,20 +31,20 @@ export async function Blog() {
|
||||
{/* Posts grid */}
|
||||
<RevealGroup className="flex flex-col gap-10 items-start px-[var(--layout-padding-x)] w-full">
|
||||
|
||||
{/* Featured post — image on top on Mobile, side-by-side from md+ */}
|
||||
<RevealItem className="w-full border border-border rounded-xl overflow-hidden grid grid-cols-1 md:grid-cols-12 transition-transform duration-300 hover:-translate-y-1">
|
||||
<div className="relative w-full aspect-video md:aspect-auto md:col-span-7 md:h-[220px] bg-bg-muted">
|
||||
{/* Featured post — image on top on Mobile, side-by-side from sm+ */}
|
||||
<RevealItem className="w-full border border-border rounded-xl overflow-hidden grid grid-cols-1 sm:grid-cols-12 transition-transform duration-300 hover:-translate-y-1">
|
||||
<div className="relative w-full aspect-video sm:aspect-auto sm:col-span-7 sm:h-[220px] bg-bg-muted">
|
||||
{featured.thumbnail && (
|
||||
<Image
|
||||
alt=""
|
||||
src={featured.thumbnail}
|
||||
fill
|
||||
sizes="(min-width: 768px) 58vw, 100vw"
|
||||
sizes="(min-width: 640px) 58vw, 100vw"
|
||||
className="object-cover pointer-events-none"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col justify-between gap-4 py-4 px-4 md:pr-4 md:pl-4 md:col-span-5 min-w-0">
|
||||
<div className="flex flex-col justify-between gap-4 py-4 px-4 sm:pr-4 sm:pl-4 sm:col-span-5 min-w-0">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex gap-2 items-center font-semibold text-text-muted text-body uppercase whitespace-nowrap">
|
||||
<span>{featured.category}</span>
|
||||
@@ -58,7 +58,7 @@ export async function Blog() {
|
||||
>
|
||||
{featured.title}
|
||||
</p>
|
||||
<p className="font-normal text-body leading-6 line-clamp-2 md:line-clamp-none">
|
||||
<p className="font-normal text-body leading-6 line-clamp-2 sm:line-clamp-none">
|
||||
{featured.excerpt}
|
||||
</p>
|
||||
</div>
|
||||
@@ -75,25 +75,25 @@ export async function Blog() {
|
||||
</div>
|
||||
</RevealItem>
|
||||
|
||||
{/* Secondary posts — image on top on Mobile, side-by-side from md+ */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full">
|
||||
{/* Secondary posts — image on top on Mobile, side-by-side from sm+ */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 w-full">
|
||||
{secondary.map((post) => (
|
||||
<RevealItem
|
||||
key={post.id}
|
||||
className="border border-border rounded-xl overflow-hidden grid grid-cols-1 md:grid-cols-5 transition-transform duration-300 hover:-translate-y-1"
|
||||
className="border border-border rounded-xl overflow-hidden grid grid-cols-1 sm:grid-cols-5 transition-transform duration-300 hover:-translate-y-1"
|
||||
>
|
||||
<div className="relative w-full aspect-video md:aspect-auto md:col-span-2 md:h-[230px] bg-bg-muted">
|
||||
<div className="relative w-full aspect-video sm:aspect-auto sm:col-span-2 sm:h-[230px] bg-bg-muted">
|
||||
{post.thumbnail && (
|
||||
<Image
|
||||
alt=""
|
||||
src={post.thumbnail}
|
||||
fill
|
||||
sizes="(min-width: 768px) 29vw, 100vw"
|
||||
sizes="(min-width: 640px) 29vw, 100vw"
|
||||
className="object-cover pointer-events-none"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col justify-between gap-4 py-3 px-4 md:col-span-3 min-w-0 text-text-primary">
|
||||
<div className="flex flex-col justify-between gap-4 py-3 px-4 sm:col-span-3 min-w-0 text-text-primary">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex gap-2 items-center font-semibold text-text-muted text-body uppercase whitespace-nowrap">
|
||||
<span>{post.category}</span>
|
||||
@@ -107,7 +107,7 @@ export async function Blog() {
|
||||
>
|
||||
{post.title}
|
||||
</p>
|
||||
<p className="font-normal text-body leading-6 line-clamp-1 md:line-clamp-none">
|
||||
<p className="font-normal text-body leading-6 line-clamp-1 sm:line-clamp-none">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -19,11 +19,10 @@ export function Footer() {
|
||||
<div className="flex flex-col items-center w-full max-w-[1280px] py-8 md:py-4">
|
||||
|
||||
{/* 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:. */}
|
||||
<div className="flex flex-col lg:flex-row items-center lg:justify-between gap-6 lg:gap-0 px-8 md:px-16 w-full">
|
||||
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:. */}
|
||||
<div className="flex flex-col sm:flex-row items-center sm:justify-between gap-6 sm:gap-0 px-8 md:px-16 w-full">
|
||||
|
||||
{/* Logo: "einfach produktiv" white + "." gold */}
|
||||
<div className="flex items-center p-2 shrink-0">
|
||||
|
||||
+44
-36
@@ -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 (
|
||||
<section className="bg-bg-base w-full overflow-hidden">
|
||||
{/* 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. */}
|
||||
<div className="flex flex-col md:grid md:grid-cols-12 md:items-center gap-8 md:gap-[var(--layout-grid-gap)] pt-10 md:pt-0">
|
||||
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. */}
|
||||
<div className="flex flex-col sm:grid sm:grid-cols-12 sm:items-center gap-8 sm:gap-[var(--layout-grid-gap)] pt-10 sm:pt-0">
|
||||
|
||||
{/* 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. */}
|
||||
<Reveal className="order-1 md:order-none md:col-span-5 flex flex-col gap-7 items-start pl-[var(--layout-padding-x)] pr-10 md:pr-0">
|
||||
<Reveal className="order-1 sm:order-none sm:col-span-5 flex flex-col gap-7 items-start pl-[var(--layout-padding-x)] pr-10 sm:pr-0">
|
||||
|
||||
{/* 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. */}
|
||||
<p
|
||||
className="font-semibold leading-[0] shrink-0 text-[0px] text-text-primary"
|
||||
style={{ fontFamily: "var(--font-playfair)" }}
|
||||
>
|
||||
<span className="text-h1 lg:text-display">
|
||||
Verliere dich nicht<br className="hidden sm:inline md:hidden" /> im mehr. Finde heraus, was wichtig ist.
|
||||
Finde heraus, was wichtig ist
|
||||
</span>
|
||||
{/* 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() {
|
||||
</Reveal>
|
||||
|
||||
{/* 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. */}
|
||||
<div className="order-2 md:hidden relative w-full aspect-[887/583] max-h-[16rem]">
|
||||
<div className="order-2 sm:hidden relative w-full aspect-[887/583] max-h-[16rem]">
|
||||
<HeroImage />
|
||||
</div>
|
||||
<Reveal
|
||||
className="hidden md:block md:col-span-7 relative w-full aspect-[887/583]"
|
||||
className="hidden sm:block sm:col-span-7 relative w-full aspect-[887/583]"
|
||||
delay={0.15}
|
||||
>
|
||||
<HeroImage />
|
||||
|
||||
@@ -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. */}
|
||||
<nav className="hidden lg:flex items-center gap-12">
|
||||
{navLinks.map((link) => {
|
||||
const isActive = isNavLinkActive(link.href, pathname, activeSection);
|
||||
|
||||
@@ -43,17 +43,17 @@ export function Newsletter({
|
||||
{/* Outer section padding — same fluid horizontal padding as all other sections */}
|
||||
<div className="px-[var(--layout-padding-x)] w-full">
|
||||
|
||||
{/* 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">
|
||||
{/* Rounded card: cream bg, stacks below sm (moved down from the old md:) */}
|
||||
<Reveal className="bg-bg-muted flex flex-col sm:flex-row gap-8 sm:gap-12 items-center px-8 py-8 sm:py-0 rounded-md w-full">
|
||||
|
||||
{/* 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">
|
||||
{/* Left: copy — fixed width from sm+ so the form always gets the remaining space.
|
||||
Icon+text stacked (icon on top, centered) below sm: — side by
|
||||
side they squeezed the text into a narrow column on a 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 sm+, where the fixed copy-column width
|
||||
leaves real room. */}
|
||||
<div className="flex flex-col items-center gap-4 text-center w-full sm:flex-row sm:items-start sm:gap-8 sm:text-left sm:w-[var(--newsletter-copy-width)] sm:py-4 sm:shrink-0">
|
||||
|
||||
{/* Decorative envelope icon, tilted -4° as per design.
|
||||
w-[4rem], not w-16 — this project's --spacing-16 is a
|
||||
@@ -89,20 +89,24 @@ export function Newsletter({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: form — takes remaining space, centered vertically from md+ */}
|
||||
<div className="flex w-full md:flex-1 items-center md:self-stretch min-w-0">
|
||||
{/* Right: form — takes remaining space, centered vertically from sm+ */}
|
||||
<div className="flex w-full sm:flex-1 items-center sm:self-stretch min-w-0">
|
||||
{status === "success" ? (
|
||||
<p className="text-body text-text-primary font-medium">{successMessage}</p>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="flex flex-1 flex-col gap-4 min-w-0 w-full">
|
||||
|
||||
{/* Input + submit button — stacked below lg: (was md:).
|
||||
The card above already goes side-by-side at md: with a
|
||||
fixed-width copy column (--newsletter-copy-width), which
|
||||
only leaves ~200px for this form column at 768px — not
|
||||
enough room for input+button side by side. Stacked
|
||||
through the whole Tablet range instead, side by side
|
||||
again once the form column has real room at lg:. */}
|
||||
{/* Input + submit button — stacked below lg:, a deliberate
|
||||
exception to the site's sm: (640px) structural
|
||||
consolidation, not a leftover of it. The card above
|
||||
already goes side-by-side at sm: with a fixed-width
|
||||
copy column (--newsletter-copy-width), which only
|
||||
leaves ~128px for this form column at 640px and
|
||||
~197px at 768px — not enough room for input+button
|
||||
side by side even at the new, lower floor. Stacked
|
||||
through the whole 640-1023px range instead, side by
|
||||
side again once the form column has real room (≈333px)
|
||||
at lg:. */}
|
||||
<div className="flex flex-col lg:flex-row gap-4 items-stretch w-full">
|
||||
<input
|
||||
ref={emailRef}
|
||||
|
||||
@@ -156,14 +156,14 @@ export function NewsletterModal({ open, onClose }: { open: boolean; onClose: ()
|
||||
<Image alt="" src="/icon-close.png" width={24} height={24} className="size-full object-contain" />
|
||||
</button>
|
||||
|
||||
{/* modal-top: photo + copy/form, stacked below md */}
|
||||
<div className="flex flex-col md:flex-row items-stretch border-b border-border">
|
||||
<div className="relative w-full md:flex-1 aspect-[4/3] md:aspect-auto">
|
||||
{/* modal-top: photo + copy/form, stacked below sm (moved down from the old md:) */}
|
||||
<div className="flex flex-col sm:flex-row items-stretch border-b border-border">
|
||||
<div className="relative w-full sm:flex-1 aspect-[4/3] sm:aspect-auto">
|
||||
<Image
|
||||
src="/newsletter-modal-photo.jpg"
|
||||
alt="Notizbuch mit Kaffee und Stift"
|
||||
fill
|
||||
sizes="(min-width: 768px) 50vw, 100vw"
|
||||
sizes="(min-width: 640px) 50vw, 100vw"
|
||||
className="object-cover"
|
||||
/>
|
||||
</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. Hidden below md: — removed on mobile 2026-07-24. */}
|
||||
<div className="hidden md:block w-16 h-14 -rotate-4 -scale-y-100">
|
||||
flipped. Hidden below sm: — removed on mobile 2026-07-24. */}
|
||||
<div className="hidden sm: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>
|
||||
|
||||
@@ -263,7 +263,7 @@ export function NewsletterModal({ open, onClose }: { open: boolean; onClose: ()
|
||||
items-start + a fixed icon bounding box (icons have different
|
||||
native proportions, e.g. the sparkle glyph isn't square) fixes
|
||||
it without needing the flip trick. */}
|
||||
<div className="flex flex-col md:flex-row items-start px-8 md:px-20 py-6 md:py-9 gap-8 md:gap-6">
|
||||
<div className="flex flex-col sm:flex-row items-start px-8 md:px-20 py-6 md:py-9 gap-8 sm:gap-6">
|
||||
{features.map((f) => (
|
||||
<div key={f.title} className="flex-1 flex gap-6 items-start w-full">
|
||||
<div className="relative h-10 w-10 shrink-0 flex items-center justify-center">
|
||||
|
||||
@@ -42,14 +42,14 @@ export async function ProductSpotlight() {
|
||||
// id="spotlight" — the Navbar's "Shop" link becomes an anchor to this
|
||||
// section instead of navigating to /shop whenever exactly 1 product is
|
||||
// active (see Navbar.tsx/layout.tsx).
|
||||
<section id="spotlight" className="w-full bg-bg-base py-12 md:py-16 px-[var(--layout-padding-x)]">
|
||||
<Reveal className="max-w-[75rem] mx-auto rounded-md flex flex-col md:flex-row gap-8 md:gap-12 items-center p-6 md:p-10">
|
||||
<div className="group relative w-full md:w-[23.75rem] md:shrink-0 aspect-[410/227] rounded-sm overflow-hidden">
|
||||
<section id="spotlight" className="w-full bg-bg-base py-12 sm:py-16 px-[var(--layout-padding-x)]">
|
||||
<Reveal className="max-w-[75rem] mx-auto rounded-md flex flex-col sm:flex-row gap-8 sm:gap-12 items-center p-6 sm:p-10">
|
||||
<div className="group relative w-full sm:w-[23.75rem] sm:shrink-0 aspect-[410/227] rounded-sm overflow-hidden">
|
||||
<Image
|
||||
src={image}
|
||||
alt={product.name}
|
||||
fill
|
||||
sizes="(min-width: 768px) 380px, 100vw"
|
||||
sizes="(min-width: 640px) 380px, 100vw"
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
{fullyOutOfStock ? (
|
||||
|
||||
@@ -61,8 +61,9 @@ function useActiveSection(sections: TOCSection[]) {
|
||||
// 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.
|
||||
// even the site's 640px structural floor, so sm: wouldn't leave room for
|
||||
// a real 2-column split at Tablet widths — a deliberate exception to the
|
||||
// site-wide sm: consolidation, not a leftover of it.
|
||||
//
|
||||
// Generic over `sections` — originally written just for /versand
|
||||
// (VersandTOC), generalized once /datenschutz needed the identical
|
||||
|
||||
@@ -15,7 +15,7 @@ export function TestimonialsGrid({ testimonials }: { testimonials: Testimonial[]
|
||||
if (testimonials.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section className="w-full bg-bg-base flex flex-col gap-8 items-center py-12 md:py-16 px-[var(--layout-padding-x)]">
|
||||
<section className="w-full bg-bg-base flex flex-col gap-8 items-center py-12 sm:py-16 px-[var(--layout-padding-x)]">
|
||||
<div className="max-w-[1600px] mx-auto w-full flex flex-col gap-8 items-center">
|
||||
<Reveal
|
||||
className="font-semibold text-h-emphasis text-text-primary text-center"
|
||||
@@ -24,11 +24,11 @@ export function TestimonialsGrid({ testimonials }: { testimonials: Testimonial[]
|
||||
Was andere sagen
|
||||
</Reveal>
|
||||
|
||||
<RevealGroup className="grid grid-cols-1 md:grid-cols-12 gap-6 md:gap-[var(--layout-grid-gap)] w-full">
|
||||
<RevealGroup className="grid grid-cols-1 sm:grid-cols-12 gap-6 sm:gap-[var(--layout-grid-gap)] w-full">
|
||||
{testimonials.map((t) => (
|
||||
<RevealItem
|
||||
key={t.id}
|
||||
className="group relative md:col-span-4 bg-bg-muted rounded-xl p-6 flex flex-col gap-4 transition-transform duration-300 hover:-translate-y-1"
|
||||
className="group relative sm:col-span-4 bg-bg-muted rounded-xl p-6 flex flex-col gap-4 transition-transform duration-300 hover:-translate-y-1"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
|
||||
@@ -30,23 +30,29 @@ export async function Tools() {
|
||||
</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 grid — 3 cols sm+ (moved down from the old md: so the grid
|
||||
arrives where the fluid floor also sits), stacked below sm;
|
||||
internal icon+text layout stays horizontal at every size, only
|
||||
the outer span changes */}
|
||||
<RevealGroup className="grid grid-cols-1 sm:grid-cols-12 gap-10 sm: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"
|
||||
className="sm: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,
|
||||
below lg: so it doesn't dwarf the title/description text,
|
||||
which does shrink toward its own fluid floor there. Full
|
||||
56px only from lg: up — at md: (Tablet, where this grid
|
||||
already switches to 3-up) the title/description are still
|
||||
fairly close to their own fluid floor, so the full-size
|
||||
icon read as too big next to them too. */}
|
||||
56px only from lg: up — a deliberate exception to the
|
||||
site's sm: (640px) structural consolidation: the grid now
|
||||
switches to 3-up at sm:, but title/description are still
|
||||
fairly close to their own fluid floor through the whole
|
||||
640-1023px range, so the full-size icon still reads too big
|
||||
next to them there. Not re-verified visually — narrower
|
||||
exception kept as-is, same reasoning as Hero's content
|
||||
sizing. */}
|
||||
<div className="relative flex items-center justify-center shrink-0 size-11 lg:size-14">
|
||||
<Image alt="" src={tool.icon} fill sizes="(min-width: 1024px) 56px, 44px" className="object-contain" />
|
||||
</div>
|
||||
|
||||
@@ -11,10 +11,10 @@ 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-start md:items-center justify-center py-8 px-[var(--layout-padding-x)]">
|
||||
<div className="w-full bg-bg-base flex flex-col sm:flex-row gap-6 sm:gap-12 items-start sm: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" />}
|
||||
<div key={item.id} className="flex items-center gap-6 sm:gap-12">
|
||||
{i > 0 && <div className="hidden sm:block h-10 w-px bg-border" />}
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="relative size-8 shrink-0">
|
||||
<Image alt="" src={item.icon} fill sizes="32px" className="object-contain" />
|
||||
|
||||
Reference in New Issue
Block a user