feat: item color map, fix regex matching, fix sort order, fix resize handle

This commit is contained in:
Sebastian Seedorf
2026-06-04 11:04:58 +02:00
parent b9377daa04
commit 955b0a890d
7 changed files with 562 additions and 41 deletions

87
web/bin/fix-colors.ts Normal file
View File

@@ -0,0 +1,87 @@
import { readFileSync, writeFileSync } from 'fs';
const CSV = 'public/factorio_item_colors.csv';
function hexToHsl(h: string): [number, number, number] {
let r = parseInt(h.slice(1, 3), 16) / 255, g = parseInt(h.slice(3, 5), 16) / 255, b = parseInt(h.slice(5, 7), 16) / 255;
const mx = Math.max(r, g, b), mn = Math.min(r, g, b), l = (mx + mn) / 2;
if (mx === mn) return [0, 0, Math.round(l * 100)];
const d = mx - mn, s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn);
let hue = 0;
if (mx === r) hue = ((g - b) / d + (g < b ? 6 : 0)) * 60;
else if (mx === g) hue = ((b - r) / d + 2) * 60;
else hue = ((r - g) / d + 4) * 60;
return [Math.round(hue), Math.round(s * 100), Math.round(l * 100)];
}
function hslToHex(h: number, s: number, l: number): string {
s /= 100; l /= 100;
const c = (1 - Math.abs(2 * l - 1)) * s, x = c * (1 - Math.abs(((h / 60) % 2) - 1)), m = l - c / 2;
let r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; } else if (h < 120) { r = x; g = c; } else if (h < 180) { g = c; b = x; }
else if (h < 240) { g = x; b = c; } else if (h < 300) { r = x; b = c; } else { r = c; b = x; }
const to = (v: number) => Math.round((v + m) * 255).toString(16).padStart(2, '0');
return '#' + to(r) + to(g) + to(b);
}
function djb2(s: string): number {
let h = 5381;
for (let i = 0; i < s.length; i++) h = (h * 33) ^ s.charCodeAt(i);
return h >>> 0;
}
function hueDist(a: number, b: number): number {
const d = Math.abs(a - b);
return Math.min(d, 360 - d);
}
const lines = readFileSync(CSV, 'utf-8').trim().split('\n');
const header = lines[0];
const raw = lines.slice(1).filter(l => l.trim()).map(l => {
const [k, c] = l.split(',');
return { key: k.trim(), color: c.trim() };
});
// Dedup by key
const seen = new Set<string>();
const rows: typeof raw = [];
for (const r of raw) { if (seen.has(r.key)) continue; seen.add(r.key); rows.push(r); }
const n = rows.length;
const parent = Array.from({ length: n }, (_, i) => i);
function find(x: number): number {
while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x]; }
return x;
}
function union(a: number, b: number) { parent[find(a)] = find(b); }
const hsl = rows.map((r, i) => ({ ...r, hsl: hexToHsl(r.color), idx: i }));
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
if (hueDist(hsl[i].hsl[0], hsl[j].hsl[0]) <= 0.5 &&
Math.abs(hsl[i].hsl[2] - hsl[j].hsl[2]) <= 0.5)
union(i, j);
}
}
const groups = new Map<number, typeof hsl>();
for (let i = 0; i < n; i++) {
const root = find(i);
if (!groups.has(root)) groups.set(root, []);
groups.get(root)!.push(hsl[i]);
}
let fixed = 0;
for (const [, items] of groups) {
if (items.length < 2) continue;
items.sort((a, b) => a.idx - b.idx);
const [oh, os, ol] = items[0].hsl;
for (let i = 1; i < items.length; i++) {
const hash = djb2(items[i].key);
const h = (oh + (hash % 5 - 2) + i * 7 + 360) % 360;
const l = Math.max(0, Math.min(100, ol + ((hash >> 4) % 5 - 2)));
items[i].color = hslToHex(h, os, l);
fixed++;
}
}
hsl.sort((a, b) => a.idx - b.idx);
writeFileSync(CSV, header + '\n' + hsl.map(r => `${r.key},${r.color}`).join('\n') + '\n');
if (fixed) console.log(`Fixed ${fixed} close colors`);