mirror of
https://github.com/10h30/blog-balodeplao.git
synced 2026-05-12 15:21:15 +09:00
162 lines
5.1 KiB
Plaintext
162 lines
5.1 KiB
Plaintext
---
|
|
interface Props {
|
|
title?: string;
|
|
description?: string;
|
|
ogImage?: string;
|
|
canonical?: string;
|
|
metadata?: {
|
|
title?: string;
|
|
description?: string;
|
|
ogImage?: string;
|
|
canonical?: string;
|
|
ignoreTitleTemplate?: boolean;
|
|
};
|
|
}
|
|
|
|
import Seo from "@/components/seo/Seo.astro";
|
|
import Schema from "@/components/seo/Schema.astro";
|
|
import Navbar from "@/components/layout/Navbar.astro";
|
|
import Footer from "@/components/layout/Footer.astro";
|
|
import { ClientRouter } from "astro:transitions";
|
|
import "@/styles/global.css";
|
|
|
|
import { siteConfig } from "@/config/site";
|
|
|
|
const { title, description, ogImage, canonical, metadata } = Astro.props;
|
|
|
|
const finalTitle = metadata?.title || title;
|
|
const finalDescription = metadata?.description || description;
|
|
const finalOgImage = metadata?.ogImage || ogImage;
|
|
const finalCanonical = metadata?.canonical || canonical;
|
|
const finalIgnoreTitleTemplate = metadata?.ignoreTitleTemplate || false;
|
|
---
|
|
|
|
<!doctype html>
|
|
<html lang={siteConfig.lang} class="scroll-smooth">
|
|
<head>
|
|
<Seo
|
|
title={finalTitle}
|
|
description={finalDescription}
|
|
ogImage={finalOgImage}
|
|
canonical={finalCanonical}
|
|
ignoreTitleTemplate={finalIgnoreTitleTemplate}
|
|
/>
|
|
<ClientRouter />
|
|
<Schema type="WebSite" data={{}} />
|
|
<slot name="head" />
|
|
<script is:inline>
|
|
const getTheme = () => {
|
|
if (
|
|
typeof localStorage !== "undefined" &&
|
|
localStorage.getItem("theme")
|
|
) {
|
|
return localStorage.getItem("theme");
|
|
}
|
|
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
return "dark";
|
|
}
|
|
return "light";
|
|
};
|
|
|
|
const setTheme = () => {
|
|
const theme = getTheme();
|
|
if (theme === "light") {
|
|
document.documentElement.setAttribute("data-theme", "light");
|
|
} else {
|
|
document.documentElement.setAttribute("data-theme", "dark");
|
|
}
|
|
window.localStorage.setItem("theme", theme);
|
|
};
|
|
|
|
// Run immediately on first hard load
|
|
setTheme();
|
|
|
|
// Run before the new page renders during View Transitions
|
|
document.addEventListener("astro:after-swap", setTheme);
|
|
</script>
|
|
</head>
|
|
<body
|
|
class="bg-background text-foreground antialiased selection:bg-blue-500/30 dark:selection:text-blue-200"
|
|
>
|
|
<a
|
|
href="#main-content"
|
|
class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 z-50 rounded-md bg-primary px-4 py-2 text-white shadow-lg ring-2 ring-white"
|
|
>
|
|
Skip to content
|
|
</a>
|
|
|
|
<div class="relative flex min-h-screen flex-col overflow-x-hidden">
|
|
<!-- Background Glow -->
|
|
<div class="pointer-events-none fixed inset-0 z-0 hidden md:block">
|
|
<div
|
|
class="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(59,130,246,0.15)_0%,transparent_50%)]"
|
|
>
|
|
</div>
|
|
<div
|
|
class="absolute inset-0 bg-[radial-gradient(circle_at_bottom_right,rgba(168,85,247,0.15)_0%,transparent_50%)]"
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<Navbar />
|
|
<main id="main-content" class="relative z-10 grow pt-18">
|
|
<slot />
|
|
</main>
|
|
<Footer />
|
|
</div>
|
|
<script>
|
|
// Keep a global reference to prevent duplicate observers during View Transitions
|
|
let scrollObserver: IntersectionObserver | null = null;
|
|
|
|
const setupAnimations = () => {
|
|
// Disconnect previous observer if it exists (prevents ghost triggers)
|
|
if (scrollObserver) {
|
|
scrollObserver.disconnect();
|
|
}
|
|
|
|
// Instantly bypass scroll animations for elements already visible in viewport
|
|
// This solves the Astro View Transitions "double bounce" issue completely
|
|
document.querySelectorAll(".reveal:not(.active)").forEach((el) => {
|
|
if (el.getBoundingClientRect().top < window.innerHeight) {
|
|
// Temporarily disable transition during initial show
|
|
el.setAttribute(
|
|
"style",
|
|
"transition: none !important; transform: none !important;",
|
|
);
|
|
el.classList.add("active");
|
|
// Reactivate their explicit styles so CSS takes over next time
|
|
requestAnimationFrame(() => {
|
|
el.removeAttribute("style");
|
|
});
|
|
}
|
|
});
|
|
|
|
scrollObserver = new IntersectionObserver(
|
|
(entries, observer) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
// Add class synchronously to prevent frame delays
|
|
entry.target.classList.add("active");
|
|
observer.unobserve(entry.target);
|
|
}
|
|
});
|
|
},
|
|
{
|
|
root: null,
|
|
rootMargin: "0px",
|
|
threshold: 0.1,
|
|
},
|
|
);
|
|
|
|
const remainingElements = document.querySelectorAll(
|
|
".reveal:not(.active)",
|
|
);
|
|
remainingElements.forEach((el) => scrollObserver?.observe(el));
|
|
};
|
|
|
|
document.addEventListener("astro:page-load", setupAnimations);
|
|
document.addEventListener("astro:after-swap", setupAnimations);
|
|
</script>
|
|
</body>
|
|
</html>
|