seedproject-web/app/src/layouts/BaseLayout.astro
Carlos Arias 43ae1e2015 Baseline: full site build (brand, pages, DB, agent-editable)
Establishes the deploy baseline on main so the admin agent's publish/rollback
has a clean starting point. Everything built to date: sumi-e brand system,
homepage, projects (DB-driven case studies), resume, about, services +
website-design detail, contact form + DB, changelog, favicon + share card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoFYZY9gkGPNDqZ7NuEa9a
2026-07-23 20:21:28 +00:00

107 lines
3.5 KiB
Text

---
import "../styles.css";
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
import DevConsole from "../components/DevConsole.astro";
import { SITE, imageSrc } from "../lib/blog-data.js";
const {
title = `${SITE.name}`,
description = SITE.description,
canonical,
ogType = "website",
ogImage,
jsonLd,
flushFooter = false,
} = Astro.props;
const siteUrl = Astro.site?.toString() || SITE.url;
const absoluteUrl = (value) => {
const src = imageSrc(value) || value;
if (!src) return undefined;
try {
return new URL(src, siteUrl).toString();
} catch {
return src;
}
};
const canonicalUrl = absoluteUrl(canonical || Astro.url.pathname);
// Default share card (the branded portrait + tagline). Any page can override
// by passing its own `ogImage`.
const ogImageUrl = absoluteUrl(ogImage || "/og-image.jpg");
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<meta name="description" content={description} />
<meta name="author" content={SITE.name} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content={ogType} />
<meta property="og:site_name" content={SITE.name} />
{canonicalUrl && <meta property="og:url" content={canonicalUrl} />}
{ogImageUrl && <meta property="og:image" content={ogImageUrl} />}
{ogImageUrl && <meta property="og:image:width" content="1200" />}
{ogImageUrl && <meta property="og:image:height" content="630" />}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
{ogImageUrl && <meta name="twitter:image" content={ogImageUrl} />}
{canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
{/* Favicons — the vermillion 印 seal. SVG for modern browsers, PNG/ICO
fallbacks, and an apple-touch-icon for home-screen bookmarks. */}
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="theme-color" content="#d0342c" />
<link
rel="preload"
href="/fonts/inter-latin.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/fonts/fraunces-latin.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link rel="alternate" type="application/rss+xml" title={SITE.name} href="/rss.xml" />
{
jsonLd && (
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
)
}
<script is:inline>
(function () {
try {
var theme = localStorage.getItem("theme");
var dark = theme
? theme === "dark"
: matchMedia("(prefers-color-scheme: dark)").matches;
if (dark) document.documentElement.classList.add("dark");
} catch (error) {}
})();
</script>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to content</a>
<div class="flex min-h-dvh flex-col">
<Header />
<main id="main-content" class="flex-1">
<slot />
</main>
<Footer flush={flushFooter} />
</div>
<DevConsole />
</body>
</html>