From 67ed949d8af6cb04329bb68c47b7cdfafb6eb0b6 Mon Sep 17 00:00:00 2001 From: James Brumond Date: Sun, 2 Jul 2023 23:46:40 -0700 Subject: [PATCH] first commit, basic http server rending a sample svg is functioning --- .editorconfig | 9 + .gitignore | 2 + image-templates/Disaster-Girl.jpg | Bin 0 -> 28992 bytes package-lock.json | 47 +++++ package.json | 14 ++ readme.md | 209 ++++++++++++++++++++++ src/config.ts | 19 ++ src/image-id.ts | 6 + src/render-svg.ts | 53 ++++++ src/start.ts | 9 + src/storage/interface.ts | 33 ++++ src/storage/memory/images.ts | 80 +++++++++ src/storage/memory/store.ts | 8 + src/storage/store.ts | 24 +++ src/web/request-handlers/image-request.ts | 85 +++++++++ src/web/server.ts | 12 ++ tsconfig.json | 9 + 17 files changed, 619 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 image-templates/Disaster-Girl.jpg create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 readme.md create mode 100644 src/config.ts create mode 100644 src/image-id.ts create mode 100644 src/render-svg.ts create mode 100644 src/start.ts create mode 100644 src/storage/interface.ts create mode 100644 src/storage/memory/images.ts create mode 100644 src/storage/memory/store.ts create mode 100644 src/storage/store.ts create mode 100644 src/web/request-handlers/image-request.ts create mode 100644 src/web/server.ts create mode 100644 tsconfig.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5b49d35 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ + +root = true + +indent_style = tab +indent_size = 4 + +[*.{md,yaml,yml,json}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd87e2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +build diff --git a/image-templates/Disaster-Girl.jpg b/image-templates/Disaster-Girl.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e2e50f0b31e3d849c47435b87d76f27c15e44eb9 GIT binary patch literal 28992 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.17" + } + } + }, + "dependencies": { + "@types/node": { + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", + "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", + "dev": true + }, + "typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..12ae447 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "mini-macro-service", + "private": true, + "scripts": { + "tsc": "tsc --build" + }, + "engines": { + "node": ">=17" + }, + "devDependencies": { + "@types/node": "^20.3.3", + "typescript": "^5.1.6" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c40a76b --- /dev/null +++ b/readme.md @@ -0,0 +1,209 @@ + +# Minimal Image Macro Server (mini-macro) + +Self-hostable image macro creation and hosting service. + +- Add and use whatever template images you want +- Add as many pieces of text, formatted as you want, to each image +- Images are stored and transferred efficiently as SVGs +- SVG markup is screen-reader accessible +- Web UI and [REST API](#rest-api) for managing image macros and templates + + + +## Efficient, Accessible SVG Image Macros + +Rather than render new PNG (or other raster) formatted images, the renderer used here actually generates SVG files that look something like this: + +```xml + + + Image Macro Title Text + + + + Image Template Title Text + + + + Top Text + + Bottom Text + +``` + +That sample XML above is about 800 bytes in total with the comments removed; The raster formats that might otherwise be used would typically be several kilobytes _at least_. + +Obviously, the externally referenced template image still has to be downloaded. However that external image (the largest part of an image macro, and the part that gets reused over and over) can be cached separately by clients so that it does not need to be downloaded separately each time it gets used to make a new image. Additionally, only one copy of it actually gets stored on the server, regardless of how many times its used. + +Additionally, since an SVG is markup rather than a raster image, they can be read by screen readers (including the additional `` text for both the whole image, and the template). + + + +## Embedding in HTML + +The one drawback to this approach is that, in web browsers, [SVGs do not load external resources when rendered in an image context](https://developer.mozilla.org/en-US/docs/Web/SVG/SVG_as_an_Image) (e.g. when used in an `<img>` tag). That said, you can embed it using either the `<embed>` or `<object>` elements, which will both render it as a document. + +```html +<!-- NOT This: <img> doesn't work with external resources --> +<img type="image/svg+xml" src="http://example.com/FrztglsLexLRWg"> + +<!-- Either of these will work: --> +<embed type="image/svg+xml" src="http://example.com/FrztglsLexLRWg"></embed> +<object type="image/svg+xml" data="http://example.com/FrztglsLexLRWg"></object> +``` + + + + +## REST API + +### Image Template List + +#### Media Type: `application/vnd.mini-macro.image-templates+json` + +_todo_ + +Supports: `GET` + +```json +{ + "items": [ + { + "id": "<template_id>", + "title": "Image Template Title", + "width": 800, + "height": 600, + "links": { + "self": { + "href": "/templates/<template_id>", + "type": "application/vnd.mini-macro.image-template+json" + }, + "alternate": { + "href": "/templates/<template_id>", + "type": "image/png" + } + } + } + ], + "links": { + "next": { + "href": "/templates?anchor=<template_id>", + "type": "application/vnd.mini-macro.image-templates+json" + } + } +} +``` + +### Image Template + +General Support: `DELETE` + +#### Media Type: `application/vnd.mini-macro.image-template+json` + +_todo_ + +Supports: `GET`, `POST`, `PUT` + +```json +{ + "id": "<template_id>", + "title": "Image Template Title", + "width": 800, + "height": 600, + "links": { + "self": { + "href": "/templates/<template_id>", + "type": "application/vnd.mini-macro.image-template+json" + }, + "alternate": { + "href": "/templates/<template_id>", + "type": "image/png" + } + } +} +``` + +#### Other Media Types: `image/png`, `image/jpeg` + +Supports: `GET`, `PUT` + + + +### Image Macro + +General Support: `DELETE` + +#### Media Type: `application/vnd.mini-macro.image-macro+json` + +_todo_ + +Supports: `GET`, `POST`, `PUT` + +```json +{ + "id": "<image_id>", + "name": "Image Macro Name", + "text_style": { + "fill": "#fff", + "stroke": "#000", + "stroke_width": 2, + "font_size": 48, + "font_family": "sans-serif", + "font_weight": 600 + }, + "text": [ + { + "text": "Top Text", + "top": 50, + "left": 400, + }, + { + "text": "Bottom Text", + "top": 550, + "left": 400, + }, + ] + "links": { + "self": { + "href": "/images/<template_id>", + "type": "application/vnd.mini-macro.image-macro+json" + }, + "alternate": { + "href": "/images/<template_id>", + "type": "image/png" + }, + "/rel/template": { + "href": "/templates/<template_id>", + "type": "application/vnd.mini-macro.image-template+json" + } + } +} +``` + +#### Other Media Types: `image/svg+xml` + +Supports: `GET` + diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..6cdf8c1 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,19 @@ + +export namespace http { + export namespace images { + export const port = 54320; + export const address = '0.0.0.0'; + export const public_url = 'http://me.local.jbrumond.me:54320'; + } + + export namespace api { + export const port = 54321; + export const address = '0.0.0.0'; + export const public_url = 'http://me.local.jbrumond.me:54321'; + } +} + +export namespace storage { + export type Mode = 'memory' | 'file' | 'sqlite'; + export const mode: Mode = 'memory'; +} diff --git a/src/image-id.ts b/src/image-id.ts new file mode 100644 index 0000000..09cec75 --- /dev/null +++ b/src/image-id.ts @@ -0,0 +1,6 @@ + +import { pseudoRandomBytes } from 'crypto'; + +export function generate_image_id() { + return pseudoRandomBytes(10).toString('base64url'); +} diff --git a/src/render-svg.ts b/src/render-svg.ts new file mode 100644 index 0000000..227fa82 --- /dev/null +++ b/src/render-svg.ts @@ -0,0 +1,53 @@ + +export interface ImageParams { + title: string; + image: { + url: string; + title: string; + width: number; + height: number; + }; + text: TextParams[]; + text_style: { + font_size: number; + font_family: string; + font_weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; + fill: string; + stroke: string; + stroke_width: number; + } +} + +export interface TextParams { + text: string; + top: number; + left: number; +} + +export const render_svg = (params: ImageParams) => ` +<?xml version="1.0" encoding="UTF-8"?> +<svg version="1.1" + width="${params.image.width}" height="${params.image.height}" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + style="user-select: none"> + <title>${params.title} + + + ${params.image.title} + + ${params.text.map((text) => ` + ${text.text}` + ).join('')} +`.trimStart(); diff --git a/src/start.ts b/src/start.ts new file mode 100644 index 0000000..4cdb766 --- /dev/null +++ b/src/start.ts @@ -0,0 +1,9 @@ + +import { init_http_servers } from './web/server'; +// import { } from './api/server'; + +main(); + +async function main() { + init_http_servers(); +} diff --git a/src/storage/interface.ts b/src/storage/interface.ts new file mode 100644 index 0000000..0b4a7d9 --- /dev/null +++ b/src/storage/interface.ts @@ -0,0 +1,33 @@ + +import { TextParams } from '../render-svg'; + +export interface Store { + get_image_by_id(image_id: string) : Promise; + get_image_data_by_id(image_id: string) : Promise; +} + +export type ImageMacroMediaType = 'image/svg+xml'; +export type ImageTemplateMediaType = 'image/jpeg' | 'image/png'; + +export interface ImageTemplate { + id: string; + title: string; + width: number; + height: number; + media_type: ImageTemplateMediaType; + content: Buffer; +} + +export interface ImageMacro { + id: string; + media_type: ImageMacroMediaType; + content: string; +} + +export interface ImageMacroData { + id: string; + title: string; + template_id: string; + text_nodes: TextParams[]; + content: string; +} diff --git a/src/storage/memory/images.ts b/src/storage/memory/images.ts new file mode 100644 index 0000000..5e07312 --- /dev/null +++ b/src/storage/memory/images.ts @@ -0,0 +1,80 @@ + +import { http } from '../../config'; +import { render_svg } from '../../render-svg'; +import { ImageMacro, ImageMacroData, ImageTemplate } from '../interface'; + +const images_by_id: Record = Object.create(null); + +export async function get_image_by_id(image_id: string) : Promise { + const image_data = images_by_id[image_id]; + + if (! image_data) { + return null; + } + + if ('media_type' in image_data) { + return structuredClone(image_data); + } + + return { + id: image_id, + media_type: 'image/svg+xml', + content: image_data.content, + }; +} + +export async function get_image_data_by_id(image_id: string) : Promise { + return structuredClone(images_by_id[image_id]); +} + +// ===== Test Data ===== + +const template_id = 'QF9ci-R-RfqUBA'; +const image_id = 'FrztglsLexLRWg'; + +images_by_id['QF9ci-R-RfqUBA'] = { + id: 'QF9ci-R-RfqUBA', + title: 'Disaster Girl', + media_type: 'image/jpeg', + width: 500, + height: 375, + content: Buffer.from('/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/CABEIAXcB9AMBIgACEQEDEQH/xAA0AAABBQEBAQAAAAAAAAAAAAAEAAIDBQYBBwgBAAMBAQEBAAAAAAAAAAAAAAABAgMEBQb/2gAMAwEAAhADEAAAALm/z0Pbx7srAuDXi5u5Za2eW4jU0hD1VFWaF0aZOr1tNF5sK1oc9LrRZa6clAZwtroRFam7oc4jzgFScCkIuzqTAtbOv0TSrdNVXI9PPbJ5/fYL0MVJSTDBa72vFpBMorwDxDgwNuwjmi3RDA1qr2Wahehz4p2Pla8XXNUjBTYaVMrBaLDm1/ZImaAsMULsRk8oZdjJvuswWPTDVwqC6WzzGeizthUZbnSu4insq4wIBzhEV65E1emUt1UMBOEY4oG0C+1VNcXBoBFU5rJu2Y6HUZDcNYl+lsgBo9OM5ZbUpwceBOy1sqMNrUV4ww5+V9jLJMGNpIiJ4iO8chvVwFFKKxqaqWPHLKShGhumEEZyYLNkxDKOt0TJqiLdURT8zKNj0Lkdvnqq3cZCoqOEPz0Grb6qFTNkTTyYiKiRveueWMN7Ss7ulLuJ4rAepB13YRFVEXVWpz+UYGY9EoSnF/pqO1on5yRDaK/zbGc51DCxkzRFUGjaZxhQnpqG7iQIadoBpypZKxIohFMOMAOM0xmLtbwJA7uSzdLWOosOpkolhy9s5JBW/GPQ2+ejScnPWs6cqLuoc56djUjpW21ZhEWR1wDqaq1vMyKJXOUuigwp/PdzQR04reZSkUfQQ3h2pno9dPq7Ho86s0VTjx+sFefEK97lq8smsuwDxMjkma0jxz6l3JqdFg6olatWiPHPzjQFUqox5FbXSaEOKMLy3x7w1wlbWKjgKetx6SQUuH10YLYTdnZ5+/6vNix+up41oJjB8t3hTVwgGzvvOe6qitOe3hnWnPLJVspXT6eVlpUk+dNU9tVrLrvG1d5rx4rQWsufV6CaKb0cEOKucirMoLuhx9C/3nlvo18xthTnXzmyixC0BeULc7LP0lCq3BXnZBe0ZRW1Zl2VBaNEKsTQw9KYMNhxEXl4NE2NKzva/LoNsKnQeX7kVbY1nP1V8xs+/MFaRAOLpgF5z9FAHc122QLbQzPbFFlk3PBOP7PL4VH3fjgjXLzInqapzPsKzaaZ+VV/rufH5/e+oXzjyLQ6KClnrmmsya+Ehly6msh5uXtfDlrfl47sVr4sZwLcGGAD358gd5Pnix6fSef2VxYkMurQKMVw60jkcckBgz3NpDp8OnJTUl3xenYmKx8X080PfidCbzQi55iea+z+S64ZL3byb2wzoOag/wAy8haW1XjGBz17Q+32BlxFeh5cpQk2mUcsnbwErrBt5aKyzbOjltb3zqMNdTZRgtyvOkXpNB4upPaYcrcAbHXV2O19WZsIL+lAc1GUQYKAazTdNXaqrAi1yNsi87XsHoLfDFNeoLGK8/Y2S3OXRnBNSLydYIG2ovN0wFvrTsejPq5bCpHW5UOggviBR+T+y+W3li/cPGfdNNDO8r+HzjK3E1K9S7zO7A7OvFj2wPqcwxMXN+YwqqJrGvzO+wG+GlLdDtyCi1wDnW2OFgKA3mcOl5naqDPYgJylVVlFnnOirKmyZ183GOdF2plfA+amIrnIlq7uNlQNYBBHPUzCPXUz06w8Dnx39vq/IiA9fi8zBm/SNV43d8m51hUWr3tKoGv0x1ur8pt4197zOHwvCtpvPnWbrXuj8NufC77C2820UPaYTl/eNRX+n+e900AO2bvWd565Jhx+eh+q81x+YM19H+Vezz4aPavusLD6h5PfLaacaHLU+OuBHcU7SgHKLIHDK4oYo1pWiG5GyoOkCeHWpiZc4HANrp4Gu0t0ypqE9Dg1VJsEshBtcvxep6h45635FjVzeMsfR8sra4HQ+f7Zx1Bb4Vbg6TLzh5rnvY/LfZ83PSWdlh1VPPVLPy+7xc0Tvp8mpPxeg5ur0/A9hw0aSBe9/C05z4L++wLue7CiDb6XHjrTX5rfHQY21rVAY8QeGkZnLKkw0ouNRpzoywyTE0HQ6bLExVN/QVm6elNcHcBcM+OONBSD40pwo2rFVKtXG1xW0RQ521bltZ53SViNSrYPbmGw3rvn0d/pdv51ovN64PMrDJ656G+x+v35Z9hTX+HXtshv/OPm+nzt3efTc0Gozmvx3KpNtiMNsvZZO29Xx97sMTJz9W7wTRXDZquHt4975YZ3TnMAss5h0wxPkgKvq7VzoOS5poiZ3VNdx1cnJmNDm0TVNhA4zDC6nTG54A9Fk2J4EV7wGjY2DCIXEyx9D849SJxsXqrHPmgvqEYw9gHfVPmeB9a8k9DA/bYv0zl38WKprvh6DjabfS49JQCLf2LD4R3l90Ekxvqcdb6Hmpsb3PmOgyc1gDg2en5nptvV6Xm6gKnS1+kY4QwjTPP6EiG8uZ2+zOG6sq3SRd4fA9aTTxvpTpjWhau2rlVVW2NPJx9ecTX0emoqmtlHdphYkhSzUIc49TK1iDqahWPs/nheefr68ytLW4bWedt+r2fkdnJpfHt1k+/C72uMpuXenZTw56aTceb+jRWstclq9IrK66KToaXWUrWfH0Aau/x/ovnI8EbWzaZ+52WGv8NL0BgFLFFdZ0ZHh2zk8vR3Oc5+grYZDaRpaEtdRM+N1LsbogaK+BUFV3cKMabfVCTK3ZgtecQ3dTeTpxZVKGnHoc1PJSYmfS9biHeZvoMmZT9U6H0TwDdZaWdF6pUZZ4dk4/ucLI7gPz+rzSK1n2gvRh22GgLyJ5dfFYQtD9N4gB5krBQraFrDRGmdUHHHv5dBel9ZVS2bKDpavO07PCWVMqvNngdSr03aaUu1jDaBnasYL/uViZrnZQ8WgqSymqDpMEmNqbytFWKRt5vGmaTF3iqepyT20245k8dXegSi8vsPShgyvqGbiw0vM1f66V5JSev1fXn5JD60qnBG7OKLx0e0FKy0OpAi6t9kwYPS5BV8hYjWXMteXMsjVFkQc4iR0T2oRZQFWZq7So0m6IDATu7TLNo9GbAdOlJXH1goh2taOsqQqleHZu1i7uCGZrNVt1WIpoyWuQuEQOIeOV5pOSr02LXZ7HSksOcFHNGQS7kcQzSat0v0UzymPnr0wbCko1zM3KndwDEzTRrglPNRbCYMLB6Em8BF6CgwC3cJPnz9tBTya0ojKg54jR+YIoNUNSWlbvFnPFrnVVl9865F3cN6ngqLdVxWEsNCCFFkPS6u8stYaS5jTN3N9LNY6k2ePVV3CGkgRFQuAE9t5JJB6i5k++DHd7UcTkDev4EalQ4OypEKmQM5L0cERjUAx2STqJLHida205JVsteKqdtw2XTNt2Kqpp750AIf2oWdvM9NRhmwqz/VfPPWC691uRTn5bgVOfimaDanUdRjitQ4unOOjcwV5gCdDhd3gYrnHdJBbJGprGSxaY8Tk16g6HvTyyugcE3YOhM6FBMo0N6ag7xIEyTiEm8By5wOtd0I07gRwTpMRFRBBGRGOBnWI5zjRqntQMNxU983qPRfL/QTa3Lywwb0TEjutyHU8Fd2GP0Lmz4I11PBFENByBJVWF22Ngjla8muZ3ikKM2JoNTKo9GUfevic+FycqZwUvYeNz8HcE3InIkUbgcmoHRv4DE6MJuwoJucQMhK4A6mjDvYkEjGzjBguYwpwbgTDelJlljQPf4W7Wlld5rQO6mk2IIs1bWpbICYgA0TaFLW4HrWDPhCQmZm9ophcmGJCgm4pibHylGmKo9GaPzp4yUN0JnRcCVsSBPZ0JnQPCd0HAnbC8JuDMGZyDop2McNksTgl7AkT8T2MiIiCCE/qKwiQcDogSRjBaGLPSoiunzbxTyY3pSX0Z03ZGXha1YVIYZS2gJyHP70tvXwjr66eGZ6AcIs4GmVzkOR4jgZcVx6G3kXRzEdGmG7sDRF9FQyoo0iTrHg6Zjw65sYSdjcEjgpQK5AgmgTgli5wI5OCga6vQWHazoWTAYQsoR3sj4TEhd5GOwt6PmO+mzW3Lnp82O19YtKwoxhmNCWxMPksJfBJRkw4SoCInSMIEhnGSBD6zTFJqa9D47vRzCcJjRF2Hg3t6QEfXoOSRxgXyu6BsUDQnijQEPg4BLxGgTNXzoJhQbdigGAfJVIVsIK4czJHgL2RCjlchkERoJ60xTV9pMNdZ9d+2kInU4BDpQwkRtjQFtTCUYiUPevSZFO9KvhtxnOP7ZjXkErVC0jQedPKZBFxORRoJHQOCZw8oMlgaBLhUD286HHP6ET5YU2864GdfGCkjJZEiQw47nESTCdAkitcB48DWEcicCauC65iZIfWuT05WPfn07llVHGx1a0dEY06QxSdCJ5BM1DOeczPV+uzDmvBvq5RVKFVFy1Lp5W9SSXEm+8SQnpA3iQcSQd6kNNSF3iSanSYokgXUg7xJC4k0upDSSB0aQOKSATqTXepA5yQTW6SvR55LPsdClmxmJA8hJlzepdOd1ZpbzyqSF53ElxaUySvL//xAAjEAACAgIDAAIDAQEAAAAAAAABAgMEABEFEhMQFAYVIDAW/9oACAEBAAECAY7cXLR8l7yXftpdWwXnrGu0TRyxkFocjV53Z2MhYEOkk0m4yMjxQotR1lmHDsp5d1koVly0QQXkqhAMJxmHyABr4IdWjEjTeTrXuJM4F2KQysekqyJKkgqvYn9Q7GTOynWNgCZ2gEUaRXIEkngoZBnND3gSxZgkRAkagodvJtH7bwYP5YFYZvr2aMUU9dSpDQzKdOWyR5XmMImzYLM5OK0bdXj0uLlOOJdzMscE0B1z00UUtuTOOrLFMY2TASWNiaMgrmgNfJ+Gy0kTV55saL67U5HWGKMuXMzzyyERGVuoHnIG+ImjZ3ORmHK5Vy8prmaGKKCOXifpT1pakMLRewsi6vK/bEq5GIwPgEfz2ZXqZ5RJHaOdGQ0zUU97AIKR1/pvH8y4fgYrg5qDIiCWK1UjiMUMoXmMXkK96qgzXTpeh1omOVQAcjX+WwmK49hJImNRLsMshhkkJNrHl7g0pJTMOixMkuHAAAuLiLDWEaTmOrEqVeVtc+nLXeVqSiOpTrOJQd5ydhZTIjS36ln41/Jza4qTV60ePEAlrs6ukxJ6LDEjtIdjHywCYycVVjEUEMSgTVGzjuS/ILvH3JbHF843KxX4T6rJXCtvfMR1q4GhFGTiKz994fl45oTHBdMSYokriHJks2HsDIcUIJo5W9YpGycOqhMiQL0TA4cyMRWt254b1fzim4qavhCLf5Hh+Rtc7x3P8g8GbyJYh56YvIkok9A2+zchHckhjuiyrtN6y2JbtifceKUyMWHsIIlGSlwqoIA8KPv0EpkBu3OQl4Sryq14p14avNXXEk5qzxNqK1xL2GUq242Wy3ITO07XY8eH1S52z6X0hiv6G4kksjmR9CKMOIZEkkMo6EMWGlUJGY5pFOK6uDu9YscbSS7FFnMVaPG9zlqWTk61Ss1GHbPE3f0Mgks8o9tBXmfkKdyPkO31ox9yW2olikWGyzCYIa0sYwYirhiaB1II8WWvDPGcDCRwp7FrFqOje5B7jpT4WT8d+sBYIzxyXjYK+wVf0M8/I/d+z7q2w9QzxccluVc+oK/1GhIMEixQR07BDeXmsct6Lm0MwKLE9Ywdmm0F8ycczWuIoS1V4i7x9QK8pIsLZIBwyNMZDKbHZpPYymQ2I32lhZorfvSqT0li81h6yZZqFokWIl1MH1DGkHJRwwRwtB9avVnilydhm0+OumRKlBbV6DmLEVYWa8fINzlrkp7nt3OEMWtS8lLYEqSd2VpWkrX+xIkiswzRcqHc9+wdjbyoI4zA8XmiyhE5MVECR1xDJFMLcTqqaUDNENKLickeXe498yOe0HDzU4a9Oj6NK92Sbq8YxY+jxrGIJ6njXkWZz6CaPlPExuGFajLDZgqwrGxYxRuekMHMR0zWUKQckbkgIZINbUg9bFEyU6lijEloUo5YjFxcq1oY/SSyI0qSu9vPH4BwntJA0c5Sdikj5DIA1eKuFtZUtxB5PNIjnkI85hYVroTPdtc1FGlSRZmKZvuliUfpqV+9zFq39qryb8j+3rWpJ+rlYpuSmuLXSJVb4BwkZFICVYPWEhwMn5NH+Ry/ki86vNWOUp8l/wBRb5erzlnnv3EfNRchDavQ7H5HLyI4qrC0EKz134xKVqksa0V42vSucJb4dl3hPpXjkm2MkuvKI1gZNKsmdgc3jln7SBGnhZVsMYgEXIka1XlyGoeKsV+qRpYuc9Lbearch/I7PNV+XTl2TibFjEu/sYgknbXXk4pYp46QsV5BGEySWSwseRxdXABWQHO3ZCpJUxkST4krY9bx4mG7hHCU7I4iK2nFNu0nFUF4+zxl6lJjJEjQLWAFiLmK/KRc4U7JgyKapytnl552HIrwsMkktd3eXaRqiRpGVWMJKrFwSHjbbNCYyW7dvRJDnBD8irtJx9x84CPkBxxe/ZtcNdt2oub5qKSHwg47iuB/IK5Toq068EUgOUskkSJo1MrQi3RSyRYbDiCNI4dLH1iQiTHyUSgSB3Ias0TTZ2YiQnpwkfLyrVswmP8AHo+RFCK3IKtehyNm1PBJXHjCtWP8lbxePrxuV45jfucLas5RksMGJrm80B72DLJ1jSGMIsYXrhbdvFaZXwP33VMbTGJ9qzZHLw7Sn0lue3AHkV4qxYejNBb5u5EZl4k6M3Hn8pUTM7niqtardFmTjHmaa1DOWMv2JeQrpJE40mVa6jQUYwbJGY3SMlEqHFYtXIE7AliRJVPFjy62M1wIapz1ZjBYarPJFDM3E4riTjOT/JpiFjlj4qzHd5SbdZmlkYoxbGaN6+TTuyiIVoS6pvZMmSZ3tZCTk6TKCDCclOE5uCfinGakiWvV+OfDjh6N3I8774yBV5N0uz3kspMr9JU5EPHHnH1WoLTmqE0693ioHtza7VEGAD43t8YSpLkEhJeZPiM7kOH5QcOe/YYDGAebHfgVtC7ACp4vEiapYpLxU3GtSkggRY+RUyVHrydt3C68eeQvYXkKiuFAAzWbJfHEkcyRBq0td0xTjfxv6axR1a7ScichvDmbHKivVt3JZm7Uciv1+R++11bnI2fdGOQvdeUViLsVjvblbK5hrcjSlpkw5XC4MB32bCThHV68aq8y2kYZs/O8ZZOSl5zl+R4nlfG7w/l9dI2ieNwV4yPz8vMp06dfMxqzxTxLAkKn3ayz1DVs35ecmkNVYM79/Tv6em86+ZjsQJkuWRIDgIP8Gx3Ml1qmVr9K9f4+QwtuxhqfWow+hkMxmE3tsHsS2WRAioEIVdddT5LjZC8Nn3EnYku07WvtLZSzHZJdXyyJVII238DNrk8aVkWtNQvX+Hho260tT9b+njoil9XzMfmIxH9fxEJitClCIfreJhEQgZWZ8cwg4s8U6l8kkaQt6RlSrI/ZhcEw1rD/ABHZe4vIff8AvG4J5bvH8uBO7cueb/ejl25z9y/Lm99v7C2ftrYM32WC4JzN6eiuXkkuSHHxSztiWaTyJLG4cK3cYtmOYSE3wwTNa/jwI80Ro1jJLSNDyFbmXRuK/Rfom/H/ANF+mbihxxpGu0arhU4zDApjQeK1V4/lI5BJlcNVaI1Px2OSCeGSCzBUh5CuMjniMbotpUxAwbHHzNWd/jQYnXToYwntHd/Zpzf/AEC/kS/kQ58c0OU/Y/e+7903DbNw325VuYPOt+QtzFmawXyhnTyEPHQSrLGa8sWnxqUVKLjloNBZSHArBgw+YuV7b387ze9/GtdPH65r+AhNf6/h4+Hia/0v1360URVZHyUuOKVIPqiGtWlV1EZhel9RKaVBCRLlzIsfNMNEfCQ61rWta1rWta1rXzr53ve+3f1M3p6F9nNcDFGNV48mxypUdfPz2WZmy5iF80Tj4M673ve+3be+3bt27du4P860R1+CuiNZtsAfAOAxD3ilNhpfOUQyA7wgg42WyuaOMdOhXNa1rX8b38b+Nb3873377Y9m/lgA+AcPIJHsezXByEPLT260ySdzIZDK0jvaK4uNjYCQw1/W83m99u3btts3ve/gp5ZvOvQxGDydUGlEDmQSakRokhjrdI5vYytM0xl9LDJkYkxs369z/jvNddfJO/jYf51op036B8IlTrKsKypUl3XlkRYVQBixEpk7kkgzGHIhJjYVYICc3snfp6GTv37D/HZPqH+N733KnBOLAZoZaseO0ckiwx/Ya4Lf3DZksrdBwD4slcQMVRy7H479u2dNZvfbtvetaOa11668vLoGB69OgGGE1+iS9ghVcXEFuExGPzWqnH/ViHzIbBkEjdWxsWN2+N73muusI0B1CdddQPjfxrCvlrN73vsX7swm9xMrQ5LEw2qqApXpm+zMSTrTYRNJ8bJEgkMgbN7LBuwO+5cNs4G3veb2W9vQS+nphAABbvrpGXuQnxkpGgICMOEkl80q4w05Yr8dcJU9ixfAnmIum+3fO+xIJfbuG2cIEfXQYt6+nffZigVR8Sx8W4fvtixb4OHOmHDmiCpSRQOnycIJU77+vq0m/ne9g7L943lk7d97+Bir4+YjChN+nr7Q2fUTLMX3o4QQ87SgltBerpaiYKPT1Lk73vNFdjAfQneazr/OgNa0BoYshl9fb07fxDaFiOTRd7TXTZI0c2qBPNhLIW8y2973m83vvv8AnXx33hGAoCSfjfbYDLgwp/RESQSMSCOvUDSRqiRfXaoY5VkjYD/Xfxo/BGd++a+dfwC3y0/8aA6VqTwuPNlI6gBIuPh45a5j6XYVqyUG4z//xAA9EAABAwIEBAMGBAQGAwEBAAABAAIRAyEEEjFBECJRYRMycSAjQoGRoQUUUsEzQ7HwJDBictHhFVOC8UD/2gAIAQEAAz8BqzGreyp02gOZCp1PKU/WQiO6cTeUMvVBwlB6l0xZADRHNopUKFDlJWVslM0lCLK6KnjZSUVdSERZFS3ug+nBJEIVKBpnoopPZo4Jzqrs3wpzHB7NNwg9kjons/C4I1CJYwu06JoaA0IPxttALovdpyhAjsjMxCkZiLqdUFZZnEoCygf5EqyusMWXGU9xCp5ImZ7KgQC05XdlXYJbWlvRNY/JU17qhWdEgfNU2HlespyQgRYhOmCjKETHEoh65hKLaXKUS6USOEFX4WVkQeMLOiFOyJYY1RpZXd4KFen4g1WXFVQB6prmJ9GJPKShWoUaYFiRKDKbRGyyTl2VSvUee6qOERAQY2TdeJXkjlCAGkKVCgQg1izOJ43/AMmmXeFVZB6wqBtkAncIgTTMj0UMiq53zTGnM069pTWC7T6hOa6QS7tKIvVEKkSC2ot5kIHRQiVzQgECUdUSFzIBiupHC6kIESECFlMKFKKIUCya8d02oH0z9UaR8NwMBM/8i5zDY6hFrc7fonVGUwRuvDw9ExuFFMAKrVaSN1lw0kDMnkydAiSWwYTaTe6LiCUAFupcF8IVlJ/yZV1722p0IVcjK4g+iY0w5UK210xwytavC8zJaqLznpWITmNipTlvVUniRZHar9VVZ6Kpm50wMKBcVKkrI1SEcyMcI4XUIEa8MwRmChnhBo4d1eVmruBRAzAT1TjjiWNN9bJxYQ9MxLwapkDQJgY0Wyjqm5QbFB7MwMQnRlbVcCsUwDLVn1CxjJmkD6FVBd9ApsS6k4fJBzyII6SpbcKZKJJc75KGyt+N/aymFJVRzcxMu2WUe8lrh8k2pSnfqqlEyWy1UqggWKJtqFzQ0BOFnNsvEHIjS3I+adFyszYKaGHRcxsgVJXLKyyr8CiT7BRV0SuYKCuVZT2WYS26c7EmAjoRZNptLmsErFVq3kysQCccE7wqmV3ZfiFO/iOc0HyuQxGHAm5Fwi58g6KyB2QOyBEQqIDTkEyrcIGsJpIFzwsoaoHtXuoMJ9EDMMzfRYfFtiAHdwn4cljgMuxT6lIiBlWRxcx0FGjy1b914l2kIuYbJzZEIE8yAdGyLTymFUdurXR1QzcwTfDV0UVAUKTxCtwkom6qN2UCHiFnpzY+ie4uAMBU8LSLnECNSsJiw7wqjXZTBgrCYar4ZdJXjAeDTnvCxrZgZR1Kq1qJfUJdKmQGiCjTr5tim0i4Ewg7ymfYpsLQ611Tc0EOCEWTHvAKo4Z/hNp5j2CqYmpzUy0d1CLndvbBahuvBdD2yFQxF2WKe0HVw6ou8lSAnAw64TarDy3VSieWRCqERl+aIInReINU4HVWXMg+yhkINGilikoQgWq6kKCpKuiUSiNlJQAQdZZ2GDdfiGGDi2qS0bG6JZlcIqdFihQ8Np5XawjhPELbAhOqPLsxBJRoU20yAY3Kw9alGUHrColhptGX5QqOQlx+6Aw4dTEheM52YESm02Q1X4sqhgcE1rtSQgBogm+KHRJTA2wUnVbINMIHjHCylyDk7DvJGiY8ZHfdNYM7Psg8z9kNIhAtmE2NLrLUym4Q8OWGOycwI1ApUKQgRCc0nossoyrKUVdGVKugDKEKCrcD1U63Qz+JAELD+JkqGVg612QEyQKWyqi2UqpSdqQn1cXl1VNktqWJQFOBogGF0LGUTLGZWA6lPxNImpaEKLyykw1HDWFTxtTwyC142KzVGBXPHNVDeqgmmgFkYSi8mF4IGdB4lpRUoFXjgWsGa/yVDENiRKYxuZl1Uo3cJYqdV4dSN1LedNDdVF1D1ZBwTXNhXhEPhFQgRoiXIDhAQhSVdBAqWmEQIOoQUIIHg6ly/ChiK8gqliqrjXqHK3aVgmvH5R99xKJouL4Rzk6qu7FNNIgHuixmaqZI6L3QPZcsKqWeGKfu9yj4NSkOlin4bEPJgmbzumP/ABE1HGCdFmrBECVI78IeCF79MbVayCSVygLwTmO5VBxDXlUnCKZCqAkseq9MczZTSYIIKtMoFMfT5SZ9VldzC/VPoiCUZh4lpVCmctMwd0bNme6bk5lEwbJ5vqnA3C8Xssi50CFBQIQQKsoRhEq/EsctigbhcsyjHDdchMwq1Sv4FM5y6yfQptzAmo7ZVMPiocC0HUFB+JmmE0mlSecrTqsJTDBhiMx1hV6D6dUVCOyz4U5jKig30T6WHc+mJKfiMOaLqTs57KpQwj6mjiFRqOczEWceqP58eGSWg6r3igZQJJ+ygXBQ1lAHlmU0vadCV4b/ABXNBjqVWeYYwNHqnupwWEE/ECSD6rxQ1rhfqvytWrzGJCq06rQLyE6tTeXt8qwtTWAQqdVsNeLqoNKiYGF2ZU4Idsg60W6p4GZhkfpJTn1c5EeiqUrsMp1QZTZHSVeCmV7QExrdLoBvdEWhEq6MQngKyi3EZVdFztEGwrKFmCM2Kg3QlNA1hVKtQYekZk3IWHwVGnVLQXAXKbUxZfTbOUrxqoc+nCrPJNKk4nayxWLAfV5O0IsfmDyb9E4UgO0I0cPlcZcSsmGHopYqeacgn0QiNlh6rpLYVPDt5GomogCZIuOqaNwFTmMwB9UACNdxCykjKR3hV6hkSB6KD8f2Q7z3Q1n9kCeaD6oPguiwQbh6kW/dU6dGTEkqa9MtcYjqsQ3EODHwE6AJ5fVU3XgIZoAsjHKbdE9pg3VMdFJlhhOaUapEmEWaOTqYhZjzKTKkaIqnh3w7ULD1BlLgCszc2xUlHop9UfC0V0KIXiG6shCtZOCJ2UCei/MN8OjOdZacuu7qvHphiw9EfwxdU6VQPyDLKoGm0wNFh8pAIQ2XKpqtvF17gBco4AHqeydoI/qnHcz2CI3jtupMC59ET8I/+moNP8OmfR5BX6qFQDrn/wC04CG1Hf8A3dPg+U29EGuIfTBb1BuFTq/wng9tD/2nUzr9QgbGzvRZTYhpRb8+mhWHrtDK4gfdUab/ABaNQOZ2OirVKxcHWTTVAhMamgyg26B7oPE6FeC3KSvE1QBRywiX811L0Q2yLQuWVGLqIOxDBGrgg2i30WZ+inZNzXCb4dgmh5UlXRCJVoV0E0heE8uaNVlpo0KfJTJI6BfmqgblIPQoV6MIMzUHOjun4atnbiuXoSqIpjPVErBMsaoVCrXYWVLSqD6LQKg+qplohwKzaG3XqjECzU3SSe0ohsDKBveAFQpg3zeht9Ub+GGj5wq5bJeO8TCqfCBpc5T/AMLESObKOrbJ7zDnA3iDZPZdhdA1AKfk5p+e6Iv5eyc9kVHR0d/z1HdScr2wfsUIAcSOjuiew6j/AJUiJEjUdkQc9BxbU0idf+VUDIfZwTHv7oEd04NsnHVAW3QLU78wRsnGkCpVpRRDxCtdQ9ZqXKoxdQboHE0v9wXumqTJTUMvKEfDhFtQlHhIUIyjOnCm3zOAVEbyqbGcrCUdBQn1VV1TOxrWrFEXrAeiLn5jVcXdlmuQ93qulP6p34jVcQYgJ1Kq9mpaYKrPdDS75FVWNl7+bodAqdORLqh7lPc3M94Yzb/pZRFJt9yVVrXLiGjc/wBwqhPu6VSrO5sPqi5zQ7KSNmjdQ/Ix1mWM79U5h95cbH90DbKJ7oPaC0267z0WYzo4WMf1H/CB5SNdI39E4jNcgWFk5keuqFQeC6zm+U9O3oiDkqiO+xRpXbJZuBq30Uw9pB3KMS3mjUbwmmmM9IVOhMTCadoRmAspjgKjMxWSoQES6YR8ICLrIO6OqkKbwjKc9wHVeFThAY19tbqMZT9VnYCo4s0kJurUag5RKdT1UcZRITCTUMyspIEfMqpiKWcuAHZChT8QklNq38Mx3TG0jygEJrqQMBNNFwETCxvikBpiU7AYao+owyQsXicY9zWwHuLjOgVPCsF8zt3FVMRZginuTZU6TfDo875u8iw9FUxD8znPeTvoPqmi5bmP2CpUzzOYD9YTX21teb/JQCZiLaQAgzK74t7IWA6yO9kMlxEfZAgyJGhCj4rbE7dihHNIB+x6rXP5SOYj7OQmMt9Y2WpbYjftsVnpCpHZ3YqOVzp2BTmHM0A9QEHjMwAEdtOy5z+yzvgpjbplTm3THPIN4TWNytsm0gXO9VhsfhjkggWIITAPLComplJEqkSqZqX2VNpy2TJ7pmtpXKh+ansh+Yb6hBlJvChhmzVqtaO5VfEOjCUzl/WdFj34mX1bHWy8Uc+ipYdxDYTqrr24QiiETYo1KRAGoVbxS7xFQwzG0HEZgqQd4QvO6ytBoVInoqbqEVHEuRoWN2qoa+dpt0VX4aYWJxbodApi5Ky8lJlyhrUILW6fuUa/LfJ+kW+qo025naDoYb9f+Exgim2Tt/8AixFUw57i47AwAiTLrkWv90G7WDZUOjW+vVXLd5UgX2/v7qb9bH+/70XMDvossbtdyunboszck+hXwn6FS0tBPLdvog4A2ugx0Ecpt8kDIBv9j3RYcjwbazsi0+IPqEHAGVQpOOp9AmVQTBHQJzW5WEDunZ83jHMqhfm/Mme6dXvUrl3bQL8hXc9vMDqJVOoyMrm+qqvxmenUdZVRVBc4kdFiHumjDAN1iRV8Quuq76kudHYKrla/PPZU6tMOmFh6znPLtlSw1YOLgYMrBNpiH5nfpAusfjf4IFGmdzcoMYatZxqvjVxlU3MsEG05Ca+jcqu7GOysmnMAqs1uZwRq1S3RVKVSIkIhVaw5KbifRYgG9Mj1VSILZCfUaXBzm+ixNElwJciDDwZ7pgGiadlGyBFgvHqhgsNz0/7TKIbSYPQfuUGAuefl1TqhzPsBoOiZTGWmA4/39VVrv5iXE6BZTl81Q79EGjcnr1QDQBuI/wCVLDG8baotcL+qAfLbHb1V839jZQZ03QIUtk+ih4PWxVw8WJRytqjUFCZHlf8A1QeCCoJpP8w0PUJlcTo7Y/sqmHMsNjtsqYF2PaejTZX8yqPtJ+q54JMoNKeQ51NhcN1FrrxDCyr8w7lCfRExMqpTHlTi2IKfmsCfknUnNmbdUTQFOkwh3UnRVSTzkfNPJkvJRw+IFSMyaSxjqZbe5tCwzcP/ABBJCy3aUXM94WgHS6xWQuo4hwadkXe7qeYbr3DjrZN8bM0rDOdzkT3WGr4jNIhUWUxlLQFTfuE2EHCCExhOVU31SS1YegyXMWFxLSQxYdmHcfD2WWtEWJsE3DYWY5o07lQ51R5ubn9ggHZn7XDOnc9Sn1bCcqtJsCjOVojrCFPzDa4TiQSPQLmi3/Skf0UAFT6zIUHsdVEf36qDB01Ul47LNI7ypDhOn7qaTwd1molp1B/v91LQTqLFZ2hws5u6D2g6SgekHZc3LooIKFWo+1gvBxLrHVFzM/VN/wDFZrS5TiKtvjKz11kqq5CLoCjzBUsSHFwEoYao/l1NkPxBojli6qYSuWOv0KKJRJVlUyl2U5eqIi6qQBnMDusQxmXMC3unsqZ2vhydicKWy0OiJVRjnEVN0ReUX6KoNyFWZ/McPmqjKgFV5LVT8OKJlx7JzxmLpTLmyxNeqWgQ1VKAIq6KmWQTKpufngTO6NRxGoB33KDG6zf6p1Qz1M3QBg3PQK8zJ/ooHLr1WUi0k3A/dBjZPzPVTBjXVQob6he8Cy+iyqfl9wuYd7KHuHUStHdQFOZQag+f7oMq9j/RRvvlK8OpGxsrOHRCNZ9Vor1imUsMHjUvA+yp/kmtHmsmYf8ACeZ8H1Rc5zjuVNUoDEKHkqmxwBcJXiNEGUKFaHGxTKhGUqhQxhwtc5TAIJ3WFr4c121OYXF1mEoqq+qGMFyJTapc7FfCbNVLDfh0U2gaAK6JR0TSJIU1XdkRUIk2RKGSDqmsMFVajM7Wy1PZdzYTUfENzC8SoGyVTZSz5ri6p1OUahSVkBy7BQAeo2/qnF36WhSJDeX9WqaNif3Um+23RAaBZRLvUo1HZosNB17oXbsNFEHoUMkdFFWO6lkq6LY/06qCB3Cku9CFmotO0XXO4eq98O//AApYDvCzsnqEKjJ6j7qWydRyldCnEiyIpVj3VXFe7EEB06qoB5V/gm8plEN8p+iOZxhf4p3onvY9zQTCecS/XWFhmfgzMsZso+qouwHjfHEr8qwVJjcIY+oXu10CFKlDiV4lFe8aO6NLH0gNxdEh5XuGU+rlJWQKEMjgUGFxQNV3SV4UNbqjVeQ4rmC8DDN8TRYeth3kETCOYhXK99rCoMwnnBML3rj3RQPmOoJsnOytYIkR/wBLTkLzsEcwNR1/0NOizeXlb1UcoHyUXsjWg/y9h+rv6LZZcp0n7FAv7OH3QnKfi09Vlqj1UgtKIJCIurdwrlQANICgn1j7L3tM+i5BHVEEDuoD29LhAuudde6LXQRKNQTKd+TqmdyneJM7lVJF/sopNZF7onYLO1xiEfzTrWhYfB4N5qmCTN1Tq4qrVyw17yQFneGlxgCwTqWemanJOi/MVwxvlajnhFrZus1BDxG+qp0cZTc/om1cPnboURXYdgUQs5ChZ6BqFB4LV+XxFVp+EoVquibRxA7qXBVPCDJ5U6MuxUVHK5QpnMVTry1gKDgA0XKdSdDrFE1HGYtAlAAjMSdLW+6HlpgDqVndG25UCGD5oNEkwNyUa5l7S2ls06u7nt2Uq3dAiNijlndpv3Qez10RnuszA4fNZxPZTPZEGFqOqvPqtf8AcP6KHiNjKmkB3n+ih699I3b+3Axqi1pRH4c+2sqvM+GVUDhNM6q7eU6LsUTSOyD9RKNGiHN0COUJrHtMwhUw5rg7IOe7e6gB0LxKcQngOgWRdiGDusM3Ff4iJy8s6LCGkKLXZSNAvExDAPLMo9FIFoRBsFRpfh/huMP6Klh6c9Ua9WrVNs2ihxU1233UvYJVJzGsdEpoe0NOy53oklBrSX6Ki58UwiwS3zBVqryXiIGqvJOuwRqvmwY0wAg4gN2+/dBjLrQC52AUkF1yL9gVP14WUE9CjM7rKcu23bsszZ37rl6FX0Uc4+a34WHqob/9LnN+slbdp+6uuYFbdFCAAEJ7sNYBECMgQm9MKm8/wgqAN6QVIPim2OE4ErkVPE4k+LcBCh+GVBT0AWd6dEJ5CFLBX1KP5ttrSntxYIBNk5hkyEcQ8F5mFS0JCoE6hUHnzBUzcEIFrVlYuYlOFVobe6NSDUEFMcZQY6Qixr3yolU8ZU8N+io/h5mmCJRY3RF1JtMWc8x8hqUXwXWtbsFmHRgsB1WVrepuVbsvlwjjKhSjlg/VQ8sd8l3BQcIcLoCRJ7cbDtdS31UweFlfgA8Km3CCXAFU/wBQVLqCVqXCG9VQfo4FNbVtHDNgHR0TS2FNd0Jr8M6i54BKZgsQ1rSCpEgpxMTKquBFWwGiGcQmuu4SUxwy5AqYeJaqFNs5FS1yhUW+XVHP5z6IvpSNk7KROi5ig3EtedkyowOb0Q4f4ZyJCdQrZtwqmLOUsAheHS0uhnqv6NyNH9UZAjXX0RcQNpVvY6cLqylSiU+m7MEajQQ7VVOkp8aD0IWVxB4Q35LksjlB4W4QgPhKrxDA/wCS/EH+XOPVP/D684wm+ip4ugRTBNtlVoVDlLm33VemZmVVGydXpGnl1TTNkMI2Gtuq+IreIHuaOycapzGT3RG6f+YDtQEAYiFTFWCVQcPOFRzjnCoSDmCplgyu+iZGqokk2+abTr5hGUqmcLIIXiOfdQ5e9bOkpmHa0BCowOlGNURRjqoCzVSvFqwhSwj6k2aNOp2X5Wj4jj/2US+VMeqsrexPAqeEoOBTqFUgCRNwg9shWQ8f5SocrK0Lk4XVo4YSi3myysNTs1oKLPJTVTFhpywAqdNoY5oWE/EaWgT8LLmy5n9FaUMmaUMqIKIY49lzu9VCkEwj0V/KiOoXqp3Kd+o2Tv1J02cVUjzkqqxmUOMIkarmhOBF7olgnVVWWBsq43VUi909w0QpvJeE019P+1TxD6NFx5c4cUD4bGdyVEDeZKsPVQwKOEcO6lBBA7hQrKb7hfJw1UtXvx3aVoryrlSFdXV+Bf5iSmgTCa4WC9zEKKydRdYmFSxdKHwVkmpSbI3Chh2UuTSU3I4J4qA7Sg98TFkcO0iVBRnZW0uuyaXaKnpCZ0TAUwBNClHx4Cz1R2QhCdV3Q7IToE07AoA9IVV7y9kzNkWmHGX79uyk5tpR1lQ0SgVKtwhQj1R6p3VHqgRdNeJBCIMgXH3HRSyRde/p/NchUNB6q08Lz7BI0To3QjS6e8xBj0VRpkNd9FUGx+iq0nS0GPRCrTy1B9UyoTUp77KnTfzAhPZU90HEHssbUbDabl+IzemV+IeJIpkLGMYAaZlYrXwyq4/lFVZ/hkfJGdCr2aUf0p+mVVOiq9FUJ0VSLNTqNSXNJlF/MFVmE/unZYVXaVVG6qGTKq5CvDpSwd5RfVcOi+my1UbwiDqD81JUhQijwb+oIHRwPzR6ohFT6qCRsbhQ6kej1LSPmppMXLw5QPYwRZ5B9Fg2jyfZYSPJ9lhif4dlhY8gCwx+AKh8LQhSbytupGWoqFcZgBKFA3pSOoWHZ8MfJUf/AF/ZUD8H2VN3w/ZU2mMn2TD/ACvsqZ/lKm8fw4Qc4gNRA0TomFUOyeVUIVaYTqh5xKfT0EBVJhVJ8yqEeZVI1TifMqtM6ynOgE/JZKMKKbr+pC0UUwbaLMf1dlUbTzmnTA9EWO0t0Qr0Q5pRjRGVlReYVFtrk9gsOXAOaW9ym/yK3yN0aZiq2O4uECAWn6IIET0uvck9CIU5ypDR0JWoUq5Ctxc02lGSHBcydF0c+qJGqIsDZaDVAN5bFV8PEPlU6rYqxKwOJFw1YN3lIHzVA6VD9UNqx+qzfzCq7Ry1B9FiQNWysSB5QVXaOakq/wD6zCqNEeG76KpECm4fJVGnyO+ieT5T9Ec3lP0TgJylEss0pw2KebwngTBVR3wH6KudKZVeowSII1TzXYHmLEoMqtY0k7lBmGHU/ZXQfTaD1WStpaU2rhywkaWKrgx4TvkLJ48Vr2kCREqyuVLoRDhTbadXHRNNOwFlno5mCS3UIg2VQ2PMEWmQC2dtkSiQi6g7/aVmw89QveO/3L+qy37rm9h9IEjmagXRCh1gnSjM3QDUSDGqdGiB1QlE+VYhvlqFYto/iFYwGC4rFNPVYgG4KqA6FdQVT3VE7hUD0WHP6VhejVhejVhOgWE6BYQ7BYQbBYPoFhBsFhRsFhhoAqI2CA8oVTEOloIiR9U974lvSSoa1trK6BeAVN3BNjT7JzjFwFkJKspUuBhOaPLI7qPK0T9EY09bqi8k5Gz6lAizQPQIbhMGyyN0XJ8l/hL/AAyPuor1R6Fcs+pUtcO1lmY0rm416lPIdFOy7Lsh0Q6IdF2XbgUfYB2TY0CYdkzomdE3uu5RPxFO/WUR8ZVT9RVT9ZTx8RTyfMU4/GU4/wAxf600a3VIfCmDyiOqzYkD5rM75wv6LPi8u2qBbop2QbtdeHTFr78L8MwiFewRnfgAgFAViEMpXuKo/wBSjEVT/pC5Wjsv6FTT00M8N+DWiw//AIwgghwCCCCAQUIRHGdl7xzlopB7BZsW93RqtwDn5joFZQCpPCRwCACjhPDkKjxR6KcQ8dco4X+qhjlfv7fdDr/kFFFT7BRRR4FFFHiONiZtC5T8lZWd6KPH6yFYIAICnrdABCpaUInVAIEcB7MLkXNU+SP5p3YDhzqxb0EIyoHthDgPbB4EKfbHsCEfY5D6LlI31Uheb0WSvUZ+pocoCnsqlNt9OqcbSm0j7yo0diVTqjke13oUMhMqwKlWXdHqu6CkdlyKalX1AU16vyVlf7qXTvF1HC59f8gIIIIIIIcZCIF/ZupOvAhBNKCBQV0VFOFJciWqQT1XgYmnU2Fj6Ll1WZ8IGnCLSYTiTCe10sAB6qpUINV5PbZZRZEbqeEcbLlhQah/1H7Bc9T1j7LljrZXKIugdRCBI1UOPr7ZRXfiEEEEEEECgiFPtAqERwBQ4E2WSoP0mxWSq4Tym6lgHRXI0govw+Q+Zlvkgwym1GaqR6qNkEArKDPA9SrLvx17KWT1/dWeerir+llt1QUIl09FzH14X4d13QCHCdl2RKPEIBAoIIIIBBAjXhb2AgU5qIKBQcs0olvLNtJTmAWPyU3yzIgrwaoc243CDm9iqjZyOIT2v5nw4dVVIAkJw8wTY3CYfiTG6uA+apudAcpE8LK6gKKTusErw6c/oElZKTR2UmUbuQAQnRZKcfFvwtwKJQQTQh0XZdkRw7qd+AUI9V3U8ZXdPCizk02lDqraoQrIFNK6FPZunbqd00K8heitCgq+aLbhfoJb6J5/nO/onf8Asd9U551d9UwXIzHug3ZQyON+GYAdSB+65Az9ZupsP/xZiOigQig05nLMew4BCeE7I8Aggrq3CDwIQQhBBBDi6dVAQdqoNioXf2QgE0ojRPGy7IynAzsp1siW2RY69N0dlSJuXD1CojQA90DoLKOEKOJMr3vYNJ+qL6vTKPuVI/09t1AQ6woUnKFeOAXbhGyvKJKcPRTur6oIQtkUeA43lW4zvxCA34AqE7onpxGqf1QITQmppTSjsjIDk6lWbAloN/RBwB1BTCbgKk74QmAy2yLe6HRf2eA/sIIAIkaWQBe9x0MfZcmd/wARmETwEz/YW3sBCNEOiPRAFRoiUUTw6oAruu6kIIo9UQrronEaJ0o9V3U7oRcpippibCIKKJ3RG6PXgCrzwn1Vb4QE8YYMqEZmW+XAFAoIHgeHorSSEFLQNS65+akzBKPb5rqiR+6j1UPI7qGqeFlCndX1QQlBQhwkIooqOLUJQlFFMnnVMiGBFOR68TwaV3RRQOyIPZAC6am9UE1j+gOqPDup4wuyDRLiB6qkJykuPZFzDYiVlO89lF7om5t2Q6KVdXDo9VlEdLLIMo21QQQPsnqrqODYQCClT7B9qQijxMotCvKCHAolE7+y6mMp5m9OioFuYuLR3Co1PJWpu+aHUfVU2/GPkqY6/IIjyU/mSqz/AI8voIUnmknvfjECJd0V5Nzxjt3TaTsskk7JrxofmssudtoOpUHX/Jvwk+0IQAQIR4niCNUGCEZXdWRRNkVmMItMHgITgJgx19vMIXh7STuvEY+1xEnr7Hdd1Nkdt1Hr1UlbQnj+W76KrB9076L8vXzPEB9pOxUvAAglCAdmozc/5VvZngVdbKOFuEngR/kEG2qJueLnU8hHs2U8KlbmEBnUqhRoinRku+JxGq5z7WgVR97Ad1SYfeEkqnTLC1gHcIFmimypOoONQAtAvZZ3EMtPwrEVB5Wwf9Se0wQwfNf/xAAmEAEAAgICAgICAwEBAQAAAAABABEhMUFRYXGBkaGxwdHw4RDx/9oACAEBAAE/EFUGbFgs83LUjVylCt4qA2gONQegteguNwQPhIctWuA7UVLMM+o6gW8xhQuHSWjpsqWGo6bmBGNIucQEzGLjlaPbMI0sVbjjuVOGU5OJiBhZdy8W4hApqNKOHuFRFTqFUnyIncLDcCEs28kcFb1eZchpZHUcnJ9EY1Wr3daYyYNIv7lzZi/9RDwA1FFwkZzTR2wY0U0EcgAa8zNi2riodIRqXK4Wjo1HAM3eWb9y8yz/AMDCEdRYvUycQqjXiyzCpMLFhPYwVx9KohBLcI2FRpQXLZJ00qUK7mhhAlnF3DAh3uMRYPBHWqz1FeZBVoqC3czK1LlmUJ2nMCoNJbWbIwudxWRgSlw3S3A3DZdQEviCmyCQkdlQ4av3N54cSyYU+4Xddex/2HzwLa37jxvnCtMYacpSMI0xX8RwbgjqD4ACGSjDiEFeEKKjrO47XA5no5EJABHp1E4ENLZcQZV4uXbocQsEMIZtHZKVBly46llwL1Gxz9D8wI1mlFvpj/kSGnyTFY9aS3KsM0P3G6RdBceXRoUUzRvAkSAOskupHwQHgfMzqQqMVmVxhTL4VcdQ4juK1MpEYzGbSyvcxcRDeUwxISmZBuOV0wKxqHXmNBOJQXmLsGejEZi1yIFO9eXEfNYBTZ4/qVGAB5OIvrQt9JkfZZYC6tRozM88HqBOoHJvxHqxcXU0R0VAgHKxhC29AgW6HIQ0Exq4guXNxGkteCYw8R6uCGzMMpUQwlRIbmkHK4i8azZbIr7lMsc5WQEeuaxExRR1zGYDiy8/EahNN1X6lCOgEu10IIpz/DVQoDUaIKAbXO4SwQdpmGmbCV13CKFjHmZCGZfAcQdNYiAMU5YZi6gsOplMYnMJU7gG9EAC0sJAZj4sqOFmEJrfZDDfEMmHDvzALAVxXBqodS+ILsxfqKSjRaiBKBzTLM6piGOnEoiuZgIpY4sLgItzimEpQMXB7KNUVf3AOIKTKPNSkFrB3MvUVyKuCtQrcNoTKMWXHJGytMSwcVMUDnl+IVkYAYtnnhbcYMHTyHjuFqgx1GwwTs/cXiT/ALmXZl8malw4eSXSoOqQtmQcSpAXxAQLPiGMipdpXxNNLVRqXQZImWKlKwacwogppgZmLAFEQW2kb0ZcL+5cBzUYFavTMAeZjFTyjpfTMclrl4nwUjtAL1C90nlz/UMt3GbVmFZIcM4vI46YwKdASJMRQ4XHcH1EWQwUQrqoTQ1IQxH4jXEWpo7gzoeQxKA8/wDkIj9RLHbBoiy5WYjxBZOXMbMcRURN1aMygYs3GwMskTFryRrwOmU3hCI6WB4hgprVYhNmuNRggpxW5Spl1FZ9AmOUhFiRAiBSAJsmtNkqaO5eqR2UgYNwgV3HZUVZTiEMwq1Q25iCQgG9kABw4T+SMV1oeGPjgHLMMvbWcVDiAtGNBiJZSblJyF4LxL44fAfmMETLyP6j+Hlojm1jeMfmOMzEFYIMnLlhYiPEtgIOYi1uTqOADwNwuxhJ4fMM4LZkY9QeqVFBOFpDD/zf/l4xFQxCq91G6kWYjvqesZCViVaRpj4ozQLCFIobDj7iKJDkoh4o8i4xHc1xDWYeTdxPAt0RwoYmZYGowlbmecXAIIaUl+ciC1OoWWADUyxDaCWCM99DoYNCawY2iajmIrVGHwIBZx3qFmdMPMNyeoqJ46ilScjfqKqo0lkI1erKushuXBAVt5h3jDQ1LyYmwlce4b/gxLpUC24aZhuNWjBBhJmjtl4hJaoeC7M1HKAaFsIsAX6ijrJGrAOVghEK2EWIFQ1KYNx70hpMAliMIwRyYhW2yIqOIu3VOoiaPtx/yPDVrIMkaCK5ho0eVqDLhPZK8N9x1ClraNTNIfbEq6DaKBbTcul1Gmwz3OfNse6OqZlAYUZ2Zbu1csFwFkmVHE8wgSHETMYyMQODDBwgVGFiHrUyLc1RnzKKUDuXhCMViHYqvJKSgeDFwoORn1MYP6I2LBsWDroS0huISgYGcpaRxWrNEeY6bIaYlSl0+ZqrIrj7jTfw6Z1KJMYOxNu/5hNaVRbEQ5gYLbhlF4liMkxBh5qZoSA0jKCkzBKuFFtcQNQgtbmCj4jTEvYcRm+G64IAcRnOGFgV52R1RhZVG4LrthYC/ePiJLG2NKC46gAZV8xgAbmCpMQhdumE5IFUYgOZl1dQk6hbSvnDCAKnAiZgTHghWNlwoq5idxEBzDullGRNmqzKrhN+YABpFLlP7egsfTplTVC7SszL4L4I8Xb2gw1JvtE1Lpwhi83EaKpmjUQWDn4JbkGDLaFEyB/MxlsP6jobuEKsJ5X6uXALM7hUXDzLxxoDiZRgS2EhKUXD40hVstabmotZvhgo1jdQEsNagaaPMVLs+5dIlkLX6gsqDSKr6jQhHBbk/uKRciqyQRC+lkC/BJjxanMZfDLFm2XG5QkPiKVaQwt3GW1iBkwnMchmQ1FDgZlazEWxiLrLipcXGSOo2BKuCuu4CwK59sIVKziNsuY627Iq2cxzllbuO70pWiGykYbtlGzUDDA2ePGLiUFC98R8agCuEFqvLH1BJtuIwlRTR7QIOtgr+0MI2gaie2HOGYcLDcqjc3sK/EpkZWDA7epm9K1oX8S0aUiE2b1WJeFwkcW+9SgOaU4f39RFQBmi/nUbu7KQ5oyqpKVbKF8wSAUgWwxx7jHlppa1KPy0TeoslgpgdiFFMUUle4Ra4uiL1orClXA7tvjY9w8Wi3gQZQuHSLGOy2XcaqLTTuGdrfMqGGoVaw5mGjyqMKi67JRrI+YwR7mg2wYumNqix7oyQ61LzUWalMrYahccO5RKCtxdLmM1VdwlHENb4op6ZgtXF8QAVVcsaEKMgCEYJYZt2zhLQrGJmQ1QG48aNo/vGJV4LgJ4BR2lIOSBtopn3BSmwRgCWPDmB08y4TQLYriV2LbVSmkeUqXCFBy3H5FGabOPph+e5f6i1BDsokcoPKUU8y8QBmwzwhK8QUqbfaX8QSCFcX25upbavgTHYZG8xVADYZZ96gQAsDyac+IdCzdN2DiIysFXenUZCFCZF4hMeEhSGp0I+4nHYmYf4cRsD+TUCig4qX9lnjX+YTEF/ExYWt0wwuQcVfmEQ2vzErWZWnmUswTxGcZjxuwgXQOWYAKFkBaPxHOFUxSovnHHUpa5piWgDFqgHKNyIoSvzHKS6jeLR7ish5WQUqaKMXFI2rctvIVedwgNmS0QFsBZULhgLalSNDQx1tKuCmiZNUB3BUZwEq9Ewf8AkqV+BW/PUJYB92PcLyy4HMfpKvCb+hmPgPQs/mJ1SfLn83G1UcUw+Fg0ODUFQYLLoED1mmvmVxSywPypv9s3IVhfB/UozBVkh5DUI1s3wfXEOr8Gn1MKALocexeJX0gDIvk6hdCYVKD60/FepjDFP7BxK9S0eJYmBiuIhqqvrEYmswc3AixAeRKBXwS+yOG4QKWObZWisQlS2ag5wSqApeoA2c8w0JcZRM1AIgtEdVgGvMJqKgwXCJtX4yiakYR0YJVBzcRy8EK+UCAyhnUVxQW2WBGTMdCbgCuVxrmy3mF3ZmSInYZCo5aCkYARgtWI2arKFIYMG6ZVEPuBtoFRiy7iAAhRzEOqNacuj+4bAowo7/3coXR0pjyxxT3NqPLtfEuBc9CeLX6CHBmNYPld+qjty7yIfq61mLFQ4WH9PzM06uAnrT9RYCZAw6zVdZhjozCnkTH+zKAKRrA4YzruJ4LMgyX3jnzBF0IIrXp6Ox5ilCOi7BLsT7slGQa21ea4+x8MZZkFU0O9VFqJsKz2PWmDQrJaPbgeHPkl7lCJaRomBH2cNMUHJlZvRcf5RX+ajatMk1sZaKu3cEqc1GtJRxC1LfMdA68QaGqmQHEZYwQfqGKWytAbalZp6gFaGWyA9y2V6hUHLOpfmOKuJWiIaGX0MygVCbRjqKHFzEC9CCyMexWgq3I+rOhEHNimEPeLTZ1+UIRmNA88QcInQ1qDwc8kKMqXkfInNGi/LCgEmXBjg4+NQoybN2+OS/H1uEzkOUK/H8RpSGQKPXD4PmKVxayQOrZy+dQYyLwtra48W/8AJQlhK2AbomEzz0NQU37XNcBKvO4ABUbFWdv9mdX1HplsLA5Lp3W7jEjWRV9ezlcjFgVpQqKf0vHio6FlgpTf7lvNBscg5/UsXe67dHt1w6q2NCLIIydPTAULWx7Ox2fXUBgwUDT7E+odVYG3yXXiXDQVTDgLzXfVQbYYrSs+4Q1B6qLk7i53ojXJBqMS17qUAhGqjG0x2GJk0lpNqmEDOouo5bm5LZzG/gF+pQHAGA9kAhArUDdRmlMy/aw2XFLRnqMCxAsKj8kFeJgvUuY3PQREEC6uoxEg1A8kaBEac4CXoodqhiXis3cctydTqEEeuh/U1Z2K94iWGQsC3n+oWGjWoHwH4IrxbOEfn+seY/pLkh0OflrwzUACCg6tx4KIfaI5fyUD9SyAMFCvR49EvC40QoHhavnEQCSBkENXQZvP5ijdl6DR9VCmUABdB0TkSzOqI2RZ0o2vVcckqAY9LpOS/rHUExMBSth+jXn+cYELQ3/hd879mGwAARODyOE9PMS6LYDCeQ94SG2KQivMAMO2hDOi4oMxstvj3AJhbAFvZxfZz+imAFYPb06jT+RiOB47gyKuoa9WI1YNg5hYAdQrItMURSapScVBwCh1qIQKauaJE6uUAFDA6YahCmiUtsV3BFLcGUlbzUvEZf5QlJlLHGYTHISgiQgOAiPOaBRejbClzNah6IwBKG3mU6EOItRS8BGX1FHmF2DEV4MximLnui42cha0sCKwXcx8ODTBEcVsCWCz2xBs4DLLcC14Yi6CsbxmVE8Kg6O4+OclOUNW9bP+sVQkoMtvJ7b7+YUKtLEr7yuutfUAoJVovHJ9QKOGEiHxs+2o4zQHA7ajJtA6AZXq8ecy4aIEPK6X5zL0bK+67a+PmWxLsrN2W2fTHAMSjygVk+B8Pib+jgt7/wDh8+UEOYbBprg8/sZjEBgZFjLyNZ6+IXlApFXwX9ZlRQlKKXXZ6YDIcg34f7mHNRamsZ2Pjk+eSMrQWO1j7xXxHwrZyX0dtVH4lqSfS3k8yvVMW5LO3s87itApxVPk8RYNFBYxypbsSJfNjcKuiUQD4lQqewh8ROkVQE+gls19hS40RNbG4YGBlvD4qF/W1ghINgi1gOt6q0wPUdmPzDNosW2/EZmjI4hILKXcSJB/KD+Jq026qCU6UfjTR+Yta2bw9dfEAhWYxKMUhYwC0KZR1M9lQNw2AOiMG6eJmdwIRtsVHJUs9Iv/AOomalzyj7BDdVHJPaqEQTN6Y/BHZhVtn1B25epXwkPEGLF8EewYEi6deVwfwTBHq7r/ALM/eCDTqFo2u3jvErORcI76X3M0LulsHz29w44SDRFoKikZPD3517mOCEVOzLigoKXZdrrmBAMryf4wQb2WN5rP7YUMo5PTeY4AVhHi2x+oCB5DNHn6ajYDFnG8a+tQtIWH2vT6f3KRVGyH4fuoWQyGvq/umANo19Z4/wB3FwmCw62/3m+2EmZw05H/AAfjzEIb0j2fH9w6VEsMPtFhNyjL2cPmZABt/BRSor8Q9GvhRbUx0rNvfF2wEItRmHdARqk1DEFvUAxQhrQeUhhgKsEpgi5mhPdSoKTpTWAhoVccmzJK9Am/N5Rws8txuvjS/qDw0CKB7h0UwAy/iJikVQWsRvqxcvmEVvddfEBuH1rhoBYtUQwUbLzFcEMQuFRLBPzCgSUUkKKL8wLGviKok6YEE1kgorviU2WF1iGXx4hqK2qiXNxZ5E/gillqwK9HxjvcrYTXmaOD+PvmYlAMrFvsODiWDWZQar3CAIjOVW9HeejLKGSOFg11caKFLA0+DwQCJVsNGNx0HJKFLrtMY2lbcZmMjBl6/wAyk1SMLjs+v1M5BpQnCcwWSwG086+xn4YFLmmXIXTXrDB9g1WZkDZgR4H/AH1DyxV30CyGW0H1lH+4zKq3dXk/NPmNenkbXpP91Arxag5/4/zCjlYp4Tf5iJUSJlWeTPf9QGyt5G4XRtf6gy2AImgQMGoARQCKFFmbLzBUgcgcZYbiWWR6DErZu43BuBRCqxEWhgKiamrEddQgsW2A6qo2SPslS3YxZcvzHQ8SoI2yjEGwYhdGG6uES+gcCEyAVtLnUjauSG9bqOkbV1zFVrPNxlYxK9AuhYsIA8p5yJLlG3lWA+40GnYxSFneuYVIdK7ieAmymNaCtSn2ozkVf/yGJLK8qGhv5VYea0VPY2vz3jEVneQdl58sCObTMHl/39RSjBpa/B/mMgA7OPMPIKscvb/sftbycqcEzlWP+D9Ri3lW5pArBKkOkS/LTMQNsWcPcfJ7qv8AfXzC5K5HkK/X6j2XgP4b/EDgxRdb/wCfcINAhfTX6YWS6bHN4GvzUczdo88P2xXZe/pWf96gKjhbw5w/f7i+Eyh0Ox/3UaKs2QbC+Zt6Las2ePu4Th3Pn4fiV0/sjUPS0AezcXABajSsYm3Civaty5JGYdRTcEHIt3CcI8TBwefcEC553iCFuAoD0weaTQHPUQxM9d4l2+wvQTOFgBx7ZYV3QKq01E0hrXL2WI2uJ3mEUsIJlHC47K6heJ2hINwcNNF5hikG/EeZckfRmdxUCHbcY2ocoU4ErkgVdzGlqG9ug/MsfdmcDOP+PuNd1ULTNeL0edyyrWqy2+Xf6g4CFrGVde/1D4HRr/qGLd178+f3DSoeR0eWORhxY9LH6IpqvJ/nzHAGdnjI/qBU6SD1G28aD8wa5mshwyjLSP5M/m4xtlQN13L8SzVovr/YiCH/AJLuCsFr/aW0BL064mSvKB+afxGiYDB9ZPwwvjdtdp/8hhurPQsly7gHvr+SLZaYznb3EwaXzHxQvB8RIJD8FkHXl4YtNgeDmV5shiGeyOYsEHZOhLEGFHiE9lUbtcL/AJhYVnC7E4gXsDQ/qGWAKOQi1p4FWGCZgrF5MoVBzyqChQXRCb2Qx6gyg08sAzGe43S35iRTMcyArBi6C9R3dDpDHGOGUNRUjkeIC5oj5u4aBo7j1EdLnuKSGoLXiUxWoHDOCKAq5ygWUYUAkZLNPbe38THGQQC9LTF+fqM7sGHZDoe/UPM5+T5f6hZUUXfB5/7HFHknLt4cd+tnGNcLyuRgr0rFvZj4RSFRFQh1TJ41EA+EWenHzx9QZmsWpkWXkf8AfccGxprk7jS15Kwcef4j5ryecSq18UPVXDOwU/z/ADMpbsrjkzFGlR9XHEytmHsxGRop8PEGvm3wf4hAopVuHfs3AxiMWR6SJ4jlu6jEXVe00dygqOQ+0d0qNo1rb5l7AW8x7DeQMQHdgeGMVDiaBNCw2JSRwQhssS2rjoMV4cLFAXuH6U3cx65IXbWka0C4Lwwwe9E5gBwDHlIxVQ0dblwqNbAVqo6hsziZdGi+uIveDUzRViGu8JAOVPzLhjRtj0GFk7Znqw3UKQKZtsj9VK1zKFtFlczFeAjooyvm1/ETTuHBxZbXGAuMXTO1VeLc1NRaN8y+e3xAplBTTB48sHyha+se4QIGx10hx0+T1DRrBwVs8xLVpkfMLUXgS+df74h7bTIGsOa8h8MqiiZLVjmvek8/MUlum0/cdptqgdwqGRGeT3KBdrPZEpPiK+hV+pjcR+lVKqYwH5RnIPoY/ud4YPgH8svG95KnGMDXblKjBkRJQ56lJC3KvdpVLkufUBZynHmG7QRcQy5PUIS16qNKCeWbKAuARY6OpzEqhkZ8QSFSjMLctzIcQl9RKmC48ysYlBcLGxnPzqCeCiiYg05SCcoVX5jyxozPEuYUsQlNQXaabYNuKKm8cw6Aojnt2xtVRhB2aJRgQGYWZ2c3G9TUaHmXsMqMuuCV+ZLKzESBNOGXC+ZjVRxb+5fDSgYvx32sFLC4zbs8sAfcvb5hmUuKBXvohIhlCvgXt8vxULERQVwI/mPtjDiId5GuHv8AuCy4Ap53+z/kDdxZW8uadf3CEKDh/wCoZcaGt4rceHBuq4vZ/MYNNGD2eSANap1UY1wS3YnBlwrY28uouChhdHH6l4C6UDq/6EHCupl3FDF2uwMKqm1MlApOdQsT/EAy/wAXLE/VBzU9R0J6kbNajt6zGt6lBLFg6lSgSH1G2pbdsABCiCg1kl8rC8j2XUNWHEg4lGxpKZVtDBX5mH8pK1JIFhDOKumJnUzg2OJcDmEgU4EvGAwMLK7NYjtrUrPEdm2YQtHNyuDmAfNHPqGaGem2H3gFez6MfMLDtocUa/uIoBwQVbz2f3AtmLJxcdgt4UQxwU8S6HUMvHPiUDHqNht46hol+o67C/iEWrv8n9kDWB44Qw1WWv8Avy/UGsAfE4oFXW5i0HNi/iOFIKJ7uD1CV5bhaFC4L43Lo9V9MDJeJkGXs8xw1U9wQ5qGrgpZV7ijR1BBkEOkXR43qXV/CCrbLyLpMc0isWBw8Q9A1lzAziIo+cMUglwwCnQQ31oLj0ot7uYuodw9cGAApH7I8KXBWQPBEEIPpjaNA3bMGo4dQjIYZ4haCLUCasMQugXmanplKNxyQclPMPAcLG7hgBWOrmSLrrc7Xyv4gAs28hx95fiKeyi7+2MhoWph+JwQiwqLg8ykKrHniNlWGsjOU5mBpGDuX5qENDBADTdf/I+KhjBGCp+BL1KPIiWEegjXF+2Cg5/sxXJZKNa8wqo1brpjV0Z7l2jklWhWYUU1GggPqVRJ0GGBQ7VQ0BS0tieId4cEQn2yQtc2heZXXf5ik0VBaALjZflUfAA0KpYuTVq5SoVe48k7BNWVVDyA7YEBZXcNZ9kWj7oOi2nlG2VfeYqW3hKgh4KiS6ruHRsWw1TuFmQbARO4QAD5gX9kSb6TOszKoXAu4xbE26VB2ND5UhHe0Pot+LuMHgAx7bhWoKqliSjc+Yq7uAFtnubmY+C4c3dfMI7b/cu21uN6IhpVm8QmS8dS9EW035PMuxms3uGYBS/UsMZcR4WH4EsoiADlpmnKwy+ZZRnC4v8A8dqglcP/AAYhD5jSsMahpTY9Rb7pxOGavEKBvTqkjPoLY/mVCL+SZmFrSRlBBWbXeI1UrsS7utLGYhNjMb6HnqKLCvwTbBvFy3K28xVALrMGS04WzKg66hhE7RiaIPLHhS43Gq2XcQWqsZDZiLrWnLChIJbLSQIBEzRx5hfcdJQMF4hygQbw0tPnUPcIGVeXAV6JhrkU8uvwQDPlTnqEyKNQtAdeZVyYiqN+ou91HKoNBFnLTwS2NNR9433AISsfqA0SYIK0NZgbd6Qdnfpm41rqJlzQt9wKXTT6ZkFq4mR3p88QEurd/qIlOdR2g1b5ldR4VOriJSw7TZBVWcRlzohJIOoQZErMx2XIfxBvD2HEIr1caVM+ZirsozGRS7X4mLFEJSxe6iwBKfEEaFTLQVekxNs+YUGleI2bX1F2ynxHiVcqBLXzKxuEbWvc60u5VFxtD7mDr/yJ1VMeJcFUeoogQhaLkEkPJ1ODLp+iZOgbtj2h5f17mQjA158fiBQUbzDAyVSkSHhZC+2P3KSlhq1zFxGWb9+oVoC67inDD3UYZXVMoDvi1z9Ril2dRVeBoPk/p5lQwksa3HKTaP1K06PyM8bMy+njcF1lIbQ0l/8Al68Q1DiZLiNERHqGoUo0FR8W4ZRi98puiPKhAcOQgu4sppCXKFo0/wDYoFrSmJNsBsfMaeLUhWEhrEtV2ctzuzis2klaIvqA50sZULlm2VLfxUzQV9xq2ZlWRjFgePiXBr1UusEdsOhcJQRc0kEAYhbdp+Y4IbgqEPmN5sQ0cholYKtwrgL7jMTSueeuuIwWKJd88szAOOB35g0Gq2dZlXZV5cQUHcqDIVH9xwvUaPMsItkY44xHbVlaCT4YvTEErr9sKxad3Me3UEru+1wwdHo7P5+4dbpH0k+Y19suisXX0wVy3pO4lNtpv+4/mX/4S+pqQnEKMWvCZit9IgQ08qIHf6BH0AvxHXS/EWtXCncpjrjMOp03E748v8R+krVNIdxYOSCwGcZCpD694gLdoQoT6CGl+giIQHqKLWFdFcaiJwr3HKqLEKTULCbl1GncubjgZjjZ7jcAHuN0g+Ybn5yuOTxcWBUlhoIeVa1TV6iKFoycX0H0S3JK9xrX3FWmKoPEcNtQBxbUJcKuhaPnmBQjWN8w805UcEw5nFdPUErV+IBf1G4bhC9HNRqlHZZ+YHpXSFVCs21yU/uZyP8Ag5IGVEwrEuYfmUgF4P5/FwxRlFPCSq1pT+Vf1C0uf0rixTtIBU83/wBiZW+Ja0rXMrNEDHH1OCuJWJrcGNHA4lZY8EbtDjzAVaSqVTkgqlh2waI7JmUPLB/UNwwtXlCfgEpG3U9SHij1lONi7CKQjq4GSxeIuBFdxpZfhhMPrQ4K4pgXtHthXN4pAA5pQ4TH5ZjEvnGbsPJUaKl8TIVXVMzLeKWX118WoLKNc1USLU1XZwnqUrSxMarH5lYFFFKs4vxHKjO5x8vMFb2n9x98CXHBBZEZDmDML6DwxhZRS7F6ZTYEArKI/ohN0j4ox05hMANor09wUMLWO5WXDYGU5itJHhJgKjdlsoqOi0yjMrCqjTZmVjY4GvEzOg/AB/cxzilj3UqbrkMNAuv2/wDkMKc4/wCRKUdyqa51P9qGFyZwkbJ3rJEX0NRtgoGQ1F7rH7mpN+DmNRyN8wAG3fMuja4lYGuc7hjZF5Jr2e4VQj7l6Q8y+pRNO3cDhl1yNq1eybv5CY5XMmilm/sIvlyeSZP65gX9cIz+OHf0wv8Axh9FHioHSlev/FRLV0GQB8EDMKCnyz8QsgdqFmYEjggEWpcITYLiY1ucrX4hrZbxbMpB0AuGsaw+WEAxdQbKSsMLjqw/S8QN8Brp9QVXV5K/ETp3cYn4AgA6zATFpXm5rhYagrQEFwQyqYaV7Ib8BPj/AKmIK/kpl3kfyTsCiPI/9jZLBXyf8hmjruU298wa7j6nCrd1ApX5VM8xRi+Ub3Fgx+EpmFzDjqHRDliq6IHiUrITrYrld6jwteoq6zuvhiThHzMABfMbvNHiD5jTpPmLXX3TE39kxrT5mtXzKZi+YhyvuXG1PzH8mkN0WEiKM4c6EjENXat0ZlwhjBjglyYpE/mKYzSxDJ0JRaH8QdqywYmaZC/aWXqz6h2vLASq/EK3a+493BsrcBwaczsW/RChq07JnQPqGlldRCjGKxzKLsgdRopbF+af5njj+r/kRicD8Si7w/ZX/wAhmZFr1WT/AHcMR7bPB5go03KlGDEDeCp4Q8ZefGGP/mtRPU9Z+Eb8QcFDtERoqyoJ4iU1HoIB4n4RSohuNVYiWqj4I7UaHP5jYlsQRY8sMm1ZTEqKJt64IXbumq+obLayzBO1/ZGpMEX7f+QwGU7gQa115nOdwPcG+ng3KKaPiXFagiDCsJVsDm5c6phYALKHgh3Q5mZkjrGzi+Ir8ZzEHcr3g/qDkj6Gf6ikbMjUId6r9IGXmwoec/8AY8rMluY3eplkceoOtwd7gzmB7gf/AAKdwKbiTmAjBhglupkiM8NRAZjbtlPLM1vEYps8n/gvbEdXUW+RhYqMM3E8uYJuPBECBMJecS9DEsPEGztHfMxHWal3gkl/Ivilm4vUaA3C1hXU3wjFsI0gUWXiFVmEKbqA/wAxItvBxFK3vREbpxG1cOc/3KeupQjeJdFyDh81MBwh+khwXyv7lVnIIZOLihDsXrhlvfGKldnnMLLKdy1bhyuYO4BN5gO5VpWGYIS1KEp8yyWXAHuCJ7jRkiLsYsLhRmXemW6g9x/2o0L5iDDOxLlL4gF1ULGTEswMx8GK9MtjZbGtLGwSuthUYnBX8SsfLj9S5HQFbrD+4d0ZJdvEN7ywMifwwpCua+udj6JRyDZYnxuNSAC2UodwAU67iXJNz/gljpC0/SKd3WqisZPKHeqzi4XlanjLFe4w/HExt5iGzi1+HH6iUa0e2ozrnGNx0ryYmJ4wiFVuLncHMEjXmYYDqCJZLTvMS6nqiGLgl5xGdUxEkTg3AjK4NYXjBfcDsTPQ5XbcbSxSDubIFFJFYbwRzRqZlMRfUZXVvNbgjrYB6qLCjjD+Y9ZhVvxHbK0HwfxColhyPcoVnMLMCUE48ceJUgpeof8AFKBDRvCvpCuYhHEVfOYAXi/cz4gKzYbv9z5WpivTG2HMdVuFlRM3+o/bKGy1+AExA2qf71GKGjVdagVFZ3fP+3LYut4zEcFLvUVHG3MtxmWlxVcS03BeYre5k7zGnOIgaVTCkwxvzDsnMsKcRPWXqJAhqYosmrYxJaWQdRg3C5juNjcq7/8AGbrLYn1Cm8MKzFRARqBBLFtQ0ShCgRuupWtsJ+cwRjyfmC1aL8CbhNL2O+D/ABC6FZ3KiCzEJir8JkXS8y3gITeJmWnqVgckSvJEYangxHUsX0dZjVR6qVNMsy0LWDJzZfan+Ame5wPNL/UsseFPb/yWXtwJUzp5IbwnjiMHgGPfEDa7QcbijLULNxEwIAc2jtZlriHqsxGW0HW0BWFRZXiF1lhXcL7biMZzHAWQD1AV6jXNExApErDMbKSpYAXLE3L4RAZcRDzEB4i7TZcbwTgCky4jWdwIRIWYHhK3EqrWf4JYK8FItP7gsbFCFemMCswTsliKQsrkmbR0aYhCsxwfmXojsJX2vEMxs8kNaNPeGFKR5pClh1YNSuEymIL4QLpzEsX3Lvd2nusEJaqkbyGD8S0SkC+/8sJktZfb/sRlhx8VAGsHBCULro/5MJOy+3Aesy+7hdcRfIsQKDEAG8XBBV+ZWVioWrE2y75gdkjcAIbCQubgGoYPFihniXGGDtxejlM2WNK8Qu0mZRWu4nhDAjTZBTgjeCW0AiOCIJAWGC9xAqo7dVDMytWKHxc4lRi0vkEtWrxFHRXUNjYvYWMDdca9QwQ9yii3Q4hWqu8sfTBC1eiEsO2bUGPN43YTlXEVgvxAJmTEo4qG6+4GCVUrwLnL/vqMLaqB7t+pcHKPsXb+CKmm3PgQwGmBDfmUo6MXClBt7uWCtPxDevN5eCO9wqWkcYJXgg8BXEajjxAnuHIQLAEaYxC01AuGY64MRoNYjbuDvETMtMuteJSoZjtIaQ6iXbZFa8xSxjBQF1GoRZRBYKsvJl9zTDLcswhjuJOUhVqMX+fEbUtTeKXd0YYqYcYhQNrUsymRYRMi00dxz7oCQEMprTDRY4tliOCAQoQ3jcWrjaz+IgbjbV9RxfXruYCuEd2qH8MKGBZjK6D6GJqoNlwt76/cbAFdeIwU7MVnMoZM8DEVbDbx/wDImBcD9sxGi3MG7FUAcn4m8cY7gbZ1cio2wWYozgfpKtXD0vcGYOGUMuYXlaj2YFpbfERwalQu6gtWl1LzMpfvEyYjXmNt8RLkgF0jVeYZC5ijAsvdpbQGJxkQ2XCZ8xnIXBFYiqiiI8tsTbNhHlUOL6mPFC5ntULqAsfHEWrx6hzf1TMhdQzOHjEE5tW4UWLTwQlWuJQq6+U7hR4lSBvio5RTbOYW/AWd44fKyqgH5vV/AYiJbfgmDLRHIZaxjEM2C15/iKa3y8rLWvNcxgBguUloQmC7KbdTzDIBmEjnMFdwG3qZdWWDBmENqGQZUxQJUIog7lneeiLXTRF4xrkgCaxHKhdxuChC9Lg2E3AJk3FVXgLW/Mw5qBOUgcFTqhjgDEXWsXEVQaygjbAOm4ZkzCMDUQPiPTCxcLtLzjP3qOcM2G9rP6ghS4jzMRRoT9TX0xtGrlsg4YhxVnuPOSr6zCt0rzcvolYFJg3RRtdEZT2lOAV0QLJH4PUadA74SpVX5dEx6q7alwcqKt/2Iz3CP6ijilV+CCunDUHGI05bjVrwwaUCH3EAlGSXKn3FS8EqKMVGCCDQTNtzFOxgAsdxXZIyC8wqbIXJimGJdocQ5W5kNIIyO5c3GqrZ2wu9sFI2OYHkaiIXTFbPpEWq+YDLWIrmj6gOoMoIhtzCB58wkroeHmUc4iOLbYgVpK1jsilxF1bMDEPbFQ0A5cI6PAuL9s1xFBfeIaV4PCLkxcAtr4IJPhG37loT5rcWi+4yKKrceja6KS7BkGHe0+5ShaMvPMaYhqZ1xHXChLdssM23UyqBotMwxYzBRgVlzEs6jRgxHWHEVymTcG5jd1CjBESNkd5jqG5ewYgsGNVlRS4qKsGo6glRxlIyrZRAFXqZ8wyFzoI7zHaUzywWoP8A8g1BQh6TT6MYgRlxHyRy/GBH6cwO7rN44mWU8BYU1c/xuXVF+VXxGsgaNT53FsStqt+YV3RX3AoduQOCNIDgHXb1F5bi+DwR8Y3Chpjk7NPSEUWvC0ImoEyA2yzBh2Bs1/vEBhy778wh7S3mCYr1BcxVgzcvR4jcLI+F/wCKzWY4yy8hB73mGYvxHOGmIYvEveYjhDcFUcYlbSR0YSuaYhU/EwGcpa2VuUaWJhOIBpcxwDl7lDKZ4SKyzMEsnqmGCS5Sxs7hxADefUz9wdMqDVFoxVaqAQJSDk3/AFEFF5lg3nxDvBVeIh4fqFMoXxmWhYO7ydxTILVZ4g3drLyZjESMoGroC1jXsRwK8xiCjwWuEj6BtKNFuh89Q27Yv/nEqGxlDt7lrh6PErSsfEvEuiWpD/wSXbUQPKKFVuXcb4lkzxBVljRjv/wWnEFcGiIt3Dh4hUFailuN2f8AgLKZl2xF3HCVxcqVq4mICVQ5IspbAu54YJJRyc/+VAqbYgCSwQYXdTPhml7/AAZizDVqLOvjcUDqFgXUMuZY+EXSy5qH1jLAhXu1v4hw0ytH4mTdNhSlOIzAFFj5NRAE8ZOJdfJa1+KiCBppNgX3/sEMNoWaFeSovQdXfzU//8QAKREAAgIBBAIBAwUBAQAAAAAAAAECEQMQEiExBEFREyAiBRQwQGEjcf/aAAgBAgEBPwBlIVDoY4jiMizdZZYmWWIo7Ij7070SKL+2LLLKQ0VWjJOiTF0WWJieiEXoiuShaWiy/tWq5P8A3RpDdEpWLljVFjExMsQkULgasv0R1vRfItK0WvGrJzSN/JGibFIsTEIiJiJTSfA5P0LI26YhulZuTViafWti5f28osscqJZaM+VNcMjO4qzHJV2ZHZuoUxSIuxFiYlxybalyOiMXuESaqmNOkmY00nZZZuNyQpikmxNaWxsckiU+Dyc6fFmJN8s3pEcjfESWfLGVPgjltWyWeK4Pr2qRhtR5FKxMiRHFv2ON9iSXQhEpRXY8vpI3s3s3X2WXRGddid8rRySJSRKSZOfaMrSlyRk1HgWRt0zw53laP1KdSSJeU1wj6jySTs8eCVOyMhMixWRdI3DkKTfQpkst8IcrYk2VpX2J0bzyPIjFcdkvJ/G36JeXNT46J+ZFOm6Y5p8slmSVRI5EvR4km8zZ+pPo/CtqRDxqW6Tow+QktseWYpNrk3CkQbZaNy6NyoUlHkbvnRf4WJl6WXZ3pZLBBu3yVBeiMcUnwlZn/TsOWW5rkXj4YLYyHj4IrhIy4MbVRSMHg5HJzujP4iyR55aM+LPCSUVVkvHzXyY4SwpyXJjy5Ha9oWaaX5Il5k0uDH5mRSSfTMclOKaIyi3SZKNDdvWzcJiY/sTs2j6I5nOKaXujxYyWabZGNs8hPe0jJuWFtdn6clOf/Tsl+PBLIlFsl+oR+qsa5bNsX2ZcWNRbaMGOMZNR9lcE4QSuSR+2xydxMUKikLEomR8CG6HIvRMT++0+DHCMY0kQjTdmNcnj4JZs0rVJezzPDyYINXbuzxcKjFSa5JU0Zo/8ZHjRk/JTa4E2eVJLHbPGkpZaXdDg1E8jE8kEjxcDxppuxOuTepcoyHQ+RIrVMi7+2xJIpDoXR47/ABPJam6a6HxwJLoy492Jx+TF4Uo5U7tI20ZsKyw2Mw+KseTcuxp1yZIqapOjDFRjTdsSvgSrgm7Y3olo1rFlaLrSiWSiMt3NcGRqPQsiR4+R7XRlyJydjjfKNtMjtrkajY69DViiuxjTsjF2ba5Gm02O750X2NDQkhMYtXinLmbMbcI7V0eQpzpxfRizvqXZ40ksTY5XK2bkSdssstlyE5Iv8bHKTFJr2LI07YsrfDG7d6WWWWWcMXB7LFw9LKj8lR+TbH5MuCMuU+Tx/Kli/CfQpY5O0xOPyVC+xqPyfivZwcHA5qqODgTjfOkRKxwaEISscSq0ZYnq4STqjZL4Ppy+BYpfBLA5KmiXhTTuLH4/kL2OHkL0bs67R9bIu0fuZLtH7r/D91/gvK/w/dX6Fnk+kLJN9IhGd2yPB0xzsSvkoTpjknyOmUPRPWMa70rWitKHFP0PHB+h4YP0fQh8H7eHwPx4+j6LXsUGhqmLsfei4RYxISQ2S1XRZf2X/DRRRRJciEWbmWhOxaMloxMv+jRJci7GqEtNtjVCestFyyiv6TV9ksfwbH0xxp6L7X2MQuf6rVmR1wyinrY39iVIr+SxO/4JRTHGtdptKrVFm7+s1ZJV6N3+G5vRlCg2OLQjb/SX2M77MiSY9EQxxEkhrgUU+zaj/8QAKREAAgICAQQCAgMBAAMAAAAAAAECEQMhMQQQEkETUSAiMDJhQBQjcf/aAAgBAwEBPwCiiihNoUhSFskhRKGhooofb2M9D7t9q/ForumWuyI7Ioe2eJQ0NCGMrsz0X+FfyWJWQjQ9Ii7ZRQ0NFDGWNiaKGkuBfg2X+Nl97EYsTm9CxJRpck7ujFGyeNoSHEaGyWyjxvgh0kpK2QxxjKpEsMPjckN2Qi5Oh4pJuL5Q4tafZ9uCy+9IoojC2QwOTpHRYJRb8lonjUJtrgyxfldGGLXocbFibJQ9GSLWmMocaFJxdo+duDaexNt29mWS+NVoZhvyTFOHySkt64+zqnFyTj9cfRWhopig29jxL0ODWxxa7URiRhZDC7OkwOCuSMk60jwbJ4YrcuCGDFKNoeGpUiOH7P8Ax1F+UuDqqnNuI8bQ1Q3XJMU0lwKVcDk3yPtFSfAsXts8EeCPFLu4tobqNMuJGLZGEuCGNqrMWJ6dDyRVJjUXsjNXSM0/ODX0zE/jx/8A1kutUXSI53kmmnRnyNxpEobJIkiVElbKFEeOUVbHhklbRHFW2KNIbSL713as8EYsTW2tEVBWzDkU501r0PqvBNONNCzt7kiXU1+qQ+pUVpHTycm2zNNRxV7sUZTelZh6KSXlJ0RcW/G7oyxV6HElAmoLlDi+DxftiVPkk5T0zylVPs+zRXdrvQuoyJUqF5y22P5cdOzH1T05JOvsebNNeSWh5ct1wYpyf9+BvHhk2t/4LLDJNKapCcY0sVV7MuOcpXF2hwlg8p3f+GDK8sW2tjiuT5t/10Tyxm9olDejL0mTFFSktMX+EVSHoSs8RRHGhoX4cFiez4IwbUnwrOsmnigkOVI6Vp4k2ybfm1HmtHVTyxwJ1X2Y25q/ZjxtySrkeHHF+L5FOUXSMWSeR+LJwjCFpUSk0Y5zk6Q4xtOapmef7WjJ1U8v9nZjjbGxKyMSj/BrQ4/nTjTM2WUp3J7Msk0q+ibM2WOPBBJ7OnzrPkuGtHW9Q5ScE9IxvZgleaJ8nllkn6HR0j/9h1MqxiyXIwTjFuzqsibSRLaoUaZjerOWRjQ2X2Y0SVb7vtQ5NqjybexMbJvZibgrTIu9sTraMGXxypsk8UbmntnnZiy+DbMudTg9F7MGb45NtWmdRk+SdpUjyrbHJN2iOo0RiIbELvKNrsh91ZtujadND2ZEk9kV+qQm0hMbd6PKQpMU2hTbTTEJok9Hm2qI+KdMTXoiX2Xa+zslGhD7+cVwiKUppocYxk0/ZLElwZY/ukfHSEnQotqzwZ4s8ZI8Wb7Jv6OfQ4p+hwjyhKlRFiRRXajaGySGh8dqHLVUJyjtDlJu2RnLhrRkwp7Q/KqZ4yYnJEZSvg/Z+i5GzaFB3YkxpjTSEtEuBtJEciLsbHKhSE7KKKJLtR+taG41yfr9nnFexZa9jyxfIskBShxZcX7KXpiRRTHBsUWhr/R0vY2q0S2iKtHxJCVaHwOHlGhYpIimuTQkUSVldpTvRf52WxTkuGecvs+WX2fLIjm+0LJBrgckyPB6If1GUNURtFjTFE9dmNbK/mss8iyHAyP9Svs8TxfsqmJFFD47McdFf8NkOBvRil6JOzY5MixI0MY+zk+Dy/mr8E64I5Ps+RLaRGXlG0R3Gxxv0NURky+zGxj1sv8A5E6MT5fohKS4HkbLsSEWNjGN2y/5ErGq/gjJxMUk1soqiyzy7VY0eNM8f+aMnF2iOVtEXast90eSQ2nwNFv8V+C/hYu67Y+Pwk2fR7JFn//Z', 'base64'), +}; + +images_by_id[image_id] = { + id: image_id, + title: 'Example Image', + template_id, + text_nodes: [ + // + ], + content: render_svg({ + title: 'Example Image', + image: { + url: `${http.images.public_url}/${template_id}`, + title: images_by_id[template_id].title, + width: images_by_id[template_id].width, + height: images_by_id[template_id].height, + }, + text_style: { + fill: '#fff', + stroke: '#000', + stroke_width: 2, + font_size: 48, + font_family: 'sans-serif', + font_weight: 600, + }, + text: [ + { + text: 'Top Text', + top: 50, + left: 250, + }, + { + text: 'Bottom Text', + top: 320, + left: 250, + }, + ] + }) +}; diff --git a/src/storage/memory/store.ts b/src/storage/memory/store.ts new file mode 100644 index 0000000..1eed87c --- /dev/null +++ b/src/storage/memory/store.ts @@ -0,0 +1,8 @@ + +import { Store } from '../interface'; +import { get_image_by_id, get_image_data_by_id } from './images'; + +export const memory_store: Store = { + get_image_by_id, + get_image_data_by_id, +} diff --git a/src/storage/store.ts b/src/storage/store.ts new file mode 100644 index 0000000..ea85be9 --- /dev/null +++ b/src/storage/store.ts @@ -0,0 +1,24 @@ + +import * as conf from '../config'; +import { Store } from './interface'; +import { memory_store } from './memory/store'; + +export let store: Store; + +switch (conf.storage.mode) { + case 'memory': + store = memory_store; + break; + + case 'file': + // + // break; + + case 'sqlite': + // + // break; + + default: + console.error('Unknown storage mode configured'); + process.exit(1); +} diff --git a/src/web/request-handlers/image-request.ts b/src/web/request-handlers/image-request.ts new file mode 100644 index 0000000..a717fa9 --- /dev/null +++ b/src/web/request-handlers/image-request.ts @@ -0,0 +1,85 @@ + +import { ImageMacro, ImageTemplate } from '../../storage/interface'; +import { store } from '../../storage/store'; +import { IncomingMessage, ServerResponse } from 'http'; + +export async function handle_image_request(req: IncomingMessage, res: ServerResponse) { + if (req.url === '/' && req.method === 'GET') { + res.writeHead(200, { + 'content-type': 'text/html', + }); + res.end(` + + + +

<embed>

+ + +

<object>

+ + +

<iframe>

+ + + + `); + return; + } + + if (! req.url.startsWith('/')) { + return send_404_not_found(res); + } + + switch (req.method) { + case 'OPTIONS': return send_options_response(res); + case 'GET': return send_image_response(res, await get_image(req)); + case 'HEAD': return send_image_response(res, await get_image(req), false); + } + + return send_415_method_not_allowed(res); +} + +function send_options_response(res: ServerResponse) { + res.writeHead(200, { + 'access-control-allow-origin': '*', + 'access-control-allow-methods': 'GET, HEAD, OPTIONS', + }); + res.end(); +} + +function send_image_response(res: ServerResponse, image: ImageTemplate | ImageMacro, send_content = true) { + if (! image) { + return send_404_not_found(res); + } + + const buf = image.media_type === 'image/svg+xml' + ? Buffer.from(image.content, 'utf8') + : image.content; + + res.writeHead(200, { + 'content-type': image.media_type, + 'content-length': buf.byteLength, + 'cache-control': 'public, max-age=31536000', + }); + + res.end(send_content ? buf : void 0); +} + +function send_404_not_found(res: ServerResponse) { + res.writeHead(404, { + 'content-type': 'text/plain' + }); + res.end('Image not found'); +} + +function send_415_method_not_allowed(res: ServerResponse) { + res.writeHead(415, { + 'content-type': 'text/plain' + }); + res.end('Method not allowed'); +} + +function get_image(req: IncomingMessage) { + const image_id = req.url.slice(1); + return store.get_image_by_id(image_id); +} diff --git a/src/web/server.ts b/src/web/server.ts new file mode 100644 index 0000000..80d5b77 --- /dev/null +++ b/src/web/server.ts @@ -0,0 +1,12 @@ + +import * as conf from '../config'; +import { createServer } from 'http'; +import { handle_image_request } from './request-handlers/image-request'; + +export function init_http_servers() { + const image_server = createServer(handle_image_request); + + image_server.listen(conf.http.images.port, conf.http.images.address, () => { + console.log('HTTP image server listening at %s:%d', conf.http.images.address, conf.http.images.port); + }); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..16c5890 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "./build", + "rootDir": "./src" + }, + "include": [ + "./src/**/*.ts" + ] +} \ No newline at end of file