// The face-judging shapes AND the crop maths, with NO server imports. // // Same boundary rule as lib/note-types.ts and lib/phrase-types.ts, and for the // same reason: FaceDeck.tsx needs CORNER_PX, autoCrop() and the reason list as // VALUES at runtime. A value import is not erased, so one that reaches // lib/faces.ts -> node:fs would follow into the client chunk and fail the // build -- a break `tsc --noEmit` cannot see and only the bundler catches. /** CORNER_W / CORNER_H in make-thumb.mjs. The corner is square and 300px. */ export const CORNER_PX = 300; /** facecrop.py's `side = max(fw, fh) * 2.2` -- a portrait, not a nose. */ export const PAD = 2.2; /** * How far off the face centre a crop has to sit before it is worth saying. * * Not zero, because facecrop.py truncates the crop origin to whole pixels, so * a perfectly centred crop still reports up to half a pixel of displacement. * Eight is comfortably above that and far below the 93px that put YouTube * chrome in the bottom-left corner of the Super Mario RPG cover. */ export const SHOVE_FLOOR = 8; /** A rectangle in SOURCE pixels. Never in canvas or preview pixels. */ export type Box = { x: number; y: number; w: number; h: number }; export type FaceBox = Box & { score: number; t: number }; export type FaceVerdict = "clean" | "recrop" | "reject"; export type FaceJudgement = { /** `