Commit Graph

29 Commits

Author SHA1 Message Date
Marco 51fee198f4 Add a Vitest unit test suite (cart totals, invoice tax grouping, bundle contents)
No test infrastructure existed in this repo yet. Covers the pure logic
most likely to silently produce wrong numbers on a live order: discount/
shipping math, per-rate invoice grouping, and bundle-contents string
building. Extracted describeBundleContents() out of the checkout route
into its own module so it's importable from a test (route.ts files only
allow HTTP-method exports).
2026-07-22 11:40:31 +00:00
Marco 91f6fef6ea Support partial returns — per-item quantity, item-only Gutschrift (no shipping refund, no discount reproration)
Customers can now select which items and how many units to return
instead of only the whole order. The Gutschrift reflects only the
returned quantities, excludes shipping (already delivered), and leaves
the original discount untouched — confirmed policy, not an engineering
default. Stornorechnung (pre-shipping cancellation) is unaffected and
stays a full reversal including shipping.
2026-07-22 11:30:46 +00:00
Marco e50d43ea44 Rename invoice-settings to company-settings, add its own Live Preview, and refine invoice PDF layout
Company data now has its own Payload admin group and a live in-browser
PDF preview (react-pdf's PDFViewer) instead of just a plain settings
form. Invoice header is a brand-colored rule instead of a filled band,
and the footer is now pinned to the page bottom instead of following
content flow.
2026-07-22 11:11:58 +00:00
Marco 179b59d73d Redesign invoice PDFs, add correction-invoice downloads, return reasons, and per-product tax/bundle support
Invoice + Stornorechnung/Gutschrift PDFs get a modern header-band layout,
a "bereits beglichen" badge for immediately-paid orders, labelled bank
details, and a per-tax-rate summary breakdown. Correction invoices can
now be re-downloaded from the account (regenerated deterministically,
not stored as files, same approach as the original invoice). Return
requests capture a reason. Products can define bundles (bundleItems) and
a per-product VAT rate override, both snapshotted onto order items.
2026-07-22 10:31:22 +00:00
Marco 5232b14cdf Generate invoice PDFs attached to order confirmation, and send emails on order status changes
Invoice PDFs (§14 UStG line items, tenant-configurable VAT rate) are now
generated at checkout and attached to the confirmation email, plus
available on demand from the order-detail page. Payload-side, orders now
also email the customer on shipped/cancelled/return_requested/returned,
with Stornorechnung/Gutschrift correction PDFs attached for the latter two
so the original invoice's immutable number stays honest.
2026-07-22 09:49:56 +00:00
Marco fa02d95dff Make checkout's login prompt reactive instead of persistent, and give the confirmation email real style and voice
The always-visible "Schon Kundin?" toggle was gendered and shown to every
logged-out visitor regardless of relevance. Card 1's email field now
checks on blur (/api/account/check-email) whether that address already
has an account, and only then swaps in a gender-neutral login form,
pre-filled — the collision check in handleSubmit stays as a fallback.

The order-confirmation and password-reset emails also got a real visual
pass: same warm background/brand color/circular success-icon treatment as
the on-screen /bestellbestaetigung page, serif heading, thin brand
divider, table-based layout for email-client compatibility. Copy is
on-brand and a little playful now instead of generic transactional
boilerplate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 08:38:41 +00:00
Marco ec75a480bd Detect checkout email collisions and show login state in the navbar
Registering with an email that already has an account previously just
failed with a generic error and no clear next step. registerCustomer()
now flags emailExists specifically, and checkout switches straight to the
login toggle (email pre-filled, scrolled into view) instead. The account
icon also gets a small underline while logged in, matching the nav links'
active-state styling — it was otherwise the only nav element that gave no
visual signal of session state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 08:26:00 +00:00
Marco f0df359db4 Add password reset, order confirmation email with editable templates, and fix missing account entry points
Password reset uses Payload's built-in forgot/reset-password flow,
customized to link to this app instead of the Payload admin. Order
confirmation email and the password-reset email's wording both come from
a new Payload email-templates collection, editable without a deploy and
previewable via Live Preview at /email-preview/[type] (same mechanism as
Posts/LegalPages/Testimonials, sample data instead of a real document).

Also: order numbers get a random suffix (prevents guessing, motivated by
a considered-and-deferred guest order-lookup feature); the discount code
field only shows in the cart when a code is actually active (codes now
apply via a ?code= link instead of manual entry); and three navigation
gaps found while testing — no reachable login link with an empty cart, no
logout link anywhere, no way back from profile to order history.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 08:14:08 +00:00
Marco df05ea5358 Add rate limiting, sliding sessions, email verification, GDPR self-service, order cancellation/returns, and critical-error alerting
Complements Payload's per-account login lockout with per-IP rate limiting
on auth routes; proxy.ts silently refreshes an active customer's session
via Payload's built-in refresh-token endpoint instead of a long-lived
token. Registration now sends a non-blocking email-verification link
(doesn't gate login, since checkout registers and immediately logs in
mid-purchase). /konto/profil gets GDPR export/delete; order detail pages
get self-service cancel/return-request, backed by a Payload hook that
closes a real gap (a customer's JWT could previously PATCH any field of
their own order, not just status). Checkout failures now email an alert
independent of Payload's own health, since Kuma's uptime checks can't see
an order silently failing to persist.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 07:28:01 +00:00
Marco 7f37f111e8 Add real order persistence, customer accounts, and cart sync
Checkout now persists orders server-side (Payload orders collection,
re-priced from live product data, discount codes redeemed exactly once)
instead of writing a client-only sessionStorage snapshot. Buying requires
an account (registration inline in checkout, no separate step) — accounts
get order history with delivery status, profile/address editing, password
change, and a cart that syncs across devices while logged in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 06:45:42 +00:00
Marco 06abf1a6ae Add discount code feature (server-validated) and RelatedProducts polish
Discount codes:
- New shared lib/cartTotals.ts (computeSubtotal/computeCartTotals) factored
  out of the previously-triplicated subtotal/totalSavings/total math in
  CartContent/CheckoutContent/BestellbestaetigungContent, extended to also
  fold in a discount amount (percent or fixed, clamped so total can't go
  negative).
- lib/discount.ts mirrors lib/cart.ts's exact localStorage pattern so an
  applied code survives the /cart -> /checkout transition without a second
  input field — Checkout only displays it.
- New /api/discount/validate (read-only check) and /api/discount/redeem
  (re-validates + increments the redemption counter, called once from
  checkout's handlePurchase right before the OrderSnapshot is written).
  Both talk to Payload's new discount-codes collection through
  lib/discountServer.ts, a server-only module kept separate from
  lib/payload.ts on purpose (that file is also imported by "use client"
  components; the RSC-boundary break hit earlier this session was exactly
  this mistake with next/headers).
- OrderSnapshot gains discountCode/discountAmount so /bestellbestaetigung
  displays what was actually applied instead of losing it on recompute.

RelatedProducts: no longer falls back to re-suggesting a product already
in the cart just to pad the grid out to 3 cards — shows only the
genuinely available remainder (down to 1 card), centered in the 12-column
grid instead of left-aligned.
2026-07-21 21:16:17 +00:00
Marco 028a1fc4ec Add active-product-count-driven automation
- Products gain `active`/`spotlight*`/`updatedAt` on the base Product type
  (folded in from the now-removed separate SpotlightProduct type) so shop
  grid, spotlight, and related-products can each filter `.active` from the
  same already-fetched list — cart/checkout/order-confirmation/product-
  detail pages keep resolving any product regardless of active status.
- getSpotlightProduct() now derives from getProducts() instead of its own
  Payload query: with exactly 1 active product, that one IS the spotlight
  (overriding any `spotlight` flag elsewhere); otherwise same
  most-recently-updated tie-break as before, just computed client-side.
- ProductGrid drops the already-dead SHOP_GRID_EXCLUDE_IDS list in favor of
  the same `active` filter, with an empty-state message if 0 active.
- RelatedProducts gates on >=2 active products regardless of cart contents
  or how many display slots would otherwise resolve.
- Navbar's "Shop" link becomes an anchor to the homepage spotlight section
  (id="spotlight") instead of a real /shop navigation whenever exactly 1
  product is active — passed down from the now-async root layout, which
  fetches the catalog once for this decision.
2026-07-21 20:39:22 +00:00
Marco 4f2f137b27 Fix RSC build break: keep next/headers out of lib/payload.ts
payload.ts's mapping functions/types are also imported by "use client"
components (LiveTestimonialsGrid, LivePostContent) — importing
next/headers anywhere in that module made it unbundlable for the client,
breaking the production build. draftMode() is now only ever called in the
Server Component pages themselves; they pass the resulting boolean into
getPostBySlug/getLegalPage/getTestimonials as a plain `draft` option.
2026-07-21 19:21:10 +00:00
Marco 26ae4a15f4 Wire testimonials CMS collection and Payload Live Preview
Testimonials on /todo-cards, /newsletter, /challenge now come from the new
Payload testimonials collection via a shared TestimonialsGrid component,
instead of 3 separately hardcoded arrays.

Adds Next.js Draft Mode (/api/preview) plus Live-Preview-aware client
wrappers (LiveRichText, LiveTestimonialsGrid, LivePostContent) for posts,
legal pages, and testimonials — mounted only while Draft Mode is enabled,
so ordinary visitors keep getting the plain static components.
2026-07-21 19:02:20 +00:00
Marco 2d6cff9f40 feat(blog): optional quote label + swappable related-product card per post
Both were fully hardcoded before: every blockquote showed a static
"Merke dir:" label, and every post's "Passend dazu" card always linked
the same flagship product. Now driven by two new Posts fields —
quoteLabel (empty hides the label/icon/underline, blockquote still
renders) and relatedProduct (empty hides the card entirely) — mirroring
Products.spotlight but per-post instead of a single site-wide flag.

README's collection table updated to match today's Payload changes
(shipping-settings, the new Posts fields, trust-badges' placeholder
tokens, admin sidebar grouping) — also fixed a stale claim that `media`
isn't tenant-scoped; it already was.
2026-07-21 13:10:06 +00:00
Marco 225a8567a9 feat(shipping): move delivery-time settings to Payload, polish product/cart CTAs
The delivery-time range (handling + transit days) was a hardcoded
HANDLING_DAYS/TRANSIT_DAYS_DE pair in lib/shipping.ts — changing it
needed a code deploy. Now sourced from Payload's new Shipping Settings
collection via getShippingSettings(), threaded down as a prop to the
few Client Components (Cart/Checkout/VersandModal) that can't fetch it
themselves, with the old code constants removed.

Also: the delivery-time note is now shown on every purchase CTA (shop
grid, home spotlight, ToDo-Karten hero + pricing panel), not just one
of them — required next to each buy button per Art. 246a §1 Abs.1
Nr.8 EGBGB, not just somewhere reachable via a link. Checkout's
sidebar was missing the "ab 39€ kostenlos" note Cart already had;
that's fixed too, and both now show the delivery-time range on its own
line instead of crammed onto the shipping-cost line.

Related smaller fixes bundled in since they touch the same files:
price/delivery-time spacing tightened into its own group, the
redundant "Sichere Zahlung" note under Cart's checkout button (already
shown via the trustBadges list right below) replaced with "Sichere
SSL-Verschlüsselung" to match Checkout, and ToDo-Karten's pricing panel
no longer shows a premature payment-security note at the add-to-cart
step.
2026-07-21 12:44:35 +00:00
Marco 819f87cdeb fix(bestellbestaetigung): rebuild testimonial band, tighten hero, add spotlightEyebrow
Testimonial band: rebuilt as a proper flexbox two-column layout
(photo column with an explicit width, quote in a flex-1 sibling)
instead of an absolutely-positioned text block offset with
percentage margin/padding — that measured against the row's full
width and, combined with a max-w- on the text box, left almost no
room for the actual text on wide viewports (wrapped to one word per
line). Also dropped the muted-bg box behind the quote and widened
the photo fade to match the actual mockup, which has no separate
colored panel there at all.

Hero: removed the separate big checkmark badge — the 4-step bar
right above it already renders every step as a checkmark, so it was
just repeating that. Added more top spacing to compensate. Dropped
"Deine Bestellung macht sich jetzt auf den Weg zu dir." and added
"inkl. MwSt." under Gesamtbetrag for consistency with /cart and
/checkout.

Wired the new Products.spotlightEyebrow field (CMS-editable "Neu im
Shop" label) through lib/payload.ts into ProductSpotlight.tsx.
2026-07-20 00:18:47 +00:00
Marco 90be4696af feat: add /bestellbestaetigung order confirmation page
Matches the Figma mockup (checkmark hero, order summary card,
delivery-status panel, testimonial band) with the checkout's 4-step
bar inserted (all steps done) and the "Bis dahin: Lass dich
inspirieren" block omitted, per request.

Extracted the step bar into a shared CheckoutSteps component so
/checkout and /bestellbestaetigung don't duplicate it. The actually-
selected shipping cost and payment method are captured into a
sessionStorage snapshot by /checkout's "Jetzt kaufen" click (there's
no real order backend, so this click is what "placing the order"
means here) and read back on the confirmation page — not just
defaulted to the first active method of each, so the receipt matches
what the shopper actually picked. Also tightened the checkout
newsletter-consent copy ("Wenn du zustimmst" instead of "Wenn du
oben zustimmst").
2026-07-19 23:57:03 +00:00
Marco 7b35b3e55b feat(shop): show sale badge and strikethrough price on discounted products
Products with compareAtPrice set now show a "-XX%" badge over the
image plus a struck-through original price, wherever price is
displayed (shop grid, homepage spotlight, cart line items). Also
switched the "inkl. MwSt. zzgl. Versand" rows on Pricing.tsx and
ProductSpotlight from items-center to items-baseline — with a large
price next to small disclosure text, center alignment left the
small text visibly floating above the price's bottom edge.
2026-07-19 23:31:35 +00:00
Marco d472eb546f Move trust badges, shipping/payment methods, Werkzeuge cards, product spotlight into CMS
New Payload collections, all editable without a code deploy:
- TrustBadges: the horizontal Schneller-Versand/Versandkostenfrei/Mit-
  Liebe-verpackt row (TrustRow.tsx, now an async server component).
- CartTrustBadges: the Sichere-Zahlung/14-Tage-Rückgaberecht/Nachhaltig-
  verpackt sidebar bullets — shared by /cart (title only) and /checkout
  (title + description), which previously had two different hardcoded
  bullet lists for what's conceptually the same content.
- ShippingMethods: /checkout's Versandart radios. Each method has its own
  optional freeShippingThreshold — omitted means "never free" (Express),
  not "always free". /cart's FreeShippingBanner now targets the lowest
  threshold among active methods instead of a single global constant, and
  hides entirely if no active method has one.
- PaymentMethods: /checkout's Zahlungsart radios, icons as an array
  (Kreditkarte shows 3 logos, PayPal/Überweisung show 1).
- Products: new spotlight/spotlightHeadline/spotlightText/spotlightImage/
  compareAtPrice fields. ProductSpotlight.tsx (homepage) now shows
  whichever product has `spotlight` checked instead of being hardcoded to
  ToDo-Karten, with its own marketing copy separate from the plain
  catalog name/description. AddToCartButton takes an explicit productId
  prop now instead of a hardcoded "todo-karten" constant.
- WerkzeugeCards: the homepage's "Meine Werkzeuge" 3-card grid (Tools.tsx,
  now async). Icons use a uniform box instead of the previous per-card
  hand-tuned width/height/rotation, which only worked for 3 known,
  upside-down-authored SVGs — those were re-exported as pre-flipped PNGs.

lib/payload.ts gained getTrustBadges/getCartTrustBadges/getShippingMethods/
getPaymentMethods/getWerkzeugeCards/getSpotlightProduct, all with the same
graceful-empty-array-on-fetch-failure pattern as the existing functions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 23:15:46 +00:00
Marco 8bfbf74693 Add featured-post flag, align tool-page connector arrows, misc blog polish
- Posts collection: new "featured" checkbox (Payload) — /blog now prefers
  a manually-featured post over just the most recent one. Query sorts by
  "-featured,-publishedAt" so multiple accidentally-featured posts
  resolve deterministically (most recent among them wins) instead of
  erroring or being ambiguous.
- challenge/page.tsx: step-connector arrow now uses the same
  /icon-arrow-connector.svg asset as todo-cards/newsletter's HowItWorks,
  replacing its own hand-drawn inline SVG.
- todo-cards & newsletter HowItWorks: connector arrows now vertically
  centered on their icon (md:mt-[1.625rem]), matching Challenge's
  margin-based centering technique instead of sitting flush at the row's
  top edge.
- Blog overview: featured card overlaps the header slightly (-mt-8) per
  Figma-inspired refinement, and now also shows the publish date in its
  meta row (previously only the list items did).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 21:24:04 +00:00
Marco ed58a65b8f Add blog overview page (/blog), link Navbar "Blog" to it
- app/blog/page.tsx: header (title/subheading + bleeding photo with a
  left-edge fade, per Figma node 4577:330), featured-post card (most
  recent), remaining posts as a divided list with category/readTime/date,
  and the reusable Newsletter signup panel. Uses all real posts from
  Payload — the Figma mockup padded its list out to 4 items with two
  posts that were never actually seeded, so this only renders what
  exists (currently 1 featured + 2 listed).
- lib/payload.ts: BlogPost now carries publishedAt (needed for the list's
  per-post date), removed the now-redundant duplicate field on PostDetail.
- Navbar: "Blog" now links to /blog instead of scrolling to the
  homepage's #blog anchor — same real-route pattern as "Shop".
- blog/[slug]/page.tsx: earlier title-size tuning (fluid clamp between
  text-h-feature and text-display) from this session's follow-up
  feedback, not yet pushed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 21:04:10 +00:00
Marco 2b270cf299 Add blog detail page (/blog/[slug])
- app/blog/[slug]/page.tsx: article header (category/readTime, Playfair
  display title, excerpt, byline), full-bleed hero image, RichText body,
  "Passend dazu: ToDo-Karten" cross-promo, author bio card, "Weiterlesen"
  related-post card. Widths, fonts, and the Passend-dazu card's
  border/padding/spacing were corrected against the actual built Figma
  frame (page-blog-detail, node 4667:344, jCCZyh1DGwdjpv1wGge9To) via
  get_design_context after a few visually-wrong guesses.
- RichText.tsx: new "quote" case renders Lexical's blockquote feature as
  the "Merke dir:" pull-quote — label+underline+divider+Caveat lines,
  using the real exported sparkle/underline assets, matching Figma's
  actual (background-less) structure instead of a guessed bordered card.
- lib/payload.ts: getPostBySlug() for single-post fetches.
- lib/format.ts: formatDate() — "03. Juli 2025"-style German dates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 20:51:41 +00:00
Marco 7a3ae0ea53 Add /widerruf and /agb legal pages, fix newsletter modal scroll jank
- New /widerruf page: CMS-driven content via getLegalPage("widerruf"),
  sidebar TOC + "Nachhaltig handeln" callout, TrustRow, and a download
  card for the Muster-Widerrufsformular PDF attachment.
- New /agb page, same legal-page shell pattern as Impressum/Datenschutz.
- lib/payload.ts: getLegalPage now also resolves the optional `attachment`
  media relation (depth=1) for legal pages that have a downloadable file.
- NewsletterModal: replaced the body overflow/position scroll-lock hacks
  (which fought with the sticky Navbar and the global scroll-smooth CSS)
  with a wheel/touch event interceptor that never touches scroll position
  at all, plus `preventScroll: true` on all modal/drawer focus() calls to
  stop the browser's implicit scrollIntoView from nudging the page for
  elements near the sticky navbar's reserved scroll-padding-top zone.
  Same preventScroll fix applied to VersandModal and the Navbar mobile
  drawer for consistency.
- globals.css: scrollbar-gutter: stable, so a modal's overflow:hidden
  lock never causes a scrollbar-width layout shift.
- Divider: stagger its reveal animation slightly (delay 0.3s) behind the
  section above it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 19:57:28 +00:00
Marco 1b434fe23e Add /impressum and /datenschutz pages backed by Payload
- lib/payload.ts: getLegalPage(type) alongside the existing
  getBlogPosts/getProducts fetchers
- New RichText component: small dependency-free Lexical JSON → JSX
  renderer for Payload's richText fields (headings get stable
  "section-N" ids for anchor/TOC linking)
- New SectionTOC: generalized from VersandTOC into a reusable
  scroll-spy sidebar driven by any {id, title}[] — Datenschutz needs one
  built from CMS-authored headings, not a hardcoded array. VersandTOC is
  now a thin wrapper around it. Sticky positioning moved from the nav
  itself to each page's sidebar wrapper, so a TOC and an extra card
  (Impressum/Datenschutz both have one) scroll together as one unit
  instead of the card drifting away independently
- TOC clicks set the active item immediately and hold it for ~1s over
  the scroll-spy observer, covering both "target already fully visible,
  nothing to scroll" and "an unrelated section flickers through the
  intersection band mid-scroll"
- Both pages follow the same breadcrumb+H1+TOC-sidebar+content layout
  established for /versand
2026-07-19 17:38:02 +00:00
Marco 8c397c5dcb Move product catalog to Payload CMS
Products now come from Payload's new "products" collection instead of a
hardcoded catalog, same pattern already used for blog posts:

- lib/payload.ts: getProducts()/getProductBySlug() (server-side fetch,
  60s ISR)
- New /api/products route so client components (CartContent,
  RelatedProducts) can reach the same data without a server-only import
- lib/products.ts: useProducts() hook replacing the old PRODUCTS record
- ProductGrid (/shop) fetches server-side directly; now shows all
  catalog products except notizbuch-klarheit (matches Figma's 4-card
  page-shop-overview — still cross-sold via RelatedProducts)
- ProductSpotlight and /todo-cards' Pricing now pull price/photo from
  the same CMS product instead of a separately hardcoded "12,90 €", so
  the two can't silently drift apart
- formatPrice moved to a new lib/format.ts (plain, no "use client") —
  Server Components can't call functions exported from a "use client"
  module directly, which lib/products.ts now is because of the hook

Also fixes two unrelated bugs surfaced along the way: the add-to-cart
button visibly resizing when its "Hinzugefügt ✓" success state showed
(fixed with a CSS-grid text stack sized to the wider of the two
strings), and removes the now-unused local product images from public/.
2026-07-19 17:00:07 +00:00
Marco 9e5532be63 Add shop, cart, versand pages and Impulse & Tipps detail page
- New /shop overview, /cart (real cart state via useSyncExternalStore),
  /versand (shipping policy page with TOC) and /newsletter detail page
- Cart: quantity/removal, order summary, related-products cross-sell
  with randomized picks, VersandModal quick-reference instead of
  navigating away, MwSt. disclosure next to unit prices
- Add-to-cart UX: inline success feedback (green state) plus a
  fly-to-navbar-cart-icon animation (CartFlyProvider) with a delayed
  badge count-up; AddToCartButton no longer navigates straight to /cart
- Shared lib/products.ts catalog and lib/shipping.ts constants (cost,
  free-shipping threshold, handling/transit days) so cart, trust badges
  and the versand page can never drift apart
- Fix Navbar smooth-scroll easing (ease-out instead of ease-in-out, no
  more perceived start delay); compress newsletter modal photo
  2.4MB -> 85KB to fix first-open jank
2026-07-19 14:42:22 +00:00
Marco e4f0c66d7b Add ToDo-Karten product page, fluid design tokens, and Navbar/Hero fixes
New /todo-cards page (Hero, How-it-works, Focus, Testimonials, Pricing)
built with the fluid clamp() token system (app/lib/fluid.ts) and
scroll-reveal animations (app/components/Reveal.tsx), matching styling
consistency with /challenge's testimonial section.

Navbar: page-aware active state and anchor-link navigation from any
route (not just "/"), fixed logo click to actually navigate instead of
silently rewriting the URL, fluid nav text size, custom hash-scroll
handling for cross-page anchor links.

Hero: responsive breakpoint fix for the text/image split at Tablet
widths, entrance animation for the brand's orange dot, removed a red
dot artifact from hero.png.

Also includes prior uncommitted work on About/Blog/Newsletter/Footer
and the cart lib (app/lib/cart.ts).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 11:34:52 +00:00
Marco 35670126ca Wire up the blog section to Payload CMS instead of hardcoded posts
Blog.tsx now fetches the einfach-produktiv tenant's posts from the
shared Payload instance at build/render time (ISR, 60s revalidate),
via a small typed fetch helper in app/lib/payload.ts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 13:27:14 +00:00