wrolpi/app/scripts/generate-favicons.js
Roland e00f0439da
ui: the last of Semantic UI, in name and in dead CSS
The library has been uninstalled for some time, but the codebase still read as
though it were there: identifiers named after it, rules written against its
class names, and about 180 comments explaining a rule in terms of a thing that
is no longer installed.

Renamed, keeping the vocabulary the app actually uses: semanticUIColorMap ->
navColorHexMap, semanticColorNames -> paletteColorNames, semanticNames ->
iconNameAliases, semanticSizes -> sizeAliases, and SemanticUIColors ->
NavColors in wrolpi/schema.py, which is what validates nav_color on the way in.
ColorToSemanticHexColor had no callers at all and is gone.

The baseline ratchet has done its job -- the list reached zero, so the JSON is
deleted and the guard keeps only the checks with teeth: no package, no import,
no `className="ui "`, no toast library.  Its Theme.tsx check went with the
wrappers it was watching for.  It also asserts its own walk found files, so a
clean result cannot mean it scanned nothing.

Deleted from App.css: every rule requiring `.ui`, `.inverted`, `.segment` or
`.modal`, none of which any component has set since the stylesheet stopped
loading, plus the form skeletons, ui-alerts and progress-text rules that
nothing renders.  Two of them were doing harm rather than nothing: the drag
tints had a dark-theme variant under `.inverted` that stopped applying, leaving
15% of a fixed blue over a dark row, and now mix a token instead.

Two selectors naming markup that no longer exists were live bugs.  The file
browser's filter had `.ui.input.file-browser-filter`, so it rendered at the
browser default width on a bar laid out to give it 34em -- now qualified by its
container, because `.wrolpi-action-input` sets `width: 100%` at the same
specificity and is loaded later.  And SearchBox takes named props rather than a
rest spread, so the `id` DestinationForm hands it was dropped in silence and
the label it renders pointed at nothing; the download forms' Destination field
has had no associated label since the migration.

"Semantic roles" survives in four places, where it means what it says.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:05:54 -06:00

75 lines
2.6 KiB
JavaScript

#!/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 navColorHexMap (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 navColorHexMap from Vars.js (single source of truth)
function parseColorMapFromVars() {
const varsContent = fs.readFileSync(varsPath, 'utf8');
// Extract the navColorHexMap object using regex
const match = varsContent.match(/export const navColorHexMap\s*=\s*\{([^}]+)\}/);
if (!match) {
throw new Error('Could not find navColorHexMap 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 navColorHexMap');
}
return colorMap;
}
// Parse colors from Vars.js
const navColorHexMap = parseColorMapFromVars();
console.log(`Found ${Object.keys(navColorHexMap).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(navColorHexMap)) {
// 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(navColorHexMap).length} favicon files in public/`);