mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
159 lines
5.3 KiB
Plaintext
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">·</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>
|