266 lines
7.3 KiB
Text
266 lines
7.3 KiB
Text
---
|
|
// Import section components
|
|
import { SITE } from "@data/constants";
|
|
import MainLayout from "@/layouts/MainLayout.astro";
|
|
import { Image } from "astro:assets";
|
|
import { getCollection, render } from "astro:content";
|
|
|
|
// Use `getStaticPaths` to generate static routes for generated pages on build
|
|
export async function getStaticPaths() {
|
|
const insightPosts = await getCollection("insights", ({ id }) => id.startsWith("fr/"));
|
|
return insightPosts.map((post) => {
|
|
const idWithoutLang = post.id.replace(/^fr\//, ''); // Remove the "fr/" prefix
|
|
return {
|
|
params: { lang: 'fr', id: idWithoutLang },
|
|
props: { post },
|
|
};
|
|
});
|
|
}
|
|
|
|
// Get the props for this page that define a specific insight post
|
|
const { post } = Astro.props;
|
|
|
|
const { Content } = await render(post);
|
|
|
|
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 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 flex-none" 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>
|