Files
Juan DenisandClaude Opus 4.8 8ee33ef2c6 fix(favicon): make the per-install tint actually cover the fingerprint URLs
The install-time tint only recolored favicon.svg, but the page also shipped a
fixed raster favicon.ico + .png — and favicon-hash scanners hash /favicon.ico,
which was identical across every install. So the "car-paint" tint didn't change
what those tools cluster on.

- index.html links only the SVG now (drop the raster <link>s).
- Remove the unused favicon-16x16/32x32.png.
- install.sh: after tinting favicon.svg, copy it over favicon.ico too, so a
  blind /favicon.ico fetch also returns the per-install color instead of a fixed
  icon. (SVG bytes under an .ico name — browsers use the linked SVG; this only
  affects what a scanner hashes at that URL.)

Still obscurity/defense-in-depth, not a real gate — but now it does what it says.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:04:00 -04:00

50 lines
2.6 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- Single SVG favicon so the per-install tint (install.sh randomize_favicon)
is the only mark served; no fixed raster icon to fingerprint-cluster on. -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="description" content="Control Panel" />
<title>Control Panel</title>
<!-- Fonts are SELF-HOSTED (see src/styles/base/_fonts.scss → /fonts/*.woff2).
No Google Fonts / third-party CDN: a self-hosted control panel must not
leak every user's IP + usage to Google. Falls back to system-ui until
the woff2 files are present (run frontend/scripts/fetch-fonts.mjs). -->
<script>
// Prevent flash of unstyled content (FOUC) by setting theme + accent before React loads
(function() {
var d = document.documentElement;
d.setAttribute('data-theme', localStorage.getItem('theme') || 'dark');
var accent = localStorage.getItem('accent_color');
if (accent) {
var m = accent.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
if (m) {
var r = parseInt(m[1],16), g = parseInt(m[2],16), b = parseInt(m[3],16);
var hx = function(v){ return Math.max(0,Math.min(255,v)).toString(16).padStart(2,'0'); };
var dk = function(v){ return Math.round(v*0.88); };
var br = function(v){ return Math.round(v + (255-v)*0.30); }; // toward white
var dm = function(v){ return Math.round(v*0.70); };
d.style.setProperty('--accent-primary', accent);
d.style.setProperty('--accent', accent);
d.style.setProperty('--accent-hover', '#'+hx(dk(r))+hx(dk(g))+hx(dk(b)));
d.style.setProperty('--accent-bright', '#'+hx(br(r))+hx(br(g))+hx(br(b)));
d.style.setProperty('--accent-dim', '#'+hx(dm(r))+hx(dm(g))+hx(dm(b)));
d.style.setProperty('--accent-bg', 'rgba('+r+','+g+','+b+',0.13)');
d.style.setProperty('--accent-bg-soft', 'rgba('+r+','+g+','+b+',0.07)');
d.style.setProperty('--accent-glow', 'rgba('+r+','+g+','+b+',0.35)');
d.style.setProperty('--accent-shadow', 'rgba('+r+','+g+','+b+',0.3)');
}
}
})();
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>