Commit Graph

186 Commits

Author SHA1 Message Date
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
Marco 340dcd2c94 Fix notify-form input height to actually match the cart button
py-3 alone didn't match AddToCartInlineButton's real height — that
button's tallest child is its 1.875rem cart-icon image, not its text,
so an input with the same padding but only text content still rendered
shorter. Explicit h-14 matches the button's actual 56px.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 07:23:34 +00:00
Marco 70bc839f48 Restore reserved low-stock line, clearer notify-me placeholder text
NotifyMeForm's single-row redesign matched AddToCartInlineButton's
button height exactly, which made the sold-out card's earlier
low-stock-line omission overcorrect — it ended up shorter than its
siblings instead of taller. Keeping that line unconditional (like every
other card) is what actually lines them up. Also swapped the generic
"E-Mail-Adresse" placeholder for one that states the purpose.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 07:17:14 +00:00
Marco 82c1b1fd43 Match sold-out card height to in-stock siblings
NotifyMeForm was two stacked rows (email input + full-width button),
making an out-of-stock ProductCard taller than its in-stock siblings.
Redesigned as a single input with the submit control embedded inside it
(same py-3 as AddToCartInlineButton's own button, so the row height
matches exactly), and ProductCard now skips its reserved-height
low-stock line entirely for a fully-out-of-stock product, since that
line can never apply there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 07:14:49 +00:00
Marco c8f231baa6 Dynamic AGB address, real shipping costs on /versand instead of hardcoded constants
- AGB: name/address/email now come from company-settings via new
  VertragspartnerBlock (mid-document, see LegalPages.ts's contentPart2).
- VersandSections/VersandModal/CartContent/CheckoutContent/versand page:
  shipping cost and free-shipping threshold now come from the real
  ShippingMethods data (already fetched elsewhere for checkout), not the
  hardcoded lib/shipping.ts constants — those had already drifted from
  the real Payload values once. lib/shipping.ts deleted, nothing left to
  export.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 06:53:12 +00:00
Marco 9617478b0d Add mobile swipe gallery, shared ProductCard, dynamic Datenschutz address, GSC verification
- ProductGallery: touch swipe (left/right) now navigates slides on mobile,
  previously click-only.
- New shared ProductCard component used by ProductGrid/RelatedProducts/
  MerklisteGrid — product title is now the card's link (replaces the
  separate "Mehr erfahren" line), consistent aspect ratio across all
  three grids, more compact cards.
- Datenschutz: name/address/email now come from company-settings via a
  new VerantwortlicherBlock, same single-source pattern as Impressum's
  AnbieterAngaben — no more hand-typed address to keep in sync.
- layout.tsx: render googleSearchConsoleVerification via Next's native
  verification.google metadata field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 05:38:55 +00:00
Marco 23f8efcc2b Fix Klaro backdrop only covering a column, not full viewport
Root cause: .cookie-modal is the full-viewport wrapper, NOT the
dialog box — the previous centering fix wrongly applied
position/transform to it. Adding a transform to that wrapper created
a new containing block for its position:fixed children, so .cm-bg
(the backdrop) started positioning itself relative to the now-
shrunken/centered wrapper instead of the real viewport — the dark
overlay only covered a 640px-wide column (screenshot-confirmed
2026-08-02) instead of the whole screen.

Moved all sizing/position/shadow overrides to .cm-modal.cm-klaro (the
actual dialog box, a sibling of .cm-bg — both children of the
untouched full-screen .cookie-modal). Also fixed several selectors
that were quietly matching nothing: .cn-body doesn't exist in the
settings modal (that's .cm-header/.cm-body), and the footer button
row is .cm-footer-buttons, not .cm-buttons (that class belongs to the
small notice/context-notice components only). Restyled the modal's
close button and structural padding to match, now that the real
selectors are confirmed against klaro's own consent-modal.jsx source.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:20:16 +00:00
Marco 6e6f273524 Match Klaro modal backdrop color to NewsletterModal's own overlay
Klaro's default .cm-bg is plain black at 50% opacity — functionally
correct (standard modal dimming, confirmed intentional, not a bug)
but a different shade from this site's own established modal
backdrop. Now matches NewsletterModal.tsx's
rgba(134,134,134,0.9) exactly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:15:15 +00:00
Marco 3336ce77c1 Fix Klaro settings modal losing horizontal centering
The unconditional max-width:640px override collided with Klaro's own
breakpoint-dependent centering (margin:auto only applies above an
internal JS-set breakpoint; below it the modal is fixed/width:100%
with no left/margin, so the wider modal rendered flush-left instead
of centered). Explicit left:50%+translate(-50%,-50%) centers on both
axes regardless of which of Klaro's own branches is active.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:12:12 +00:00
Marco 90c52687bf Fix invisible service descriptions, broken empty-translation glitch; redesign modal list
Real bugs, both confirmed via screenshot 2026-08-02:
- dark3 was set to a near-white cream tone assuming background-only
  use, but Klaro's .cm-list-description rule uses it as a TEXT color
  — every service description was almost invisible. Changed to
  --color-text-muted.
- The service.disableAll.description="" override to hide Klaro's
  boilerplate toggle-all text broke its own t() lookup, rendering a
  literal "[missing translation: de/service/disableAll/description]"
  string instead — worse than the original. Reverted, hidden via a
  plain CSS display:none on .cm-toggle-all .cm-list-description
  instead.

Also a real redesign pass on the settings modal's service/purpose
list (wider modal, row separators, bigger/clearer toggle switches,
distinct footer) — was previously only reachable via the notice's own
generic styling, still visibly "default library layout" per Marco's
own read.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:09:28 +00:00
Marco 7ef6d2652b Klaro polish: remove redundant toggle-all text, responsive button rows, real per-service descriptions
- Empties Klaro's own "Mit diesem Schalter..." boilerplate under the
  modal's per-service toggle-all switch — redundant next to a plainly
  labeled switch.
- .cn-ok/.cn-buttons/.cm-buttons now explicit flex-wrap rows (side by
  side until they genuinely don't fit, then wrap) instead of relying
  on Klaro's own fragile inline-block flow.
- Every tracking-codes provider gets a real description in the modal's
  service list (was title-only) — what it does and, where publicly
  documented and stable, the actual cookie names/lifetimes (_ga/
  _ga_<container-id> for GA4, _fbp/fr for Facebook Pixel). GTM/Maps
  descriptions are deliberately non-specific about exact cookies —
  neither has a fixed, publicly enumerable list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:04:13 +00:00
Marco ac6c3d6b71 Add GoogleMapsEmbed.tsx — consent-gated Maps embed, prepared but unused
Uses Klaro's own built-in contextual-consent mechanism (not
loadTrackingCode.ts's script-injection path) — the iframe renders
with data-name="google-maps" and its real src; Klaro's DOM scan finds
it after mount and blanks/restores src based on that service's
consent state, showing its own placeholder (styled via
KlaroConsentManager.tsx's KlaroTheme) in the meantime.

No map embedded anywhere in the app yet — this is prep work only, per
explicit request ahead of an actual page needing one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:58:19 +00:00
Marco 417bbd430e Add product image gallery, harden Klaro border reset, remove attribution link
ProductGallery.tsx (main image + thumbnail strip) wired into
TodoKartenHero.tsx, only replacing the static hero photo once a
product actually has extra gallery photos — no visual change
otherwise. Backend field: Products.gallery (see payload repo).

Klaro's hard black border was still showing after the previous
color-only pass — the targeted border:none rule wasn't enough, so
this blanket-resets border/outline on every .klaro descendant and
re-adds only the shadow this theme actually wants. Also disables the
"Realisiert mit Klaro!" attribution link (BSD-3-Clause has no
on-page-attribution requirement, Klaro's own disablePoweredBy flag
covers this cleanly).

Updates the frontend README with the gallery + Klaro fixes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:41:23 +00:00
Marco f6b001dd11 Restyle Klaro to actually match the brand, not just its accent colors
CSS-var overrides alone (colors, corner position) still left Klaro's
own border/shadow/spacing/typography, which read as an obviously
bolted-on library widget next to this site's hand-designed
components. New KlaroTheme component overrides Klaro's real DOM
classnames directly (confirmed against kiprotect/klaro's own scss
source) — kills the default border, adds this site's own soft-shadow
card look, restyles every button, uses the site's actual serif/sans
font pairing. Also warms up the notice/modal copy.

Updates both READMEs with the tracking-codes/Klaro/back-in-stock/
settings-search/DHL-shipment-label work from this session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:20:28 +00:00
Marco 72b7bc629c Restyle Klaro banner to brand + add persistent reopen button
Was full-width ('wide' theme) with Klaro's default green/dark
palette, neither matching the brand. Now: compact bottom-left corner
notice (~380px), brand colors via Klaro's CSS-var overrides
(dark1/light1 etc. — confusingly named, they're background/text, not
a dark-mode switch), neutral gray decline button instead of red.

Also adds a persistent bottom-left cookie icon (KlaroConsentManager.tsx)
so a visitor can reopen the consent manager any time via Klaro.show(),
not just on first visit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:07:26 +00:00
Marco 537543bf91 Replace custom cookie banner with Klaro (open-source, self-hosted CMP)
Swaps the hand-rolled CookieBanner.tsx/useConsent.ts/TrackingScripts.tsx
for kiprotect/klaro — brings a real per-service consent list and
bundled German UI translations that a custom implementation would
have had to build from scratch (per user feedback that a proper CMP
is worth it over a purely custom binary accept/reject banner).

klaroConfig.ts builds Klaro's config dynamically from the existing
tracking-codes backend collection (one Klaro "service" per row,
grouped by consentCategory as its purpose). loadTrackingCode.ts is
the actual script-injection side effect, wired in via each service's
`callback(consent)` — same GA4/Facebook-Pixel/GTM/custom loader logic
TrackingScripts.tsx had, just triggered imperatively instead of
declaratively rendered. Brand color applied via Klaro's CSS custom
property overrides (styling.green1 etc.), not custom SCSS.

No @types/klaro package exists — types/klaro.d.ts declares only the
small surface actually used.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:33:10 +00:00
Marco 259002f5c0 Add product thumbnail to back-in-stock email, left-align notify button text
renderBackInStockHtml() (and sendBackInStockEmails.ts on the backend)
now show a small product image above the product name, when the
product has one. Also left-aligns NotifyMeForm's button text to match
the email input's own left-aligned placeholder — was centered, read
as inconsistent next to it.

Also adds the klaro dependency, ahead of swapping the custom cookie
banner for it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:19:59 +00:00
Marco 87388479de Fix out-of-stock CTA regressions: grid alignment, preview accuracy, label length
- Replace the invisible reserved-space trick with items-start on every
  product grid (ProductGrid/MerklisteGrid/RelatedProducts) — the
  reservation looked worse in practice (visible dead space under
  in-stock cards' buttons) than letting an out-of-stock card simply be
  taller than its siblings.
- New renderBackInStockHtml() in lib/emailTemplates.ts, used by the
  Live Preview instead of the generic order-status renderer — that one
  showed a fake order number and "Bestellung ansehen", neither of
  which apply to a back-in-stock mail (no order exists). CTA is now
  "Zum Produkt", matching the real backend send.
- Shortened NotifyMeForm's button label ("Benachrichtigen") — the
  longer version wrapped to two lines on narrow single-column cards.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:12:23 +00:00
Marco 70ee518e1d Ship tracking-codes Phase 2: cookie consent banner + gated script injection
CookieBanner.tsx (equally-weighted Akzeptieren/Ablehnen, TTDSG) +
useConsent() cookie hook + TrackingScripts.tsx render active
tracking-codes rows only once their consentCategory is actually
accepted ('necessary' always renders). Wired into layout.tsx via the
new getTrackingCodes() fetcher. This is what makes the Phase 1
backend collection (tracking-codes) actually usable end to end.

Also reworks NotifyMeForm back to always-visible input+button (better
UX than a collapse-to-reveal step) — the resulting taller CTA is now
reserved on every card via NotifyMeFormReservedSpace, an invisible
twin rendered behind the real button, so an in-stock card's row
height matches an out-of-stock sibling's without the grid's
row-stretch pushing buttons out of alignment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:03:11 +00:00
Marco d046e5c3bb Fix out-of-stock CTA: replace button instead of stacking, collapse notify form
Two problems in the previous back-in-stock commit: the disabled
"Ausverkauft" button and NotifyMeForm stacked, making out-of-stock
cards visibly taller than in-stock siblings — and since ProductGrid's
cards rely on plain CSS Grid row-stretch for equal card height, that
extra height stretched sibling cards and pushed their own buttons
down (screenshot: "In den Warenkorb" CTAs misaligned across a row).

NotifyMeForm now replaces the button slot entirely when out of stock
(matching the height of a normal button when collapsed), and only
expands to the email input after a click.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 19:55:38 +00:00
Marco 2638515683 Fix NotifyMeForm layout: stack input/button instead of side-by-side
Narrow product cards (shop grid, related products) truncated the
email placeholder when input and button shared a row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 19:52:37 +00:00
Marco 0dcaad8b8c Add back-in-stock notification signup form
NotifyMeForm.tsx replaces the disabled Add-to-cart button's spot once
a product/variant is out of stock — POSTs to the new
/api/stock-notifications route, which forwards to the backend's new
stock-notifications collection. Threaded product.numericId (not the
commerce slug id) into AddToCartButton/AddToCartInlineButton for this,
same split WishlistButton already uses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 19:48:00 +00:00
Marco ce3ac3e79a Sync the cart to an already-logged-in device, not just at login
Cross-device sync only ever worked one way (local→server on every
change via CartSync) plus a server→local merge at the exact moment of
login/register — a customer already signed in on a second device (no
fresh login action happening) never picked up changes made elsewhere.

Adds pullServerCart(): idempotent (server qty overwrites a matching
local line rather than adding to it, safe to call repeatedly, unlike
mergeServerCartIntoLocal()'s additive merge which only stays correct
right after a login clears the local cart's ambiguity). Triggered on
CartSync's mount and on window focus — covers "open the site while
already logged in" and "switch back to this tab after changing the
cart on another device," without a polling interval.
2026-08-01 10:34:02 +00:00
Marco 77a9ef27a9 Make Spotlight's wishlist heart hover-only, matching the shop grid
Was always visible in ProductSpotlight.tsx, unlike ProductGrid.tsx's
cards — inconsistent for what's still a single card in a hoverable
context. The `group` ancestor needed for revealOnHover already exists
there (used for the image's hover-zoom effect), so this is just the
prop.
2026-08-01 10:28:51 +00:00
Marco aabfad3541 Replace Unicode "←" with an SVG icon in cart's "Weiter einkaufen" links
Same font-fallback bug ArrowRightIcon.tsx already documents and fixed
for "→" elsewhere (Tools.tsx, Blog.tsx, ProductGrid.tsx) — the site's
custom web fonts don't cover U+2190, so it fell back to a system font
whose vertical metrics sit visibly low on mobile. Missed in that
earlier pass since CartContent.tsx uses the left-pointing twin.
2026-08-01 08:32:19 +00:00
Marco e49dacf057 Fix search overlay CSS trap and blog filter navigation race
SearchOverlay was rendered as a <header> descendant, so the header's
conditional backdrop-blur-md (once scrolled) made it the containing
block for the overlay's fixed positioning, clipping the opaque
background to the header's height and letting page content show
through underneath. Now rendered as a header sibling, same pattern
already used for NewsletterModal.

Blog category chips are now a client component gating navigation
behind useTransition, disabling the chips while a navigation is
pending so rapid clicks can't fire overlapping RSC navigations that
commit out of order and briefly show an empty result.
2026-08-01 06:17:09 +00:00
Marco 8b636bef25 Add the same badge pulse animation to the wishlist icon as the cart icon
Same mount-guard pattern as CartLink's own pulse fix — useWishlist()'s
count only fills in after its client-side fetch resolves, so without the
guard this would have pulsed on every page load too, not just a real
add/remove during the session. Fires on either direction (add or
remove) since the wishlist has no separate "flying" animation like the
cart's ball-to-icon effect to lean on instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018unaXmuzVA8ct1b6WoyP1U
2026-07-31 22:54:42 +00:00
Marco b88a8edd35 Fix legal-page autolink color, add Blog breadcrumb, revert breadcrumb color
- RichText.tsx: Lexical's auto-detected "autolink" nodes (a typed-out
  email/URL, as opposed to an editor-inserted link) fell through to
  Payload's unstyled default converter — only "link" was overridden.
  AGB's Vertragspartner email rendered as plain black text because of
  this. Both node types now get the same text-brand/hover:underline
  treatment.
- Breadcrumb color revert: the breadcrumb Startseite links across the
  4 legal pages/shop should stay their original hover:text-brand
  treatment — only the actual content/contact links needed the
  always-brand-colored style, not page-level breadcrumbs.
- /blog was the one page missing both a breadcrumb and the
  text-h-feature heading size every other section page (/shop, legal
  pages) already uses — added to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018unaXmuzVA8ct1b6WoyP1U
2026-07-31 22:50:48 +00:00
Marco 7c26bf7b7c Fix cart-badge false pulse, blog hero-card misattribution, legal breadcrumb color
- Cart badge pulsed on every page load/reload, not just real cart
  changes — useCartCount's SSR snapshot is always 0, so hydration alone
  satisfied the "count increased" check. Skip the very first effect run.
- WishlistButton: touch-device smaller/subtler sizing now applies
  regardless of revealOnHover (e.g. the homepage spotlight heart), not
  just the shop grid's hover-reveal mode.
- /blog: the big featured-style hero card was applied to whatever post
  sorted first post-filter, even when that post isn't actually the
  `featured` one (a category filter can exclude the real featured post
  entirely). Now gated on the post's own `featured` flag.
- Legal pages' "Startseite" breadcrumb link now matches the same
  text-brand/hover:underline treatment already used elsewhere on these
  pages (e.g. Impressum's Anbieter email link), instead of staying muted
  until hover.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018unaXmuzVA8ct1b6WoyP1U
2026-07-31 22:46:24 +00:00
Marco bf6c5d079a Fix order-filter dropdowns, restructure Konto identity line, drop blog reset link
- CustomSelect.tsx: mousedown on an option blurred the trigger button
  before the click landed, unmounting the list before anything could be
  selected — add preventDefault on the list's mousedown to stop that.
- KontoShell no longer renders "Eingeloggt als ..." above each page's
  content; each page renders its own title then AccountIdentity right
  below it, and ProfileForm's now-duplicate email/Kundennummer line is
  removed.
- Blog category filter: drop the separate "Zurücksetzen" link — clicking
  an active category badge again already deactivates it.
- Werkzeuge cards: more vertical gap between stacked cards below sm:.
- Legal pages' "Stand: ..." line is now derived from the LegalPages doc's
  own updatedAt instead of a hand-typed string.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018unaXmuzVA8ct1b6WoyP1U
2026-07-31 22:29:36 +00:00
Marco d018d87e25 Fix account-nav feedback, blog filter position, and small icon polish
- Remove Merkliste from AccountNav — the Navbar's own wishlist icon already covers it, a second nav entry was redundant.
- Fix duplicate "Mein Profil" heading (ProfileForm already renders its own; KontoShell no longer adds a second one).
- "Eingeloggt als ..." now renders once in KontoShell, consistently on every /konto/* page, instead of only on the orders page.
- Blog category filter chips moved into the hero's own text column (was a separate bar below the entire hero including the photo) — visible immediately on every width instead of requiring a scroll past the hero image first.
- ArrowRightIcon's arrowhead wings shortened (looked like a generic oversized chevron at full length).
- Search overlay's "Esc" text button replaced with an X icon; the actual Escape keyboard shortcut is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 22:13:08 +00:00
Marco 6f033d52b5 Add persistent account navigation (sidebar/tabs); fix CTA arrow on Android
Konto-Aktionen links (profile, wishlist, logout) used to live at the bottom of the orders page's own content — unreachable once the order list got long enough. New KontoShell + AccountNav give every /konto/* page a persistent sidebar (sm+) / tab bar (mobile) instead, always reachable regardless of list length. The order filters also collapse behind a "Filter" toggle on mobile now, since the tab bar above them left little room for 3 full-width dropdowns.

Also: replaced the Unicode "→" arrow in CTA links (Tools.tsx, Blog.tsx, ProductGrid.tsx) with an SVG icon — the glyph isn't covered by the site's custom fonts, so browsers fall back to a system font per-platform; confirmed sitting visibly low relative to the label text on Android/Chrome (Galaxy S22), not reproducible in desktop Chromium. An SVG has no font-fallback path, so it renders identically everywhere.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 21:58:04 +00:00
Marco 92727ff22a Keep icon touch targets full-size; shrink logo instead; fluid nav gap
Revert the 40px icon-shrink below 375px (hard to tap accurately) and reclaim that space from the logo instead (76-130px depending on width, vs the always-181px controls). Re-verified overflow-free at 320px via Playwright with all 4 icons at full 44px.

Also: wishlist heart's touch-friendly smaller/subtler size now applies to already-wishlisted (filled) hearts too, not just not-yet-wishlisted ones. The wishlist page's login redirect now returns to the wishlist instead of the generic account/orders overview. Desktop nav-link gap now scales fluidly between 1024-1300px instead of a fixed 48px that felt too wide right where nav labels have the least room.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 21:20:01 +00:00
Marco 1a3a8ba20e Show search/wishlist icons on all screen widths, not just sm+
Both were deliberately hidden below 640px to avoid icon-row overflow. Verified via an actual Playwright viewport sweep (320-640px) that simply un-hiding them did overflow — the header's fixed 181px logo + 32px padding left no room. Fixed properly: logo shrinks to 130px and padding drops to px-4 below sm, plus a 40px icon-size step below 375px. Re-verified overflow-free down to 320px.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 21:08:11 +00:00
Marco b144758b82 Restore a light background on mobile wishlist hearts
Fully transparent made the heart invisible against some product photos — keeps it smaller than the default but with a semi-transparent (60%) background circle instead of none, and stops dimming the icon itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 20:53:15 +00:00
Marco 92c6953724 Make mobile wishlist hearts smaller/subtler instead of full-size everywhere
Full-size, always-visible hearts on touch devices recreated the exact "a heart on every card" visual noise revealOnHover exists to avoid. Touch devices now get a smaller, backgroundless, slightly transparent heart instead — present and tappable everywhere, but visually quiet. Desktop hover-reveal behavior is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 20:49:25 +00:00
Marco 9bf6e3f4f2 Fix wishlist heart invisible on mobile product grids
revealOnHover relied purely on group-hover/group-focus-within, both mouse/keyboard-only pseudo-classes with no reliable touch equivalent — a tap on a card never reliably triggers group-hover the way a mouse hover does, leaving the heart permanently hidden on touch devices. Adds pointer-coarse:opacity-100 so it's always visible there; desktop hover-reveal behavior is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 20:43:40 +00:00
Marco 51e4860fe3 Add spotlight wishlist toggle and shop filter/grid polish
Per-product spotlightShowWishlist opt-in (independent of the global
wishlistEnabled toggle), a reveal-on-hover WishlistButton variant for
multi-card grids (avoids a heart on every card reading as visual noise),
and related PriceRangeFilter/ProductGrid/CustomSelect adjustments.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 13:00:55 +00:00
Marco ee39a5d7dc Use CustomSelect for checkout's country pickers too
Promoted CustomSelect from konto/bestellungen/components/ to a shared
app/components/ location. New includeAllOption (checkout doesn't want
a "clear" pseudo-option — a country is always genuinely selected) and
fullWidth (matches the other w-full form fields, no sm: shrink) props.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 23:15:08 +00:00
Marco bc40e22910 Gate search behind CompanySettings.searchEnabled; fix two reported bugs
Search icon now gated behind the new backend toggle, same pattern as
wishlistEnabled — off by default.

Fixed: blog category filter bar was invisible — it was wrapped in
<Reveal>, which sits right at/just past the hero's bottom edge, the
exact "already near the initial viewport" position Reveal.tsx's own
comment documents as a whileInView(margin:"-80px") trap (an element
already visible without scrolling can permanently never register as
"entered view", since `once: true` never gets a second chance). Now a
plain div, no scroll-reveal animation needed for a filter bar anyway.

Fixed: wishlist count showing one behind on the Navbar badge —
useWishlist.ts's toggle() broadcast the "refetch me" event immediately
after the optimistic local update, before the POST request was even
sent. Another instance's resulting refetch could race the actual
server-side write, get the pre-toggle list, and then never get told to
refetch again. Broadcast now only fires after the request settles.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 22:51:37 +00:00