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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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
}