Commit Graph

421 Commits

Author SHA1 Message Date
Marco 601c2f5694 PDP: tighten gap between 'Was kann er?' heading and its icon row (they read as two unrelated sections) 2026-08-30 00:37:15 +00:00
Marco 917795d810 PDP: restore breathing room after Hero + before pricing panel (both had shrunk to nothing/py-8) 2026-08-30 00:35:42 +00:00
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 7babd0401f Cart: use the shared ProductBadge (was missing "Neu")
CartContent.tsx had its own fourth independent discount-badge
implementation, never updated for Products.badge — "Neu" showed on the
PDP and shop grid but not here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 23:59:11 +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 2686fd3106 Migrate /tasse-die-pause onto the PDP block system
First of the 3 legacy hand-coded PDPs migrated, now that Products.layout/
ProductBlocks.tsx exist. "Die Pause." got real final copy this session
(product doc's layout populated directly in Payload: hero body, a "So
einfach ist eine Pause" 3-step section, product facts as a pill list, a
Björn-quote contrast line, closing copy, pricing panel) — good first
candidate since its own page was still just a provisional scaffold.
Hero.tsx/Pricing.tsx in ./components are now dead code, left for a
follow-up cleanup pass rather than deleted here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 23:02:33 +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 7c842808cf Fall back to newsletter CTA when a post has no related product
The "Passend dazu" slot went empty for most posts (only 1 of 12 has
relatedProduct set), leaving /newsletter with no incoming link
anywhere on the site despite being a fully built page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 21:40:12 +00:00
Marco 06fee1739d Add auto-updating sitemap.xml and robots.txt
Sitemap combines hardcoded static routes with Payload's Pages and
published Posts collections (hourly ISR revalidation), so new CMS
pages/blog posts appear without a code change. Excludes noindex
transactional routes (checkout, cart, bestellbestaetigung,
newsletter-confirmed, konto/*) to avoid Search Console warnings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
2026-08-29 21:28:42 +00:00
Marco 31e4f907f2 Make Redirects.urlPrefix actually gate which route a code resolves under
resolveAndTrackRedirect() now filters on urlPrefix in addition to code,
with each route.ts passing its own literal prefix — previously the field
was admin-display-only, so a code marked "/sticker" in Payload silently
kept resolving under /r/<code> too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5mssdCBir9kyXTmqBjV3h
2026-08-28 13:15:41 +00:00
Marco d02707a212 Remove gap between "Bis Sonntag" and "Björn" on newsletter-confirmed
Line break only, not a full gap-4 paragraph gap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5mssdCBir9kyXTmqBjV3h
2026-08-28 12:58:13 +00:00
Marco e9b7a2f582 Add /sticker/[code] legacy QR redirect route + placeholder /sticker page
Mirrors app/r/[code]/route.ts's Payload Redirects lookup under the fixed
/sticker prefix already printed on existing sticker QR codes, so those
stay admin-editable/toggleable in the same collection instead of needing
a code-level static redirect.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5mssdCBir9kyXTmqBjV3h
2026-08-28 12:58:10 +00:00
Marco bd1b73e304 Align newsletter-confirmed page visually with transactional page family
Adds the two-tier headline and yellow checkmark list already used on
bestellbestaetigung/PageBlocks, and matches the CTA button sizing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5mssdCBir9kyXTmqBjV3h
2026-08-28 12:53:51 +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 0791e7c8bc Fix shop/related/wishlist grids not equalizing card heights
ProductCard already has a h-full + flex-1 spacer specifically to keep
every card in a row the same height regardless of content (title wrap,
subline wrap, low-stock line) — but all three grids using it set
items-start on the grid container, which overrides the default stretch
and silently defeats that. A product whose subline wraps to 2 lines made
its card taller than its row siblings instead of them matching. Dropping
items-start (default items-stretch) fixes all three call sites.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 23:34:25 +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 1d7f49217c Add PDP for Einfach anfangen. (todo-starter)
New product page at /einfach-anfangen — Hero (breadcrumb/price/CTA),
"Eine Karte. Ein Tag." 3-step section (same icon set as todo-cards'
HowItWorks), a spec bullet list, and a closing pricing panel. Follows the
same hand-coded structure as der-eine/todo-cards (bespoke Hero prose, not
read from Products.description). detailHref updated in Payload to point
here instead of the unbuilt /todo-starter.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 22:48:09 +00:00
Marco 8b9e2bdb42 PDP breadcrumbs: read the real product name instead of hardcoded copy
der-eine's breadcrumb hardcoded "Der Eine" — missing the trailing period
its own headline/pricing card already style in brand color. Switched all
three PDP breadcrumbs to the actual product.name via ProductName so they
can't drift from the real title again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt
2026-08-26 22:42:43 +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 56fae6ff13 Product JSON-LD: add sku + gallery images, single-source the meta description
sku was tracked in Payload but never exposed to the frontend at all —
added to the Product type and wired into buildProductSchema (a real
Schema.org property Google's rich-result validator checks for).
image is now an array (main + gallery) when a product has gallery
photos, instead of always just the one main image.

/der-eine's page metadata description was a separately hardcoded
string that had drifted from Products.description (the one JSON-LD/
cart/checkout actually use) — now reads from the live product via
generateMetadata, same pattern /tasse-die-pause already uses. Updated
Payload's own description field to the more product-descriptive text
that used to live only in that hardcoded string.
2026-08-26 21:20:44 +00:00
Marco 39ebf2604d Hero block: make subline more prominent (was too muted) 2026-08-26 20:58:47 +00:00
Marco 2280a4a4ba Cut /ueber-mich over to the page builder
Content recreated as an 11-block Pages document — first real use of
HeroBlock's optional portrait image (about-author.jpg, uploaded to
Media fresh) alongside the headline. Checklist has no image (uses
ChecklistImageBlock's now-optional image field). Verified via a
temporary alternate slug before switching back and removing the
static file.
2026-08-26 20:51:50 +00:00
Marco b0d76c891f Frontend support for Hero block's optional portrait image 2026-08-26 20:50:25 +00:00
Marco 7fc7ee2c3a Cut /3x3-system over to the page builder
Content recreated as a 17-block Pages document (hero, prose sections,
the 2 key-line quotes, the pill list, the category step row, the
matrix example image — uploaded to Media fresh since the closest
existing media doc turned out to be a different file — and the
closing CTA card). Verified via a temporary alternate slug against
the live catch-all route (including the inline bold "Klarheit."
formatting) before switching back and removing the static file.
2026-08-26 20:45:08 +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