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).
This commit is contained in:
Marco
2026-08-25 12:36:03 +00:00
parent 988f259371
commit 13fb37eb5e
8 changed files with 128 additions and 194 deletions
+4 -1
View File
@@ -60,7 +60,10 @@ function getNavLinks(singleActiveProduct: boolean) {
// mark "Werkzeuge" active in the nav for the same reason — confirmed by
// page-weekly-impulses's own breadcrumb ("Startseite Werkzeuge
// Impulse & Tipps") and active-underline position, same as page-todo-karten's.
const WERKZEUGE_ROUTES = ["/todo-cards", "/lebensuhr", "/newsletter"];
// /der-eine and /tasse-die-pause (both bespoke product detail pages, same
// "own route, not nested" shape as /todo-cards) added here for the same
// reason (2026-08-25).
const WERKZEUGE_ROUTES = ["/todo-cards", "/lebensuhr", "/newsletter", "/der-eine", "/tasse-die-pause"];
function isNavLinkActive(href: string, pathname: string, activeSection: string): boolean {
if (href === "#werkzeuge") {
-75
View File
@@ -1,75 +0,0 @@
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 (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" className="size-5 shrink-0 mt-1">
<path d="M4 10.5l4.5 4.5L16 5.5" stroke="#f6a701" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
// 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 (
<section className="w-full bg-bg-base flex flex-col lg:flex-row gap-10 lg:gap-16 items-center py-12 md:py-16 px-[var(--layout-padding-x)]">
{/* 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. */}
<Reveal className="group relative w-full max-w-[20rem] lg:shrink-0 aspect-square rounded-md overflow-hidden bg-bg-muted p-10">
<Image
src={photo}
alt={product.name}
fill
sizes="320px"
className="object-contain transition-transform duration-500 group-hover:scale-105"
/>
</Reveal>
<Reveal className="flex flex-col gap-6 items-start flex-1 min-w-0 w-full" delay={0.1}>
<p
className="font-semibold text-h-emphasis text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
Ein Stift, der bleibt
</p>
<p className="text-body text-text-body">
Die meisten Kugelschreiber landen irgendwann in einer Schublade und werden nicht wieder rausgeholt. Der Alltagsstift soll man behalten.
</p>
<ul className="flex flex-col gap-3 items-start w-full">
{bullets.map((b) => (
<li key={b} className="flex gap-[0.625rem] items-start w-full">
<IconCheck />
<span className="flex-1 font-semibold text-body text-text-primary">{b}</span>
</li>
))}
</ul>
</Reveal>
</section>
);
}
@@ -1,72 +0,0 @@
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 (
<section className="w-full bg-bg-base flex flex-col gap-12 items-center py-12 sm:py-16 px-[var(--layout-padding-x)]">
<Reveal className="flex flex-col gap-2 items-center text-center">
<p
className="font-semibold text-h-emphasis text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
So einfach ist er im Alltag
</p>
<p className="text-body text-text-muted">
Du nimmst ihn, schreibst, steckst ihn wieder ein. Mehr braucht es nicht.
</p>
</Reveal>
<RevealGroup className="flex flex-col sm:flex-row gap-8 items-center sm:items-start w-full lg:px-[16.25rem]">
{steps.map((step, i) => (
<Fragment key={step.title}>
<RevealItem className="group flex flex-col gap-4 items-center text-center flex-1 max-w-xs sm:max-w-none">
<Image
src={step.icon}
alt=""
width={step.width}
height={step.height}
className="h-16 w-auto object-contain transition-transform duration-300 group-hover:scale-110"
/>
<p className="font-semibold text-body text-text-primary">{step.title}</p>
<p className="text-body-sm text-text-primary text-center">{step.desc}</p>
</RevealItem>
{i < steps.length - 1 && (
<div className="flex items-center justify-center shrink-0 sm:mt-[1.5rem]">
<StepArrow className="w-8 h-8 rotate-90 sm:w-10 sm:h-4 sm:rotate-0" />
</div>
)}
</Fragment>
))}
</RevealGroup>
</section>
);
}
+62
View File
@@ -0,0 +1,62 @@
import Image from "next/image";
import { Reveal } from "../../components/Reveal";
import { getProductBySlug } from "../../lib/payload";
// 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.
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." },
];
// 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;
return (
<section className="w-full bg-bg-base flex flex-col lg:flex-row gap-10 lg:gap-16 items-center py-12 md:py-16 px-[var(--layout-padding-x)]">
<Reveal className="group relative w-full max-w-[20rem] lg:shrink-0 aspect-square rounded-md overflow-hidden bg-bg-muted p-10">
<Image
src={photo}
alt={product.name}
fill
sizes="320px"
className="object-contain transition-transform duration-500 group-hover:scale-105"
/>
</Reveal>
<Reveal className="flex flex-col gap-6 items-start flex-1 min-w-0 w-full" delay={0.1}>
<p
className="font-semibold text-h-emphasis text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
Was kann er?
</p>
<p className="text-body text-text-body">
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.
</p>
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-4 w-full">
{items.map((item) => (
<li key={item.title} className="flex flex-col gap-1 items-start">
<span className="font-semibold text-body text-text-primary">{item.title}</span>
<span className="text-body-sm text-text-muted">{item.desc}</span>
</li>
))}
</ul>
</Reveal>
</section>
);
}
@@ -6,11 +6,12 @@ import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKle
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",
];
// 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 <img
@@ -51,7 +52,7 @@ export async function Hero() {
Werkzeuge
</Link>
<span></span>
<span className="text-text-primary">Der Alltagsstift</span>
<span className="text-text-primary">Der Eine</span>
</p>
<div className="flex flex-col gap-6 items-start w-full flex-1 lg:justify-center">
@@ -60,22 +61,22 @@ export async function Hero() {
className="font-semibold text-h-page text-text-primary"
style={{ fontFamily: "var(--font-playfair)" }}
>
Der Alltagsstift<span className="text-brand">.</span>
Der Eine<span className="text-brand">.</span>
</p>
<p
className="font-semibold text-h3 text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
Ein Stift, der einfach da ist, wenn du ihn brauchst.
Für die eine Sache, die gerade zählt.
</p>
</div>
<p className="text-body text-text-body">
Der Kugelschreiber für die eine Sache, die heute zählt. Schlicht, hochwertig und immer griffbereit.
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.
</p>
<ul className="flex flex-col gap-3 items-start w-full">
{checklist.map((item) => (
{features.map((item) => (
<li key={item} className="flex gap-[0.625rem] items-start w-full">
<IconCheck />
<span className="flex-1 text-body text-text-primary">{item}</span>
@@ -109,7 +110,8 @@ export async function Hero() {
{product && (
<AddToCartButton
label="Alltagsstift bestellen"
label="Der Eine. bestellen"
productId={product.id}
numericId={product.numericId}
outOfStock={!product.active || product.outOfStock}
maxQty={product.maxQty}
@@ -5,17 +5,11 @@ import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKle
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.
// 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.
export async function Pricing() {
const [product, shipping, defaultTaxRate, kleinunternehmer] = await Promise.all([
getProductBySlug("stift-kugelschreiber"),
@@ -33,18 +27,10 @@ export async function Pricing() {
return (
<section className="w-full bg-bg-base px-[var(--layout-padding-x)] py-8">
<Reveal className="bg-bg-muted rounded-md flex flex-col lg:flex-row gap-8 lg:gap-12 items-center p-6 lg:pl-8 lg:pr-10 lg:py-6">
{/* 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. */}
<div className="group relative w-full lg:w-[15.625rem] lg:shrink-0 aspect-square rounded-sm overflow-hidden bg-bg-base">
<Image
src={product.image}
alt="Der Alltagsstift"
alt="Der Eine."
fill
sizes="(min-width: 1024px) 250px, 100vw"
className="object-contain p-4 transition-transform duration-500 group-hover:scale-105"
@@ -67,16 +53,9 @@ export async function Pricing() {
className="font-semibold text-h-small text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
Der Alltagsstift
Der Eine.
</p>
<ul className="flex flex-col gap-[0.375rem] items-start">
{bullets.map((b) => (
<li key={b} className="flex gap-2 items-center">
<Image alt="" src="/icon-bullet-dot.svg" width={4} height={4} className="size-1 shrink-0" />
<span className="text-body-sm text-text-primary">{b}</span>
</li>
))}
</ul>
<p className="text-body-sm text-text-primary">Metall. Kugelschreiber. Fertig.</p>
</div>
<div className="flex flex-col gap-3 items-start w-full lg:w-[18.75rem] lg:shrink-0">
@@ -104,6 +83,7 @@ export async function Pricing() {
<AddToCartButton
label="In den Warenkorb"
className="w-full inline-flex items-center justify-center px-6 py-[0.8125rem] rounded-sm bg-brand hover:bg-brand-hover active:scale-[0.97] transition-all font-bold text-body text-text-primary text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2 focus-visible:ring-offset-bg-muted"
productId={product.id}
numericId={product.numericId}
outOfStock={!product.active || product.outOfStock}
maxQty={product.maxQty}
+34
View File
@@ -0,0 +1,34 @@
import { Reveal } from "../../components/Reveal";
// 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 (
<section className="w-full bg-bg-base flex flex-col gap-6 items-center py-12 sm:py-16 px-[var(--layout-padding-x)]">
<Reveal className="flex flex-col gap-5 items-start text-left max-w-2xl w-full">
<p
className="font-semibold text-h-emphasis text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
Warum Der Eine.?
</p>
<p className="text-body text-text-body">
Weil du nicht alles gleichzeitig machen kannst.
</p>
<p className="text-body text-text-body">
Aufschreiben hilft dabei, aus all den Dingen im Kopf das herauszuholen, was gerade wichtig ist. Und manchmal ist das eben nur: die eine Sache.
</p>
<p className="text-body text-text-body">
Daher steht auf der einen Seite des Stifts <strong className="font-semibold text-text-primary">Der Eine.</strong> und auf der anderen <strong className="font-semibold text-text-primary">einfach produktiv.</strong>
</p>
<p className="text-body text-text-body">
Mehr steckt eigentlich nicht dahinter. Und vielleicht ist genau das der Punkt.
</p>
</Reveal>
</section>
);
}
@@ -1,26 +1,26 @@
import type { Metadata } from "next";
import { Hero } from "./components/Hero";
import { HowItWorks } from "./components/HowItWorks";
import { Why } from "./components/Why";
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 title = "Der Eine. Für die eine Sache, die gerade zählt.";
const description =
"Der Kugelschreiber für die eine Sache, die heute zählt. Schlicht, hochwertig und immer griffbereit.";
"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.";
export const metadata: Metadata = {
title,
description,
alternates: {
canonical: "/der-alltagsstift",
canonical: "/der-eine",
},
openGraph: {
title,
description,
url: "/der-alltagsstift",
url: "/der-eine",
},
twitter: {
title,
@@ -28,13 +28,13 @@ export const metadata: Metadata = {
},
};
export default async function DerAlltagsstiftPage() {
export default async function DerEinePage() {
const [product, seller] = await Promise.all([
getProductBySlug("stift-kugelschreiber"),
getCompanySettings(),
]);
const productSchema = product
? buildProductSchema(product, "https://einfach-produktiv.mk360.de/der-alltagsstift", seller)
? buildProductSchema(product, "https://einfach-produktiv.mk360.de/der-eine", seller)
: null;
return (
@@ -44,8 +44,8 @@ export default async function DerAlltagsstiftPage() {
)}
<main className="flex flex-col flex-1">
<Hero />
<HowItWorks />
<Focus />
<Why />
<Pricing />
</main>
<Footer />