added theme toggle

This commit is contained in:
liyunze 2026-09-03 12:12:59 +08:00
parent 7ce605fc30
commit 1a3249a9fb
11 changed files with 150 additions and 22 deletions

View file

@ -10,7 +10,7 @@ import Card from "../global/Card.astro";
<div class="w-[90%] md:w-[600px] lg:w-2/3"> <div class="w-[90%] md:w-[600px] lg:w-2/3">
<Card <Card
shadowColor="rgb(4, 57, 57)" shadowColor="rgb(4, 57, 57)"
className="animate-fadeIn bg-gradient-to-br from-[#101010] via-[#252525] to-[#101010]" className="theme-dark-card animate-fadeIn bg-gradient-to-br from-[#101010] via-[#252525] to-[#101010]"
> >
<div class="flex flex-col items-center justify-center gap-2 py-10"> <div class="flex flex-col items-center justify-center gap-2 py-10">
<div class="mb-1 px-5"> <div class="mb-1 px-5">
@ -36,7 +36,7 @@ import Card from "../global/Card.astro";
<div class="text-muted-foreground text-base"> <div class="text-muted-foreground text-base">
Email me at: Email me at:
</div> </div>
<div class="text-white"> <div>
<a <a
href="mailto:hello@rython.dev" href="mailto:hello@rython.dev"
class="hover:text-accent">hello@rython.dev</a class="hover:text-accent">hello@rython.dev</a

View file

@ -19,19 +19,19 @@ export default function Experience() {
</TabsList> </TabsList>
<TabsContent <TabsContent
value="experience" value="experience"
className="rounded-md bg-gradient-to-bl from-[#101010] via-[#272727] to-[#181818] px-5 py-5" className="theme-dark-card rounded-md bg-gradient-to-bl from-[#101010] via-[#272727] to-[#181818] px-5 py-5"
> >
{ExperienceTabContent()} {ExperienceTabContent()}
</TabsContent> </TabsContent>
<TabsContent <TabsContent
value="education" value="education"
className="bg-secondary rounded-md bg-gradient-to-bl from-[#101010] via-[#272727] to-[#181818] px-5 py-5" className="theme-dark-card bg-secondary rounded-md bg-gradient-to-bl from-[#101010] via-[#272727] to-[#181818] px-5 py-5"
> >
{EducationTabContent()} {EducationTabContent()}
</TabsContent> </TabsContent>
<TabsContent <TabsContent
value="extracurricular" value="extracurricular"
className="bg-secondary rounded-md bg-gradient-to-bl from-[#101010] via-[#272727] to-[#181818] px-5 py-5" className="theme-dark-card bg-secondary rounded-md bg-gradient-to-bl from-[#101010] via-[#272727] to-[#181818] px-5 py-5"
> >
{ExtracurricularTabContent()} {ExtracurricularTabContent()}
</TabsContent> </TabsContent>

View file

@ -63,7 +63,7 @@ const {
.card { .card {
/* background: #1c1c1c; */ /* background: #1c1c1c; */
/* background: linear-gradient(135deg, rgb(38, 38, 38)#101010 20%, 100%); */ /* background: linear-gradient(135deg, rgb(38, 38, 38)#101010 20%, 100%); */
box-shadow: 0 10px 25px 10px var(--shadowColor); box-shadow: 0 8px 20px 2px var(--shadowColor);
padding: 0.5rem; padding: 0.5rem;
} }
</style> </style>

View file

@ -9,9 +9,9 @@ import { Image } from "astro:assets";
<div class="w-[90%] md:w-[600px] lg:w-1/2"> <div class="w-[90%] md:w-[600px] lg:w-1/2">
<Card <Card
shadowColor="rgba(4, 57, 57, 0.5)" shadowColor="rgba(4, 57, 57, 0.5)"
className="animate-fadeIn bg-gradient-to-br from-[#101010] to-[#202020]" className="theme-dark-card animate-fadeIn bg-gradient-to-br from-[#101010] to-[#202020]"
> >
<div id="intro" class="flex h-fit flex-col text-white"> <div id="intro" class="flex h-fit flex-col">
<div class="w-full px-5 pt-10 pb-2 lg:px-10"> <div class="w-full px-5 pt-10 pb-2 lg:px-10">
<div class="flex h-20 flex-row"> <div class="flex h-20 flex-row">
<div class="mr-1 flex w-20 items-center justify-center"> <div class="mr-1 flex w-20 items-center justify-center">

View file

@ -10,7 +10,7 @@ import Card from "../global/Card.astro";
> >
<Card <Card
shadowColor="rgb(4, 57, 57)" shadowColor="rgb(4, 57, 57)"
className="bg-gradient-to-br from-[#101010] to-[#202020]" className="theme-dark-card bg-gradient-to-br from-[#101010] to-[#202020]"
> >
<div <div
class="flex flex-col items-start justify-start gap-2 px-5 py-5 text-left" class="flex flex-col items-start justify-start gap-2 px-5 py-5 text-left"
@ -51,7 +51,7 @@ import Card from "../global/Card.astro";
</Card> </Card>
<Card <Card
shadowColor="rgb(4, 57, 57)" shadowColor="rgb(4, 57, 57)"
className="bg-gradient-to-br from-[#202020] to-[#101010] lg:col-span-2" className="theme-dark-card bg-gradient-to-br from-[#202020] to-[#101010] lg:col-span-2"
> >
<div class="w-full px-5 py-5 lg:w-4/5"> <div class="w-full px-5 py-5 lg:w-4/5">
<div class="mb-6"> <div class="mb-6">

View file

@ -22,9 +22,31 @@ export const HoverNavigation = ({
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null); const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const [activeIndex, setActiveIndex] = useState<number | null>(null); const [activeIndex, setActiveIndex] = useState<number | null>(null);
const [hamburgerOpen, setHamburgerOpen] = useState(false); const [hamburgerOpen, setHamburgerOpen] = useState(false);
const [theme, setTheme] = useState<"light" | "dark">("dark");
const hamburgerRef = useRef<HTMLDivElement>(null); const hamburgerRef = useRef<HTMLDivElement>(null);
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setTheme(
document.documentElement.dataset.theme === "light"
? "light"
: "dark",
);
}, []);
const toggleTheme = () => {
const nextTheme = theme === "dark" ? "light" : "dark";
document.documentElement.dataset.theme = nextTheme;
localStorage.setItem("theme", nextTheme);
document
.querySelector('meta[name="theme-color"]')
?.setAttribute(
"content",
nextTheme === "dark" ? "#101010" : "#fafafa",
);
setTheme(nextTheme);
};
// Track which section is in view // Track which section is in view
useEffect(() => { useEffect(() => {
const sectionIds = items.map((item) => { const sectionIds = items.map((item) => {
@ -106,7 +128,7 @@ export const HoverNavigation = ({
return ( return (
<> <>
<div className="bg-secondary/50 fixed top-5 left-1/2 z-30 box-border hidden -translate-x-1/2 items-center justify-center overflow-auto rounded-[40px] border-2 border-solid border-gray-700 px-5 py-3 backdrop-blur-md md:flex md:w-11/12 lg:w-2/3 xl:w-auto"> <div className="theme-surface bg-secondary/50 fixed top-5 left-1/2 z-30 box-border hidden -translate-x-1/2 items-center justify-center overflow-auto rounded-[40px] border-2 border-solid border-gray-700 px-5 py-3 backdrop-blur-md md:flex md:w-11/12 lg:w-2/3 xl:w-auto">
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex flex-row"> <div className="flex flex-row">
<nav <nav
@ -161,13 +183,15 @@ export const HoverNavigation = ({
))} ))}
</motion.ul> </motion.ul>
</nav> </nav>
<ThemeToggle theme={theme} onToggle={toggleTheme} />
</div> </div>
</div> </div>
</div> </div>
<div className="bg-secondary/50 fixed top-5 left-1/2 z-50 flex w-4/5 -translate-x-1/2 justify-end rounded-full border-2 border-solid border-gray-700 pr-5 backdrop-blur-xl lg:hidden"> <div className="theme-surface bg-secondary/50 fixed top-5 left-1/2 z-50 flex w-4/5 -translate-x-1/2 items-center justify-end rounded-full border-2 border-solid border-gray-700 pr-5 backdrop-blur-xl lg:hidden">
<ThemeToggle theme={theme} onToggle={toggleTheme} />
<div <div
ref={hamburgerRef} ref={hamburgerRef}
className="flex rounded-full px-1 py-1 md:hidden [&>div]:rounded-full [&>div_div]:bg-white!" className="flex rounded-full px-1 py-1 md:hidden [&>div]:rounded-full [&>div_div]:bg-current!"
> >
<HamburgerCross <HamburgerCross
toggle={setHamburgerOpen} toggle={setHamburgerOpen}
@ -185,7 +209,7 @@ export const HoverNavigation = ({
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }} exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="bg-secondary/50 fixed top-24 right-0 left-1/2 z-40 w-4/5 -translate-x-1/2 rounded-2xl py-2 text-white backdrop-blur-xl md:hidden" className="theme-surface bg-secondary/50 fixed top-24 right-0 left-1/2 z-40 w-4/5 -translate-x-1/2 rounded-2xl py-2 backdrop-blur-xl md:hidden"
> >
<nav aria-label="Mobile Navigation"> <nav aria-label="Mobile Navigation">
<ul className="flex flex-col gap-2"> <ul className="flex flex-col gap-2">
@ -220,6 +244,49 @@ export const HoverNavigation = ({
); );
}; };
const ThemeToggle = ({
theme,
onToggle,
}: {
theme: "light" | "dark";
onToggle: () => void;
}) => (
<button
type="button"
onClick={onToggle}
aria-label={`Switch to ${theme === "dark" ? "light" : "dark"} theme`}
title={`Switch to ${theme === "dark" ? "light" : "dark"} theme`}
className="ml-2 grid size-10 shrink-0 cursor-pointer place-items-center rounded-full transition-colors hover:bg-current/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
>
{theme === "dark" ? (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className="size-5"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<circle cx="12" cy="12" r="4" />
<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" />
</svg>
) : (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className="size-5"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z" />
</svg>
)}
</button>
);
export const NavBarLink = ({ export const NavBarLink = ({
className, className,
children, children,
@ -230,7 +297,7 @@ export const NavBarLink = ({
return ( return (
<div <div
className={cn( className={cn(
"relative z-20 h-fit w-full overflow-hidden rounded-2xl text-center mix-blend-difference transition-all ease-out", "theme-nav-link relative z-20 h-fit w-full overflow-hidden rounded-2xl text-center mix-blend-difference transition-all ease-out",
className, className,
)} )}
> >

View file

@ -57,7 +57,7 @@ export function Button({
<div <div
className={cn( className={cn(
"relative flex h-full w-full items-center justify-center border border-slate-800 bg-slate-900/[0.8] text-sm text-white antialiased backdrop-blur-xl", "theme-dark-card relative flex h-full w-full items-center justify-center border border-slate-800 bg-slate-900/[0.8] text-sm antialiased backdrop-blur-xl",
className, className,
)} )}
style={{ style={{

View file

@ -26,7 +26,7 @@ function TabsList({
<TabsPrimitive.List <TabsPrimitive.List
data-slot="tabs-list" data-slot="tabs-list"
className={cn( className={cn(
"text-primary inline-flex h-9 w-fit items-center justify-center rounded-lg bg-[#101010] p-[3px]", "theme-dark-card text-primary inline-flex h-9 w-fit items-center justify-center rounded-lg bg-[#101010] p-[3px]",
className, className,
)} )}
{...props} {...props}

View file

@ -21,7 +21,20 @@ import { ClientRouter } from "astro:transitions";
content="Rython, student, developer, frontend, web developer, streamer, coworking" content="Rython, student, developer, frontend, web developer, streamer, coworking"
/> />
<meta name="author" content="RythonDev" /> <meta name="author" content="RythonDev" />
<meta name="theme-color" content="#2B2D31" /> <meta name="theme-color" content="#101010" />
<script is:inline>
(() => {
const savedTheme = localStorage.getItem("theme");
const theme =
savedTheme === "light" || savedTheme === "dark"
? savedTheme
: window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark";
document.documentElement.dataset.theme = theme;
})();
</script>
{thumbnail && <meta name="twitter:image" content={thumbnail} />} {thumbnail && <meta name="twitter:image" content={thumbnail} />}
@ -72,15 +85,23 @@ import { ClientRouter } from "astro:transitions";
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
color-scheme: dark;
}
html[data-theme="light"] {
color-scheme: light;
} }
body { body {
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
color: #fff; color: var(--page-foreground);
background: #1d1c20;
background: background:
radial-gradient(circle, #303030 1px, transparent 1px), #101010; radial-gradient(circle, var(--page-dot) 1px, transparent 1px),
var(--page-background);
background-size: 20px 20px; background-size: 20px 20px;
transition:
color 0.25s ease,
background-color 0.25s ease;
} }
</style> </style>

View file

@ -34,7 +34,7 @@ import { Button } from "@/components/ui/moving-border";
<Button <Button
client:visible client:visible
borderRadius="1.75rem" borderRadius="1.75rem"
className="bg-secondary hover:bg-accent cursor-pointer border-slate-800 px-5 text-white transition-all duration-300 hover:text-black" className="bg-secondary hover:bg-accent text-primary cursor-pointer border-slate-800 px-5 transition-all duration-300 hover:text-black"
duration={2000} duration={2000}
as={"a"} as={"a"}
href="/projects" href="/projects"

View file

@ -14,6 +14,46 @@
--animate-gradient: gradient 15s ease infinite; --animate-gradient: gradient 15s ease infinite;
} }
:root {
--page-background: #101010;
--page-foreground: #fff;
--page-dot: #303030;
--surface: rgb(29 28 32 / 0.75);
--surface-border: #374151;
}
html[data-theme="light"] {
--color-primary: #171717;
--color-secondary: #f5f5f5;
--color-muted-foreground: #525252;
--page-background: #fafafa;
--page-foreground: #171717;
--page-dot: #d4d4d4;
--surface: rgb(255 255 255 / 0.78);
--surface-border: #d4d4d4;
}
html[data-theme="light"] .theme-surface {
background-color: var(--surface);
border-color: var(--surface-border);
color: var(--page-foreground);
}
html[data-theme="light"] .theme-nav-link {
color: #111827;
mix-blend-mode: normal;
}
html[data-theme="light"] .card {
box-shadow: 0 6px 16px rgb(15 23 42 / 0.12) !important;
}
html[data-theme="light"] .theme-dark-card {
background: linear-gradient(to bottom right, #ffffff, #f1f5f9, #e2e8f0) !important;
border-color: #cbd5e1 !important;
color: #171717;
}
@keyframes gradient { @keyframes gradient {
0% { 0% {
background-position: 0% 50%; background-position: 0% 50%;