Improve article page readability and appearance on desktop

Adjust article page layout on desktop, reducing image size, title font size, and text size while narrowing content width for better readability and visual balance.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: a2ee97c2-94c0-42af-9ffb-1551048efa6a
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/BvOHMz7
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic 2026-03-07 07:24:54 +00:00
parent 0df13503b8
commit d13867d660
2 changed files with 103 additions and 92 deletions

View File

@ -221,110 +221,113 @@ export default function ArticlePage() {
<Header />
<PageSideAds />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<Link href="/">
<Button variant="ghost" size="sm" className="mb-4 gap-2" data-testid="button-back">
<ArrowLeft className="w-4 h-4" />
Zurück
</Button>
</Link>
<div className="max-w-4xl mx-auto">
<Link href="/">
<Button variant="ghost" size="sm" className="mb-3 gap-2" data-testid="button-back">
<ArrowLeft className="w-4 h-4" />
Zurück
</Button>
</Link>
{article.coverImage && (
<div className="relative overflow-hidden rounded-md mb-6">
<img
src={article.coverImage}
alt={article.title}
className="w-full aspect-video object-cover"
data-testid="img-article-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent" />
</div>
)}
{article.coverImage && (
<div className="relative overflow-hidden rounded-md mb-5 max-h-[420px]">
<img
src={article.coverImage}
alt={article.title}
className="w-full h-full object-cover"
style={{ maxHeight: "420px" }}
data-testid="img-article-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent" />
</div>
)}
<div className="mb-6">
<h1
className="text-3xl md:text-4xl font-bold text-foreground mb-4 leading-tight"
data-testid="text-article-title"
>
{article.title}
</h1>
<div className="mb-5">
<h1
className="text-2xl md:text-3xl font-bold text-foreground mb-3 leading-tight"
data-testid="text-article-title"
>
{article.title}
</h1>
<div className="flex flex-wrap items-center gap-4 text-sm text-muted-foreground">
<span className="flex items-center gap-1.5">
<User className="w-4 h-4" />
{article.author}
</span>
<span className="flex items-center gap-1.5">
<Calendar className="w-4 h-4" />
{format(new Date(article.publishedAt), "d. MMMM yyyy", { locale: de })}
</span>
<span className="flex items-center gap-1.5">
<Eye className="w-4 h-4" />
{article.views.toLocaleString()} Aufrufe
</span>
<div className="flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
<span className="flex items-center gap-1.5">
<User className="w-3.5 h-3.5" />
{article.author}
</span>
<span className="flex items-center gap-1.5">
<Calendar className="w-3.5 h-3.5" />
{format(new Date(article.publishedAt), "d. MMMM yyyy", { locale: de })}
</span>
<span className="flex items-center gap-1.5">
<Eye className="w-3.5 h-3.5" />
{article.views.toLocaleString()} Aufrufe
</span>
</div>
<div className="mt-3">
<ShareButtons
url={`${window.location.origin}/article/${article.slug}`}
title={article.title}
image={article.coverImage ? (article.coverImage.startsWith("http") ? article.coverImage : `${window.location.origin}${article.coverImage}`) : undefined}
/>
</div>
</div>
<div className="mt-4">
{(() => {
const sanitized = sanitizeContent(article.content);
const blocks = sanitized.split(/(?=<(?:p|h[2-4]|div)[\s>])/i).filter(Boolean);
const midPoint = Math.max(2, Math.floor(blocks.length / 2));
const firstHalf = blocks.slice(0, midPoint).join("");
const secondHalf = blocks.slice(midPoint).join("");
const proseClasses = `prose prose-base dark:prose-invert max-w-none
prose-headings:text-foreground prose-headings:font-semibold prose-headings:text-lg
prose-p:text-foreground/85 prose-p:leading-relaxed prose-p:text-[15px]
prose-a:text-primary prose-a:no-underline hover:prose-a:underline
prose-strong:text-foreground
prose-img:rounded-md prose-img:w-full prose-img:object-cover
[&_iframe]:rounded-md [&_iframe]:my-5 [&_iframe]:max-w-full
[&_div[style]]:flex [&_div[style]]:justify-center
[&_blockquote:not(.instagram-media)]:border-l-primary [&_blockquote:not(.instagram-media)]:bg-accent/50 [&_blockquote:not(.instagram-media)]:rounded-r-md [&_blockquote:not(.instagram-media)]:py-1
[&_.instagram-media]:!bg-transparent [&_.instagram-media]:!border-0 [&_.instagram-media]:!shadow-none [&_.instagram-media]:!p-0 [&_.instagram-media]:mx-auto`;
return (
<div ref={(el) => {
if (!el) return;
el.querySelectorAll("a[href]").forEach((a) => {
const href = a.getAttribute("href") || "";
const isBunny = href.includes("mediadelivery.net") || href.includes("bunny.net") || href.includes("b-cdn.net");
const isInternal = href.startsWith("/") || href.includes("folx.tv");
if (!isBunny && !isInternal && href.startsWith("http")) {
a.setAttribute("target", "_blank");
a.setAttribute("rel", "noopener noreferrer");
}
});
}}>
<article
className={proseClasses}
dangerouslySetInnerHTML={{ __html: firstHalf }}
data-testid="article-content"
/>
<InArticleAd />
<article
className={proseClasses}
dangerouslySetInnerHTML={{ __html: secondHalf }}
data-testid="article-content-continued"
/>
</div>
);
})()}
<div className="mt-6 pt-5 border-t border-border">
<ShareButtons
url={`${window.location.origin}/article/${article.slug}`}
title={article.title}
image={article.coverImage ? (article.coverImage.startsWith("http") ? article.coverImage : `${window.location.origin}${article.coverImage}`) : undefined}
/>
</div>
<RelatedArticles currentSlug={slug || ""} />
</div>
{(() => {
const sanitized = sanitizeContent(article.content);
const blocks = sanitized.split(/(?=<(?:p|h[2-4]|div)[\s>])/i).filter(Boolean);
const midPoint = Math.max(2, Math.floor(blocks.length / 2));
const firstHalf = blocks.slice(0, midPoint).join("");
const secondHalf = blocks.slice(midPoint).join("");
const proseClasses = `prose prose-lg dark:prose-invert max-w-none
prose-headings:text-foreground prose-headings:font-semibold
prose-p:text-foreground/85 prose-p:leading-relaxed
prose-a:text-primary prose-a:no-underline hover:prose-a:underline
prose-strong:text-foreground
prose-img:rounded-md prose-img:w-full prose-img:object-cover
[&_iframe]:rounded-md [&_iframe]:my-6 [&_iframe]:max-w-full
[&_div[style]]:flex [&_div[style]]:justify-center
[&_blockquote:not(.instagram-media)]:border-l-primary [&_blockquote:not(.instagram-media)]:bg-accent/50 [&_blockquote:not(.instagram-media)]:rounded-r-md [&_blockquote:not(.instagram-media)]:py-1
[&_.instagram-media]:!bg-transparent [&_.instagram-media]:!border-0 [&_.instagram-media]:!shadow-none [&_.instagram-media]:!p-0 [&_.instagram-media]:mx-auto`;
return (
<div ref={(el) => {
if (!el) return;
el.querySelectorAll("a[href]").forEach((a) => {
const href = a.getAttribute("href") || "";
const isBunny = href.includes("mediadelivery.net") || href.includes("bunny.net") || href.includes("b-cdn.net");
const isInternal = href.startsWith("/") || href.includes("folx.tv");
if (!isBunny && !isInternal && href.startsWith("http")) {
a.setAttribute("target", "_blank");
a.setAttribute("rel", "noopener noreferrer");
}
});
}}>
<article
className={proseClasses}
dangerouslySetInnerHTML={{ __html: firstHalf }}
data-testid="article-content"
/>
<InArticleAd />
<article
className={proseClasses}
dangerouslySetInnerHTML={{ __html: secondHalf }}
data-testid="article-content-continued"
/>
</div>
);
})()}
<div className="mt-8 pt-6 border-t border-border">
<ShareButtons
url={`${window.location.origin}/article/${article.slug}`}
title={article.title}
image={article.coverImage ? (article.coverImage.startsWith("http") ? article.coverImage : `${window.location.origin}${article.coverImage}`) : undefined}
/>
</div>
<RelatedArticles currentSlug={slug || ""} />
</main>
<Footer />
</div>

View File

@ -151,6 +151,14 @@ When adding new articles:
9. Facebook embeds: copy the iframe code as-is from user
10. After deploy, go to Facebook Sharing Debugger and click "Scrape Again" to refresh cache
## Article Page Layout
- Outer wrapper: `max-w-7xl` (for PageSideAds), inner content: `max-w-4xl mx-auto`
- Cover image: `max-h-[420px]` with object-cover
- Title: `text-2xl md:text-3xl` font-bold
- Meta (author/date/views): `text-xs` with `w-3.5 h-3.5` icons
- Body text: `prose-base` with `prose-p:text-[15px]` and `prose-headings:text-lg`
- In-article ad splits content at midpoint
## Important Notes
- Tailwind `object-[center_25%]` does NOT work — must use inline `style={{ objectPosition: "center 25%" }}`
- Horoscope widget navigates to /horoskop on click (no modal)