portfolio/src/layouts/layout.astro
2026-07-02 14:58:07 +07:00

133 lines
3.8 KiB
Plaintext

---
import "@fontsource/geist-sans";
import "@fontsource/geist-sans/500.css";
import "@fontsource/geist-sans/600.css";
import "@fontsource/geist-sans/700.css";
import "@fontsource/geist-sans/800.css";
import "@fontsource/geist-sans/900.css";
import "@fontsource/roboto-condensed/600.css";
import "@fontsource/roboto-condensed/400.css";
import "@fontsource/jetbrains-mono/400.css";
import { ClientRouter } from "astro:transitions";
import Analytics from "@vercel/analytics/astro";
import { SITE } from "@/lib/config";
import "@/styles/global.css";
import BackToTop from "@/components/back-to-top.astro";
export interface Props {
title?: string;
author?: string;
profile?: string;
description?: string;
ogImage?: string;
pageType?: string;
canonicalURL?: string;
}
const {
title = SITE.title,
author = SITE.author,
profile = SITE.profile,
description = SITE.desc,
ogImage = SITE.ogImage,
pageType = SITE.pageType,
canonicalURL = new URL(Astro.url.pathname, Astro.url),
} = Astro.props;
const socialImageURL = new URL(ogImage ?? SITE.ogImage, Astro.site).toString();
const structuredData = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: `${title}`,
image: `${socialImageURL}`,
author: [
{
"@type": "Person",
name: `${author}`,
...(profile && { url: profile }),
},
],
};
---
<script>
document.addEventListener("astro:after-swap", () => {
const getThemePreference = () => {
if (
typeof localStorage !== "undefined" &&
localStorage.getItem("theme")
) {
return localStorage.getItem("theme");
}
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
};
const isDark = getThemePreference() === "dark";
document.documentElement.classList[isDark ? "add" : "remove"]("dark");
if (typeof localStorage !== "undefined") {
const observer = new MutationObserver(() => {
const isDark = document.documentElement.classList.contains("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
}
});
</script>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<!-- <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> -->
<link rel="canonical" href={canonicalURL} />
<meta name="generator" content={Astro.generator} />
<!-- General Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<meta name="author" content={author} />
<link rel="sitemap" href="/sitemap-index.xml" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content={pageType} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={socialImageURL} />
<meta property="og:url" content={canonicalURL} />
<!-- X / Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={canonicalURL} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={socialImageURL} />
<ClientRouter />
<!-- Google JSON-LD Structured data -->
<script
type="application/ld+json"
is:inline
set:html={JSON.stringify(structuredData)}
/>
<meta name="theme-color" content="" />
</head>
<body class="relative min-h-screen overflow-x-hidden">
<slot />
<BackToTop />
<Analytics />
</body>
</html>