Files

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>