make everything noscript compatible as best as possible

This commit is contained in:
Moritz Hölting 2026-03-21 18:56:09 +01:00
parent bbdc4ea50e
commit 1a0eaf2dcb
9 changed files with 106 additions and 108 deletions

View File

@ -1,12 +0,0 @@
function animate() {
const animateElements = document.querySelectorAll('.animate')
animateElements.forEach((element, index) => {
setTimeout(() => {
element.classList.add('show')
}, index * 150)
});
}
document.addEventListener("DOMContentLoaded", animate)
document.addEventListener("astro:after-swap", animate)

View File

@ -1,7 +1,6 @@
import type { CollectionEntry } from "astro:content"; import type { CollectionEntry } from "astro:content";
import { formatDate } from "@/lib/utils"; import { formatDate } from "@/lib/utils";
import { Icon } from '@iconify-icon/solid'; import { Icon } from "@iconify-icon/solid";
type Props = { type Props = {
entry: CollectionEntry<"blog"> | CollectionEntry<"projects">; entry: CollectionEntry<"blog"> | CollectionEntry<"projects">;
@ -31,13 +30,11 @@ export default function ArrowCard({ entry, pill }: Props) {
<div class="text-sm line-clamp-2">{entry.data.summary}</div> <div class="text-sm line-clamp-2">{entry.data.summary}</div>
<ul class="flex flex-wrap mt-2 gap-1"> <ul class="flex flex-wrap mt-2 gap-1">
{entry.data.tags.map( {entry.data.tags.map((tag) => (
(tag) => ( <li class="text-xs font-departure uppercase py-0.5 px-1 rounded bg-black/5 dark:bg-white/20 text-black/75 dark:text-white/75">
<li class="text-xs font-departure uppercase py-0.5 px-1 rounded bg-black/5 dark:bg-white/20 text-black/75 dark:text-white/75"> {tag}
{tag} </li>
</li> ))}
)
)}
</ul> </ul>
</div> </div>
<div class="relative overflow-hidden w-4 h-4"> <div class="relative overflow-hidden w-4 h-4">
@ -56,4 +53,4 @@ export default function ArrowCard({ entry, pill }: Props) {
</div> </div>
</a> </a>
); );
} }

View File

@ -82,7 +82,6 @@ const { title, description, image = "/open-graph.jpg" } = Astro.props;
<!-- Global Scripts --> <!-- Global Scripts -->
<script is:inline src="/js/theme.js"></script> <script is:inline src="/js/theme.js"></script>
<script is:inline src="/js/scroll.js"></script> <script is:inline src="/js/scroll.js"></script>
<script is:inline src="/js/animate.js"></script>
<ClientRouter /> <ClientRouter />
@ -107,11 +106,3 @@ const { title, description, image = "/open-graph.jpg" } = Astro.props;
integrity="sha384-m7LqaUc4JRc2uA7D4zSVUs/sgkYhmOOe9+Gd8DFmmAXH8vzs15fmw05YXvpxsoQB" integrity="sha384-m7LqaUc4JRc2uA7D4zSVUs/sgkYhmOOe9+Gd8DFmmAXH8vzs15fmw05YXvpxsoQB"
crossorigin="anonymous" crossorigin="anonymous"
/> />
<noscript>
<style>
.noscript-hide {
display: none;
}
</style>
</noscript>

View File

@ -10,20 +10,23 @@ type Props = {
}; };
export default function Blog({ data, tags }: Props) { export default function Blog({ data, tags }: Props) {
function filterPosts(): CollectionEntry<"blog">[] {
return data.filter((entry) =>
Array.from(filter()).every((value) =>
entry.data.tags.some(
(tag: string) =>
tag.toLowerCase() === String(value).toLowerCase(),
),
),
);
}
const [filter, setFilter] = createSignal(new Set<string>()); const [filter, setFilter] = createSignal(new Set<string>());
const [posts, setPosts] = createSignal<CollectionEntry<"blog">[]>([]); const [posts, setPosts] =
createSignal<CollectionEntry<"blog">[]>(filterPosts());
createEffect(() => { createEffect(() => {
setPosts( setPosts(filterPosts());
data.filter((entry) =>
Array.from(filter()).every((value) =>
entry.data.tags.some(
(tag: string) =>
tag.toLowerCase() === String(value).toLowerCase()
)
)
)
);
}); });
function toggleTag(tag: string) { function toggleTag(tag: string) {
@ -32,14 +35,14 @@ export default function Blog({ data, tags }: Props) {
new Set( new Set(
prev.has(tag) prev.has(tag)
? [...prev].filter((t) => t !== tag) ? [...prev].filter((t) => t !== tag)
: [...prev, tag] : [...prev, tag],
) ),
); );
} }
return ( return (
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6"> <div class="not-noscript:grid grid-cols-1 sm:grid-cols-3 gap-6">
<div class="col-span-3 sm:col-span-1"> <div class="col-span-3 sm:col-span-1 noscript:hidden">
<div class="sticky top-24"> <div class="sticky top-24">
<div class="text-sm font-semibold font-departure uppercase mb-2 text-black dark:text-white"> <div class="text-sm font-semibold font-departure uppercase mb-2 text-black dark:text-white">
Filter Filter
@ -58,21 +61,27 @@ export default function Blog({ data, tags }: Props) {
"hover:bg-black/10 hover:dark:bg-white/15", "hover:bg-black/10 hover:dark:bg-white/15",
"transition-colors duration-300 ease-in-out", "transition-colors duration-300 ease-in-out",
filter().has(tag) && filter().has(tag) &&
"text-black dark:text-white" "text-black dark:text-white",
)} )}
> >
<div class={cn( <div
"relative size-5 fill-black/50 dark:fill-white/50", class={cn(
"transition-colors duration-300 ease-in-out", "relative size-5 fill-black/50 dark:fill-white/50",
filter().has(tag) && "transition-colors duration-300 ease-in-out",
"fill-black dark:fill-white" filter().has(tag) &&
)}> "fill-black dark:fill-white",
)}
>
<Icon icon="pixelarticons:square" /> <Icon icon="pixelarticons:square" />
<Icon icon="pixel:check" class={cn("absolute top-0 right-0", <Icon
filter().has(tag) icon="pixel:check"
? "block" class={cn(
: "hidden" "absolute top-0 right-0",
)} /> filter().has(tag)
? "block"
: "hidden",
)}
/>
</div> </div>
{tag} {tag}
</button> </button>
@ -105,4 +114,4 @@ export default function Blog({ data, tags }: Props) {
</div> </div>
</div> </div>
); );
} }

View File

@ -11,7 +11,10 @@ const { pathname } = Astro.url;
const subpath = pathname.match(/[^/]+/g); const subpath = pathname.match(/[^/]+/g);
--- ---
<header id="header" class="fixed top-0 w-full h-16 z-50"> <header
id="header"
class="fixed top-0 w-full h-16 z-50 noscript:bg-zinc-200/75 noscript:border-black/10 noscript:backdrop-blur-sm noscript:saturate-200"
>
<Container size="md"> <Container size="md">
<div <div
class="relative h-full w-full flex flex-row justify-between items-center" class="relative h-full w-full flex flex-row justify-between items-center"

View File

@ -10,22 +10,23 @@ type Props = {
}; };
export default function Projects({ data, tags }: Props) { export default function Projects({ data, tags }: Props) {
function filterProjects(): CollectionEntry<"projects">[] {
return data.filter((entry) =>
Array.from(filter()).every((value) =>
entry.data.tags.some(
(tag: string) =>
tag.toLowerCase() === String(value).toLowerCase(),
),
),
);
}
const [filter, setFilter] = createSignal(new Set<string>()); const [filter, setFilter] = createSignal(new Set<string>());
const [projects, setProjects] = createSignal<CollectionEntry<"projects">[]>( const [projects, setProjects] =
[] createSignal<CollectionEntry<"projects">[]>(filterProjects());
);
createEffect(() => { createEffect(() => {
setProjects( setProjects(filterProjects());
data.filter((entry) =>
Array.from(filter()).every((value) =>
entry.data.tags.some(
(tag: string) =>
tag.toLowerCase() === String(value).toLowerCase()
)
)
)
);
}); });
function toggleTag(tag: string) { function toggleTag(tag: string) {
@ -34,14 +35,14 @@ export default function Projects({ data, tags }: Props) {
new Set( new Set(
prev.has(tag) prev.has(tag)
? [...prev].filter((t) => t !== tag) ? [...prev].filter((t) => t !== tag)
: [...prev, tag] : [...prev, tag],
) ),
); );
} }
return ( return (
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6"> <div class="not-noscript:grid grid-cols-1 sm:grid-cols-3 gap-6">
<div class="col-span-3 sm:col-span-1"> <div class="col-span-3 sm:col-span-1 noscript:hidden">
<div class="sticky top-24"> <div class="sticky top-24">
<div class="text-sm font-semibold font-departure uppercase mb-2 text-black dark:text-white"> <div class="text-sm font-semibold font-departure uppercase mb-2 text-black dark:text-white">
Filter Filter
@ -60,21 +61,27 @@ export default function Projects({ data, tags }: Props) {
"hover:bg-black/10 hover:dark:bg-white/15", "hover:bg-black/10 hover:dark:bg-white/15",
"transition-colors duration-300 ease-in-out", "transition-colors duration-300 ease-in-out",
filter().has(tag) && filter().has(tag) &&
"text-black dark:text-white" "text-black dark:text-white",
)} )}
> >
<div class={cn( <div
"relative size-5 fill-black/50 dark:fill-white/50", class={cn(
"transition-colors duration-300 ease-in-out", "relative size-5 fill-black/50 dark:fill-white/50",
filter().has(tag) && "transition-colors duration-300 ease-in-out",
"fill-black dark:fill-white" filter().has(tag) &&
)}> "fill-black dark:fill-white",
)}
>
<Icon icon="pixelarticons:square" /> <Icon icon="pixelarticons:square" />
<Icon icon="pixel:check" class={cn("absolute top-0 right-0", <Icon
filter().has(tag) icon="pixel:check"
? "block" class={cn(
: "hidden" "absolute top-0 right-0",
)} /> filter().has(tag)
? "block"
: "hidden",
)}
/>
</div> </div>
{tag} {tag}
</button> </button>
@ -106,4 +113,4 @@ export default function Projects({ data, tags }: Props) {
</div> </div>
</div> </div>
); );
} }

View File

@ -11,19 +11,10 @@ const { suffix } = Astro.props;
name="doomsday-year" name="doomsday-year"
id="doomsday-year" id="doomsday-year"
min="1583" min="1583"
class="w-16 font-departure" class="w-16 font-departure noscript:hidden"
/><span class="noscript-hide">{suffix}</span> /><span class="text-red-500 not-noscript:hidden"
<noscript> >JavaScript is required for this feature to work</span
<span class="text-red-500" >{suffix}
>JavaScript is required for this feature to work.</span
>
<style>
#doomsday-year {
display: none;
}
</style>
</noscript>
<script> <script>
const currentYearInput = document.getElementById( const currentYearInput = document.getElementById(

View File

@ -98,6 +98,7 @@ First, we take the last two digits of the year. If this number is odd, we add 11
Finally, we take 7 minus modulo 7 of the result and count forward that many days from the Doomsday of the century. The resulting weekday is the Doomsday of the year. Finally, we take 7 minus modulo 7 of the result and count forward that many days from the Doomsday of the century. The resulting weekday is the Doomsday of the year.
This algorithm can be remembered with the following flowchart: This algorithm can be remembered with the following flowchart:
<div class="noscript:hidden">
```mermaid ```mermaid
flowchart TD flowchart TD
B[Let T = last two digits of year] --> C{Is T odd?} B[Let T = last two digits of year] --> C{Is T odd?}
@ -116,6 +117,10 @@ flowchart TD
K --> L[Doomsday of the year] K --> L[Doomsday of the year]
``` ```
</div>
<div class="not-noscript:hidden font-semibold italic">
Please enable JavaScript to see the flowchart for the first method.
</div>
The second method is more mathematical and follows the steps: The second method is more mathematical and follows the steps:

View File

@ -84,12 +84,19 @@
.animate { .animate {
opacity: 0; opacity: 0;
transform: translateY(5px); transform: translateY(5px);
transition: opacity 1s ease, transform 1s ease; animation: fadeInUp 1s forwards;
animation-delay: 0.5s;
} }
.animate.show { @keyframes fadeInUp {
opacity: 1; 0% {
transform: translateY(0); opacity: 0;
transform: translateY(5px);
}
100% {
opacity: 1;
transform: translateY(0);
}
} }
article img { article img {
@ -98,4 +105,4 @@
display: block; display: block;
margin: 0 auto; margin: 0 auto;
} }
} }