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>
This commit is contained in:
@@ -129,6 +129,54 @@ function renderNode(node: LexicalNode, key: string): ReactNode {
|
||||
{renderChildren(node.children, key)}
|
||||
</p>
|
||||
);
|
||||
// Lexical's default blockquote feature — used sitewide as a "Merke
|
||||
// dir:" pull-quote callout, per page-blog-detail's actual built Figma
|
||||
// frame (node 4676:341, file jCCZyh1DGwdjpv1wGge9To) — NOT a bordered/
|
||||
// background card (an earlier version of this guessed one; the real
|
||||
// design has no background or padding at all, just a plain 3-column
|
||||
// row: label+underline, a full-height divider rule, then the quote
|
||||
// lines). Icon is the actual exported sparkle asset from that node
|
||||
// (icon-sparkle-merke-dir.png), not a hand-drawn approximation. The
|
||||
// "Merke dir:" label itself is generic/hardcoded here rather than
|
||||
// content-authored, since a blog post's own body text drives which
|
||||
// lines get quoted, not the label framing them — legal pages never
|
||||
// use blockquotes, so this styling is effectively blog-only in
|
||||
// practice despite living in the shared renderer.
|
||||
case "quote":
|
||||
return (
|
||||
<div key={key} className="relative flex items-start gap-6 w-full my-6">
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<span className="flex h-7 w-6 items-center justify-center shrink-0">
|
||||
<img alt="" src="/icon-sparkle-merke-dir.png" className="w-full h-full object-contain" />
|
||||
</span>
|
||||
<span
|
||||
className="font-bold text-text-primary text-[1.625rem] whitespace-nowrap"
|
||||
style={{ fontFamily: "var(--font-caveat)" }}
|
||||
>
|
||||
Merke dir:
|
||||
</span>
|
||||
</div>
|
||||
{/* Hand-drawn underline image, not a plain bar — exported
|
||||
straight from the Figma node (label-underline). */}
|
||||
<img
|
||||
alt=""
|
||||
src="/icon-merke-dir-underline.png"
|
||||
className="absolute left-8 top-[2.1875rem] w-[8.5rem] h-[1.4375rem] object-cover pointer-events-none"
|
||||
/>
|
||||
<div className="w-px self-stretch bg-brand shrink-0" />
|
||||
<div className="flex flex-col gap-3.5 flex-1">
|
||||
{(node.children ?? []).map((child, i) => (
|
||||
<p
|
||||
key={`${key}-q-${i}`}
|
||||
className="text-text-primary text-[1.75rem] leading-[1.1]"
|
||||
style={{ fontFamily: "var(--font-caveat)" }}
|
||||
>
|
||||
{renderChildren(child.children, `${key}-q-${i}`)}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return renderChildren(node.children, key);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user