/** * Transient overlays composited ON the feed (in screen space, so corruption never drags * them): the shipment stamp and the scram bar. * * Both are painted into small canvases and uploaded only when their event fires — never * per frame. The stamp is small (256x128) precisely so the per-event upload stays cheap. */ export const STAMP_W = 256; export const STAMP_H = 128; export const SCRAM_W = 512; export const SCRAM_H = 64; export interface Overlays { stampCanvas: HTMLCanvasElement; scramCanvas: HTMLCanvasElement; /** Repaint the stamp for a landed shipment. OSHA-placard deadpan, in the item's chip colour. */ paintStamp(name: string, count: number, color: string): void; } function roundRect(g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) { g.beginPath(); g.moveTo(x + r, y); g.arcTo(x + w, y, x + w, y + h, r); g.arcTo(x + w, y + h, x, y + h, r); g.arcTo(x, y + h, x, y, r); g.arcTo(x, y, x + w, y, r); g.closePath(); } function paintScramBar(g: CanvasRenderingContext2D) { // A white tape-tracking bar with the deadpan legend punched out of it. g.clearRect(0, 0, SCRAM_W, SCRAM_H); g.fillStyle = '#f6f6f2'; g.fillRect(0, 0, SCRAM_W, SCRAM_H); // torn edges: the bar should not look like a clean rectangle g.fillStyle = 'rgba(0,0,0,0)'; g.globalCompositeOperation = 'destination-out'; for (let x = 0; x < SCRAM_W; x += 7) { const t = ((x * 37) % 11) / 11; g.fillRect(x, 0, 7, 3 + t * 5); g.fillRect(x, SCRAM_H - (3 + (1 - t) * 5), 7, 3 + (1 - t) * 5); } g.globalCompositeOperation = 'source-over'; g.fillStyle = '#0a0a0c'; g.font = "bold 30px 'Courier New', monospace"; g.textBaseline = 'middle'; const label = 'TRACKING LOST'; const w = g.measureText(label).width; g.fillText(label, (SCRAM_W - w) / 2, SCRAM_H / 2); } export function createOverlays(): Overlays { const stampCanvas = document.createElement('canvas'); stampCanvas.width = STAMP_W; stampCanvas.height = STAMP_H; const sg = stampCanvas.getContext('2d')!; const scramCanvas = document.createElement('canvas'); scramCanvas.width = SCRAM_W; scramCanvas.height = SCRAM_H; paintScramBar(scramCanvas.getContext('2d')!); return { stampCanvas, scramCanvas, paintStamp(name: string, count: number, color: string) { sg.clearRect(0, 0, STAMP_W, STAMP_H); sg.save(); // Stamped by hand, badly: a few degrees off true. sg.translate(STAMP_W / 2, STAMP_H / 2); sg.rotate(-0.045); sg.translate(-STAMP_W / 2, -STAMP_H / 2); // dark backing so the legend survives on top of white SMPTE bars sg.fillStyle = 'rgba(6,6,10,0.72)'; roundRect(sg, 12, 22, STAMP_W - 24, STAMP_H - 44, 6); sg.fill(); sg.strokeStyle = color; sg.lineWidth = 5; roundRect(sg, 12, 22, STAMP_W - 24, STAMP_H - 44, 6); sg.stroke(); sg.fillStyle = color; sg.textBaseline = 'middle'; // Long codex names have to fit the placard — shrink until they do. let size = 22; sg.font = `bold ${size}px 'Courier New', monospace`; while (sg.measureText(name).width > STAMP_W - 48 && size > 9) { size -= 1; sg.font = `bold ${size}px 'Courier New', monospace`; } const nw = sg.measureText(name).width; sg.fillText(name, (STAMP_W - nw) / 2, STAMP_H / 2 - 12); const qty = `×${count}`; sg.font = "bold 26px 'Courier New', monospace"; const qw = sg.measureText(qty).width; sg.fillText(qty, (STAMP_W - qw) / 2, STAMP_H / 2 + 20); sg.fillStyle = 'rgba(255,255,255,0.5)'; sg.font = "9px 'Courier New', monospace"; sg.fillText('SHIPPED / NO QUESTIONS', 22, 34); sg.restore(); }, }; }