migrate to astro
This commit is contained in:
parent
82150df591
commit
5e67b2bb0d
135 changed files with 5886 additions and 8330 deletions
61
src/components/BaseHead.astro
Normal file
61
src/components/BaseHead.astro
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
// Import the global.css file here so that it is included on
|
||||
// all pages through the use of the <BaseHead /> component.
|
||||
import "../styles/global.css";
|
||||
import ubuntuRegularWoff2 from "@fontsource/ubuntu/files/ubuntu-latin-400-normal.woff2?url";
|
||||
import ubuntuBoldWoff2 from "@fontsource/ubuntu/files/ubuntu-latin-700-normal.woff2?url";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
|
||||
const { title, description, image = "/blog-placeholder-1.jpg" } = Astro.props;
|
||||
---
|
||||
|
||||
<!-- Global Metadata -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<!-- Font preloads, keep them even if it throws a warning for not using them due to the system providing them -->
|
||||
<link
|
||||
rel="preload"
|
||||
href={ubuntuRegularWoff2}
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href={ubuntuBoldWoff2}
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin
|
||||
/>
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title} />
|
||||
<meta name="description" content={description} />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={new URL(image, Astro.url)} />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={Astro.url} />
|
||||
<meta property="twitter:title" content={title} />
|
||||
<meta property="twitter:description" content={description} />
|
||||
<meta property="twitter:image" content={new URL(image, Astro.url)} />
|
||||
148
src/components/DarkModeToggle.astro
Normal file
148
src/components/DarkModeToggle.astro
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<script>
|
||||
const themeToggleBtns = document.querySelectorAll(
|
||||
".theme-toggle",
|
||||
) as NodeListOf<HTMLInputElement>;
|
||||
const sliders = document.querySelectorAll(".slider");
|
||||
|
||||
// Set initial state of toggle based on previous settings
|
||||
if (
|
||||
localStorage.getItem("color-theme") === "dark" ||
|
||||
(!("color-theme" in localStorage) &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
) {
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = true));
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = false));
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
// Remove no-transition class after initial load
|
||||
window.addEventListener("load", () => {
|
||||
setTimeout(() => {
|
||||
sliders.forEach((slider) =>
|
||||
slider.classList.remove("no-transition"),
|
||||
);
|
||||
}, 0);
|
||||
});
|
||||
themeToggleBtns.forEach((btn) => {
|
||||
btn.addEventListener("change", function () {
|
||||
// If is set in localStorage
|
||||
if (localStorage.getItem("color-theme")) {
|
||||
if (localStorage.getItem("color-theme") === "light") {
|
||||
document.documentElement.classList.add("dark");
|
||||
localStorage.setItem("color-theme", "dark");
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = true));
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
localStorage.setItem("color-theme", "light");
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = false));
|
||||
}
|
||||
} else {
|
||||
if (document.documentElement.classList.contains("dark")) {
|
||||
document.documentElement.classList.remove("dark");
|
||||
localStorage.setItem("color-theme", "light");
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = false));
|
||||
} else {
|
||||
document.documentElement.classList.add("dark");
|
||||
localStorage.setItem("color-theme", "dark");
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = true));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<label class="switch" for="theme-toggle">
|
||||
<span class="sr-only">Toggle dark mode</span>
|
||||
<input
|
||||
id="theme-toggle"
|
||||
class="theme-toggle"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
/>
|
||||
<span class="slider round no-transition" aria-hidden="true"></span>
|
||||
</label>
|
||||
|
||||
<style>
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--tw-mytheme-200);
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: gold;
|
||||
transition: 0.4s;
|
||||
background: radial-gradient(
|
||||
yellow,
|
||||
orange 63%,
|
||||
transparent calc(63% + 3px) 100%
|
||||
);
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--tw-mytheme-600);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
background-color: white;
|
||||
background: radial-gradient(
|
||||
circle at 19% 19%,
|
||||
transparent 41%,
|
||||
var(--tw-mytheme-50) 43%
|
||||
);
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 5px var(--tw-mytheme-700);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.no-transition {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.no-transition:before {
|
||||
transition: none !important;
|
||||
}
|
||||
</style>
|
||||
14
src/components/Footer.astro
Normal file
14
src/components/Footer.astro
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
const today = new Date();
|
||||
---
|
||||
|
||||
<footer class="bg-gray-100/60 dark:bg-mytheme-900 shadow-sm">
|
||||
© {today.getFullYear()} Alexander Daichendt. All rights reserved.
|
||||
</footer>
|
||||
<style>
|
||||
footer {
|
||||
padding: 2em 1em 6em 1em;
|
||||
color: rgb(var(--gray));
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
17
src/components/FormattedDate.astro
Normal file
17
src/components/FormattedDate.astro
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
interface Props {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
const { date } = Astro.props;
|
||||
---
|
||||
|
||||
<time datetime={date.toISOString()}>
|
||||
{
|
||||
date.toLocaleDateString('en-us', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
}
|
||||
</time>
|
||||
14
src/components/Header.astro
Normal file
14
src/components/Header.astro
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
import HeaderLink from "./HeaderLink.astro";
|
||||
---
|
||||
|
||||
<header class="mb-8 w-full lg:w-[768px] max-w-[calc(100%-2em)] lg:mx-auto">
|
||||
<nav>
|
||||
<div class="flex gap-4">
|
||||
<HeaderLink href="/">Home</HeaderLink>
|
||||
<HeaderLink href="/blog">Blog</HeaderLink>
|
||||
<HeaderLink href="/publications">Publications</HeaderLink>
|
||||
<HeaderLink href="/contact">Contact</HeaderLink>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
56
src/components/HeaderLink.astro
Normal file
56
src/components/HeaderLink.astro
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
|
||||
type Props = HTMLAttributes<"a">;
|
||||
|
||||
const { href, class: className, ...props } = Astro.props;
|
||||
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, "");
|
||||
const subpath = pathname.match(/[^\/]+/g);
|
||||
const isActive = href === pathname || href === "/" + (subpath?.[0] || "");
|
||||
---
|
||||
|
||||
<a
|
||||
href={href}
|
||||
class:list={[
|
||||
className,
|
||||
isActive ? "active" : "",
|
||||
"p-2 hover:text-mytheme-800 hover:dark:text-mytheme-100 inline-block no-underline relative",
|
||||
]}
|
||||
{...props}
|
||||
>
|
||||
<slot />
|
||||
</a>
|
||||
|
||||
<style>
|
||||
/* Hover animation for non-active links */
|
||||
a:not(.active)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 4px;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
background-color: var(--tw-mytheme-400);
|
||||
transition: all 0.3s ease-in-out;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
a:not(.active):hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Solid underline for active links */
|
||||
a.active::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: var(--tw-mytheme-400);
|
||||
}
|
||||
|
||||
a.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
5
src/components/Li.astro
Normal file
5
src/components/Li.astro
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<li class="pt-2 mr-4" {...Astro.props}>
|
||||
<div class="self-center">
|
||||
<slot />
|
||||
</div>
|
||||
</li>
|
||||
27
src/components/Link.astro
Normal file
27
src/components/Link.astro
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
interface Props {
|
||||
href: string;
|
||||
disablePrefetch?: boolean;
|
||||
}
|
||||
|
||||
const { href, disablePrefetch = false } = Astro.props;
|
||||
|
||||
const internal = !href.startsWith("http");
|
||||
|
||||
let linkProps = internal
|
||||
? !disablePrefetch
|
||||
? { "data-astro-prefetch": `${!disablePrefetch}` }
|
||||
: {}
|
||||
: {
|
||||
rel: "nofollow noreferrer noopener",
|
||||
target: "_blank",
|
||||
};
|
||||
---
|
||||
|
||||
<a
|
||||
{...linkProps}
|
||||
{href}
|
||||
class="text-special text-mytheme-700 dark:text-mytheme-300 font-medium hover:bg-outline hover:text-dark no-underline"
|
||||
>
|
||||
<span class="underline break-words"><slot /></span></a
|
||||
>
|
||||
3
src/components/Ol.astro
Normal file
3
src/components/Ol.astro
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<ol class="list-disc list-inside" {...Astro.props}>
|
||||
<slot />
|
||||
</ol>
|
||||
9
src/components/Picture.astro
Normal file
9
src/components/Picture.astro
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
import { Picture as AstroPicture } from "astro:assets";
|
||||
---
|
||||
|
||||
<AstroPicture
|
||||
src={Astro.props.src}
|
||||
alt={Astro.props.alt}
|
||||
formats={["avif", "webp"]}
|
||||
/>
|
||||
3
src/components/Ul.astro
Normal file
3
src/components/Ul.astro
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<ul class="list-disc list-outside ml-8" {...Astro.props}>
|
||||
<slot />
|
||||
</ul>
|
||||
Loading…
Add table
Add a link
Reference in a new issue