From 1a663e3e3a7f586d8decdfebb690ff05e7af060f Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Tue, 11 Aug 2026 14:15:32 -0400 Subject: [PATCH] feat(brand): render inverted dev-channel icon tiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same sticker with every hex color inverted, into brand/icons-dev/ — outside public/, so a production build cannot pick it up. Render jobs grow an optional transform and a label that keeps two jobs rendering one source at one size out of each other's scratch files. --- brand/icons-dev/icon128.png | Bin 0 -> 8460 bytes brand/icons-dev/icon16.png | Bin 0 -> 836 bytes brand/icons-dev/icon32.png | Bin 0 -> 1911 bytes brand/icons-dev/icon48.png | Bin 0 -> 3038 bytes prek.toml | 2 +- scripts/render-icons.mjs | 50 +++++++++++++++++++++++++++++++----- 6 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 brand/icons-dev/icon128.png create mode 100644 brand/icons-dev/icon16.png create mode 100644 brand/icons-dev/icon32.png create mode 100644 brand/icons-dev/icon48.png diff --git a/brand/icons-dev/icon128.png b/brand/icons-dev/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..c8b03dc2bf23dd8ace7e402825c9d8816bd6f958 GIT binary patch literal 8460 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 N!PC{xWt~$(6958T`3nF5 literal 0 HcmV?d00001 diff --git a/brand/icons-dev/icon16.png b/brand/icons-dev/icon16.png new file mode 100644 index 0000000000000000000000000000000000000000..21fa22f7a164e014b6fde9593b070a33e867d639 GIT binary patch literal 836 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 literal 0 HcmV?d00001 diff --git a/prek.toml b/prek.toml index 284649c..825c9c5 100644 --- a/prek.toml +++ b/prek.toml @@ -64,7 +64,7 @@ id = "icons" name = "render icon pngs from brand/sticker.svg" entry = "npm run icons" language = "system" -files = '^(brand/sticker(-round)?\.svg|scripts/render-icons\.mjs|public/icons/icon\d+\.png|web/public/icons/icon\d+\.png|brand/icon480\.png|brand/icon\d+-round\.png)$' +files = '^(brand/sticker(-round)?\.svg|scripts/render-icons\.mjs|public/icons/icon\d+\.png|web/public/icons/icon\d+\.png|brand/icon480\.png|brand/icon\d+-round\.png|brand/icons-dev/icon\d+\.png)$' pass_filenames = false [[repos.hooks]] diff --git a/scripts/render-icons.mjs b/scripts/render-icons.mjs index f76a008..ee40c07 100644 --- a/scripts/render-icons.mjs +++ b/scripts/render-icons.mjs @@ -13,9 +13,9 @@ // Set CHROME to point at a specific Chrome/Chromium binary. import { execFileSync } from 'node:child_process' -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' -import { join, resolve } from 'node:path' +import { dirname, join, resolve } from 'node:path' import { pathToFileURL } from 'node:url' import { rasterize } from './rasterize.mjs' @@ -35,6 +35,21 @@ const JOBS = [ 480: ['brand/icon480.png'], }, }, + { + // The dev channel's tiles: the same sticker, colors inverted, so a dev + // install is obvious in the toolbar next to the store one. Not in + // public/, so they never reach a production build; scripts/dev-channel.mjs + // copies them over dist/icons/ when the dev channel is on. + svg: 'brand/sticker.svg', + label: 'dev-', + transform: invertHexColors, + outputs: { + 16: ['brand/icons-dev/icon16.png'], + 32: ['brand/icons-dev/icon32.png'], + 48: ['brand/icons-dev/icon48.png'], + 128: ['brand/icons-dev/icon128.png'], + }, + }, { // For avatar slots that crop uploads to a circle; not shipped anywhere. svg: 'brand/sticker-round.svg', @@ -60,6 +75,22 @@ export function pngSize(bytes) { return { width: dv.getUint32(16), height: dv.getUint32(20) } } +/** + * Photographic negative of every hex color in an SVG: each channel becomes + * 255 - c. Three-digit hex expands first. Keywords like `none` are left + * alone, so `fill="none"` stays unfilled. Inverting twice is the identity, + * which is what the test leans on. + */ +export function invertHexColors(svg) { + return svg.replace(/#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b/g, (_, hex) => { + const full = hex.length === 3 ? [...hex].map((c) => c + c).join('') : hex + const channels = full + .match(/../g) + .map((pair) => (255 - parseInt(pair, 16)).toString(16).padStart(2, '0')) + return `#${channels.join('')}` + }) +} + export function findChrome() { const candidates = process.env.CHROME ? [process.env.CHROME] @@ -79,11 +110,15 @@ function main() { const chrome = findChrome() const work = mkdtempSync(join(tmpdir(), 'substandard-icons-')) try { - for (const { svg, outputs } of JOBS) { - const source = readFileSync(resolve(svg), 'utf8') + for (const { svg, outputs, label = '', transform } of JOBS) { + const source = transform + ? transform(readFileSync(resolve(svg), 'utf8')) + : readFileSync(resolve(svg), 'utf8') for (const [size, dests] of Object.entries(outputs)) { const n = Number(size) - const name = dests[0].split('/').pop().replace(/\.png$/, '') + // The label keeps two jobs rendering the same source at the same + // size from sharing a scratch file inside `work`. + const name = label + dests[0].split('/').pop().replace(/\.png$/, '') const svgText = source.replace(' ${dests.join(', ')}`, ) -- 2.51.2