Files
Pouya LajevardiandClaude Opus 5 67847d94fa fix: regenerate favicon.ico with a transparent ground; tick Pouya's read-through
public/favicon.ico shipped with no transparency: all three frames declared a
32-bit alpha channel and then carried alpha=255 on every one of their 256/1024/
2304 pixels, ground opaque cream rgba(250,247,242,255). Pouya's read-through
finding, confirmed by parsing the ICO container directly.

The render source carries true alpha (2,272,386 transparent px, 20,795 partial),
so this is an export, not a mask derived from the cream ground — R13's harder
branch did not fire and R13 is unchanged on its own terms.

New scripts/icons.mjs + npm run icons re-derives the icon from the committed
master: asserts the source is still the documented crop (R14), verifies a
candidate file and renames on success so a rejected build cannot replace a good
favicon, and runs a boundary-colour halo test. Composition is unchanged —
ink bbox and pixel count identical at all three sizes.

apple-touch-icon.png is byte-identical and stays opaque cream deliberately; the
reason lives in docs/reference/brand-assets.md §The icon set, with the bar and a
pointer in BaseLayout.astro, docs/06 and R13.

docs/06: the read-through is ticked, and the cutover callout drops to ONE
blocker — Q60's waiting period.

Two adversarial review rounds, 15 findings, all resolved, none declined; stopped
at two per D19. claims-auditor correctly deferred to cutover per D20. Gates on
the committed bytes, exit status read: check 0, build 0 (23 pages),
check:claims 0, check:intake 0, og:proof 0, lint 0, lighthouse 0 (worst of 23
99/100/100/100). Nothing deployed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
2026-09-02 16:14:35 -04:00

279 lines
10 KiB
JavaScript

/**
* Regenerates `public/favicon.ico` from the committed brand master.
*
* LOCAL ONLY, like `bio:pdf`. Not wired into `npm run build` or either deploy
* path — the icons are committed artefacts and this is what re-derives them.
*
* Writes the favicon ONLY. `apple-touch-icon.png` is deliberately not touched
* and must stay opaque — `docs/reference/brand-assets.md` §The icon set.
*/
import { readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import sharp from 'sharp';
const root = fileURLToPath(new URL('..', import.meta.url));
const MARK = `${root}src/assets/brand/sml-infinity-mark.png`;
const MASTER = `${root}src/assets/brand/sml-infinity-mark-master.png`;
const OUT = `${root}public/favicon.ico`;
/** Sizes carried in the container, ascending — the order BaseLayout declares. */
const SIZES = [16, 32, 48];
/**
* The mark spans 7/8 of the canvas and is centred on both axes. Not a taste
* decision at this point: it is the composition already shipping, measured off
* the previous icon at all three sizes (14/16, 28/32, 42/48) and off the touch
* icon (158/180). Regenerating for alpha must not also restyle the mark.
*/
const INK_FRACTION = 7 / 8;
/** `--cream` — the ground the previous icon was matted against. */
const CREAM = [250, 247, 242];
const die = (msg) => {
rmSync(`${OUT}.candidate`, { force: true });
console.error(`icons: ${msg}`);
process.exit(1);
};
/**
* R14 — the icon must be traceable to the artwork in this repository, not to a
* file on someone's disk. The render source is a tight crop of the master, so
* assert it still IS that crop before deriving anything from it.
*/
async function assertProvenance() {
const mark = await sharp(MARK).metadata();
const crop = { left: 159, top: 646, width: 2668, height: 1704 };
if (mark.width !== crop.width || mark.height !== crop.height) {
die(
`render source is ${mark.width}x${mark.height}, expected ${crop.width}x${crop.height}`,
);
}
const [a, b] = await Promise.all([
sharp(MASTER).extract(crop).raw().toBuffer(),
sharp(MARK).raw().toBuffer(),
]);
if (!a.equals(b))
die('render source is no longer the documented crop of the master');
console.log(
`provenance: ${crop.width}x${crop.height} at (${crop.left},${crop.top}) of the master — identical`,
);
}
/**
* The whole point of the regeneration. A source without alpha would mean
* deriving a mask from the cream ground, which is a different and lossier job —
* so fail rather than silently ship a matted icon again.
*/
async function loadMark() {
const meta = await sharp(MARK).metadata();
if (!meta.hasAlpha)
die(
`${MARK} has no alpha channel — cannot export a transparent icon from it`,
);
const { data, info } = await sharp(MARK)
.ensureAlpha()
.raw()
.toBuffer({ resolveWithObject: true });
let transparent = 0;
for (let p = 3; p < data.length; p += 4) if (data[p] === 0) transparent++;
if (transparent === 0)
die(`${MARK} declares alpha but every pixel is opaque`);
console.log(
`source: ${info.width}x${info.height} alpha, ${transparent} fully transparent px`,
);
}
/**
* Resize onto a TRANSPARENT canvas. sharp premultiplies around the resample, so
* the ribbon's anti-aliased edge blends toward its own colour rather than
* toward the RGB sitting under alpha 0 — that is the cream halo this change
* exists to remove, and it would come straight back with a matted background.
*/
async function frame(size) {
const w = Math.round(size * INK_FRACTION);
const png = await sharp(MARK)
.resize({
width: w,
kernel: 'lanczos3',
fit: 'inside',
withoutEnlargement: false,
})
.toBuffer();
const { height: h } = await sharp(png).metadata();
if (h > size) die(`size ${size}: mark is ${w}x${h}, taller than the canvas`);
const left = Math.round((size - w) / 2);
const top = Math.round((size - h) / 2);
const out = await sharp({
create: {
width: size,
height: size,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 },
},
})
.composite([{ input: png, left, top }])
.png({ compressionLevel: 9, effort: 10, palette: false })
.toBuffer();
console.log(
` ${size}x${size}: mark ${w}x${h} at (${left},${top}), ${out.length} B`,
);
return out;
}
/** ICO container: 6-byte header, one 16-byte directory entry per frame, then the PNG payloads. */
function buildIco(frames) {
const header = Buffer.alloc(6);
header.writeUInt16LE(0, 0); // reserved
header.writeUInt16LE(1, 2); // type 1 = icon
header.writeUInt16LE(frames.length, 4);
const dir = Buffer.alloc(16 * frames.length);
let offset = header.length + dir.length;
frames.forEach(({ size, png }, i) => {
const e = i * 16;
dir[e] = size; // width — a byte; 0 would mean 256, which SIZES never is
dir[e + 1] = size; // height
dir[e + 2] = 0; // palette size — 0 for truecolour
dir[e + 3] = 0; // reserved
dir.writeUInt16LE(1, e + 4); // colour planes
dir.writeUInt16LE(32, e + 6); // bits per pixel
dir.writeUInt32LE(png.length, e + 8);
dir.writeUInt32LE(offset, e + 12);
offset += png.length;
});
return Buffer.concat([header, dir, ...frames.map((f) => f.png)]);
}
/**
* Re-read the container FROM DISK and decode each frame, rather than inspecting
* the buffers we just built — a check that reads its own inputs proves nothing.
* (It is still `sharp` decoding `sharp`'s output, so it is not a second
* instrument. The independent reads are in `docs/reference/brand-assets.md`.)
*/
async function verify(path) {
const buf = readFileSync(path);
const count = buf.readUInt16LE(4);
if (count !== SIZES.length)
die(`container declares ${count} frames, expected ${SIZES.length}`);
for (let i = 0; i < count; i++) {
const e = 6 + i * 16;
const size = buf[e];
const len = buf.readUInt32LE(e + 8);
const off = buf.readUInt32LE(e + 12);
if (off + len > buf.length)
die(`frame ${i}: range ${off}+${len} exceeds ${buf.length} B`);
const { data, info } = await sharp(buf.subarray(off, off + len))
.ensureAlpha()
.raw()
.toBuffer({ resolveWithObject: true });
if (info.width !== size || info.height !== size)
die(`frame ${i}: decoded ${info.width}x${info.height}, dir says ${size}`);
const corners = [
[0, 0],
[size - 1, 0],
[0, size - 1],
[size - 1, size - 1],
];
for (const [x, y] of corners) {
const a = data[(y * size + x) * 4 + 3];
if (a !== 0)
die(`frame ${size}: corner (${x},${y}) has alpha ${a}, expected 0`);
}
/*
* THE CORNER AND TRANSPARENCY CHECKS CANNOT SEE A CREAM HALO. A frame whose
* edge was matted against cream and then had its background knocked out has
* clear corners, transparent pixels and opaque pixels, and passes every one
* of them. What distinguishes it is the colour the edge blends TOWARD.
*
* ⚠️ AND IT IS THE BOUNDARY, NOT THE PARTIAL-ALPHA PIXELS. A first version
* of this guard inspected only pixels at 0 < alpha < 255 and MISSED a
* purpose-built haloed fixture entirely, because a knockout sets alpha per
* pixel and leaves NO partial alpha at all — 0 such pixels in the fixture.
* A guard that cannot see the defect it is named for is worse than none.
*
* So: take every painted pixel that touches a fully transparent one, and
* measure how many sit near cream. Measured on this artwork — correct
* frames 1 / 2 / 5 of 61 / 146 / 258 boundary pixels (1.4-1.9%); the haloed
* fixture 33 of 115 (28.7%). The gate is 10%, roughly 5x clear of both.
*/
const NEAR_CREAM = 20;
const HALO_SHARE = 0.1;
const alphaAt = (x, y) =>
x < 0 || y < 0 || x >= size || y >= size
? 0
: data[(y * size + x) * 4 + 3];
let clear = 0;
let ink = 0;
let boundary = 0;
let boundaryNearCream = 0;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const i = (y * size + x) * 4;
const a = data[i + 3];
if (a === 0) {
clear++;
continue;
}
if (a === 255) ink++;
const onEdge =
alphaAt(x - 1, y) === 0 ||
alphaAt(x + 1, y) === 0 ||
alphaAt(x, y - 1) === 0 ||
alphaAt(x, y + 1) === 0;
if (!onEdge) continue;
boundary++;
const d = Math.max(
Math.abs(data[i] - CREAM[0]),
Math.abs(data[i + 1] - CREAM[1]),
Math.abs(data[i + 2] - CREAM[2]),
);
if (d <= NEAR_CREAM) boundaryNearCream++;
}
}
const share = boundary === 0 ? 0 : boundaryNearCream / boundary;
if (clear === 0)
die(`frame ${size}: no transparent pixels — the ground is fully matted`);
if (ink === 0)
die(`frame ${size}: no opaque pixels — the mark did not render`);
if (boundary === 0)
die(`frame ${size}: no boundary pixels — cannot test the edge colour`);
if (share > HALO_SHARE)
die(
`frame ${size}: ${boundaryNearCream} of ${boundary} boundary pixels ` +
`(${(share * 100).toFixed(1)}%) sit within ${NEAR_CREAM} of cream — ` +
`the edge was matted against the ground before the ground was removed`,
);
console.log(
` ${size}x${size}: ${clear} transparent, ${ink} opaque, corners clear, ` +
`edge ${(share * 100).toFixed(1)}% near cream`,
);
}
console.log(`verified ${path} (${buf.length} B)`);
}
await assertProvenance();
await loadMark();
console.log('frames:');
const frames = [];
for (const size of SIZES) frames.push({ size, png: await frame(size) });
/*
* Verify a CANDIDATE file, then move it into place. Writing OUT first and
* verifying after would mean a failing check exits 1 having already replaced a
* good favicon with the one it just rejected — and nothing downstream re-checks,
* because this script is deliberately outside the build and both deploy paths.
*/
const candidate = `${OUT}.candidate`;
writeFileSync(candidate, buildIco(frames));
console.log('verify:');
try {
await verify(candidate);
} catch (err) {
rmSync(candidate, { force: true });
throw err;
}
renameSync(candidate, OUT);
console.log(`wrote ${OUT}`);