Commit Graph

225 Commits

Author SHA1 Message Date
Marco 807fda3d2a PDP: widen stepRow fact-cards back to 75rem (48rem read too squeezed) 2026-08-30 00:26:10 +00:00
Marco 2b3868b605 PDP hero: plain checkmarks for usps; spotlight image now links to PDP
- product.usps rendered via STEP_ICONS[usp.icon] (checkCircle — a
  circled glyph), inconsistent with the plain checkmark every other
  feature-bullet list on the site uses (der-eine's own original
  Hero.tsx, RichText/PageBlocks' own checklists). Reused PageBlocks'
  CheckIcon (now exported) instead — usp.icon stays stored, just
  unused for this particular list (reported 2026-08-30).
- ProductSpotlight's image had no link at all — every other product
  card (ProductGrid, RelatedProducts, MerklisteGrid, via ProductCard)
  already makes its image clickable. Same Link-wraps-only-the-image
  pattern as ProductCard.tsx (WishlistButton stays an unnested sibling).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-30 00:20:14 +00:00
Marco cefe2531d4 Hide RichText heading underline on PDPs; more breathing room after Hero
- der-eine's original "Was kann er?" heading was plain text, no
  decoration — RichText.tsx's shared heading converter always appends a
  small brand-underline span (wanted on blog/legal pages, not here).
  Hidden via a scoped CSS selector, same approach as the earlier icon-
  color overrides, not touching the shared component.
- Generic block spacing bumped from py-8 sm:py-12 to py-12 md:py-16,
  matching der-eine's own original Focus.tsx rhythm — felt too tight
  right after the Hero (reported 2026-08-30).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-30 00:18:07 +00:00
Marco a4606d26ad PDP richTextSection width cap; swap seal→vase, hourglass→star icons
- richTextSection on Products now renders inside the same max-w-[48rem]
  mx-auto the fact-cards/crossSell card already use — the "Was kann er?"
  heading sat edge-to-edge while the cards under it were capped, still
  reading as unconstrained (reported 2026-08-30).
- "Vintage-Porzellan"'s medallion and "Für den einen Moment"'s hourglass
  still didn't land (reported 2026-08-30) — replaced with a literal
  pottery/vase silhouette and a single star, reusing the same 'seal'/
  'hourglass' enum keys already assigned to Tasse's blocks so no new
  migration is needed, just new icon content behind the same keys.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-30 00:15:17 +00:00
Marco 3e5c7b1a12 PDP hero CTA: plain "In den Warenkorb", not "\${product.name} bestellen"
Matches the closing ProductPricingPanel's own label now — both buttons
say the same generic thing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-30 00:12:32 +00:00
Marco 9de2693961 Narrow PDP fact-card section to match blog body width
der-eine's original Focus.tsx used max-w-[75rem] — read too wide/loose
for a 3-4 card row once ported to the block system (reported
2026-08-30). Narrowed to max-w-[48rem], matching the blog body column
(RichText.tsx) and the crossSell "Passt dazu" card's own width.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-30 00:12:02 +00:00
Marco 14d824491f Wire up real Live Preview for block-driven PDPs
Products.ts's admin.livePreview (added earlier this session) only made
the admin UI show a preview iframe — the actual page had no mechanism
to receive postMessage updates and re-render with unsaved draft data,
unlike Pages/Posts which already have this (LivePageContent.tsx/
LivePostContent.tsx). Edits in the backend were invisible in the
preview until an actual save (reported 2026-08-30).

New LiveProductContent.tsx mirrors LivePageContent.tsx's pattern
exactly: useLivePreview<PayloadProduct> + mapPayloadProduct, rendering
via ProductBlocks.tsx's now-exported renderProductBlock (sync) instead
of the full async ProductBlocks wrapper. testimonialsRef/crossSell
skipped in the live subset (need an async fetch a client component
can't perform inline) — same "editable subset only" scope Pages/Posts
already accept. shipping/tax/Kleinunternehmer context is fetched once
via the newly-exported getProductBlockContext() and passed in rather
than re-fetched per keystroke.

/der-eine and /tasse-die-pause now check draftMode() and swap in
LiveProductContent instead of the normal async ProductBlocks when
Payload's Live Preview iframe has it enabled.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-30 00:09:07 +00:00
Marco 9c6b717920 Fix icon sizing regression; recolor filled dots; add seal/droplets/hourglass
- pen/sparkle/compact icons were carried over from der-eine's original
  32x32-viewBox SVGs but rendered at their native 32px instead of the
  64px der-eine's own IconBadge wrapper actually used — half the
  intended size on /der-eine's fact cards (reported 2026-08-30).
- The PDP black-icon override only recolored `stroke`; target's/
  ProductSpotlight-unrelated icons using a hardcoded `fill="#f6a701"`
  center dot (e.g. IconTarget) kept a stray yellow dot inside an
  otherwise-black icon on /einfach-anfangen. Added a second override
  targeting exactly that hardcoded fill value.
- 3 new icons (seal/droplets/hourglass) for Tasse "Die Pause."'s fact
  cards — cup/shield/checkCircle/pause didn't fit "Vintage-Porzellan"/
  "Spülmaschinenfest"/"Für den einen Moment" at all.
- ProductSpotlight (homepage) now uses the shared ProductBadge too —
  missed in the earlier badge-consolidation pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-30 00:06:16 +00:00
Marco 31b8e89ea4 Simplify crossSell to a single hand-picked product; badge everywhere; gallery cursor
- CrossSellBlock: dropped mode (automatic/manual) + hasMany products
  down to one required `product` relationship, same filterOptions-
  excludes-self dropdown as Products.relatedProduct — the "automatic,
  same category" mode added complexity a plain dropdown already covered
  better, and the frontend's card was always designed around one
  recommendation, not a grid (reported 2026-08-29).
- New shared ProductBadge component (app/components/ProductBadge.tsx),
  used by both ProductBlocks.tsx (PDP) and ProductCard.tsx (shop grid,
  cart's RelatedProducts, wishlist grid) — Products.badge ("Neu") only
  showed on the PDP before, now consistent everywhere a product's image
  renders a badge.
- ProductGallery's arrow/thumbnail buttons get an explicit cursor-pointer
  class.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 23:53:41 +00:00
Marco 48e73796b1 crossSell: match der-eine's/blog's own "Passt dazu" card style
ProductCard (price, wishlist heart, add-to-cart button) read as a
commerce grid, too busy for a single lightweight recommendation slot —
der-eine's own hand-coded PasstDazu.tsx (and the blog's "Passend dazu"
card) already had the right, more editorial look: bordered card,
thumbnail + name + description + "Entdecken" arrow, no price/buy
button. Reused that exact markup here instead, stacked rather than a
grid. getWishlistEnabled/ProductCard now unused in this file, removed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 23:42:35 +00:00
Marco c98294a6a4 Migrate /der-eine onto the PDP block system
der-eine was the reference the whole PDP block-builder was designed to
match (gallery sizing, black stepRow-facts icons, dynamic CTA label —
see ProductBlocks.tsx). Migrated it onto its own system: hero body,
usps (the 3 hero features), a "Was kann er?" fact-card section (its own
4 icons — pen/sparkle/compact, new; sunHigh, reused — carried over into
the shared StepIcons set), pricing panel, automatic crossSell.
Testimonials.tsx dropped, not migrated (testimonialsRef removed from
the PDP builder earlier this session). Old Hero/Focus/Testimonials/
Pricing/PasstDazu components left as dead code, same as
/tasse-die-pause's own migration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 23:40:21 +00:00
Marco f98536ff88 Remove ToDo-Karten product and its dedicated /todo-cards page
Product deleted from Payload (no orders referenced it), so its bespoke
page went with it — Navbar's WERKZEUGE_ROUTES and the sitemap no longer
list it. The blog's "3minuten" relatedProduct pointer and the homepage's
"ToDo-Karten" Werkzeuge card were cleared/removed in Payload directly
(no code change needed there — CMS content).

Also fixes a real bug found in the process: ProductBlocks.tsx's
AddToCartButton calls never passed productId, silently falling back to
its "todo-karten" default — every block-driven PDP's buy button (e.g.
"Die Pause.") was adding the wrong product to the cart.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 23:29:15 +00:00
Marco 207f33724b ProductHero: match der-eine's gallery sizing, CTA label, icon color
der-eine is the reference the whole PDP block system is meant to look
like — three concrete deviations fixed:
- Gallery was full-bleed across the whole lg:col-span-7 column; der-eine
  caps it at max-w-[28rem], centered, vertically centered at lg: — same
  treatment now, same reasoning (der-eine's own Hero.tsx comment on why).
- Hero CTA was a generic "In den Warenkorb" instead of "${product.name}
  bestellen" like der-eine's/todo-cards' own buttons (the closing
  ProductPricingPanel button already said the generic label correctly —
  matches der-eine's own two-CTA pattern, hero named, pricing-panel
  generic).
- stepRow icons render brand-yellow (STEP_ICONS, shared with Pages.layout
  content pages that want that) instead of der-eine's black
  (Focus.tsx's #231f20). Recolored via a CSS descendant-selector override
  scoped to ProductBlocks.tsx's own stepRow case, not touching the shared
  icon set or PageBlocks.tsx.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 23:12:42 +00:00
Marco 3e0e6c220d Add cup/pause step icons
Companion to the payload-repo commit adding these to StepRowBlock's
enum — none of the existing 10 fit a physical drink-related product.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 23:08:49 +00:00
Marco 52d0b8558e NewsletterModal: link to /newsletter for the full pitch
Quick-access signup stays in the modal; a small "mehr erfahren" link
under the description sends anyone not ready to type an email straight
to /newsletter (testimonials, how-it-works) instead of only being
reachable via the Navbar — the same gap the blog's newsletter-fallback
card closed for readers, closed here for the modal's own audience.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 22:59:37 +00:00
Marco 74bec632ca Frontend: render storySplit/faq/crossSell, gallery+badge+usps, qty stepper
Wires up the new PDP page-builder gaps (Products.layout backend already
built, migrated in a companion payload repo commit):

- storySplit/faq render via PageBlocks.tsx (shared with Pages.layout);
  crossSell is Products-only, resolved server-side in ProductBlocks.tsx
  (manual: getProductsByIds; automatic: active products sharing a
  category, falling back to any other active product).
- ProductGallery gets an optional `badge` overlay slot; ProductHero now
  uses it (previously a single plain <Image>, no badge at all) instead
  of duplicating a second image element.
- New shared ProductBadge helper (discount % / Ausverkauft / Neu) used
  by both ProductHero and ProductPricingPanel — only the pricing panel
  showed this before.
- product.usps (max-3 icon+text) renders in the hero, reusing
  StepRowBlock's icon set via STEP_ICONS.
- AddToCartButton gets an opt-in showQuantityStepper prop (default off,
  no behavior change for existing callers); enabled on both PDP
  buy buttons.
- quote gets a PDP-specific full-bleed brand-background treatment,
  scoped to ProductBlocks.tsx's own switch case so Pages.layout's
  existing quote styling (e.g. /ueber-mich) is untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 22:58:42 +00:00
Marco 43bffaa5c1 RichText: honor a link's newTab field
The link/autolink converters never read node.fields.newTab, so setting
"open in new tab" on a link in the Payload editor had no effect.
Needed now that the Die Sieben post's template-download link opens a
PNG in-place instead of navigating away from the article.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 22:35:32 +00:00
Marco 6f6a6119f5 Restore original wide photo for homepage About section; fix Weiterlesen
About section goes back to the original wide banner image (its own
new file, about-banner.jpg) — the new bjoern.png headshot is a square
close-up that never suited this wide/short strip. Blog posts keep the
new photo via about-author.jpg, unaffected.

Also fixes /blog/[slug]'s "Weiterlesen" card: it picked the first of
the 4 most-recent posts that wasn't the current one, so nearly every
post converged on the same one or two most-recent articles. Now picks
the chronologically next-older post (wrapping to the newest from the
oldest), so every post points somewhere different.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 22:17:29 +00:00
Marco 55d613d409 About section: switch author photo to object-contain
object-cover on the square about-author.jpg inside this wide/short
banner was scaling the image up to fill the width and cropping most
of the height away, blowing the face up to fill the entire strip.
Verified the exact crop with a sharp simulation before switching.
object-contain shows the whole photo at its real proportions instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 22:10:41 +00:00
Marco 193b85a3ca About section: shift author photo crop upward
about-author.jpg became a square close-up portrait; centered object-cover
in this wide, short banner was cropping straight into an extreme
eyes-to-chin close-up. Biasing the crop toward the top keeps more
forehead/hair in frame.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 22:07:46 +00:00
Marco 98b3b3b6da Make product card row alignment fully dynamic via CSS Grid subgrid
Replaces the fixed line-clamp-2 subline cap with real subgrid alignment:
each grid (ProductGrid/RelatedProducts/MerklisteGrid) declares 6 explicit
row-tracks (image/header/price/belowPrice/lowstock/button, last one
minmax(0,1fr)), and every card spans those same 6 tracks via
grid-template-rows: subgrid — so row heights are genuinely shared across
a row of cards. A subline can now wrap to any number of lines (3, 4, ...)
without being truncated, and the price row still starts at the same Y on
every card in that row.

ProductCard.tsx is flattened from nested flex divs into 6 direct grid-row
children so each one can be its own subgrid track; the old flex-1 spacer
is replaced by self-end on the button's row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 23:49:50 +00:00
Marco 8f006b6603 Keep product card prices aligned regardless of subline/name length
The previous equal-height fix (items-stretch) made cards match overall
height, but a subline wrapping to 2 lines still pushed that card's own
price row down relative to its row siblings — the flex-1 spacer only
re-aligns the button at the bottom, not the price above it. Name now
clamps to 1 line, subline always reserves a clamped 2-line slot
(rendered even when empty) — every card's price row starts at the same Y
regardless of title length or whether a subline is set at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 23:37:58 +00:00
Marco e882b8b6ac Match block-driven PDP hero/pricing panel to hand-coded siblings visually
Reported: einfach-anfangen's block-built PDP looked visually far off from
todo-cards. Root cause: ProductBlocks.tsx wrapped every block (including
the hero) in one generic padded div, so the hero image lost its
edge-to-edge desktop bleed/hover-scale and neither section had its own
Reveal scroll-in animation, unlike every hand-coded PDP. ProductHero and
ProductPricingPanel now render their own full <section> matching
todo-cards' Hero.tsx/Pricing.tsx exactly (own Reveal, own grid, own
padding) instead of relying on the shared per-block wrapper, which now
only applies to genuinely generic content blocks.

Also wires up the testimonialsRef block for Products.layout (added to the
backend in commit 82583a1) — same async-fetch handling as PageBlocks.tsx,
plus the 'einfach-anfangen' TestimonialsPage value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 23:24:14 +00:00
Marco 1ab4088bf0 Wire up Products.layout (PDP page builder) frontend
New ProductBlocks.tsx renders a product's layout — delegates to
PageBlocks.tsx's renderPageBlockSync for the 9 block types shared with
Pages.layout, handles productHero/productPricingPanel itself (need live
product/shipping/tax context a generic content page doesn't have).

Converts /einfach-anfangen to render through this instead of its own
Hero/HowItWorks/Focus/Pricing components (now deleted) — the first PDP
built as CMS blocks end to end. der-eine/todo-cards/tasse-die-pause are
untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 23:12:26 +00:00
Marco a2d4cb29fc Fix Page Builder's QuoteBlock label being silently ignored
app/components/Quote.tsx (the shared component PageBlocks.tsx/Pages.layout
uses) never had a `label` prop at all — dropped when it was extracted from
RichText.tsx's own local Quote. QuoteBlock.label was set correctly in the
admin and mapped through payload.ts, but PageBlocks.tsx's "quote" case
never passed it through, and even if it had, this component had nowhere
to put it. Both fixed; matches RichText.tsx's existing label treatment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 22:50:11 +00:00
Marco a1b0dffff7 Render the 6 new blog-post blocks (StepRow/Icon/PillList/ChecklistImage/Table/CtaCard)
Follows Posts.content's BlocksFeature update (docker/payload commit
5426a02) — mirrors PageBlocks.tsx's own JSX for each block field-for-field
since it's the same Block config reused a second time. Works in both the
server-rendered page and the client-side Live Preview renderer (shared
converters), unlike testimonialsRef which needs an async fetch and stays
page-builder only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 22:40:12 +00:00
Marco 1330e3e621 Show product subline on cards/cart/PDP hero; brand-color trailing period
Follows Products.subline (docker/payload commit ad7156e). Cards and cart
line items showed nothing but the bare name — added the subline under it.
der-eine and todo-cards' hero taglines were hardcoded copy nearly
identical to this new field — switched both to read subline instead, so
future edits go through the CMS.

Also adds a shared ProductName component: every hand-written PDP headline
already styled a trailing "." in brand color, but the few places that
render product.name as plain text (cards, cart, the Payload-driven
tasse-die-pause hero) had silently lost that styling. Centralizes it so
it can't drift per call site again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 22:26:14 +00:00
Marco 21b5f41127 Enlarge Hero block portrait (80px -> 128px)
/ueber-mich's about-author.jpg read as barely-there next to the full
headline at size-20 — bumped so it actually registers as a real photo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 22:04:32 +00:00
Marco 7489f83564 Follow Products.description consolidation to richText-only
Backend dropped the plain-text description field in favor of a single
richText one (see docker/payload commit 1ba8d07). Renders formatted
(bold/italic/multiple paragraphs) via the shared RichText component on
the PDP (der-eine, tasse-die-pause); everywhere else (Passend-dazu cards,
Product JSON-LD, homepage spotlight fallback) derives plain text at read
time via a new extractPlainText() helper instead of a second field.
Removes the description line from cart line items entirely — it only
bloated the cart with no real benefit there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 22:04:28 +00:00
Marco 82696cb259 Homepage blog teaser: make the whole card clickable, not just "Zum Beitrag"
The cards already had a hover-lift effect implying the whole thing
was clickable, but only the small arrow line was an actual link —
image and title had pointer-events-none/no href at all. The full
/blog listing page already wraps its own cards in one Link; this
brings the homepage teaser (Blog.tsx) in line with it. The former
inner CTA Link is now a plain span using group-hover for its color
change, since one <a> can't nest another.
2026-08-26 21:26:46 +00:00
Marco 0fea84f362 Fix homepage spotlight teaser dropping italic formatting
SpotlightText's minimal Lexical converter only checked format bit 1
(bold) — bit 2 (italic) was silently ignored, so Tasse "Die Pause"'s
italicized quote ("Nur noch schnell … Nein.") rendered as plain text
on the homepage despite showing correctly in the admin's richText
editor.
2026-08-26 21:24:26 +00:00
Marco 39ebf2604d Hero block: make subline more prominent (was too muted) 2026-08-26 20:58:47 +00:00
Marco b0d76c891f Frontend support for Hero block's optional portrait image 2026-08-26 20:50:25 +00:00
Marco 098ba79c8f Frontend support for the Hero block; H1 moves out of the hardcoded wrapper
app/[slug]/page.tsx and LivePageContent.tsx no longer render a
hardcoded H1 from page.title — that's now the Hero block's job (see
HeroBlock.ts's own comment on why). Breadcrumb stays, still built
from page.title.

Also backfilled /lebensuhr's already-migrated Pages document with a
Hero block (order 0) carrying its real headline — without it the
page would have lost its H1 entirely once the hardcoded wrapper was
removed.
2026-08-26 20:41:21 +00:00
Marco 2d9508324a Frontend support for stepRow subtitle + new icon block
Mirrors the backend schema additions (StepRowBlock.items.subtitle,
new IconBlock) needed for the /lebensuhr Pages migration: PageBlock
type + mapPayloadPageBlock in payload.ts, a "icon" case in
PageBlocks.tsx, and SYMBOLIC_ICONS (the clock face) added alongside
STEP_ICONS in StepIcons.tsx — a distinct registry since these icons
are standalone, not part of an icon-above-text row.
2026-08-26 20:26:46 +00:00
Marco 885389b300 Add the page-builder frontend: /[slug] catch-all + block renderer
Pages/getPageBySlug/mapPayloadPage in payload.ts follow the exact
getPostBySlug/mapPayloadPost pattern. PageBlocks.tsx renders a Pages
doc's `layout` field, reusing existing components (RichText,
StepArrow, STEP_ICONS, TestimonialsGrid) rather than reinventing per-
block styling — matches what /lebensuhr, /3x3-system, and
/7-tage-klarheits-check already hand-built. LivePageContent.tsx
mirrors LivePostContent.tsx's live-preview pattern, using a synchronous
subset of the block renderer (testimonialsRef needs an async fetch a
client component can't perform inline, so it's skipped in preview
only — same "editable subset" scope-cut LivePostContent.tsx already
makes). buildWebPageSchema in structuredData.ts is the generic
JSON-LD fallback for content types with no bespoke schema (Article/
Product don't fit a page-builder page).

Verified end-to-end: inserted a real Pages test document (SQL, since
no admin auth available here) covering richTextSection/quote/ctaCard,
confirmed /[slug] renders it correctly, confirmed notFound() after
deleting it, then cleaned up the test row.
2026-08-26 20:08:24 +00:00
Marco 940545888c Consolidate per-page icon duplicates into a shared registry
/lebensuhr, /3x3-system, and /7-tage-klarheits-check each defined
their own local copies of the same hand-drawn brand-line icons.
Moved them into app/components/icons/StepIcons.tsx as a keyed
registry (STEP_ICONS) — also what the new Payload Pages collection's
StepRowBlock select field maps onto by key, so a page-builder step
row can reference these same icons without duplicating SVG paths a
third time.
2026-08-26 20:03:57 +00:00
Marco e2d81fabc7 Add the trailing period to "einfach produktiv." wherever it was missing
The period is part of the brand name (see Footer.tsx's own comment on
the two-tone logo treatment) — fixed every place it was cleanly
appendable (string/heading endings, or before a dash, matching the
existing precedent in VertragspartnerBlock.tsx). Left the handful of
genuinely awkward mid-sentence spots alone (separable-verb endings,
"einfach produktiv-Konto" hyphenated compounds) rather than force a
period that breaks the sentence — flagging those separately.

Also: Über-mich's checklist now uses the same brand checkmark as
/lebensuhr and /7-tage-klarheits-check instead of plain bullets.
2026-08-26 19:25:27 +00:00
Marco a1c9a2127b Navbar: mark Werkzeuge active on /lebensuhr and /3x3-system 2026-08-26 19:06:06 +00:00
Marco c2d0b6e364 Rename /ueber-bjoern to /ueber-mich, link it in the nav
Nav's "Über Björn" entry pointed to the homepage's #ueber-bjoern
teaser section, not the new bio page — updated the label to "Über
mich" and the href to the real /ueber-mich page. The homepage's own
#ueber-bjoern anchor id (About.tsx) is untouched, it's a separate
section.
2026-08-26 18:55:49 +00:00
Marco c64902c381 Rename /lebensuhr to /7-tage-klarheits-check, update CTA/content copy
Renames the route, its canonical/OG metadata, Navbar's active-route
list, and the testimonials page filter to match. Also updates CTA
copy ("Klarheits-Check starten"), the "So funktioniert" heading, the
"3. Umsetzen" step text, and the breadcrumb label.
2026-08-26 15:04:03 +00:00
Marco bf48a1561c Home Blog featured post: stack image-then-text through 1024px, not 640px
Side-by-side kicked in at sm (640px), squeezing the text column hard on
tablet widths. Moved the breakpoint to lg (1024px), matching the
secondary posts' grid below, which already used lg for the same reason.
2026-08-25 20:35:53 +00:00
Marco 168f17bcef Home Blog featured post: taller card on lg+ via min-h, image follows
The card's height was purely driven by the (short, fixed) text column's
content regardless of viewport width. lg:min-h-[26rem] gives it real
height on large screens; the image's sm:h-full and the text column's
justify-between both stretch to match.
2026-08-25 20:29:24 +00:00
Marco 7e00d51b89 Revert "Home Blog section: cap width at max-w-[75rem], same as the rest of the site"
This reverts commit 233a41f7c9.
2026-08-25 20:23:15 +00:00
Marco 233a41f7c9 Home Blog section: cap width at max-w-[75rem], same as the rest of the site
Was full-bleed w-full with no cap, so on very wide screens the featured
card (and its now-h-full image column) kept stretching wider while the
image's height stayed pinned to the text column's content height —
increasingly squashed the wider the viewport got.
2026-08-25 20:20:18 +00:00
Marco 24ae330037 Home Blog featured post: image stretches to full row height, not a fixed 220px
The 58%-wide image column at a hardcoded 220px tall produced a badly
elongated crop on wide viewports. sm:h-full lets the grid's natural
row-stretch (driven by the text column) size it instead, matching how
/blog's own featured card already does it.
2026-08-25 20:14:32 +00:00
Marco 4fa80d6bfb Allow bold formatting in the spotlight product's marketing text
Products.spotlightText switches from a plain textarea to a richText field
(Lexical, same default editor as every other richText field here) so
admins get a formatting toolbar. Rendered via a small dedicated inline
converter (bold + paragraphs only) rather than the full block-oriented
RichText.tsx, which is styled for article-length content (blog posts,
legal pages) and would add heading/paragraph spacing this teaser doesn't
want.
2026-08-25 12:50:00 +00:00
Marco eb160cac61 Preserve paragraph breaks in the spotlight product's text
spotlightText is a plain textarea, and default HTML whitespace handling
collapsed admin-entered paragraph breaks into one run-on line.
2026-08-25 12:36:07 +00:00
Marco 13fb37eb5e Rename Der Alltagsstift back to Der Eine., move route to /der-eine
New copy throughout (hero, "Was kann er?", the "Warum Der Eine.?" brand-story
section replacing the old phone-vs-pen scenario grid, final CTA) plus the
Payload product's name/detailHref updated to match (done directly via psql,
no Payload admin API key available — see reference_payload_direct_db_access).
2026-08-25 12:36:03 +00:00
Marco c065a3223f Link product images to detail pages, not just titles
Image sits as a sibling to the badge/WishlistButton overlays, not
wrapping them — nested interactive elements would be invalid HTML and
would fire navigation on a wishlist click.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 08:19:42 +00:00