summaryrefslogtreecommitdiff
path: root/src/layouts
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2025-07-03 10:56:21 +0300
committerDawid Rycerz <dawid@rycerz.xyz>2025-07-03 10:56:21 +0300
commit456cf011b36de91c9936994b1fa45703adcd309b (patch)
tree8e60daf998f731ac50d100fa490eaecae1168042 /src/layouts
Initial fork of chrismwilliams/astro-theme-cactus theme
Diffstat (limited to 'src/layouts')
-rw-r--r--src/layouts/Base.astro34
-rw-r--r--src/layouts/BlogPost.astro80
2 files changed, 114 insertions, 0 deletions
diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
new file mode 100644
index 0000000..09d7727
--- /dev/null
+++ b/src/layouts/Base.astro
@@ -0,0 +1,34 @@
+---
+import BaseHead from "@/components/BaseHead.astro";
+import SkipLink from "@/components/SkipLink.astro";
+import ThemeProvider from "@/components/ThemeProvider.astro";
+import Footer from "@/components/layout/Footer.astro";
+import Header from "@/components/layout/Header.astro";
+import { siteConfig } from "@/site.config";
+import type { SiteMeta } from "@/types";
+
+interface Props {
+ meta: SiteMeta;
+}
+
+const {
+ meta: { articleDate, description = siteConfig.description, ogImage, title },
+} = Astro.props;
+---
+
+<html class="scroll-smooth" lang={siteConfig.lang}>
+ <head>
+ <BaseHead articleDate={articleDate} description={description} ogImage={ogImage} title={title} />
+ </head>
+ <body
+ class="bg-global-bg text-global-text mx-auto flex min-h-screen max-w-3xl flex-col px-4 pt-16 font-mono text-sm font-normal antialiased sm:px-8"
+ >
+ <ThemeProvider />
+ <SkipLink />
+ <Header />
+ <main id="main">
+ <slot />
+ </main>
+ <Footer />
+ </body>
+</html>
diff --git a/src/layouts/BlogPost.astro b/src/layouts/BlogPost.astro
new file mode 100644
index 0000000..888e7a2
--- /dev/null
+++ b/src/layouts/BlogPost.astro
@@ -0,0 +1,80 @@
+---
+import { type CollectionEntry, render } from "astro:content";
+
+import Masthead from "@/components/blog/Masthead.astro";
+import TOC from "@/components/blog/TOC.astro";
+import WebMentions from "@/components/blog/webmentions/index.astro";
+
+import BaseLayout from "./Base.astro";
+
+interface Props {
+ post: CollectionEntry<"post">;
+}
+
+const { post } = Astro.props;
+const { ogImage, title, description, updatedDate, publishDate } = post.data;
+const socialImage = ogImage ?? `/og-image/${post.id}.png`;
+const articleDate = updatedDate?.toISOString() ?? publishDate.toISOString();
+const { headings, remarkPluginFrontmatter } = await render(post);
+const readingTime: string = remarkPluginFrontmatter.readingTime;
+---
+
+<BaseLayout
+ meta={{
+ articleDate,
+ description,
+ ogImage: socialImage,
+ title,
+ }}
+>
+ <article class="grow break-words" data-pagefind-body>
+ <div id="blog-hero" class="mb-12"><Masthead content={post} readingTime={readingTime} /></div>
+ <div class="flex flex-col gap-10 lg:flex-row lg:items-start">
+ {!!headings.length && <TOC headings={headings} />}
+ <div
+ class="prose prose-sm prose-headings:font-semibold prose-headings:text-accent-2 prose-headings:before:absolute prose-headings:before:-ms-4 prose-headings:before:text-gray-600 prose-headings:hover:before:text-accent sm:prose-headings:before:content-['#'] sm:prose-th:before:content-none"
+ >
+ <slot />
+ <WebMentions />
+ </div>
+ </div>
+ </article>
+ <button
+ class="hover:border-link fixed end-4 bottom-8 z-90 flex h-10 w-10 translate-y-28 cursor-pointer items-center justify-center rounded-full border-2 border-transparent bg-zinc-200 text-3xl opacity-0 transition-all transition-discrete duration-300 data-[show=true]:translate-y-0 data-[show=true]:opacity-100 sm:end-8 sm:h-12 sm:w-12 dark:bg-zinc-700"
+ data-show="false"
+ id="to-top-btn"
+ >
+ <span class="sr-only">Back to top</span>
+ <svg
+ aria-hidden="true"
+ class="h-6 w-6"
+ fill="none"
+ focusable="false"
+ stroke="currentColor"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path d="M4.5 15.75l7.5-7.5 7.5 7.5" stroke-linecap="round" stroke-linejoin="round"></path>
+ </svg>
+ </button>
+</BaseLayout>
+
+<script>
+ const scrollBtn = document.getElementById("to-top-btn") as HTMLButtonElement;
+ const targetHeader = document.getElementById("blog-hero") as HTMLDivElement;
+
+ function callback(entries: IntersectionObserverEntry[]) {
+ entries.forEach((entry) => {
+ // only show the scroll to top button when the heading is out of view
+ scrollBtn.dataset.show = (!entry.isIntersecting).toString();
+ });
+ }
+
+ scrollBtn.addEventListener("click", () => {
+ document.documentElement.scrollTo({ behavior: "smooth", top: 0 });
+ });
+
+ const observer = new IntersectionObserver(callback);
+ observer.observe(targetHeader);
+</script>