From 71cd75425df2a0df0bb69bf4fd6e04d2776f0b30 Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 25 Aug 2026 13:10:00 +0000 Subject: [PATCH] Redesign Der Eine. PDP from mockup, reusing existing site patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sections rebuilt to match der-eine.png (hero, tagline banner, brand-story + feature icons, lifestyle photo, final CTA bar with trust badges), but each piece is built from patterns that already exist elsewhere on the site rather than one-off layouts: - Focus.tsx (feature icons) mirrors todo-cards/HowItWorks.tsx's exact icon-top/title/desc card shape and typography - Why.tsx (brand story) mirrors todo-cards/Focus.tsx's photo+text layout - Pricing.tsx's trust-badge row reuses getCartTrustBadges() (real, already-vetted copy) with /cart's own markup, not invented text - Banner.tsx reuses --font-caveat and /icon-separator-right.svg (both already used in Divider.tsx) instead of introducing new assets Photos: product.image/gallery repointed (via direct DB write, see reference_payload_direct_db_access) to two new Payload media docs cropped directly from the mockup — the previous gallery was mismatched supplier stock photos of a different-colored pen. Lifestyle.tsx's desk photo is a static /public asset (page decoration showing the pen next to another product, not a photo of the pen alone, so it doesn't belong in Products.gallery). --- app/der-eine/components/Banner.tsx | 28 +++++++ app/der-eine/components/Focus.tsx | 115 ++++++++++++++------------ app/der-eine/components/Hero.tsx | 67 ++++----------- app/der-eine/components/Lifestyle.tsx | 22 +++++ app/der-eine/components/Pricing.tsx | 29 +++++-- app/der-eine/components/Why.tsx | 43 +++++++--- app/der-eine/page.tsx | 10 ++- public/der-eine-lifestyle.png | Bin 0 -> 463042 bytes 8 files changed, 187 insertions(+), 127 deletions(-) create mode 100644 app/der-eine/components/Banner.tsx create mode 100644 app/der-eine/components/Lifestyle.tsx create mode 100644 public/der-eine-lifestyle.png diff --git a/app/der-eine/components/Banner.tsx b/app/der-eine/components/Banner.tsx new file mode 100644 index 0000000..809a30c --- /dev/null +++ b/app/der-eine/components/Banner.tsx @@ -0,0 +1,28 @@ +import Image from "next/image"; +import { Reveal } from "../../components/Reveal"; + +// A full-bleed yellow tagline strip between Hero and the "Warum" section — +// no equivalent banner/CTA-strip component exists elsewhere on the site +// (checked), so the container itself is new, but reuses two real sitewide +// building blocks instead of inventing them: the --font-caveat handwritten +// typeface (Divider.tsx/About.tsx) and /icon-separator-right.svg (also +// Divider.tsx's own "Leichtigkeit" sparkle icon), rotated to point down-right. +export function Banner() { + return ( +
+ +
+ +
+

+ Ein guter Stift. +
+ Mehr muss er nicht sein. +

+
+
+ ); +} diff --git a/app/der-eine/components/Focus.tsx b/app/der-eine/components/Focus.tsx index 6c60049..f12ffb4 100644 --- a/app/der-eine/components/Focus.tsx +++ b/app/der-eine/components/Focus.tsx @@ -1,62 +1,69 @@ -import Image from "next/image"; -import { Reveal } from "../../components/Reveal"; -import { getProductBySlug } from "../../lib/payload"; +import { Reveal, RevealGroup, RevealItem } from "../../components/Reveal"; -// Distinct from Hero.tsx's own (shorter) feature bullets — this is the -// "Was kann er?" section's full four-item breakdown (build, engraving, -// size, use case), each with its own one-line explanation rather than a -// bare phrase. +// Simple stroke-only line icons — no matching /public asset existed for +// these concepts (checked: pencil/pen/sparkle/shield), so these are drawn +// fresh rather than upscaling a raster crop from the mockup, same reasoning +// as Hero.tsx's own IconCheck (crisp at any size, recolorable). +function IconPencil() { + return ( + + + + + ); +} + +function IconPen() { + return ( + + + + + ); +} + +function IconSparkle() { + return ( + + + + ); +} + +function IconShield() { + return ( + + + + ); +} + +// This page's single feature enumeration (Hero has no bullet list anymore). +// Same card shape as todo-cards/components/HowItWorks.tsx — icon on top, +// centered, RevealGroup/RevealItem stagger, identical title/description +// typography (font-semibold text-body / text-body-sm text-text-primary) — +// reused here instead of a one-off layout, for visual consistency with the +// rest of the site. 4 cards instead of HowItWorks' 3 numbered steps, no +// connecting arrows (these are independent facts, not a sequence). const items = [ - { title: "Metallgehäuse", desc: "Robust, wertig und angenehm in der Hand." }, - { title: "Lasergravur", desc: "„Der Eine.“ auf der einen Seite. „einfach produktiv.“ auf der anderen." }, - { title: "Kompakt", desc: "Passt ans Notizbuch, auf den Schreibtisch oder in die Tasche." }, - { title: "Für jeden Tag", desc: "Für Notizen, Gedanken, Listen und alles, was du lieber aufschreibst, bevor du es wieder vergisst." }, + { icon: IconPencil, title: "Metallgehäuse", desc: "Robust, wertig und angenehm in der Hand." }, + { icon: IconPen, title: "Schwarze Mine", desc: "Klassische Kugelschreibermine für ein sauberes Schriftbild." }, + { icon: IconSparkle, title: "Lasergravur", desc: "„Der Eine.“ und „einfach produktiv.“" }, + { icon: IconShield, title: "Passt überall rein", desc: "Kompakt und leicht – für Tasche, Etui, Notizbuch." }, ]; -// Unlike todo-cards/components/Focus.tsx, this product has no dedicated -// lifestyle photo — only 3 supplier catalog macro shots exist, all -// 338×338px (no larger source, confirmed against the Payload media API). -// Uses gallery[1] (the chrome-rings close-up) — deliberately a different -// frame than Hero's gallery (which opens on `image`, id 85) and Pricing's -// own photo (product.image again, but cropped/treated differently there), -// so the same handful of source photos don't read as one repeated image -// across the page. Contained square tile, not object-cover, for the same -// "don't blow up a 338px source" reasoning as Hero's gallery cap. -export async function Focus() { - const product = await getProductBySlug("stift-kugelschreiber"); - if (!product) return null; - const photo = product.gallery[1] ?? product.image; - +export function Focus() { return ( -
- - {product.name} - - -

- Was kann er? -

-

- Schreiben. Und das ziemlich gut. Metallgehäuse, angenehmes Gewicht und eine klassische Kugelschreibermine — kein besonderes Produktivitäts-Gadget, sondern einfach ein guter Stift, den du gern zur Hand nimmst. -

-
    - {items.map((item) => ( -
  • - {item.title} - {item.desc} -
  • - ))} -
-
+
+ + {items.map(({ icon: Icon, title, desc }) => ( + + +

{title}

+

{desc}

+
+ ))} +
); } diff --git a/app/der-eine/components/Hero.tsx b/app/der-eine/components/Hero.tsx index 5908698..086488e 100644 --- a/app/der-eine/components/Hero.tsx +++ b/app/der-eine/components/Hero.tsx @@ -6,23 +6,10 @@ import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKle import { formatPrice, discountPercent } from "../../lib/format"; import { effectiveTaxRate } from "../../lib/cartTotals"; -// The one bullet list on this page — after feedback that three separate -// lists across Hero/Focus/Pricing was too much, this is the single place -// features get enumerated, everywhere else stays prose. A subset of the -// "Was kann er?" section's four cards (Focus.tsx) — "Für jeden Tag" is a -// use case, not a build fact, so it's covered by the body copy instead. -const features = ["Massives Metallgehäuse", "Lasergravur: „Der Eine.“ & „einfach produktiv.“", "Kompakt genug für jede Tasche"]; - -// Same fix/reasoning as TodoKartenHero.tsx's IconCheck — icon-check.svg's -// fill can't be recolored from outside the SVG when loaded via /next/image, so this stays an inline stroke path per page. -function IconCheck() { - return ( - - - - ); -} +// No feature bullet list here (unlike this page's earlier draft) — matches +// the mockup's minimal hero (headline, short 3-line description, price, +// buy button only). The material/engraving/size facts live in Focus.tsx's +// icon row instead, one enumeration point instead of two. // Same "compact early teaser + delivery-time repeated next to every buy // button" reasoning as TodoKartenHero.tsx. @@ -56,33 +43,16 @@ export async function Hero() {

-
-

- Der Eine. -

-

- Für die eine Sache, die gerade zählt. -

-
- -

- Manchmal reicht ein Stift und ein Stück Papier. Der Eine. ist ein schlichter Kugelschreiber aus Metall — angenehm in der Hand, klein genug für die Tasche und gemacht für alles, was kurz raus aus dem Kopf und irgendwo hin muss. +

+ Der Eine.

-
    - {features.map((item) => ( -
  • - - {item} -
  • - ))} -
+

+ Der Kugelschreiber zu unseren ToDo-Karten. Aus Metall, mit schwarzer Mine und „Der Eine.“ auf der Seite. +

{product && ( @@ -110,7 +80,7 @@ export async function Hero() { {product && ( - {/* max-w cap, not the full lg:col-span-7 width — the source photos - are only 338×338px (checked directly against the Payload media - API, no larger size exists), so letting ProductGallery stretch - to the full column blows them up hard. Capping the display - width keeps the upscale factor small enough that it doesn't - look broken; it can't fix the underlying resolution, only hide - it. Real fix is new source photos, not a layout tweak. */} + {/* max-w cap, not the full lg:col-span-7 width — image/gallery are + still fairly small source crops (~594×370, ~480×230, taken + 2026-08-25 from the mockup design), so letting ProductGallery + stretch to the full column would still upscale noticeably. */} {product && product.gallery.length > 0 && (
diff --git a/app/der-eine/components/Lifestyle.tsx b/app/der-eine/components/Lifestyle.tsx new file mode 100644 index 0000000..f750305 --- /dev/null +++ b/app/der-eine/components/Lifestyle.tsx @@ -0,0 +1,22 @@ +import Image from "next/image"; +import { Reveal } from "../../components/Reveal"; + +// Full-bleed desk photo (ToDo-Karten notepad + "Der Eine." + notebook) — +// a static /public asset, not a Payload product photo: it's page decoration +// showing the pen alongside another product, not a photo *of* this product +// alone, so it doesn't belong in Products.gallery (which ProductGallery.tsx +// treats as swappable thumbnails of just the pen itself). Cropped from the +// der-eine.png mockup, 2026-08-25. +export function Lifestyle() { + return ( + + Der Eine. neben den ToDo-Karten und einem Notizbuch auf dem Schreibtisch + + ); +} diff --git a/app/der-eine/components/Pricing.tsx b/app/der-eine/components/Pricing.tsx index 15852f4..29e4754 100644 --- a/app/der-eine/components/Pricing.tsx +++ b/app/der-eine/components/Pricing.tsx @@ -1,21 +1,21 @@ import Image from "next/image"; import { AddToCartButton } from "../../components/AddToCartButton"; import { Reveal } from "../../components/Reveal"; -import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKleinunternehmer } from "../../lib/payload"; +import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKleinunternehmer, getCartTrustBadges } from "../../lib/payload"; import { formatPrice, discountPercent } from "../../lib/format"; import { effectiveTaxRate } from "../../lib/cartTotals"; // Price/photo come from Payload (same "stift-kugelschreiber" product the // shop/cart use), not a hardcoded literal — same reasoning as -// todo-cards/components/Pricing.tsx. Uses product.image (id 85, the -// confident macro tip shot) — a third, distinct frame from Hero's gallery -// opener and Focus's gallery[1] chrome-rings shot. +// todo-cards/components/Pricing.tsx. Uses product.image (the mockup-derived +// hero crop, see media ids 88/89 uploaded 2026-08-25). export async function Pricing() { - const [product, shipping, defaultTaxRate, kleinunternehmer] = await Promise.all([ + const [product, shipping, defaultTaxRate, kleinunternehmer, trustBadges] = await Promise.all([ getProductBySlug("stift-kugelschreiber"), getShippingSettings(), getDefaultTaxRatePercent(), getKleinunternehmer(), + getCartTrustBadges(), ]); if (!product) return null; const discount = discountPercent(product.price, product.compareAtPrice); @@ -55,7 +55,7 @@ export async function Pricing() { > Der Eine.

-

Metall. Kugelschreiber. Fertig.

+

Metall. Kugelschreiber. Für jeden Tag.

@@ -81,7 +81,7 @@ export async function Pricing() {
{anyLowStock &&

Nur noch wenige verfügbar

} + {/* Same content (getCartTrustBadges()) and markup as /cart's + title-only row (CartContent.tsx) — reuses the real, + already-vetted badge copy ("Nachhaltig verpackt" etc.) + instead of inventing new text to pixel-match the mockup's + placeholder wording. */} + {trustBadges.length > 0 && ( +
+ {trustBadges.map((badge) => ( +
+ + {badge.title} +
+ ))} +
+ )}
diff --git a/app/der-eine/components/Why.tsx b/app/der-eine/components/Why.tsx index 841674f..e06c52a 100644 --- a/app/der-eine/components/Why.tsx +++ b/app/der-eine/components/Why.tsx @@ -1,15 +1,32 @@ +import Image from "next/image"; import { Reveal } from "../../components/Reveal"; +import { getProductBySlug } from "../../lib/payload"; + +// The brand-story section explaining the product's name. Same photo+text +// layout as todo-cards/components/Focus.tsx (identical classes/structure, +// reused for visual consistency rather than a one-off design) — the photo +// is product.gallery[0], an engraving close-up ("einfach produktiv." on the +// pen barrel) uploaded specifically for this section, see the mockup-derived +// media uploaded 2026-08-25 (ids 88/89 replacing the earlier mismatched +// green-pen supplier photos). +export async function Why() { + const product = await getProductBySlug("stift-kugelschreiber"); + const photo = product?.gallery[0] ?? product?.image ?? null; -// Plays the same structural role as todo-cards/components/HowItWorks.tsx (a -// content section between Hero and the material/Pricing sections, matching -// the site's established product-page rhythm) — but this is the brand-story -// section explaining the product's name, so flowing prose fits better than -// this file's former 2×2 scenario grid (replaced 2026-08-25 alongside the -// Der Alltagsstift → Der Eine. rename). -export function Why() { return ( -
- +
+ {photo && ( + + Gravur „einfach produktiv.“ auf dem Stiftkorpus + + )} +

- Weil du nicht alles gleichzeitig machen kannst. + Unsere ToDo-Karten helfen dir, dich auf das zu konzentrieren, was heute wichtig ist.

- Aufschreiben hilft dabei, aus all den Dingen im Kopf das herauszuholen, was gerade wichtig ist. Und manchmal ist das eben nur: die eine Sache. + Der Stift greift diese Idee auf.

- Daher steht auf der einen Seite des Stifts Der Eine. und auf der anderen einfach produktiv. + Auf der einen Seite steht „Der Eine.“, auf der anderen „einfach produktiv.“

- Mehr steckt eigentlich nicht dahinter. Und vielleicht ist genau das der Punkt. + Mehr Geschichte braucht es eigentlich nicht.

diff --git a/app/der-eine/page.tsx b/app/der-eine/page.tsx index 9915a3b..16dd79e 100644 --- a/app/der-eine/page.tsx +++ b/app/der-eine/page.tsx @@ -1,15 +1,17 @@ import type { Metadata } from "next"; import { Hero } from "./components/Hero"; +import { Banner } from "./components/Banner"; import { Why } from "./components/Why"; import { Focus } from "./components/Focus"; +import { Lifestyle } from "./components/Lifestyle"; import { Pricing } from "./components/Pricing"; import { Footer } from "../components/Footer"; import { getProductBySlug, getCompanySettings } from "../lib/payload"; import { buildProductSchema } from "../lib/structuredData"; -const title = "Der Eine. – Für die eine Sache, die gerade zählt."; +const title = "Der Eine. – Der Kugelschreiber zu unseren ToDo-Karten."; const description = - "Der Eine. ist ein schlichter Kugelschreiber aus Metall. Angenehm in der Hand, klein genug für die Tasche und gemacht für alles, was kurz raus aus dem Kopf und irgendwo hin muss."; + "Der Eine. ist ein Kugelschreiber aus Metall, mit schwarzer Mine und „Der Eine.“ auf der Seite — der Stift zu unseren ToDo-Karten."; export const metadata: Metadata = { title, @@ -44,8 +46,10 @@ export default async function DerEinePage() { )}
- + + +