added theme toggle

This commit is contained in:
liyunze 2026-09-03 15:46:26 +08:00
parent 21ddf2d621
commit a42899e522
7 changed files with 256 additions and 17 deletions

View file

@ -1,10 +1,11 @@
---
import VerticalNav from "./verticalNav.astro";
import SearchModal from "../ui/SearchModal.astro";
import ThemeToggle from "../ui/ThemeToggle.astro";
---
<div
class="w-full backdrop-blur fixed top-0 text-white bg-secondary/75 z-[60] text-lg py-3 h-[var(--header-height)]"
class="site-nav w-full backdrop-blur fixed top-0 z-[60] text-lg py-3 h-[var(--header-height)]"
data-pagefind-ignore
>
<div
@ -21,7 +22,7 @@ import SearchModal from "../ui/SearchModal.astro";
<!-- Vertical Navbar -->
<aside
id="vertical-navbar"
class="sidebar fixed block overflow-y-auto duration-300 z-[60] lg:hidden bg-[#151515] py-8 px-8 h-[calc(100vh-var(--header-height))] top-[--header-height] left-0 w-full"
class="sidebar fixed block overflow-y-auto duration-300 z-[60] lg:hidden py-8 px-8 h-[calc(100vh-var(--header-height))] top-[--header-height] left-0 w-full"
transition:persist="vertical-nav-from-left"
>
<VerticalNav showHome={true} />
@ -47,30 +48,33 @@ import SearchModal from "../ui/SearchModal.astro";
transition:name="navbar"
>
<a
class="no-target-blank hover:text-black hover:bg-white rounded-md transition-colors duration-300 px-2 py-1 text-white hover:no-underline"
class="no-target-blank nav-link rounded-md transition-colors duration-300 px-2 py-1 hover:no-underline"
href="/get-started/introduction">Get started</a
>
<a
class="no-target-blank hover:text-black hover:bg-white rounded-md transition-colors duration-300 px-2 py-1 text-white hover:no-underline"
class="no-target-blank nav-link rounded-md transition-colors duration-300 px-2 py-1 hover:no-underline"
href="/guide/general-tips">Guide</a
>
<a
class="no-target-blank hover:text-black hover:bg-white rounded-md transition-colors duration-300 px-2 py-1 text-white hover:no-underline"
class="no-target-blank nav-link rounded-md transition-colors duration-300 px-2 py-1 hover:no-underline"
href="/games">Games</a
>
<a
class="no-target-blank hover:text-black hover:bg-white rounded-md transition-colors duration-300 px-2 py-1 text-white hover:no-underline"
class="no-target-blank nav-link rounded-md transition-colors duration-300 px-2 py-1 hover:no-underline"
href="/groups">Groups</a
>
<a
class="no-target-blank hover:text-black hover:bg-white rounded-md transition-colors duration-300 px-2 py-1 text-white hover:no-underline"
class="no-target-blank nav-link rounded-md transition-colors duration-300 px-2 py-1 hover:no-underline"
href="/credits">Credits</a
>
</div>
<!-- Search bar -->
<div class="hidden lg:flex lg:flex-1 justify-end relative">
<div class="flex lg:flex-1 justify-end items-center gap-3 relative">
<div class="hidden lg:block">
<SearchModal id="searchModal" open="Open modal" />
</div>
<ThemeToggle />
</div>
</div>
</div>
@ -99,6 +103,34 @@ import SearchModal from "../ui/SearchModal.astro";
);
}
.site-nav {
background: var(--nav-background);
color: var(--text-color);
border-bottom: 1px solid color-mix(in srgb, var(--border-color) 55%, transparent);
}
.site-nav .sidebar {
background: var(--sidebar-background);
}
.site-nav a {
color: var(--text-color);
}
.site-nav .nav-link:hover {
background: var(--control-hover);
}
html[data-theme="light"] {
--pagefind-ui-primary: #171717;
--pagefind-ui-text: #171717;
--pagefind-ui-background: #ffffff;
--pagefind-ui-border: #cbd5e1;
--pagefind-ui-tag: #171717;
--foreground: #171717;
--background: #171717;
}
.hamburger-menu {
--x-width: calc(var(--hamburger-height) * 1.41421356237);

View file

@ -1,6 +1,6 @@
<div class="flex flex-col">
<div class="text-center text-4xl lg:text-6xl font-bold">
<span class="text-slate-200">Getting started with</span>
<span class="text-[var(--text-color)]">Getting started with</span>
<div
id="coworking-streams"
class="text-transparent animate-gradient py-1 lg:py-3 bg-[length:300%]"

View file

@ -15,7 +15,7 @@ const { id } = Astro.props;
<button
onclick={`window.${id}.showModal()`}
class="text-sm flex bg-[#101010] w-60 px-2 py-2 border border-gray-600 border-solid text-left rounded-lg group"
class="search-trigger text-sm flex w-60 px-2 py-2 border border-solid text-left rounded-lg group"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -33,7 +33,7 @@ const { id } = Astro.props;
<dialog
id=`${id}`
class="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-[#202020] border-2 border-solid border-[#505050] text-white rounded-lg w-4/5 lg:w-1/2 h-5/6 px-10 py-10 animate-fadeIn"
class="search-dialog fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 border-2 border-solid rounded-lg w-4/5 lg:w-1/2 h-5/6 px-10 py-10 animate-fadeIn"
>
<form method="dialog">
<button class="absolute top-3 right-3"
@ -52,3 +52,17 @@ const { id } = Astro.props;
</form>
<div id=`${id}-search` class="mt-2"></div>
</dialog>
<style>
.search-trigger {
background: var(--control-background);
border-color: var(--border-color);
color: var(--text-color);
}
.search-dialog {
background: var(--surface-color);
border-color: var(--border-color);
color: var(--text-color);
}
</style>

View file

@ -0,0 +1,115 @@
<button
type="button"
class="theme-toggle"
aria-label="Switch to light theme"
title="Switch to light theme"
>
<svg
class="sun-icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2M12 20v2M4.93 4.93l1.42 1.42M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.42-1.42M17.66 6.34l1.41-1.41"></path>
</svg>
<svg
class="moon-icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"></path>
</svg>
</button>
<script is:inline>
(() => {
const updateThemeToggle = () => {
const button = document.querySelector(".theme-toggle");
if (!button) return;
const isDark = document.documentElement.dataset.theme === "dark";
const nextTheme = isDark ? "light" : "dark";
button.setAttribute("aria-label", `Switch to ${nextTheme} theme`);
button.setAttribute("title", `Switch to ${nextTheme} theme`);
};
if (!window.__themeToggleReady) {
document.addEventListener("click", (event) => {
const button = event.target.closest(".theme-toggle");
if (!button) return;
const theme =
document.documentElement.dataset.theme === "dark"
? "light"
: "dark";
document.documentElement.dataset.theme = theme;
document.documentElement.style.colorScheme = theme;
localStorage.setItem("theme", theme);
updateThemeToggle();
});
window.__themeToggleReady = true;
}
document.addEventListener("astro:page-load", updateThemeToggle);
updateThemeToggle();
})();
</script>
<style>
.theme-toggle {
display: grid;
width: 2.25rem;
height: 2.25rem;
flex: 0 0 auto;
place-items: center;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
background: var(--control-background);
color: var(--text-color);
cursor: pointer;
transition:
background-color 150ms ease,
border-color 150ms ease,
transform 150ms ease;
}
.theme-toggle:hover {
background: var(--control-hover);
}
.theme-toggle:active {
transform: scale(0.94);
}
.theme-toggle:focus-visible {
outline: 2px solid #3f84d8;
outline-offset: 2px;
}
.theme-toggle svg {
width: 1.25rem;
height: 1.25rem;
grid-area: 1 / 1;
}
.sun-icon,
:global(html[data-theme="light"]) .moon-icon {
display: none;
}
:global(html[data-theme="light"]) .sun-icon {
display: block;
}
</style>

View file

@ -67,7 +67,7 @@ export const Card = ({
return (
<div
className={cn(
"relative z-20 h-full w-full overflow-hidden rounded-2xl border border-transparent bg-[#232323] p-4 group-hover:border-slate-700 dark:border-white/[0.2]",
"relative z-20 h-full w-full overflow-hidden rounded-2xl border border-[var(--card-border)] bg-[var(--card-background)] p-4 shadow-sm group-hover:border-[var(--card-border-hover)]",
className,
)}
>
@ -87,7 +87,7 @@ export const CardTitle = ({
return (
<h4
className={cn(
"mt-4 font-bold tracking-wide text-zinc-100 text-xl",
"mt-4 font-bold tracking-wide text-[var(--text-color)] text-xl",
className,
)}
>
@ -105,7 +105,7 @@ export const CardDescription = ({
return (
<p
className={cn(
"mt-5 text-sm leading-relaxed tracking-wide text-zinc-400",
"mt-5 text-sm leading-relaxed tracking-wide text-[var(--muted-text)]",
className,
)}
>

View file

@ -11,7 +11,7 @@ const { title, description } = Astro.props;
---
<!doctype html>
<html lang="en">
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="description" content={description} />
@ -48,6 +48,34 @@ const { title, description } = Astro.props;
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link href="/pagefind/pagefind-ui.css" rel="stylesheet" />
<script is:inline>
(() => {
const applyTheme = (root) => {
const savedTheme = localStorage.getItem("theme");
const theme =
savedTheme === "light" || savedTheme === "dark"
? savedTheme
: window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";
root.dataset.theme = theme;
root.style.colorScheme = theme;
};
applyTheme(document.documentElement);
if (!window.__themePersistenceReady) {
document.addEventListener("astro:before-swap", (event) => {
applyTheme(event.newDocument.documentElement);
});
document.addEventListener("astro:after-swap", () => {
applyTheme(document.documentElement);
});
window.__themePersistenceReady = true;
}
})();
</script>
<script src="/pagefind/pagefind-ui.js" is:inline></script>
<script is:inline src="/_vercel/insights/script.js"></script>
<ClientRouter />
@ -74,8 +102,8 @@ const { title, description } = Astro.props;
html {
font-family: system-ui, sans-serif;
background-color: #171717;
color: rgb(255 255 255);
background-color: var(--background-color);
color: var(--text-color);
}
html,

View file

@ -1,5 +1,55 @@
@import "tailwindcss";
:root,
html[data-theme="dark"] {
--background-color: #171717;
--surface-color: #202020;
--nav-background: rgb(23 23 23 / 0.82);
--sidebar-background: #151515;
--control-background: #101010;
--control-hover: #2f2f2f;
--text-color: #ffffff;
--muted-text: #9ca3af;
--border-color: #505050;
--card-background: #232323;
--card-border: rgb(255 255 255 / 0.2);
--card-border-hover: #334155;
}
html[data-theme="light"] {
--background-color: #f8fafc;
--surface-color: #ffffff;
--nav-background: rgb(255 255 255 / 0.86);
--sidebar-background: #f8fafc;
--control-background: #ffffff;
--control-hover: #e2e8f0;
--text-color: #171717;
--muted-text: #4b5563;
--border-color: #cbd5e1;
--card-background: #ffffff;
--card-border: #cbd5e1;
--card-border-hover: #94a3b8;
}
html,
body {
transition:
background-color 180ms ease,
color 180ms ease;
}
html[data-theme="light"] .text-white {
color: var(--text-color);
}
html[data-theme="light"] .text-gray-400 {
color: var(--muted-text);
}
html[data-theme="light"] .guide-content code {
background-color: #e2e8f0;
}
@theme {
--color-primary: #fafafa;
--color-secondary: #171717;