Fork of Chiri for Astro for my blog
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a1e6ead60fb44c152fefa3a86179bcf2b84d6249 101 lines 3.2 kB view raw
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>