---
import ThemeToggle from '../components/ThemeToggle.astro';
import WispMark from '../components/WispMark.astro';
import { nav, site } from '../site';
import '../styles/blog.css';
interface Props {
title: string;
description?: string;
/** Page URL path, used for canonical + og:url. Defaults to the current route. */
path?: string;
/** 'article' on posts, 'website' everywhere else. */
ogType?: 'website' | 'article';
publishedAt?: string;
/** AT-URI of this page's Standard.site document record. */
standardDocumentUri?: string;
/** Constrain the main column to reading width. */
narrow?: boolean;
/** Keep the page out of search results (drafts, error pages). */
noindex?: boolean;
}
const {
title,
description = site.description,
path = Astro.url.pathname,
ogType = 'website',
publishedAt,
standardDocumentUri,
narrow = false,
noindex = false,
} = Astro.props;
const canonical = new URL(path, site.url).href;
const fullTitle = path === '/' ? title : `${title} · ${site.title}`;
const year = new Date().getFullYear();
const shellClass = narrow ? 'shell shell--narrow' : 'shell';
---
<!doctype html>
<html lang={site.language}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{fullTitle}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
{noindex && <meta name="robots" content="noindex, nofollow" />}
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="alternate" type="application/rss+xml" title={site.title} href="/rss.xml" />
{standardDocumentUri && <link rel="site.standard.document" href={standardDocumentUri} />}
<meta property="og:type" content={ogType} />
<meta property="og:url" content={canonical} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:site_name" content={site.title} />
{publishedAt && <meta property="article:published_time" content={publishedAt} />}
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
{/* Applied before first paint so a stored theme never flashes the other one. */}
<script is:inline>
try {
const stored = localStorage.getItem('wisp-theme');
document.documentElement.dataset.theme =
stored === 'light' || stored === 'dark' ? stored : 'dark';
} catch {
document.documentElement.dataset.theme = 'dark';
}
</script>
</head>
<body>
<a class="skip-link" href="#content">Skip to content</a>
<header class="nav">
<div class="shell nav__inner">
<a class="nav__brand" href="/">
<WispMark />
<span><b>wisp</b>.place</span>
<span class="nav__section">/blog</span>
</a>
<nav aria-label="Main">
<ul class="nav__links">
{
nav.map((item) => (
<li>
<a
href={item.href}
aria-current={!('external' in item) && path === item.href ? 'page' : undefined}
{...('external' in item && item.external
? { target: '_blank', rel: 'noopener' }
: {})}
>
{item.label}
</a>
</li>
))
}
<li class="nav__links-persist">
<a href="/rss.xml">RSS</a>
</li>
<li class="nav__links-persist">
<ThemeToggle />
</li>
</ul>
</nav>
</div>
</header>
<main id="content">
<div class={shellClass}>
<slot />
</div>
</main>
<footer class="footer">
<div class="shell footer__bottom">
<p>© {year} wisp.place · built by @nekomimi.pet</p>
<p>Hosted on wisp.place, like everything else.</p>
</div>
</footer>
</body>
</html>