Replaced all custom color hex codes used in various component classes with their corresponding color names from the expanded Tailwind color palette. This ensures consistent styling across components and enhances readability and maintainability of the code.
263 lines
7.1 KiB
Text
263 lines
7.1 KiB
Text
---
|
|
// Import section components
|
|
import { SITE } from "@/data_files/constants";
|
|
import MainLayout from "@/layouts/MainLayout.astro";
|
|
import { Image } from "astro:assets";
|
|
import { getCollection } from "astro:content";
|
|
|
|
// Use `getStaticPaths` to generate static routes for generated pages on build
|
|
export async function getStaticPaths() {
|
|
const insightPosts = await getCollection("insights");
|
|
return insightPosts.map((post) => ({
|
|
params: { slug: post.slug },
|
|
props: { post },
|
|
}));
|
|
}
|
|
|
|
// Get the props for this page that define a specific insight post
|
|
const { post } = Astro.props;
|
|
|
|
const { Content } = await post.render();
|
|
|
|
const pageTitle: string = `${post.data.title} | ${SITE.title}`;
|
|
---
|
|
|
|
<MainLayout title={pageTitle}>
|
|
<section class="py-6 sm:py-8 lg:py-12">
|
|
<div class="mx-auto max-w-screen-xl px-4 md:px-8">
|
|
<div class="grid gap-8 md:grid-cols-2 lg:gap-12">
|
|
<div>
|
|
<div class="h-64 overflow-hidden rounded-lg shadow-lg md:h-auto">
|
|
<Image
|
|
class="h-full w-full object-cover object-center"
|
|
src={post.data.cardImage}
|
|
alt={post.data.cardImageAlt}
|
|
draggable={"false"}
|
|
format={"avif"}
|
|
/>
|
|
</div>
|
|
<div
|
|
id="progress-mobile"
|
|
class="fixed left-0 top-0 h-2 w-full bg-gradient-to-r from-orange-400/30 to-orange-400 md:hidden"
|
|
>
|
|
</div>
|
|
<div id="pin" class="mt-10 hidden space-y-4 md:block">
|
|
<div
|
|
class="h-px w-full overflow-hidden bg-neutral-300 dark:bg-neutral-700"
|
|
>
|
|
<div
|
|
id="progress"
|
|
class="h-px w-full bg-gradient-to-r from-orange-400/30 to-orange-400"
|
|
>
|
|
</div>
|
|
</div>
|
|
<p class="text-pretty text-sm font-light text-neutral-500">
|
|
Table of Contents:
|
|
</p>
|
|
<div id="toc" class="">
|
|
<ul
|
|
class="space-y-2 text-pretty text-base text-neutral-700 transition duration-300 dark:text-neutral-400"
|
|
>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="md:pt-8">
|
|
<h1
|
|
class="mb-4 text-balance text-center text-2xl font-bold text-neutral-800 dark:text-neutral-200 sm:text-3xl md:mb-6 md:text-left"
|
|
>
|
|
{post.data.title}
|
|
</h1>
|
|
|
|
<article
|
|
class="text-pretty text-lg text-neutral-700 dark:text-neutral-300"
|
|
>
|
|
<Content />
|
|
</article>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</MainLayout>
|
|
|
|
<style is:global>
|
|
:root {
|
|
--transition-cubic: cubic-bezier(0.165, 0.84, 0.44, 1);
|
|
}
|
|
|
|
html {
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
article h2,
|
|
article h3,
|
|
article h4,
|
|
article h5,
|
|
article h6 {
|
|
font-weight: bold;
|
|
margin-top: 2.5rem;
|
|
scroll-margin-top: 3rem;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 1.5rem;
|
|
line-height: 2rem;
|
|
}
|
|
|
|
h3 {
|
|
font-size: 1.25rem;
|
|
line-height: 1.75rem;
|
|
}
|
|
|
|
h4 {
|
|
font-size: 1.125rem;
|
|
line-height: 1.75rem;
|
|
}
|
|
|
|
p {
|
|
margin-top: 1.5rem;
|
|
}
|
|
|
|
#toc li {
|
|
display: flex;
|
|
align-items: center;
|
|
opacity: 0.8;
|
|
transition: all 300ms var(--transition-cubic);
|
|
}
|
|
|
|
#toc li.selected {
|
|
opacity: 1;
|
|
}
|
|
|
|
#toc li svg {
|
|
width: 0;
|
|
height: 0;
|
|
transition:
|
|
height 400ms var(--transition-cubic),
|
|
width 400ms var(--transition-cubic);
|
|
}
|
|
|
|
#toc li.selected svg {
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
margin-right: 0.3rem;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
const onScroll = (): void => {
|
|
const article = document.querySelector("article");
|
|
if (!article) return;
|
|
|
|
const articleHeight = article.offsetHeight;
|
|
const articleOffsetTop = article.offsetTop;
|
|
|
|
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
|
|
if (articleHeight && articleOffsetTop && scrollTop) {
|
|
const progress =
|
|
((scrollTop - articleOffsetTop) /
|
|
(articleHeight - window.innerHeight)) *
|
|
100;
|
|
|
|
const progressBar = document.getElementById("progress");
|
|
const progressBarMobile = document.getElementById("progress-mobile");
|
|
|
|
if (progressBar && progressBarMobile) {
|
|
progressBar.style.width = `${progress}%`;
|
|
progressBarMobile.style.width = `${progress}%`;
|
|
}
|
|
}
|
|
};
|
|
|
|
document.addEventListener("DOMContentLoaded", (event) => {
|
|
window.onscroll = onScroll;
|
|
|
|
// Set initial width of progress bar
|
|
const progressBar = document.getElementById("progress");
|
|
const progressBarMobile = document.getElementById("progress-mobile");
|
|
|
|
if (progressBar && progressBarMobile) {
|
|
progressBar.style.width = "0%";
|
|
progressBarMobile.style.width = "0%";
|
|
}
|
|
});
|
|
</script>
|
|
<script>
|
|
import { gsap } from "gsap";
|
|
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
|
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
|
|
gsap.timeline({
|
|
scrollTrigger: {
|
|
scrub: 1,
|
|
pin: true,
|
|
trigger: "#pin",
|
|
start: "top 20%",
|
|
endTrigger: "footer",
|
|
end: "top bottom",
|
|
},
|
|
});
|
|
|
|
const SVG_HTML_STRING =
|
|
'<svg class="w-0 h-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fa5a15"><path stroke-linecap="round" stroke-linejoin="round" d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"></svg>';
|
|
|
|
function setActiveLinkById(id: string | null) {
|
|
const listItems = document.querySelectorAll("#toc li");
|
|
listItems.forEach((item) => item.classList.remove("selected"));
|
|
|
|
if (!id) return;
|
|
|
|
const activeLink = document.querySelector(`#toc a[href="#${id}"]`);
|
|
|
|
if (!activeLink) return;
|
|
|
|
const listItem = activeLink.parentElement;
|
|
listItem?.classList.add("selected");
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
// The article element that contains the Markdown content
|
|
const article: HTMLElement | null = document.querySelector("article");
|
|
// The ToC container <ul> element
|
|
const tocList: HTMLElement | null = document.querySelector("#toc ul");
|
|
|
|
const headings: NodeListOf<HTMLElement> | [] = article
|
|
? article.querySelectorAll("h1, h2, h3, h4, h5, h6")
|
|
: [];
|
|
|
|
headings.forEach((heading, i) => {
|
|
if (heading instanceof HTMLElement) {
|
|
const listItem = document.createElement("li");
|
|
listItem.className = "toc-level-" + heading.tagName.toLowerCase();
|
|
|
|
const tempDiv = document.createElement("div");
|
|
tempDiv.innerHTML = SVG_HTML_STRING;
|
|
|
|
const svg = tempDiv.firstChild;
|
|
listItem.appendChild(svg as Node);
|
|
|
|
const link = document.createElement("a");
|
|
link.href = "#" + heading.id;
|
|
link.textContent = heading.textContent;
|
|
listItem.appendChild(link);
|
|
|
|
tocList?.appendChild(listItem);
|
|
|
|
gsap.timeline({
|
|
scrollTrigger: {
|
|
trigger: heading,
|
|
start: "top 20%",
|
|
end: () =>
|
|
`bottom top+=${i === headings.length - 1 ? 0 : (headings[i + 1] as HTMLElement).getBoundingClientRect().height}`,
|
|
onEnter: () => setActiveLinkById(heading.id),
|
|
onLeaveBack: () =>
|
|
setActiveLinkById((headings[i - 1] as HTMLElement)?.id),
|
|
},
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script>
|