Files
iptvnator/apps/website/src/layouts/BlogPost.astro
T

159 lines
5.3 KiB
Plaintext

---
import BaseLayout from './BaseLayout.astro';
import GiscusComments from '../components/GiscusComments.astro';
interface Props {
title: string;
description: string;
pubDate: Date;
updatedDate?: Date;
author: string;
tags?: string[];
heroImage?: string;
}
const { title, description, pubDate, updatedDate, author, tags = [], heroImage } = Astro.props;
const formattedDate = pubDate.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
const formattedUpdatedDate = updatedDate?.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
const AUTHOR_AVATAR_URL = 'https://avatars.githubusercontent.com/u/1503032';
const canonicalURL = new URL(Astro.url.pathname, Astro.site).toString();
const blogPostSchema = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: title,
description,
datePublished: pubDate.toISOString(),
dateModified: (updatedDate ?? pubDate).toISOString(),
author: {
'@type': 'Person',
name: author,
url: 'https://github.com/4gray',
},
publisher: {
'@type': 'Organization',
name: 'IPTVnator',
url: 'https://4gray.github.io/iptvnator/',
logo: {
'@type': 'ImageObject',
url: 'https://4gray.github.io/iptvnator/favicon-256x256.png',
},
},
mainEntityOfPage: canonicalURL,
image: heroImage ? new URL(heroImage, Astro.site).toString() : undefined,
};
---
<BaseLayout
title={`${title} | IPTVnator Blog`}
description={description}
image={heroImage}
type="article"
publishedTime={pubDate.toISOString()}
modifiedTime={(updatedDate ?? pubDate).toISOString()}
keywords={tags}
jsonLd={blogPostSchema}
>
<article class="pt-36 pb-24">
<div class="relative mx-auto max-w-3xl px-6">
<div class="pointer-events-none absolute -top-16 -right-14 hidden w-44 rotate-[-8deg] opacity-30 mix-blend-screen md:block lg:w-52">
<img
src="/iptvnator/illustrations/blog-corner-signal-mark.svg"
alt=""
aria-hidden="true"
class="w-full object-contain"
loading="lazy"
/>
</div>
<!-- Back link -->
<a
href="/iptvnator/blog/"
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.1em] text-surface-500 transition-colors hover:text-accent-400"
>
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
</svg>
Back to blog
</a>
<!-- Header -->
<header class="mt-10">
<div class="flex flex-wrap items-center gap-2">
<time datetime={pubDate.toISOString()} class="font-mono text-xs text-surface-500">
{formattedDate}
</time>
{
tags.length > 0 && (
<>
<span class="text-surface-700">&middot;</span>
{tags.map((tag) => (
<span class="rounded-full border border-dashed border-accent-500/30 px-2.5 py-0.5 text-xs font-medium text-accent-400">
{tag}
</span>
))}
</>
)
}
</div>
<h1 class="mt-5 font-display text-3xl font-bold tracking-tight text-surface-50 sm:text-4xl">
{title}
</h1>
<p class="mt-4 text-lg text-surface-400">{description}</p>
<div class="mt-6 flex items-center gap-3">
<img
src={AUTHOR_AVATAR_URL}
alt={`${author} avatar`}
class="h-8 w-8 rounded-full border border-dashed border-accent-500/30 object-cover"
loading="lazy"
/>
<div>
<div class="text-sm font-medium text-surface-100">{author}</div>
{
updatedDate && (
<div class="font-mono text-[11px] text-surface-500">
Updated {formattedUpdatedDate}
</div>
)
}
</div>
</div>
<hr class="section-divider mt-10" />
</header>
<!-- Hero image -->
{heroImage && (
<div class="relative mt-10">
<div class="absolute -inset-6 rounded-2xl bg-accent-500/[0.03] blur-2xl" />
<div class="relative overflow-hidden rounded-xl border border-surface-700/60 bg-surface-900 shadow-lg shadow-black/20">
<img src={heroImage} alt="" class="w-full" loading="eager" />
</div>
</div>
)}
<!-- Content -->
<div
class="prose prose-invert mt-12 max-w-none prose-headings:font-display prose-headings:font-semibold prose-headings:tracking-tight prose-headings:text-surface-100 prose-p:text-surface-300 prose-a:text-accent-400 prose-a:no-underline prose-a:decoration-accent-500/30 hover:prose-a:underline prose-strong:text-surface-100 prose-code:rounded prose-code:bg-surface-800/50 prose-code:px-1.5 prose-code:py-0.5 prose-code:font-mono prose-code:text-accent-300 prose-code:before:content-[''] prose-code:after:content-[''] prose-pre:border prose-pre:border-dashed prose-pre:border-surface-700/60 prose-pre:bg-surface-900/50 prose-blockquote:border-accent-500/30 prose-blockquote:text-surface-400 prose-hr:border-dashed prose-hr:border-surface-700/60"
>
<slot />
</div>
<GiscusComments />
</div>
</article>
</BaseLayout>