mirror of
https://github.com/10h30/blog-balodeplao.git
synced 2026-05-12 23:21:16 +09:00
Initial commit
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
---
|
||||
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 SpeedInsights from "@vercel/speed-insights/astro";
|
||||
import Analytics from "@vercel/analytics/astro";
|
||||
|
||||
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-[72px]">
|
||||
<slot />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
<SpeedInsights />
|
||||
<Analytics />
|
||||
<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>
|
||||
Reference in New Issue
Block a user