From fccccbd118e74eb326715a2b6e372c0ab7e20b73 Mon Sep 17 00:00:00 2001 From: vl Date: Sun, 20 Sep 2026 17:51:04 +0200 Subject: [PATCH] feat: render the panel image as a 1-bit 800x480 BMP or PNG With the move to BYOS there is no TRMNL cloud to interpret a Liquid template, so the application draws the screen itself. The pipeline is satori (flexbox to SVG) then resvg (SVG to pixels) then a hand-written 1-bit encoder. Chromium was the alternative and was rejected: half a gigabyte and a real memory appetite in the runtime image, for a screen of eight blocks. Playwright still handles the E2E tests, in its own pinned image, never in the application one. The payoff is testability. The layout is asserted on the element tree and the geometry on the SVG text, so a rendering regression shows up as a readable diff instead of a pixel comparison. The BMP and PNG encoders are verified field by field against their specifications, including an independent CRC-32 for the PNG: a device rejecting a malformed image is expensive to debug from a shop window. Two properties are pinned because the battery depends on them: the same payload must produce byte-identical output, and changed hours must produce different output. The filename handed to the device is a hash of these bytes, and the firmware skips the redraw when it is unchanged. The fonts are vendored into public/fonts and the logo into public/brand, both committed. Rendering must not depend on an install tree, a CDN or the network, or the bytes drift and the panel wakes for nothing. `npm run screen:preview` writes a real 1-bit image plus a magnified view, which is where clipping and thin strokes give themselves up. That is how the week strip was caught clipping and condensed. `npm run brand` rebuilds the assets: it locates the wordmark band in the shop logo rather than hard-coding offsets a future revision would break, and thresholds it with the panel's own encoder. sharp is a devDependency used only there; nothing at runtime needs it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd --- lib/screen/encode.test.ts | 153 ++++++++++++++++++++ lib/screen/encode.ts | 205 +++++++++++++++++++++++++++ lib/screen/fonts.ts | 39 ++++++ lib/screen/h.test.ts | 35 +++++ lib/screen/h.ts | 30 ++++ lib/screen/layout.ts | 24 ++++ lib/screen/logo.ts | 32 +++++ lib/screen/render.test.ts | 215 +++++++++++++++++++++++++++++ lib/screen/render.ts | 213 ++++++++++++++++++++++++++++ package-lock.json | 54 +++++++- package.json | 3 + public/brand/logo-eink.png | Bin 0 -> 349 bytes public/brand/logo.png | Bin 0 -> 5778 bytes public/fonts/README.md | 20 +++ public/fonts/inter-400.woff | Bin 0 -> 30696 bytes public/fonts/inter-600.woff | Bin 0 -> 31260 bytes public/fonts/inter-700.woff | Bin 0 -> 31320 bytes public/fonts/source-serif-600.woff | Bin 0 -> 25624 bytes scripts/brand.ts | 112 +++++++++++++++ scripts/preview.ts | 129 +++++++++++++++++ 20 files changed, 1262 insertions(+), 2 deletions(-) create mode 100644 lib/screen/encode.test.ts create mode 100644 lib/screen/encode.ts create mode 100644 lib/screen/fonts.ts create mode 100644 lib/screen/h.test.ts create mode 100644 lib/screen/h.ts create mode 100644 lib/screen/layout.ts create mode 100644 lib/screen/logo.ts create mode 100644 lib/screen/render.test.ts create mode 100644 lib/screen/render.ts create mode 100644 public/brand/logo-eink.png create mode 100644 public/brand/logo.png create mode 100644 public/fonts/README.md create mode 100644 public/fonts/inter-400.woff create mode 100644 public/fonts/inter-600.woff create mode 100644 public/fonts/inter-700.woff create mode 100644 public/fonts/source-serif-600.woff create mode 100644 scripts/brand.ts create mode 100644 scripts/preview.ts diff --git a/lib/screen/encode.test.ts b/lib/screen/encode.test.ts new file mode 100644 index 0000000..c431583 --- /dev/null +++ b/lib/screen/encode.test.ts @@ -0,0 +1,153 @@ +import { describe, expect, it } from 'vitest'; + +import { encodeBmp, encodePng, toBitmap, type Bitmap } from './encode'; + +/** Builds RGBA pixels from a picture drawn with '#' for ink and '.' for paper. */ +function pixels(rows: string[]): { rgba: Buffer; width: number; height: number } { + const height = rows.length; + const width = rows[0]?.length ?? 0; + const rgba = Buffer.alloc(width * height * 4); + rows.forEach((row, y) => { + [...row].forEach((cell, x) => { + const value = cell === '#' ? 0 : 255; + const offset = (y * width + x) * 4; + rgba[offset] = value; + rgba[offset + 1] = value; + rgba[offset + 2] = value; + rgba[offset + 3] = 255; + }); + }); + return { rgba, width, height }; +} + +describe('toBitmap', () => { + it('packs eight pixels into one byte, most significant bit first', () => { + const { rgba, width, height } = pixels(['#.......']); + const bitmap = toBitmap(rgba, width, height); + // Black at x=0, white elsewhere: 0111 1111. + expect(bitmap.bits[0]).toBe(0b0111_1111); + }); + + it('pads the final byte of a row that is not a multiple of eight', () => { + const { rgba, width, height } = pixels(['###']); + const bitmap = toBitmap(rgba, width, height); + expect(bitmap.bits).toHaveLength(1); + expect(bitmap.bits[0]).toBe(0); + }); + + it('treats mid-grey either side of the threshold', () => { + const rgba = Buffer.from([126, 126, 126, 255, 128, 128, 128, 255]); + const bitmap = toBitmap(rgba, 2, 1); + // The darker pixel becomes ink, the lighter one paper. + expect(bitmap.bits[0]).toBe(0b0100_0000); + }); + + it('treats a transparent pixel as paper, not as ink', () => { + const rgba = Buffer.from([0, 0, 0, 0]); + expect(toBitmap(rgba, 1, 1).bits[0]).toBe(0b1000_0000); + }); + + it('inverts on request', () => { + const { rgba, width, height } = pixels(['#.......']); + expect(toBitmap(rgba, width, height, { invert: true }).bits[0]).toBe(0b1000_0000); + }); + + it('honours a custom threshold', () => { + const rgba = Buffer.from([200, 200, 200, 255]); + expect(toBitmap(rgba, 1, 1, { threshold: 220 }).bits[0]).toBe(0); + }); +}); + +describe('encodeBmp', () => { + const bitmap: Bitmap = toBitmap(...(() => { + const { rgba, width, height } = pixels(['#.......', '.......#']); + return [rgba, width, height] as const; + })()); + + it('writes a BMP signature and a 62-byte pixel offset', () => { + const bmp = encodeBmp(bitmap); + expect(bmp.subarray(0, 2).toString('ascii')).toBe('BM'); + // 14-byte file header + 40-byte info header + two palette entries. + expect(bmp.readUInt32LE(10)).toBe(62); + }); + + it('declares one bit per pixel and no compression', () => { + const bmp = encodeBmp(bitmap); + expect(bmp.readUInt32LE(14)).toBe(40); // info header size + expect(bmp.readInt32LE(18)).toBe(8); // width + expect(bmp.readInt32LE(22)).toBe(2); // height + expect(bmp.readUInt16LE(26)).toBe(1); // planes + expect(bmp.readUInt16LE(28)).toBe(1); // bits per pixel + expect(bmp.readUInt32LE(30)).toBe(0); // BI_RGB + expect(bmp.readUInt32LE(46)).toBe(2); // palette entries + }); + + it('pads every row to four bytes', () => { + const bmp = encodeBmp(bitmap); + // Two rows of one byte each, padded to four: eight bytes of pixel data. + expect(bmp.readUInt32LE(34)).toBe(8); + expect(bmp.length).toBe(62 + 8); + expect(bmp.readUInt32LE(2)).toBe(bmp.length); + }); + + it('stores rows bottom-up, as the format requires', () => { + const bmp = encodeBmp(bitmap); + // The last source row lands first in the file. + expect(bmp[62]).toBe(0b1111_1110); + expect(bmp[66]).toBe(0b0111_1111); + }); + + it('pads a 800-pixel row to exactly 100 bytes', () => { + const wide = toBitmap(Buffer.alloc(800 * 4, 255), 800, 1); + const bmp = encodeBmp(wide); + // 800 bits = 100 bytes, already a multiple of four. + expect(bmp.readUInt32LE(34)).toBe(100); + }); +}); + +describe('encodePng', () => { + const bitmap = toBitmap(...(() => { + const { rgba, width, height } = pixels(['#.......', '.......#']); + return [rgba, width, height] as const; + })()); + + it('writes the PNG signature', () => { + const png = encodePng(bitmap); + expect([...png.subarray(0, 8)]).toEqual([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); + }); + + it('declares a one-bit greyscale image of the right size', () => { + const png = encodePng(bitmap); + expect(png.subarray(12, 16).toString('ascii')).toBe('IHDR'); + expect(png.readUInt32BE(16)).toBe(8); // width + expect(png.readUInt32BE(20)).toBe(2); // height + expect(png.readUInt8(24)).toBe(1); // bit depth + expect(png.readUInt8(25)).toBe(0); // greyscale + }); + + it('ends with an IEND chunk', () => { + const png = encodePng(bitmap); + expect(png.subarray(png.length - 8, png.length - 4).toString('ascii')).toBe('IEND'); + }); + + it('writes a CRC a decoder will accept', () => { + // An independent CRC-32 implementation: a decoder rejects the file if ours + // is wrong, and a device rejecting the image is expensive to debug. + const reference = (data: Buffer): number => { + let crc = 0xffffffff; + for (const byte of data) { + crc ^= byte; + for (let bit = 0; bit < 8; bit += 1) { + crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1; + } + } + return (crc ^ 0xffffffff) >>> 0; + }; + + const png = encodePng(bitmap); + const length = png.readUInt32BE(8); + // Chunk body is the type plus the data; the CRC follows it. + const body = png.subarray(12, 12 + 4 + length); + expect(png.readUInt32BE(12 + 4 + length)).toBe(reference(body)); + }); +}); diff --git a/lib/screen/encode.ts b/lib/screen/encode.ts new file mode 100644 index 0000000..dd45e32 --- /dev/null +++ b/lib/screen/encode.ts @@ -0,0 +1,205 @@ +/** + * Turns the rendered SVG into what the panel actually consumes: a 1-bit + * 800x480 image. + * + * The firmware on this kit references `.bmp`, but the documentation is not + * explicit, so both encoders live here and `DEVICE_IMAGE_FORMAT` chooses. The + * bit-packing is shared, so whichever format wins shows identical pixels. + * + * There is deliberately no dithering. The source is flat black on flat white; + * dithering would only fuzz the text edges and cost contrast on a panel that + * has none to spare. + */ + +import { deflateSync } from 'node:zlib'; + +import { Resvg } from '@resvg/resvg-js'; + +import { SCREEN_HEIGHT, SCREEN_WIDTH } from './layout'; + +export type Bitmap = { + width: number; + height: number; + /** One bit per pixel, MSB first, row-major. A set bit is white. */ + bits: Uint8Array; +}; + +/** Mid-grey. Anti-aliased glyph edges fall either side of it. */ +export const DEFAULT_THRESHOLD = 127; + +export type EncodeOptions = { + threshold?: number; + /** + * Flip black and white. The knob to reach for if the panel ever comes out + * as a negative; the pixels are otherwise correct. + */ + invert?: boolean; +}; + +/** Rasterises the SVG at its natural size and returns RGBA pixels. */ +export function rasterise(svg: string): { width: number; height: number; rgba: Buffer } { + const resvg = new Resvg(svg, { fitTo: { mode: 'width', value: SCREEN_WIDTH } }); + const rendered = resvg.render(); + return { width: rendered.width, height: rendered.height, rgba: rendered.pixels }; +} + +/** Thresholds RGBA pixels into one bit each, no dithering. */ +export function toBitmap( + rgba: Buffer, + width: number, + height: number, + { threshold = DEFAULT_THRESHOLD, invert = false }: EncodeOptions = {}, +): Bitmap { + const bytesPerRow = Math.ceil(width / 8); + const bits = new Uint8Array(bytesPerRow * height); + + for (let y = 0; y < height; y += 1) { + for (let x = 0; x < width; x += 1) { + const offset = (y * width + x) * 4; + const red = rgba[offset] ?? 0; + const green = rgba[offset + 1] ?? 0; + const blue = rgba[offset + 2] ?? 0; + const alpha = rgba[offset + 3] ?? 255; + + // Composite over white first: the panel is paper, so anything + // transparent is paper too, not ink. + const opacity = alpha / 255; + const over = (channel: number): number => channel * opacity + 255 * (1 - opacity); + const luminance = 0.299 * over(red) + 0.587 * over(green) + 0.114 * over(blue); + + const isWhite = invert ? luminance <= threshold : luminance > threshold; + if (isWhite) { + const index = y * bytesPerRow + (x >> 3); + bits[index] = (bits[index] ?? 0) | (0b1000_0000 >> (x & 7)); + } + } + } + + return { width, height, bits }; +} + +/** + * A 1-bit BMP: 14-byte file header, 40-byte info header, a two-colour palette, + * then bottom-up rows padded to four bytes. Uncompressed on purpose — the + * firmware reads it straight through. + */ +export function encodeBmp(bitmap: Bitmap): Buffer { + const { width, height, bits } = bitmap; + const sourceRowBytes = Math.ceil(width / 8); + const rowBytes = Math.ceil(width / 32) * 4; + const pixelBytes = rowBytes * height; + const offset = 14 + 40 + 8; + + const buffer = Buffer.alloc(offset + pixelBytes); + + buffer.write('BM', 0, 'ascii'); + buffer.writeUInt32LE(buffer.length, 2); + buffer.writeUInt32LE(0, 6); + buffer.writeUInt32LE(offset, 10); + + buffer.writeUInt32LE(40, 14); + buffer.writeInt32LE(width, 18); + buffer.writeInt32LE(height, 22); + buffer.writeUInt16LE(1, 26); + buffer.writeUInt16LE(1, 28); + buffer.writeUInt32LE(0, 30); + buffer.writeUInt32LE(pixelBytes, 34); + buffer.writeInt32LE(2835, 38); + buffer.writeInt32LE(2835, 42); + buffer.writeUInt32LE(2, 46); + buffer.writeUInt32LE(2, 50); + + // Palette, BGRA. Index 0 is black, index 1 is white. + buffer.writeUInt32LE(0x00000000, 54); + buffer.writeUInt32LE(0x00ffffff, 58); + + // BMP rows run bottom to top. + for (let y = 0; y < height; y += 1) { + const source = (height - 1 - y) * sourceRowBytes; + const target = offset + y * rowBytes; + for (let byte = 0; byte < sourceRowBytes; byte += 1) { + buffer[target + byte] = bits[source + byte] ?? 0; + } + } + + return buffer; +} + +const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); + +/** A 1-bit greyscale PNG, for firmwares that prefer it to BMP. */ +export function encodePng(bitmap: Bitmap): Buffer { + const { width, height, bits } = bitmap; + const rowBytes = Math.ceil(width / 8); + + const header = Buffer.alloc(13); + header.writeUInt32BE(width, 0); + header.writeUInt32BE(height, 4); + header.writeUInt8(1, 8); // bit depth + header.writeUInt8(0, 9); // greyscale + header.writeUInt8(0, 10); + header.writeUInt8(0, 11); + header.writeUInt8(0, 12); + + // Every scanline carries a leading filter byte; "none" keeps it readable. + const raw = Buffer.alloc((rowBytes + 1) * height); + for (let y = 0; y < height; y += 1) { + const target = y * (rowBytes + 1); + raw[target] = 0; + for (let byte = 0; byte < rowBytes; byte += 1) { + raw[target + 1 + byte] = bits[y * rowBytes + byte] ?? 0; + } + } + + return Buffer.concat([ + PNG_SIGNATURE, + chunk('IHDR', header), + chunk('IDAT', deflateSync(raw, { level: 9 })), + chunk('IEND', Buffer.alloc(0)), + ]); +} + +function chunk(type: string, data: Buffer): Buffer { + const length = Buffer.alloc(4); + length.writeUInt32BE(data.length, 0); + const body = Buffer.concat([Buffer.from(type, 'ascii'), data]); + const crc = Buffer.alloc(4); + crc.writeUInt32BE(crc32(body), 0); + return Buffer.concat([length, body, crc]); +} + +const CRC_TABLE = (() => { + const table = new Uint32Array(256); + for (let index = 0; index < 256; index += 1) { + let value = index; + for (let bit = 0; bit < 8; bit += 1) { + value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1; + } + table[index] = value >>> 0; + } + return table; +})(); + +function crc32(data: Buffer): number { + let crc = 0xffffffff; + for (const byte of data) { + crc = (CRC_TABLE[(crc ^ byte) & 0xff] ?? 0) ^ (crc >>> 8); + } + return (crc ^ 0xffffffff) >>> 0; +} + +/** The whole pipeline: SVG in, panel-ready bytes out. */ +export function encodeScreen( + svg: string, + format: 'bmp' | 'png', + options: EncodeOptions = {}, +): Buffer { + const { width, height, rgba } = rasterise(svg); + const bitmap = toBitmap(rgba, width, height, options); + if (bitmap.width !== SCREEN_WIDTH || bitmap.height !== SCREEN_HEIGHT) { + throw new Error( + `Rendered ${bitmap.width}x${bitmap.height}, expected ${SCREEN_WIDTH}x${SCREEN_HEIGHT}`, + ); + } + return format === 'png' ? encodePng(bitmap) : encodeBmp(bitmap); +} diff --git a/lib/screen/fonts.ts b/lib/screen/fonts.ts new file mode 100644 index 0000000..0c46c1e --- /dev/null +++ b/lib/screen/fonts.ts @@ -0,0 +1,39 @@ +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; + +import type { Font } from 'satori'; + +import { SANS, SERIF } from './layout'; + +/** + * Font binaries for the panel renderer, read from `public/fonts` rather than + * from `node_modules`. + * + * The rendered pixels feed a content hash that decides whether the e-ink panel + * redraws, so they have to be reproducible. Vendored files make that true; an + * install tree or a CDN would not. + */ +const FILES: { file: string; name: string; weight: Font['weight'] }[] = [ + { file: 'inter-400.woff', name: SANS, weight: 400 }, + { file: 'inter-600.woff', name: SANS, weight: 600 }, + { file: 'inter-700.woff', name: SANS, weight: 700 }, + { file: 'source-serif-600.woff', name: SERIF, weight: 600 }, +]; + +let cached: Font[] | null = null; + +export async function loadScreenFonts(): Promise { + if (cached) { + return cached; + } + const directory = path.join(process.cwd(), 'public', 'fonts'); + cached = await Promise.all( + FILES.map(async ({ file, name, weight }) => ({ + name, + data: await readFile(path.join(directory, file)), + weight, + style: 'normal' as const, + })), + ); + return cached; +} diff --git a/lib/screen/h.test.ts b/lib/screen/h.test.ts new file mode 100644 index 0000000..6612370 --- /dev/null +++ b/lib/screen/h.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; + +import { h } from './h'; + +describe('h', () => { + it('omits children entirely when there are none', () => { + expect(h('div', { style: {} })).toEqual({ type: 'div', props: { style: {} } }); + }); + + it('accepts null props', () => { + expect(h('div', null, 'text')).toEqual({ type: 'div', props: { children: 'text' } }); + }); + + it('unwraps a single child', () => { + expect(h('div', null, 'only').props.children).toBe('only'); + }); + + it('keeps several children as a list', () => { + expect(h('div', null, 'a', 'b').props.children).toEqual(['a', 'b']); + }); + + it('flattens nested arrays and drops empty branches', () => { + // `condition && node` and `condition ? node : null` are the two shapes the + // renderer uses to leave a block out; neither may reach satori. + expect(h('div', null, ['a', ['b', null]], false, null, 'c').props.children).toEqual([ + 'a', + 'b', + 'c', + ]); + }); + + it('keeps a zero, which is a legitimate child', () => { + expect(h('div', null, 0).props.children).toBe(0); + }); +}); diff --git a/lib/screen/h.ts b/lib/screen/h.ts new file mode 100644 index 0000000..9986a1c --- /dev/null +++ b/lib/screen/h.ts @@ -0,0 +1,30 @@ +/** + * A minimal element factory for satori. + * + * satori consumes `{ type, props }` trees, which is exactly what JSX would + * produce. Building them directly keeps the renderer out of the JSX transform + * entirely: no pragma, no React import, and the same file runs unchanged under + * Vitest, under `next build` and under the preview script. + */ + +export type ElementNode = { + type: string; + props: Record & { children?: ElementChild }; +}; + +export type ElementChild = ElementNode | string | number | null | false | ElementChild[]; + +export function h( + type: string, + props: Record | null, + ...children: ElementChild[] +): ElementNode { + const flat = children.flat(Infinity as 1).filter((child) => child !== null && child !== false); + return { + type, + props: { + ...(props ?? {}), + ...(flat.length > 0 ? { children: flat.length === 1 ? flat[0] : flat } : {}), + }, + }; +} diff --git a/lib/screen/layout.ts b/lib/screen/layout.ts new file mode 100644 index 0000000..5286ba5 --- /dev/null +++ b/lib/screen/layout.ts @@ -0,0 +1,24 @@ +/** + * Screen geometry and typography, in one place. + * + * The panel is 800x480 and strictly 1-bit: there is no grey to lean on, so + * hierarchy has to come from size, weight and solid inversion. Nothing here + * may introduce a shadow, a gradient or a hairline — they dither into mud on + * e-ink and cost a redraw for nothing. + */ + +export const SCREEN_WIDTH = 800; +export const SCREEN_HEIGHT = 480; + +export const BLACK = '#000000'; +export const WHITE = '#ffffff'; + +export const PADDING = 24; +export const HEADER_HEIGHT = 76; +export const WEEK_HEIGHT = 112; +export const BANNER_HEIGHT = 84; +/** Solid rules only; anything thinner than 2px breaks up on e-ink. */ +export const RULE = 3; + +export const SANS = 'Inter'; +export const SERIF = 'Source Serif 4'; diff --git a/lib/screen/logo.ts b/lib/screen/logo.ts new file mode 100644 index 0000000..96a55cb --- /dev/null +++ b/lib/screen/logo.ts @@ -0,0 +1,32 @@ +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; + +/** + * The 1-bit wordmark, inlined as a data URI. + * + * It is read from the committed `public/brand/logo-eink.png` and embedded in + * the SVG rather than linked: the renderer must not depend on the network, and + * an image the rasteriser silently failed to fetch would leave a hole in the + * shop window. Regenerate the file with `npm run brand`. + */ + +/** Matches the committed asset; see scripts/brand.ts. */ +export const LOGO_WIDTH = 260; +export const LOGO_HEIGHT = 36; + +let cached: string | null | undefined; + +export async function loadLogoDataUri(): Promise { + if (cached !== undefined) { + return cached; + } + try { + const file = path.join(process.cwd(), 'public', 'brand', 'logo-eink.png'); + cached = `data:image/png;base64,${(await readFile(file)).toString('base64')}`; + } catch { + // Not fatal: the header falls back to the shop name set in the settings, + // which is worse-looking but still a working screen. + cached = null; + } + return cached; +} diff --git a/lib/screen/render.test.ts b/lib/screen/render.test.ts new file mode 100644 index 0000000..8b4150f --- /dev/null +++ b/lib/screen/render.test.ts @@ -0,0 +1,215 @@ +import { describe, expect, it } from 'vitest'; + +import { SCREEN_HEIGHT, SCREEN_WIDTH } from './layout'; +import { encodeScreen } from './encode'; +import { renderScreenSvg, screenTree } from './render'; +import type { ScreenPayload } from './contract'; + +const PAYLOAD: ScreenPayload = { + schema: 1, + generated_at: '2026-09-22T12:00:00+02:00', + shop_name: 'ITA ITO', + logo_url: 'https://trmnl.loxi.ch/brand/logo-eink.png', + today: { + date_fr: 'Mardi 22 septembre', + date_en: 'Tuesday 22 September', + status: 'OPEN', + status_fr: 'OUVERT', + status_en: 'OPEN', + hours_text: '10:00 – 13:00 · 14:00 – 18:30', + next_change_fr: 'Ferme à 13:00', + next_change_en: 'Closes at 1:00 pm', + is_exception: false, + }, + week: [ + { label_fr: 'Lun', label_en: 'Mon', hours_text: '', is_today: false, is_closed: true, is_exception: false }, + { label_fr: 'Mar', label_en: 'Tue', hours_text: '10:00 – 18:30', is_today: true, is_closed: false, is_exception: false }, + { label_fr: 'Mer', label_en: 'Wed', hours_text: '10:00 – 13:00 · 14:00 – 18:30', is_today: false, is_closed: false, is_exception: false }, + { label_fr: 'Jeu', label_en: 'Thu', hours_text: '14:00 – 18:30', is_today: false, is_closed: false, is_exception: true }, + { label_fr: 'Ven', label_en: 'Fri', hours_text: '', is_today: false, is_closed: true, is_exception: true }, + { label_fr: 'Sam', label_en: 'Sat', hours_text: '10:00 – 18:30', is_today: false, is_closed: false, is_exception: false }, + { label_fr: 'Dim', label_en: 'Sun', hours_text: '', is_today: false, is_closed: true, is_exception: false }, + ], + banner: { + kind: 'MESSAGE', + text_fr: 'Fermeture exceptionnelle jeudi après-midi', + text_en: 'Exceptionally closed on Thursday afternoon', + }, +}; + +describe('screenTree', () => { + it('drops the banner block entirely when there is nothing to say', () => { + const withBanner = JSON.stringify(screenTree(PAYLOAD)); + const without = JSON.stringify(screenTree({ ...PAYLOAD, banner: null })); + expect(withBanner).toContain('Fermeture exceptionnelle jeudi après-midi'); + expect(without).not.toContain('Fermeture'); + // The week block takes the freed room rather than leaving a white gap. + expect(without.length).toBeLessThan(withBanner.length); + }); + + it('places every key label in the drawing', () => { + // satori vectorises text into paths, so the strings are only observable + // here, on the element tree it is handed. + const tree = JSON.stringify(screenTree(PAYLOAD)); + for (const label of [ + 'ITA ITO', + 'Mardi 22 septembre', + 'OUVERT', + '10:00 – 13:00 · 14:00 – 18:30', + 'Ferme à 13:00', + 'Closes at 1:00 pm', + 'Lun', + 'Jeu*', + 'Dim', + ]) { + expect(tree).toContain(label); + } + }); + + it('leaves no value unrendered', () => { + // A dropped or mistyped field surfaces as one of these in the drawing + // rather than as a crash, so they are worth failing the build over. + // (Liquid-style {{ }} holes are not checked: there is no template engine + // left in this pipeline, and JSON braces would match them anyway.) + expect(JSON.stringify(screenTree(PAYLOAD))).not.toMatch( + /undefined|\[object Object\]|NaN|null,null/, + ); + }); + + it('drops the separator when only one language has something to say', () => { + const frenchOnly = JSON.stringify( + screenTree({ ...PAYLOAD, today: { ...PAYLOAD.today, next_change_en: '' } }), + ); + expect(frenchOnly).toContain('Ferme à 13:00'); + expect(frenchOnly).not.toContain('"·"'); + + const englishOnly = JSON.stringify( + screenTree({ ...PAYLOAD, today: { ...PAYLOAD.today, next_change_fr: '' } }), + ); + expect(englishOnly).toContain('Closes at 1:00 pm'); + expect(englishOnly).not.toContain('"·"'); + }); + + it('drops the whole next-change line when there is nothing ahead', () => { + const tree = JSON.stringify( + screenTree({ + ...PAYLOAD, + today: { ...PAYLOAD.today, next_change_fr: '', next_change_en: '' }, + }), + ); + // Checked against the exact strings: the banner legitimately starts with + // "Fermeture", which a looser assertion would trip over. + expect(tree).not.toContain('Ferme à 13:00'); + expect(tree).not.toContain('Closes at 1:00 pm'); + }); + + it('drops the English banner line when the translation is missing', () => { + const tree = JSON.stringify( + screenTree({ ...PAYLOAD, banner: { ...PAYLOAD.banner!, text_en: '' } }), + ); + expect(tree).toContain('Fermeture exceptionnelle jeudi après-midi'); + expect(tree).not.toContain('Exceptionally'); + }); + + it('shows the wordmark when one is available, and the shop name otherwise', () => { + const withLogo = JSON.stringify(screenTree(PAYLOAD, 'data:image/png;base64,AAAA')); + expect(withLogo).toContain('"img"'); + expect(withLogo).toContain('data:image/png;base64,AAAA'); + + // The fallback keeps the panel working if the asset ever goes missing. + const withoutLogo = JSON.stringify(screenTree(PAYLOAD, null)); + expect(withoutLogo).not.toContain('"img"'); + expect(withoutLogo).toContain('ITA ITO'); + }); + + it('omits the hours line on a day with no hours', () => { + const tree = JSON.stringify( + screenTree({ ...PAYLOAD, today: { ...PAYLOAD.today, hours_text: '' } }), + ); + expect(tree).not.toContain('10:00 – 13:00 · 14:00 – 18:30'); + }); +}); + +describe('renderScreenSvg', () => { + it('renders exactly 800x480', async () => { + const svg = await renderScreenSvg(PAYLOAD); + expect(svg).toContain(`width="${SCREEN_WIDTH}"`); + expect(svg).toContain(`height="${SCREEN_HEIGHT}"`); + }); + + it('uses no grey, no gradient and no shadow', async () => { + const svg = await renderScreenSvg(PAYLOAD); + expect(svg).not.toMatch(/linearGradient|radialGradient|feGaussianBlur|filter=/); + + // Only pure black and pure white may appear, in either hex spelling. + const normalise = (colour: string): string => + /^#[0-9a-f]{3}$/i.test(colour) + ? `#${[...colour.slice(1)].map((digit) => digit + digit).join('')}`.toLowerCase() + : colour.toLowerCase(); + + const paints = [...svg.matchAll(/(?:fill|stroke)="([^"]+)"/g)].map((match) => + normalise(match[1] ?? ''), + ); + const unexpected = paints.filter( + (paint) => !['#000000', '#ffffff', 'none', 'transparent'].includes(paint), + ); + expect(unexpected).toEqual([]); + }); +}); + +describe('encodeScreen', () => { + it('produces a 1-bit BMP of the exact panel size', async () => { + const bmp = encodeScreen(await renderScreenSvg(PAYLOAD), 'bmp'); + expect(bmp.subarray(0, 2).toString('ascii')).toBe('BM'); + expect(bmp.readInt32LE(18)).toBe(SCREEN_WIDTH); + expect(bmp.readInt32LE(22)).toBe(SCREEN_HEIGHT); + expect(bmp.readUInt16LE(28)).toBe(1); + // 800x480 at one bit, plus headers: comfortably inside any firmware limit. + expect(bmp.length).toBe(62 + 100 * 480); + }); + + it('produces a 1-bit PNG of the exact panel size', async () => { + const png = encodeScreen(await renderScreenSvg(PAYLOAD), 'png'); + expect(png.readUInt32BE(16)).toBe(SCREEN_WIDTH); + expect(png.readUInt32BE(20)).toBe(SCREEN_HEIGHT); + expect(png.readUInt8(24)).toBe(1); + // Compressed, so it must come out far smaller than the raw bitmap. + expect(png.length).toBeLessThan(62 + 100 * 480); + }); + + it('is byte-stable for the same payload', async () => { + // The device skips the redraw when the filename is unchanged, and the + // filename is a hash of these bytes. Instability would wake the panel for + // nothing, every single cycle. + const first = encodeScreen(await renderScreenSvg(PAYLOAD), 'bmp'); + const second = encodeScreen(await renderScreenSvg(PAYLOAD), 'bmp'); + expect(first.equals(second)).toBe(true); + }); + + it('refuses to hand the device an image of the wrong size', () => { + // Better a loud failure here than a panel that silently draws nothing. + const square = ''; + expect(() => encodeScreen(square, 'bmp')).toThrow(/expected 800x480/); + }); + + it('passes the invert option through to the bit packing', async () => { + const svg = await renderScreenSvg(PAYLOAD); + const normal = encodeScreen(svg, 'bmp'); + const inverted = encodeScreen(svg, 'bmp', { invert: true }); + expect(normal.equals(inverted)).toBe(false); + // Only the pixels differ; the headers are identical. + expect(normal.subarray(0, 62).equals(inverted.subarray(0, 62))).toBe(true); + }); + + it('changes when the hours change', async () => { + const before = encodeScreen(await renderScreenSvg(PAYLOAD), 'bmp'); + const after = encodeScreen( + await renderScreenSvg({ + ...PAYLOAD, + today: { ...PAYLOAD.today, hours_text: '14:00 – 18:30' }, + }), + 'bmp', + ); + expect(before.equals(after)).toBe(false); + }); +}); diff --git a/lib/screen/render.ts b/lib/screen/render.ts new file mode 100644 index 0000000..2afad75 --- /dev/null +++ b/lib/screen/render.ts @@ -0,0 +1,213 @@ +/** + * Draws the screen payload as an 800x480 SVG. + * + * This module draws and nothing else. Every string it places has already been + * computed and translated by the view model, so there is no business logic to + * disagree about — which is also why its output can be snapshotted as text + * rather than compared as pixels. + * + * e-ink discipline: no greys, no shadows, no gradients, no hairlines. + * Hierarchy comes from size, weight and solid inversion. + */ + +import satori from 'satori'; + +import type { ScreenPayload, ScreenWeekDay } from './contract'; +import { loadScreenFonts } from './fonts'; +import { h, type ElementNode } from './h'; +import { LOGO_HEIGHT, LOGO_WIDTH, loadLogoDataUri } from './logo'; +import { + BANNER_HEIGHT, + BLACK, + HEADER_HEIGHT, + PADDING, + RULE, + SANS, + SCREEN_HEIGHT, + SCREEN_WIDTH, + SERIF, + WEEK_HEIGHT, + WHITE, +} from './layout'; + +export function screenTree(payload: ScreenPayload, logo: string | null = null): ElementNode { + return h( + 'div', + { + style: { + display: 'flex', + flexDirection: 'column', + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT, + backgroundColor: WHITE, + color: BLACK, + fontFamily: SANS, + }, + }, + header(payload, logo), + main(payload), + weekStrip(payload.week), + banner(payload), + ); +} + +function header(payload: ScreenPayload, logo: string | null): ElementNode { + return h( + 'div', + { + style: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + height: HEADER_HEIGHT, + paddingLeft: PADDING, + paddingRight: PADDING, + borderBottom: `${RULE}px solid ${BLACK}`, + }, + }, + logo + ? h('img', { + src: logo, + width: LOGO_WIDTH, + height: LOGO_HEIGHT, + style: { display: 'flex' }, + }) + : h( + 'div', + { style: { display: 'flex', fontFamily: SERIF, fontSize: 34, letterSpacing: 6 } }, + payload.shop_name, + ), + h('div', { style: { display: 'flex', fontSize: 22 } }, payload.today.date_fr), + ); +} + +function main(payload: ScreenPayload): ElementNode { + const { today } = payload; + return h( + 'div', + { + style: { + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + justifyContent: 'center', + paddingLeft: PADDING, + paddingRight: PADDING, + }, + }, + h( + 'div', + { style: { display: 'flex', fontSize: 92, fontWeight: 700, lineHeight: 1 } }, + today.status_fr, + ), + today.hours_text + ? h( + 'div', + { style: { display: 'flex', fontSize: 36, fontWeight: 600, marginTop: 10 } }, + today.hours_text, + ) + : null, + nextChangeLine(payload), + ); +} + +function nextChangeLine(payload: ScreenPayload): ElementNode | null { + const { next_change_fr: fr, next_change_en: en } = payload.today; + if (!fr && !en) { + return null; + } + return h( + 'div', + { style: { display: 'flex', alignItems: 'baseline', marginTop: 12 } }, + fr ? h('div', { style: { display: 'flex', fontSize: 24 } }, fr) : null, + fr && en ? h('div', { style: { display: 'flex', fontSize: 24, paddingLeft: 10, paddingRight: 10 } }, '·') : null, + en ? h('div', { style: { display: 'flex', fontSize: 20 } }, en) : null, + ); +} + +function weekStrip(week: ScreenWeekDay[]): ElementNode { + return h( + 'div', + { + style: { + display: 'flex', + height: WEEK_HEIGHT, + borderTop: `${RULE}px solid ${BLACK}`, + }, + }, + ...week.map((day) => weekColumn(day)), + ); +} + +function weekColumn(day: ScreenWeekDay): ElementNode { + // Today is inverted rather than tinted: a solid block is the only emphasis + // a 1-bit panel renders cleanly. + const inverted = day.is_today; + // A column is only 114px wide. The spaced en dash reads better in the main + // block, but here it is the difference between fitting and clipping. + const lines = day.is_closed + ? ['—'] + : day.hours_text.split(' · ').map((slot) => slot.replace(' – ', '–')); + + return h( + 'div', + { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1, + flexBasis: 0, + paddingTop: 8, + paddingBottom: 8, + paddingLeft: 6, + paddingRight: 6, + backgroundColor: inverted ? BLACK : WHITE, + color: inverted ? WHITE : BLACK, + }, + }, + h( + 'div', + { style: { display: 'flex', fontSize: 19, fontWeight: 700, marginBottom: 6 } }, + `${day.label_fr}${day.is_exception ? '*' : ''}`, + ), + ...lines.map((line) => + h('div', { style: { display: 'flex', fontSize: 16, lineHeight: 1.35 } }, line), + ), + ); +} + +function banner(payload: ScreenPayload): ElementNode | null { + if (!payload.banner) { + return null; + } + const { text_fr: fr, text_en: en } = payload.banner; + return h( + 'div', + { + style: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + height: BANNER_HEIGHT, + paddingLeft: PADDING, + paddingRight: PADDING, + backgroundColor: BLACK, + color: WHITE, + }, + }, + h('div', { style: { display: 'flex', fontSize: 25, fontWeight: 600 } }, fr), + en ? h('div', { style: { display: 'flex', fontSize: 19, marginTop: 4 } }, en) : null, + ); +} + +/** Renders the payload to an SVG string, exactly 800x480. */ +export async function renderScreenSvg(payload: ScreenPayload): Promise { + const [fonts, logo] = await Promise.all([loadScreenFonts(), loadLogoDataUri()]); + return satori(screenTree(payload, logo) as unknown as React.ReactNode, { + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT, + fonts, + }); +} diff --git a/package-lock.json b/package-lock.json index 8e08996..32bf1ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,8 @@ }, "devDependencies": { "@eslint/js": "^9.39.5", + "@fontsource/inter": "^5.3.0", + "@fontsource/source-serif-4": "^5.3.0", "@tailwindcss/postcss": "^4.3.3", "@types/node": "^26.6.2", "@types/node-cron": "^3.0.11", @@ -40,6 +42,7 @@ "msw": "^2.15.0", "postcss": "^8.5.28", "prisma": "^7.10.0", + "sharp": "^0.35.4", "tailwindcss": "^4.3.3", "tsx": "^4.23.15", "typescript": "^6.0.3", @@ -415,6 +418,7 @@ "version": "1.11.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1111,6 +1115,26 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@fontsource/inter": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.3.0.tgz", + "integrity": "sha512-RofMylZmjlJEfELXeNHFWBRcSs75rGU/6bV2S2jfnvv/3rPXPGe0LgUJTklcHZ9lM4OZmAVFhcJPnACfb91A3g==", + "dev": true, + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/source-serif-4": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/source-serif-4/-/source-serif-4-5.3.0.tgz", + "integrity": "sha512-yQz8xmIgMzks8zQbpuca3UYtjxK798XCyQAGdDXmzBgvV7sdoK6d0YvE9F9kiYxORCEMRBgGZDZsSYcXj5KvwA==", + "dev": true, + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1181,8 +1205,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=18" } @@ -1194,6 +1218,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1216,6 +1241,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1235,6 +1261,7 @@ "version": "0.35.4", "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1257,6 +1284,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1273,6 +1301,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1289,6 +1318,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1305,6 +1335,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1321,6 +1352,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1337,6 +1369,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1353,6 +1386,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1369,6 +1403,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1385,6 +1420,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1401,6 +1437,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1417,6 +1454,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1439,6 +1477,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1461,6 +1500,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1483,6 +1523,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1505,6 +1546,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1527,6 +1569,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1549,6 +1592,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1571,6 +1615,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1590,6 +1635,7 @@ "version": "0.35.4", "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -1609,6 +1655,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1628,6 +1675,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -1647,6 +1695,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -1666,6 +1715,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -9717,8 +9767,8 @@ "version": "0.35.4", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", + "devOptional": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", diff --git a/package.json b/package.json index d7e8158..e948632 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ }, "devDependencies": { "@eslint/js": "^9.39.5", + "@fontsource/inter": "^5.3.0", + "@fontsource/source-serif-4": "^5.3.0", "@tailwindcss/postcss": "^4.3.3", "@types/node": "^26.6.2", "@types/node-cron": "^3.0.11", @@ -52,6 +54,7 @@ "msw": "^2.15.0", "postcss": "^8.5.28", "prisma": "^7.10.0", + "sharp": "^0.35.4", "tailwindcss": "^4.3.3", "tsx": "^4.23.15", "typescript": "^6.0.3", diff --git a/public/brand/logo-eink.png b/public/brand/logo-eink.png new file mode 100644 index 0000000000000000000000000000000000000000..7f56643c8f1dac747aa9e4b3f8982ba15a4c68da GIT binary patch literal 349 zcmV-j0iyniP)Zq}6b4{h42(X4kK!C9$LQ2&l%ZpekgK>;#$16;9qW;*uK(6Nz<{G5I+V$J`Y=NN ze+z6pUhER@_eEO+RvZj~>oBw$QHu2L`3f(*0_@?a0h~r*5GpBvXx`vOr+|}tN}?Ks zBLpQ|pd(K#Ufi<=;#?T%0gukGP>#|bawbT2gcd=w(2g!E0T~Fd2#SSaVYB##jD!ZU zWCu_~vTZB|>?(M_N<9yqD!`v4?DvHdnOzCT600000NkvXXu0mjf;=qwt literal 0 HcmV?d00001 diff --git a/public/brand/logo.png b/public/brand/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2d646f13aa76d4b5ab394c1c41dc17ee09712f75 GIT binary patch literal 5778 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

x{~0=`Y~T89cWpAJo}r{%=p? z|I%e2L)n0ej2P|3vw4vzFc6&_i#LMKX%Y$+KC&fem4kuDO=2{HZ4)^F1#r7ol;*~V z{Wd*{p}BzhZhuAqzUXE6&?4Z(%*tB?+k(ucztCJY4EZLwv+IkB!bk#Xf7`N{|L|1w zu4M%?}_)fKZtnN3!~pum&%5~K^5LBo;i$)gYEO+tEnRn0Wh6}sMh+* z8=4f3g83e>o?fZAlo`Pr{Dats_)(BzKXY9DBD%2{}Q zMLGloH`>WyoO)95YwpkY7pb7!%tzj!P`eM75~ufHw#sSaTGT@lh#UYQl=OT#`sLi_ zir7I_TD|mjyY3`=fFl$~&B7I|Z>Imw#zHR3t;xYx8OSmi|8pf|y2I^Y`qg@(K#n6E z0FOnZ~<2IdaAc zs6}dak)j;C2KJd;>H6&~a2<@m`*DW*UIJoDVCGgow4$y>AA()K+EE^>J)P&k7 z8q7zebSu@4?{u5Rd*6e1$m?X;^=L6SZP~TAozF0edW6m~nR^;+Wz!^0D9cK<{_e%| zDJ+VY=Q0K*07D6y1fx3|=7}m#6&5wj=qB&ZZ~)EkG~?I2c;A0iv1>-~YNyuPZ~%MG zyb|qvezZIN0MGF{-5pJtWEqVF(0kSN(Q=dK;Aq2n=b%j z1_W;%iXQxKJD{+>{`W9U+vKEiuL4kOH`h{|~m?zMB95 literal 0 HcmV?d00001 diff --git a/public/fonts/README.md b/public/fonts/README.md new file mode 100644 index 0000000..6dc6788 --- /dev/null +++ b/public/fonts/README.md @@ -0,0 +1,20 @@ +# Fonts used to render the e-ink screen + +These four files are committed on purpose. + +The panel image is rendered server-side by satori, which needs the font +binaries at render time. Reading them from `node_modules` would make the +rendering depend on the install tree, and on what the production image happens +to trace; reading them from a CDN would make it depend on the network. Both +would let the rendered pixels drift, and the rendered pixels feed a content +hash that decides whether the panel redraws at all. + +So the exact binaries live here, next to the code that uses them. + +| File | Source | Licence | +|---|---|---| +| `inter-400.woff`, `inter-600.woff`, `inter-700.woff` | `@fontsource/inter` (Inter, Rasmus Andersson) | SIL Open Font License 1.1 | +| `source-serif-600.woff` | `@fontsource/source-serif-4` (Source Serif 4, Adobe) | SIL Open Font License 1.1 | + +Both licences permit redistribution. To refresh them, re-copy from +`node_modules/@fontsource/*/files/*-latin-*-normal.woff`. diff --git a/public/fonts/inter-400.woff b/public/fonts/inter-400.woff new file mode 100644 index 0000000000000000000000000000000000000000..2f21ed484182ce5eeaf53879ea2bd2460ee06324 GIT binary patch literal 30696 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 literal 0 HcmV?d00001 diff --git a/public/fonts/inter-600.woff b/public/fonts/inter-600.woff new file mode 100644 index 0000000000000000000000000000000000000000..323fa679d99c6d019afca278b5b23fa77a1c29bd GIT binary patch literal 31260 zcmZU31CTAtwr$(Cz1y~J?zV03w$0tPZQHhO+qUuhoO|QF7x611t8$Jt=Exb9Rf$#8 zMNU){01)8kaK{0F|G5A{Zv5E)BLATO_Ye~n5&a>k{b12QWrl*0LRsv$oZ=650sw$% z1^}S?!RyyyD5j_^2mpZn8vp6^=vy2AU_ZWqlt1w~ zvPBu1{jqg$1OTwV0RRA}0sufzkRql(cU}861PIm#01&kMTn#k=L?HiLjN-Scra&rZ zFEdFGOivkL3_)fNOaM8Ld&e{#3i+H*YWwxDAQkR~ysF3iChtQNz85HAVCoyp|fa_XdB5+EEI)cdgX||PZMWgk4nDzRUk-_@KN|W_^lQnjH=k-w5 z{o?7m^7UeiWcP9eGO^U?um~}9azeD!VKgN%T{x=eMh68LJXY6d zhRgM2!XK5oKSr3Zv0pWgUn6mMCZ}8Wf$g?JJ=pf^_8!1*wq7`8$YW>~1FERl z#d~@9f~9&no-pX~DSOhYZt{Cl()YRK*1#LeUHvSm`Ofoh=JT(Wbo zB(~tQC|253#2W$UlHX@pUP1@ zC3%zktp8H+sp4C(Jy(5PS2-WZd$1`NZpAtRmoKZ-gkCP-nzLT5mT)O(2H)Rpd{A$O z*V(Uh({ahuoNGM!a#Vk&{Ls7!Zk_}_hJ(o%zkGc1V)tV!mWa{MmXVd#chr-0xUq3Q zf?5G^5K_`hu~YCuzfEhV3Z9Zn>HF$P3xc_~k#R@iq<2ua3IG-&^Cjk}?2#p58;F4L zXm5PbvAv2w#W(_akh*a|jO=_xCJ%_Gm+jkNA_)!Gib;pogEE9N1fHlq#(gHzLi0+= zyzDY;%K_d;oA~P9xQFMYXYFAJc34B!Wtylq(iAr4gx*Yn^KfXx#$7Q@A2DUWm^_u= z;_35D!k&7Cvp3}Yu>snsexkv^G_eVSdA~SvO%L?O#wntjkL=`t&4tg0uV+H~aeicE zoS3wX(a$m;J1zIFcc0uAf%~DjePJ<9+%tSi{3x5FXoqhb%J2nY&+FUDLf_bMJL);7 zd9>quf=}&Dei0g_xWK<>jzo4cEXE-$iVClds+rU58wW)sDc4CPjiIeloEtT3iL?>U z+UuAX9_H1Jjs8Ma98931YziY*RmNUasVbK(s~0Y>;#YE66rx~(!C(=L9LPK+_{}5` z$XO~|ih&e55?xX)pPjcQ*7?_c^*e$3d)#clQf2o3BxMU}!{b^W{i^dl1&fqdOV(h_ zjyPIK7u0jmgW46AWPNmZxF)uORBH62eZrG8Ufyu@4GgHsFa{f?ONGMNXx}+|Dw8+ z7OqnJqKbT52?Y1BX6EAN?%`Hj#YCXbt=IgjCYk=!P)26enC4}wk4e-hlW9Km>xFe& z&38xh)ETSRcTLoNmLvBb;rE%7g#Uxc9Fy z_{qj&Zo74{HJYB@Jh<$95da4OAJ-EQ2oOhvi9~=JMvN$#2MU$LLYf0uq(>R7&uL-+ za-_#4Duu5`>$Q{f;HNkTPAiV@O7r-&q&nyd4}BMGKHB(}2OyV&6suIm2z7dBNH}(8 z{#fVUUpM8p-5XF4D*4J~EJy;jBf&V=>~yy$ft?QkB@~{%8+x64lMa(i1eTS&E5g1b zjG@QuAFQcIi#wqd2(1MOj1yL~XMx#d%(6Zg%x##SJ^p6#R!74HVk@`{IQ0k%%%G3x z{PeZQv=rZphXd7EXvLCTPOt*+ul*`8)aYdx3rRQ(1rT|c+PIkIvr8hIa4RfpB&dDS z+UGNL1d#ta#V~9h)d=H?tLkD5wqF72jdW5i1)@_5?9+nDM zeGeEiwt#(!TVm`9-g_I0n(elo>H>IQHDC*YgvUR_VRB%@4$Ma^+rkx>1!ZermG)J@ zr%SSjImI$?rIN5y+FhRCPXhM-94d++{A zD={#R{KiT(qR6zWtBezhQnnvejH8fNq?}JJMKNKJqEBBf8^JkB8P+<|;Ei@mqBF`R zLaSA)3H%jSpHS#;OkMwGAKycwqTZFp;@?=IoZ(b~Tw&U|$W|ktGLKfjJ*JXANv6v} zk;4xOMWJLKCS4myrNS`CQ`W2VbVmDBE9bN_?ai|1dz!RQ0InR_kE3GER@K80Rhl8i zb_hbY&{HVY>gbegGm_px;~L_Es(yYx^tjm)g`MoUFKL70us>v@)f-vfVgtNf$opK?bU`k!TjD*uIli3R;4q! zSl-Tgh1<$(6LBtj6&9XkKRY5~*=}ZsPIH}CsKtDg*QnJ{8eY_5#)DpYQN2uRqrQlW z{dV41N0sC>m{z5>tXqBP8o&mUecy`ErfDZ({CMjXX5iIFZC0#t`JIn<#x{(3{pi6}w9;B7B#pN^%2Yn{w)FCcS)D-6rIo8)XXm>0C#I z)q8sA8ZxS^fj-qHEFP;XSM4P(YL`m~!j{rNn0?^mrelXef}tpIIMsrIhTA5LVU&`N zanT@c@aP6ylRB&qKP;3aF7CG}pSe?1O*C$WVb~*&`R1M`mSVC|J=5fr9#3r7GAQnR zt%<3Ir^h$i_>)<>3WV~qnDEOfg(x`ZY&xPe39!ddj0{}8S`Vpok|&gO-H342&Cdc6 zrhEkAezb{~Yy$lPKAU3C>?J6E7k!*FFl5Du$vCu6!XOuqVu?(Uw{(e&W{xII{Q*uD z6LE-X_RT7Mvgryf9B4eeVjESKQdT2n0wVlxZlw02=*meM(%Xp2iJmGfoI%$lP4g{j zdT&|zM^+YDzw_izV+T}2p*Jzg_;q7Kl51v(k!|tjiPY83WPBZxxtHGa&ayFBT}3G( z;F@kVmCV~!D-YE?bR&(DS=TNKF}BA;_b8fn_^W95)`^i{;@kzyi7FRY-RWP$og4sC zaUl9Hd_Pl>A&@DTqt^P?&Q;)4leKj9qA9lJ`qHaSm$Imc*x-ZJ^FxPQ?KO0RgGpOm zS>4x!n=)5BXP9;4E8?`J)M<*Sm`=gCV{RUf)%s>aL>DS~d-4!fYhc_{0tu;P20t>L zcQXs+Goz>h*mS~7ieRzoB=N&#Jwa_9v+^EgTL}p_?yBexn@OT(IdaWJmK4bIJu`4( zaDakHrppUr^EWjpZ(=W1SP#IuEEB3G$S4Y`x68Cg_E^ND2l)Lx*rtD0MGodNk_C2_ zNo+AnQyhu=9M{`w#SseD?2>9nz6D!m=9o2Bb7w1!x|NEG28=h+xSI5c(df$Dsb71Y z#pe_GI+kayc17c)M#WWweyIr#9S7VUsRn6*TUOA z|0PNL@arC-b3=LovP`Y{z$d3R6ZjAz~ykgt6Y$Fn&2&2N$|<<({-Z zeA)Ii#WPV&NN#HuK7ZRu1izelO$}+U*IxMi8^+Be45KA2k1}TFeTh zc2@mp*4E=Uw`9QMop0(AVW>+`82;ogdvINHrIFAYSw^aU*%Ipk_*)Xo2h?!$)A;JE;HOrm$7(rsENUv<2FT4na+)Y2RwV0# z4*Z%?fk-<3oze1iyBB5|{5$-1Kuxou(QLCm=?gp;Sje0IJ_HF=|5dI|pT#v`V*Hh9s@xqMU9yRPq}67=(n^1`m206DVf7Hbsby zbOsfTYc?Wuq|!9PQqxx)Tj#zQgfj77BDKW2D;1{PsD0=nE3b0;zHy7%#h69|kDL?^ zq9&oM`s1fx^s9A%F)4y%;Am$&zk!&$Xl3EE)7?kU=iuvFz&j+#rGQwWf0@$fY{^|) zi9rsG(R^Y#u>Mk(@Vb?y&D-l@t+eba6meo^1V_@vVqDRvDnzUFH}y(1sxvR7SgC-* zX4h|YdJPu8jG&i?q&co56ksu7BJBuO^n><)rs?jAcT zw>3@j7FX=9;PDT^7DPu5$+xT(N0 zOqb~&sk8KPmy@QME-Kwuq*-SvJ2=U^Gz<)6B&E8~+uxUJ=<_ODDxDet_6t>yL}n7_55nNc^UWdL=w+K z9;FqAG8Zn0mv~omq9sTNYDQO(7LzQ|qfb;~h_SFZ6~~2r$d9!0;ICr}1tRyn#x8${ zKU!^yWG`e_SG2k&UK4Hc)kC*ih7?`g=uzcQ|-n^6diz?-pQp$m)oWc7%_Wi1t z*k8{9;*kdBoNXxjRj5AM##QYm@p+x*u_ynDr7P#+3)dKAVvl(-R_YlOWb8 zK%ATnJu(*#XU^lmnq`hOi(_OL#I!OK)@07J#+pToG)os<$cJG~$V*S?4c7Ums+5ms zT$78tSpdU|Qt(fem}WlKW-j*WR2JhLp&dd5IzeEMFzrmR%2xf=ehs+#3fHO!(~tyr^Cvl6Y;tXrvB3D+?64G~&B)XWZx!ee{= zrxI_{Cdj0nn_8u}Pr}N$LT=b^KDzdb>1@b3VAq{4{Cd8XtqJEBwON*;<&6R zWPec;#8l}FUujDY{#^1|{iCcqG)>}39Dk#wyrHHeOWB~xzPc6#wehbmi-1xy7s5Ev zV1!m@Mqe`n7g|f&56yeN7iyP-n3{XW!vI3RyAzu z#X{Cj2%p;MK=HrX9&%qjo`L_fV#Gi5+iQi3h-Ih1uZ$0zy~R^hS|~hM9#%VTArw3x(Cz zNUJPBJ<**!-SfrHIHI=;0q5QY4ZxE-0HP&=de)XlGsH|X1QkRK{&S-HZK7!qr((pA z^v9DTskRe`sz%`u8B&N{8nFv5*6g5+>VLx`LVygXOt#l@%l}}i?saOJD?gr5*61Yy zo)~2uxFGMBrc<}EL6U)HWvg*a!ybJc$RLoLguG}pxMnoxOF^`tiv@`#!XP{nrGa)p zhRUT6%VST=0nNAx-~*Zk_nO5AW`HpLgLdqSjkTPTP{?LGu~=_5t28~gt}}wFesOvM zc@cOSaG7-pAohjWB-12AEo22b@;Y48RdYqe+G4=LFo&wG)SxiiqYHunPn6CQ z3FS@pJLt%4rFGq1?%m&GXUs!X+G6hmi~mLZ63 z-q4loUF;wF`uv6#hW7jlcID}#Dp6Ya#csFquSu~;v92|Ot(0smE}(_$yTF?v&sWBe zooE1%y>>d$$$E&xwf)+NXb|yen2}I>@@S}%P|1=A%91dPk|?Z_zZxZxoF(DzCDEd0 z=5Wa>W|Pj?4I*3(V&IO#WR9Zfjv{Q1V(} z{{M+|&L^_y8ZV$0L(HpG#MHb8h{~#l7%GnlD~}k=fC$Zim`}@PFy5{Qwv&Ae+iRhs zaCG89-v*I0HP17D%%0ajKXzsjq6vu8P+VKGUBbfM!~P+u#NOA^04!lCl6Z3#Shi|8 z-4dGqSpLYU)<vg`F{)xPLKAmUHSpDsLR%%7^Wkz3-wjl5Y{QEH;e0bMaI3 zmEy6QBCZsrpFY*yS)Yye){%A}s%j6bY9GvI56xyD(EOF?0C#I2m?c3J*y6mm-l1eo zLw^3x-_Q2662b&5dmoz9=vG*#A}&`AgXOfCp-~=?c$1mr=QIwHKu}@{CnmB}Q&SlxevbXodqL$g%d5`y&a^8p`d=UkwOQ-*ERE&S z2A-Q!LpZ1~B2s9$iFR1{KGH}C!2nVb?2y#1zhYIn}{2B7QSkK~m;bFV<|D5k!=z)C}y!oFsomnsLm&Y&egYzCQb#J@{Yrb!; z9W6OF&;3B$drP)ppGG}Sz_jAkFxL=1s5-${Q3{o-puA)gIR%x^ zimuLbz0ayGjG6g?S;PZar2AQfg;)fM7Zp1fG`$v7^O-danN{|gbmQ>RH6%Syb|9yGi-iG@*g`Kc?+$gi2_&$+| zw`l0DC3hmhQ&{{Mk+taHu0wYs0t2aI&(EUqw?RTvqD<$pV6mW{++}qtzu0D8=z$21 zTIPeuPAHGcPQk$1z=8Z@PB!iM@Smswu#l(UsVh{6+I7U?(kyhMiCiSDM*2^?f3Hf^7nn)RZ-C zmDI38>Vxi6e7QtTvQm&P_;TM7h-E>a zsHXJ44ZrstDriS*;gb8ma(>8NKR?#>-*9*<#^t{%zToJRkH+$VWbf-qL}6*}I~+*b zR}k?l5h{vXhgv4G`qr(<3UVAmEEWY6ICV`Ghu|%SKadd2V7vEQ37a^2PjQ{$Z5kF4 zEBf6k3cO}|o4yfu`e{YGMs%4_&jEwHrDa2pfx5$;%FQb6U^`*YHOJVD*U#C;R1wUu zhHRtU>Izs4mMqDDJ`oil-|+15uZc@z6KBZhO$=BR4M!pI3gtxf-yt=@t!K+^K<*{LKgb@Vl<1qVA?Bp9J`HIbK3}3g$;9K-=Ky##FX#xWie?965A_(^f4Kr z=fU1Kp4=;k;-DO^M2{fRZ|D=cRsq)H2%#DR20Z9)VwsB(4u?rrtF^rV-Voy%07bV< zsPq0Y>qU4bqyGYteL-e_Ap5hH-NW+%xq&Q%Bt&-8B?OLN2A-*r^GmBZ--(&+97?+v z{Q>(ki|*`K=Y%Sx_(=g!o&#JqeN|&v4_fx}{Q$Ez9<}I1q*{3d&IcV9o}J&ERdO2bmsRN`z>~nU?e*7@bh=^Sh`4vF``$UHnve9;2;wDBlBaIt{24 znw@pr3Q<%FO|HhC1r^i^tjIg+4xk1GIt*p1Q2+?>3=hQ~u}w=({4u`(-7t0ck`d#P_CivFX+mi zA#`@I=(X8QW;-BFub-Bw(~d(-TEWc^Aby-Uo~q&3G&XmeyP(s}Vx6ncXpLf>>U0K+ zyWNN~ZdJdgMYiwX-KXRbj}h$PCI>JihPFQ&vTnN{bRyCotrGTEN4|MG_#jOfV`E{8 z_;^YmRL>}UWUii;Pb7SlujVpmuifa!K7Vh?lPn$-u{xcwZdQpss_jnA9JP1fxufJ) zRHuh^&7wM$uXiAUQ=g|w2SWk%&?lGr`syRrXC)#sF&nrm{>8XfZ;q~mD5X(gdYH_1 zh?9rEc_YKo`oKSgJ%ruGKcVQO3sen457gc@*Qe#C-9V~>x9o4- zJ@{k9PJe-y6yzz0zzqOs@RRj$p_Q>_`|k$gzaP@po18y0`EE76d!t|RwhyR0v*d2& zd3)O3ux$@mIx^62wX%BS-Eg80eAw5~J&fTm(lTp>n);VkkcvfxE&^?+|YIo&SNH>Vss5dDkPe@_Vd-O6mhj zft}YQitzj1rNh_(<__Tc11a0Xy8=|M2PhrDKo8;0&t(SGm;;0oz#RjrAV3rf8oNXH z2Vz&z6%{W?b60^K6)s3yUr`MeEl6Eo0Rt5*NGDg(>`_QsU0Om~zQ?%}Js!En+IFB| zuwZ~>kfd*5cmPnZ7ET%*5kizx;<7*>&hr+j#2#^egBbqc6ZuY zxvAIJnNQX@;F>cARG#V4zi&w9!~ec0?o(qjjm4Xd(QyrGL7*wY3>blUW+9{{;bY`~ zKSsh9#6wRYsJ!oFwnm6s36Bp#FBqM+xKl~@jZz-sZRTXtQrFrktDtvNbBCPX!HrLb z5_5x1OKB2w{eRA=HZ9M-bmqGDDqQ86i&K=QiS=U|4#({U+I34t=^6inRBF$49aK2~ z!&3fXp(YqXDKja({uU8Bg!|H!!=tn*8Oa^t`xrp)7^dPL1l|~8(Hg+S8790KgtHPI zETf+EpO|Wpo=Aa-2KeZK)#`!7>4CH2mmD4N`RG&i?6KtT7ux7`(&|&s=+VFEm$edz zUQ|yCN{v5FO{m3;_1AszHEjuyV)ye}F}NxOJn@4w^8+i1yo<~B{upBO9KhxtCf*o? z)f(c+7(l!jrnMRbs~Ekb9^050Ifxh^A^F-t*4e?U+ChQS!(P%uG||f_-Q&u^L@CQ- zh9YKTBxXS(W+ftSc#*(A4OT4*>!uN=8=V%o=ykU0`$;~Co0zVUo(znbs=4WB>*yuk z=;6TWrM2o|oaiNq;OBD`pkn5iNf47i5EUg6(-HM)zl5fu$rGveT@in`x}sJ?!^0xWrjk>yl^3JRxY#X* zpQcJOnJlnz8jU?n7iihvmeoyp`z}^3*+lIaPclx7mMKUb4}mMsW>)cw!{cE=FfUCA zILwSeQKo@_?Qx%hY2o;{p$AMwZ1)vP8Qog03Gm|GHRKqWTvOUFXXUPj?)D1rZ%B#Z zXos0e(WxRs7SRAJoYwI73hw?$_FZRu1pkfg>lcTEo?LXM%DU*bbAabXX#d(lb9R;aAI=Gi9!c=q7MbxyQtx|+Gnso z3@k(*I8^o*K3EAdTDw>qwp5A4nP!#I^>XQmI?X*BMMne>sZ3CLwx9Q zqbG7>U~;3ca%1RnqnBr6{BvV?b7NGSzUBb>btu_Tkb~6O4OJM5_BibOR@WM#2d`bA z+Hh=rge$iLd7P{CJdm^1LGY6eCd7i&Sa8s8>2J0`&)?w?0h`*0N-knjf}$#b@yOf= zdLQXve&vLl?wjwU@1gHlzA?U4zM1W<>@{2U=L*}3@m{963l#;b9nI@>OTNmPS>AVP z3tmRHNjHau3sztm4=*x_5z~(#{9B_NE&(YOQ+e(__?D>plnB2W37)K8dMG)4j9_;1 zzkLiY5P$H45nW;W#Wl@e5a0*B0s8-JEbO=K1Pn8JM3ovr31mY{D@5vyzz^9&a~LJ6P+bI#w)}k zDh&G#%a(YULbdh1kM4cSRKgj%Z%NK4^9t32;Uc&s&IPFNfW;#f^bHAnml zX+QLgqeAj&+$sBvgZv8ckc1k?sj|<8!MQaCcg_}`W1q%;vw}OlV3qtyqqQR0Bs1Q$ z5hE}taY~Zb$ykHraxW(Jh;rDf_V_QG6bD*~Kjz$o-Q_hiLvio}7Tu>21=*-e-U@O7 zKG}AyY*0SWcRukTE}>(KTECWC2sx+P7UV24?kp0lJaVEu+F<4;W=RF`z-6I@yX34; z)#kyPa@FUqVPNEA{ku5!rRB4Wbw~6Y{eudAt|V9cQeFNKuGTl%WnhET(c%~;%Q874 zSo@WMiiq2C8H-}4%;9gj$EDweKXVQ4sb%>VX&D<#{(ZMaPJ#-Ni@G#iOq7G zO1y`1t5U3?@iJ9zULLB}Jq1}o1vCXsK|)MRiJ30Ci1&f8$>uF}l^MfiLCMi?;jdSg zctHhJr6-^=nv<}Ouw%PQYuZu=NksTY9*=aS)q`~9)H?BG`7juJNiuB><1rkqO6jvl zN&$;w<+!=kT%)RszlVt=N$R9ZOCWp0$n(V&NfuDzhpJaEQ$Dhg7$kG?iKQ{3)3QfG z5(xrsN`g)cf>I3v9*%9jOP9sD~*ldRZ?9@Gb@=GI~L^hF~{_)?MN8 z_Y^(-vTvxq`uJS;-nj7~$B*#)?Sw#Z8kpAd8Snbw37l#WllCmIFkdi+#A96C&<})o zQ7ld!(%CK8@MTvf_v~py`~dj}qxV3Py8#E8C}MtcT@Cg0oeaYC^o;fNSWGy^dwS@h z1(`C9)yA3e#hGdtnS9BUz`%kX38=yWq5S>50x;p|=|A45y?3$^fPsg#izr}VBJKt3 zsqHF~eG`Cy;Q=Q15omvOL3?Wf0r?RCYBu=H04$JC0B|+USbnmZ-Txv`GIBDy&%N4z z4+cKF--jZs58oj$Nk~Yk5)3NA5{3$B5J)LZ2;6|jkb&#`Bon)?IYj{bY6_r;OlM}V zIde(;&vM#&-8p@&>GF#A9sul*?+p8irgN$}KV01oIC@M4z11Ea$5O&TtM1h8YLc=q;nTcy}qwWBGAo z(P~c}l&h@ki77?XqgB(Vs9B*} zPsDd=w4s^^Lc@s^rlknr{Xz0ne$rt5n8sQb#%7hV&xEBTsHK%PHBwbdgpRRERLP)L zty6xD$x8^4wiIk3uHHa2K;!IE#hdjGIH>nM=&y^(baLv8i&a{%pv)jPeSRkfQ`1{h z)1aV9LH+uvyc$%hmwLq)qRi_o27Ud=U41jOhhW}Nt-%SXbWpGEbyg}XO*J({EDe+= zZ43l`k(+FN`u$1zxXU0! z=WJi)o8?)$5y1De7mpI4BSZ^X7R1M8`4fq{GzRD#s(Lbwml$B4nf|aDgE7JNY_F`X z_w{W^QzoXu_Z}$Qm|k9%Kh!oSYf|mvb_BVc|LJ*}CKFX1gw;*tJA69-XdiKwaS*n~ z^1;O%l7_4<-^G_)8}4G^Xr>r!fluzVwm7W)`<<4Atd3~Jjy}pg3Lk;HGCqVxrD-B2f0RE=eqLg6wc0jP%i#RT zckT08jMbiEhge9KT!@h633qrpO)p-W^EP(qjnMZ!fER!A^lKB|uKzd@5-n4ke6{ewg+!Z&XTZFK>dn)^gr+eMgdT$__?StIhC)wwD82p1P z#Ji&9dC0GQo;iy!9}wN4mr1e2RJuRLo1kMGln9uJF3#kUA zTR7$~H!Zq5ZjnI^&wrl4QlUhDCxsr)wzVbo;jG`qdyJ1*2B|l;i?;NFfz^R#4ll(J z&yV+y0hnSPBTwIfwqEB5cdfsD3~fspzSAYlrWQ)l=8cU_v5GF8TzWrvGuq?7w=O`& zJ}~!AI#*i7#si@84xIRKSDqcboGs>Fo`kON;g=v08{#Z}p%X#mN+M39+^V3=RVEgw zJ+!{q;?50=#qfSBWfGXHb_WlCpUM*Prz{^pVT|h39K$bUIg*@-dGtm@w{Z~E`PAG% z5$9iiDAqBFueG7ra~>QRij)>Yk3BK4HQw z_t7zVnSX2hR?qfjI~xD~;M_4ZdL-IfTFll09BN zqc71O>O!x;86Vf~y&J)AYKR3Wxo5Ix|LeqdNPpV)1zo(mte^$y^vq&h)i)GKh+wbVle0gq_QPxh~+FOaKLpesFDY>njHSDxH+hU(1h z2M8CoQ?zK+Pv<7g@uZ;`8FHVRK^*x86;|U{RE2`1M$?%UC5-76ir61S5$4m^2Wmq) zY6XEk)jH4%K!LrrQ zvT9w!Gc0LDV3nR{-cp|)@8BGcZwZp;jbBeb&PPgJVw4~9lPKz1={ICrL&_Tqqa5~P zf!H1KqH{|{uJOPqPQ8bFLSg2AYH`C`@!Jc7s**)=MF}U6r_iI)V3PK(G6arrI~+ac zZicYyQt)#0FS6XyQYIx0&#$XavDeRN{M+~F4;AS2_31_@KKzw$Mcmn-yF3SOK{fQI zd`ZM=^jp@2WFu-{9|AYsGI$kdlaEkGJ(X18)e06t|JhG!V?8q>DnVaMkj93X-@Un9 z{wgu<@k$CUR-V51;*{^Cr6?u(v>Z8pZ)6uyc7XD|s`#jWypmqPk4_O#6ZYyh0;6d& zh}DXf_8d7&!9^VcPD0?T0vOqk3}-BJNbsl+m`LVPKMl2{}Liz^J`D< zyU(6exPy5gmgoYae_*|Idn_#|^?pTfHEM0FzVW%MF~YmuXUDVxOJ4dZ_XJh|FKs#$ zlW7a?;Z^H}c#iqF-Vi{wj7?t2=5l&%Wj+h2uLiY>Mdu!Hxy;@?2PTBQx1Mc%bv~D2 z$aD>BgW`cj1UXO9HnXylV&coZEa|g}6IuGW1$5sVNGtxixu~7~by@$_X!Y7$Pk{VE z?LM^BM$Y(sqPBp4+LtyPL3xTbBfbp4G<3Si0)V8eXbDbH?mPHaSjfM7`Fv*)gPief zjz0g}YiHI#>uHf#GRkuOf@GaPSsI~^kYIBt#J2r?oBMR^yNkAkG4Oa|dg@*`9uHd#l z^_D>>qeiFUbM?n$qDw_rd~VjO*Xf#glcsKsTW@O*Yc~aDIfLfu*n7uL8cAMtoNgU` z&Fs_!^5#cpZrvU*dV4Hg zkLIm7ko^h!b-cZkDIJ35Eij-ub$Q_}hj+=%9+Oz2mD7sC0N(?s&kX!E z@lz}G4!wwp$_!78oJ1hg93aqqypB=+P;c$BTDq8XO00O0b2%yulWf&X$8Vs*_>tnx z_kIT3gWow7cT1c52tLXxqC*}@yR7=EIU(YMRL^9K$Ce^dD6Q>uqr!3>?3)rMN)&%z zOUo1zdbR(G*A$|5baFB>S!(8=!e^RBfo*KU({Ln>zm%h(ddXeLzN*k2^t3HZe%}b5 zq7nwDL~fi$k@?tB`sXgBE-v;Rhrbv$g8sn{j@7X+ zk_ww2 z4OAP941{IA9RAZp{nQ2u4;K1edkrnqLN8A_X77!=yU`| zvFQQv=*3#t=UbR&z}3Y4lOu_r-T`ilCzrJpGjEqzBJ7%ARd?|S9UrQnWqmz~E-GW#DK-_t;@ z$uV85)Gdw`ZAz>aMftrGMv=#hsOpPxp|P#Z8An>}cjv<)QS}Rn70Ly#4S=mUTR4g9 zoy~mq+Vfh8FT&k7&=87j&E}Tb2n=Q;7m1ZKNxkOs#DZK5<%mQH) zoQa-o=Txt^ZzlWH|C$VO?>$WJVT(<6Y3Mps(pb9x-g&r~IPL9SG}(2Fy5>(jZZp(5 z!09?4V93h;cH6zW-FG>P-q6wMqDmgA4^>6%BAB)(G)WuH0PNU7b>mO_7RK}Hy3Ynt z=%y(D0RM|IaKJBI=wO`2$w+fB_fnJ4sW8&WY;b#!8J(7ne>t(u%G7`Gw@dMNotTZx z@82?2$2WSWon)k#k?S<7L{E{;F-AeAR~Lv-$r#sugY9Gy>x$eXWLq+^yky(ye-5C@ zhgHi*z6Z40!y>-q6h*Gs@=K1g2PJOd7ZYNj%^=;%z4b2m!k4#&kp#j=22>XqnVnT2 zEeFW4xHu5Fl=G^E(|eP^&6{fqI?t%yIhHNXswc5FQ}oKBTdKNDB$&XBq~Rf}rbe>x zP?W4$@plc$5YmuSk3%=V9DRlv=sxZ@v=0y0#g49V_`F>R6GQ&J9~y+tb7mI^9K-%R z;O67=<2YiDjPb1zmQQ@wK%AQn;*ghD?E>L~? ztj>CWxd?Y8s!u{R5pagPf=jyNEswLy$u}&$Fv^cL2_n6l!1UYCc}w#VOdKw_LG3OL zD2R5N+C=eP?=0f`WNa&xq^;ifkbPoMEYC_rQQSQO)D>A zl7QTg<+u>Lc26K7I$+gs;rT-k`P!Y}#UeR&mO@4M#qB9mM zBB^(*zeV_vw!*bO#z&q*MH$UMxxBa!%C)G6t?6LFvysmxm656>&tr2uzqVcKMiuZ@ zb(ywLj8%64|Nh*}Mjq7DMdBT}EBdW`-$j73A%aRzO-9)Xaj(T6oeKlQl&*iLlo{$7 za^Ff1!W-}grc`=hz0Hy03m8+v;@6&P+a820yB-~4vVW%dHH+^QqP)Vl%B;LM@P>rF z*nnocKcp-r$Vti)xg0)XLzrrAXP)d+4sxULvIABtOF<%4P9@P)PSx})P+2`-e~LzY zN$mJ(UMqrx{dSq+8d-1vY?vh**;Z&c7;QnfUaJdBlE>QRw5SGTZjvH|@gD<0#Re?5 z+tCx^FUIa_3>)gI9Ow^%NA8DeyAL2Dw$7v@pAeQ>q4KANVk7CFg(V_E?ar^tcqoYf z7jrm_#}whDf{w)whc=_Gh0mqzs&&1yE85m74vC3Acn(;I$; zIoK^Lkx9g1PKx`58w@amP< zOZJQ%x2L3J_xQ1UN(g_-htAmxnw_Jw2bGCvTN%l4M|8Dwi@7CGD|BSMU#)riYS%nP z8EKKz0YMikDhOI*X;wb5vY%-Lk+;UoWa@h~mvNq=G_*DO|DEj)Ky~=Pw;yXo z3&RXcw2^Iu_;Y0f8I>4;;{5LQ?uFjGh`}kuW>3N+MrlpFQV~17x5TGme{G_#Ph5>A zhL4gE#xr_yAu*g|T_eKW;$F>&Vq7D_UE*E}arBjtBr)CjJ?KPpMpI*jxmIE?*QERC zm6^4OHx|?Sxir;BoK|+v7f6}oT8TYP_VH-#^d2IE@}-gSsStU)G&wBbRo(JHftY3} z)Xrjt@~S*s72l~3d24Y`D%-b9{Y)#1xwLVP5Z{mV=G{9J6Sw|V}FzAgp|jqCkZ==cLS+^FVNWLy#N z=z2z1-fWl~#-OQ-a9`y|!v+-7szZy}o*7i3HD?6atLcMA(IN<<4nuOx?7g<%f`5;r6&1%h8jEBY_1>wT6%0#mV01ooWEC?iKETB z=@YjTFHjk%%r^utxG@aDc@>dO#5<6^czWCh)pHkOBPt>lN@fl(Vg>*zXx`9wAiVLq z+FL6Z-JVxHZq=JrR~H@MIH6(j^xCwbY_IC^W1G0dS;tE9-kvpm|Ij`LZJTBu8~t@< zesSIBfyMqKYo`~#xJL5fuE+Q-$#}+6lMmA(fS;w5M5vPF#;{r8$Co$djUPwfzu18;tG1>MgrBr+k^C@_k;+2j z!O+ma&`vQO_2ajq3jXdQK%h)+=e-Dr;Gt(SQU?wgFfcXajQUIUJ?8}cN-nP`y)zcej zdmWkbLv6;c8RM7FPi@obV;C70!VlndqkNW=RlMntUy zl*Dr?BjS=fBHqd@V|wd(bUO!pQUh4!bTKNF<)W+=|9$T%Y2fgEVwyd3)P zq-Rb2=)Ia{_i-~+7La)0yc`*Jyzy&&{jYCy@K4}Ipe~+p!&uqL>#ecwl z%XB)NY%uoL93oX!bn2WIzQrAyH~ev_RJxr$FzB~e%iL_ z!K|vb=J<+?0ezi@2My=H8FDpulE=E`mKKQRFJ#w-`qvbWtO*RLku>Ib_%`wHT|B}+ zU%v41*4KVr=oW21YE-;xRnzzouXUay`}Cdt=dsLnMI*P4%H2?uzcG*GZiQXm+zshA z#uIY4Xois}Ne1R%O^<_&>4r<9PN^^l08W4Da!SvA$s{m`DgzZ9P}Ud_x*=QO(eua8 z(wqpZT)k7^Fu1;`qNZ_l4LuJ|2uO>hN5L~EEK#^dCPoH%h19s(28SmF+I(_c$JLjd z5BE2gq7&xyaM;gT_w*^5yB{18D;@OsLSx~4*v~_D9JNS|2a=QIf#$}@95qV&3?PTd z{fvkoz`N<-U1w6=u~rJ@%5G(7%@Tv$pn_PhG}l(by}l5tl{JO+(?k7Jgo34mv5BdP zLwB(;0UFjE@o_oJX!zR?W}afy?cD^H+6*v zYwe3m!=@!&diP#j!2k(oWfP!WxsJ@?VNiSh6EY(|AY|5HT1#u0cq^buRcyfvaz6!x znjgGCMA?Fr#5*7)$i_uR^9W;fCZ6A=Yv8e`JiIh(=-A}esVDf`sg7o!`p?Grr2krT z$5do>qT)*GXq%k!@bWz#hPrIq23rR^n=SGoRa0$z`rU|99eq7*)E2p|z1@b<>8~G) zjs9YgQeIJMWy@%u4dcFh<=HUbzk4yYaELhle_y}Sa%y<*CRhP168F5#c){|h*jg3fOP2cN$_^&zIJ!hw`^ z2-dI^pP(+GAww>%E-|G(CU#!ooH@zUBm5^st14qL--Q=HSh4o&+BLP~S0pU^ z!>sK6hTxH2!^T8JjSu|9uOvJa4g*JqHJmP;a&qd-0|O0=3fJT}T$}=)C+>AmrqgG``bB>Bz zCbsP?O5JdC-r`&H`tpC8&nzClGC|*V>hj^u*Xrgy-Z6Y;T+ED=z_Cfru`|u$_9F9- z&mxzr<&)0TW{$7>ertH1SHoA!R^6X>D``$@%Irk9l<@({bA~g*b1`w6h*Jm~>2X}A z9?Jf%p2ywS`|gJqW(-T^KStxtg9+_Em4sQU)V#jp{#U1j=_@o|zWvXoBbH`YENi;G zV)^aa%co{7Osu5aK57`X`_Ewa2cq5S z{zW~mRmX?Too>XWiXVbs5_Ia4Tl0*Oj#smoGlU$0xxv3=r|9pH{HYJo;^1xkHcAPnm9-b7t&;rj_TePjM%~nFD~srwjH1KE?5 zWHVCIgL!j%sX1M3rE+r%1xx^P;EqpB*fk}7ucE*mI!_@*^PiWv54E(Bqo$3=vu=;;L|6u&*aa(|w8c2E&AHPpAi9qOg4`xSq;FrlRM&Hgcw zffJ+CQygsh=b3~3Y*J!8;~c$RW#*GkRpc!W@bfn?jN4iO70CRJg;6zQU8DK;`wz;W zw)AR@fk78j9yZhlFEUY<%*a>Rne@jC0?mEG9Rr3tM7b+Ue09mXy9ZW}U%*E!%^_H8 zmbC%JKrf=!$0lf?XxQ0CCS`jqKY z(`CZN{AzMBw=YMf%Fb4)a^TvOuHV?-Vdb-?s|lUQT4|KLCyg?K`K&!In&Xih7Mbgp z>#ryX=DOI%_L+;XPp=pglqjDblb=sz{m`^|b(Vv3d2&FyhwI3wMaT1t57#xk9%L6? zlx)g7YU@zK*KoSrWI+z|g@=Fd;IaI~TrsyUy1u+R(*-pTUau8OOzF@Cb`JlPzjo{fIuBzc!59(37``SZN*|0J z6c#3A6O|=pjQ8}P82==(GGfk5{=q5aiFBLRqC}Lsxt2f5Z`gI44{m1=-2;}x7&2Nj zgoILIei5}@y~;n3MYa7oqK(K}4t@NDK2SZC!%kX$^(t!RAGAr{YI}By^jXS3V157V zy@K^Edt-Wtr*nz5b&0cQ$n-bLX64F@a}oUH7Rz&I{Wp6BNXc4&1Pm|owrN|*V+iH>+^2|I*V`w0O z1*BRf4tgHZT@3aicXFJRINgHlJ^^B1J**h|J-_AY%o&eS!JQi@>3*$!&bq0=)6?{H z2CS=GaI&QQd_(b~LHcJDK9LDlY5Vy7Z}T0;3k%O6i}zC3*V{#Utv|Xpd$O~|G-~;tNM$5#WwMQF@>{kuU`I5L$p_sy2K|k>&VhY58o*n zbC&<*;5+=oQ$+)8`y97)8H5{S7LT1!?;F>cnzbyQtnnvc-#SS5wJ5r;B>^2lX%(f( zKDnII(UdL++Kw!59RpmZFi$8`nD)=Sr68H#5Z$cr%fRDeVuR3Kou z=zfRn-QOqat|IH>C;WYMoa_*Ew!>sMg&v>$3_oa-I!XGIIu##$g@*(w5XO|pchYCi zb;xRf5$l3)QV!Dtjae$wqcoD^m&|wjOhvLN)FGl+*Uc)!B^O&z`cK_K6 zJ=)UnLXWo0M&Ce-OlU#O6t)Gm^yo50`DZ=bf)(nIp*OU#2k7|Y>G>gpJ zlHvvYe#&0z3}i3i%in&>dwlmD8TTaQ{rk9R;x9W=JvG*RULA+=^=R%LG>@;#MoVv? zC4A)#zJkuSZ@3nGPMo1Fop$)#-Mi3o4DJx#;u7dc2st1Gwz5QsGvN2~`*xyi`UmCg z;P;{IG(zq`IVgKKzXy%n&F|y)k{tUY+*P!2wKRtusNsO@d!mSy_Pi&Y(Zh)R{D|=U z{BZIbk(Y;0efgz&0s<^A4=~mO6=<3Zh)vGIJ<1NaN71Jls;H~nnAqklYPyYP@w0C8 zjqDw~<}+l-KlzORKV+cZf9n>p)E3mhU1FW-856Av3l&*Ns)R#RvN2n3=Y}uMiK$QV z2(tH6p>{TPNp}3q1TTMkWei5!<{r!ph;ekt$eMF7GbF;rHYJbVpB&{DO9n%1Rxu2{ z^SKGgaf(bAkWb-WXbl$*NHrjv06EG01fIJENDUyX$@&EMLM?1AAhklDW6ZDMlY4;F z3CKmb7iy810a7m@XW?FGheQg<3_w-_vIU!hre^_Z0AwBL{v~qY5?M@U0HRUCmZ~SS z)4^D@!Nf$W%JLF zPWH3DcnNaYxpB^%%{!3;e{1LFIdeAd`XSis9vRKfR6<6Rz-V@4G&@rq)`$OW zJc{LyO+?OpuJu7q6ZqpOW*mQ`&qFln%8{MBj8;qo-$72mHsGc|i%QD{mqTt7A>C8Y4Rtf^pegG6 zCm=sSsS2> zNn0_r9e0g)bB}j(i+6X6cLg4ScrhCxdC15a?;ak)Cxl?=mp&gPFjc~d8s&+`Wh@w8 zvN>ybvFq^o#9ZSK22R>IGJJMv#7DY=n><7F6-F_sgW_`1@nZj}BMK&X>F5{?8psU` z4Rn^rk5O1fOd78KxN=z12Df;(0^PoPoSv^jp#+(tVUBGBnf^1qpERGFFyUOY=5K7i zhkHS6bb*IQ0bWvmzG?Oa_+awvrVC{URMqhb)1y_@3Gvk`;E{;$BYT;)4vcV1qRW6h6>)=xnt@)KVdJ@dX&n-QUX4tRUgeO zYe9+^^b^1UN}&S|s6A9>_N{4QL0XZs%7O1tjT;tR8iUi*zIyN74{dMtPt6>W;}YwP z3{|6jLrPURINo~VDe1s?e-`%-@aP&CQn!EXHZlr>}Bl5?UX+(O-q8?6ef!y+bx zV0PI2{H;gd*z7tYJwD7m!rf%%(1NKUkrP5C5}T3Ze8+7_!$%Fx!hPi7!_4Z8%)^v+ zQNzs1ITkw;+ei=7jDpxnru4%$pX=VdDm~oUisv*QnL%r;>`XRLdWIQ9bCmANmAPDS zCq&f42;fVZEIXWf#8lWbyA@`okJ z_QSX(Y$clo&d`K8BLC}iVd&=Q{p0YLXk>&cV`O4cQFwYnNN6VhsA$Ki zv70i>GAb%c-efC7r^U`}(bY4$Cw8-bomcRN56W%?vc4NF;~7c@4^)+st5FtLxW2U@o2(g5eEn&GLl;=29Z z4mKL1d3KUcbnX38Sc9}ZPp36{&1d@9@Vw0hX-%oRrwxJ}W76~nO;f4LL*G~tkQf(W z8)(Z(5_PRITbfp$FODdWl-tj$2#AQBp4hm1_3(gU!{vdtzO>dC0%9y+{j|>+tH2a9 za6JDuUaR-oR`ocB73H)7IsN{Vb4D>pO|=7dNx|xL*Em=I*rYk>Y4eie{hZ_6GgcP_ zM#cL1#YF{qga&(g1qJJdjF0fl2+-9t8W$WkDj;xlNXS?tJ)OXGuZZy>i-NoqAt4IS zU^Ld>&CSox&CQ?M@hQj`0ojq8w$~txUn+75Yos?_BL$gerZuJLoi+$`@>JRB4z7$; zO%6?7obO#VF)D9EzGI**W)p+_6JzxwrbNk|0wyH|sG_P9)8{4)PTM|r)x~18*FMz7 zCo?6$Kbd|$xE{ZWkI=gi`YnhGa}stay1jkfwQKb^mVf^F@*B`^GK$B0(Vg!57`#_~ z8Qpp9HPB=Ra~|)-<6gdVnt@%^H?Rx3wr(B&c>Q{YaO!#o9cAC9yBzU^f>i4?aZ~B2 za@!XgJs{9?U&3&-HtWL^k@NZ6RS}E!O_@$VEW~pUl-im;Js<8cT zQI1K8!z&KV5352}^CM1tm|gXIEnGLZ>j|36bo}M+TH+fY?+d?%@k;Ve8BbPN{u9*5 z9PGZIFNywQF(9{jE6DI;@G5*)@)ee$jSPb~0WutKLhvmjfNTKX}OP?JgvI3BA0of@am8=DR zQ<4J6E&-X$-otyjnSksTkQr<~-Yda??CHJ=C!wR#L_qcmh&LdW6xl}*VpUv<=CY;4 zs;D=s;!^qozYMV}nu;3PG;O;Aa)%#6tjI9ZU9hJ(XsigY8?fg4GB&ea3?uX?=(WdI zwx0G#gC5T%UqQ z(<&W1c1c|P($V7<$HXok8?98ts{DMp__0f4oV7AL zdu3K~)~c*Mg@MTd#U=j9frXuNpnGpxOtx5Z*ULS`o>D9F`7^5b7NG4YeTC4-NP^j$ zQd9A%=Hc9PHk7tMq4Wuf0h=h50o_LFcO(k@i{uW_t&~pT_Tx#?y+HpTrLXY?ID&Bl z8m1tFME-&w_rnJ|xQF!P8Ay}`+7PQSMdMo4i-kr8b#oHej$hQ5BzF!TWRNJ6rRM&S zZm*hUkKS@grFe;Je6Ye|$PoM*J2u-bHb61JYB0B!oggt7>@ZkqKdpJN{a`nb%4Q;Q zBd*FXYnPs3f1A;8 z(mf%G{SP^tPaKJ*6^Vw77u?y=al#y zJ`29>XwOKu z18rD6&`XzP4f}wech;yQ1wOius15&_k4*6rpXU+MGd+B6ulajEtJP7I4_NSkU_l22 zqa#0L{L@-={^0`?OFm>RKC4`d&VGT;tx<;w{Xu_XKcT;$gXSWx{~9FgtmN|3N4(h1 zF6E!C!S5?qBc1m25$#B)=W5WZm#bx-Ymv_Ld|74sh-ZtWJNak8_|>bycaPv|dRp>o zT)kuyq;0U50&bjo6yCDtTu;Dp=XzIZdQWbAChCHRrHvt#%D-Irw#bbPG$-Iqn>+F z2~HB`54VJR?^*hYbg6s#2;xQl*=k`TQD2T2k)A$6Gd5V|AI8?JT7z_+x%Z4MYbSAa z?y1p;blu8-7#l3kFw#iLLo$C%5t2EG- z_W!1vhIT@K%j7ed)M_M~Xtf1wXHCCcpto-vCWUs~%Ilmt)64e`qbzrAb=Sy$sVN|IMjoFo;qieKbLt?J2(O~Z3LL zW%oDZ6N{o3bo|s*-c(>j>^nVG6SS$qEA`3=?g^U+Qd60G&p8p;`ET=1{`~B_r?@AB z?X{B6*{Fod4e=}0!INZ9|C9Jlu8)qcPfo6jiK$D5x{;5MN+s2an4(fmj)<77Qca0y zid1+hZE}nFldQ;^aC+UC+$>e;knm^Z)<=c-muNWME)m zVQ>=VOuQG*Z}XLbnS%iY9+kCEfYNh+Uilj>vWnv|kju=#1QG=ROuP*Zc-muNWME)F z@K1<=LA2!Om2br&t2h`?0TVL-oo5DPc-pPfQ?x8u5Jl0I2bH&N+qP|^*m$;W+qP}n zZQHhOTf5xP{Fq}$GPg0Y(05hG%?Cje!jYt{Ot!~e+2{FeUwVSgPA#X=3u`aReXKU_193tR;IlBi}rpW>i86O z^gU|%gQ?-hlJtjABVGHGs3-T!aA_nd=^+hdjMPup=4h^$W(Df)p*$qj<>Xv`m&8r%7k|dlUD~)^|bhH&MzAW_p?{MNBH2zqmtgl82<8okBmU z7yd(y4XtU;`}=?BY)d%Yyvr}92hXHyyNZ4eC=nO((3-s|&D4xBJnc@Aotf$^Q|+s? zG$+e0G<5sX(Db9VOb@3sU70mG`^+XOCpY>%C?_}i%FL429+H*0yidk$;+{9` z{b1kVIu~b|P}8iNx)0)U*5P`6FDM&WyupYWJprJ%YBuXEc@5&GvMY%G=_O z$j?M0(}{+clPuYe$IP?Ll5y_iU}xK(sEcri9mgVbH!(Al=Yd%=P8P`>nn?%aKyW~i z1b}cdPi%h&Pwdq+o8jbu?e7TKt7$;KPCwrnp2%moQ_dEgjac?{!x#ksfgaI8q7sv; z^1Li^?*==&k4359RoB5CXpeHd++nVzOYk*=(BA(5eJ|?3c-joX0|3}C006MA#;V)4 zZQHhO+qP}nwr$(CZ6*MK7XMEGbHEC)1snipzzy&Od;os}8Ke(#2gQRS!Gz!xj0aP} zim)#10tdh`a0XlgH^4pc1iS(tz&8k?{HQdljyj+|Xat&u7NK=$7dnP6p?l~R`i3!1 zh>PROxIS)&d*NaDDt?IHl0+mO$wi8hN@N6?LKcueRMIFk0Zl`5&?2+~twUSTE_47L zLqD)YtOTpVX0YSzGP}=H@oc;hugV+p;e0Zm&sX#9{4hVyZ}aE;vj~J1(M4iWR8$mo z#d5J(>=&oSbs0q_kVR!hSy#4{UFASIR?d`19(IVGW0%{b_M*LOU)%4FIPc24+OD}9=VrR4Zll}lKDs|%_*%Z1@8tXW zQGS|VS9#PCbkoMiNnN6;yiJcxJ|q+J`&$aS)>9|8L5WU zM4Blrk*-URq<7LUIf*RFNOt9da#^{iJVd@7C=r+wI2KG9Y!I9pTp7F-vO`5f14HLR z50$*iXl1{0UAeD3SKce%L0pg&qy-_M0|v5yf}k9z2I_(N;5K*+UW3ozH%tgqLJ5YU z1wEJ*=7mLJSy&a;g-u~w*cJAL!{B&011^NC;AXf79);)NwV2`J;b!3>;S*{q)mJO1 zjnppcR`spcUhA&)*M@83wdvY?ZH4v_$9E2PH(rf zN81OT1Wv$d>a2A3I;Wg_&L65!pXQ-OX*pV*HlQtOC)%41p=0SZI-jnf8|f~3nBJl< z=ueh{g;_r~jE!T{*gUq3tz+BRK6ZxPW-r{dt`cimcaS^V-R-`Qn33F(T9KZSYthWn zhSBlSBhjBcK2OdC4mja1&&YG~g1jWJ$ZPV3d=j7c{}FzhfACZ76D6Rk=3#N?=XfJ3r zE~K`g+PH{uUW|)BbxN4#$+(nu9*k?{pNqu>1yr+bJV`y%#qO)`xxppn!K=2GMwA5ejA%r} zF2cA)VUqR)Icm5VmK(>z9?Ecuhko4s8u=vT=nix<$uOfDV=Um2n`DS-G?dKXP;#&F zxpmG3M_LR2KIdH8LrQ$7{G-|Zqq%{;ikve0YIVlZvzQiL%5mxZe1}u*5&TjP)Mky8 zPOm(DnRM^>|7Kt2Rqvi*9BPK-s;x62@?m$U?-hmgSvddzc-m~iLx30t006<=yV$mE z+qP}nHXGZvZQFKZ){SlJof!i7>B|<@@CyNffFz+2Ygxkyj?vfv0}WzO#v9DwBsB!d zXu@Zb8ehrXwBbWCbf)$;y?g&*0zpyDN8x)S)cMYu%V5pU}Kw5ksda+na!z0ANsO@Ui755Eo^BkTeE@6 zwxJ4DZEHKK+1?I}pt>FHL=6Vo*)H^_9|P=aH@j2Q9`>Y`z3got`%>F})L|!&?C$^v z`kRBO>tKiQmLo9Gh238Dn%BMIO>cSIJO1IH{^j5P}h)bq0`^FzM?rY86=cK2>#acd1)q zW@)an_)v)4taENHw>yc;-O2*m%D=PC)h<5N6;po81#Zk8eoyw&1^NotA#erUsCr)^ z&$fWA?E`nlw-rwlW`3)e--JMvb1a~3{Gh3Gd>~Ha+Qv@^Q}YmR-~3rSylt<58^l{g z8P5XRP_Y5-#y^*7Tpl$Eo@@N3n=9jY&Gc>H#`sem^UvNEJYWD^tMniQ;`lok=)=DO z?u@@5i};xte^b-p-w9_0D|xBnVF*;)u$%GyOZm$El?(KNq4A$4K7#nw66lz*u720p zOus80aBDJsnd=C+25zw)YBo3I2rU!;ksj8xOt z<$Q&8J4UYGmI&^^6urJ%;8$bu(e^!~(9N|`>f3jKs1OB4O$Vcf*19>b-e~6mp)=S5 zzZ$-@do^CvzF)L9VFcV_7LM_zZ`lI|z{q&lx9ohes|#VivqCJY2SemcLiHl8L3J#c zw9N-m7lizo;w68T@sd5WToT3~kCjZwITaWjM3%)38J<|dMWS4(kjYa;4sj_nCnd{x z8=9Qrg5@cDsAL`)1u9SW&8S%$BBKq>r1iuq1m`f@@b$SUdfsT2;krh7Er~q6q+7ia ze0T8m5{JjP(~%}K14F<}Sy?tXdMsF6v4Upze;ubXc1r6~Ix?OU8E17`@l*OHD<=Gz zCp0jb)}~D3^1#(|kw1vbrY0|+WEyos+d0~`_EhzRMHRb{Dh=zZlK{sII$&ORV!E+u zirONbEI0<`DLzOtqL;9;Wa0jn<`*lvx*YxWM{}R`S2(}epBXD0VR=L_bDSjI$z*nH|2>w;?a4&!)~~v^PHx=v5vKw&iVtLUrck_8>PV) zd7TwTPB4&fx2#X)K)zl{M)ZD(4(Wj>xyUF@5=xTHB7a=Thz)l*qC}Dhmp_-5mO7=BWS{C;k?OD))e)Xx z(&lN-l}XO}G*m(HlFvG6>McMY8Ml3rdP0ah-pSR2J{flx!pV8lQ?LIhB4gH_LyrYy zTTuDU;0KopnAqD^dRHrEDyUcP*^h^K%&HA^U+IQA-vb~*DQWjJO z0BBjhWy$ZXsWvB|V`!~!_pS5$<~#)efSW1~{ijX!9lv$-R^J%N|IP@eR_-R>Iwt^t zn+E_e;KBcM5N&3xZv+5X)O=(9ak4QPYv!7LE5CL7-!j2>CP5=W_BXS3asvP?K>+{| z%x_AV70wkQD_g^FoXs}|WE=nhg}g)g5oN9K_T8@i`L`YRcP0SF13cR3TN?ua4&QzN zk$n3n&w`>GVrT34?S~@`002P=03awzQ8T=|t&$D`f%gIcK|4>?(Br@a3i9HV@}^pX zsa!oQB;7FGWk9h6nK>{46uchoQ}ielv%aZqS3^S7xaSILo^u;~_f7cTU_igE=V}Ra zqmEYyiMdS-ipS40f}%85S+Ub&mub1v<5zi8Cq}FV^`)p9-ENWQ=P!fBXmbT-yWxZW zCT4Ir<`yCPpF(&M6E_3gRs)kj{%C6;1f88uc;z=*udx}fHF=n#R(9|*E{0pLHCeCy zIWI-#{!;VJuKL@|m9}IcLL%WHX?SDXnaNtsqjR{os&eoN-8iNa_g0=9u99*#@WTg} zlL(F;jqfqbRcU0@ z9%+`fmoPCT`z|9hkS#h&GD;R${t(-~BWMSm#CSZ5;L%(f3OMNCbplJb^meQ)ak8C} zYr7WqbvzpwwE!t$#cRS5(|B(mh)wbr8J{TKV9jmQ+@fdu5=5WaD-f|~o6dkQ^v}!& z5iYPt?2x)o@q%h^;dyet;4(O>*V%%SsVm3b4a&gskGB%`kFjlycD_sed)GIukB3jm zXPekVvGFWruC;wDK&hr zeGBmw9Tm0BB$A*TI9!hEIbbjErH;$Zs4N%yzr`DKZn%4R&QE8ivD|SiTjy2IDQzg| zx&3$o7r8sfazW%lsM@>~9>%Ina z)-qZvzSd)T;cNbE%@Y<|P1UPM7YUF0p4A;2n`X98VUHG!F7;i4(q`wsPz;%RVOsb; zurqsnykFTpROaEc#INAWw>d4=l&97X@70{(xzKC^x56&v-*)X5C@`y#7Rwvqw{i|y z*qJI;lKN1@APyUVuaZrxVqh8AS*H1JVJ$M#*pV+4L&A^SgP9rL_Tuzyp5yUC;l^?H zCgEFX2dy7CDywmqz%dWA%8@lo`d~ad($cGE5{B8QU3*hV`xiWBi}pw&6BwSHV(3Ka zrsQabD8J^fVJ$wZ=zc8qGT!UEz?luP3PKFh{`ojxPk_b6z@B$4;k*dlVjkZbNe%?R zE($bo%CN~jbRy@fG4X9SX$eZ3>uYZda&5*Nv;P~EX390=n*+k!ED)T!m)1uyaY=Qq z(Ck+|$TnfmT3mWwoEtMP%kQK&aY$zLhx>@QkLQlLxcSt?k^9oZMsR3>#}^WFG4f-; z%m=Nw@xYS-WPy*xDl{i%2_oyKblkmgJ@2} zOG4LSROggX%A`g%?4?1(Y@CWCz(tcPfA9CJf9{42pg(Wpy;Ib|3qK_6>`aIUu@ zQF;z5^kw1QB=Q6@|eDf4k*82X4A&<0MKq4>slP2HE z$ilv2yc60l##HiU)5jH`VnY{-rp6Vc#XrU-zZbqxe~8>`b$^1(DXsm1`Mrhq+p%1S z^6>V4$>I+E4tsp5WTR>vI&Gl}_D!wS+P;xyl*Vte@q9jij`=Q6DWDYxH(zVUcE2_uXX~A(ptpW1hq~N}1e^-IbYQ)Ijjs&NPXa}Z$!jPVF`f(k@ zf|G`FHG*p_)N{2+6$KuebNLlndR$klT{a?(qrLgX-)}Y#PLzuYUqhLfB)nbPm@K6( z{<+8x2o&sMY#_~$RcVwTZu8EI_41-Xmw811fe_=2fwl*D+OEoj193@8;5-41W>hYHPQKf;Y0O!( z!RLlmB>*$eR3x?&K@M1X|Ae3H9O!2}4dmYQQ}yLZX;!246{h7{6RY|f8-(SWi`kUx z4#!#(W~X;bTy6yVOYFg`b4tbV)*T^HA*gvcWf7dSa-_$+l0DY60I8X@VNwC6wMzKp zy#nd$jGQw@H(BO%` z3xqe$iXNPU5;41sJ**K*S#uOqj{F(z-MT@$q;jkw+)Fc_CpSrCw**&~F#2ar&Ty9L zn1b5RMn$=&N)&P;MGYhp^DO49gb_3gPsD*HEJ`Adnvj~a ziV~VLlGAC6$+p7Sf?BJZoy?gk!E{av#(!sINKXXe%ROhtPB-sW!*gL4Kwu32AVum0 zo-)P9`7=f;#MefmO6&PQkTWR^{OKntOdUrtj+1FJb{MXergINZos>3air^g8qjLni z`%@e~ZrbTiqxbs(y>9wdN0Hp*jskJ_jWTrHn!ZrZn9sz>0gbQak7R!Fw zY0N&5xNMLyy0i&fZJUH#WwJ2cA#KiLd$v&f?_VkQeVt8oukMiWihJk1R~zl2D5<~p zr0lXS_J-_L+k-0F&A-o7b1(*!M|_uRzp4zsbcQaYq{M9^&%~;*ohMZNnph}r^I%o0zC3Ml-Jfk?tio=krqdi1B}T8j$Q#gUXpcyCmU!+ zX`535swgDok3J)FyHPmQ2$L;CpFz<;(>44WZFnz#cqoa1L`glqxpTB*?1q)m-$q{Z zja@Y?rIePH7n7sf?&B-d=4FkCw%%KU9`XMV@DrBH^6!*oprPlboXq zvvH0aGiotv+{)C)5+S$RcxD)tqZ?%}F&CzZhwyBi|-b;kJ}SyWOKJKlYzbSqD2T@55<+J}?Ulr{63j#o8xEh(qoB~xV% zIP&2~FFgh%>11as;``f>9GU(GO}-TMV{Ux$@7qnJlvUVqEw?Rj z#@zaNxx3RY({ehgi8L_GAR|6B++u4~207gJDu2n9x-#+Ay?I=)fn!ZtW3ic_G-v8$ zRxz}2uEpWMV}`pUXGwiYW{#yAk#g3B#hby=u=Op*d8fjEc>r!zwhn53En8Pm%FeAq zkX1!br%nXJ%*P}_q8%|kVP5fS7gv&KHO(jhs}pA~R3i$*a;7Z}s^haf1z;Xc!4s2EB|eL0k!^+oZin(WI{-Xm%vdCs8G-bsHPc7{{)d9Yu<( z#3GlV@lWXS_t_7AxY*!FYmAzicTbGc_zkkGOJv|XL1SMVSPvP(TzNiY_2FAnPQplg zHm27AgCoxFOHLP>dM;P#hEmV}CdYB;H)cLjgp5#*;M4F*iefKoB4>-s){?A75@zpH z-b*<71YfSF8upPj@F1t16m!rsrTSXu=KF)(tWmRC2hnsBCNvS$^5awI5MIM8um7`F z>v!Ym8+@2m`<}ziGA|3r^)U;n)>qDVU?>Ab97e+t{Rsl0-XU#O2D1Emk27sks-LoMv{Y z7LO%3qQn&^a8t_okS~*4q8f=dYS%)AN(V&t3eLM4xP58XDnEDS+hSSMwga9K41`oh zcJ%-)5@Oeg>0pKPm_(#LYM5x~?c)1XG2M+@r_Ux)%tLy3KGhk8=`}K}1=5il;`tAf zJUr@|_eZM$@baJIBUJ8|Ol~7>5UN`UYKO8jYf@f2roM?|x>i2hjoacX2a)5*iH}NI z5Tuz_S1apT<`nr#sq76ZC+g;XUy%kS=CM7nR-dFxV*v?vSPbEbG!h9fS&`L750UkC zU8@sVs;gVOFCf!T{YVigCXiKIpEuoe?Ma%4AQ^$w{9F5YOw$Dq~c?0&|fj)KFygB1w6);-55l zX^45Y)4?Kc+IY9P@61GIH(RIiugkzeiD3C6hPzQ;V%iH3e_A$1yUAk{+@vwU^#pFo|GseX>xnmcryG)C5VWtX5C>BU>bN(MRSDy%*xxGikOZ5>bfuyd zV~VP3us*yxV=j3Z%)0cZqHf$xRYRYryn-o9W%0XKE6Ow}bFhBYXHVnc&uV#>$Efc` zdx1H6q+C+OVDYI&$Epm@b2vyR#~Qz55a4jHA9#_d!Z6@6%1Ur~ldo!WPrT)Oqe8QTS*_k!3|_<4T{z* z<8(36bA!5{AXp@=98A~24)(l#dS*BQn5V2++%%mc?7S@b8)(qzV zE&Z656~hN9186(U1Td{i#Xv^&q`0w)VVY11fWC={FQ(%LPky2w|BP@dAjCBv;$fUx zC?Lzs$JHr@)X6_?4wcUG+cJd2BbbbR?rQd0aGN zG@#7H3roS!5}TRl7=zNnRv%RQcFjSM+9BP^@{JIXgv2TYst0E95*5G`wn3f<4`T?8 zLQ)bM!4MwR=-1RnMQK=7=;en;ky=&c8Nj1THOx>q%;>Bh(^)&AV_$~ChD6b@ts`Q? zVQW~?7#f0L-z}?m5Av{~)iB0Hfmun3eyN7PIqv`jWVY*U&u>TK~Y3?jEl0kGfvU~Nrkn| zB*o%%qogX!1a?C0E-CHwWPTRh0;I5M-V|lW_neqAo{&?KfK-u?b(Vm3mXMc;fS8F8 zyN(#bfe8LKj{rIsGCU6>JRejt4@EK`W;73HG#{8M4=OE>Q!|HDOpioNpYBkf4{bU> zNZr3sJ(wYo7?^sv9;v(r%d!UTtOgIe1~I(`Q>zAb%??A{4ra(6*z_9j@AIFr0rVRY zcH#j)Fx6!}ahQG)ntm~yeo>zO5ISp_5RUF=3t3cho^2)wDOb1NADavqHzJceJO*|f zD#nD#dX&k8Fgu}scptt^q;&40Xa8mSuAmc^=GU;OLX!ExxK!_4%eo)O zsf3r#%;{j~G?PJbHGXhPkqVkX44QlBK^wxULmj+J5jJb~o6W8V9|%&2{$5P?eh84! zYo5Va93h5ZU-z&R)+&xhn~bI5u^Q1z9KxHx8Z5^X%WJ(TQKJ|mvVKyp*ACl5TSSZ3&;$niswBNwWU2@&>y=uH zv4{-o(o#IDh|Y@id;)G=g2D-mqla*7FU+RU#Ky$|?~akNxciNsv$&{zXHx%ev)2obb*_RcfXUOX4r0<#teNLO8MAS?WwLj8}KU zis?RboAX(q#s=imFM*olTp(p?dX)@_cjzMz0LggXe_73kuJh$F0<-LUk(4S-FTgax za5?@IwrG>J5%ZM1PyACC!&eI|c_uB1?i3X9`8wS!;0Wg|i{j}-H<|o-Ss^%zqZnL9 zoavQuggIbR1(lGQK`d%WJIrn!Xuh8(E$7_9*g5uL9j#0?gdkmziMt&3V?8sXC2*|8 zzq}>*tmSuli}`Fwo8EOJ}5N(Qsj9zqk5n@T6`Na`eFwwM?nCmD+*?DH|?nAJQ$0!4`<93Rh4 zO-*GS|4w@&cS0&wIuG%(oyyTfRu17Yiix(?8;xbjGKJ<6k+J+|i z<pWx3afD!If5!Yyw$u0?c-T&T zyR+?cJ@Ai$SO3$dQ|pDj@`Qyw2)?7muJzYot+$QU!$qg&*>4rk?xHRDhf%jP2;I+W zm@7zM)L#j&!^+1zg)4!8xPJK|ltPs%Xm8mhE+LiEqRZ1RH7TSk>-W@P$(&h2*1D zTilZtl}cq+MdXZ=3R7ffO7B%W9;el0xP}2Kk6vWQU9b2F6cbh`!&F5rNRm{AOhFF5 z)gA{8;;fow2b#H>w2pk`jk0HrVwQ~(8my%nv|KX9XE2$is+kActMgTG&Sg?>4Z09c z@<*O(0f{avln%OM@)vh)e{nx1u@e`L8fDg!-o`WW77RVK<&GtI3yU8jvlbjZe$gL` z!a)6U5Mb2|Ymm^ADEswLuuxD>;kq)JUu-icd`|>NBlAvVFPukhuV`RxU_pfEOZzGC zPJd%NKp&cowuP#TnT=5$?v3I}<|1`&T69LU>}%4cH&r|F$kDbr*%V;S z`XHVli1m44qS3BF|dn7$Hs{ML?fi|jO^ znFRrTP0Ri<3g!WKA~&PFjqQv*+Z<~%RzGVSTSYL<7P5tMqbq1JP_ifk_DEEKe9gNn zu=-OPn>a%uZ@k~4Xeb(qPdF#C?-r>ECZ9=$?$GUrxn36HmjKW29jL z?MLoo)jT#rRpCQC*w?5bj$QeFCWuK8b?#b;| zv%7iUq1KUwk%Y;wJB1q`OYltXV5x5==azkS@fr*9ph?HKaUH5^Bm!_ z8LAq?yV0_j?)q7D@My%wqtweIao*{%@a%ujO3q;`9AFaC-Zi2fjZ1+gayg3Qkxsso z357B1-ci^eGKDA}p?1t_KdFL6+&+hi(Z?}Y_<*B^zvbuI^{IT ztR38Z5Bi%6$4f2Ziq__KV+U-iS-fNA39V7QLxbL6VW$gG#=VMkN_6Y)&0|sy@d&{l zZlWJUVsPuDA?v2={#Rt$gH__*%J3IoJ3o{OQ(PQO5kGI~z1k_IugvA+(y@fE%H?e4 z%#}OC=!g8K0?ERD5u5Wd+eVf6gZj?o^kG}qtp`eeMRj_3=M1WI`C2;?1kG8hbTBk< zH$zIPpPxQreO3}8GmC+TQW(aSMsrLZWGSs8^Zi7&W4r?V^(&cfI<$K;dc!{xM}QeV|$pdZ5mZxjr2a-8xbgyk%eO&c2-y2g5mDa*&r00uKPlpdSD@ z*UnhA{dZ#cf0xTgCjX6=PfyGX-qt>~SC-t30$+EVJGSjUYkLOzjdoT~f;&#kzAwib zx~DPxd0J+za8uvn3R1c;velo5TG_?kQw>yVV=$WZ!deluzR!AHnqvs>BG>!)tZDDR zqG~6=nMH<9v5(U(t0I=>V6;WUO{pZirLc_B?@}_7FCM&MinO+g=$# zO7_6fgI_gwa`(|$)3kpDhRoot^H5o0il!;B1U5=wS@W=zqr>ItMg>MF;6(HILvMa` z_CmP9PHhve|30&Wn(T$*fO*p+xBC511D)Ip{0DYUk0|o@*A6|#HV97uw;hyhH{UXF zxgL;o03!pWhXA)3aAOWIN&rtRl%gO}C|KM!y&dF^k{c>skk*bO2P#~Uj=qvQDq4_+ zz9I%Hc+jt0C9?-%X$@%!X@zc=4)g@%8f)ABf`Nj5k^z$5{-J&#y;?YFK(G)-=puh% zxc#$BT+bZs1o1OkX4XB>F@r@?!}MUrC=2eL)ksq%ci;ZQU#w~N(Eo$**Qt><~|5Z-s8 z-xmyv&31lOl~%@u`e6Fxt)!$z$}|>lG{(d?s0V@l31-9y#4`&aD~TAT2zwt6pZ^(p3`y;CE3-LF+)8+~A9~K@ zyvdVFwrBL`KEY;IHZ66vjjHO$PHOI;^BaWm@nBMJuxTl6Qm+4ZiE7jG!8YA zp1CkdWtvn!s_A&tR-jY2Xq2AuKblIN+0Ok6mwzagZxm{x5wr@k@{7Ev@B!SXt{fhf zP04WXF#mf$dixMH&j85!AggviCe9Gy`2d`i*gzT0g#Y+tgY@_xm>9r&H>`Fy6izps zm4M_(zwdjmnpd|aPoMC5kF$2KMn<>(d7rG6V9bI>a!_i*Nory(W}Lt7v!7{8fE34X z?`4C_LZIW{aAv>3OQLS$vwhwN*}eL)d4`DB2Vk`aIWzhZ&xh!&2EZ#uZfQo>$A|YL z$A(Ehw~>Es<5g{=!0BNx>LHrwWt8r6=U}3g<*`5$vojI1A`!C@5jQ+b;GYDm6@_=v ziqMZt37+@3SoQv`7>^&Hs*s)tjGU~w?qhH7Aztt1#Oa~4>Sh}6A&C^=cM_y#5s*m~ zSJ)R5BN5jY0Y8ZGibr>ls~V={Bxy5BsE8M3k|G_%HFpquzsv*gHxSB$h@3WxV1f^?y&i z9XxAIol%{UVVj-NE1?nLQDu`UsaGls(Pi8m7DJDdC7H|?*f@>Go~HA39Is0nrhL8U zE0*kH_Dsha$3{z(WKIVlm8a7y_{9+ku%MV1CIp;j#$YH@z#tB|Pat$~0$V@&O+{_@ z6ib=hTdoN35K>A9R{IlHM4}JZ~Y(k%;zBYiT zzmKOkpJ#B7rze4D?E7?j$J3|8b77weBjhLse6h_n6hlUDA!yZFe1!bjN6r9NUx`OLnZ$*5 zg~{z=>F$S-%%XqPYRe4sk9~}2WO3~yiQnx%2OrkEqt^Q;)_W`02QSupc-O~1)`vFM zM|2o!_J6z#rWguwlDW8}ia^sHg@4`XS|fDhwFy=mj;;-J=e8q{ahIM2aGm^Z8e@MY%9ikm}k#b6{)wiuF@^}E2n4p-lQ$~nAj)W9p}&4Kx90<$t8wO--GaP zjIOx_rBqE7czWSmqU--e{+^cL&FW!*mea=w=Aa1cWpstK!w*JugX#OJW&Vr+Ki~uC zvs+)_E%H=ELi=27M4cZT%HI7XyN9p!Ns)EGiOAz0TT6cuKX?_4yi(~?+x4cbwmo== z8as2`$qu6j#kYKq`y+JyJN7BlV#{7^hVTfl5R0fVTppG^=^&X3D&a8NA~f~Jxwaar zgK7}9Rt1QT(ql$t&Ykc`d)SL`;?R766k=02FJOw z*M`xhH5PZ)7M^pD)?uT9C%s^W;!(4;BE=*#!L$)0FeqtKlFr#!ljLGIHuaEd$g1`z zj9rQot;8R5cHI8riiNQ__#TV?!-$f6#5Hdjxd5Mht5!BBpZ6=DcmS8ssYSg{+dYJW zOMMe+1{rq-3046)Ndavja|5%a0;K<<(85DY@JaC-#NqldE-m z%qzpaDt@jcciUoJ{vfXQ7x_hCgY)6SC?@L?1tNIcrGcub`%)RJQisfeyxha0eBt+6 zgL`6GzDZWb4imKyk{@^484=v`id_;T;9X*~^j9^(Q>9fo&d_*?IyWy5Rr`*TyrAL- zC2c`sY)grmF1o1CzKF@j4Na99<3vHpp}ffF3u}UqBC7Hua2f4!czgJfeWf*BsiPz! zd?T-CI?~F1x=L!@&lH7l7zasm9Zlm=9PLW!(+4U+izAiz*_B+Qs`IdeB$8weGUY|k z-67<;;)-Mo=${8_m(P>FvQQW#vk6J1v0_uQhr$wxg6_&f&htW24T7FdLg+BP@{zq5 z(mhTz{YW%JR24mJ=lyu+0}>fMVA_MQ>w{}<@c6q*Uca-isXu%9-F9Dj@Sw&H@%!wB zL2(+GSM!-}d*KP3YY>xnEwC`3F$RCey1Jw93-h5^oH(X)Sg_;EE>G+_(1rX45*Uct z1x@J!8epc3{f@!V4%EU5$fF3%oVnAZP$4h(;9%t~NJ{HNCLLmn^aFRzXdMW1ObAvc zp@L8^fZR2^vDr_lkEcgZgMf7XEyY5ERI3ZYnN=bS#qo1-0f4Q*K93w^i_47=i_p2NU@0i^JDI;*-Z+D8ky%ErO$jsJJe{fHVkU zsKP6;lp>9QJRqe6qVHdv$T&G&f(Q!EG$o-Hvu&}$q>vF9{uopb6Xwuh<>N(GW zn}>MkdDoF|_lo7R0W~CqxJv5xL5kxQUxGdMIA2{9xmIr2FW981GH>{z-Hd?ed93Zd zkeDa_&0yPyb!v#}^Uk_6NxGK!Bgs|j)~bTP@rRNUwi31=wuH7lV~rEYphh56_6adL z5^2DIwCgtTX$&t^TxB#BHAX(OdZzL;2a4j?(x}tY$kXybtg>3ovPMnH^jm#M*`K`# zaPTu>qT2BZ{__RI#48knl#)u(;mSpXGG-E%T+}I&)ZgC$#5B!D^}raC- zo;Hh`L8G$_Ofzd1GcVCJ%V#r7sM~|+dhQDf@byOszZM}Niu)-bgoG#uKO#cskPw1n zq{i)_7*wX5&t>g4gy}JtiFaR2{(;eLH^>xy6RFSlLw#6#vp#j>T%VqMCHL{XcMP@4 zA+8wa0BV5KNM7Y)0qTCL3O&%|eSxLT*+&k5DT#witkLFW39Zg&~ky&CrijwE^g( zqz*WWY1DCzOag^a@|xxDj)kp4s5xTSRduQmC6y{blMRcjami-LmX0OGf7FTd-ia^t z-nkfs6xeO!pKLaS6cnyHmU?nap7OTw6S-QRisGJ%L`7wU73MaD6~>a|p2~ja&Zxz- zl$0DEQ?S5l3S*08?-q&51T^Vdr=m5IQk5aVQbfK|K|?D9UB~P(Tu+Tm4p%uqPx|l8 z{|rlPj~Abmy;iG=rAF!e8Ii1yd}~qpxCFJlG`Ep&`{mQIp}k?OmhG0ST?axwq0y4x z?&I(>>F&tI<@7~dBxpTWXCZzeW`u~AiY(oZqqFG2NI8dn(ApKZ z`8}dUw)fis%pRzcyLIhor$u+!hz6#)+oIbcBS!VhT5Mr3A|V?Us{MklMp1cMKi7de&%?d;~dyT&;8M2Kq#U zv*_|!Ry`RgKR)CFCn@Vssx5Z}M=5vJJhJDhq_{*!P=){~l~;9QN0dQ}~T z#m}>h0_H@$@))%Ml(_JSjpajNzgb#}2v%G6mplDet4?7!Eet(xEm_1YiF>38!Vn=9 zagsd9=bP<3qw|G8871nk_4B>m-Gn7m;k&`Fgv?XKB%#mcG)j`{PFv~h9Mgfd=#W!xYFX(~G zExAoXM)c%N8aj916VjE1=3y;Yt&wzF!ht_>E|)F2%`g}6$LbNul!eLqS&C3j(y1vw ze{rRJ{YnonHZR^0o+sAzkUq4`MPE#_w=Bm_T8C3!0`mcnIzjr1TC?$^Pr44l?FZmf z?i{t`XSU4H@u#L=%_rrIF*wmG*wb5i!4mgcf}KMR%)QyIsCZ)263I zDqp6E_ol(C5ie!3v>GQ|wZT-&X%?XtWixIy zNN4b>Vn2S|1-mT^@>G%;CO<@dIZE+S6NA5d z4`dbx0lL9Bk`4F6%K7i>ybk}Nu!b#`!W6W(?L8%wt+4*<=KfPIJN;sMVN_IQ6m7y_ zJXfkO0WO#Wm~YKP!k*EovHH=4j~ukwQ2`WqArweU-`Ymu&N)9cPdWk2+&vvG6qVxEDI9waIizx zZyPQWZY@nc8g?N;Bl?kRyz@O$If3@}qZe{b!zzQ6!iCqu~VazW!VtauG%{?s@}rS9Un}x$`f@HWwnc` z(t$CH-@|!7wnI8idwdGNp332xFH<_@<_U{RzVOAEY6Fy+`QitbL~vs~jHv=i*%>f@ zKr7wNmV6)(pJMM+Zlvt`f0l?vz>q4B> zKM~0u*-KOH4N!cP>+wN8{#T>LlxeYF2P)-$50|c0<1}wWog)zgDzQ3t6Ub?0N+bCO z7ZIuANk1$@Vsp83I-7pJpT4hP68 z!%9d*-BhfJKn~@OwHadi7&G@Jl&c`Qk2JCD;17`~(?aY;L21%8RAwMag?~&F6U2LMF}L$uHBd<9tX_VpYtW|Lfn;zGqTskQrue^9~&A&SrR;z)dmft+}`+6)2H zZ>u*mzbGNX6pd0@pzN~tm@%u|uDY1JYh2a3+PXS@(opv-mTCuAS9=XcHEL5P`K&)K zWloy`5cz-Uu9@3gfjX6Y@$)u3QcJCnHU|yKn4?JG{Kr4cFTp6Sxew+k5K)yAQAAV> zwPt^y+CD&Ekg_XMcm%m$2(%X9UkWa2yQpZTx?Gu9TwFau`S{!NN>n_uGc%v<=q5ai zoUK)f3dXI_sk$_&(s9P^!z4w_7<UxUWzUfHwKkOd=4Lc?ADh=e{C&TzpFFB;x z_4!N^VUL9P$$fXn(RX*ue8M{o=47uR`DC>X?SolD>i%x;5cmQL6@8~_4Zrk8e4q#D z=~oW83)U_!+Sufa$NlPcyRgi?adFw)EG#@u=<_t1$h{tseZ>}YPi^;}S!RupHU-SVh-=l4r;Uj6-fWprX>M{$T$xkyRW-E9pX+Z@l!+ZoOJl#jWNDlpVwS+99+=R9wc`yJHacmyg+QBhcm9tYp6J*$r` z&!R`CivkMEq-)1@A`sbMN!MGgTHr0M^Kb6zId0*j+5U_9Ei80xN>>&S^_u7*BuZV4 zvxBP5DCS1IHKV`S`8@Tc>^V&$P!xfsx#fpvQqz_sgXw(gp9{)!bu5@6URtR)mnPoKbtOxS2srJvG2-*5YDBNX1fc}UPycjVAmhsRU3_Azw6;(JcF!b+S^ZqDig zE$xKQ^D7^df7x3vHUY8jB9+mwdBUOpYAxpZ;4|n)-8=8Dx2I_(zD^xqTZ~6eF_-BM zg|B47zEawhY4}&vU;QGCkD@V@r!39+XWyFyM6_Zll%qM3n<5)Rkgv>+O|)B%WU8Xg zKBvBR(;yDHeda4n;LUpZy5s^@^?QPIH1ejSN@Mm52jRKGBLC6=8kty#m-%rjZz{=3 z$XVrr!W0OLQCd@Cr+Xa_*~F{g-dS!P2b@H^`h%-8hN|#mno8R5if9KR{|-n~7Bil+ zB~J`|XM)-Ec9Tf_INy=MZKB)P!pbm4Re7&p ztFDF)FNpsXSc<3u_3Wv4M71u znjCObramoF=SQT`yW&9A$>~zhFK|-9zM`J-P(!HLtac%zzE+=~s50 zv*<0LrD09dcWrU-Gg4%aE~Du&e0WMt8KG$?2{LjjJe2*X6_Kp?wH zE@znt@pf*X5i@53Y1`^2*s7d|foFofCJMJyVWm;=y{oGa3w+Xw-mK-eEKji&}Z0ry;*6OyJy@ zJYskw#W{w4YWQgyvgu~4@|%}3IXTqc&N=Ai%(1VGB-h8L1P${fUlgSUWcmvEpGJ5_ zN2G>V$N0p@=I(w`+XKH{kms8nsvV5qA6n!yGFF>MwZb@HHhxEpFqUzmr&G=(ybVv+ zb1XCp71D^Z?RrG@5!{pMkSZY8r6DEc|2Qu4`4PqXW1f9NH>WjrqAH4t<9saF6@Hf6 zsppn*CE+kGrIlqZa4TlArIoJhh@@`UpR;HGxl0!%46Xby3`vqBDg+olyCH|XX zds(Z5pX1(?);_R|gtwve$H`sv^9M?YcY$dQa8YZ4|C8Qms`$Ut-7K0V{_hpYo3SJ@ zPo+_;2mkZnLb(LV1En$ET&#7oSH%_MEjB*tNC@p&!rMC9 z*!(*P#)l8c>MriH9I5MoXwT-QWsCz5)YevaVt~WuUYTWyY;7v@vPu$@-f!75>FSbM zA2e8zKh2AVCXb3vKijft+SfK|D@%O_4T|>j2r_d~Zk|%{az@ge5#GT`5&mu=JzbQm z8!ETw0e6fG#`O)+62K3|NeJam!rM?k>BRUq$fvtgJuxO3TMBFXCdC4DA0^!`0`|@4 zPW^7(vinPG$E@7baC6bgRih@&teTh}nC?+oRMa3OO@F& Om!xA*I5a%sc#BSn9X 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 R)JSReoA?KePXy`!003TsIQRen literal 0 HcmV?d00001 diff --git a/public/fonts/source-serif-600.woff b/public/fonts/source-serif-600.woff new file mode 100644 index 0000000000000000000000000000000000000000..2e64ea62b3caf5cbac503c74f45e5398bb6c8f7a GIT binary patch literal 25624 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 { + const directory = path.join(process.cwd(), 'public', 'brand'); + await mkdir(directory, { recursive: true }); + + const response = await fetch(LOGO_URL); + if (!response.ok) { + throw new Error(`Could not fetch the logo: ${response.status} ${response.statusText}`); + } + const original = Buffer.from(await response.arrayBuffer()); + await writeFile(path.join(directory, 'logo.png'), original); + + const band = await findWordmarkBand(original); + const cropped = await sharp(original) + .extract({ left: 0, top: band.top, width: band.width, height: band.height }) + .resize({ width: EINK_WIDTH }) + .flatten({ background: '#ffffff' }) + .ensureAlpha() + .raw() + .toBuffer({ resolveWithObject: true }); + + // Reuse the panel's own encoder, so the logo is thresholded by exactly the + // rule the rest of the screen is. A line drawing gets no dithering: it would + // only fuzz the serifs. + const bitmap = toBitmap(cropped.data, cropped.info.width, cropped.info.height); + await writeFile(path.join(directory, 'logo-eink.png'), encodePng(bitmap)); + + console.log(`logo.png ${original.length} bytes (original)`); + console.log(`logo-eink.png ${cropped.info.width}x${cropped.info.height}, 1-bit`); + console.log('Both are committed; the CDN is never read at runtime.'); +} + +/** + * The logo stacks a wordmark, a two-line tagline and a drawn motif. Only the + * wordmark survives at panel size, so find its band rather than hard-coding + * pixel offsets that a future logo revision would silently break. + */ +async function findWordmarkBand( + source: Buffer, +): Promise<{ top: number; height: number; width: number }> { + const { data, info } = await sharp(source) + .flatten({ background: '#ffffff' }) + .greyscale() + .raw() + .toBuffer({ resolveWithObject: true }); + + const bands: { top: number; bottom: number }[] = []; + let start: number | null = null; + + for (let y = 0; y < info.height; y += 1) { + let ink = 0; + for (let x = 0; x < info.width; x += 1) { + if ((data[y * info.width + x] ?? 255) < 128) { + ink += 1; + } + } + if (ink > 0 && start === null) { + start = y; + } else if (ink === 0 && start !== null) { + bands.push({ top: start, bottom: y - 1 }); + start = null; + } + } + if (start !== null) { + bands.push({ top: start, bottom: info.height - 1 }); + } + + const wordmark = bands[0]; + if (!wordmark) { + throw new Error('Found no ink in the logo; is the download correct?'); + } + + // A couple of pixels of air, so the thresholding cannot clip a serif. + const top = Math.max(0, wordmark.top - 2); + const bottom = Math.min(info.height - 1, wordmark.bottom + 2); + return { top, height: bottom - top + 1, width: info.width }; +} + +main().catch((error: unknown) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/preview.ts b/scripts/preview.ts new file mode 100644 index 0000000..0807bcc --- /dev/null +++ b/scripts/preview.ts @@ -0,0 +1,129 @@ +/** + * Renders the panel locally, so the layout can be iterated on without a device. + * + * npm run screen:preview + * + * Writes `.preview/screen.svg`, `.preview/screen.bmp`, `.preview/screen.png` + * and an `index.html` that shows the real 1-bit image at 1:1 next to a + * magnified view, which is where clipping and thin strokes give themselves up. + */ + +import { mkdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + +import type { ScreenPayload } from '../lib/screen/contract'; +import { encodeScreen } from '../lib/screen/encode'; +import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../lib/screen/layout'; +import { renderScreenSvg } from '../lib/screen/render'; +import { buildScreenPayload } from '../lib/screen/viewmodel'; +import type { ScheduleContext } from '../lib/schedule/types'; + +const ZURICH = 'Europe/Zurich'; + +/** A deliberately busy week: a lunch break, a late opening and a holiday. */ +const CONTEXT: ScheduleContext = { + timezone: ZURICH, + weekly: [ + { dayOfWeek: 0, isClosed: true, slots: [] }, + { dayOfWeek: 1, isClosed: true, slots: [] }, + { dayOfWeek: 2, isClosed: false, slots: [{ open: '10:00', close: '18:30' }] }, + { + dayOfWeek: 3, + isClosed: false, + slots: [ + { open: '10:00', close: '13:00' }, + { open: '14:00', close: '18:30' }, + ], + }, + { dayOfWeek: 4, isClosed: false, slots: [{ open: '10:00', close: '18:30' }] }, + { dayOfWeek: 5, isClosed: false, slots: [{ open: '10:00', close: '18:30' }] }, + { dayOfWeek: 6, isClosed: false, slots: [{ open: '10:00', close: '18:30' }] }, + ], + exceptions: [ + { + date: '2026-09-24', + isClosed: false, + slots: [{ open: '14:00', close: '18:30' }], + reason: 'TEMPORARY', + noteFr: 'Ouverture retardée', + noteEn: 'Late opening', + source: 'MANUAL', + }, + ], + vacations: [], + holidays: [ + { date: '2026-09-25', nameFr: 'Jeûne genevois', nameEn: 'Geneva Fast', isAutoClosed: true }, + ], +}; + +function payload(): ScreenPayload { + return buildScreenPayload({ + now: new Date('2026-09-23T10:30:00Z'), + ctx: CONTEXT, + shopName: 'ITA ITO', + logoUrl: 'https://trmnl.loxi.ch/brand/logo-eink.png', + messages: [ + { + textFr: 'Fermeture exceptionnelle jeudi matin', + textEn: 'Exceptionally closed on Thursday morning', + startsAt: null, + endsAt: null, + priority: 0, + isActive: true, + }, + ], + }); +} + +async function main(): Promise { + const directory = path.join(process.cwd(), '.preview'); + await mkdir(directory, { recursive: true }); + + const screen = payload(); + const svg = await renderScreenSvg(screen); + + await writeFile(path.join(directory, 'screen.svg'), svg); + await writeFile(path.join(directory, 'screen.bmp'), encodeScreen(svg, 'bmp')); + await writeFile(path.join(directory, 'screen.png'), encodeScreen(svg, 'png')); + await writeFile(path.join(directory, 'payload.json'), `${JSON.stringify(screen, null, 2)}\n`); + await writeFile(path.join(directory, 'index.html'), page()); + + console.log(`Preview written to ${path.relative(process.cwd(), directory)}/index.html`); + console.log('Open it in a browser, or look at screen.png directly.'); +} + +function page(): string { + return ` + + + +Aperçu écran — ITA ITO + + + +

Aperçu de l'écran — ${SCREEN_WIDTH} × ${SCREEN_HEIGHT}, 1 bit

+

Image réelle envoyée à l'appareil, après seuillage. Aucun gris n'est possible ici.

+

Taille réelle

+
Aperçu à l'échelle 1:1
+

Agrandi ×2 — pour repérer les coupures et les traits trop fins

+
Aperçu agrandi
+ + +`; +} + +main().catch((error: unknown) => { + console.error(error); + process.exitCode = 1; +});