Skip to content

Commit 970c139

Browse files
committed
✨ Support theme switching
1 parent 925f21a commit 970c139

16 files changed

+1130
-952
lines changed

.astro/settings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"enabled": false
44
},
55
"_variables": {
6-
"lastUpdateCheck": 1720959402784
6+
"lastUpdateCheck": 1722751696611
77
}
88
}

src/components/Card.astro

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@ const { pic, desc, date } = post.frontmatter;
1717
class='pb-3 md:mt-5 mt-4 md:mr-5 flex flex-col justify-center rounded-b-md shadow-md overflow-hidden mx-auto md:w-80 w-96 card-content preload-link'
1818
>
1919
<img
20-
data-placeholder-background='linear-gradient(to bottom, #ece9e6, #ffffff)'
20+
data-placeholder-background='linear-gradient(to bottom, var(--placeholder-gradient-start), var(--placeholder-gradient-end))'
2121
data-src={`${pic}?x-oss-process=image/resize,w_640/format,webp`}
2222
data-alt={title}
2323
alt=''
2424
class='lozad block md:w-80 md:h-48 w-96 h-52 rounded-t-md'
2525
/>
2626
<div class='w-full flex justify-between items-center leading-tight pt-4 pl-3 pr-3'>
27-
<div class='text-gray-800'>{title}</div>
28-
<div class='text-grey-darker text-sm text-gray-600'>{date}</div>
27+
<div class='text-gray-800 dark:text-gray-200'>{title}</div>
28+
<div class='text-grey-darker text-sm text-gray-600 dark:text-gray-300'>{date}</div>
2929
</div>
30-
<p class='text-gray-600 text-sm md:w-78 w-94 overflow-hidden pt-2.5 line-clamp-2 px-3 w-full h-12'>
30+
<p class='text-gray-600 dark:text-gray-300 text-sm md:w-78 w-94 overflow-hidden pt-2.5 line-clamp-2 px-3 w-full h-12'>
3131
{desc}
3232
</p>
3333
</a>

src/components/Footer.astro

+41-35
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,53 @@
1+
---
2+
import ThemeSwitch from './ThemeSwitch.astro';
3+
---
4+
5+
<ThemeSwitch />
6+
17
<script type='text/javascript' src='https://gw.alipayobjects.com/os/k/h3/heti-addon.min.js'></script>
28
<script type='text/javascript' src='https://gw.alipayobjects.com/os/k/3j/lozad.min.js'></script>
39
<script type='text/javascript' src='https://gw.alipayobjects.com/os/k/x5/intense.min.js'></script>
410

511
<!-- This is intentionally inlined to avoid FOUC -->
612
<script is:inline>
7-
document.addEventListener('DOMContentLoaded', function () {
8-
// Optimization of letter spacing
9-
const heti = new Heti('.heti');
10-
heti.autoSpacing();
11-
12-
// Lazy loading of images
13-
const observer = lozad('.lozad', {
14-
loaded: function (el) {
15-
el.alt = el.getAttribute('data-alt');
16-
},
17-
});
18-
19-
observer.observe();
20-
21-
// Jump to external link
22-
const links = document.links;
23-
for (let i = 0; i < +links.length; i++) {
24-
if (links[i].hostname != window.location.hostname) {
25-
links[i].target = '_blank';
26-
}
27-
}
28-
29-
const zoomImgs = document.querySelectorAll('#write img');
30-
const isMobileDevice = /Android|iPhone/i.test(navigator.userAgent);
31-
if (zoomImgs && zoomImgs.length > 0 && !isMobileDevice) {
32-
Intense && Intense(zoomImgs);
33-
}
34-
});
13+
document.addEventListener('DOMContentLoaded', function () {
14+
// Optimization of letter spacing
15+
const heti = new Heti('.heti');
16+
heti.autoSpacing();
17+
18+
// Lazy loading of images
19+
const observer = lozad('.lozad', {
20+
loaded: function (el) {
21+
el.alt = el.getAttribute('data-alt');
22+
},
23+
});
24+
25+
observer.observe();
26+
27+
// Jump to external link
28+
const links = document.links;
29+
for (let i = 0; i < +links.length; i++) {
30+
if (links[i].hostname != window.location.hostname) {
31+
links[i].target = '_blank';
32+
}
33+
}
34+
35+
const zoomImgs = document.querySelectorAll('#write img');
36+
const isMobileDevice = /Android|iPhone/i.test(navigator.userAgent);
37+
if (zoomImgs && zoomImgs.length > 0 && !isMobileDevice) {
38+
Intense && Intense(zoomImgs);
39+
}
40+
});
3541
</script>
3642

3743
<!-- Global site tag (gtag.js) - Google Analytics -->
3844
<script async src='https://www.googletagmanager.com/gtag/js?id=G-DT01FBW30E' is:inline></script>
3945
<script>
40-
// @ts-nocheck
41-
window.dataLayer = window.dataLayer || [];
42-
function gtag() {
43-
dataLayer.push(arguments);
44-
}
45-
gtag('js', new Date());
46-
gtag('config', 'G-DT01FBW30E');
46+
// @ts-nocheck
47+
window.dataLayer = window.dataLayer || [];
48+
function gtag() {
49+
dataLayer.push(arguments);
50+
}
51+
gtag('js', new Date());
52+
gtag('config', 'G-DT01FBW30E');
4753
</script>

src/components/Header/Header.astro

+2-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import HeaderRight from './HeaderRight.astro';
1111
<AstroLogo size={40} />
1212
</a>
1313
<a href='/'>
14-
<h1 class='text-gray-800'>{SITE.title}</h1>
14+
<h1 class='text-gray-800 dark:text-gray-200'>{SITE.title}</h1>
1515
</a>
1616
</div>
1717
<HeaderRight />
@@ -36,7 +36,6 @@ import HeaderRight from './HeaderRight.astro';
3636
flex-shrink: 0;
3737
font-weight: 600;
3838
line-height: 1;
39-
color: hsla(var(--color-base-white), 100%, 1);
4039
gap: 0.25em;
4140
}
4241

@@ -59,7 +58,7 @@ import HeaderRight from './HeaderRight.astro';
5958

6059
.logo a:hover,
6160
.logo a:focus {
62-
color: var(--theme-text-accent);
61+
color: var(--theme-accent);
6362
}
6463

6564
.logo h1 {

src/components/Header/HeaderLink.astro

+14-13
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@ const isActive = href === Astro.url.pathname.replace(/\/$/, '');
66
---
77

88
<a href={href} class:list={[className, { active: isActive }]} {...props}>
9-
<slot />
9+
<slot />
1010
</a>
1111

1212
<style>
13-
a {
14-
text-decoration: none;
15-
font-size: 1.1rem;
16-
margin-left: 1rem;
17-
margin-top: 0.3rem;
18-
font-weight: bold !important;
19-
color: #1f2937 !important;
20-
}
21-
a.active {
22-
font-weight: bold;
23-
text-decoration: underline;
24-
}
13+
a {
14+
text-decoration: none;
15+
font-size: 1.1rem;
16+
margin-left: 1rem;
17+
margin-top: 0.3rem;
18+
font-weight: bold !important;
19+
color: var(--theme-text) !important;
20+
}
21+
22+
a.active {
23+
font-weight: bold;
24+
text-decoration: underline;
25+
}
2526
</style>

src/components/Header/HeaderRight.astro

+7-7
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import Search from './Search.astro';
55
---
66

77
<div style='flex-grow: 1;' class='text-right md:mr-1 -mr-1'>
8-
<Search />
9-
<HeaderLink href={SITE.blogPage} target='_blank'>Blog</HeaderLink>
10-
<HeaderLink href={`https://twitter.com/${SITE.twitterId}`} target='_blank'>Twitter</HeaderLink>
11-
<HeaderLink href={`https://github.com/${SITE.githubId}`} target='_blank' class='lg:inline-block hidden'
12-
>GitHub</HeaderLink
13-
>
14-
<HeaderLink href='/rss.xml' target='_blank' class='lg:inline-block hidden'>RSS</HeaderLink>
8+
<Search />
9+
<HeaderLink href={SITE.blogPage} target='_blank'>Blog</HeaderLink>
10+
<HeaderLink href={`https://twitter.com/${SITE.twitterId}`} target='_blank'>Twitter</HeaderLink>
11+
<HeaderLink href={`https://github.com/${SITE.githubId}`} target='_blank' class='lg:inline-block hidden'
12+
>GitHub</HeaderLink
13+
>
14+
<HeaderLink href='/rss.xml' target='_blank' class='lg:inline-block hidden'>RSS</HeaderLink>
1515
</div>

src/components/Header/Search.astro

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ import SearchIcon from './SearchIcon.astro';
1515
</button>
1616
<dialog
1717
aria-label='search'
18-
class='h-full max-h-full w-full max-w-full bg-white shadow backdrop:backdrop-blur sm:mx-auto sm:mb-auto sm:mt-16 sm:h-max sm:max-h-[calc(100%-8rem)] sm:min-h-[15rem] sm:w-5/6 sm:max-w-[48rem] sm:rounded-md opacity-0 text-left'
18+
class='h-full max-h-full w-full max-w-full shadow backdrop:backdrop-blur sm:mx-auto sm:mb-auto sm:mt-16 sm:h-max sm:max-h-[calc(100%-8rem)] sm:min-h-[15rem] sm:w-5/6 sm:max-w-[48rem] sm:rounded-md opacity-0 text-left'
1919
>
2020
<div class='dialog-frame flex flex-col gap-4 p-6 pt-12 sm:pt-6'>
21-
<button data-close-modal class='ms-auto cursor-pointer rounded-full bg-black text-white px-4 py-2 close-icon'
21+
<button data-close-modal class='ms-auto cursor-pointer rounded-full px-4 py-2 close-icon'
2222
>X</button
2323
>
2424
{

src/components/LeftSidebar.astro

+5-6
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ const sidebarSections = allPosts.reduce((col, item, i) => {
2626
}, []);
2727
---
2828

29-
<nav aria-labelledby='grid-left' class='pt-6 text-gray-800'>
29+
<nav aria-labelledby='grid-left' class='pt-6 text-gray-800 dark:text-gray-200'>
3030
<div class='logo flex'>
3131
<a href='/' class='logo-image' data-astro-prefetch>
3232
<AstroLogo size={40} />
3333
</a>
3434
<a href='/' data-astro-prefetch>
35-
<h1 class='text-gray-800'>{SITE.title}</h1>
35+
<h1 class='text-gray-800 dark:text-gray-200'>{SITE.title}</h1>
3636
</a>
3737
</div>
3838
<ul class='nav-groups'>
@@ -114,11 +114,11 @@ const sidebarSections = allPosts.reduce((col, item, i) => {
114114
}
115115
.nav-link a:hover,
116116
.nav-link a:focus {
117-
color: var(--theme-text-accent);
117+
color: var(--theme-accent);
118118
}
119119

120120
.nav-link a[aria-current='page'] {
121-
color: var(--theme-text-accent);
121+
color: var(--theme-accent);
122122
font-weight: bolder;
123123
}
124124

@@ -136,7 +136,6 @@ const sidebarSections = allPosts.reduce((col, item, i) => {
136136
flex-shrink: 0;
137137
font-weight: 600;
138138
line-height: 1;
139-
color: hsla(var(--color-base-white), 100%, 1);
140139
gap: 0.25em;
141140
padding: 0.25em 0;
142141
}
@@ -160,7 +159,7 @@ const sidebarSections = allPosts.reduce((col, item, i) => {
160159

161160
.logo a:hover,
162161
.logo a:focus {
163-
color: var(--theme-text-accent);
162+
color: var(--theme-accent);
164163
}
165164

166165
.logo h1 {

src/components/ThemeSwitch.astro

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<button id='theme-toggle' class='theme-toggle' aria-label='Toggle theme'>
2+
<svg class='sun-and-moon' aria-hidden='true' width='24' height='24' viewBox='0 0 24 24'>
3+
<mask class='moon' id='moon-mask'>
4+
<rect x='0' y='0' width='100%' height='100%' fill='white'></rect>
5+
<circle cx='24' cy='10' r='6' fill='black'></circle>
6+
</mask>
7+
<circle class='sun' cx='12' cy='12' r='6' fill='currentColor' mask='url(#moon-mask)'></circle>
8+
<g class='sun-beams' stroke='currentColor'>
9+
<line x1='12' y1='1' x2='12' y2='3'></line>
10+
<line x1='12' y1='21' x2='12' y2='23'></line>
11+
<line x1='4.22' y1='4.22' x2='5.64' y2='5.64'></line>
12+
<line x1='18.36' y1='18.36' x2='19.78' y2='19.78'></line>
13+
<line x1='1' y1='12' x2='3' y2='12'></line>
14+
<line x1='21' y1='12' x2='23' y2='12'></line>
15+
<line x1='4.22' y1='19.78' x2='5.64' y2='18.36'></line>
16+
<line x1='18.36' y1='5.64' x2='19.78' y2='4.22'></line>
17+
</g>
18+
</svg>
19+
</button>
20+
21+
<style is:global>
22+
.theme-toggle {
23+
position: fixed;
24+
bottom: 24px;
25+
right: 24px;
26+
background: none;
27+
border: none;
28+
cursor: pointer;
29+
color: var(--theme-text);
30+
padding: 0;
31+
}
32+
33+
.sun-and-moon > :is(.moon, .sun, .sun-beams, .moon-circle, .moon-crater) {
34+
transform-origin: center center;
35+
transition:
36+
transform 0.5s ease,
37+
opacity 0.5s ease;
38+
}
39+
40+
.sun-and-moon > :is(.moon, .sun) {
41+
fill: currentColor;
42+
}
43+
44+
.theme-toggle:is(:hover, :focus-visible) {
45+
color: var(--theme-accent);
46+
}
47+
48+
.sun-and-moon > .sun-beams {
49+
stroke: currentColor;
50+
stroke-width: 2px;
51+
}
52+
53+
.moon-circle {
54+
transition: transform 0.5s ease;
55+
}
56+
57+
[data-theme='dark'] .sun-and-moon > .sun {
58+
transform: scale(1.75);
59+
}
60+
61+
[data-theme='dark'] .sun-and-moon > .sun-beams {
62+
opacity: 0;
63+
}
64+
65+
[data-theme='dark'] .sun-and-moon > .moon > circle {
66+
transform: translateX(-7px);
67+
transition: transform 0.5s ease;
68+
}
69+
70+
[data-theme='dark'] .moon-circle {
71+
transform: translateX(-12px) scale(0.92);
72+
}
73+
74+
[data-theme='dark'] .moon-crater {
75+
transform: translateX(-7px);
76+
}
77+
78+
[data-theme='light'] .moon-circle,
79+
[data-theme='light'] .moon-crater {
80+
opacity: 0;
81+
}
82+
83+
@media (prefers-reduced-motion: reduce) {
84+
.sun-and-moon > :is(.moon, .sun, .sun-beams, .moon-circle, .moon-crater) {
85+
transition: none;
86+
}
87+
}
88+
</style>
89+
<script>
90+
const themeToggle = document.getElementById('theme-toggle');
91+
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
92+
93+
function changeGiscusTheme(newTheme) {
94+
const giscusScript = document.getElementById('giscus-script');
95+
if (giscusScript) {
96+
giscusScript.setAttribute('data-theme', newTheme);
97+
98+
// Reload giscus
99+
const iframe = document.querySelector('iframe.giscus-frame') as HTMLIFrameElement;
100+
if (iframe) {
101+
iframe.contentWindow.postMessage({ giscus: { setConfig: { theme: newTheme } } }, 'https://giscus.app');
102+
}
103+
}
104+
}
105+
106+
function setTheme(theme) {
107+
if (theme === 'dark') {
108+
document.body.classList.add('dark');
109+
changeGiscusTheme('https://gw.alipayobjects.com/os/k/weekly1/comment-dark.css');
110+
} else {
111+
document.body.classList.remove('dark');
112+
changeGiscusTheme('https://gw.alipayobjects.com/os/k/weekly/comment.css');
113+
}
114+
document.body.setAttribute('data-theme', theme);
115+
116+
localStorage.setItem('theme', theme);
117+
}
118+
119+
function toggleTheme() {
120+
const currentTheme = document.body.getAttribute('data-theme');
121+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
122+
setTheme(newTheme);
123+
}
124+
125+
const savedTheme = localStorage.getItem('theme');
126+
if (savedTheme) {
127+
setTheme(savedTheme);
128+
} else {
129+
setTheme(prefersDarkScheme.matches ? 'dark' : 'light');
130+
}
131+
132+
themeToggle.addEventListener('click', toggleTheme);
133+
134+
prefersDarkScheme.addEventListener('change', (e) => {
135+
const newTheme = e.matches ? 'dark' : 'light';
136+
setTheme(newTheme);
137+
});
138+
</script>

0 commit comments

Comments
 (0)