diff --git a/app/public/favicon-blue.svg b/app/public/favicon-blue.svg new file mode 100644 index 00000000..5c009b53 --- /dev/null +++ b/app/public/favicon-blue.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-brown.svg b/app/public/favicon-brown.svg new file mode 100644 index 00000000..97f40a1f --- /dev/null +++ b/app/public/favicon-brown.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-green.svg b/app/public/favicon-green.svg new file mode 100644 index 00000000..5fd5014f --- /dev/null +++ b/app/public/favicon-green.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-grey.svg b/app/public/favicon-grey.svg new file mode 100644 index 00000000..0607af96 --- /dev/null +++ b/app/public/favicon-grey.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-olive.svg b/app/public/favicon-olive.svg new file mode 100644 index 00000000..383ed663 --- /dev/null +++ b/app/public/favicon-olive.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-orange.svg b/app/public/favicon-orange.svg new file mode 100644 index 00000000..52d6114f --- /dev/null +++ b/app/public/favicon-orange.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-pink.svg b/app/public/favicon-pink.svg new file mode 100644 index 00000000..14e9154b --- /dev/null +++ b/app/public/favicon-pink.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-purple.svg b/app/public/favicon-purple.svg new file mode 100644 index 00000000..af4cbd56 --- /dev/null +++ b/app/public/favicon-purple.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-red.svg b/app/public/favicon-red.svg new file mode 100644 index 00000000..52842dcd --- /dev/null +++ b/app/public/favicon-red.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-teal.svg b/app/public/favicon-teal.svg new file mode 100644 index 00000000..afc01fe2 --- /dev/null +++ b/app/public/favicon-teal.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-violet.svg b/app/public/favicon-violet.svg new file mode 100644 index 00000000..3a46d5a0 --- /dev/null +++ b/app/public/favicon-violet.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/public/favicon-yellow.svg b/app/public/favicon-yellow.svg new file mode 100644 index 00000000..ff3c131f --- /dev/null +++ b/app/public/favicon-yellow.svg @@ -0,0 +1,106 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/scripts/generate-favicons.js b/app/scripts/generate-favicons.js new file mode 100644 index 00000000..5db437d5 --- /dev/null +++ b/app/scripts/generate-favicons.js @@ -0,0 +1,75 @@ +#!/usr/bin/env node +/** + * Generate colored favicon SVGs from the base icon.svg + * + * Usage: node scripts/generate-favicons.js + * + * This script reads the base icon.svg and generates a colored version + * for each color in semanticUIColorMap (from src/components/Vars.js). + * The favicons are saved to public/favicon-{colorname}.svg + */ + +const fs = require('fs'); +const path = require('path'); + +const srcDir = path.join(__dirname, '..', 'src', 'components'); +const publicDir = path.join(__dirname, '..', 'public'); +const varsPath = path.join(srcDir, 'Vars.js'); +const iconPath = path.join(publicDir, 'icon.svg'); + +// Parse semanticUIColorMap from Vars.js (single source of truth) +function parseColorMapFromVars() { + const varsContent = fs.readFileSync(varsPath, 'utf8'); + + // Extract the semanticUIColorMap object using regex + const match = varsContent.match(/export const semanticUIColorMap\s*=\s*\{([^}]+)\}/); + if (!match) { + throw new Error('Could not find semanticUIColorMap in Vars.js'); + } + + const colorMap = {}; + const colorEntries = match[1].matchAll(/(\w+):\s*['"]([^'"]+)['"]/g); + + for (const entry of colorEntries) { + colorMap[entry[1]] = entry[2]; + } + + if (Object.keys(colorMap).length === 0) { + throw new Error('No colors found in semanticUIColorMap'); + } + + return colorMap; +} + +// Parse colors from Vars.js +const semanticUIColorMap = parseColorMapFromVars(); +console.log(`Found ${Object.keys(semanticUIColorMap).length} colors in Vars.js\n`); + +// Colors that conflict with white stroke - use black stroke instead +// Must match Nav.js conflictingColors +const conflictingColors = ['red', 'orange', 'yellow', 'olive', 'pink']; + +// Read the base icon +const iconSvg = fs.readFileSync(iconPath, 'utf8'); + +console.log('Generating colored favicons from icon.svg...\n'); + +// Generate a favicon for each color +for (const [colorName, hexColor] of Object.entries(semanticUIColorMap)) { + // Replace the dark fill color (#1b1c1d) with the nav color + let coloredSvg = iconSvg.replace(/fill:#1b1c1d/g, `fill:${hexColor}`); + + // Use black stroke for conflicting colors (better contrast) + const useBlackStroke = conflictingColors.includes(colorName); + if (useBlackStroke) { + coloredSvg = coloredSvg.replace(/stroke:#ffffff/g, 'stroke:#000000'); + } + + const outputPath = path.join(publicDir, `favicon-${colorName}.svg`); + fs.writeFileSync(outputPath, coloredSvg); + + const strokeInfo = useBlackStroke ? ', black stroke' : ''; + console.log(` Created favicon-${colorName}.svg (${hexColor}${strokeInfo})`); +} + +console.log(`\nGenerated ${Object.keys(semanticUIColorMap).length} favicon files in public/`); diff --git a/app/src/components/Nav.js b/app/src/components/Nav.js index 7f9c8374..c049cf92 100644 --- a/app/src/components/Nav.js +++ b/app/src/components/Nav.js @@ -8,9 +8,32 @@ import {useCPUTemperature, useIOStats, useLoad, useMemoryStats, usePowerStats, u import {useReorganizationStatus} from "../contexts/FileWorkerStatusContext"; import {SearchIconButton} from "./Search"; import {Icon, Popup} from "./Theme"; -import {HELP_VIEWER_URI, NAME} from "./Vars"; +import {HELP_VIEWER_URI, NAME, semanticUIColorMap} from "./Vars"; import _ from "lodash"; +function updateFavicon(colorName) { + // Fall back to violet if invalid color + const safeColor = colorName in semanticUIColorMap ? colorName : 'violet'; + const faviconPath = `/favicon-${safeColor}.svg`; + + // Update all favicon links + const iconLinks = document.querySelectorAll('link[rel="icon"], link[rel="shortcut icon"]'); + iconLinks.forEach(link => { + link.href = faviconPath; + }); + + // Also update the 32x32 and 16x16 specific icons if they exist + const icon32 = document.querySelector('link[sizes="32x32"]'); + const icon16 = document.querySelector('link[sizes="16x16"]'); + if (icon32) icon32.href = faviconPath; + if (icon16) icon16.href = faviconPath; + + // Update theme-color meta tag for iOS/Android status bar + const hexColor = semanticUIColorMap[safeColor]; + const themeColorMeta = document.querySelector('meta[name="theme-color"]'); + if (themeColorMeta) themeColorMeta.content = hexColor; +} + const links = [ {text: 'Videos', to: '/videos', key: 'videos'}, {text: 'Archive', to: '/archive', key: 'archive'}, @@ -80,6 +103,13 @@ function useNavColorSetting() { } }, [settings.nav_color]); + // Update favicon to match nav color + React.useEffect(() => { + if (navColor) { + updateFavicon(navColor); + } + }, [navColor]); + return navColor }