forked from
quillmatiq.com/augment
Fork of Chiri for Astro for my blog
1<script is:inline>
2 // Favicon theme switcher for system theme-based favicon updates, using external SVG file
3 class FaviconThemeSwitcher {
4 constructor() {
5 this.faviconLink =
6 document.querySelector('link[rel="icon"]') ||
7 document.querySelector('link[rel="shortcut icon"]') ||
8 document.querySelector('link[rel="apple-touch-icon"]')
9
10 if (!this.faviconLink) {
11 console.warn('Favicon link not found, skipping theme switcher')
12 return
13 }
14
15 this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
16 this.svgUrl = '/favicon.svg'
17 this.currentColor = null
18 this.svgContent = null
19
20 this.mediaQuery.addEventListener('change', () => this.updateFavicon())
21 this.init()
22 }
23
24 async init() {
25 if (!this.svgContent) {
26 try {
27 const res = await fetch(this.svgUrl)
28 this.svgContent = await res.text()
29 } catch (e) {
30 console.warn('Failed to fetch favicon.svg:', e)
31 return
32 }
33 }
34 this.updateFavicon()
35 }
36
37 updateFavicon() {
38 const color = this.mediaQuery.matches ? '#ccc' : '#111'
39 if (this.currentColor === color) return
40 this.currentColor = color
41 this.updateFaviconColor(color)
42 }
43
44 updateFaviconColor(color) {
45 if (!this.svgContent) return
46 try {
47 const parser = new DOMParser()
48 const doc = parser.parseFromString(this.svgContent, 'image/svg+xml')
49
50 // Remove all <style> tags
51 doc.querySelectorAll('style').forEach((style) => style.remove())
52
53 // Recursively set fill attribute for all elements
54 function setFillRecursively(node) {
55 if (node.nodeType === 1) {
56 // Element node
57 node.setAttribute('fill', color)
58 // Remove fill from style attribute if present
59 if (node.hasAttribute('style')) {
60 let style = node.getAttribute('style')
61 style = style.replace(/fill\s*:\s*[^;]+;?/gi, '')
62 node.setAttribute('style', style)
63 }
64 for (let i = 0; i < node.childNodes.length; i++) {
65 setFillRecursively(node.childNodes[i])
66 }
67 }
68 }
69 setFillRecursively(doc.documentElement)
70
71 const serializer = new XMLSerializer()
72 const svg = serializer.serializeToString(doc)
73 const blob = new Blob([svg], { type: 'image/svg+xml' })
74 const blobUrl = URL.createObjectURL(blob)
75 this.faviconLink.href = blobUrl
76 if (this.previousBlobUrl) {
77 URL.revokeObjectURL(this.previousBlobUrl)
78 }
79 this.previousBlobUrl = blobUrl
80 } catch (e) {
81 console.warn('Failed to update favicon color:', e)
82 }
83 }
84 }
85
86 // Initialize favicon theme switcher
87 let faviconSwitcherInitialized = false
88 function init() {
89 if (faviconSwitcherInitialized) return
90 faviconSwitcherInitialized = true
91
92 try {
93 new FaviconThemeSwitcher()
94 } catch (error) {
95 console.warn('Failed to initialize favicon theme switcher:', error)
96 }
97 }
98
99 // Use only astro:page-load as it fires on initial load and navigation
100 document.addEventListener('astro:page-load', init)
101</script>