feat(ui): Leseerlebnis fuer Wissen und rechtliche Seiten
- Artikel: Lesefortschritt als feine Honiglinie am oberen Rand, Initiale in Serifenschrift und Honig, groesserer Titel, angenehme Zeilenlaenge - Wissen-Uebersicht und "Weitere Artikel" als ruhige Liste mit grossen Serifen-Titeln, Unterstrich und gezeichnetem Pfeil statt "→" - Rechtliche Seiten mit klarerer Ueberschriften-Hierarchie Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
e75392c75b
commit
dfda94ddfc
4 files changed
+62
-17
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ReadingProgress } from "@/components/reading-progress";
|
||||||
import { Container } from "@/components/ui";
|
import { Container } from "@/components/ui";
|
||||||
import { BackLink } from "@/components/back-link";
|
import { BackLink } from "@/components/back-link";
|
||||||
import { wissenSlugs } from "@/lib/site-config";
|
import { wissenSlugs } from "@/lib/site-config";
|
||||||
@@ -67,6 +68,7 @@ export default async function WissenArticlePage(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
|
<ReadingProgress />
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }}
|
||||||
@@ -78,23 +80,38 @@ export default async function WissenArticlePage(
|
|||||||
<Container className="max-w-2xl">
|
<Container className="max-w-2xl">
|
||||||
<BackLink href="/wissen" label={t.wissenArticlePage.backLabel} />
|
<BackLink href="/wissen" label={t.wissenArticlePage.backLabel} />
|
||||||
<p className="tracking-wide-caps text-xs text-honey-dark mb-3">{t.wissenArticlePage.eyebrow}</p>
|
<p className="tracking-wide-caps text-xs text-honey-dark mb-3">{t.wissenArticlePage.eyebrow}</p>
|
||||||
<h1 className="text-3xl text-forest">{article.title}</h1>
|
<h1 className="text-4xl md:text-5xl leading-[1.05] tracking-[-0.01em] text-forest">{article.title}</h1>
|
||||||
<div className="mt-8 space-y-5 text-ink/75 leading-relaxed">
|
<div className="mt-10 max-w-[65ch] space-y-6 text-lg text-ink/80 leading-[1.75]">
|
||||||
{article.body.map((paragraph, i) => (
|
{article.body.map((paragraph, i) => (
|
||||||
<p key={i}>{paragraph}</p>
|
// Initiale wie im Buch: der erste Buchstabe des Artikels gross in Honig.
|
||||||
|
<p key={i} className={i === 0 ? "first-letter:float-left first-letter:mr-3 first-letter:mt-1.5 first-letter:font-serif first-letter:text-7xl first-letter:leading-[0.8] first-letter:text-honey-dark" : undefined}>
|
||||||
|
{paragraph}
|
||||||
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{otherArticles.length > 0 ? (
|
{otherArticles.length > 0 ? (
|
||||||
<div className="mt-16 border-t border-ink/10 pt-10">
|
<div className="mt-16 border-t border-ink/10 pt-10">
|
||||||
<p className="text-sm tracking-wide-caps text-ink/70 mb-6">{t.wissenArticlePage.otherArticlesHeading}</p>
|
<p className="text-sm tracking-wide-caps text-ink/70 mb-6">{t.wissenArticlePage.otherArticlesHeading}</p>
|
||||||
<div className="space-y-6">
|
<div className="divide-y divide-ink/10 border-y border-ink/10">
|
||||||
{otherArticles.map((otherSlug) => {
|
{otherArticles.map((otherSlug) => {
|
||||||
const other = t.wissenArticles[otherSlug];
|
const other = t.wissenArticles[otherSlug];
|
||||||
return (
|
return (
|
||||||
<Link key={otherSlug} href={`/wissen/${otherSlug}`} className="group block">
|
<Link key={otherSlug} href={`/wissen/${otherSlug}`} className="group flex items-start justify-between gap-6 py-6">
|
||||||
<h2 className="text-lg text-forest group-hover:underline">{other.title}</h2>
|
<span>
|
||||||
<p className="mt-1 text-sm text-ink/65 leading-relaxed">{other.excerpt}</p>
|
<span className="link-draw pb-0.5 font-serif text-2xl text-forest">{other.title}</span>
|
||||||
|
<span className="mt-2 block text-sm text-ink/65 leading-relaxed">{other.excerpt}</span>
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="mt-2 h-4 w-4 flex-shrink-0 text-forest transition-transform duration-300 ease-[var(--ease-arrive)] group-hover:translate-x-1"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.6"
|
||||||
|
>
|
||||||
|
<path d="M4 12h15M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
+15
-8
@@ -23,19 +23,26 @@ export default async function WissenPage() {
|
|||||||
<ScrollReveal>
|
<ScrollReveal>
|
||||||
<SectionHeading level="h1" eyebrow={t.wissenListPage.eyebrow} title={t.wissenListPage.title} />
|
<SectionHeading level="h1" eyebrow={t.wissenListPage.eyebrow} title={t.wissenListPage.title} />
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
<div className="mt-14 space-y-10">
|
<div className="mt-14 divide-y divide-ink/10 border-y border-ink/10">
|
||||||
{wissenSlugs.map((slug, i) => {
|
{wissenSlugs.map((slug, i) => {
|
||||||
const article = t.wissenArticles[slug];
|
const article = t.wissenArticles[slug];
|
||||||
return (
|
return (
|
||||||
<ScrollReveal key={slug} delay={(i % 5) * 0.06}>
|
<ScrollReveal key={slug} delay={(i % 5) * 0.06}>
|
||||||
<Link href={`/wissen/${slug}`} className="block group">
|
<Link href={`/wissen/${slug}`} className="group block py-9">
|
||||||
<h2 className="text-xl text-forest group-hover:underline">{article.title}</h2>
|
<h2 className="link-draw inline pb-0.5 text-3xl leading-tight text-forest">{article.title}</h2>
|
||||||
<p className="mt-2 text-sm text-ink/65 leading-relaxed">{article.excerpt}</p>
|
<p className="mt-3 max-w-[60ch] text-ink/70 leading-relaxed">{article.excerpt}</p>
|
||||||
<span className="mt-3 inline-flex items-center gap-1 text-sm text-honey-dark tracking-wide-caps">
|
<span className="mt-4 inline-flex items-center gap-2 text-sm text-honey-dark tracking-wide-caps">
|
||||||
{t.wissenListPage.readMore}
|
{t.wissenListPage.readMore}
|
||||||
<span className="transition-transform group-hover:translate-x-1" aria-hidden="true">
|
<svg
|
||||||
→
|
aria-hidden="true"
|
||||||
</span>
|
viewBox="0 0 24 24"
|
||||||
|
className="h-3.5 w-3.5 transition-transform duration-300 ease-[var(--ease-arrive)] group-hover:translate-x-1"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.6"
|
||||||
|
>
|
||||||
|
<path d="M4 12h15M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ export function LegalPage({
|
|||||||
return (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="max-w-3xl">
|
<Container className="max-w-3xl">
|
||||||
<h1 className="text-3xl text-forest">{title}</h1>
|
<h1 className="text-4xl md:text-5xl leading-[1.05] tracking-[-0.01em] text-forest">{title}</h1>
|
||||||
{noticeText ? (
|
{noticeText ? (
|
||||||
<p className="mt-2 text-xs text-honey-dark tracking-wide-caps">{noticeText}</p>
|
<p className="mt-2 text-xs text-honey-dark tracking-wide-caps">{noticeText}</p>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="mt-10 space-y-10">
|
<div className="mt-10 space-y-10">
|
||||||
{sections.map((section) => (
|
{sections.map((section) => (
|
||||||
<section key={section.heading}>
|
<section key={section.heading}>
|
||||||
<h2 className="text-lg text-forest">{section.heading}</h2>
|
<h2 className="text-2xl text-forest">{section.heading}</h2>
|
||||||
<div className="mt-3 space-y-3 text-sm text-ink/70 leading-relaxed">
|
<div className="mt-3 space-y-3 text-sm text-ink/70 leading-relaxed">
|
||||||
{section.paragraphs.map((p, i) => (
|
{section.paragraphs.map((p, i) => (
|
||||||
<p key={i}>{p}</p>
|
<p key={i}>{p}</p>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion, useScroll, useSpring } from "motion/react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feine Honiglinie am oberen Rand, die sich beim Lesen eines Artikels
|
||||||
|
* fuellt — zeigt, wie weit man ist, ohne vom Text abzulenken. Rein
|
||||||
|
* dekorativ fuer Sehende; die Seite bleibt ohne sie vollstaendig lesbar.
|
||||||
|
*/
|
||||||
|
export function ReadingProgress() {
|
||||||
|
const { scrollYProgress } = useScroll();
|
||||||
|
const scaleX = useSpring(scrollYProgress, { stiffness: 140, damping: 26, mass: 0.4 });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{ scaleX }}
|
||||||
|
className="fixed inset-x-0 top-0 z-[55] h-[3px] origin-left bg-gradient-to-r from-honey-dark to-honey"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user