diff --git a/app/der-alltagsstift/components/Focus.tsx b/app/der-alltagsstift/components/Focus.tsx new file mode 100644 index 0000000..dad572d --- /dev/null +++ b/app/der-alltagsstift/components/Focus.tsx @@ -0,0 +1,75 @@ +import Image from "next/image"; +import { Reveal } from "../../components/Reveal"; +import { getProductBySlug } from "../../lib/payload"; + +// Only visually-verifiable claims (Metallkorpus/Chromringe from the +// product photos) — no engraving/color claim, both still unconfirmed +// (see plan notes: Farbvariante/Gravur-Merkmal open as of 2026-08-24). +const bullets = [ + "Korpus aus Metall, kein Plastik", + "Liegt gut in der Hand", + "Passt in jede Tasche", +]; + +// Same fix/reasoning as Hero.tsx's own IconCheck. +function IconCheck() { + return ( + + + + ); +} + +// Unlike todo-cards/components/Focus.tsx, this product has no dedicated +// lifestyle photo yet — only 3 supplier catalog macro shots exist, all +// 338×338px (no larger source, confirmed against the Payload media API). +// Rather than object-cover-stretching a small square into a wide rectangle +// (blurs badly, and is exactly what Pricing.tsx's photo slot already does — +// see plan notes on the "einheitlich" feedback), this uses a contained, +// padded square tile so the source resolution reads as a deliberate macro +// crop instead of a blown-up thumbnail. Uses gallery[1] (the chrome-rings +// close-up) — deliberately not gallery[0], which shows an assortment +// including a black pen and would imply a color variant that isn't +// confirmed yet (see Focus's bullets comment). +export async function Focus() { + const product = await getProductBySlug("stift-kugelschreiber"); + if (!product) return null; + const photo = product.gallery[1] ?? product.image; + + return ( +
+ {/* Fixed max-width, not a viewport-percentage width — same reasoning + as Hero.tsx's gallery cap: the source is only 338×338px, and a + percentage width grows past that on wide screens, exaggerating + the upscale blur. */} + + {product.name} + + +

+ Ein Stift, der bleibt +

+

+ Die meisten Kugelschreiber landen irgendwann in einer Schublade und werden nicht wieder rausgeholt. Der Alltagsstift soll man behalten. +

+ +
+
+ ); +} diff --git a/app/der-alltagsstift/components/Hero.tsx b/app/der-alltagsstift/components/Hero.tsx new file mode 100644 index 0000000..72a95b5 --- /dev/null +++ b/app/der-alltagsstift/components/Hero.tsx @@ -0,0 +1,139 @@ +import Link from "next/link"; +import { AddToCartButton } from "../../components/AddToCartButton"; +import { ProductGallery } from "../../components/ProductGallery"; +import { Reveal } from "../../components/Reveal"; +import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKleinunternehmer } from "../../lib/payload"; +import { formatPrice, discountPercent } from "../../lib/format"; +import { effectiveTaxRate } from "../../lib/cartTotals"; + +const checklist = [ + "Funktioniert ohne Strom oder Internet", + "Aus Metall, kein Einwegstift", + "Passt in 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 ( + + + + ); +} + +// Same "compact early teaser + delivery-time repeated next to every buy +// button" reasoning as TodoKartenHero.tsx. +export async function Hero() { + const [product, shipping, defaultTaxRate, kleinunternehmer] = await Promise.all([ + getProductBySlug("stift-kugelschreiber"), + getShippingSettings(), + getDefaultTaxRatePercent(), + getKleinunternehmer(), + ]); + const discount = product ? discountPercent(product.price, product.compareAtPrice) : null; + const taxRate = product ? effectiveTaxRate(product, defaultTaxRate) : null; + const anyLowStock = product + ? product.active && (product.variants.length > 0 ? product.variants.some((v) => v.lowStock) : product.lowStock) + : false; + + return ( +
+
+ +

+ + Startseite + + + + Werkzeuge + + + Der Alltagsstift +

+ +
+
+

+ Der Alltagsstift. +

+

+ Ein Stift, der einfach da ist, wenn du ihn brauchst. +

+
+ +

+ Der Kugelschreiber für die eine Sache, die heute zählt. Schlicht, hochwertig und immer griffbereit. +

+ +
    + {checklist.map((item) => ( +
  • + + {item} +
  • + ))} +
+ +
+ {product && ( +
+ {discount !== null && ( +

{formatPrice(product.compareAtPrice!)}

+ )} +

{formatPrice(product.price)}

+

+ {kleinunternehmer + ? product.noShippingCost + ? "Keine Versandkosten" + : "zzgl. Versand" + : `inkl. ${taxRate}% MwSt. ${product.noShippingCost ? "– keine Versandkosten" : "zzgl. Versand"}`} +

+
+ )} + {!product?.noShippingCost && ( +

+ Lieferzeit: {shipping.totalDays.min}–{shipping.totalDays.max} Werktage innerhalb Deutschlands +

+ )} + {anyLowStock &&

Nur noch wenige verfügbar

} +
+ + {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. */} + {product && product.gallery.length > 0 && ( + +
+ +
+
+ )} +
+
+ ); +} diff --git a/app/der-alltagsstift/components/HowItWorks.tsx b/app/der-alltagsstift/components/HowItWorks.tsx new file mode 100644 index 0000000..18fa315 --- /dev/null +++ b/app/der-alltagsstift/components/HowItWorks.tsx @@ -0,0 +1,72 @@ +import { Fragment } from "react"; +import Image from "next/image"; +import { Reveal, RevealGroup, RevealItem } from "../../components/Reveal"; +import { StepArrow } from "../../components/StepArrow"; + +// Same 3 generic step icons as todo-cards/components/HowItWorks.tsx — their +// artwork (pen tip, checklist, arrow) reads generically enough to carry a +// different 3-step flow without looking mismatched. +const steps = [ + { + icon: "/icon-step-1.png", + width: 165, + height: 177, + title: "1. Griffbereit halten", + desc: "Der Alltagsstift liegt dort, wo du ihn brauchst – Tasche, Schreibtisch, Notizbuch.", + }, + { + icon: "/icon-step-2.png", + width: 180, + height: 168, + title: "2. Sofort notieren", + desc: "Du musst nicht erst dein Handy entsperren oder eine App öffnen.", + }, + { + icon: "/icon-step-3.png", + width: 180, + height: 177, + title: "3. Weitermachen", + desc: "Notiert ist notiert, du machst einfach da weiter, wo du warst.", + }, +]; + +export function HowItWorks() { + return ( +
+ +

+ So einfach ist er im Alltag +

+

+ Du nimmst ihn, schreibst, steckst ihn wieder ein. Mehr braucht es nicht. +

+
+ + + {steps.map((step, i) => ( + + + +

{step.title}

+

{step.desc}

+
+ {i < steps.length - 1 && ( +
+ +
+ )} +
+ ))} +
+
+ ); +} diff --git a/app/der-alltagsstift/components/Pricing.tsx b/app/der-alltagsstift/components/Pricing.tsx new file mode 100644 index 0000000..978126d --- /dev/null +++ b/app/der-alltagsstift/components/Pricing.tsx @@ -0,0 +1,116 @@ +import Image from "next/image"; +import { AddToCartButton } from "../../components/AddToCartButton"; +import { Reveal } from "../../components/Reveal"; +import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKleinunternehmer } from "../../lib/payload"; +import { formatPrice, discountPercent } from "../../lib/format"; +import { effectiveTaxRate } from "../../lib/cartTotals"; + +// Only visually-verifiable claims — same reasoning as Focus.tsx's bullets +// (no engraving/color claim, both unconfirmed as of 2026-08-24). +const bullets = [ + "Kugelschreiber aus Metall", + "Chromfarbene Details", + "Handlich, für jede Tasche", +]; + +// 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. +export async function Pricing() { + const [product, shipping, defaultTaxRate, kleinunternehmer] = await Promise.all([ + getProductBySlug("stift-kugelschreiber"), + getShippingSettings(), + getDefaultTaxRatePercent(), + getKleinunternehmer(), + ]); + if (!product) return null; + const discount = discountPercent(product.price, product.compareAtPrice); + const taxRate = effectiveTaxRate(product, defaultTaxRate); + const fullyOutOfStock = + !product.active || (product.variants.length > 0 ? product.variants.every((v) => v.outOfStock) : product.outOfStock); + const anyLowStock = product.active && (product.variants.length > 0 ? product.variants.some((v) => v.lowStock) : product.lowStock); + + return ( +
+ + {/* object-contain on a bg-bg-base tile, not object-cover — the + source is a 338×338px supplier macro shot (no larger original + exists), so stretching it across a 410×227 landscape crop like + before blows it up and blurs it. A contained square reads as a + deliberate close-up instead. Uses product.image (id 85, the + confident macro tip shot) — distinct from Focus.tsx's photo + (gallery[1], the chrome-rings shot) so the two sections don't + repeat the same frame. */} +
+ Der Alltagsstift + {fullyOutOfStock ? ( + + Ausverkauft + + ) : ( + discount !== null && ( + + -{discount}% + + ) + )} +
+ +
+

+ Der Alltagsstift +

+
    + {bullets.map((b) => ( +
  • + + {b} +
  • + ))} +
+
+ +
+
+
+ {discount !== null && ( +

{formatPrice(product.compareAtPrice!)}

+ )} +

{formatPrice(product.price)}

+
+

+ {kleinunternehmer + ? product.noShippingCost + ? "Keine Versandkosten" + : "zzgl. Versand" + : `inkl. ${taxRate}% MwSt. ${product.noShippingCost ? "– keine Versandkosten" : "zzgl. Versand"}`} +

+ {!product.noShippingCost && ( +

+ Lieferzeit: {shipping.totalDays.min}–{shipping.totalDays.max} Werktage innerhalb Deutschlands +

+ )} +
+ {anyLowStock &&

Nur noch wenige verfügbar

} + +
+
+
+ ); +} diff --git a/app/der-alltagsstift/page.tsx b/app/der-alltagsstift/page.tsx new file mode 100644 index 0000000..4074804 --- /dev/null +++ b/app/der-alltagsstift/page.tsx @@ -0,0 +1,54 @@ +import type { Metadata } from "next"; +import { Hero } from "./components/Hero"; +import { HowItWorks } from "./components/HowItWorks"; +import { Focus } from "./components/Focus"; +import { Pricing } from "./components/Pricing"; +import { Footer } from "../components/Footer"; +import { getProductBySlug, getCompanySettings } from "../lib/payload"; +import { buildProductSchema } from "../lib/structuredData"; + +const title = "Der Alltagsstift – Ein Stift, der einfach da ist, wenn du ihn brauchst."; +const description = + "Der Kugelschreiber für die eine Sache, die heute zählt. Schlicht, hochwertig und immer griffbereit."; + +export const metadata: Metadata = { + title, + description, + alternates: { + canonical: "/der-alltagsstift", + }, + openGraph: { + title, + description, + url: "/der-alltagsstift", + }, + twitter: { + title, + description, + }, +}; + +export default async function DerAlltagsstiftPage() { + const [product, seller] = await Promise.all([ + getProductBySlug("stift-kugelschreiber"), + getCompanySettings(), + ]); + const productSchema = product + ? buildProductSchema(product, "https://einfach-produktiv.mk360.de/der-alltagsstift", seller) + : null; + + return ( + <> + {productSchema && ( +