Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/lib/components/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
import Github from "$lib/icons/github.svg?component";
import { label, links } from "$lib/links";

import ThemeIcon from "./ThemeIcon.svelte";

let {
index,
onToggleTheme,
}: {
index: boolean;
onToggleTheme: () => void;
} = $props();
</script>

Expand Down Expand Up @@ -63,6 +67,13 @@
<Github aria-hidden="true" />
<span class="sr-only">Github profile</span>
</a>
<button
class="cursor-pointer"
onclick={() => onToggleTheme()}
type="button"
>
<ThemeIcon />
</button>
</div>
</nav>
</header>
27 changes: 27 additions & 0 deletions src/lib/components/ThemeIcon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<svg class="" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<mask id="theme-mask">
<rect fill="white" height="24" width="24" x="0" y="0" />
<circle
class="duration-500 motion-safe:transition-[cx,cy] dark:delay-500 dark:[cx:16.5px] dark:[cy:7.5px]"
cx="24"
cy="0"
fill="black"
r="5.5"
/>
</mask>

<g mask="url(#theme-mask)">
<circle
class="duration-500 not-dark:delay-500 motion-safe:transition-[r] dark:[r:9px]"
cx="12"
cy="12"
r="5"
/>

<path
class="duration-500 not-dark:delay-500 motion-safe:transition-transform dark:scale-80 dark:rotate-90"
d="M5 13H1V11H5V13ZM23 13H19V11H23V13ZM11 5V1H13V5H11ZM11 23V19H13V23H11ZM6.4 7.75L3.875 5.325L5.3 3.85L7.7 6.35L6.4 7.75ZM18.7 20.15L16.275 17.625L17.6 16.25L20.125 18.675L18.7 20.15ZM16.25 6.4L18.675 3.875L20.15 5.3L17.65 7.7L16.25 6.4ZM3.85 18.7L6.375 16.275L7.75 17.6L5.325 20.125L3.85 18.7Z"
transform-origin="center"
/>
</g>
</svg>
8 changes: 8 additions & 0 deletions src/lib/context/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getContext, setContext } from "svelte";

const KEY = "theme";

export const setTheme = (theme: () => "dark" | "light") =>
setContext(KEY, theme);

export const getTheme = () => getContext<() => "dark" | "light">(KEY);
12 changes: 12 additions & 0 deletions src/lib/js/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");

const storageTheme = localStorage.getItem("theme");

const theme =
storageTheme === "dark" || storageTheme === "light"
? storageTheme
: mediaQuery.matches
? "dark"
: "light";

document.documentElement.classList.toggle("dark", theme === "dark");
43 changes: 42 additions & 1 deletion src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@

import "./style.css";

import { browser } from "$app/environment";
import { onNavigate } from "$app/navigation";
import { page } from "$app/state";
import Header from "$lib/components/Header.svelte";
import { setTheme } from "$lib/context/theme";
import themeScript from "$lib/js/theme?url";
import { MediaQuery } from "svelte/reactivity";
import { fly } from "svelte/transition";

import type { Snapshot } from "./$types";
Expand Down Expand Up @@ -36,14 +40,51 @@
// eslint-disable-next-line svelte/@typescript-eslint/no-unnecessary-condition
if (scrollElement) scrollElement.scrollTop = scrollPosition;
});

// Handle theme
const mediaQuery = new MediaQuery("(prefers-color-scheme: dark)");

let storageTheme: null | string = $state(
browser ? window.localStorage.getItem("theme") : null,
);

let theme: "dark" | "light" = $derived(
storageTheme === "dark" || storageTheme === "light"
? storageTheme
: mediaQuery.current
? "dark"
: "light",
);

setTheme(() => theme);

$effect(() => {
if (browser)
document.documentElement.classList.toggle("dark", theme === "dark");
});
</script>

<svelte:window
onstorage={(e) => void (e.key === "theme" && (storageTheme = e.newValue))}
/>

<svelte:head>
<script src={themeScript}></script>
</svelte:head>

<div
class="grid h-full overflow-hidden motion-safe:transition-all"
class:grid-rows-[auto_0fr]={index}
class:grid-rows-[auto_1fr]={!index}
>
<Header {index} />
<Header
{index}
onToggleTheme={() => {
const newTheme = theme === "light" ? "dark" : "light";
storageTheme = newTheme;
window.localStorage.setItem("theme", newTheme);
}}
/>

<main
class="grid grid-cols-1 grid-rows-1 justify-items-center overflow-hidden"
Expand Down
2 changes: 2 additions & 0 deletions src/routes/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

@plugin "@tailwindcss/typography";

@custom-variant dark (&:where(.dark *, .dark));

@theme {
--animate-terminal-blink: terminal-blink 1s step-end infinite;

Expand Down
Loading