forked from
quillmatiq.com/augment
Fork of Chiri for Astro for my blog
1<!--
2 This is a critical style block to prevent flashing on page load in dark mode.
3 It is injected into the <head> of the document before the main stylesheets are loaded.
4-->
5<style is:inline>
6 :root {
7 --bg: #ffffff;
8 }
9
10 html.dark {
11 --bg: #1c1c1c;
12 }
13
14 html {
15 background-color: var(--bg);
16 color-scheme: light;
17 }
18
19 html.dark {
20 color-scheme: dark;
21 }
22</style>
23
24<script is:inline>
25 // Global Theme Manager
26 ;(function () {
27 // Prevent duplicate initialization
28 if (window.ThemeManager && window.ThemeManager.initialized) {
29 return
30 }
31
32 window.ThemeManager = {
33 STORAGE_KEY: 'chiri-theme',
34 initialized: false,
35
36 getSystemTheme() {
37 return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
38 },
39
40 getStoredTheme() {
41 try {
42 return localStorage.getItem(this.STORAGE_KEY)
43 } catch {
44 return null
45 }
46 },
47
48 setStoredTheme(theme) {
49 try {
50 if (theme === 'system') {
51 localStorage.removeItem(this.STORAGE_KEY)
52 } else {
53 localStorage.setItem(this.STORAGE_KEY, theme)
54 }
55 } catch (e) {
56 console.warn('Failed to store theme preference:', e)
57 }
58 },
59
60 getEffectiveTheme() {
61 const stored = this.getStoredTheme()
62 return stored || this.getSystemTheme()
63 },
64
65 isUsingSystemTheme() {
66 return this.getStoredTheme() === null
67 },
68
69 applyTheme(theme) {
70 document.documentElement.classList.remove('light', 'dark')
71 document.documentElement.classList.add(theme)
72
73 // Dispatch event for other components
74 document.dispatchEvent(
75 new CustomEvent('themechange', {
76 detail: {
77 theme,
78 isUserChoice: !this.isUsingSystemTheme(),
79 isSystemTheme: this.isUsingSystemTheme()
80 }
81 })
82 )
83 },
84
85 toggle() {
86 const currentTheme = this.getEffectiveTheme()
87 // Simply toggle between light and dark
88 const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
89
90 this.setStoredTheme(newTheme)
91 this.applyTheme(newTheme)
92 },
93
94 init() {
95 if (this.initialized) return
96
97 // Set initial theme (maintain current theme when refreshing page)
98 this.applyTheme(this.getEffectiveTheme())
99
100 // Listen for system theme changes
101 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
102 const newSystemTheme = e.matches ? 'dark' : 'light'
103
104 // Always follow system theme changes and update stored theme preference
105 this.setStoredTheme(newSystemTheme)
106 this.applyTheme(newSystemTheme)
107 })
108
109 this.initialized = true
110 }
111 }
112
113 // Initialize theme manager
114 window.ThemeManager.init()
115
116 // Apply theme when needed
117 const applyTheme = () => {
118 if (window.ThemeManager) {
119 const theme = window.ThemeManager.getEffectiveTheme()
120 window.ThemeManager.applyTheme(theme)
121 }
122 }
123
124 // Apply immediately on script load
125 applyTheme()
126
127 // Listen for navigation events
128 document.addEventListener('astro:before-preparation', applyTheme)
129
130 // Apply theme after DOM changes (both swap and page load)
131 const applyThemeAfterNavigation = () => {
132 if (window.ThemeManager) {
133 const currentTheme = window.ThemeManager.getEffectiveTheme()
134 const appliedTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light'
135
136 if (currentTheme !== appliedTheme) {
137 // Use requestAnimationFrame to ensure theme is applied in the next frame, avoiding conflicts with view transition
138 requestAnimationFrame(() => {
139 window.ThemeManager.applyTheme(currentTheme)
140 })
141 }
142 }
143 }
144
145 document.addEventListener('astro:after-swap', applyThemeAfterNavigation)
146 document.addEventListener('astro:page-load', applyThemeAfterNavigation)
147
148 // Handle system theme changes
149 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
150 if (window.ThemeManager && window.ThemeManager.isUsingSystemTheme()) {
151 setTimeout(applyTheme, 0)
152 }
153 })
154 })()
155</script>