├── .gitignore
├── LICENSE
├── README.md
├── craco.config.js
├── package-lock.json
├── package.json
├── public
├── CNAME
├── favicon.png
├── index.html
├── robots.txt
├── type.gif
└── type.png
├── src
├── Actions.tsx
├── App.tsx
├── ArchaicText.tsx
├── Constants.tsx
├── Cursor.tsx
├── Hud.tsx
├── Keyboard.tsx
├── LineHandles.tsx
├── OldPointer.tsx
├── Pointer.tsx
├── PointerUtils.tsx
├── RawPointer.tsx
├── Rotators.tsx
├── State.tsx
├── Text.tsx
├── font.css
├── index.css
├── index.tsx
├── react-app-env.d.ts
└── reportWebVitals.ts
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Constraint Systems
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Type
2 |
3 |
6 |
7 | A directed typing experiment. You choose the direction the letters should flow.
8 |
9 | https://type.constraint.systems
10 |
11 | ## About the tech
12 |
13 | Type uses a WebGL renderer built with three.js. It assembles a canvas sprite sheet of the letters, and uses that for a texture, which is placed on instanced rectangles.
14 |
15 | ## Dev
16 |
17 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
18 |
19 |
--------------------------------------------------------------------------------
/craco.config.js:
--------------------------------------------------------------------------------
1 | // craco.config.js
2 | module.exports = {
3 | style: {
4 | postcss: {
5 | plugins: [require("tailwindcss"), require("autoprefixer")],
6 | },
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "textvector",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@craco/craco": "^6.3.0",
7 | "@testing-library/jest-dom": "^5.14.1",
8 | "@testing-library/react": "^11.2.7",
9 | "@testing-library/user-event": "^12.8.3",
10 | "@types/jest": "^26.0.24",
11 | "@types/node": "^12.20.27",
12 | "@types/react": "^17.0.24",
13 | "@types/react-dom": "^17.0.9",
14 | "@types/three": "^0.132.1",
15 | "gh-pages": "^3.2.3",
16 | "lodash": "^4.17.21",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2",
19 | "react-scripts": "4.0.3",
20 | "three": "^0.132.2",
21 | "typescript": "^4.4.3",
22 | "web-vitals": "^1.1.2"
23 | },
24 | "scripts": {
25 | "start": "craco start",
26 | "build": "craco build",
27 | "predeploy": "craco build",
28 | "deploy": "gh-pages -d build",
29 | "test": "craco test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": [
34 | "react-app",
35 | "react-app/jest"
36 | ]
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | },
50 | "devDependencies": {
51 | "autoprefixer": "^9.8.7",
52 | "postcss": "^7.0.38",
53 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.16"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/public/CNAME:
--------------------------------------------------------------------------------
1 | type.constraint.systems
2 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/constraint-systems/type/822966ab107a85bea01e586fbcaa4b9706e57c28/public/favicon.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Type
10 |
15 |
16 |
17 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/type.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/constraint-systems/type/822966ab107a85bea01e586fbcaa4b9706e57c28/public/type.gif
--------------------------------------------------------------------------------
/public/type.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/constraint-systems/type/822966ab107a85bea01e586fbcaa4b9706e57c28/public/type.png
--------------------------------------------------------------------------------
/src/Actions.tsx:
--------------------------------------------------------------------------------
1 | import State from "./State";
2 | import * as THREE from "three";
3 |
4 | export const setRay = (
5 | state: State,
6 | targetVector: THREE.Vector3,
7 | mouse: THREE.Vector2,
8 | newZ: number
9 | ) => {
10 | const rayVec = state.tempVec.set(
11 | (mouse.x / window.innerWidth) * 2 - 1,
12 | -(mouse.y / window.innerHeight) * 2 + 1,
13 | 0.5
14 | );
15 | const camera = state.camera;
16 | rayVec.unproject(camera);
17 | rayVec.sub(camera.position).normalize();
18 | const distance = (newZ - camera.position.z) / rayVec.z;
19 | targetVector.copy(camera.position).add(rayVec.multiplyScalar(distance));
20 | };
21 |
22 | export const updateVector = (state: State): void => {
23 | const visibleHeight =
24 | 2 * Math.tan((state.camera.fov * Math.PI) / 360) * state.camera.position.z;
25 | const zoomPixel = visibleHeight / window.innerHeight;
26 |
27 | const worldMouse = [0, 0];
28 | worldMouse[0] =
29 | (state.camera.position.x / zoomPixel +
30 | (state.mouse.x - window.innerWidth / 2)) *
31 | zoomPixel;
32 | worldMouse[1] =
33 | (state.camera.position.y / zoomPixel -
34 | (state.mouse.y - window.innerHeight / 2)) *
35 | zoomPixel;
36 |
37 | const start = state.text.linePositions[state.text.activeLine] || [0, 0];
38 | state.vector.set(
39 | worldMouse[0] - start[0] - state.lastPosition[0],
40 | worldMouse[1] - start[1] - state.lastPosition[1]
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useRef } from "react";
2 | import State from "./State";
3 | import Keyboard from "./Keyboard";
4 | import Pointer from "./Pointer";
5 | import Hud from "./Hud";
6 | import { BACKGROUND_COLOR, SAVE2X, TEXT_COLOR, TRANSPARENT } from "./Constants";
7 |
8 | function App() {
9 | const canvasRef = useRef(null!);
10 | const printCanvasRef = useRef(null!);
11 | const [state, setState] = useState(null);
12 | const [settingsOpen, setSettingsOpen] = useState(false);
13 | const [aboutOpen, setAboutOpen] = useState(false);
14 | const [backgroundColor, setBackgroundColor] = useState(BACKGROUND_COLOR);
15 | const [textColor, setTextColor] = useState(TEXT_COLOR);
16 | const [transparentBackground, setTransparentBackground] =
17 | useState(TRANSPARENT);
18 | const [save2x, setSave2x] = useState(SAVE2X);
19 | const keyboardRef = useRef(null);
20 |
21 | useEffect(() => {
22 | document.fonts.load('16px "custom"').then(() => {
23 | const newState = new State(canvasRef.current, printCanvasRef.current);
24 | setState(newState);
25 | });
26 | }, []);
27 |
28 | useEffect(() => {
29 | if (state) {
30 | state.setBackgroundColor(backgroundColor);
31 | }
32 | }, [state, backgroundColor]);
33 |
34 | useEffect(() => {
35 | if (state) {
36 | state.text.setColor(textColor);
37 | }
38 | }, [state, textColor]);
39 |
40 | useEffect(() => {
41 | if (state) {
42 | state.transparentBackground = transparentBackground;
43 | }
44 | }, [state, transparentBackground]);
45 |
46 | useEffect(() => {
47 | if (state) {
48 | state.save2x = save2x;
49 | }
50 | }, [state, save2x]);
51 |
52 | return (
53 | <>
54 |
55 |
56 | {state ? (
57 | <>
58 |
59 |
60 |
75 | >
76 | ) : null}
77 |
88 | >
89 | );
90 | }
91 |
92 | export default App;
93 |
--------------------------------------------------------------------------------
/src/ArchaicText.tsx:
--------------------------------------------------------------------------------
1 | // import * as THREE from "three";
2 | // import { Euler } from "three";
3 | // import State from "./State";
4 |
5 | // const LIMIT = 1000;
6 | // class ArchaicText extends THREE.InstancedMesh {
7 | // chars: Array;
8 | // state: State;
9 | // aspect: number;
10 | // lineStarts: Array<[number, number]>;
11 | // lines: Array;
12 | // positions: Array<[number, number]>;
13 | // activeLine: number;
14 |
15 | // constructor(state: State) {
16 | // const geometry = new THREE.PlaneBufferGeometry();
17 | // var uv = geometry.getAttribute("uv");
18 | // let texture;
19 | // let texScale = [1, 1];
20 | // let aspect;
21 | // const chars = " abcdefghijklmnopqrstuvwxyz?,.!1234567890".split("");
22 | // {
23 | // const c = document.createElement("canvas");
24 | // const cx = c.getContext("2d")!;
25 | // cx.clearRect(0, 0, c.width, c.height);
26 | // const fs = 64;
27 | // cx.font = fs + "px custom";
28 |
29 | // const ch = Math.round(fs * 1.2);
30 |
31 | // const toMeasure = cx.measureText("n");
32 | // const cw = toMeasure.width;
33 |
34 | // c.width = cw * chars.length;
35 | // c.height = ch;
36 | // // have to set font again after resize
37 | // cx.font = fs + "px custom";
38 |
39 | // cx.fillStyle = "black";
40 | // cx.textBaseline = "middle";
41 | // for (let i = 0; i < chars.length; i++) {
42 | // const char = chars[i];
43 | // cx.fillText(char, i * cw, ch / 2);
44 | // }
45 | // // document.body.appendChild(c);
46 | // texture = new THREE.CanvasTexture(c);
47 | // // texture.magFilter = THREE.NearestFilter;
48 |
49 | // uv.setXY(0, 0, 1);
50 | // uv.setXY(1, 1, 1);
51 | // uv.setXY(2, 0, 0);
52 | // uv.setXY(3, 1, 0);
53 | // texScale[0] = cw / c.width;
54 | // texScale[1] = ch / c.height;
55 |
56 | // aspect = [cw / ch, 1, 1];
57 | // // aspect = [1, 1, 1];
58 | // }
59 |
60 | // const visible = Array(LIMIT).fill(1);
61 |
62 | // geometry.setAttribute(
63 | // "visible",
64 | // new THREE.InstancedBufferAttribute(new Float32Array(visible), 1, false)
65 | // );
66 |
67 | // const offsets = [];
68 | // for (let i = 0; i < LIMIT; i++) {
69 | // offsets.push(0, 0);
70 | // }
71 |
72 | // geometry.setAttribute(
73 | // "offset",
74 | // new THREE.InstancedBufferAttribute(new Float32Array(offsets), 2, false)
75 | // );
76 |
77 | // const vertexShader = `
78 | // varying vec2 vUv;
79 | // attribute vec2 offset;
80 | // varying vec2 vOffset;
81 | // uniform vec2 texScale;
82 | // varying vec2 vTexScale;
83 | // uniform vec3 aspect;
84 | // uniform float scale;
85 | // attribute float visible;
86 |
87 | // void main() {
88 | // vUv = uv * texScale;
89 | // vOffset = offset * texScale;
90 | // vTexScale = texScale;
91 |
92 | // gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position * aspect * scale, 1.0) * visible;
93 | // }
94 | // `;
95 |
96 | // const fragmentShader = `
97 | // uniform sampler2D texture1;
98 | // varying vec2 vUv;
99 | // varying vec2 vOffset;
100 | // varying vec2 vTexScale;
101 |
102 | // void main() {
103 | // vec4 color = texture2D(texture1, vec2(vUv.x + vOffset.x, vUv.y + vOffset.y));
104 | // gl_FragColor = color;
105 | // }
106 | // `;
107 |
108 | // var uniforms = {
109 | // texture1: { type: "t", value: texture },
110 | // texScale: { value: texScale },
111 | // aspect: { value: aspect },
112 | // scale: { value: 0.5 },
113 | // };
114 |
115 | // const material = new THREE.ShaderMaterial({
116 | // uniforms: uniforms,
117 | // vertexShader: vertexShader,
118 | // fragmentShader: fragmentShader,
119 | // });
120 | // material.transparent = true;
121 |
122 | // super(geometry, material, LIMIT);
123 | // this.positions = [];
124 | // this.lines = [""];
125 | // this.activeLine = 0;
126 | // this.chars = chars;
127 | // this.state = state;
128 | // this.aspect = aspect[0];
129 | // this.lineStarts = [[0, 0]];
130 |
131 | // state.scene.add(this);
132 |
133 | // this.setChars();
134 |
135 | // // setInterval(() => {
136 | // // this.addText(chars[Math.floor(Math.random() * chars.length)]);
137 | // // }, 20);
138 | // }
139 |
140 | // getPositionFromAngle(
141 | // prev: [number, number],
142 | // angle: number
143 | // ): [number, number] {
144 | // const rx = 0.6 * this.aspect;
145 | // const x = prev[0] + Math.cos(angle) * rx;
146 | // const y = prev[1] + Math.sin(angle) * rx;
147 | // return [x, y];
148 | // }
149 |
150 | // setChars() {
151 | // let counter = 0;
152 | // const offsetBuffer = this.geometry.attributes.offset.array;
153 | // // @ts-ignore
154 | // offsetBuffer.fill(0);
155 | // for (const line of this.lines) {
156 | // for (const char of line.split("")) {
157 | // // @ts-ignore
158 | // offsetBuffer[counter * 2] = this.chars.indexOf(char);
159 | // counter++;
160 | // }
161 | // }
162 | // this.geometry.attributes.offset.needsUpdate = true;
163 | // }
164 |
165 | // addText(data: string) {
166 | // if (!this.state.dragging) {
167 | // const rad = Math.atan2(this.state.vector.y, this.state.vector.x);
168 | // this.lines[this.activeLine] += data;
169 | // const previous = this.state.lastPosition.slice() as [number, number];
170 | // const position = this.getPositionFromAngle(previous, rad);
171 | // this.state.lastPosition = position.slice() as [number, number];
172 |
173 | // this.state.cursor.setStart(
174 | // this.state.lastPosition[0],
175 | // this.state.lastPosition[1]
176 | // );
177 | // const positionDiff = [
178 | // position[0] - previous[0],
179 | // position[1] - previous[1],
180 | // ];
181 | // this.setPosition(
182 | // this.activeLine,
183 | // this.lines[this.activeLine].length - 1,
184 | // position
185 | // );
186 | // this.setChars();
187 | // this.updatePositions();
188 |
189 | // this.state.camera.position.set(
190 | // this.state.camera.position.x + positionDiff[0],
191 | // this.state.camera.position.y + positionDiff[1],
192 | // this.state.camera.position.z
193 | // );
194 |
195 | // this.state.cursor.setEnd(
196 | // this.state.lastPosition[0] + this.state.vector.x,
197 | // this.state.lastPosition[1] + this.state.vector.y
198 | // );
199 | // }
200 | // }
201 |
202 | // setPosition(
203 | // lineIndex: number,
204 | // index: number,
205 | // position: [number, number]
206 | // ): void {
207 | // let point = 0;
208 | // for (let i = 0; i < lineIndex; i++) {
209 | // index += this.lines[i].length;
210 | // }
211 | // point += index;
212 | // this.positions[point] = position;
213 | // }
214 |
215 | // backspace() {
216 | // if (!this.state.dragging) {
217 | // const line = this.lines[this.activeLine];
218 | // if (line.length > 0) {
219 | // const previous = this.state.lastPosition.slice();
220 | // const length = line.length;
221 | // this.lines[this.activeLine] = line.substring(0, length - 1);
222 | // this.setPosition(this.activeLine, length - 1, [0, 0]);
223 | // if (length === 1) {
224 | // this.state.lastPosition = [0, 0];
225 | // } else {
226 | // this.state.lastPosition = this.positions[length - 2].slice() as [
227 | // number,
228 | // number
229 | // ];
230 | // }
231 | // this.setChars();
232 | // this.updatePositions();
233 |
234 | // const positionDiff = [
235 | // this.state.lastPosition[0] - previous[0],
236 | // this.state.lastPosition[1] - previous[1],
237 | // ];
238 |
239 | // this.state.camera.position.set(
240 | // this.state.camera.position.x + positionDiff[0],
241 | // this.state.camera.position.y + positionDiff[1],
242 | // this.state.camera.position.z
243 | // );
244 |
245 | // this.state.cursor.setStart(
246 | // this.state.lastPosition[0],
247 | // this.state.lastPosition[1]
248 | // );
249 | // this.state.cursor.setEnd(
250 | // this.state.lastPosition[0] + this.state.vector.x,
251 | // this.state.lastPosition[1] + this.state.vector.y
252 | // );
253 | // }
254 | // }
255 | // }
256 |
257 | // enter() {
258 | // this.lines.splice(this.activeLine + 1, 0, "");
259 | // this.activeLine += 1;
260 | // this.lineStarts = [
261 | // ...this.lineStarts.slice(0, this.activeLine + 1),
262 | // [0, 1],
263 | // ...this.lineStarts.slice(this.activeLine + 1),
264 | // ];
265 |
266 | // this.setPosition(this.activeLine, 0, [0, 0]);
267 | // console.log(this.lineStarts);
268 | // }
269 |
270 | // updatePositions() {
271 | // const matrix = new THREE.Matrix4();
272 | // const euler = new Euler(0, 0, 0);
273 | // let prev = [0, 0];
274 | // let charCounter = 0;
275 | // for (let j = 0; j < this.lines.length; j++) {
276 | // const line = this.lines[j];
277 | // const start = this.lineStarts[j];
278 | // for (let k = 0; k < line.length; k++) {
279 | // const position = this.positions[charCounter];
280 | // const x = position[0];
281 | // const y = position[1];
282 | // const rad = Math.atan2(x - prev[0], prev[1] - y);
283 | // euler.z = rad - Math.PI / 2;
284 | // matrix.makeRotationFromEuler(euler);
285 | // matrix.setPosition(x + start[0], y + start[1], 0);
286 | // this.setMatrixAt(charCounter, matrix);
287 | // if (k === 0) {
288 | // prev = [0, 0];
289 | // } else {
290 | // prev = [x, y];
291 | //
292 | // }
293 | // charCounter++;
294 | // }
295 | // console.log(line);
296 | // }
297 | // this.instanceMatrix.needsUpdate = true;
298 | // }
299 | // }
300 |
301 | // export default ArchaicText;
302 |
303 | export {};
304 |
--------------------------------------------------------------------------------
/src/Constants.tsx:
--------------------------------------------------------------------------------
1 | export const BACKGROUND_COLOR = "#ffffff";
2 | export const TEXT_COLOR = "#000000";
3 | export const TRANSPARENT = false;
4 | export const SAVE2X = true;
5 |
--------------------------------------------------------------------------------
/src/Cursor.tsx:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { MeshBasicMaterial } from "three";
3 | import { updateVector } from "./Actions";
4 | import State from "./State";
5 |
6 | export class Cursor extends THREE.Line {
7 | state: State;
8 | nextMarker: THREE.Mesh;
9 | curMarker: THREE.Mesh;
10 | mouse: THREE.Mesh;
11 |
12 | constructor(state: State) {
13 | const material = new THREE.LineBasicMaterial({
14 | color: 0x00ff00,
15 | linewidth: 2,
16 | });
17 | const points = [];
18 | points.push(new THREE.Vector3(0, 0, 0));
19 | points.push(new THREE.Vector3(60, 0, 0));
20 | const geometry = new THREE.BufferGeometry().setFromPoints(points);
21 | super(geometry, material);
22 | this.state = state;
23 | this.visible = true;
24 |
25 | {
26 | const geometry = new THREE.CircleGeometry();
27 | const material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
28 | this.mouse = new THREE.Mesh(geometry, material);
29 | this.mouse.scale.x = 0.4;
30 | this.mouse.scale.y = 0.4;
31 | state.scene.add(this.mouse);
32 | }
33 |
34 | {
35 | const geometry = new THREE.PlaneGeometry();
36 | const material = new THREE.MeshBasicMaterial({ color: 0xff00ff });
37 | this.nextMarker = new THREE.Mesh(geometry, material);
38 | this.nextMarker.scale.x = 0.25;
39 | this.nextMarker.scale.y = 0.5;
40 | state.scene.add(this.nextMarker);
41 | }
42 |
43 | {
44 | const geometry = new THREE.CircleGeometry();
45 | const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
46 | this.curMarker = new THREE.Mesh(geometry, material);
47 | this.curMarker.scale.x = 0.08;
48 | this.curMarker.scale.y = 0.08;
49 | state.scene.add(this.curMarker);
50 | }
51 | }
52 |
53 | setStart(x: number, y: number) {
54 | const positions = this.state.cursor.geometry.attributes.position.array;
55 | // @ts-ignore
56 | positions[0] = x;
57 | // @ts-ignore
58 | positions[1] = y;
59 | this.state.cursor.geometry.attributes.position.needsUpdate = true;
60 |
61 | this.curMarker.position.x = x;
62 | this.curMarker.position.y = y;
63 |
64 | this.updateMarker();
65 | }
66 |
67 | updateMarker() {
68 | const start = this.state.text.linePositions[this.state.text.activeLine];
69 | const rad = Math.atan2(this.state.vector.y, this.state.vector.x);
70 | const position = this.state.text.getPositionFromAngle(
71 | [
72 | this.state.lastPosition[0] + start[0],
73 | this.state.lastPosition[1] + start[1],
74 | ],
75 | rad
76 | );
77 | this.nextMarker.position.set(position[0], position[1], 0);
78 | this.nextMarker.rotation.z = rad;
79 |
80 | const positions = this.state.cursor.geometry.attributes.position.array;
81 | // @ts-ignore
82 | positions[0] = this.nextMarker.position.x;
83 | // @ts-ignore
84 | positions[1] = this.nextMarker.position.y;
85 | this.state.cursor.geometry.attributes.position.needsUpdate = true;
86 |
87 | const linePosition =
88 | this.state.text.linePositions[this.state.text.activeLine];
89 | this.curMarker.position.set(linePosition[0], linePosition[1], 0);
90 |
91 | const mouseMaterial = this.mouse.material as MeshBasicMaterial;
92 | if (this.state.mode === "choosePosition") {
93 | this.nextMarker.visible = false;
94 | this.visible = false;
95 | mouseMaterial.color.setHex(0x00ff00);
96 | this.mouse.scale.x = 0.4;
97 | this.mouse.scale.y = 0.4;
98 | this.curMarker.visible = false;
99 | } else if (this.state.mode === "navigation") {
100 | mouseMaterial.color.setHex(0x00ffff);
101 | this.visible = false;
102 | this.nextMarker.visible = false;
103 | this.curMarker.visible = false;
104 | } else {
105 | this.nextMarker.visible = true;
106 | this.visible = true;
107 | mouseMaterial.color.setHex(0xffff00);
108 | this.mouse.scale.x = 0.4;
109 | this.mouse.scale.y = 0.4;
110 | this.curMarker.visible = true;
111 | }
112 | }
113 |
114 | updateEndAndCursor() {
115 | updateVector(this.state);
116 | const start = this.state.text.linePositions[this.state.text.activeLine];
117 | this.state.cursor.setEnd(
118 | this.state.lastPosition[0] + this.state.vector.x + start[0],
119 | this.state.lastPosition[1] + this.state.vector.y + start[1]
120 | );
121 | }
122 |
123 | setEnd(x: number, y: number) {
124 | const positions = this.state.cursor.geometry.attributes.position.array;
125 | // @ts-ignore
126 | positions[3] = x;
127 | // @ts-ignore
128 | positions[4] = y;
129 | this.state.cursor.geometry.attributes.position.needsUpdate = true;
130 | this.updateMarker();
131 |
132 | this.mouse.position.set(x, y, 0);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/Hud.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useRef } from "react";
2 | import State from "./State";
3 |
4 | interface DialogProps {
5 | title: string;
6 | children: any;
7 | display: boolean;
8 | setDisplay: any;
9 | clearModals: any;
10 | }
11 |
12 | function Dialog({
13 | title,
14 | children,
15 | display,
16 | setDisplay,
17 | clearModals,
18 | }: DialogProps) {
19 | const [offsetX, setOffSetX] = useState(0);
20 | const [offsetY, setOffSetY] = useState(0);
21 | const pointerDown = useRef(false);
22 | const pointerOrigin = useRef([0, 0]);
23 | const offsetOrigin = useRef([0, 0]);
24 |
25 | useEffect(() => {
26 | const downHandler = (e: KeyboardEvent) => {
27 | let press = e.key.toLowerCase();
28 | if (!e.ctrlKey) {
29 | if (press === "escape") setDisplay(false);
30 | }
31 | };
32 |
33 | window.addEventListener("keydown", downHandler);
34 | return () => {
35 | window.removeEventListener("keydown", downHandler);
36 | };
37 | }, [display, clearModals, setDisplay]);
38 |
39 | return (
40 |
44 |
56 |
57 |
{
60 | pointerDown.current = true;
61 | pointerOrigin.current = [e.clientX, e.clientY];
62 | offsetOrigin.current = [offsetX, offsetY];
63 | }}
64 | onPointerMove={(e) => {
65 | if (pointerDown.current) {
66 | setOffSetX(
67 | offsetOrigin.current[0] + e.clientX - pointerOrigin.current[0]
68 | );
69 | setOffSetY(
70 | offsetOrigin.current[1] + e.clientY - pointerOrigin.current[1]
71 | );
72 | }
73 | }}
74 | onPointerUp={() => {
75 | pointerDown.current = false;
76 | }}
77 | >
78 | {title}
79 |
80 |
setDisplay(false)}
84 | >
85 | X
86 |
87 |
88 |
{children}
89 |
90 |
91 | );
92 | }
93 |
94 | function Hud({
95 | state,
96 | settingsOpen,
97 | setSettingsOpen,
98 | aboutOpen,
99 | setAboutOpen,
100 | backgroundColor,
101 | setBackgroundColor,
102 | textColor,
103 | setTextColor,
104 | transparentBackground,
105 | setTransparentBackground,
106 | save2x,
107 | setSave2x,
108 | }: {
109 | state: State;
110 | settingsOpen: boolean;
111 | setSettingsOpen: any;
112 | aboutOpen: boolean;
113 | setAboutOpen: any;
114 | backgroundColor: string;
115 | setBackgroundColor: any;
116 | textColor: string;
117 | setTextColor: any;
118 | transparentBackground: boolean;
119 | setTransparentBackground: any;
120 | save2x: boolean;
121 | setSave2x: any;
122 | }) {
123 | const actions = [
124 | () => {
125 | clearModals();
126 | setSettingsOpen(!settingsOpen);
127 | },
128 | () => {
129 | clearModals();
130 | setAboutOpen(!aboutOpen);
131 | },
132 | () => {
133 | state.printImage();
134 | },
135 | () => {
136 | if (state.mode === "normal" || state.mode === "choosePosition") {
137 | state.setMode("navigation");
138 | } else if (state.mode === "navigation") {
139 | state.setMode("normal");
140 | }
141 | },
142 | ];
143 |
144 | const clearModals = () => {
145 | setSettingsOpen(false);
146 | setAboutOpen(false);
147 | };
148 |
149 | const buttons = ["settings", "about", "print"];
150 | if (state.touch) buttons.push("escape");
151 |
152 | return (
153 | <>
154 |
164 | {buttons.map((text, i) => {
165 | return (
166 |
{
177 | e.stopPropagation();
178 | actions[i]();
179 | }}
180 | >
181 | {text}
182 |
183 | );
184 | })}
185 |
186 |
233 |
269 | >
270 | );
271 | }
272 |
273 | export default Hud;
274 |
--------------------------------------------------------------------------------
/src/Keyboard.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import { updateVector } from "./Actions";
3 | import State from "./State";
4 |
5 | function Keyboard({ state }: { state: State }) {
6 | const keylist = useRef({});
7 |
8 | useEffect(() => {
9 | const downHandler = (e: KeyboardEvent) => {
10 | const kl = keylist.current;
11 | let press = e.key.toLowerCase();
12 | kl[press] = true;
13 | if (state.mode === "normal" || state.mode === "choosePosition") {
14 | if (kl.arrowdown) {
15 | state.mouse.y += 16;
16 | state.cursor.updateEndAndCursor();
17 | state.movedCheck = true;
18 | }
19 | if (kl.arrowup) {
20 | state.mouse.y -= 16;
21 | state.cursor.updateEndAndCursor();
22 | state.movedCheck = true;
23 | }
24 | if (kl.arrowleft) {
25 | state.mouse.x -= 16;
26 | state.cursor.updateEndAndCursor();
27 | state.movedCheck = true;
28 | }
29 | if (kl.arrowright) {
30 | state.mouse.x += 16;
31 | state.cursor.updateEndAndCursor();
32 | state.movedCheck = true;
33 | }
34 | }
35 | if (state.mode === "choosePosition") {
36 | if (press === "enter") {
37 | const start = state.text.linePositions[state.text.activeLine];
38 | const newStart = [
39 | state.lastPosition[0] + state.vector.x + start[0] - 0.01,
40 | state.lastPosition[1] + state.vector.y + start[1],
41 | ] as [number, number];
42 | state.text.activeLine++;
43 | state.text.lines.push("");
44 | state.text.linePositions.push(newStart);
45 | state.text.relPositions.push([]);
46 |
47 | state.lastPosition = [0, 0];
48 | state.text.updatePositions();
49 |
50 | state.setMode("normal");
51 |
52 | updateVector(state);
53 | state.cursor.setEnd(
54 | state.lastPosition[0] + state.vector.x + newStart[0],
55 | state.lastPosition[1] + state.vector.y + newStart[1]
56 | );
57 | } else if (press === "escape") {
58 | state.setMode("navigation");
59 | state.text.selectedLines = [];
60 | state.text.renderLinesSelected();
61 | }
62 | } else if (state.mode === "navigation") {
63 | if (press === "backspace") {
64 | const sorted = state.text.selectedLines.slice().sort(function (a, b) {
65 | return a - b;
66 | });
67 | let adjuster = 0;
68 | for (const index of sorted) {
69 | state.text.lines.splice(index - adjuster, 1);
70 | state.text.relPositions.splice(index - adjuster, 1);
71 | state.text.linePositions.splice(index - adjuster, 1);
72 | state.text.setChars();
73 | state.text.updatePositions();
74 |
75 | const selectedBuffer =
76 | state.text.geometry.attributes.selected.array;
77 | // @ts-ignore
78 | selectedBuffer.fill(0);
79 | state.text.geometry.attributes.selected.needsUpdate = true;
80 |
81 | state.text.activeLine = Math.max(0, state.text.activeLine - 1);
82 | const start = state.text.linePositions[state.text.activeLine] || [
83 | 0, 0,
84 | ];
85 | updateVector(state);
86 | state.cursor.setEnd(
87 | state.lastPosition[0] + state.vector.x + start[0],
88 | state.lastPosition[1] + state.vector.y + start[1]
89 | );
90 |
91 | adjuster++;
92 | }
93 | } else if (press === "escape") {
94 | state.text.selectedLines = [];
95 | state.text.renderLinesSelected();
96 | state.setMode("normal");
97 | }
98 | } else if (state.mode === "normal") {
99 | if (press.length === 1) {
100 | state.text.addText(e.key);
101 | } else {
102 | if (press === "backspace") {
103 | state.text.backspace();
104 | } else if (press === "enter") {
105 | state.text.enter();
106 | } else if (press === "escape") {
107 | state.setMode("navigation");
108 | }
109 | }
110 | }
111 | };
112 |
113 | const upHandler = (e: KeyboardEvent) => {
114 | const kl = keylist.current;
115 | let press = e.key.toLowerCase();
116 | kl[press] = false;
117 | };
118 |
119 | window.addEventListener("keydown", downHandler);
120 | window.addEventListener("keyup", upHandler);
121 | return () => {
122 | window.removeEventListener("keydown", downHandler);
123 | window.removeEventListener("keyup", upHandler);
124 | };
125 | }, [state]);
126 |
127 | return null;
128 | }
129 |
130 | export default Keyboard;
131 |
--------------------------------------------------------------------------------
/src/LineHandles.tsx:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import State from "./State";
3 |
4 | const LIMIT = 1000;
5 | class LineHandles extends THREE.InstancedMesh {
6 | state: State;
7 | visibles: Array;
8 | positions: Array<[number, number]>;
9 |
10 | constructor(state: State) {
11 | const geometry = new THREE.CircleGeometry();
12 | const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
13 |
14 | super(geometry, material, LIMIT);
15 | state.scene.add(this);
16 |
17 | this.state = state;
18 |
19 | this.visibles = new Array(LIMIT).fill(0);
20 | this.positions = new Array(LIMIT).fill([0, 0]);
21 |
22 | this.updatePositions();
23 | }
24 |
25 | setPosition(index: number, position: [number, number]) {
26 | this.positions[index] = position;
27 | this.updatePositions();
28 | }
29 |
30 | updateSelections() {
31 | this.visibles.fill(0);
32 | if (this.state.text) {
33 | if (this.state.text.selectedLines.length > 0) {
34 | for (const index of this.state.text.selectedLines) {
35 | this.visibles[index] = 1;
36 | }
37 | }
38 | if (this.state.text.activeLine !== null) {
39 | this.visibles[this.state.text.activeLine] = 1;
40 | }
41 | }
42 | const matrix = new THREE.Matrix4();
43 | for (let i = 0; i < LIMIT; i++) {
44 | if (this.visibles[i] === 1) {
45 | const position = this.positions[i];
46 | matrix.makeScale(0.0, 0.0, 1);
47 | matrix.setPosition(position[0], position[1], 0);
48 | this.setMatrixAt(i, matrix);
49 | } else {
50 | matrix.makeScale(0.0, 0.0, 1);
51 | this.setMatrixAt(i, matrix);
52 | }
53 | }
54 | this.instanceMatrix.needsUpdate = true;
55 | }
56 |
57 | updatePositions() {
58 | const matrix = new THREE.Matrix4();
59 | for (let i = 0; i < LIMIT; i++) {
60 | const position = this.positions[i];
61 | matrix.makeScale(0.08, 0.08, 1);
62 | matrix.setPosition(position[0], position[1], 0);
63 | this.setMatrixAt(i, matrix);
64 | }
65 | this.updateSelections();
66 | this.instanceMatrix.needsUpdate = true;
67 | }
68 | }
69 |
70 | export default LineHandles;
71 |
--------------------------------------------------------------------------------
/src/OldPointer.tsx:
--------------------------------------------------------------------------------
1 | // import { useEffect } from "react";
2 | // import State from "./State";
3 | // import * as THREE from "three";
4 |
5 | // export class SubPointer {
6 | // state: State;
7 | // id: number;
8 | // current: THREE.Vector2;
9 |
10 | // constructor(state: State, e: PointerEvent) {
11 | // this.state = state;
12 | // this.id = e.pointerId;
13 | // this.current = new THREE.Vector2(e.clientX, e.clientY);
14 |
15 | // // left button
16 | // this.state.pointers.push(this);
17 | // switch (this.state.pointers.length) {
18 | // case 1:
19 | // // start 1
20 | // this.state.PointerOne.start(this.state.pointers.slice(0, 1));
21 | // break;
22 | // case 2:
23 | // // start 2
24 | // if (Date.now() - state.PointerOne.startTime) {
25 | // // revert selection
26 | // // state.selectedGrid = state.gridCache;
27 | // // renderSelected(state);
28 | // }
29 | // this.state.PointerOne.cancel();
30 | // this.state.PointerTwo.start(this.state.pointers.slice(0, 2));
31 | // break;
32 | // default:
33 | // // three or greater start three
34 | // this.state.PointerTwo.end();
35 | // this.state.PointerThree.start(this.state.pointers.slice(0, 3));
36 | // }
37 | // }
38 |
39 | // move(e: PointerEvent) {
40 | // this.current.set(e.clientX, e.clientY);
41 | // switch (this.state.pointers.length) {
42 | // case 1:
43 | // this.state.PointerOne.move();
44 | // break;
45 | // case 2:
46 | // this.state.PointerTwo.move();
47 | // break;
48 | // default:
49 | // this.state.PointerThree.move();
50 | // break;
51 | // }
52 | // }
53 |
54 | // remove() {
55 | // const index = getPointerIndexById(this.state, this.id);
56 | // if (index !== -1) {
57 | // this.state.pointers.splice(index, 1);
58 | // }
59 | // switch (this.state.pointers.length) {
60 | // case 0:
61 | // this.state.PointerOne.end();
62 | // break;
63 | // case 1:
64 | // // down to 1
65 | // this.state.PointerTwo.end();
66 | // // do not step down to 1
67 | // // this.state.PointerOne.start(this.state.pointers.slice(0, 1));
68 | // break;
69 | // case 2:
70 | // // down to 2
71 | // this.state.PointerThree.end();
72 | // this.state.PointerTwo.start(this.state.pointers.slice(0, 2));
73 | // break;
74 | // default:
75 | // }
76 | // }
77 | // }
78 |
79 | // type PointerProps = {
80 | // state: State;
81 | // };
82 |
83 | // const PointerComponent = ({ state }: PointerProps) => {
84 | // useEffect(() => {
85 | // const { canvas } = state;
86 |
87 | // const handlePointerMove = (e: PointerEvent) => {
88 | // if (state.lastPointerButtonPressed === 0) {
89 | // if (state.pointers.length > 0) {
90 | // let pointer;
91 | // if (state.pressed.includes(" ")) {
92 | // pointer = getPointerById(state, 999);
93 | // } else {
94 | // pointer = getPointerById(state, e.pointerId);
95 | // }
96 | // if (pointer) {
97 | // pointer.move(e);
98 | // }
99 | // } else {
100 | // if (state.PointerHover.active) {
101 | // state.PointerHover.move(e.clientX, e.clientY);
102 | // } else {
103 | // state.PointerHover.start(e.clientX, e.clientY);
104 | // }
105 | // }
106 | // } else if (state.lastPointerButtonPressed === 1) {
107 | // if (state.PointerMiddle.active) {
108 | // state.PointerMiddle.move(e);
109 | // }
110 | // } else if (state.lastPointerButtonPressed === 2) {
111 | // // right
112 | // }
113 | // };
114 |
115 | // const handlePointerDown = (e: PointerEvent) => {
116 | // state.PointerHover.end();
117 | // state.lastPointerButtonPressed = e.button;
118 | // if (state.lastPointerButtonPressed === 0) {
119 | // new SubPointer(state, e);
120 | // canvas.setPointerCapture(e.pointerId);
121 | // } else if (state.lastPointerButtonPressed === 1) {
122 | // state.PointerMiddle.start(e);
123 | // } else if (state.lastPointerButtonPressed === 2) {
124 | // // right
125 | // }
126 | // canvas.setPointerCapture(e.pointerId);
127 | // };
128 |
129 | // const handlePointerUp = (e: PointerEvent) => {
130 | // if (state.lastPointerButtonPressed === 0) {
131 | // if (state.pointers.length > 0) {
132 | // const pointer = getPointerById(state, e.pointerId);
133 | // if (pointer) pointer.remove();
134 | // } else {
135 | // state.PointerHover.end();
136 | // }
137 | // canvas.releasePointerCapture(e.pointerId);
138 | // } else if (state.lastPointerButtonPressed === 1) {
139 | // state.PointerMiddle.end();
140 | // state.lastPointerButtonPressed = 0;
141 | // } else if (state.lastPointerButtonPressed === 2) {
142 | // // right
143 | // }
144 | // };
145 |
146 | // const handleMousewheel = (e: WheelEvent) => {
147 | // e.preventDefault();
148 | // // discreteZoom(state, e.deltaY);
149 | // };
150 |
151 | // if (canvas) {
152 | // canvas.addEventListener("pointerdown", handlePointerDown);
153 | // canvas.addEventListener("pointermove", handlePointerMove);
154 | // canvas.addEventListener("pointerup", handlePointerUp);
155 | // canvas.addEventListener("pointercancel", handlePointerUp);
156 | // canvas.addEventListener("wheel", handleMousewheel, {
157 | // passive: false,
158 | // });
159 | // return () => {
160 | // canvas.removeEventListener("pointerdown", handlePointerDown);
161 | // canvas.removeEventListener("pointermove", handlePointerMove);
162 | // canvas.removeEventListener("pointerup", handlePointerUp);
163 | // canvas.removeEventListener("pointercancel", handlePointerUp);
164 | // canvas.removeEventListener("wheel", handleMousewheel);
165 | // };
166 | // }
167 | // }, [state]);
168 |
169 | // return null;
170 | // };
171 |
172 | // export default PointerComponent;
173 |
174 | // const getPointerIndexById = (state: State, pointerId: number) => {
175 | // const ids = state.pointers.map((pointer) => pointer.id);
176 | // return ids.indexOf(pointerId);
177 | // };
178 |
179 | // const getPointerById = (state: State, pointerId: number) => {
180 | // const index = getPointerIndexById(state, pointerId);
181 | // if (index > -1) {
182 | // return state.pointers[index];
183 | // } else {
184 | // return null;
185 | // }
186 | // };
187 |
188 | export default {};
189 |
--------------------------------------------------------------------------------
/src/Pointer.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import State from "./State";
3 | import * as THREE from "three";
4 | import {
5 | choosePosition,
6 | moveCamera,
7 | navigationClick,
8 | updateTarget,
9 | moveLines,
10 | targetZoom,
11 | } from "./PointerUtils";
12 |
13 | const PointerComponent = ({
14 | state,
15 | keyboardRef,
16 | }: {
17 | state: State;
18 | keyboardRef: any;
19 | }) => {
20 | const pointersRef = useRef([]);
21 | const cameraDown = useRef(new THREE.Vector3());
22 | const mouse2 = new THREE.Vector2();
23 | const positionCache = useRef<[number, number][]>([]);
24 | const raycaster = new THREE.Raycaster();
25 | const clickTime = useRef<{ id: number | null; time: number }>({
26 | id: null,
27 | time: Date.now(),
28 | });
29 |
30 | useEffect(() => {
31 | const { canvas } = state;
32 | const activePointers = pointersRef.current;
33 |
34 | const handlePointerMove = (e: PointerEvent) => {
35 | if (activePointers.length === 0) {
36 | hoverMove(e);
37 | } else {
38 | const activeIds = activePointers.map((p) => p.id);
39 | const index = activeIds.indexOf(e.pointerId);
40 | if (index > -1) {
41 | const active = activePointers[index];
42 | active.current = [e.clientX, e.clientY];
43 | if (activePointers.length === 1) {
44 | oneDrag(e);
45 | } else if (activePointers.length === 2) {
46 | twoDrag(e);
47 | }
48 | }
49 | }
50 | };
51 |
52 | const handleMouseDown = (e: MouseEvent) => {
53 | // necessary to preserve focus on mobile evidently
54 | e.preventDefault();
55 | };
56 |
57 | const handlePointerDown = (e: PointerEvent) => {
58 | e.preventDefault();
59 |
60 | if (e.pointerType === "touch") {
61 | if (state.mode !== "navigation") {
62 | keyboardRef.current?.focus();
63 | }
64 | }
65 |
66 | const prevLength = activePointers.length;
67 | const activeIds = activePointers.map((p) => p.id);
68 | if (activeIds.indexOf(e.pointerId) === -1) {
69 | // limit to 2
70 | if (activePointers.length < 2) {
71 | activePointers.push({
72 | id: e.pointerId,
73 | down: [e.clientX, e.clientY],
74 | current: [e.clientX, e.clientY],
75 | });
76 | }
77 | }
78 | const activeLength = activePointers.length;
79 |
80 | if (prevLength === 0 && activeLength === 1) {
81 | oneDragStart(e);
82 | } else if (prevLength === 2 && activeLength === 1) {
83 | oneDragStart(e);
84 | } else if (prevLength === 1 && activeLength === 2) {
85 | twoDragStart(e);
86 | }
87 |
88 | canvas.setPointerCapture(e.pointerId);
89 | };
90 |
91 | const handleMousewheel = (e: WheelEvent) => {
92 | e.preventDefault();
93 | cameraDown.current.copy(state.camera.position);
94 | const percent = (window.innerHeight - e.deltaY * 2) / window.innerHeight;
95 | targetZoom(state, [e.clientX, e.clientY], cameraDown.current, percent);
96 | cameraDown.current.copy(state.camera.position);
97 | };
98 |
99 | const handlePointerUp = (e: PointerEvent) => {
100 | e.preventDefault();
101 |
102 | const prevLength = activePointers.length;
103 | const activeIds = activePointers.map((p) => p.id);
104 | if (activeIds.indexOf(e.pointerId) !== -1) {
105 | const index = activeIds.indexOf(e.pointerId);
106 | activePointers.splice(index, 1);
107 | }
108 | const activeLength = activePointers.length;
109 |
110 | if (prevLength === 1 && activeLength === 0) {
111 | oneDragEnd(e);
112 | } else if (prevLength === 2 && activeLength === 1) {
113 | twoDragEnd(e);
114 | oneDragStart(e);
115 | }
116 |
117 | canvas.releasePointerCapture(e.pointerId);
118 | };
119 |
120 | const handleClick = (e: any) => {
121 | e.preventDefault();
122 | };
123 |
124 | const hoverMove = (e: PointerEvent) => {
125 | updateTarget(state, e);
126 | };
127 |
128 | const oneDragStart = (e: PointerEvent) => {
129 | let doubleClick = false;
130 | if (Date.now() - clickTime.current.time < 500) {
131 | doubleClick = true;
132 | } else {
133 | clickTime.current.id = e.pointerId;
134 | clickTime.current.time = Date.now();
135 | }
136 |
137 | // reset down for all active pointers
138 | for (const active of activePointers) {
139 | active.down = active.current.slice();
140 | }
141 |
142 | if (e.pointerType === "touch") {
143 | if (state.mode === "choosePosition" && e.button === 0) {
144 | choosePosition(state, e);
145 | } else if (state.mode === "navigation") {
146 | updateTarget(state, e);
147 | navigationClick(
148 | state,
149 | e,
150 | doubleClick,
151 | mouse2,
152 | raycaster,
153 | positionCache,
154 | cameraDown
155 | );
156 | return;
157 | } else {
158 | state.draggingCamera = false;
159 | }
160 | } else {
161 | if (state.mode === "choosePosition" && e.button === 0) {
162 | choosePosition(state, e);
163 | } else if (state.mode === "navigation") {
164 | navigationClick(
165 | state,
166 | e,
167 | doubleClick,
168 | mouse2,
169 | raycaster,
170 | positionCache,
171 | cameraDown
172 | );
173 | }
174 | state.draggingCamera = true;
175 | cameraDown.current.copy(state.camera.position);
176 | }
177 | };
178 | const oneDrag = (e: PointerEvent) => {
179 | const active = activePointers[0];
180 |
181 | if (e.pointerType === "touch") {
182 | if (state.draggingLine) {
183 | moveLines(state, e, active, positionCache);
184 | } else if (state.draggingCamera) {
185 | moveCamera(state, active, cameraDown);
186 | updateTarget(state, e);
187 | } else {
188 | updateTarget(state, e);
189 | }
190 | } else {
191 | if (state.draggingLine) {
192 | moveLines(state, e, active, positionCache);
193 | } else if (state.draggingCamera) {
194 | moveCamera(state, active, cameraDown);
195 | }
196 | }
197 | };
198 | const oneDragEnd = (e: PointerEvent) => {};
199 |
200 | const twoDragStart = (e: PointerEvent) => {
201 | // reset down for all active pointers
202 | for (const active of activePointers) {
203 | active.down = active.current.slice();
204 | }
205 | state.draggingCamera = true;
206 | cameraDown.current.copy(state.camera.position);
207 | };
208 | const twoDrag = (e: PointerEvent) => {
209 | const a = activePointers[0];
210 | const b = activePointers[1];
211 | const minDown = [
212 | Math.min(a.down[0], b.down[0]),
213 | Math.min(a.down[1], b.down[1]),
214 | ];
215 | const maxDown = [
216 | Math.max(a.down[0], b.down[0]),
217 | Math.max(a.down[1], b.down[1]),
218 | ];
219 | const min = [
220 | Math.min(a.current[0], b.current[0]),
221 | Math.min(a.current[1], b.current[1]),
222 | ];
223 | const max = [
224 | Math.max(a.current[0], b.current[0]),
225 | Math.max(a.current[1], b.current[1]),
226 | ];
227 | const combined = {
228 | down: [
229 | minDown[0] + (maxDown[0] - minDown[0]) / 2,
230 | minDown[1] + (maxDown[1] - minDown[1]) / 2,
231 | ],
232 | current: [
233 | min[0] + (max[0] - min[0]) / 2,
234 | min[1] + (max[1] - min[1]) / 2,
235 | ],
236 | };
237 |
238 | const change = moveCamera(state, combined, cameraDown);
239 | const adjustedDown = new THREE.Vector3();
240 | adjustedDown.x = cameraDown.current.x + change[0];
241 | adjustedDown.y = cameraDown.current.y + change[1];
242 | adjustedDown.z = cameraDown.current.z;
243 | const downDiff = Math.sqrt(
244 | Math.pow(b.down[0] - a.down[0], 2) + Math.pow(b.down[1] - a.down[1], 2)
245 | );
246 | const currDiff = Math.sqrt(
247 | Math.pow(b.current[0] - a.current[0], 2) +
248 | Math.pow(b.current[1] - a.current[1], 2)
249 | );
250 | const percent = (currDiff - downDiff) / downDiff + 1;
251 | targetZoom(
252 | state,
253 | combined.current as [number, number],
254 | adjustedDown,
255 | percent
256 | );
257 | };
258 | const twoDragEnd = (e: PointerEvent) => {};
259 |
260 | if (canvas) {
261 | canvas.addEventListener("pointerdown", handlePointerDown);
262 | document.addEventListener("pointermove", handlePointerMove);
263 | canvas.addEventListener("mousedown", handleMouseDown);
264 | canvas.addEventListener("pointerup", handlePointerUp);
265 | canvas.addEventListener("pointercancel", handlePointerUp);
266 | canvas.addEventListener("click", handleClick);
267 | canvas.addEventListener("wheel", handleMousewheel, {
268 | passive: false,
269 | });
270 | return () => {
271 | canvas.removeEventListener("pointerdown", handlePointerDown);
272 | document.removeEventListener("pointermove", handlePointerMove);
273 | canvas.removeEventListener("pointerup", handlePointerUp);
274 | canvas.removeEventListener("pointercancel", handlePointerUp);
275 | canvas.removeEventListener("click", handleClick);
276 | canvas.removeEventListener("wheel", handleMousewheel);
277 | };
278 | }
279 | }, [state, keyboardRef]);
280 |
281 | return null;
282 | };
283 |
284 | export default PointerComponent;
285 |
--------------------------------------------------------------------------------
/src/PointerUtils.tsx:
--------------------------------------------------------------------------------
1 | import { updateVector } from "./Actions";
2 | import State from "./State";
3 | import * as THREE from "three";
4 |
5 | export const updateTarget = (state: State, e: PointerEvent) => {
6 | state.mouse.set(e.clientX, e.clientY);
7 | updateVector(state);
8 | const start = state.text.linePositions[state.text.activeLine];
9 | state.cursor.setEnd(
10 | state.lastPosition[0] + state.vector.x + start[0],
11 | state.lastPosition[1] + state.vector.y + start[1]
12 | );
13 | };
14 |
15 | export const moveCamera = (state: State, active: any, cameraDown: any) => {
16 | const visibleHeight =
17 | 2 * Math.tan((state.camera.fov * Math.PI) / 360) * cameraDown.current.z;
18 | const zoomPixel = visibleHeight / window.innerHeight;
19 | const dragged = [
20 | active.current[0] - active.down[0],
21 | active.current[1] - active.down[1],
22 | ];
23 | state.camera.position.x = cameraDown.current.x - dragged[0] * zoomPixel;
24 | state.camera.position.y = cameraDown.current.y + dragged[1] * zoomPixel;
25 | return [-dragged[0] * zoomPixel, dragged[1] * zoomPixel];
26 | };
27 |
28 | export const choosePosition = (state: State, e: PointerEvent) => {
29 | updateTarget(state, e);
30 | const start = state.text.linePositions[state.text.activeLine];
31 | const newStart = [
32 | state.lastPosition[0] + state.vector.x + start[0] - 0.01,
33 | state.lastPosition[1] + state.vector.y + start[1],
34 | ] as [number, number];
35 | state.text.activeLine = state.text.lines.length;
36 | state.text.lines.push("");
37 | state.text.linePositions.push(newStart);
38 | state.text.relPositions.push([]);
39 | state.lastPosition = [0, 0];
40 | state.text.updatePositions();
41 | state.setMode("normal");
42 | updateTarget(state, e);
43 | };
44 |
45 | export const navigationClick = (
46 | state: State,
47 | e: PointerEvent,
48 | doubleClick: boolean,
49 | mouse2: THREE.Vector2,
50 | raycaster: THREE.Raycaster,
51 | positionCache: any,
52 | cameraDown: any
53 | ) => {
54 | mouse2.x = (e.clientX / window.innerWidth) * 2 - 1;
55 | mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1;
56 | raycaster.setFromCamera(mouse2, state.camera);
57 | const intersects = raycaster.intersectObject(state.text);
58 | if (intersects.length > 0) {
59 | const instanceId = intersects[0].instanceId;
60 | if (instanceId !== null && instanceId !== undefined) {
61 | const lineIndex = state.text.getInstanceLineIndex(instanceId);
62 | if (lineIndex !== undefined) {
63 | if (e.shiftKey) {
64 | state.text.selectedLines.push(lineIndex);
65 | } else {
66 | if (state.text.selectedLines.includes(lineIndex)) {
67 | if (doubleClick) {
68 | state.text.selectedLines = [];
69 | state.text.activeLine = lineIndex;
70 | const relPositions = state.text.relPositions[lineIndex];
71 | state.lastPosition = relPositions[
72 | relPositions.length - 1
73 | ].slice() as [number, number];
74 | state.text.selectedLines = [];
75 | state.setMode("normal");
76 | }
77 | } else {
78 | state.text.selectedLines = [];
79 | state.text.selectedLines.push(lineIndex);
80 | }
81 | }
82 | }
83 | state.text.renderLinesSelected();
84 | state.draggingLine = true;
85 | positionCache.current = [];
86 | for (const line of state.text.selectedLines) {
87 | // positionCache.push()
88 | positionCache.current.push(
89 | state.text.linePositions[line].slice() as [number, number]
90 | );
91 | }
92 | return;
93 | }
94 | } else {
95 | state.draggingLine = false;
96 | if (!e.shiftKey) state.text.selectedLines = [];
97 | state.text.renderLinesSelected();
98 |
99 | mouse2.x = (e.clientX / window.innerWidth) * 2 - 1;
100 | mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1;
101 | updateTarget(state, e);
102 |
103 | if (doubleClick) {
104 | state.setMode("normal");
105 |
106 | const start = state.text.linePositions[state.text.activeLine];
107 | const newStart = [
108 | state.lastPosition[0] + state.vector.x + start[0] - 0.01,
109 | state.lastPosition[1] + state.vector.y + start[1],
110 | ] as [number, number];
111 | state.text.activeLine++;
112 | state.text.lines.push("");
113 | state.text.linePositions.push(newStart);
114 | state.text.relPositions.push([]);
115 |
116 | state.lastPosition = [0, 0];
117 | state.text.updatePositions();
118 |
119 | state.setMode("normal");
120 | }
121 |
122 | state.draggingCamera = true;
123 | cameraDown.current.copy(state.camera.position);
124 | }
125 | };
126 |
127 | export const moveLines = (
128 | state: State,
129 | e: PointerEvent,
130 | active: any,
131 | positionCache: any
132 | ) => {
133 | const visibleHeight =
134 | 2 * Math.tan((state.camera.fov * Math.PI) / 360) * state.camera.position.z;
135 | const zoomPixel = visibleHeight / window.innerHeight;
136 |
137 | const dragged = [
138 | active.current[0] - active.down[0],
139 | active.current[1] - active.down[1],
140 | ];
141 | for (let i = 0; i < positionCache.current.length; i++) {
142 | const index = state.text.selectedLines[i];
143 | state.text.linePositions[index] = [
144 | positionCache.current[i][0] + dragged[0] * zoomPixel,
145 | positionCache.current[i][1] - dragged[1] * zoomPixel,
146 | ];
147 | }
148 | state.text.updatePositions();
149 |
150 | updateTarget(state, e);
151 | };
152 |
153 | export const targetZoom = (
154 | state: State,
155 | target: [number, number],
156 | cameraDown: THREE.Vector3,
157 | percent: number
158 | ) => {
159 | const visibleHeight =
160 | 2 * Math.tan((state.camera.fov * Math.PI) / 360) * cameraDown.z;
161 | const zoomPixel = visibleHeight / window.innerHeight;
162 |
163 | const relx = target[0] - window.innerWidth / 2;
164 | const rely = -(target[1] - window.innerHeight / 2);
165 | const worldRelX = relx * zoomPixel;
166 | const worldRelY = rely * zoomPixel;
167 |
168 | const boundZoom = (state: State, val: number) => {
169 | const min = 3;
170 | const max = 18;
171 | return Math.min(max, Math.max(min, val));
172 | };
173 |
174 | const nextZoom = boundZoom(state, cameraDown.z / percent);
175 |
176 | const newVisibleHeight =
177 | 2 * Math.tan((state.camera.fov * Math.PI) / 360) * nextZoom;
178 | const newZoomPixel = newVisibleHeight / window.innerHeight;
179 |
180 | const newWorldX = relx * newZoomPixel;
181 | const newWorldY = rely * newZoomPixel;
182 |
183 | const diffX = newWorldX - worldRelX;
184 | const diffY = newWorldY - worldRelY;
185 |
186 | state.camera.position.x = cameraDown.x - diffX;
187 | state.camera.position.y = cameraDown.y - diffY;
188 | state.camera.position.z = nextZoom;
189 | };
190 |
--------------------------------------------------------------------------------
/src/RawPointer.tsx:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { useEffect, useState, useRef } from "react";
3 | import { updateVector, setRay } from "./Actions";
4 | import State from "./State";
5 |
6 | function Pointer({ state }: { state: State }) {
7 | const down = useRef<[number, number]>([0, 0]);
8 | const clickTime = useRef(Date.now());
9 | const cameraDown = useRef(new THREE.Vector3());
10 | const raycaster = new THREE.Raycaster();
11 | const mouse2 = new THREE.Vector2();
12 | const dragCache = new THREE.Vector2();
13 | const positionCache = useRef<[number, number][]>([]);
14 |
15 | useEffect(() => {
16 | const { canvas } = state;
17 |
18 | function pointerUp(e: PointerEvent) {
19 | state.draggingLine = false;
20 | state.draggingCamera = false;
21 | }
22 |
23 | function pointerDown(e: PointerEvent) {
24 | let doubleClick = false;
25 | if (Date.now() - clickTime.current < 500) {
26 | doubleClick = true;
27 | } else {
28 | clickTime.current = Date.now();
29 | }
30 |
31 | if (state.mode === "choosePosition" && e.button === 0) {
32 | const start = state.text.linePositions[state.text.activeLine];
33 | const newStart = [
34 | state.lastPosition[0] + state.vector.x + start[0] - 0.01,
35 | state.lastPosition[1] + state.vector.y + start[1],
36 | ] as [number, number];
37 | state.text.activeLine++;
38 | state.text.lines.push("");
39 | state.text.linePositions.push(newStart);
40 | state.text.relPositions.push([]);
41 |
42 | state.lastPosition = [0, 0];
43 | state.text.updatePositions();
44 |
45 | state.setMode("normal");
46 |
47 | updateVector(state);
48 | state.cursor.setEnd(
49 | state.lastPosition[0] + state.vector.x + newStart[0],
50 | state.lastPosition[1] + state.vector.y + newStart[1]
51 | );
52 |
53 | state.draggingCamera = true;
54 |
55 | down.current = [e.clientX, e.clientY];
56 | cameraDown.current.copy(state.camera.position);
57 | } else if (state.mode === "navigation" && e.button === 0) {
58 | mouse2.x = (e.clientX / window.innerWidth) * 2 - 1;
59 | mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1;
60 | raycaster.setFromCamera(mouse2, state.camera);
61 | const intersects = raycaster.intersectObject(state.text);
62 | if (intersects.length > 0) {
63 | const instanceId = intersects[0].instanceId;
64 | if (instanceId !== null && instanceId !== undefined) {
65 | const lineIndex = state.text.getInstanceLineIndex(instanceId);
66 | if (lineIndex !== undefined) {
67 | if (e.shiftKey) {
68 | state.text.selectedLines.push(lineIndex);
69 | } else {
70 | if (state.text.selectedLines.includes(lineIndex)) {
71 | if (doubleClick) {
72 | state.text.selectedLines = [];
73 | state.text.activeLine = lineIndex;
74 | const relPositions = state.text.relPositions[lineIndex];
75 | state.lastPosition = relPositions[
76 | relPositions.length - 1
77 | ].slice() as [number, number];
78 | state.text.selectedLines = [];
79 | state.setMode("normal");
80 | }
81 | } else {
82 | state.text.selectedLines = [];
83 | state.text.selectedLines.push(lineIndex);
84 | }
85 | }
86 | }
87 | state.text.renderLinesSelected();
88 | state.draggingLine = true;
89 | positionCache.current = [];
90 | for (const line of state.text.selectedLines) {
91 | // positionCache.push()
92 | positionCache.current.push(
93 | state.text.linePositions[line].slice() as [number, number]
94 | );
95 | }
96 | down.current = [e.clientX, e.clientY];
97 | return;
98 | }
99 | } else {
100 | state.draggingCamera = true;
101 | if (!e.shiftKey) state.text.selectedLines = [];
102 | state.text.renderLinesSelected();
103 |
104 | if (doubleClick) {
105 | state.setMode("normal");
106 |
107 | const start = state.text.linePositions[state.text.activeLine];
108 | const newStart = [
109 | state.lastPosition[0] + state.vector.x + start[0] - 0.01,
110 | state.lastPosition[1] + state.vector.y + start[1],
111 | ] as [number, number];
112 | state.text.activeLine++;
113 | state.text.lines.push("");
114 | state.text.linePositions.push(newStart);
115 | state.text.relPositions.push([]);
116 |
117 | state.lastPosition = [0, 0];
118 | state.text.updatePositions();
119 |
120 | state.setMode("normal");
121 |
122 | updateVector(state);
123 | state.cursor.setEnd(
124 | state.lastPosition[0] + state.vector.x + newStart[0],
125 | state.lastPosition[1] + state.vector.y + newStart[1]
126 | );
127 |
128 | state.draggingCamera = true;
129 |
130 | down.current = [e.clientX, e.clientY];
131 | cameraDown.current.copy(state.camera.position);
132 | }
133 | }
134 |
135 | mouse2.x = (e.clientX / window.innerWidth) * 2 - 1;
136 | mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1;
137 | state.draggingCamera = true;
138 |
139 | down.current = [e.clientX, e.clientY];
140 | cameraDown.current.copy(state.camera.position);
141 | } else {
142 | mouse2.x = (e.clientX / window.innerWidth) * 2 - 1;
143 | mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1;
144 | state.draggingCamera = true;
145 |
146 | state.mouse.set(e.clientX, e.clientY);
147 |
148 | updateVector(state);
149 |
150 | const start = state.text.linePositions[state.text.activeLine];
151 | state.cursor.setEnd(
152 | state.lastPosition[0] + state.vector.x + start[0],
153 | state.lastPosition[1] + state.vector.y + start[1]
154 | );
155 |
156 | down.current = [e.clientX, e.clientY];
157 | cameraDown.current.copy(state.camera.position);
158 | }
159 | }
160 |
161 | function pointerMove(e: PointerEvent) {
162 | state.mouse.set(e.clientX, e.clientY);
163 | state.movedCheck = true;
164 | if (state.draggingLine || state.draggingCamera) {
165 | const visibleHeight =
166 | 2 *
167 | Math.tan((state.camera.fov * Math.PI) / 360) *
168 | state.camera.position.z;
169 | const zoomPixel = visibleHeight / window.innerHeight;
170 |
171 | const dragged = [
172 | state.mouse.x - down.current[0],
173 | state.mouse.y - down.current[1],
174 | ];
175 | if (state.draggingLine) {
176 | for (let i = 0; i < positionCache.current.length; i++) {
177 | const index = state.text.selectedLines[i];
178 | state.text.linePositions[index] = [
179 | positionCache.current[i][0] + dragged[0] * zoomPixel,
180 | positionCache.current[i][1] - dragged[1] * zoomPixel,
181 | ];
182 | }
183 | state.text.updatePositions();
184 |
185 | updateVector(state);
186 | const start = state.text.linePositions[state.text.activeLine];
187 | state.cursor.setEnd(
188 | state.lastPosition[0] + state.vector.x + start[0],
189 | state.lastPosition[1] + state.vector.y + start[1]
190 | );
191 | } else if (state.draggingCamera) {
192 | state.camera.position.x =
193 | cameraDown.current.x - dragged[0] * zoomPixel;
194 | state.camera.position.y =
195 | cameraDown.current.y + dragged[1] * zoomPixel;
196 | }
197 | } else {
198 | updateVector(state);
199 |
200 | const start = state.text.linePositions[state.text.activeLine];
201 | state.cursor.setEnd(
202 | state.lastPosition[0] + state.vector.x + start[0],
203 | state.lastPosition[1] + state.vector.y + start[1]
204 | );
205 | }
206 | }
207 |
208 | const mouseWheel = (e: Event) => {
209 | const deltaY = (e as WheelEvent).deltaY;
210 | const percent = (window.innerHeight + deltaY * 2) / window.innerHeight;
211 | const newZ = Math.min(18, Math.max(3, state.camera.position.z * percent));
212 | setRay(state, state.ray, state.mouse, newZ);
213 | state.camera.position.copy(state.ray);
214 | };
215 |
216 | window.document.addEventListener("pointerdown", pointerDown);
217 | window.document.addEventListener("pointermove", pointerMove);
218 | window.document.addEventListener("pointerup", pointerUp);
219 | window.document.addEventListener("mousewheel", mouseWheel, {
220 | passive: false,
221 | });
222 | return () => {
223 | window.document.removeEventListener("mousewheel", mouseWheel);
224 | window.document.removeEventListener("pointerdown", pointerDown);
225 | window.document.removeEventListener("pointermove", pointerMove);
226 | window.document.removeEventListener("pointerup", pointerUp);
227 | };
228 | }, [state]);
229 |
230 | return null;
231 | }
232 |
233 | export default Pointer;
234 |
--------------------------------------------------------------------------------
/src/Rotators.tsx:
--------------------------------------------------------------------------------
1 | const test = {};
2 |
3 | export default test;
4 |
--------------------------------------------------------------------------------
/src/State.tsx:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { updateVector } from "./Actions";
3 | import { Cursor } from "./Cursor";
4 | import { BACKGROUND_COLOR, SAVE2X, TRANSPARENT } from "./Constants";
5 | import Text from "./Text";
6 |
7 | class State {
8 | canvas: HTMLCanvasElement;
9 | camera: THREE.PerspectiveCamera;
10 | printCamera: THREE.PerspectiveCamera;
11 | renderer: THREE.WebGLRenderer;
12 | printRenderer: THREE.WebGLRenderer;
13 | scene: THREE.Scene;
14 | text: Text;
15 | data: string;
16 | worldPixel: number;
17 | center: THREE.Vector2;
18 | mouse: THREE.Vector2;
19 | vector: THREE.Vector2;
20 | tempVec: THREE.Vector3;
21 | ray: THREE.Vector3;
22 | lastPosition: [number, number];
23 | cursor: Cursor;
24 | draggingCamera: boolean;
25 | draggingLine: boolean;
26 | movedCheck: boolean;
27 | mode: "normal" | "choosePosition" | "navigation";
28 | transparentBackground: boolean;
29 | save2x: boolean;
30 | touch: boolean;
31 |
32 | constructor(canvas: HTMLCanvasElement, printCanvas: HTMLCanvasElement) {
33 | this.canvas = canvas;
34 | this.camera = new THREE.PerspectiveCamera(
35 | 75,
36 | window.innerWidth / window.innerHeight,
37 | 0.1,
38 | 100
39 | );
40 | this.printCamera = new THREE.PerspectiveCamera(
41 | 75,
42 | window.innerWidth / window.innerHeight,
43 | 0.1,
44 | 100
45 | );
46 | this.renderer = new THREE.WebGLRenderer({
47 | canvas,
48 | alpha: true,
49 | });
50 | this.renderer.setPixelRatio(window.devicePixelRatio);
51 | this.renderer.setSize(window.innerWidth, window.innerHeight);
52 |
53 | this.printRenderer = new THREE.WebGLRenderer({
54 | canvas: printCanvas,
55 | alpha: true,
56 | });
57 | this.printRenderer.setPixelRatio(window.devicePixelRatio);
58 | this.printRenderer.setSize(window.innerWidth, window.innerHeight);
59 |
60 | this.scene = new THREE.Scene();
61 | this.center = new THREE.Vector2(
62 | window.innerWidth / 2,
63 | window.innerHeight / 2
64 | );
65 | this.vector = new THREE.Vector2();
66 |
67 | this.draggingLine = false;
68 | this.draggingCamera = false;
69 |
70 | const ZSTART = 10;
71 | // set world pixel
72 | {
73 | const visibleHeight =
74 | 2 * Math.tan((this.camera.fov * Math.PI) / 360) * ZSTART;
75 | this.worldPixel = visibleHeight / window.innerHeight;
76 | }
77 |
78 | this.data = "";
79 | this.ray = new THREE.Vector3();
80 | this.tempVec = new THREE.Vector3();
81 | this.cursor = new Cursor(this);
82 | this.mouse = new THREE.Vector2(window.innerWidth - 48, 72);
83 | this.movedCheck = false;
84 | this.setBackgroundColor(BACKGROUND_COLOR);
85 | this.transparentBackground = TRANSPARENT;
86 | this.save2x = SAVE2X;
87 |
88 | this.lastPosition = [0, 0];
89 | this.text = new Text(this, [
90 | (-window.innerWidth / 2 + 24) * this.worldPixel,
91 | (window.innerHeight / 2 - 72) * this.worldPixel,
92 | ]);
93 | this.mode = "normal";
94 |
95 | this.scene.add(this.cursor);
96 |
97 | this.camera.position.z = ZSTART;
98 |
99 | this.touch = window.matchMedia("(pointer: coarse)").matches;
100 |
101 | updateVector(this);
102 |
103 | const start = this.text.linePositions[this.text.activeLine];
104 | this.cursor.setEnd(
105 | this.lastPosition[0] + this.vector.x + start[0],
106 | this.lastPosition[1] + this.vector.y + start[1]
107 | );
108 |
109 | this.animate();
110 |
111 | const handleResize = () => {
112 | this.renderer.setSize(window.innerWidth, window.innerHeight);
113 | this.camera.aspect = window.innerWidth / window.innerHeight;
114 | this.camera.updateProjectionMatrix();
115 | };
116 | window.addEventListener("resize", handleResize);
117 | }
118 |
119 | setMode(newMode: "normal" | "choosePosition" | "navigation") {
120 | this.mode = newMode;
121 | this.cursor.updateMarker();
122 | }
123 |
124 | setBackgroundColor(color: string) {
125 | this.scene.background = new THREE.Color(color);
126 | }
127 |
128 | animate() {
129 | // this.renderer.setClearColor(0xff0000, 0);
130 | this.renderer.clear();
131 | this.renderer.render(this.scene, this.camera);
132 | requestAnimationFrame(this.animate.bind(this));
133 | }
134 |
135 | printImage() {
136 | if (this.text.charCounter > 0) {
137 | let multiplier = 1;
138 | if (this.save2x) multiplier = 2;
139 |
140 | const visibleHeight =
141 | 2 * Math.tan((this.camera.fov * Math.PI) / 360) * 10;
142 | const zoomPixel = visibleHeight / window.innerHeight;
143 | let { top, left, bottom, right } = this.text.getPoints();
144 | const pad = 0.6;
145 | top += pad;
146 | bottom -= pad;
147 | left -= pad;
148 | right += pad;
149 | const width = ((right - left) / zoomPixel) * multiplier;
150 | const height = ((top - bottom) / zoomPixel) * multiplier;
151 | const center = [left + (right - left) / 2, bottom + (top - bottom) / 2];
152 |
153 | const adjust = height / window.innerHeight / multiplier;
154 |
155 | this.printCamera.position.x = center[0];
156 | this.printCamera.position.y = center[1];
157 | this.printCamera.position.z = 10 * adjust;
158 |
159 | this.printCamera.aspect = width / height;
160 | this.printCamera.updateProjectionMatrix();
161 | this.printRenderer.setSize(width, height);
162 |
163 | this.cursor.visible = false;
164 | this.cursor.curMarker.visible = false;
165 | this.cursor.nextMarker.visible = false;
166 | this.cursor.mouse.visible = false;
167 |
168 | let cacheBackground = Object.assign({}, this.scene.background);
169 | if (this.transparentBackground) {
170 | this.scene.background = null;
171 | }
172 | this.printRenderer.setClearColor(0x000000, 0);
173 | this.printRenderer.clear();
174 | this.printRenderer.render(this.scene, this.printCamera);
175 |
176 | this.printRenderer.domElement.toBlob((blob) => {
177 | const link = document.createElement("a");
178 | link.setAttribute(
179 | "download",
180 | "type-" + Math.round(new Date().getTime() / 1000) + ".png"
181 | );
182 | link.setAttribute("href", URL.createObjectURL(blob));
183 | link.dispatchEvent(
184 | new MouseEvent(`click`, {
185 | bubbles: true,
186 | cancelable: true,
187 | view: window,
188 | })
189 | );
190 |
191 | if (this.transparentBackground) {
192 | this.scene.background = cacheBackground;
193 | }
194 |
195 | this.cursor.visible = true;
196 | this.cursor.mouse.visible = true;
197 | this.cursor.curMarker.visible = true;
198 | this.cursor.nextMarker.visible = true;
199 | });
200 | }
201 | }
202 | }
203 |
204 | export default State;
205 |
--------------------------------------------------------------------------------
/src/Text.tsx:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { Euler, ShaderMaterial } from "three";
3 | import { updateVector } from "./Actions";
4 | import { TEXT_COLOR } from "./Constants";
5 | import LineHandles from "./LineHandles";
6 | import State from "./State";
7 |
8 | const makeCanvas = (c: HTMLCanvasElement, chars: string[], color: string) => {
9 | const cx = c.getContext("2d")!;
10 | cx.clearRect(0, 0, c.width, c.height);
11 | const fs = 64;
12 | cx.font = fs + "px custom";
13 |
14 | const ch = Math.round(fs * 1.2);
15 |
16 | const toMeasure = cx.measureText("n");
17 | const cw = toMeasure.width;
18 | c.width = 2048;
19 | const rows = Math.ceil((chars.length * cw) / c.width);
20 | c.height = rows * ch;
21 | const perRow = Math.floor(c.width / cw);
22 |
23 | cx.fillStyle = "white";
24 | cx.fillRect(0, 0, c.width, c.height);
25 | cx.clearRect(0, 0, c.width, c.height);
26 |
27 | // have to set font again after resize
28 | cx.font = fs + "px custom";
29 | cx.fillStyle = color;
30 | cx.textBaseline = "middle";
31 | for (let i = 0; i < chars.length; i++) {
32 | const char = chars[i];
33 | const col = i % perRow;
34 | const row = Math.floor(i / perRow);
35 | // console.log(col * cw, row * ch + ch / 2);
36 | cx.fillText(char, col * cw, row * ch + ch / 2);
37 | }
38 | return { c, cw, ch, rows, perRow };
39 | };
40 |
41 | const LIMIT = 2000;
42 | class Text extends THREE.InstancedMesh {
43 | chars: Array;
44 | state: State;
45 | aspect: number;
46 | lines: Array;
47 | linePositions: Array<[number, number]>;
48 | relPositions: Array>;
49 | activeLine: number;
50 | lineHandles: LineHandles;
51 | dragLineIndex: null | number;
52 | selectedLines: Array;
53 | perRow: number;
54 | rows: number;
55 | canvas: HTMLCanvasElement;
56 | ch: number;
57 | charCounter: number;
58 |
59 | constructor(state: State, lineStart: [number, number]) {
60 | const geometry = new THREE.PlaneBufferGeometry();
61 | var uv = geometry.getAttribute("uv");
62 | let texture;
63 | let texScale = [1, 1];
64 | let aspect;
65 | let rows;
66 | let perRow;
67 | const chars =
68 | " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012346789%$€¥£¢&*@#|áâàäåãæçéêèëíîìï:;-–—•,.…'\"`„‹›«»/\\?!¿¡()[]{}©®§+×=_°~^<>".split(
69 | ""
70 | );
71 | const canvas = document.createElement("canvas");
72 | let ch;
73 | {
74 | const madeCanvas = makeCanvas(canvas, chars, TEXT_COLOR);
75 | const c = madeCanvas.c;
76 | const cw = madeCanvas.cw;
77 | ch = madeCanvas.ch;
78 | rows = madeCanvas.rows;
79 | perRow = madeCanvas.perRow;
80 | texture = new THREE.CanvasTexture(c);
81 |
82 | uv.setXY(0, 0, 1);
83 | uv.setXY(1, 1, 1);
84 | uv.setXY(2, 0, 0);
85 | uv.setXY(3, 1, 0);
86 | texScale[0] = cw / c.width;
87 | texScale[1] = ch / c.height;
88 |
89 | aspect = [cw / ch, 1, 1];
90 | }
91 |
92 | const offsets = [];
93 | for (let i = 0; i < LIMIT; i++) {
94 | offsets.push(0, 0);
95 | }
96 |
97 | const selected = [];
98 | for (let i = 0; i < LIMIT; i++) {
99 | selected.push(0);
100 | }
101 |
102 | geometry.setAttribute(
103 | "offset",
104 | new THREE.InstancedBufferAttribute(new Float32Array(offsets), 2, false)
105 | );
106 |
107 | geometry.setAttribute(
108 | "selected",
109 | new THREE.InstancedBufferAttribute(new Float32Array(selected), 1, false)
110 | );
111 |
112 | const vertexShader = `
113 | varying vec2 vUv;
114 | attribute vec2 offset;
115 | varying vec2 vOffset;
116 | uniform vec2 texScale;
117 | varying vec2 vTexScale;
118 | uniform vec3 aspect;
119 | uniform float scale;
120 | attribute float selected;
121 | varying float vSelected;
122 |
123 | void main() {
124 | vUv = uv * texScale;
125 | vOffset = offset * texScale;
126 | vTexScale = texScale;
127 | vSelected = selected;
128 |
129 | gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position * aspect * scale, 1.0);
130 | }
131 | `;
132 |
133 | const fragmentShader = `
134 | uniform sampler2D texture1;
135 | varying vec2 vUv;
136 | varying vec2 vOffset;
137 | varying float vSelected;
138 | uniform vec3 color;
139 |
140 | void main() {
141 | vec4 tex = texture2D(texture1, vec2(vUv.x + vOffset.x, vUv.y + vOffset.y ));
142 | vec4 colored = vec4(color, tex.a);
143 | if (vSelected == 1.0 && colored.a < 0.2) {
144 | colored.r = 0.5;
145 | colored.g = 1.0;
146 | colored.b = 0.5;
147 | colored.a = 1.0;
148 | }
149 | gl_FragColor = colored;
150 | }
151 | `;
152 |
153 | var uniforms = {
154 | texture1: { type: "t", value: texture },
155 | texScale: { value: texScale },
156 | aspect: { value: aspect },
157 | selected: { value: selected },
158 | scale: { value: 0.5 },
159 | color: { value: [1.0, 0.0, 0.0] },
160 | };
161 |
162 | const material = new THREE.ShaderMaterial({
163 | uniforms: uniforms,
164 | vertexShader: vertexShader,
165 | fragmentShader: fragmentShader,
166 | });
167 | material.transparent = true;
168 |
169 | super(geometry, material, LIMIT);
170 | this.chars = chars;
171 | this.state = state;
172 | this.aspect = aspect[0];
173 | this.perRow = perRow;
174 | this.rows = rows;
175 | this.lines = [""];
176 | this.linePositions = [lineStart.slice() as [number, number]];
177 | this.relPositions = [[]];
178 | this.activeLine = 0;
179 | this.dragLineIndex = null;
180 | this.selectedLines = [];
181 | this.canvas = canvas;
182 | this.ch = ch;
183 | this.charCounter = 0;
184 |
185 | state.scene.add(this);
186 |
187 | this.lineHandles = new LineHandles(state);
188 |
189 | // this.setChars();
190 |
191 | // const startText = "start typing";
192 | // let counter = 0;
193 | // let interval = setInterval(() => {
194 | // if (counter === startText.length - 1) clearInterval(interval);
195 | // this.addText(startText[counter]);
196 | // counter++;
197 | // }, 30);
198 |
199 | // setInterval(() => {
200 | // this.addText(chars[Math.floor(Math.random() * chars.length)]);
201 | // }, 20);
202 | }
203 |
204 | setColor(color: string) {
205 | (this.material as ShaderMaterial).uniforms.color.value = new THREE.Color(
206 | color
207 | ).toArray();
208 | }
209 |
210 | getInstanceLineIndex(instanceIndex: number) {
211 | let total = 0;
212 | for (let i = 0; i < this.lines.length; i++) {
213 | const line = this.lines[i];
214 | const lineLength = line.length;
215 | if (instanceIndex < total + lineLength - 1) return i;
216 | total += lineLength;
217 | }
218 | }
219 |
220 | renderLinesSelected() {
221 | const selectedBuffer = this.geometry.attributes.selected.array;
222 | // @ts-ignore
223 | selectedBuffer.fill(0);
224 | let charCounter = 0;
225 | for (let i = 0; i < this.lines.length; i++) {
226 | const line = this.lines[i];
227 | if (this.selectedLines.includes(i)) {
228 | for (let j = 0; j < line.length; j++) {
229 | // @ts-ignore
230 | selectedBuffer[charCounter + j] = 1;
231 | }
232 | }
233 | charCounter += line.length;
234 | }
235 | this.geometry.attributes.selected.needsUpdate = true;
236 | }
237 |
238 | getPositionFromAngle(
239 | prev: [number, number],
240 | angle: number
241 | ): [number, number] {
242 | const rx = 0.6 * this.aspect;
243 | const x = prev[0] + Math.cos(angle) * rx;
244 | const y = prev[1] + Math.sin(angle) * rx;
245 | return [x, y];
246 | }
247 |
248 | getPoints() {
249 | const _center = new THREE.Vector3();
250 | const _matrix = new THREE.Matrix4();
251 | const _scale = new THREE.Vector3();
252 | const _quaternion = new THREE.Quaternion();
253 | let left = Infinity;
254 | let right = -Infinity;
255 | let top = -Infinity;
256 | let bottom = Infinity;
257 |
258 | const activePoints = this.lines.reduce(
259 | (total, curr) => total + curr.length,
260 | 0
261 | );
262 |
263 | for (let instanceId = 0; instanceId < activePoints; instanceId++) {
264 | this.getMatrixAt(instanceId, _matrix);
265 |
266 | _matrix.decompose(_center, _quaternion, _scale);
267 | // apply parent transforms to instance
268 | // _center.applyMatrix4(this.matrixWorld);
269 | left = Math.min(_center.x, left);
270 | right = Math.max(_center.x, right);
271 | top = Math.max(_center.y, top);
272 | bottom = Math.min(_center.y, bottom);
273 | }
274 | return { top, left, right, bottom };
275 | }
276 |
277 | addText(data: string) {
278 | if (this.charCounter < LIMIT) {
279 | const rad = Math.atan2(this.state.vector.y, this.state.vector.x);
280 | const position = this.getPositionFromAngle(this.state.lastPosition, rad);
281 |
282 | const start = this.state.text.linePositions[this.state.text.activeLine];
283 | const cursor = [
284 | this.state.lastPosition[0] + this.state.vector.x,
285 | this.state.lastPosition[1] + this.state.vector.y,
286 | ];
287 | const prevSigns = [
288 | cursor[0] - this.state.lastPosition[0] < 0 ? -1 : 1,
289 | cursor[1] - this.state.lastPosition[1] < 0 ? -1 : 1,
290 | ];
291 | const nextSigns = [
292 | cursor[0] - position[0] < 0 ? -1 : 1,
293 | cursor[1] - position[1] < 0 ? -1 : 1,
294 | ];
295 | if (prevSigns[0] !== nextSigns[0] || prevSigns[1] !== nextSigns[1]) {
296 | return;
297 | }
298 |
299 | this.lines[this.activeLine] += data;
300 | this.relPositions[this.activeLine].push(position);
301 | this.setChars();
302 | this.updatePositions();
303 |
304 | // const positionDiff = [
305 | // position[0] - this.state.lastPosition[0],
306 | // position[1] - this.state.lastPosition[1],
307 | // ];
308 |
309 | const relPositions = this.relPositions[this.activeLine];
310 | this.state.lastPosition = relPositions[
311 | relPositions.length - 1
312 | ].slice() as [number, number];
313 |
314 | updateVector(this.state);
315 |
316 | // this.state.camera.position.set(
317 | // this.state.camera.position.x + positionDiff[0],
318 | // this.state.camera.position.y + positionDiff[1],
319 | // this.state.camera.position.z
320 | // );
321 |
322 | this.state.cursor.setEnd(
323 | this.state.lastPosition[0] + this.state.vector.x + start[0],
324 | this.state.lastPosition[1] + this.state.vector.y + start[1]
325 | );
326 |
327 | this.state.movedCheck = false;
328 | } else {
329 | alert("You have reached the charater limit of " + 2000 + " characters.");
330 | }
331 | }
332 |
333 | backspace() {
334 | const line = this.lines[this.activeLine];
335 | if (line.length > 0) {
336 | const start = this.state.text.linePositions[this.state.text.activeLine];
337 | const thisLine = this.lines[this.activeLine];
338 | this.lines[this.activeLine] = thisLine.slice(0, thisLine.length - 1);
339 | const relPositions = this.relPositions[this.activeLine];
340 | this.relPositions[this.activeLine] = this.relPositions[
341 | this.activeLine
342 | ].slice(0, thisLine.length - 1);
343 | this.setChars();
344 | this.updatePositions();
345 | if (thisLine.length === 1) {
346 | this.state.lastPosition = [0, 0];
347 | } else {
348 | this.state.lastPosition = relPositions[
349 | relPositions.length - 2
350 | ].slice() as [number, number];
351 | }
352 |
353 | updateVector(this.state);
354 | this.state.cursor.setEnd(
355 | this.state.lastPosition[0] + this.state.vector.x + start[0],
356 | this.state.lastPosition[1] + this.state.vector.y + start[1]
357 | );
358 |
359 | this.state.movedCheck = false;
360 | }
361 | }
362 |
363 | enter() {
364 | this.state.setMode("choosePosition");
365 | return;
366 | }
367 |
368 | setChars() {
369 | let counter = 0;
370 | const offsetBuffer = this.geometry.attributes.offset.array;
371 | // @ts-ignore
372 | offsetBuffer.fill(-1);
373 | for (const line of this.lines) {
374 | for (const char of line.split("")) {
375 | const index = this.chars.indexOf(char);
376 | const col = index % this.perRow;
377 | const row = Math.floor(index / this.perRow);
378 | // @ts-ignore
379 | offsetBuffer[counter * 2] = col;
380 | // @ts-ignore
381 | offsetBuffer[counter * 2 + 1] = this.rows - 1 - row;
382 | counter++;
383 | }
384 | }
385 | this.geometry.attributes.offset.needsUpdate = true;
386 | }
387 |
388 | updatePositions() {
389 | const matrix = new THREE.Matrix4();
390 | const euler = new Euler(0, 0, 0);
391 | let prev = [0, 0];
392 | let charCounter = 0;
393 | // clear empty
394 | for (let i = this.lines.length - 1; i >= 0; i--) {
395 | const line = this.lines[i];
396 | if (this.activeLine !== i && line.length === 0) {
397 | this.lines.splice(i, 1);
398 | this.linePositions.splice(i, 1);
399 | this.relPositions.splice(i, 1);
400 | this.activeLine--;
401 | }
402 | }
403 | for (let j = 0; j < this.lines.length; j++) {
404 | const line = this.lines[j];
405 | const start = this.linePositions[j];
406 | this.lineHandles.setPosition(j, start);
407 | for (let k = 0; k < line.length; k++) {
408 | const position = this.relPositions[j][k];
409 | const x = position[0];
410 | const y = position[1];
411 | if (k === 0) prev = [0, 0];
412 | const rad = Math.atan2(x - prev[0], prev[1] - y);
413 | euler.z = rad - Math.PI / 2;
414 | matrix.makeRotationFromEuler(euler);
415 | matrix.setPosition(x + start[0], y + start[1], 0);
416 | this.setMatrixAt(charCounter, matrix);
417 | if (k === 0) {
418 | prev = [0, 0];
419 | } else {
420 | prev = [x, y];
421 | }
422 | charCounter++;
423 | }
424 | }
425 | this.charCounter = charCounter;
426 | this.instanceMatrix.needsUpdate = true;
427 | }
428 | }
429 |
430 | export default Text;
431 |
--------------------------------------------------------------------------------
/src/font.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "custom";
3 | src: url("data:application/font-woff;base64,d09GRgABAAAAAOfoABIAAAACEJwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAADnzAAAABwAAAAcjrX3Q0dERUYAAM2UAAAAKgAAACoFcQh+R1BPUwAA55gAAAAyAAAAQCOcJJ9HU1VCAADNwAAAGdUAAHPeEsLC209TLzIAAAIMAAAAWgAAAGBP+f9JY21hcAAABawAAAMCAAAEOIO5+9FjdnQgAAAQXAAAAFQAAACgH+EPxmZwZ20AAAiwAAAG8gAADhWeNhXSZ2FzcAAAzYwAAAAIAAAACAAAABBnbHlmAAAXHAAApbwAAVJwJGGSY2hlYWQAAAGUAAAANQAAADYP9TzXaGhlYQAAAcwAAAAgAAAAJP/MANxobXR4AAACaAAAA0IAAAZ6rcSQFmxvY2EAABCwAAAGbAAABnR4PcuCbWF4cAAAAewAAAAgAAAAIAiDCBpuYW1lAAC82AAAAuYAAAYkKtj6vnBvc3QAAL/AAAANywAAINfVeYc2cHJlcAAAD6QAAAC4AAAA1n69lAt4nGNgZGBgAOLs1r4p8fw2XxnkmV8ARRhuJSZ9g9DTHH6a/PvEdJr5FJDLwcAEEgUAfLgOIwAAAHicY2BkYGA+9e8TAwNTxE+T/71MpxmAIiiABQCn5AakAAEAAAM5AZAAMgCiAAYAAgGSAnMAjQAAAu8DcQADAAJ4nGNgYYpgnMDAysDC1MUUwcDA4A2hGeMYdBldGRiYuFk5mRiZgPINDEzqDAwSjAxQ4OWkoMBwgIHrwWbmU/8+MTAwn2Lkc2BgmA6SY1JjugCkFBjYAD7MDYYAAHicrVNPSBRhFH8teJENkfayCEu4CIu4IIuyCIuxUIslxVCMDoOyCBJ4KAQPdergRawhKGmJJPDQEnixw16kiDpEloTgwYUuEYUgQRCRuPvNvOn3vp1ZN9lj8/h9f96/7/2biEU24YtYDeD7SiVKUoESlKVD2qQF6qU8lWmW9micDOjn6TyZOBc0CvQZUhMSk5YoTQPQmCCLiiDRLoJn0xx0TX0XFMAzoRmnFN4xaRfSEiQ5WqdzsLVwlttF7cekaeiNaq7I5ukyZDZlYDuIVV72gTxit+CpH3wfOg5e2cBZXluH/zytgvZ1Bnv0iSrILqGpg24immNKIIPiCUrq+EO6ru8NSsBjFnEfUzLIwNDZNqjQwjNQzWRTkqFrwADsQkj9WmEh3ySsim1goEqtsP1fyDcGmd2Cot6lY1J5GxWdRgwp9CVEDJSCzAoBP+Y/MRuQS9TzTRRQ5VakEWcIeaUd4rriGd13o4kyJi/0IHO0hI4tUlX3aI5uQ9/WORFuE9CUfNK+omFwbMRl67mTOYtD0gG+qV+LIWM76MIEvKfgSbTHMU/D1InVpkvQGkbVjcDegH1OW0xDUgjsLW2fAQ0G9jm8LfZjWLPQicHaQg5SaVPHFoOHjPY6p7OWGkr+IWbhvQtTWwYZqG0SHAMzm4OsjNsKPaQpVGIDqOjp3YBdGtI89i74ziGbToH/AnsakNXQ8WfhbQqTnoAnsZBKIy5EVqct6gPJHgfJXtfrlsgDnbb3kEL+/yI9m3Xxiz+1DpSa77Y9t/FQP16bEeOmcxO/AU5YnfhODei+kq8ip/WNREc0g3MOlZaOZnC76x/6h6rCo95vz6sN8VVVUQ5Hve/K8Y68I97hHbWm1ribv7n3eAdddIDXer4dPd0y20s4Vd337hlVhe5Z2B/xS37s7rm36h/qd7iHe9A7ci+A+4qvYF3hGbefK2Rwd83ld/wItKt8nlEPvAP3Y22E7/AkXt7nZV4ELfOYesJP1SpP8qREB/qh9vXu1IYQdaVxhqzMUUhCzjoIXHgreT9VlaMcUZveF+8tVs97w33qD3crx73B95Fl1DvgEZ5ye/m5+0zgbXvbqM62kFpo7FIp5fwFvLdpQwAAeJy9kmlsFVUYhp9v7lARC4W2QClwOTPQsmnZxSIom1KW0kVcwYIIKqCADSqCkSKggAL1QtkJO1opt0REUVotYgEp/iCBgAQzM4AR+kP2GBNux8NtQ0j4wT+/5GzJyXm+nOcFAtSOVoieMdL0SaJn04jX6yz6U48+eteAhnxIicyRkNHS6G0cNn41/giUBSrMWDPLHGOONT83S81LwQWqsUpUySqobJWquqoeqo8arKarGWq+KlFhVW4lWM0s20q10qw1tmHH2HF2vN3UTraDdmc7wx5nT0zZmHLUxd3i7vH9211F2YpNhDU7SbMPafbpwH7NFrORmWPmmYVm2LwYLNDsBJWkWikVZXdX6XXsgnvYhXXsJprd4g57QgpR9iZ3t2aL/69/078A/gG/1O/kx9XMrSmoyY8URUKR3EhOJDuSFWkTCdyqOt/L2+kVezu87d42b7O32lvlhbxFXp432sv2OnodvGS30j3o7nP3umE30/nTOeeccX53TjknnONOpVPshJxlZ2+erbJXWv9Y1bUuopXP/10mrzKBibzG67zBJCYzhTd5i6nEGA1uXxDu6q+2BCOaIh2g+7wudYx6xPAA9XlQu32IWO23EXE0pgnxJJBIU5rRnCRakExLncbWBGmjM2Bh05Z2pJBKezrQkU505mEeIY0udKUb3elBT3rxKL15jHSd3cfpSz+e4Emd5QEMZBCDeYqnGUIGQxnGcEaQyUiyyCaHXJ5hFM/yHM/zAi/yEqMZw8vkMZZxvMJ43f8CPmYhiwmxig1sYTNb2c42dvAlX1HMTkoIs4tSdvM137CHvXzHt+znB8ool194O/rHk+QQ7+lcT2OKnOEdJss1PmGNXCZfrssNbeBd+Uuq5ZLkyhVtYbZmf8H3FGhHU+VvGSBX5aJ284Ec1n86l/mslARJlDIplwPys/woP7HPKKJC0uU3OSlVciwqoZKZUiEH5Qjz+JSP+IxFLGUZhSxhBUX6ynLWsZ61XJMhMpLpMkyGywjelyzJlIz/AGp79DgAAHicrVdrWxvHFZ7VDYwBA5Kwm3XdUcaiLjuSSes4xFYcssuiOEpSgXG76zTtLhLu/ZL0Rq/p/aL8mbOifep8y0/Le2ZWCjjgPn2e8kHnnZl35lznzEJCSxIPozCWsvdELO72qPLgUUS3XLoRJ4/l6GFEhWb60ayYFYOBOnAbDRIxiUBtj4UjgsRvkaNJJo9bVNCqoRotKmo5PC7W6sIPqBrIJPGzQi3ws2YxoEKwfyRpXgEE6ZBK/aNxoVDAMdQ4vNrg2fFi3fGvSkDlj6tOFWuKRD86jMerTsEoLGkqelQPItZHq0GQE1w5lPRxn0prj8Y3nIUgHIRUCaMGFZvx3jsRyO4oktTvY2oLbNpktBnHMrNsWHQDU/lI0gavbzDz434kEY1RKmmuHyWYkbw2x+g2o9uJm8Rx7CJaNB8MSOxFJHpMbmDs9ugao2u99MmSGDDjSVkcxPEwjcnx4jj3IJZD+KP8uEVlLWFBqZnCp5mgH9GM8mlW+cgAtiQtqphwIxJymM0c+JIX2V3Xms+/VE7CAZXXG1gM5EiOoCvbKDcRod0o6bvpXhypuBFL2noQYc3luOSmtGhG04XAG4uCTfMshspXKBflp1Q4eEzOAIbQzHqLLmjJ1i7CrZI4kHwCbSUxU5JtY+2cHl9YFEHorzemhXNRny6keXuK48GEAK4nMhyplJNqgi1cTghJF0ZOrERqVbptVSycs52uY5dwP3Xt5KZFbRw6XpgXxRBaXNWI11HEl3RWKIQ0TLdbtKRBlZIuBW/wAQDIEC3xaA+jJZOvZRy0ZIIiEYMBNNNykMhRImkZYWvRiu7tR1lpuB1fp4VDddSiqu7tRr0HdtJtYL5q5ms6EyvBwyhbWQnISX1a9vjKobT87BL/LOGHnFXkotjsRxmHD/76I2QYapfWGwrbJti167wFN5lnYnjShf1dzJ5O1jkpzISoKsQrIHFv7DiOyVZdi0wUwv2IVpQvQ1pE+S0olBxKsYaZBDb858oVRyyLqvB9nyNRgyFYy2qzHn3ouc8jbqtwtu616LLOHJZXEHiWn9NZkeVzOiuxdHVWZnlVZxWWn9fZDMtrOptl+QWdXWDpaTVJBFUShFzJNjnv8rVpkT6xuDpdfM8utk4srk0X37eLUgu65J3nMPv6b+srO3rSvwb8k7DrefjHUsE/ltfhH8sm/GO5Bv9YfhH+sbwB/1h+Cf6xXId/LNtadkzl3tRQeyWR6H5OEpjc4ja2uXg3NN306CYu5gu4E115TlpVuqm4wz+T4bL3X57kOlushFx69MJ6VnbqYYTuyF5+5UR4zuPc0vJFY/mLOM1yws/qxP090xaeF6v/Evy3fU9tZrecOvt6G/GAA2fbj1uTbrboJd2+3GnR5n+josIHoL+MFInVpmzLLvcGhPb+aNRVXTSTCC8g2i+epk3HqdcQ4TtoYqt0GbQS+mrT0LJ54dPFwDsctZWUnRHOvHuaJtv2PKrgNuRsSQk3l63d6Lgky9I9Lq2Vn4t9brlz6N7K7FA7CVWCp+9twm3PPk+lIBmiKPG6YrkUpC5wwi3v6T0pTMNDoHaQYwUNO/x0zQVGC847Q4myzbWCS4xklFFw5c+cihPZiCYbUcRv3lI/1YVC6ExiITFbXstjoToI0yvTJZoz6zuqy0o5i/emIWRnbKRJ7Edt2cHLztbnk5LtylNBlSZG909+xNgknlXtebYUl/yrJywJJulK+EvnaZcnKd5C/2hzFHfochD1XTyushO3sw2nhnv72qnVPbd/atU/c++zdgSa7njPUrit6a43gm1cY3DqXCoS2qYN7AiNy1yfazbyKb7UfOs6F6jC9Wnj5tnzd3Q2h0dnsuV/LOnu/6uK2SfuYx2FVnWiXhpxbmcXDfiON4nK6xjd9Roqj0vuzTQE9xGCur32+CzBDa+26TZu+RvnzPdwnFOr0kvAb2p6GeItjmKIcMsdvMCTaL2tuaDpLcCv6rEQOwB9AIfBrh47ZmYPwMw8YE4XYJ85DB4yh8HXmMPg6/oYvTAAioAcg2J97Ni5R0B27h3mOYy+wTyD3mWeQd9knkHfYp0hQMI6GaSsk8EB62QwYM7rAEPmMDhkDoPHzGHwbWPXNtB3jF2MvmvsYvQ9Yxej7xu7GP3A2MXoh8YuRj8ydjH6MWLcmSbwJ2ZEW4DvWfga4PscdDPyMfop3tqc8zMLmfNzw3Fyzi+w+ZXpqb80I7PjyELe8SsLmf5rnJMTfmMhE35rIRN+B+696Xm/NyND/8BCpv/BQqb/ETtzwp8sZMKfLWTCX8B9dXreX83I0P9mIdP/biHT/4GdOeGfFjJhZCETPtTji+YTlyruuFQohvjvCW0w9j2aPaTi9f7R5LFufQIzRQQAAAB4nGPw3sFwIihiIyNjX+QGxp0cDBwMyQUbGdidtjEw2OoqsjJogTgOPF4sTixGHGpsEuysXFAhPyY3Jis2HRZ5VrAQj9M+oQMCB3gOcB5gc2BgZeAGigk67WNwgEOQ2E4GZgYGl40qjB2BERscOiJA/BSXjRog/g4OBogAg0uk9EZ1kNAujgYGRhaHjuQQmAQIOPD4MLkwmbBpsEixsvJp7WD837qBpXcjE4PLZtYUNgYXFwAv9zG6eJxjYCABRAFhAEMA0wUGBiY1Bob/YUy3/n9jMvj/7X8YgwQEMl1gUvv/GygDpVH0XEDS9ReqzwYIzRnMGSX+X2aU/X8Oxmf6y7iDmYlxMwDQoSa3eJw9wn1IWgkAAPCunHPmrLmuOddc69RMy9T0ac7MmTavvaw1Z2b2ZWbuzY9dexfVXHrOOq9Lr/Oc17XOOc9rXRxHREjEkDEkYowIiQiJiBgjRoyICImIu7+O3y8tLY39P2NaKO3gC80XiXR1ujN9Jj2RIcyAMyYy5jPWMvYQ1QgbYu2M6Ax0Jo7MRxqR22fFZ3vPxs5+QGFQTFTgXME53blFNAotQpvQPvR2JjqTk/kwM5gZwxRgjJg5zP556Xnn+Y3zx1gaVoNdxJ5kcbIMWaNZ4azlrJNsQjYne/pCzgXdhRUcHgfiXuNOL6ou/n0xmZObA+cs5px+CX25nkvKteXO5m5ewlyCLkUv7eIL8F585DLmMnR58vIBQUlYuIK8wriykofJW7yqvzp7dYv4kOglrlxDXmu4Fr+2lV+Qr8zX54fy169jr7dfPymY/2rsq2MShkQkMUkSkpJkIE2R5khx0jppl3RMxpCJZCZZQq4ha8kWsoPsI0fIUXKCwqFUU9QUiGKjeCk7hbZCd6G/MFT4d+FCYZzqoI5Sx6kR6iz1NXWZukbdpu4VmYr6ilxFY0WTRdNF87RcWh/NRRujTdKmafN0HJ1IZ9KFdAVdQzfQe+gu+hg9TP+H/ob+nr5F/0Q/KR4sfl8CldhK3CUTJVMlCyXxklWGgAEy1AyI0ctwM/yMKcYcI8ZIMD4wjkqFpaHSudJY6WrpZulu6SEzjYlhEpkSpo15zEKycCwii8Zis0SsepaWZWQNsEZZ46wIK8EmsIvZfLaUXc/WsS1sB3u9jF0mLasv05VZyhxloxwB5y1njbPN2eOkuGhuLpfK5XCruQ3cVi7EtXHdXD83xJ3jpgA0kAcwAAEAAmoAAnoBN+AHpoA5IAa8AzaBXeCYh+TheTQewJPzlDwDr4c3ztvifeId8dP5WD6fb+QH+PP8j+U55cpyV/lW+b6gQEAT6AUPBXHBqmBT8EGwJ0jdSLsB3YjeSAkRwmyhQqgS+oQTwsUKRAWmQlERrUiJECKMCBQpRVqRXjQs8ooCoqBotVJcKa+EKxcrP4rxYrU4JN68WXwzdPPjzc8SUBKUxKqQVdgqsCpalZIipBxpn3RdeipDyqgypowv08h0steyuGy/GqjWVfurE7ewt6S3ArdO5By5WB6Vv5WvyNflW/JP8oOvB79erqHVADWSGkdNtGbvNum29vY8iAIJIAXkg1JQBepACHSBQXABTIBJ8LSWVKuqNdT21oZr9xUhxaJiuy67TlKnrwvXva/brNurO6nH1OfVu+uX7xDu9N7Za9DdRdyF775RIpRKpe8e6l7vvTcqlKpBFVQdNgKNosaBRmfjSON445paoXapJ9QbTblN9U1Qk6tpomm2KanBaCSaPs28Zr+Z3Qw3B5oXmpPNJ1qClq9VaWFtQLugTWpPWogtopbWlsGWcMu7loNWQqukFWoNtMZad9rQbUBba9tw21zbVjuyvbhd3e5on2p/357qyO+o7jB1jHfEOz7psnVCnVHn1cV0+50FnQ2djs7Zzi09Ti/R9+pn9BtdqC5BF9Q13hXr2jYgDcUGpaHXEDTEDcfdlO76bmf3TPeaEW0EjFqjz/jGeHgfuG+4H7ifgJAQE4KhKSjxAPtA8WDkwbYJaxKZLKZJ04Jpw4wyF5j55gaz0ew0T5qj5lXzkSXHIrCoLT0Wr2Xa8tayaTmyZltpVrFVZTVZndZx6z/WuDVpPfgG37PwaOnRzqMUjIUpsBBWw4NwEH4Nr8Gpb/HfGvpq+rx9n/s1/ab+sf5w/1L/8QByAD9AG/AOHD6WPw48fvf41DZgi9i2n2CfCJ8sDbLt6XasnWin2fn2HrvDHrBH7HP2pH3XfuRAOHAOgoPvEDtMDtt3/u+WnTlOoXPCufYU+1T1NPj0wCV1DbveDRX8RzgEDcFDg0M7Q4fDyO9R36+5KW6Ne8Yddb91r7iTPwA/SEbQI4GR6R9nfoyOkkZ9o589aR6ch+QBPDaPzxP0JDwfPEfebK/CO+E9/kn90+JY3tjAWOpn9c+LvjzfoC/5S+4v236Cv8ef8J8+4z8bfrYewAVOfo2MU37j/LYzMfgc8Xzk+fFkzeT476Lfl4KK4Ehw5wXhRfuLnhcHoYbQ/Ev45fzLzXB6mBbWhEfCy39MRtCRvsjBn7NT6CnnK+SrpVcb0/BfyL/4/wKJwpELeJy0vQt4W1eVKLz3OTo6elnS0dPW09LRw2/LliX5Idty4jiOn4oTJ7GSxkmaJnXpI01pCYW2IRQ6QFpoL195dIBS2oHpnaHttBSYMoWf0pZXZwI/Q2c63LnDDPTO8Pinl1vK08q/1j4PSY5dynz/70SyfLT3OXuvvfZ6r7UJTyqE0Cp3gfDESMykr9RjNnIcT+cIITxH+JOEo5TbTziOrhrgE10kxCQKBmjPS4LR35mVYlIyJqVjUoU+WH2Adnmq3+MurGf6ub6R9b+DuxCO/PDi7+hb4BkCcZN0KQHX2M3owDzclz9KeD7HLxDiclhN0EZwCWZ/J/XyMlweyI/Qfp/XY+yk8RQ96i15fYmwJ5xgb9yFM9Xb6DvOrJ8IxuPBYGsUnnUv/Sz9KvcS3MdKxktms1HgDJTQudlH+8oHSk08PJascpSQAboQLLnhE1zYjxfoKnxJ6OLKky63y2kwBzpp1ivKaTmHv+jRo9YZ27fgjXvpXvajzE2GtxzMLUiiZGdpUuA56jByRKTkSruziRdtFs5kFk0nDZTjBuat1GzOmWGu0Ug4BH2CgZZmP0zPLek/5minuwDPLGRF9vLK7CUX2Cs3Qgvy7YnLr0tOJe+H1/LRxLFrtc+fvj9xPzU/PfF1+JnQfsFKTV18M+fil2CsnaSPDJcKvV67FaDS0+kzGHiabA3zBo7nYc0peRehPH2XgfIc/y5YnZtgkjKRQ56QYG7pLHh8findQ3MD+QJAJUL98kAq7fVHKKyQ6JVzPTSd9I7B96le6vHn7PSB0QF/efexfcW9x7lD2c7U3O4rDxYPX8NRc3ZwoO+BI93ZL2aKIjWZQldMlY+b3vpW04ly17Dpf9tD18zsPmG6/Z3iieXbfmEa7OnZLv28nPyBmIeVjVz8HTfM/T1pYlDvJiOw0sVImDOK0RBnELg5wGXKGekarI9g4IQ1IorkKMwEEA4mrCJcMpFsTyaTsskc6XTDjLL9vjD1GGXAs4a/ktRLk16PL9ufH6UDKTlupPV/eOXt4+Pb8UXt2qfqz7nQ+o/ooiynWmX4qf5C+8Q1TYxPT4/jW/UJ/eMH6Inx6kd7J5KdvTF466RJ7VMScaxw8TUuDzidJL3ktpK9A9aGtkdDLQYjzwFiNwNid8CEAfX4s7BmbKrD8zBLw1GBGgy5eZEajepnA2B92+aNV4nWVjhKBGFEWFgpOdKpVG+6V3b54q5OkznU6YalZxgwxgGIvLICA0+UAq765bSdAsRy/n5oZAT4+QpwOdzS0rutY+V4JdfTkZH/Yufh62+8vJQ40Ld02RPxcd99+V7anNtht3RMtCZGu5ovW2gfyKa6+n5+2/Of3J/p27+w3J3+v6XJW9q7kG6Q12B/72f7O/ekwHG1rW3FjbxK1I3tJGxfM1JD2K5+wuXkzM2dbranX8PNzDYy3vOLF99Jf8zPEz8xPu4SaD9McowreO08zCDC+b09fO6LHQvX7cgs5CPhwkJm53Xz7fT5XbddMS0tW/PlK0aeHj6+kLeuuKaO3zYL97sB7vcJ9X4wLLhfBG9l58TcGA9o08Olb9hx3UJH+/x1OzMLhXAkv5Dh56XpK27b9fTsbcenXCvW/MLx4adHrijnoT+Jk8NAY46RHOkudbQ0SyYq4Ly7u9JyXKC7alM9yiEF253tj4Z4nGvBH+H9hR46CpQMNqh/jB+nftFOHTRCo1SEdUzL+Fe6MEZxVdPxyIozFgvbBJN0wGUSbOFYzLkSWYnOhQ3+IXdhuuBuNYuWVnd+Ou8Z9vHh+Sh9555Wo+T2mA2h3fLqqrw7ZDADOTO27mm12jiTqxBc6F8IzgSDu0LwIVRwiZwN5zQBVNjP5YBKT80+GoP188JaCpQ3IM8xGHDVaAGRNQCNDXR/3beUIqpSw+JKycxIptOIiJkFigSUEqiQPPHlA1+G//T0M8+Un3mG4c1DF4/Qt5APATXsUfDFxmD1LvhyiMBjLPqfN62ULMj9gMHxwIiSvOymbzlXLn+o+g1aYPc6TuP0VXoamjSXvBsRTVLQLOaNHacfqJ6ip8usz/cvvkbPkYdgvm5ETlgr6DdCFxJxbF5gBMfL6M33e9PpXnwt4q+2vj7Wf/biPVwTPwbPlEp2lVDTm5SH0SyV6WdXqx9b5cd+v5ewwZeBZphgn3iBfPeR9lKKGAiwQqCFPK/tedg+6iiADqbbEwyMbGNHOK8E+CrHe7hcllG7nKTsdH89acyVo0O7s9ndQ9Ho0FI//q5MFYtT+CoH/f4gvji+rzwYiQyW+6rf7FscjOKnsV27xmge38stkUjL/fgGUwJZBHDiAoy5rZR0OjgY2xxBMQHZM38EmJIiKEADrxSVkCPBoksD+YF8n2xkeJ0rZKWst/J/gagiisYm45333fBAmf7G5HU1GQXBYjhy8Nr1s2WGf4TBp53kS9kwhR01Z6QCYJVAzwL+qSACpKD8qiagQOP2dqkvmhDNwU73GM/g5DE6qJ2KwP7y2Xx/FtY9Vxjjcwq0Jrj46J5sf3kwunL0rYLNaBKdDnvYRi3Vsac7R1Ou7UPjs5yre2YgHBqY6dn9ob1rBpNBAPSnS8c/ldxWKsmDE9vyuNyTsJ42gE2G7Chti0mc0YCwQU7AnQV2x1Mj2zfCKlBuJleRoyCDAGtcoKQt3RqNBJ12i8kokAzNmMy+TgGWNp2NUFzagVQnAA5IAHB1BCP8FfOKbFJshnaOpnoXR5KGimkmPz63MLH3/RafxWqDfy3239EP/LnRJghCfv9ovLW4bAllp7sGxufHx+dX77a4RLMJgUjLxz9ptMDUNETBOS3D26swJyvpKKWtgI0czomASGK4UuHWIB6q0Idt7nIKAHdAdi+uupyL5ej1x6o/+GmlUn2Ku1Dd3vZI9cR959heeY1rgvsmyFRpu4utLYixIqDEWRMVCfy/FfgH5QSQEEAewCfAswgxHiVG44gRVzpBEukUPDMZN5vDnQUGJ4XhizLubTGbB4ApOwH3SSr9sGX7wOj09Nh88pBAz1e/xRvNbmeh++DJ1pG9A4duaro30TVV7C9OFej7lqpeyd58/dF+ZCj7dzE5cgrG7IIxR4HHd5XacXQcFc6qEsxVDcLLCL/QlmpL+Dy4YYF0jNOsslrqoJQ1w81bwL07xuWA5QA6Tg32/ck1H/YETWbBYXLYu+JdM7lIfHRvf3Z5NF5ZGI9kk15vMhsZW3yx+CdL//ZKa5vVJjhtVql1eHd//56RWOvInuy2RW8qF4vlUt4yo0sweIS1m8x8DkQKDtmxDcirH9QHkDJOIyauKqOnNIesuVn5hp7d+NXKk8B0mOANEAaqqyIkDBy0AXmWfnX9iv8jWgGHnI6moOX5CnfhxPq93JjkE82CkdKrGd1T4PgSSZMsGUe5MABSAiIANOF4gVsTqb67AZQaAcwNtLeNDA2M58Z7u9uy7dlEvNcExL+AgkAO+L9GFZGJe1HcVeGLso9P2UN83ecphFE4E3dHhxYzmcWhqDueCW9bhO2Ryy0XW18em50dw9fQxPZCYfvEs3KxJwpbKNyRj3RO9YdC/VOdkXxnSBCsh3f0LBSikfz8l2fHRhcXR8dm6dC2fGF6upDfxvAG3nC+iDfZUoZwBpTlzxopsEpq4NeAHGizZUCOtaaTrb2x3oSvGchYoEbvkY7JChFgtBSn52eTkeN2+DrCTWmowtDIG1DRqFNGNPrx2KKOQPQ3GrIwNIqla2i0Wp6oIRCO/yXgi7dyGaABfhIthWBJuKOAGKPz9TwykYgjWtRzSanu80s9iURP/Wtnors7AS/6JfWDoquhDIDP4omD7FGkgADgDOUJICM+WKD4ZFRNhxBTW7Qv8crttRY8CMYu0IGJ1WxwCA4mKhgBW/zuLO/WBgU6xEPlc+cKye6eRLK7+35aqH6Du9AjJ7u6E3IPUejfBMjhe4gEcqKTopyY9AoKL/GBTuUXvMll+tnqN20e0SzCf4+N5qtL9O0vVj5kNMEOAorGmYwfXXkR5lYmD3Mm+hMYi4jcE3glXOS5kxvFE4PBIBpEl+Rk+hxQMgoMq0y/UJ3G12/LtFQu14/NCWOzowxbyCWVDZlKp9LJnOBdpnmbx2QSRZPkr36TfnZCHxXP22BQxxnM7wDd/ydAH3xAUo8qpCEBxBjgiPIT0gGkwRodEARGg3NGgH5SaQY08PXagbA2kE3EpOZWEQi1u7ZJQYVWWZu7wYhwxy24A4P9Ux2V/omZmW23dNcbFFC879yZi3JrT2wbGdm2/pRmWcC5dF88yGVgr/UCW73xyWYbJ5qoqn+lYJAmkTeBqgnsThCV0aobTx3tEM6qG9gPJyLT1jsQThC5zduvlGxyOpZuS8bdSbM52lmQVL1LktOIZMCScBv4UcRXJA4JNuoIkEvYvZSRI/gCrncnp46P33BuJ20+PL59cqxz1EL5/QsHLw8PzHRPTZWu3JWm7mR8ZSWevIWOTL5ptu3U1dX5Y1cf3Fvecyjb0ZQJeuZmblirPpsBUWa2ffZqKpTzwzePvnUY4QKaGdcFayyCYNZZarMD7aFzoG3CAho4hZOj3QWoj8lk8pq8isgOtCfrltOyKFMeVI9UmpEdkX/l1qeP9P+P7GF6aEowcRwH5IwzCTvRqPStb1EfDXtlu8Nmc9hlb/XfGM1HudEDz5dIAKTdydKEETQFOmeiBsBFIIgnkQiuGqkqUjDdAgYTCrpcoFy1BuWQ7Aq4Wrxup51IVDKDeKTgi0bqO6nk9iroJKdVrPLS7hNv3lXcMR8ZWsr+88rbSjunx7mh7duGbga+dKC0WzAaKvO9s7kQiCY3/3rb8PC2hdF8fvRl2Fv7gEfZGc0eKQ0S4Eo8Yrlg4AXDrRpPGp5nCGHUZLhwKNDslhx2m4VEaVRUJbicig9+uYcqq48MqJC1AzVRxPLd2cM3WCvGHYXi1I7Rwg5jxXqDBUVwmkex/OSh/Oj0ePWb49Oj+UMncS0PMLn4AmnS6bG252qAS8baWxmb1hWFTlD/FR2hkx74xsDyaCw2ujzwjcp14/Pz49dxF1qHdvdV/1ff0lBreX6ctozPMfq3qj7LSnpLXUiuYKlP6mr8PBNnQSgXcgKKZFZiZdoeCt9ZCSiX+m+V/nn1MdpV/R5d5C4s/evSr5e0ezdx3yFmIpdaN713zrDAlEcUKt3qzSR5FQjuw7RYfa7Cfaf8H+XqF+BeZSZPov2lv9SrS2YCwQU72WBD0STIpAS7yZVEklTYdIlGKZoVVSmi3Dq01Hfwza5Dlqmcoj3d8zNcuf6loegLNI977tCB3pyyUNPjy+WHVJVK0ZmY/dbKLKuK8HyS2TfnjQKnKS6NsFP+yfCvUnkF/gOGXqCZ9QxdqP6VCjv6G3ZP0BsV0yhTw9gNFTORYeNdCyiQe2VQvmK56ifoRz/xieqJRa5/cXH9bxcX4Z7dwOdfZbpdT6kTaAPwIgM5C7flVglaRnEjq7KtouABCONIJBTZOyfFcjFNJU3fYZ4ZKM1V/52eRomqa24kLT6SGQKd8vHy+jrCJtA32UEYjiFtGIfn2hA+IBVRXcVQoSRQVb9w6YYEKYuMXOYBQpXKDQ9e9eVvvOkvbwBxs/q3tL/6cPXddLH6GN57TdVXBdwpPFWnQY8ouwQ+CkSQJAOaAxBfpWfo3uoS0LFz8FYvO3eTwVIuQTmS9hhByODmGiRkgDqO00C1u3aT7rDPJ4WZFopygs/PBH9QA2BJB/KaBAfyXFohrChNAMuQntonmEGHMIEQYBZ2n9qDRkCLYBQclrGZMavTaARGBE/ew1141RG3S06nZI87XgXE+E2g3+P3uH2evvAXv9g64PI5vX5Pf6AqwuiZHgc0GnUX2GstaEeZM/Bs0Jw+6CiJhqUwCh1Bqgw5g+QLRsw3jvH5L5vsggiShIHfd/pLJqcgIheg3D6Af9jV620JeBO9AfoTGNWP/BmP3+/0SL3hKtJ/pF/1dg+Qg+rIKe5Tg0ZOY9FAM5BSL/UKjaRUJWjpBqMH7dKIqWrreEIzc9TR0pqJ48WafYONCXEwBeOzA03tLnWo8sxJJs8IGlfKcTAsv1dyNNmsZmKndiNjQ4o4AwxIltyaQEPn+8pD0dahcuYjlcpHBicnBz/CXYgMzvf2zg9G0P1S/YudIyM7VXgcZLymBTQioF4Oypw7uqyB2M8E7hHUD1DqDcRbA+lgWm5JGlFCrIOKEQ28xrimGEiawOHTrUDDuRuvPMlbjPj5BwqApl6oh87k5S1/9acUPu/TAFT95tguDUYywMhBmlGGMFMODRyKCKHw73o5AgQ+kPiU3aooL6KyX3UYVV5GGM1v+8hV599aqZwf2j45eF4B0uyaB8RV5BdvZ1Cius7YQTIgn/EGIA9AmpBA0VsBVAw+TH/S7OKdcU9HnBGLQlZT+XppirkNIpxfM4r7opR5RhBiU6uzY6Xpge7lB09fjgohXd1eGNyWya/M7X7bm7J7i/EvDoyPZLu2OyMjHzhwIti/szNbLPR2DXtag9fMVk6GstMKPQNVhutkslasFIFdVuNoDIXgo0hEoMm409yMIstSN61+5xD3DqDFt3HvwP0K8+WZvJQoxXjNEVe3O+xNFhPoNyALKbsjhRtV3RHwwUi7Tp6qlEaLE5VTllPHONP6r3Gx8ffl18H9NXlQIqDtg6wL9IDWobkUdvI4uqy7RrsKQHHlriPvrZP47jz8tVuBWL7glZucNpuzSfZy2fUMwqBFpeldyEvcFo4aAVWMRrJq4DgNSYQjoiI5gKzsgx8nc/NseOTGAfCwhAXZJwI68XLLci+H9KuJAg7yOw5sAxGCcnYg8zzXs7Tr7IiRSsC+DKWzMMpf+Ps8zX5/s6fPz9nXf+Hr89b+ynDSSAKEXq/UF17/Txh/G4w/A+MPId/w2EW+BiHkIgCioCsgMQmLz8IAcYjjHLIkQLK0Rtt/+/yV19tNRtD3jb4bLn/+uydv8eEfgmA13nqcXkePfqfZY7GYrV3fqT5Ufd+Pu6wWi9Xst76s4FEExjDM7DZAs10OI49Um1fgp/MvN3G7wsi/NJqtQQq0K/6WD4rITEAIgh3//iM/rPwQCM8z9pC1CX6sITsdrXb89rf0+0TVdRUe0YTWLRPgBNr60LyHVl2Fb67qz20iTf1SP6IwiNle1FJzNCfKOfrr6uenqB104F3TIKDsXv9bzrBY5vo5fhGf8d2L99Dn+TFFA8Y7AlxBPgMKt197FGtZt0X8ILCg0PLdX95337X8X5R//2qZbGJv11wDir1dkGmWa0J7O3SB7+6F575Pfy48FCYHwjSIzqh4s+eSjc9lFsysdO999/3y2jJvKyt2e5A16QyTiSUSe8x4fLLkMzN+yiz+iI03hSW2ex4zkkkK+lOD6gSa1N7Hlq4XTLCQRtxExhv2oPpBDfZmi80C/5vt1d+z5xQv/ikXuvi/YH6t7Dlb+DHwMX6Af5H+S7W1ZVYZ48Pce+m7edANSAvru9EfgZ3cWb/88LXXVvjQPYwPjQPNAawnYaCypz7np0axpjqjF49DiktEIxFRSmei4Og8aXBbdrxOQ/RcknrHpTWZSLjjLjmB+x7NeCNUscmLMrPbgqiRk5m3UrWRjqJ0Ph7MTnc/uH28Z8Jasd+wfOLNS6amIzv2HKsM5+LDHc0P9A/dnx7vau7ODsUjE3Ory9Xb13Jjh8o9A65YV2B2APUnJn/g3u4gh0tWgQq0RWROaWW2Qc2AwcQSNnLuKO76Edj1pejGb0E2nddYELRYKZmTsqe9XRVUke8Zxaxi89Ut0bJXLjR4qA9UMHLD1FQe7ZjMBIOZyY7RcpNpubJ9sLhjcnRwO3fhfnvS6Xb3FyKFcv/5/jL87l99YXxmZvz8xPTMOC4vzssJ6/d6OuvoG9JZYVT1ClGY1nTWAU3MOnijrSLuHECddWCnWGm6kclZ51GQuHxfZmh6/Pz49FBm/zGiy3ykBnMrFYjb1ABzxuJUS2gdzHMKzDd8C9frTe0I87aE1FLnHgNKmEozt6qGRSA/FhpiBMoamBH0lWUN+JUdxcHt2wdHJ+mKAmZJcsSaHl7PrKrAP8+gPj4zrcuyGeIDqKPmCUMGCoaaJ+UFoDDA8vQhG47CkEcMCzIivTvOjGGqqy7m1SGMdslCTHVOlW88efLG6gM7Rru3OSvSjXtO3lihhsmxsUl6+NipU8eu6BqfDgXHp08de3tgbHJyTNn7RVXncaEkC0xDNAKgCVODVS2RaAq7i4BK5YrJEosbyGVzaIfzyvEwzTKxpHj/0vQnP1n64Q8r9GNLy9tmloaXl6qX04+xNd11cR+jF62khxx50m/iBKNGMCKq6r3GAIB8k20UpkgO4YrG9QaUFubrwFRrtFKyJNPpmOxJKJBSVzWH0k0nzbF1LGgrm23YTbsCfVOdxb0Ocfn0yTvuKG43GB17i507+oKHdhSLk5PF4g56GB07Q7nj6+tHr14qjbSs5obQxz82NTW2im+4n2CSlMUNARxNVA25Gpiv7SUBpBlVpAuSoOxtl1AdRvdm/a5HlUkVxLVtvjjSvr03UCkNDcHOftges0tSfyGU3dlVvYeujk/NlqqfVvbNKtNL1okZ9JIrZh+VALZxRkRNIDoV5zXlvABSCSGGVVEjxK3wPTHCiqxt0WSl5IU9T9xOi9/mh9szN70ZR6847mAOfFa3yAJerH79EWp+6KeaHTaxRM8tVbuWuHVmfA0F42y8yxf30edhvA7gInuU8UYB2xjq1w+XMV0A5BAPgw3rju/NvofNPZBNSm6mS/iZgCsrhoaNduPlM+PzL9NSfHRv9swPaibj/XOj9IGlqi2ze6SVv0qzFlNymRoXZkNtptHuoInrhc1tDwUU9JCIX1a5+jMnn3lu7TPXoO3h1MvV16o/euUVVX5Mgb7SRCYVL4YPhaiaL41Nk9E2DywKhaVhpi/t8krJpshYsDVVNQGEe7Y1QdBvo842+VOfmj/A/cnS+nPJ9qXlWfrvyt4HEsAFYE/KZN+TZhClDNqO9BmZUw0QmpDCvEkUeDYvDN7AC/gti00BrNa/XGFhEDKRZbfsTiQk9LMicjO89mc1Jq0ht/5hbEE0dLXNeI2GTFt2ZF84GG6diobCES61u6v3YFfv7LbqZfRTcrItWT2m/db3G9LSyc8x44o6dBeLh0AiUpg3aCzBx4Zd24rqFyufd8vehGIwVAfasAHlA0DnF4qd23ub2e6rcKnVbD48sLOjfufV6LoEdL3ORjG6QQsLB30e2Ea6FlZjnRh+kW4gSjULxYmbbvyPHaN11onz/YuDkdPH7zx+w4toPTyPb8r+PwA0Fseh8k0PcBHBDxpNjW8iU1Rgowsh89rGiipAapRk6log30y0tCc0WeUSMQUtPmEquxv45oHljXIK0DRaqckqqdUGMQX4Z6v9YY4/r0krl86LyQIS5TeZ12iN0c9r3COqcI16aaGhBc4r3S55GuUBRtgU25smDzTKYDWxCyd0qTxwec9cPgxi13rmYXurA0l2OD/XY9HEgQnA4VOwaHGYl4dUPkeR1qvzCTTE8wHfUJkd24IR7UuUowvzmrlaa7BSaoK/PMSDu5A5ZZSFAhyTc/ree9bUtFTcf3llojgyhVjdN3jNatQzsXPXePXTXAppwzzgtBHG1vU6NpTRehvKSL9nuF+1oUQoENp8bqCXstjSEcqsKD0U6G6dGQWp8Pyqcaw7lC8e2rO7vHY41DY5EBNWjYPtoexQumvPyu43HQvtv/6OcKI7NWG32RamZpY8yWw0EGtra40ZbU0L0/P7F3U5RolbwNgoZt1mNJTTsF04oggybqCSQJ0V76ISYlZHLeXi/ZUXX5Qj58/v3Hs/l3r44aXqX4dalxZKdEq1P198jVYBRA7dXsP4jybQw0cHcciuOCPEkifbr1hY2M2feMtbK9ecOH4Nl6r+6S230OPVPz121ZuO0uN4337GA1KKjUUddC1WSfL5lEAKxcThzaY0E0f/6rtNJo4zA0Uzmm479uwJuPnb/LLNYbU6bLKfvmv9pTr7QIp0M7+CblwpbDCu+P4I4wpoqgM52aNoqpFyH4cCjhWQ08gP7xvmUWG1cai49i8svG0ISHQT4Dg/cgZGeIery40Pcne56Furd7jhLy/81emmb11/ib5LSjpd8ONMStW3wdgzMPY2GHuMpEpy0GWhBs3AAjSe16WqGIm5wi4WRYZ2lgZDOrLgmmbtoJnnn7r6BsEIQi7yAqNww5ueeu7qtxlEkCgxdNoovJ0GqO0RsUlgP03iI9VfVn/812ITimpGo93417hmQJdwzVwkVGpxiVxtVApl2cIwRuVC//FbzVbgtyBf8RbTm49WXz0yD2D5mr/N4XY4XM42Py2uZ76L9gPmU0spMXRWUNTUZ2iiT82BI+WlPjfysgLNGWU16ot6WQgRaPxlumi1WYL2dfpQ9S+tTdaAnbqr08fpdUaM5rqiereRhXUx287FewAGY6QPI+A9IsjtXVHOYEQ/BoamGc+iRQTI0Bro7dRoIMaTxABi2wruMqAGBmE3rEYfycTa/bH2mKotgEreS1NjijPbGKU+FnnDO6jRp/q3x2m+h5OXr21t94mchWvO7Ozq7s70wUd7uCsS6Q7Z4WNfpru7a2emGT6KvrYYHS9bm1PBh9Jjnb5yZ+dDwb6kD1R0f6o/+KnOzrKvcyz9qWCq2cr2wMWL99CVLWMuJZlcrFSUiMt6GEyUxogAxFngzmIEHmjDKCcTDhAH5g3wX1E5ioHfDbjak0yEg4FmRxOw+j7ah7F5biB/ME/VkW/n2OQVrYSBBCOSYPIYtXdOmRsApS12LULCSOshQZsUSDRRBgl+DCf4IE6QgeFBFQwP1oHhQQADi4/8Leenz4OkfSXaeGYf7UHGiYQG5BPUrEBoWUGKcwy9gpRfCpbaYZENMM+zRrgVTwUeDTVqa0FrjRE1SysITrh1OCm7kjL6sZjJSLGfGzBmIauYzQ2oLU7sHIn2yMGWoNS7XPtILk6N7HSH42F3YWpqeFr9RJR8k330LVwnyzepiykq6FbGIbowkHUxC6sfdptXbswz+ZW35L2qLs+kn76jetsZ7qMg2QdD8bgqO72Zc3J/BaqZTIZK+bCFx6mGvC6rgXnwFIw/ixuQ42+F52rGQIwWWvT4PezpVM2iAFIjwV4HDV3JosC4Drdkp2gcoe6JhcutFeNAW9uAsWI9Pj9x4PLLD/Rks1zP7bebji605U3Vq035toWjpttvN19/8PzB66sJ+kHLZP58fgfR/Iyos/eSYmkIwx9BFwFiAsoICAUUEBTw9KTGpEGDEEXTKjGZcqYFqVnyx11pyc+CT5A05AYwIQYj4ljgiebqAH4NjFHJAzGKQD5mo82euGFtzuL32E4fP36aY/4Ou6/F+tcPUBr3Nkfp6erAcY/P3RsIusvueLIjePTKKyrZ4shgtDMR95ZNbkv3qMdvLzN4dwBr7gYKEoFP20slE3ALnkRa7GYgv80+yQG7i59DX8OM38nRXQa0/XL0Ci3aeZRfaI2mEtGO1o4eWVBi42EC/oJfxEGLfpGlLqTFdAH10YIW+YbiR8f4gd6Rsczy+LLdEd0/vt9fiIz27B/f1yo5/vsw4N7wzp0ctAlFoQV8N5aBNnZHGFr0llqHmn+xE1vAq8HuHkOfrxkEQdRFuCtFyoOWJPCGk1YLp9oAFSsKcKnWqJYcFLbBOgRphAcFiUeuigHvatB7Losv/pYPGs0iLC41iGbj+4889bFnnnmmAq+PfwmUyBdtAZ+D5x2+gI22V98zOzt75gy80bcwGNfoncikIoa19CSazTl+P+qYyEV4rjGIDCmhH2REpIZID6u3P1G9nb4d7jd/8RDw95fIDDmEa+ZFKoCYh1ZDFj8hkFs3c7Np0U87J4eHOtLJRGu4H+SvZtDP6wRFFpOJ0WhbiYw8C6/ASDpe2dc9nBu/8GE8LLrr7Nz8qnmsIzJUvGIuNdYN+NobkoPN6bXDkfTkQFxYNQ2mI9mhbM8MfuuW+yKpiK3Zbbvm8kj7zoJ8uEOe8yUDji55Bn9ZYu296V0ehynamYuE+1PNvMg57MP5mf0sFjTRlWrNmB1iaxf71isaJNEXbQ8sHPS1D9LFVEF0GOyt3UF5wOQQ7NFOXI/z3H3027ybxUb2Mkt7gkNRhpIV/E3oUZaxBlzE53HaRYFYqRVVReYbqItDFOtiJekHgrFYEF7/wd7hxbu1T0X1K4yp6ibXcp3cjzCii+wgH1N4QCvwgDwRjCajcJpg5B9oJBZQGAmGrxGjYDBiOJuZF81XAZ2n/Iqq2vJ0d7A0qHQ0nYWeRpN46xvuulJq8fu2T5TGR0fyA/19mZ54q6/T39mesGq+BtVHraqWoHtR4JoseUex7A/0cAwGG03ODD7dye2Hh4YOb09qv8nF7l25cDi3qxsD88MDMw9NDh0LTIhRL8LHGxUnAseGJumr+ZXxeHx8JX9v/sC4LI8fyNO9wf6pzns7p/qD+oevD4yvur2x4LlgzOteHR/AdX2EfpU+CLANkhR5+XNe1ReCSTBJ3cWhxTyqrg6TnsE1b1gIKty483UaGwzGFb2P0bAbuvRDly69i8EoGA3Cps9hGtq8ALpj/xtobhCUJ2Evo7B7ZWWlJIVD8dZQKpxiHpi42crSxjY4YFC/VBwwfhYOxbbtI4N9FTkSSPD9QhGu9vLWfrk7fyrdVQmEY1xzuifsD/pcqY5MxzOFUGuvWW4NNHv9LQDTCGkDumoiTiCYyVI84DQxj+Ym+Z1hd1jN7hRrsbZRKqflwoYr9MJVj5lsAgJTsJkeW/vmf3yj8QJn+rYZJH6e40HkN3/7lVca/mT0foq8wLnoJ4Dajc0+akFbGUCQo9cTVMRRIkCSSrjFYMmLDOvdG6+vfE5SHH7uGBD52BT99nL1PfQT3z0Hu6Z+jw6T7WSN0YjDA9RoonM2kQN9BYSzsxbMnjEQHjea2WQwrxETbDiT8aQVLRHcipUqiUAc3d3V5fdRsn1idKRruGt4qJDP9XYru00JlmnSqUvdbmNWKbe20WRt66Ulhehg1Eif7NXDcWlX/WbD3+o261a33cTp/Hg0vK00ks8HIpFAnvtRwy67F/fd+pcu3WoVengoE8/YPnT+/NPhQCCs+io/Tz+Lmcx/2M+JQYAPU0P19zw3y/jhvbBPvwrwRfqbZb3b0DiHd1tjedErRIfcH6TBWpwI5o92TS4tTcLL19fW0dfX0dbH/Whx27ZFfN3dMTDQ0TnA6MQEk9sW4fkOlBYAiU0gays5cMQAuuBJMxVF46oSCW2zEWJz2Bz2Jj0wz4KWylwtMU7wxnJKbhx1VQdosczy416bUm0V99Ev0I/C7MJktDQsiaDrovENhFmQFEFePMuEKQxYWtCtzPMYtQTqi8/psNtImIaNVi141qHokqjD5AqaNAV7nzYPTXZ1XGZpsgQd11w8M5UIhxNFbgfPjWajRf+ABfRzrr+0o60zLMvhgTE2rsfpR7l1kLTPlGxOEJMiARBimDnPyuLJmTB7mmDMEHerlgU1p9i60Bu5SFg8uSrzvm67lZKbkpZmv9duw9gKIlNZxCnBIuqhPpgtpSVKZVmiFBN8aWh4ZzEVjYDSELnMbLU024+/OCRKpszgDvr4UAaT3FoixZII0pShv9glmHmh0JUZUmD/Mvde+tUGn3pjzAHDJ6BQtFS59lruvfcotqV9zLYUxLx9F9DpZpTU5gRDo5VJtccqfqSg7JaZma8+ngfNsam0DJsaqJ70xOD4+HBl7UhfUXKbDVQQBGpdQRtUcWKiiFaoo29qdjh9zuaQ7WMs/m9CjRd1YTwRwSgZQ312jxqzhQiZYLZz5o1n8aeyhNx6Arfwb8uVMu5iLpUa725ez3DvWX8L7WruHgc59OJF8n2QaM/RxyUenkKonZgfI4Ct9kdznQr8Xri4jX6UBEDjyzP4dVqZgVSNxlhj0RgrjdEYbuKSPDFBDabwqwQKuYBPyWiXX5jyBfNZecgbD/iT2/JPF5x2yb06yptNrQG5gz33CRgF+qTcaG+xN2k5i3N1OYvzvBpLI4UlwdqiukJzDRmLT9wEvFoQrKaxqeWj9Av9n2/2A98WrGJ+arT6837FXv9F+jf0E9z/ZHnvYeZbUUmRksc5z/I44wZrQHf9KtSn0X/5xfGBgXF89ePOwxd3U2ZoKFPGN7xQxjeygad2lzpam61KLYUGpoo2TSSjkhSTYtEIJumhdcQvqslETEJP07o/evFbKl71jW+u1XHT6ol61gpfcqZGflr92QZmS5QxEi4MuNdMEuRZRUpFk7iTLYWbCiQc4EQTPxdULvF1l1aUpv28kvkLLBEE1LMwGysVjES4EuRUg2k/EW3UYhAt8KfJsGoGmmgAZp1nZRJQl/+jOq6UQi0thLQkWhJyHPS6SCgII2+WXIAaTboZ5FL0QCNgED+LmPsIBDVSjy93jVY/Nvqc49Ydd7y5WKTv24A8bhpaH6bvqz5VfuRvnCvfmSI63IYZ3PLkhRrcPDYrzEOCzdYT4MxiL+XNBoAdu8xtuKzBD9efvAsEQrNRNJ+1CvA3MA7DlShmiCaQ6808b95PzGZ+1WbheDPP4AdjeBe65LbueNWlHTX45VvyuWxbKiHHogx+CMCmOjPSpdBzYwhZPRvKbmBSH7wEoNXf07DOnvJ1TIv+zaYQ7q9ur7GtemaG+pSg4imPmYk0wmiUJAEyRoNO0Ig72zizhZ9TRMQJ0Iw5wXAaw2zQeGQSLaLJchY5lGAgwklU0E28uEYsZmoB+BAbNduuJBYLXbU2cdRCF4PK0mxTbsTMUP/VO5V2EcBwQOuzW9yMwHcAizdwL1ANWgnJggrX29Pd1dGeSgRamn2OJrNWUMZu9rMlzNIs5f8Qu0VltrYh5EP0cli1D27BgIeGd9IuZYM8yj0Hq/WfW7HjoeqL9fvkWuCx3+E6Wf7ampK/liTUCJTvNH7PE+4qFjKBDpGaeX0IwyRS2A5Affb1G66UnC3+1rAfaEIsEZM8JjTJM9tG1qtoRaq9290QMnFtXQobS2n7oR490VaXwbYcLixwWWa3DAYB+shLWXwu/1fOOEkSAhx1Ei7fS0yPcfTRexlD1dt8SW9zI7S5X2lzf2Obp7Q2dOcbaLOwZZvH9WddpbXhNrb5in6fOW3MnDrmOprmAGY7Xipi1o8Js36AnBhA3THTxtQiC2hIipHN7ZKc6IdTrWxWDEVAfcurmNdiOVl0ZyU5cjd935Mf+9iT1V/c2Za/bVDNNmJJaVfiGPdd3Iax9jDGNCFOjryVKPBmeU4Mlu06vHF+tA4GG9vcuGWbp7Q2OrwvbfO4fh8NlpRraEN/w57V/XrjaWizxXigzVNam63GA20e1++z2XhkJQ8MIPYd9jeLFWb37W/Amfr7shgKNr5swxy412mzGQ4rbZ7S2myKwxvbbIbDSpvH9WddgsPAI0+QP6E/5eygnhkfN2OdmXGaLqAJ2i+mxRN33pm96+7s3XcN3HnXl+68K3vn3QN33zlw111AGOWL/8DluP8HpFtc1cnShI0aSHvMAZqQoYVyAj/HlAUjhje/uyGPtOZmTiUiIbhBoM3dJmL8cSGl5w6iwKuW+0iLVHH5e/2KdiDvCmFQSedUKF6ZasHiDbsHU9QZHy9OJRLDxam2c/0TSoKH1zN6S2ZMSYBJ7+De24a5HfyRxK4x2otrijkHbC2GtsS5jW02wzmlzVNam01xTmnzuH6fzXCO0Qr2rNHNn6Xn7TwH8m8L2adEWoWIxjkNTI08aVTC19XAoU2/rsVdOUF08bsluKFTaovhOmBaqZbi461L8XniI5i+Eh0s92OKz/C27UMf4Z67A40kPTMDoUe456pDpcHBkqL7YH2oce4l2C2fUfh+O+b+g8p+ljnutbAwZtdTI3lMIqeEHCvT6n69DiYqig1RyMFSp94ehQN06G7dekWJ9Oon/f3NcVd7MqdEejElkJmR5DjWj9J4ulKUBE38LJ8GgYEIWUB2t6tzZ3+wsjIZn5pZ2T13xLQqdMvlHbG7D+8r9OzqznPptm29gUDvtrbdU6NNov3AtrnlmW3h6OJOhxi5bXqxu1DoZuENsP4sHp0PwfrvYLR6kmx+/caG60HtOsO7za4vNFx36/e5quF6h94e+Rhhe/w1wJEMKZDtZI48WLI4QX3tpEYRrRw+WKKsYos9TUTBKIjGszY7h/qGCMjWhFVqjqKqOTpvoUaj6aiZmkwjJlioAd2C+0d0WynJlMzumtqxrTQ60p9JJ+V4ayQY8HttFpNoNJACLTjQTZ3NKQVRmDCtBSwopt2GkGMtjEJNWPdrAV9AaQpe5SbGL820ykPGZXFqsL23yQ6iuMPWnZqc/+dkJJgSlsXFkVSPzQ5yptPe3z0+vUeUI61t09H4S+9JxlplumOmq224tLPF4fL5XI6W5dm711LdrYHRGe3K7ERxbNcdxXC8O7UmPzE7m2I1IWA9DgDPdnIZWI+dDTxboeeIB7sa8GPj9Rsbrge16zp+KNfdensND5C2xNh9ftFw/43Xb2y4HtSu6/dXrrv19uz+ej2Z/0lSsPHWSs5Ai98DUo6IaTfNlJtRtn0rEA+BYDKzQrauUvy/tdj3IMudBh1tY7uG8PhEqjOghMMVVJaiBsj3UjFXSGf9Ba+IVQ7ratKoithUZHA+E+mwOy12k9USchXvu37qmo57r9m1hvnjA3uLscrceGl+vjQ+92L/7uFWyWpzmh22VGD/dDl3YmKqnL3u/ptqlWmYEVVdV4z3ZPBaatiXtfXbs8W67tliXfdssq616wuXrPeeuvXgyXGyTF/lJjGWhETJMYVOI7tA9+dJEd4Fg3AFC1UwKjVGgiUZPhGMH1ira4eBMxhmrUbOrJS8HpfREGx2RT3RJotBMkqS06RWIGvUGBr/YtXJntZ1htobPT1Lw1rcdfU9egQ20qh/ZaFTzSRGOhQ5RCAdKIcocaONckhjuoUmhyTlUABV9pSrXZFDWLADYg2TQxSTVVosqBarXiaPwFW/vOCUUBSZyXWu7k7ceHIxlz4Q6y9OGXti3cUpQ+S6lrEkC3WNjt6Qm7vz+A1y4ZaW6fHzzd5JH/zyOdT9gvF4bJ1XGvfdhus3NlwPatdr+45dd+vttX3NYrzYfS6r3Qf20B64zqs5KmOlEQNArtnIclRUCaIeXErhGzUqjJC2VCwKHUOe9nZFZtg87QdThBssfX/5X0j7qf6Kv3l9vS71R5+TW5+TNldFJ3tA19tObSIXK/yO0/ngqYa+T+r63MyWfX06r5zR+l4c5CZBX2B9nTydJ8+o7QdBXsuo7fH6Q0TRy17j7EzWS28pV7L8JrZuOxvWX+n7lNZ3U3lT6RvU+uo4ovR9Vu87u2XfpN53dovnTr+B506TGu1T9JW2hr46bGEvF6HvXdyrDB97yM2bZE0liJGIxMjS7Orx00Qb0qfatmjG0qiwbY1RuCPhnu6uzqQc7oh0YEaVWS9AtWlGlV9HZRYHlRXl4uZZVf+kxFDvoCuVqaktMqveo8ZRwxv3qJKTqOqdjCIXS0Nm0cTzBtSnuJN1crAgaJmpLhchrqgrGgmBCOOGbpLUHrOwwN26SihKIQyvXg/FW1cQJUfNNys1Uej76I5aVRRamp19RamM8kuFXiAfmQSZPgH8+7GS1UbNJGTmTGZtdVI4UKpkmjL6YTargLeAEGc8imVcsYySwug7tmyNvkPWRTxKRHFExDzLusa1djyvOBobGoNs704luzuT/an+BLOdtCStmHCZa1xVWXNC1la3cXEb0uVOnKhMTl6yuC+cr1vX/tlCYXb9M8ra7q8tbZ0N5AHdBnJqk72j8GlOl8s0uqT0fVK3scxs2deny3ozDX2f1ftutt+Vvkm9r7bfV4F2mRhNa1dp1x0qHgyyXIYOksXI0iQ1os6gZQKKhPIiVpJAWeA4MZlqm08rRSO722NtmBKIIXyXpASy4pU1E2MOiwkat84SdHSlA82t4anDwe6o5Pda3b5NkwYH5FSgzdMSLjoCMZe/x2vla0mEm9ixNqNtG+VpjbaxWjysb+eW9JjlDrK+s5fIbE1sbTu3XFulr0/vO9PQ91m972Zrq/RN6n1n9b5OrB+k9HVy3JfV60r7QaW9ZOW+wh3gpr9CiRko9Re4aYVUKzI+yBo4566t5nzxfzN5F9vMKW0uXnWJPUOrHeEmflIqjdrMIi80WQ08sQjkJLHADSwciwDSfbYeDyEevweEd+a/1H7sgErUK6suXHVzw0uAd66zu/JKpRvfqk++wh0or/8a6wpxpvIZ+ClfifXAQEz0sHysl4AGH/1cE1WSO5lKglEOeqHfASUFHESi0Xk15SDY0ET/Fg0Qq2qTlZJNcmPCJIjCDOu1CIgtEidhCp7JyokTl6RPVia5TwGd6d80h5I7sJkNdDNb5UbdbaFGL6DvA7ptdDM6pfTldP3uVEPfv1D6Ak59sUEf9OnPmqnRF/TRa+3pPK3RnRS3Dtfnlevp+vs/q8/rEnxXayvFuBQxY27LxlTQDRW2ClriZ6zywhPUfn91SM3xVGtY+lltCInVsMQ7iQKHuih6qhQbRY5fsNspsUt2yemwWUQjKKJNtAnj5ZNK2qaau8vDetJSceLun546WTx2qDw+TD9fXv/N0SvL3Nn1DCsTBPRy9uJB2JPr7JlYQ/5GJZ80abJyWH4P2WeR7YICMDxWXQBDsjBthiWJydAAm1KjsLZVq5VS1O2y2wnpak/GA35X1B1RRg8PtbljNoxozWkxGCDDp+G3m+lmaiy6nGwIS//ucn60Es7P9j5d+aWusVVvq6luDy0Vc1iEpWs6G3qIS62/xL0LNTfU4Eb0OoawtliLS8HZHoY7n28jqt10H/0nxFO604K4MKXrOk6stwLtexvo12VwPceuLzZeZ7mvKbjD+9jfx3F9mb8pw9o9+WdKO5bHyuTy8pZy+Ro8w8+ekWl4RhtcTzGaWm64jjW5xriq0l4COf7t/wr0lNIvXPxXnZ7ic8+yNtpzn77Y0Eq9T91zyZlVpa+H5eCx5/4M+DQ5AyPW6JmCw6mS3GRD9EQqRbS0bsDpI2r5G6+LxfMVYLkLXpFVBhM9y5VnWHmwZZofokerP7i9fHv1tSUaHarL9/WS2VIT+qoEA88BBdRoppdsnv3rRkl/Y/Lvk/5YWi2yi4/XeH9qFG01ktxGnR//uJIAfPPNT3IvVX/6pvKb/p6lAX94qbxEa/6ZL2n+mc31KcaPQpr9RdenlmHdeAbX/oZ1OwDXKdPdlpTrvySb+oI21YM22HpUWUHhidw/KdclnvvyxQA/pKwzP8Q6Y96Oope3Yy2hZIgz6KV4lKMTtOQRpohjAfBYMqwl4rsbCpl5tBydXENFs+XTXzY5gO+YeINhH+7eqan86PcsTsFoYHXNuAvVxVpZsy+s78LdCxuZfsuX8TRr1c2IUoNjH8thjpDVz/FYrHxOMyOhz0fJJGP1EutHjSY8NaVRTdS9pImSjhMhkWQsndBqBHq1FOzavPRCAyCbLw4tH8PZjI/nRyeGhraxvNwT+6t/pg1/dmT7xFD1wzVZ7gHdN7aZnaAmj+9p4HPK9Sd139tmdoKaPL7nEpktXPP/4dk1l/iVlTa63Y+1QVmpTfWpIG/4weyjXgCzxQZbLkCJkZsLsj/MAWqCP1aUr+MohRsNwkkz5kaxdPfheZSmyFERczpGyIIallHQWoJ6pTUktT6k1mWeifR691J/Q0+idtRKKBqN4iph3hfsZVHyyEFFC1LS2Z6Iq0fNAP1w2E1GLMliBebpx0xTJZkH05gbvN5otG+shPfM9J4904lOW5NRoObHPvzhx6q/EoxNts4rMh0dGXxZThygVx44EZI8LeWlHy79Zqnc4pFCT2dHRrL0Snyv83V0sajlpdKiiVqwCqwBQ005rNh61kZBHDWAOGq2cnh4i5KsDTSPF44i/mIC8UhP98j2ke2lsVy2e7hnuB0Ln7THm/TKJ2/MI5Fz6wkBqI8KOVUnlRe2d/Ra7QaDwW7r6di+8M+JaCAhLFvmBjt6bOyytRcu39eaSrVGEwnK4y/6gZl9msth38y9x9EJMT5du0Lnu1O/TXWzt/vn8cO8KrPJDE/zW/qpWe43w9N9G+yTTqybp/Sto6en4Hqc0dN9DdeVZz2lPetS25D+rKDWV9e9mE+BjbOwJd1neeBsnPsbxqn0fUrru6nepvQNan11vW3q4jasz6f0rfPRzMN1I5vj/obrG5+1Gc/Y+Cxtjt1wz04Gz0Hlnh7lehGuN7HrB5Tr/Wp71KnYswY3fxbQEsw1n+Re1fQuIlqoiYgmRcExs0y2S/POG1UvloQuWdVCOhuz0PEAgLpE9B/0V6bo95bqk9HvuueeuvFq81NocfUuhSdW71JlHzU3Xpkrtrn4r9Vptc202majH35TvWeD7VzTe5S+D+j++U31HtaX0+3rpxr6Pqk/dzMdXunr058709D3m3rfOa0vt7Fvh953rq5vuBaboPOSDX1VXrJS4yWs76Aq8wwpOtastncHMX5bbz8/oVxn9Q0ZbEe23CtK/ALC9mDDXlGuY9/RLfsq9vygZs/f0Pdxre+mcRM12W9sSxrActAZHA43yn6s77NaX9ApLVv0TWp96axloxw4trXNiPUN6n3Znsazf9QayU2kBdNvGitmumMJJo4PaKnmXkl+qLl7LPXoo3JXhetLDHf4y12J9QvKvQjAzs/8OMOlgoCnjCmGUv0cEZD5CWPDQyKmRrQgw8XkbqawhmgIq4v7VYW1sSKWImit5Ufvv3/3LlYYCwWstQMgTN2+tG/brnJxean6diZfaXTOWU8XdRo/D9fr6KIuS3fD9TraprcvMvuUTtuU64psTB9ntSswi3qz8klYOYllUTdWTqqvlVRfIAnueQf3XvodPoh1K1gMrHOLyob+xvoLMjVPnVsomK0GJiAYrObBBT5495DFZhB43iDYzMPKmC+5P64PYUfZqeUV1OqEDeUeRPmOhcG6mxcWzk3xwWEzu7tgaDIP3Y25RPx++ln+gT8ul+iBWTauc9z76fd5H4yrjfUNq1HTeh79UcyM351wxZlypgxSEb2yquP83OxAR0d+NtAzlkiM9QR4XzyRiEf6E253op8940XuL+k1fBLwvO4ZmCxKlbM0CN3NEbORNHFNvJai1OATps26F5hP1pXeAgg+wR2jD/Id/z/kQuW3bcvDyxgLhGKt4UCM7yh0Y6ROd+G4Lxj0BZqDyhltg/QdQC+cJMWeHlTrMcBDV1TlmnLlWEpf4iQm8+naLU7OPmuNR8KB4sLgtqTFHOr2+lqKfe3hYQa7NcCb/wS8CZKjtXoHgc3qHWBtAyx30KaVOyB/sNqB2Y1VruIYIaHkq7I6wUziVAsdcIrMuTY8EO9KxH3e5gn2KeaHT9ynhsdaIi3hgMejfVD27RnuvYzfXA7grRLyFVoizu1kkntO+UxqdWIwNydYaraotURJG6vM3UEXwpLEm1o6aTpLN5YSJXNHqOvIzSZWdwRor9V05gqOf2E9Qyf87Q6XUnak+mXGx1geElD6mxRbyMXfge6EdU5ypX4sEIgVdE9rhLcdXWod+tGfHbxWK9Adi0uSyRRS6xZ79Bo78M/D+cvTIf/cwWcq9O93L1S/vbx3tUw7ql3070mdTwF4Fz3BaNgTpJFey+h1j/CcYFAKsYF2v2ZkpBoD1gI4pqCuE0eYsi8TWXInYu0JySyG0d7t1U5XatCJ5biDJTPlaOmMs3W+LTXW3aLp+QeL/V2Va7kLi6+6U+0YmFb9H5p+7HjbULb6De4darzKmmYjw/I+aCNTaTP6gR1A47fTW7kD2rpqMi5c55D2a/ZaFkPbXWe/RXgcqMFDtf29ytrhGTTcxd/BWk2yOoRJPJ+lh9XU4/BQEnFNO4cvoJphsVxdEANT2rdogVwhyD6yYMCgUogVZNf2mLvVJEY606iCuWOSHriA7lIeI/uTqv7VSQVXS4QKNFJMjHT4/R0jifO3CXR6wkGvKSa6uhLF2e0DnL36z772IfmMPNTu495VHT14NbDoM4kuhVblmLx9AShVDO3JzF1gEGFngqyNYwXmzB9RJ0NYKXho6kdnriRZxBC6c/Ff/RjRpqyP8F3/rVwb3H8r06wyMO5C9Z9pvG5k9PLqx7gL629ThkbZ4BAXW8hAqc8nsAJ6SESA56yJRq2Ud2BeHxq0bHHHvQnYFjAsiSnnhTr3hYp6dYhXYRiHtb51jPsabVNRTdP/xhmevakBz1hNavQn0DfX/Aksx/Cd6vkS80pySgvBahDXGwUFlNDtCJZECnLq0V78uzf5SrEw6edFiEEVxqxwhfTEK5WHKg/hwQjcreuZW25hNnotRoThapJo8bgIP4zHxfoUytlo+qkegXlNCgPgJWJtMclpxNXEkz1qUbYFvUI8/UT1qTv6dw+1tg7t7v9h5dzIruniOe7C0nejg3ho12B0addIcVqFmYeNI0UuOfcBR7aK0RABNL4G6859EJuBaMRgfTh/9S8qcGOY3wWcR8vFN7P65U4SwLxeJI/cHjyjVpmF2WgCKkW1mbgkUATdUsAVYHHD8OOzAAALSnUodKmwgIPciLKVRKM4ZbdZJJBMb5oqV28uv+93zxmo02xpmg93pk4v0r3rt/zj+fP/yGWrv997vdzWXEc/2Rzb2RwxX2kPO68jX8ramJw7x2O5EqNAjSdZLbDAvAU9iLjNiXImIytkCW9WMcpqarnZPtIP04DxCbfffHitp9x75eEz5y67vq/cd5qeoQeqz9Mh9vp09T10ufrf8QVjyAGN83AvKXWlZCWf04B1Jzk8x49iJZuzmOApKmENjOZE9YP8WtlBfn2krzfhSsQScSTihboDD3URxCvXzkCrP9wt/X7j4tjkYnn76uHRmV1jozMVjDLEVJtdxe2L8fGVwezyWIL/Yn50x/DIjqXthcGJyaHe2XwYi0hv3ycYDZfN984rf5L6GuhRzN706nXxA6zqO+ySMEioBhGrtiGD6dPqfzUc6gGceXkfQEAURaND/Jsb9qENGWuMOcx/fQNQIF+gN+ENtHh7XfTfgFX/LNwreZx+vyfjr4ZqehTu87VL/IbjbP17NR7O9r2D7FC4gg8tlAqtR5Q/qm/6QN0XcAm/ZT7ZoGFh5fOyBBqCgOiqUn5ZpfceV1eaGumtlW8JXHm/f3HxCP3ueoa7tXr2jrfjeNSzUWA8fTUfOPu7q2bzZfwvS0jdOTjsXAQt9zgwzyruKZtSLb4utrAzy9npN/RBPPwGH7t+ToVBij1joBaDxZ6RbrCdcKg/1dXx31jPTQGEUs9N3LSeW9vx95itIGzgWWkW0y3Hqv95ZAYW7yFfm9PldLgdbX56cD3zN6Bp9F/8GavRG2Sndx9VDMgxQH88DVfk18zMwMwpT1VOxkXIB5XDgNi5udBEJMa1Wp/6E3Tt7W1eCXhfiyR5LGJUCcfKZb16ZCFQFgwMScJWkWDHxEB2l2DP9Efy8717Ab0jkcJc796eedha908ODk5Wn8B3+r8yi4XoxES0sJj5CbaamMAOPxmbrZqni8Vp+qvZsRrfYTAtqvxGoY3s7CFMCtyrshMmih9RZrcpL2F0UHri5cpsufomWNWz3Nn1sz84z5gJq4HF8COEZ02ZKeYG84TTjrlfYznz2rrBxRAJdUhe2HF6pjxsQEmp8ZkbKGi2Y+mhqaKccUiWSoVSu0fKpVZB2qv+qjBhEUQTvXw980Orz2wxm41Gy9WHqFnhqUodXZxjshQ3gob5xuYH/2L8PW+ZvmlPNcXBBN/MnWcv1aeN9FKBnYOdMmOkxr0qncZT6al4BCXdoKjWnNSOmxfD+olQGhRzS7dM3bKUKsMz3s7drrz+4bwiW1mIUtcYeTCe2kXZqbmY/cnXmDAzFOAGQLxqjkttMSebhyJUaQfaYNQcI8GWr9fY8LVfZ3wYgPgDjQ0DUfhB9dfIiwEfNNih3z6IvIkoGShrPGa0MDmeHjEZlF2Pzvdgi8elu92b0jGz2Mzi9XyabEdh1jXp857e1SsC/bt630n/ZG/1TG9+cDDfi4O5thLu3jUQZtQCXuVt+VB+24azfTpK6boaz4ocx7y+AAqnw+dx+J1+AIWR0YQ6UBRUliRVKvf27R5ubR3e3ffKT4vT08W7UaaMDpbZGaJLu4rFXdXXlLWeAdrEMf/URGkMFtYoGEU8LlY5/hOLa3F4OJla+zyqFzNu1U5jAt0m7koyrujWPH21/BvlgDK1YM+M9YbDJ2+88aR6ktzojl//eqpY2GHk/IdOnjpW/cqxU8o5cmfK1+JBcgxPPGr8Yhio1kmFarVqVUtM9TQLZHGmWLTyysn0ShMGP2zH9LGoqp5hW8KarpSkaCSViHRGOyVve1JymkWlenZu4/nVMBFJpvrBa+jI8uy7KpCZ7OiYzAS031fto2e6c7lueAHI/+4WrbBM9cPap/uqj3HnR7PVD2dHR9GVNarrGiqfbEZZjrJzi0S64eAi0I9URiQ5QZpzOZulZmUTtpmQIbmxiJlWFYNHls8Sf3P3TsxGh8p9L1fefuct588PTm6nBc/aDB7zBLydHWFEDepRTxcvamdsAC3dQbQYUoxvjQDPBO2X8IAkWHnNQIw81ttSS/YHlKhdLFbZinWnO9rkmN8rOZusFhOJ0IhJZGcAsTB9xUQapg3HH28sRX3yxuTk5aP33rv7sMXkkob7Vq6IDC19bqo4ugMLU1tOX/Hytqt2tVXWKt0hnzt63eWzGNjJilPPqsWplTMdXcz2iNkGWFMT6Awo7YDaoMzxKm4jbEOKC1RU5BGnEzW6YIuz2YmZdg5i97Atn9WqEXmVRLO0fp6jrg/8at/h0cHx7Zcf/OQnMwMDmU9+vCPT2/lxLjW/I1vkjXRidHbplU92pNIdC22JRLuuB6Q26AGhP6AHpJaqXVgzWdMDUpvrAaE/Rg8Yp416gLiJHnDXr58zcHaTxTbvj4eWh16rplENoO+5v7QvGHXVZCrEn111OkDq9XSA0B/UAfzMXaUXtc/JonDz6YOXjZfHLzt4+ubVI6Vy6QjWzv0Z/FR/Vf231+CHnU/zG5D/ukEaXSotYn04EKfPEhOQNxOQN+3gc+WQY9xmLCI5ppTyMWuY3JaWW0PBFh/IVE1mrObTS3ssCjan9VqCNV0AMw1FeYwfoQ2oQpsOXGtdFYu9Q32FXPY97+7JZLqL7UPGVSGWm2wrrlxeGt5ZuvqgODMeiPYk23oWuhLJtkQgOpDy7t7ZP2YwCNvGZpZ1GTWFkRaloM+jy4shTQEIba0A1Mn/FYxYEU0mk2T+8xv2wYKIokl0Wf/sBi5V/Uogm272eKV2F92//hLdNpL2uSW31O6vfobU5Gj0jdH31Hxj+nkFKWJD2UHAuo+abHSSCWCheVXgh6s2YmOrzHh63dpKn7/6Mye/9vxVn74GsPzll3E1X3mFS9XpEynivkSfCG2lT4Q20SeSkuTV9IkxTlUoRDuPCoU/6jGrKgX1to13+RelVKmH9gMcvvejbZeNBJV6bApd6UZu3Zb0e0WBzVU5LvmsshzqWMKokAlMcPljjlmcOnVIMPMGg9EgGEzCfuWYRaNJFB3G0t4SaG0mk6Acs5j6J2vY5rDbHbaw9Z9gmIvONrfP5/e52xz33+9sd8FHn6tdwtMmSR3+XHLWZ0g5mjPIb5QcWRwPK00ve9WzPlP//u/rL/3sZ437fU/D/Rt0qNAb0aFSCONqF4vj2qfec2+dDoV/76zWzq9LbTy/LlQTvWvPaDi/jr64uFhtpy9q/ktmb76M8Qrl7wuqjjRQ6mNMDWujKVJGiEkSqv0S5dOIYaG9ze31/mG1R1D5Gq+pPXhAdDmzUAjrH2h8dMdU8bvI2uhhzM5++OHQwEzPB3tmc/gxN9tz+9TYB0d3VH8yNkU/umNU2YdqjXKYw6E6vSe1Ue8J/bF6T6raQb9f7QC9J1Wn96RAocmWMnbgMM2UYH1iTgD84QgLXg3NK7enBGOD2NF/IRoyAqFMItvPejdXgGjz6rWVDfrPfVcfeuYZVfsJr790i6791PSeVL3e80bmx/Seu84snFlAvSdVbcWzyOi/rL/Epsh4xZsZ7dqg94T+WL2nWH5L+ZpysgzPSNAfKK/vq3oP0ekj44aEioreY9ROXgnVKz5NTYQ0eZs8TjsSy0RMQilPQkaoutskvyZ/vPy+y06eOPzjynv7CoU++v3yNw8t7TlIv//tL2S6ujKE4bcGNwvTMpS9jyx4jWfIjXUeNEMrHoyZQG0rzI7cUJ+GFCup8bixvZnDV5y47HZQcPZWv53JFovZDJf65sE9S4dwH08XensGiW5vTbHnujfoNqEG3cZmdTmtbps7oek26oPlWFI7LLtSeeTwFVccrv6KGgYKhYGH4YGVvXsOLvV2d/d+AddwWj0fLkqmSzt0fv/6fN6k8flAs8/rluxNNiur2RelUXONy29QZrTqox4jTRw87ahYdmRHdzz77FQxu8NScZw+CIqOpbLc3T89Lpdbxqf7u5crp47de+wUwGMMxhiCMeLZslvrNKHX0WlCNZ0mtplO4/dFQj7ZD7pZUtZ0GsyQr6vmBnPxoj5TF7M2Vj6wNL1rz55d00sHynSbnE7LiTYgLj+dXTx4cPFlfNvzf+hPBjIvo1wLb4R5jRhdaCI+3I0bT1oNsdh3IPos8CiJdvSCWmgZmYkou9GzmgOAPj++86rDv6ycfedVJ4ay992XHfqeY3li96H1l154gb61s++LfaQuRtMNcse76/KiUS7Yx/JAOsn1n2uiJlGLaU4zw5WS5GEgoskgrimqiskU1E/6i2A6yOYtWQXzhpYrJXtnhzsZS7O8EEb0L80L8fk3iXfdmB+ydITXHHxqlsj1a2t6lsjJfdV/0Jx8nvGpHcXq33GpOh9fCGBwuxIX8w9Ez+OUgEdupx9v8PGpZ6o4WUFY1UaPuRoc5mpcrM/9v4rd7xeqLVc9JwuuLNT5+FLMx/fp2UfTaEEkHPDHehceyBAmrt7LpxcZvqShqeYNRAA3OgYvaY0tFMegqd4xuFIKhkKUhJKhRDQcaPaCamPCEJkgDaKIXmDmYu+ljsJc0qspm00tEWqg0TpHIU9ndEdheWyAHv+6r31Q7oijM+4fq/9x8Orlr32zK9HBHIWanzCl+QmZaiMAXV/Tt7HhiNnIqaxJ9xOim5D5CVXpqnGQUkG3JeXKDY7Cf1PdhKmvf/3r6CNsYz5Cd/Xns12JNsV3WZ8XdVlDXlSkLulJq5eN+wFTnoK4DTZvoOREBTfkRG2O+284J6oe21lO1NdoN8uJ+kcAppb/wfD8fQ14rsSdoe7xwQbdQ/Expjb3MYa29jGG/ngfY6raRb8Hcsktmuyg7MkhkDp3KtXd/KxU6Cl2mA7mHWlGYXYqGM+OAGn8QjlOrSa1YpE6lI3pV5eZ22/tKcXvp9kL3r/1s0JbPSv0xp6VWntKsS283vm+dX5K5Xxf8f+L830jsLbDTL/b+DxmD4n8l56X2up5fN057xGSxhM4iACIIfBYIkNJdIjW6vqCkNAapSQhR9Ot6UhYO/pdMWxtfvT7KI2xwHBMg938+Pfq78+BxG94nSPgb6Azu8auvXZslz7eDIAgBFR4s/HGGsYbCVMSj4WTkWQo2OyD8bqpWxtv3TFwdcMtXDrcEzeh4Q01k4bBaifC6WY3+ZXp8UhkfFqPPf2SFnu6aZ0uJU40pMWe6jU0NvbdLDZ0Y98bG/o+rvfdumaXW++r1aE4TuP0VXoarkgMF6donHPR0wD15q1i9DAoYIp+uzpAT5dZHwf02fWH+zi44fXntD4/pBfo47wIffx40qIWx8jwk5d4sx/wPMv/8Fy5DA0L1W+wWFT6HfoW3qT04ahW5LmuD+VlN30LdOJN1W/QAszvoYtH6FvIh2B+TsWuSy8AfRXZOR3K6V/waKZL37ThZA5ejsHuoj8ul89Vf463FPGWOBK8j0y/AzKZid0nzILd4D54yjnOgcWr4pgEHBPeBe5G8+VzeKdzZZhGoeFeF49wJvIhdq+8wrLsdTccokDUbPVPYOS64SHCJQ+5v+4ZuM4X76Gv8mOw/kdYbp9E2hTsagMMYTLmPTCf2vcx5XuOfQ9PfoS8TP+c/gvsQW2NsSK0GhnZlmBrrIcCKN6DR9zxnkCgJ+7WftPL6/+C32otjRB5mNVlbsca2AYjx+ONmf45Qhccdizh4vPYg45gMo5HmSQbCjSn66T7gbzYIO/TLwT9/iC+ziu/gv+oXbgBa5hO4dst6m94u0X7EEHPGNDl33F7uL8HWPRiBQGyCFLkcmkpQy3WPipYspQI/SCxtZi4/7e5b4GPs6oSv/d+z3m/M5lMJsnMZJI0zzbJJE2bJtN3afqYQkua8CihgTQFii0KrPIoKqC7qJUFdClCVfyLLIus1hcCKqwSEZQFpL7+KK4r6Cqw+IIm8+V/zr3fN68kbUF3f/+0M/PNN/e7j3PPPfecc89D1ZRNIOopxK5MUmSpMcPDJHE4uPat25RczLgf55y1/fTNQ6lUU6qhKdWUwLQ0vZbTTeFowAwsbp2WSiUDU0JKOlxyclD6jVp5ucXXp5cticXRRNdhT1QvWbZsSXVCfIvHliz7177BwT58sWjuV/8Vi1TFYlWR2N34hl+M+yPJQKVLjiSTkcfSfUGn2+93O4N96dHiL/TRtqVr1y7FN7n3SO+va2I1sVF41Yg3/Er3xBY5gxFXW2zRIpFf4C2v57/puqPkH0kT/Q6P0V2bqfbqzEwWX0K+YmbGsrJ8F/TssvQW5ekssP6bof5v8/pBSvRo5fXzEaMJo5d4YzjaQVoW6PvmsrDeTeVRvClqwOgM+w6MuklQDky/xn1Z00g2HKapL34bOWqlX6NJ+g0M04zRfAGm99Iv8jwPuAZFTlWRSXXLZis8+la6hZJI2O/VVXQcV0SiguJ1V3aMNbh69SC8tGSyoS4JfxLbMLgSt0t4m0q1dMRXplpaUkhX6FfpYZYD3rM50+hA1W4+2S9GO+AJBrgItBlz/fr8XsURxemwQhenaZsZmTjHIw9ngZbdO/t9ej+N+ST6fUKMKMa7Z181onkf5qOzz9DHaRh2ynfD71UiHr5Rlf/98Oyz9HZ8nlxmPU+Lnz8M9d9OK+H3d877++fh+cP4O/3B3N9hcBGgeZXAf3WQ4cx24nDanA7bQct7z+1iNvR9thFgb6hKRe47p9M+Rux27rJoZQGFujpIR2tzU2MqWRPz+UIJPyocPCLAhBBQCn57/DRX5LlKlyWRpyN33XXX2hW9a9VRx2XnIgO0Hv6QCTJPrYH9eb5/vL8NT6jPnkA2qA2/ItsmTq+FjZYVIyOMWj7TWdJlt+m4LCWvGf1eZBGQLBSFB0EujER4NmVuJxzS+CuZ1tK9/JVMs5ZnH4C/nfj2LHt661cXDy0eh9cPvmJePL2VmDGMUZddBRLgqsyg08YwfzNg8oRDZzIIc9z7024v+Hym1S3VUUISddFkdRJDvKL40wQyqosH4sG4CEUxRq1kYXnN3L5s9uc81mhfdsn1v/71aJZ29K1e3Xf9izuydCOqOTAv7Y7sRz+a/cyavr41x0VeEvoqzH0coNWRaW2L19VWhJyEe6QT4XI+WbDMH0OBdlvrouakrFa1BAp7gIfmFcvhfHhIrRF2D8wDy2f1szvWrdtx1pauJui0y+1c4w0HB5o379xQc6y7D1itZCxWT9e9uW7HjnU9m99TG9yXWhRwunzN1RXB2h1DQ9XbtgW3dvfGm5rix5Aecx7T9OldTpZnlsL+aJNVG5f3ZR2z2OtEo7omzP2HufjFzf239vXWp+p5tlOvXa0tmHSZvt/JEpfvJC3Z6EoTAUd2Xa64FFUDoqis23WZ6lI01YlpIdfRPT2LFvXgqz/kdofwBRLXWv9gTX1Ty8DaKnp/bjF9KJyJJevr62vXxozT6a0/w6QN+NYQ9vnCr6GfqeDHPPQA7NM/gZGm0XoSpAyVbkpRdLoC7kSV2UEeYeP9dhsFVkVSiTQhLtVdOoYt39re3p5uTwO1isGwY14HjJrOOQZLL3QMFqNJSQSIScPelZDxbBvjGspSfEsvUkgbUAqF1XTXMcz7qmOc/N7keP84+8mF9rDDCX+OsP1C4+PTtjAIgG6no9K271J+DcJg2DZN93QaT9CeTsvHoZGP9dtDD9gwdBnm0oMpPYCH2LJQ6MCGMCxycugacGdSNip0DZicHSBRKMsPAoe5ZC4yNoryGFO/+ZTrzqR4MdjKD56o3IgZHLeNtIVSPl9zi8+mxlpgWxB57YrkUtTS4OJNF+klAa9CnR+ti1TUW7Jpfaiq7uZnmzpRPu1syipq0DfYQ9152fT1nkFfQFWz+07rowEUUY3f9522j/uoct32TwjG+NiW2dJez1QlUOKSjeNQYRyIHpjdFQP+E56njduOSGRbMklJsiPZ0dxUE4ON1gdSa4ImbGqJ1IqqsrgPcYSf4Ha5pQAfLOrmYYi01crzhGmejPc70aiupre93t7pbFu+4QL+vSHtCzrtDWvOW/azZeetaUitOW/5pUbO73YFvbGmCFerffpZv8cZDDo9/meJsNVMsmVsP4nh+SBsSiQW1IE7D+EJwybBYPColUi2hk1fN8q2VsWXeZFs5ZknKz1uAjDd4qQk6tn7KQ3PdQFrKx10o/HlSMjp1DTVp39q8mW2/yndp2qajSfMlSi1wUh9jicpnqigPd0WgHsNqcvEZEHq+R6z0XIQo5dXVFRJKh6HppRQ6VFzsWGwHTii+IOeoKYpdp4zHd/siqaFPPTdk3/58+SdroCmq06XI+YxGDU8MYfLqepa0FUKn2WZXty7Kz0azGyYMskM64m2yzcULYt8Hp0YifojSxTewziXAGBCG4D5C0hWDhhO3pvoRkfEjnBx+QEsL+VhZo84aCKLmkaAjCv0pPFj48dP2bwWyPj82QE/t/AzXoBTVYRigG26KUrJabxzuKourwhVIJx602EzAm2DSG+ECk6A0yANKfYHa5wIHyqNYnt2RXfUfs34v7+bvDPiRtiE66hhMBM0ruidk38hdPYBgM09AJtezIsVRB4Y8DEa0FWZdiSZJEubxFyheS2iEFC74YKNbdVGf2TUq6p4llOCRCcAVSl6fawEu/YsCETj4wui3YLALUFHDucz2Rbp3TDW1syi3nR3tKoyDLsiwDpC6cYegLcQcvDo8fL2tkS8KiSrlS29RSAv3hPmoKw1G7iBhJRUYT4K2Po1ClSjCJHNiVLhF+MF44Uv3VnlElPFLi7g8J1lGJ67pfQ3wdsNAQ5dTKox1gvSgBBPbwbcKjsNV/sN5YJTdTTulVSu+UnijGg4SKpRPiDN3Ofa6F2PJcIuO9XtniAdA5nE7tN1Dc1RWZZdvIUb6GjOoHHHIL3QHtLtNuZ0O2p9jwu8JpuA37wYFhFILRXcJyBGsTsEuoN+m8OmewslW2GD4PgtRCwQqzHcVdzahbmYrVE7HbMFdGC/7a5Q/WP0riw6Z3ILIbvxib+n4zbECRi8tMUYe9xX63Q7mc2uh+zG7WZ/hnh/anD+dZ5TDEgimiltJGbKHgq7ozAdZHTrEl8Np029QqPba0XxjKeFMoDfaLSvoLvtfs2CijH2mA9mB0Bm0wM21jZo3G4P2vKAMca24JxDNzWvLT9ve6FPLZkmjfswetwMO2S5gBZR7JpYdQQmLYwnpxzZuD6il4YpX3thM91PvC2LwXcAMLrPTnf3G3fZ/LodoeaI+R4zxuhvETgeBI4taKcXrjDusHmxR4iRWwSPZdlXotZnSabdjJRtmikSosCGOVme8TuVaqwIJjg1yAcpsWRlRKf5MnZld5/VvtTrt3lkl6Ml9eGL3tvV/a6JCy8fXbsCLU9WrKWRA5NRXzASioT7Ux95b9c/7LgMYzGiueW2VZs3r7ZiUFRzn6UA6c10Ozmm4ckJRnaDeZV5LshhfgcTZcLUqipwgwE1gGaMfgUg2pXGZNmNJX6i/UevuX/0oIIMnAwvXb1uiF2fe8/QEH0ZWHaPy+lxJ0NGZakPB30HEXkLzuR98pLOTAeIWaqiUN4rVJ5JZKJ8aoWawdcdx70GprAxBPt3qBd247SNplm18e8bqN94eQNNG1E6YDzKns5KzPjOhPFtJmVPp3vpxafP1RfDd1P/AP16in9/nrxG30vvErnmyHGig+z7wHGhQ0T57DTYrwNIQ4BuCRc+hkofdP6dKJIc0PkX1iyIDIoatUSGUFe3eeaRlJI1n9+tKkCAKVPVI9ufm7z2UtZgvCcMoo7D4XEmw6wv18APPCSQBo+wNH0TZJd2sgJjb1fZGSAa7DxAvxlnMNHqmIjg4GRM0tE7edviDgBzumPF4hVo1YuJskXkHzswZb1C7cHDtJbp7Pjk9hZw0M20RsvkAJi2xob/079+aKBrnU/SvO72huIvhytaMSROdarCRv8Jr2L1cFVbcWTjiqEVG7sWV1f4a63LUKD2Hd2LEkOJRZU1MXUkf7W9u+hMCWXxRKaWiIzqE0UcCE+l3hhvrEMOTYQgKIriwOXdmn09K2I9m9r38c+hjn0YTeFPrRu6oqcv7zGceE4v9B0rySQLsxZvgpxPxmHmz4e7lxIfzvw4zDyPDXepGTOMx5lg/wQ4APwAAaLCE5ndAIwxkDCFqMqELDLkyUVpZ8XxVz6EJpenNGDotWQviOj81QXf6dUbfzi9dXTr7g9ufPaN00dPv5DefiR7773ZI9nPfz4LELB8yHwkhJZiPq/HBsyHxolhqVLO7/eH/KFgQIKpDqRCSkH5F1ZA2EjHQzvo/cYTzqBmA15ACzppD0iU99M3j41+TNWZRNGiWldvHzlmXJTFIefb9pIgarm9Dp77r7Rtvm+i3oRhs6jjTVkKwYbGFAa7w1ZPpz3OIG6Sui8MYhzcYE8fGy80LEnO23M/zgLvNU172Dn09gXPZWB3lqBShZ2fu5PePo6/wTMyPPPkqT3z5BTHtePG6/Rc6QmMz48HEybnLQqjBe7x8XHpiekmXnYKyh46QdmpqSmzrERS0P8u3n+NOBCP0TSN4waejdJdSj59os/HmUQMBdJovuiXrrlmiv+HoeEfMeuUoc4nzTph5ymvU5NUxMCtQAiIw27T8UxIVbhWWQc6zk+N4ZUSTRgP4v9rqDzF/0Qbx2ejAI8jZhvcz4aPUwZBQFbk6zWqSIoI9c+Da+i6LOsOHVozD6B0lXu5JAMgpSAbfpwPQDpi3G/UGJ+n24YwKqxoawraOpRva2kmzVvAbed63qqK7CG0aLW1wLDQPBFkoggVUzA1dR+9wDg8ZDxI10mDxoPYlkwiMB+D+fnw4Aq2a7IuKRyA5iXAULUpPNnlVqcThyYS65YM0MEHGEqmuJoPoAmfkfFxemj8tnHaOm7OmJg2bFeGdp/8X2j3yan8H7R7fLYL5vET+XY3ZtBZBER4OkFUO9UUVZuwUctdCIQ9ztxJu3SE9sk7gTaNvlByBesKmRP8iWMzlxw7xvRjx0aPHRMwn4I+HPpf6YMY9tw+8PXN6YjCc9QCMSsW9VH44OyFcNiAZYyGPPEQPXec7qY9uYfYPeP5Op58O3XIuVfYT6YE7Xgb/Tg0RW8t9GPqbfWD12H1Y3Ya6R3vR00muoC04eWcPVoXJ4/P0t3jtGc8tzP/7JNv7Vl5KtdgPnvo1NudeoreOlXc7qFTb1c8K9olx3EfAZptwqxUcJgLMzQdigNKk1l47pbc0+Mch3BfeVt1yOw7Bpvie8db7wei9VP5bvA63nI/RB1WN8S+yvc7UYc4tsxnnoc6SGkdEsZu9ilkFpGALc5dbO6zZh18PijPEzTffNA4Pk7PNT6BFchTGK3G7MOhU3wehkAPGRc9NcXbJ9Y+/zaeN5u31jM8rxXgOJ8OEHdmfnSB05lEVgAYRuAGYDpyD5XWg1bS3BiDzldRwXRLjZjYkfTx2ozX5R/BzOResfgLXp8NYwnP2y/uKrZVBNdWhWOt0LbDLM+Od5p9Y2tIWX3Cdhy3cEm+oVghLXjpkgrFYKc6oX8Atdth0q3xThWPl6sBr59XfVIyXmp2kR566qkps4Pyj/5W4+XAY/7y8YKMVqgPfbyxRu7rXZgSG7FhxSrOsDVqPisw6idRG4g1/2TOuN/KPGNnoadPFU+zRUfz+DefjtWaFY5/Ei5B6ByuwvFZnJHpJpji4nqElf4JdLWFfokVneRrWqxJqM2f79chDj/0p4ANXEyvAsuNysMYF5kLNTKdB3zizGcK0Ib3MHcxouE4Kas3j4eoMMW0z7LAQ1SZErkw0XzI+UF3AgnjeJjbmR/3obc+blGj8tSUoCUciH/TcQPW5JYhOk6V15vHR+wn4iEqCzg+muqCuRXzqtPH+eA5OuYaRMV/i/EXTbuoj/MmGl+HZSvazPNejkN8tYAkxTeGAs0x63rSHDPXi2MKs2EU47k1AE42gBRmu3TMWKdZK92NhBY62YkV+8vq5VYefPURXH0E3ZnkiRKybSGR2U1EIrp7FjGzExf1mqL6NAsnb+DqJ2lYpmZYVomUETLzXJOdgzU9OWVN9F8HP76x/a3g12XC79an+H6XB9/bhF9XHn7AUT2Fi9AE318JP6yqBH4mb6TxvllGjUhbuXHjwrtoPI30GhUAfJcat3QA+TVn8jcIP0YwQsIJ4Sd2gRCSHEBAvkig1s6CbqF0LcMditSB4DHpBDnh3hJPJ3lfjU9w8gibQed4Sb1vf+xir5oqgaNFZ0u2+gWJQldIwSnhDN7TFs3OjxejfSBNVHjmV46IwCdOIFlE4iXPS7wAjPE03/ou4mQRwDj118ORByfHaoGhKwZjCRzf1thxrgWLbOE21OUQkU7m5ZvQ8VbUmPc8wCVjIZG5wXI6ZsK1bI3z+rnPKhGqFVjoTBGIqghEVXapIB0rp9SIiayl9DLfRhFvpQCw1bzQXQzs4jYKHJHA29lxC2+RAIz/j8DJnIM8P/dX1W/6dooN76nyWfjb9t0UThaY48WZtgL80X8FFzf3Y7H423lgb7K5fAlZ3M+4YKkKdbuERyr2XZGVG+YhdBiEU1A6F3EJBbSuVhfAkwfQU515FOo0J6C4HRwDdFXAyDKUIaqEGaBRY6NwjY1owKupMdg7igaRFFPQ+VQBhfydJfX3ZrrNcagKnwMVBiIRWZWEo7u6S+PGSXMHwhlFa+NDXvEpU9QzmLn9zduOOdcwgmGsWZFUZaJIICpvJ5DuKloQpmyE+6GFsSXz8tfP+azgJk1YTf31dRev5acsjnKqlFc1609nOlEBIHh0oBRUVvLcB6ZgQF5dkU8BaZ/MU6Q5bbxtmjR3XcwL/7+WXuRxyKRGjKyDvdVv6o+By2MyD648jJ+U7EKvJ2R3CkbcqG5HB32ok/mPGM8dOcIVw6hmIUOwV7lMnbBpB05F/6xDI0v0w0MBrq2/4rbbjOduu40reXkdx2e3gex3Ga8D/XhBALgeJQkQTQu26KXdMVXk9FzoB600+ujj7CxjOdQ1NXsByBO73k5dh6ZQmjDG6F1Q1270OSXPAF9o8LowogE/0aUTKDAzwRzi5Epsa8FjBfdiPJaBzZ0fzfDjGYPrzxm5DWSdR/mZi4AVwt20QpnbJ18S+/To+JQ4tJF/hIcxlv7WMHlfTsIIJkenxUpnYlEwoWlARlBofUBy4NsT9siow/puM/l9Gz/XoWUMf34GCzUJKfE2+vhhBP3tnJCjSkWMj/dNAXzsy/Rg3B96A+Z/JgpDK2qMQz6MvpzYQ0USh/MOFY9BNKGm01UrzpDCW2LnGJ8Yx6agx7mdknMKep3f8+i5HA4m73VDgesqM+8o5b1EnGPkvQB5EBrUmPkj/YXAZ1HvoxweMOPcJOrksjPNJxGE+RofP0xvR+HeUhows04BF6B4UJuCHuQwYQQtEAEew2hOjhySpABUECYYm8arCfUBqr7THCj08XGhf4Q+s3umAPj0F6YeEHGV86IW720xt0D7ZN4AEjtJLtFACcyIc64IGXrpCaMbIQF4YfLfHA6KwNS5yrb5RH305jwOUDD6UEPGN3t2i8APs4+KOCGTVLS3uB4DCwC5VCbKVW/zo0eAJxsT9BPw2eibGj8MDV08zt7A1qD/Yh7JM1yfVCw7wQxKKB7y5Uvyq3de2QkwBLdghDYgOX0ypyOCCH3vo7zeZKaOL4/rT6T7yQf/UHC5AJ3i3A9XPnP6wusy14uE0aqu16y9QxXWLCrNI/IC64V3V8Q1AZQ2+g6PC4W/Pj4F/Tb17cfz+0leVuF81gmQxGca/PkK9EPgCSqOOZ4gCbHWPZdX+I6rwWpH2Q+NoucqFnWdEHEEZsovulCs8h2L5z08d/zwYVjor8s/MhWsSP3wjNVqRwLoe9B+RAGuh1wv6eitcgPmcJb5KZwMeDVsoyKUicz4EbIHI2vYZZvM3SA4obWjgRU0zIV53AKgB7eN090wU4e5Nhu1apzgsFsEWhXpKc19X+HnFkjlYNZ4RLe5yFW+71v0BxaJoD/0yekmQYFMWmHCEmYKeSFAMSDM8yhVdR0B6fdZlMJUtCE5QnQ7DNTC0rCyWzgQJV7/Y3kYwipUOdZxsnYDxqXTmKIBCQXgIQlFdGDyVrvd7rF7RFQ0L5p38w2TR09CQzmA4S7AhjtgwRw+zLcrJH/0F1O4cJ62aCDfG3BcyLniRsNRnSDQCitfFSqmebGk0dThULG7AhncnbvzGURD+gvaaq5Pcz9zcB/G8v1sjqWClN/RfEmo8rbxfeO35Tc1If9I+b4r/OR3bWYV5yVusFONqExTJwABYY9jw/jJyC4b4D8jW51OXK/izLd01Tr4Lsf5M07WcTzsnMMmaX8cOIYejnbjnL6LkRGLHzFxozPTYWkGVCtiAvCzw5h9aRcu7a3CvsAEn5bXlsdDCjIoXNgz+ROkbybdz+tdHGg7KegbWmQoimmZsaAcXwhCxEndYVOMFGoYKd93AcP+TB9nM6/XAb9hljQgQeisqWrDGPIKs3RrKodgAX58R7Sr6HWGK7WREzxktJBgzE6Nz+JYBGHiZG9qvJiPFnZOfB9QFRX5aLuNcbjplAMO07zzSBMcdHPsnAoqNUv8EBuESRBnzW3CJIu4mkvlhADf8Tg8MfagekMZVHkIQrJA00UBnkxkFTDmLFgRnPNoK+fbRXj7SJhsz2yzUwDq9S6FU0sAs8PNeAovifJTU+RDQepyMjw3Fcgrcu05fU7uQVaKxB5A4kaOvgEukUm8fylOgxAwU2QWaVDuTtiOnuGIzFEa3u7hu7RFGorsGjy4ZqXCmi3ybCY87lU8Jc6CLQs5Nwv5ji8ZuWaTx4MeF0Datm++emTJeGJwpMe4r0Cved1clwYjF8Md4cMdwxP4bZS4nZpKPNQjo8GXFUSkHWPo2PMVeujtS0au3mzcR7ejW0deVyfOoD15Xd3J6x8oyrVp53VhxVA/uozQjPHNTddAmyZszPVowYabaS4MG+oL1jArETpspVijx4P1wcr4JgJpHOGF0Xmg77NnQt0XwnxWZkIYzk/4xQpTMD83BQOhjSbTzJXNvZKVLmyaqWwqea4mE5VR+OA2nAW3YTS25ebL0BGaHmJ+eFy6EB6WXi5+3oMyh8ftgB1ZIroZSZLhZsTY5f6g38dPDNNdUeiFFAglHdCVgJJ98cWsYfB3aaDJGKafazLqjLom+jljGLZRa05Owd4Oc1uY9nYI55PbzqHJgDUnJ64f4ywcL6nfxMMT1I9ig6X/5bYpp9R/0/ZvyrRDWCCOg8nq4HEhcIYXW7h1UhvDwjisdkzbl5O1A0v6Hj6cIltGcS5gHtWgFE3KVOQlp0p4hpbmp13Qes9451Rnvn2+B/I8aKZUgtoX6XJfwciBk6GpTnjKOm2d0w8U5vMnAGThE+YQP0fh53j8HKqTzO0HwoEbnIskXYV+mAf8ndyegZ8j836ca/KxC8FQ8sXxQA3KXZy7pbCPzH4IStTwvFFznNfzj0KP4VHj9cOH88+RppO3BePpMZ4otEUOndIzdxrnF56h557KM6Vjom+eyjOSNvNm4RmQJ0/lmU/NnFu0B3ecyjPyiunH+PwSeGbnAvQACsfZztw9wkBWlHWdqKwr94eisqYOaP6y9FFjoLjsFScqe4XxweKyrScq22o8V1SWTC1Ulhuemqa/ZtnbTlD2tttKyw6doOzQUL4sdGdh+FLe30KPCekk/bOLzfLC/hLzkEZJLUmSRtLC44L0kuVkkKwm68kQ2UrOgPVxFtlFdpMJchG5lFxGriDvIdeS95EbyT+Qj5B/JB8jh8ld5NPks+SfyefJF8lXyNfJN8hj5HHYv1BVmYZNBz5h30mjeWrIvEYmjJq/oVaUmt+pCF2NFpyNwqAzxvg3CX80C6OZpwKfAfjEyjBoFP80K+81K0iLx9C2vtd8DH9Omu33mr9jQ1gFfteEBWlaM6und/f3Gx/v5394zf/EDfiKF/QX/UZdP6vuz//lftXPv8Pt1/qv5Leu7C//ey1//2i+wFG4e/PAGwMDUsOA8cTAwFX4n7UPDORuGTC8cPtTA8aWgQH28sD03bwQ/D2I//HHB/HbgHHTwAD9Av4fyJ3J7+Al7RkYmLkDPt/Jn8ZfruI/wrt4Fp5m91rPsIuxE7lKvBat4HezR9gfzpxYa/tS8gNaS3fRjwDPbYVkKH6ZkfGKXlZe6RO/xEQVv+bU7Suve772S1+ldZ6kD4G5YzjVPs/bfuCk3TxxtwJWbxZqvay6OVj3P/U3F7//1n/03IG394cr5i394To4pb/cMyetqn2BHwjmMTPXTx9ZQVaSteQ0splsIztAJjmHjJELyCS5hOwn7yJ/R64m1wFh/yD5EPkouZX8E/kE+ST5DPkc+Rfyr+RL5GvkYfIt8m3yXUIiNH4SBF1ocflC6NKXTgbgmuI1fErWffgMw6cGv/vglYJrir+ZWIZlJPjE8vgJRD7dCNcYFTZGxW/42Wg+j8/0Qhmf1QZ8h/pXsGQIm8Cq2c4T4cJR/A+09zW8skgs3qB7ikpdyV9lmHmloLev8f+0tR8VlCebQaCenOy2iykH6sjppDm5D+KPb4ifpPEBk9D2cLoLl28IynwVv3cVfpn+JbyZ9JS+xom7RYxN2gq8ltRh8ranxqdKHZxPZa4T8QPAOzCXxT2YvA7nNRfmdei5yGSafMYCZQt8hijN6wbJ6+R8FK+7SMdi8ZWkIAJwo20pjs8odAldgrqp3C3wYIG31PAMgQfJlDFAS+HpEhEkQnklPqunyD7nbmEXl+qXFDxRLPSBi9Ck2Pid14E87g+4Zv2WYh48PwbT/lwI8nnDc87DngMP/kCw72bbOAenxMPlYZsfN/Y1fzJb1KB5UiwGLOaR1yFaNespHTPXFxXqICVOI1gNOyd3J13CldK85wU/YoA/RmKIhSs8NkxTQuwlGpCAn0vA3GFYAj6rJHBMuii1s4YOxPdfM7rb4dd0VVZ1zec4/3yHD79omuZ30DXr6Vrj6+uNh+gVtV8P1rs8LpfHnQx+vfahYNLldcH/JObDJfbZB2BsR6QPkjYySvaQe8iXyUNkijxHfkR+Ju3PJB76+oOS1/Mx6nLXBL2qLJPlVNFbGuuJzFRl01eofaOIHnM58XrcHu+BOhpwOV0B50FS4XQ4KxyYekZVlQMorDK630ft8SDzeO2eSeImroDbNVlfk6wOKc5Y1C85iNOxF1CxlslhIk9W0XAiwir1cOUksRFdsekTRGVMHSGqys4nTGWnm/FozNbdB7H5aleg+pSalapjUVbhqK6YCFGnn76t1h3Q+juKWxeDP+VBV/hlbHfPW203CO1eMbfd/y2g10PzV56weV1RD/5PtX+SkVtN0xv+1i2P4F9m5Mc//uEPn/juvZ+bnOxo//HPfvyzn/7khz/64Y+OPf/097/73BPPPf7tb33ja1/9189/7sv3fnnynsl7PnnXoQ9/4Mbrrn3P3115+YF3XDi+65wzd2zbetr6lZml6fbRjtG21uamhlTAl1IrWsI8XnxvQ2NDT29PRbhC6+nqDDcAAQBaoKkaz62DApeaTDSCKIYJr3hO7N4enl1CQ1WviH8QrKilIod3V3c7k0pCZ6ZUkeZ7nrSeQaA4SYxIwh8QwSC4/3M+ETlGzSiJpN/oS/q0Rmo9AKW0ROF5S4x8bvf+prZKXfH6vFWKYl916aWrbLIc9Xk9sh5pb9q/u6GyqqqyobtvDaZuC9c5XLps06oqVl+ypXlNX/dgVTBzc7Cywh+It9cPdncP4oteJElMrolg8M5oVMT3VKjUL1Glv7OzP+jy+INeVwUWlZkkPYTl4PVaZhWl/24FBP1ZOFpdAZe5vt/1/TL8gQsHt0eUumCNLIdCIW9FhbciGJLlWDCuRLYPTHwg3NogSQ30p+uX1y9tCGJgDrvTEXFHe7Z1L1/f1xFpdyQqfP6KaFdjJdO37d697Si+rbS5HIkwJmjv7O/taKhvbU3W+dGD3F/Xv3H9sqY2h0tSVJ+7ZzGGZQsnHC7ba2v6jvat4W+9uZfpZ9pX5paYsUzhvaIar+i1vfDHz+rIqLnXVZAqjPjhoESqiuDRCqbpka4Xim5Z7NKVYShW4Y8o4nCjB+NPrqDJ3jBGNvdQwA2f1OXTRltTH43E9t17776musNV0SuvvBL4oKvbdkYv9rcF7r333qOpocrLAt0x48Owv0+PiH5Mzm6jr0rXklrg29dkVtoprMfr8aic4lG5wpWnFC0s845zMnecqwPGpC5Vl6pPJuKYao/HjXPg6dsJo8ahYUYIeBV2ne5WVMUuq8rQ2Qd1j6qqdlmWlKHg/zWepcuNnwZa/KFQrHlJkAaMCpriXysjwSUVxu/pb9kaY2yI/lbkxXmANUgX8YjTS/HkeFEDUzUem1IhVJnkx2ciyqsqSeqwGalflbY2BZLBdCqV0NUajFOynKbzsbusTIFxWBgo2fK1BheNuLfnF2y4ojMZsVcvXtXQuGpx1I6BV433R5K3TfW2AeqMUq/T4cNLn8NJ74wkX2oYbKuMtA2mXoJyW7A0++Gvfw0lc786ckRz2GrDTUuWNIVrbQ6LFzof8IMR9YuM0M4Wn42KA0yTNQM+5+/NMic9Z4Fnk+Lxy+HtblEH28v2iXr+bOIitkWxLQoTtBPbQp6oUOZ7RW3lOSFLVWq1ZWNdNv40O/+y3KfZWSZr9vHcIaxn+ihJAm+1H8h7c6ZREzm45vHqOQ9XQTYJ9ABzhdE4Dx5jBpCKTx9VfinryoN7X3p58hFZlY7X0AOKTf0vDPv0qqLJ6Gw4MHszfZTHSQ58iXglEsj9AWNg5P4wezNjcD8Jd+7hUbjds0+R94oYy+/lMZZzOSjj4mXcuZ/OPuBjJIA2QBgddPYBHh9DfrN7dhpzzBOMn1JJGsi6zGrYcmADIrinAbtoO8gDnVGN0D1EHFmrVZthfem2MWKzRW1bopFEbaQh2uD3BX1B9PXWamCkoXhKxNCCKx4aCw+3MF+imeeG0+qZq5Qrj39g5qsv7X0QxksvMW6WNPnhSXrd3lAm1BKM1dfH8I0eyDbRxt8pNiUL8HqF/cuVxrX0OiOaiEbraqujCSIbp5Nx+jxbzPWmAVjIF5jZd01rQez+MIZrB3njAkyBIY/hQXgcs+8mi8z3sdwEkg1Vu4AoilkKUzIEbHpNdWWFx6UHbAFuHgIT2og2WjhUawuCMUlFO1IWs/Tgy3gd80nAS7zD63iWZrK3tdZjXNr6VuP6+paW+lQrplEvwq0apASVPhVT62wCxh2mAkYhgfwisT0iYoliCpwBv9cDT7gB1zRM7FOCaWlx2WNF+218ZzHeTWeUML+u9AD/pushdykeZsksv6xxaZjTTwv7OM2dvokkAXf2Exf0syPTWhNyofsL9rPoyIl3k1ubE2lrzB/wBXmmd2rhBvILjvJ4x1LuVo4R8jeml+uOhydfXr73QXdIs6k8RtjDGDANsWFId79CGwEzfGEN0w1qasj7Co+NZjj/BuvTcEp/XwynmcvL1iehuV+ZMODtYM4uOm+EwvNQ5MzC+hDtlCwMyfgeH6z0wZnLOfqbo+O4jqMxYc3H4yJNZF/GFnW7QADjGXhQEGkQ9sjvwKh2srQfOqCpVNtDMJYzbhVo3qTKW6OZ+tLod/MXG8k4qiM8a6BXx7XcpcUbOVDi4aTUZc5RvIBNHTTOp2/msr175ccuQnBdNN1/0eTL56o2LeDyK2sCeczyHv+Gzx3Sdbb/xhsRjjfeSBtx4oK+cGUBv4IhnFEYd+4Nci/TYYvEdc2t6NHaFtaoIoyTlWLjZPixEGVE1TB4SjzG4uncG7J3+jV6ezZ7JJuFOXsjHxMIa8RIJbAR4DpidI/Ko6nrRJvAnET6loDfMlHhaYhScVhM8d4unDz+AqDk3qD/ZyL3B7Zy/+h++i5jeIJ5cw/tG91nfPg4jwSEMYFwDo0fzH6ZLmEP8XhA+peAVDLWyZPkmPF+Os14P/CfqyAqzLg7etBJf2vaMF0x/uLoPTJ6DmiMyso/j4z+cucv56kbRoR1lwT1gXWPrWHdUNNvC7UbFex8Nr7zRaybe7hqgLKKnMuNvjhK1OkjZJznw7DOpKKkkRwS9HWR6hRG9W7FJUkOmBGvXZYYcTDHBTYNWBZ9zANbRVwHOtuCZTWEL5mA7llPTHipnTjsjgtgNzFL24DetseqAf10nNVkvLox1hgOeaO+KNo3FiKLmHPtw7kOxdM+fpYeD9Eykhwous59K/dzFmcNLI6fuZ8bv6/P02T+or/NZo1vZuGvhEA/h+/4jfMR00dmXwKYfAFg8R6xBE3nBYTofieVHSL6s52qiE7Ag7osH0tSjdmXWqzi+g0nLT2SqYhWRSqFlVIeHd2AjrQYFU10TAsUjU8fkW47a/souzq3YyP708wLQ9IlMxcO7xjO3cg+uzHnkWo3znyMNdx007rL4N+NN+IH7KM+ngPyJ8CFrifbyFnk9ox/dNMioBZ0ZGhlbXVEdUhsk5j7TsQSzJd5UKdWNhSevFoes9sUIETRzU4Xw5wE5lfcbNsXfGgXWrKajyhjdqoodbjvVp62YXjHhm2nbVu7OjOY8Lcn/RUJf4ub54EEkbDdZHcrQkmTn62lAJNwslHkMUjNXyhMrQJKJ4ZjRLkyZYa4blQ9UEMsEulY1TwyPppub16cvG/9ufvftTtTv3PJ6ecYbM7PdIf5G/tCYrDicE9H7tmOFn5FK9Nr3fbmlXX1K1orz9myqLuroXXJ7699/JPDi5cMb7l6nh+NZv7TjrbGZ31rrl7U+gy88Jog32f8B/CDMZE349+QHzwPdiC4P/MPs3+mMfIZ4PX8018iBD+RL/Qy4j+Mz/0Afl8ifs+9Ke5/Hm367pWa6Q3SQ0XZY4iZAYaRm6Tl9Enpi7DmeSZDJvPIVSP4SegYN/LbRklFEFBRIQ7qUDQepj+/1LQiTogeisbjUXj9hr/DS/qiddVv/iT6M0zvlz7lw1RLpf2h5BGpjt4ufcHUw+a1qSLJQLK3i1aO7tsn1d2M9dwI4/p36es+ia4vrafktw1z2rhJSsGY7yNO1JhaYX0liYdHl8ZQj7st4Y97ZRuavQRRgdHFx3fT0Pbm1qGdGzbslO5bV59YsXEjny9yTNpML5G+7cNg3uX9OMreoHdL3/SJUOy34W/sgdvEb5PQx1d5H7fMee5KqZnFcM74QcMR8Zw1Nvb3MLaol/GxUXJs1kuvm/0UwKw2U41CFTpA0XzQFeugAlO6APGg12VH+8+9+Waz7+JZL2PfwHIzj89+gLwodZv7AOaEZdwCXlExPejlmOBDBPryo6G2CCmG1qR4rnHj6KhRN5r7zuio1D2zXbpPvHg7MwHA6VuFLDM9xXE3YOJu4AME9p9jgLvPmDRpklxGriY3kqOZ0PXvObCpWZHo+9+9f3xVdaRC9+ZJU58TdkdBZlzcWHnMARKNRZ08biBIChIb2IK93qIbSKG6TvTsLreN5R9Uxjx2ZpKp5HsPXnn5pZfsuXD3+eecNbLzzO3r1qzMLO/r7enu5BSrKYAJIk9GrjDGo5I+CclKnbRAnpAp1kWocLXiBKStN/e9hWnbCeneToveGebFjGzdOXIi8veXE5DAhX/6nkkTvyU+1phfhR078Cv7YG9uMPEUfSOZhkGLpQmVLwCymwiXSLla2TJfgDpgJ1Kh+AomOAmmv/lxdY94HV0H++Q6MrsOZYw6sov+gqWBjxwdeiAOaFeFC4JbOKtE4WlGuSMVrVYBr7gXjUqHS8rgrzyKP1WB7UYX3hKraNziuTlB3MwAKNLGG3Xf2Elv3vmNb+w09u2kiexj8MffuKxgOEXM1FP2ezS5p0XwRv9o9OV+Th83+kDmxkiShPPLQvYIkwu+7JF5KFPB89Tzg8R3cBl7P+pOZKLs4VF00T7fjDQQzSSKJa/5C41kbNUocQjZ0BI30qVCRu4NaUBIFzOPStcUyRQz76O/FdJEtliIQFh8ffaf6R72ClGIH61w/Q6QpZX5Eu8EvEzjuWpCsJXQgqRMja+PsjNy/zI6Sh+uDjsdDqejym2sZq8YHx43/nEPdfvsKghviuLSqcjnYHwc2lzL2wwglXQ5zUjxc0xP/bzJNHcy4cE+qU9EPjY+zs4YHc29yRujDxur3VVwSSvH6YGZh+lh3prxB/YO4/dc/Qi0+Xloc4k5zgqUa/w+O6MKV6XNP//ITaK2K4Dzb8Nh+4qHzam28QIfOgsXjTz3xuioOXpptdFVPPzcIfbTmYf5XnHF7D+TZ0wYhNHDx+UEAstDBktmAEyJFQ7Mg8FgOAgMrs+raOIgnGJk+CKQoLeP8QQA5V82lUJF9kB3ACqXjPeUw4V+cuZhaTX0Rz7+3Xx//FxX3Z/pAwjJkohijOHzeZ/wApUqjCdF3BoIEIKcd6AiAODiJ65IHBQfoImtFFrovYgQS3FsKcEV4/RR+D+6A8CFUKOXz4WZtJrDTT5+DfTzxjzcoki9AHICf2TV6qZqdZMJ4KF4YALQpwEAbRyCvXMAiD08fg3HrXIwKg/zTtKrOIpd0j4/LKGXzIiSlfQ/2RkgayZB1oz7fW6QNXu5mNmVVhEeKRQ84+nyW010rTuo6VyL4za+jm3n/lB+747bNZV7ylBVu3Pk2Pixs8pu8DVW2odEwIvyrhU6NxnqEaJ0Gyu7kWqCRr9eaJCu5V2Yc49eBc2qKivtR8kNce6QNXNhh4E7WZ3JuHVOHk2t/UHCZAzscNAke5MqhW/yiErzMcYjlXU1lalIqt4XrNfQltrMlQREH6MwiExK6XA+wDgKKtno4jXNK7IufccO3ZVd0bxmcfSnS1evXrpizVo6gokiezt35X6yqxMusp0f3Dh40+DGDSvhDXmttaSN3sAmgMeqmb6I9OJnroP0Ar8dn70GuulFXrIXoygz8clp6M10rTTAd1PYT7hCj9u0FxyQFR6F3PQ306q5QUgaXikYRT+9zriWNgJ9/gtwfwMz27OSM2vyflbd3lXsWZFNGe5tnv2oee95M8My9uGj9IvQB7SBRR01lTGO9kHU7AGMGebRNZ1rVZXTWHWXXbdJGIAClaqtHa0d7W3wdHO8Md5UEXdAH7V0VydPdNKbbpfggh9I4mFljeV1ww8uU8DB9l+5y13dUlPTEnPtuvLKkfO0UGN8dLSuKaSdN2JcO3pp9szPRJekKqhCww2d0bvPzA5vvTvaUOnIOiobop/ZOkwFswtj+0cY712c1x3MfYtnPqy7wyO09h4zd6YxBmVGeJmE4SQeKLPy+0RIAKIMUWaagSf+KnsaaFkj6SKDZDONiyTQ0SRV5ARVlXoQX0CYYUCdNNZAdZu6Kbrgr3b4dUQkul1OUEvDkH0AlolidmieZrxKJOh2gGAex3zL+hgeAtRt5mobfqlbiaj7RRXawbdVR2bZqT1us/EcZVFREeEPj4xkKhc1bTpt7aqB/qU9HW1NXYu66uO+yjqnhg51Zm4tPMq2DqFpUchrri6jC+jyU3i2lj+8yH3r6vSO/rpo57rm0c6VGzeuulq6tixM9syB+vb2eniJd3jRfxPnGfjGno71blncsj5dyyaPrlq+fJXxlaKA2khvRumtbfX1bfgywuKzLRBN8EMP3CuMo4ADhzgOJEg76SeviglcUTaBDloKPmc59F107hQOlM/BW6xlLg7MXwE+Ne80xpd0VIYJWZru6F/S39bSmKqpDicqEzDYEMxn3K1hfrK5E5pUFpq+koOntrLZM86eM1cv5Y+f2Ht2lM3V0JyJyebPoorzHcdgV+jItKZiDA9wQIAsznYsi+zBlCTqrCzHyvxZjjH/EmwAjVb+vPIcx1+U1y7FlMH4Pm+K4+8uXXnawH+fwd85T2/4gMb8FuhpIa6LSosc5feUO8qT0nTdCu2STJ2jZPjo7l0H2cdyG/vZ6zOv7d0n3Wfclz2SNV4m8psz0M52vncIX4ahzAa7LktEcTK+h5R4/7sdNpem4E4SDKJntGBpMLGf7tGFZ7vlJ+3BMy6+R3b5RLhS9D4Q+oZdf/rT4TfZnw4flu4DRuchYA+zfNOZuVu6Lzvzx2xe+wC09tuz03Q5exporYKaNC8jyr/h/VthbW3i95OGm9+v+b51f0nJfa5HM56B++1sMd9P3xDlH0A98SzIiJ8DGTFKLhIyYo0KLABxuxw2mR8zOO1M14XHNq3GhZPizts6HS4ryEvou7A81UFgqoIpiZK8YjgvNHpKhMZSwRFf07NceMzLjwUR0hQiH3uM70HT9GExztxP+HiSz6Iu5qM8L/rTJT4on84ccTvtaBHIGuoTdTUelR+ltDa2pJqTi+JNtV4HiUUj4VBA9fmJbwKGpCkKZmWvrqqsCCp+f9S/ZdOmDRvWrMlk+vs3bd20dcvmDUMbhjaetmb9mvXr1mZWZ1avWtk/2D84sGL5sr6lvbDSuzqXLIYtvbWleVFTYwO336irrYlVz6Mpb0OI5NM2oMa87BNTO5jpHVCDXvaZTMFlnL+S+df0R89L7b40tS51BF7UXX6xYwx+rV9XfwRexuvlF9vl51LG6ymDv9M/whPsJ/A20wpvVH5s5VTR38rHVj5W9Ff0BfeA3G/M+bB8g96VOYAzITOArEPm81ATjUVAGtFIKODzyC43cU0A/CWEf9DvldzuqHtLd/fixa2t3b3dANrFXYsBuIJjOgXo1pZDt2teqCYL0CxAMfebIii+Ng/sXrMgxq6C3+nzUMB4fxmUiiC00sLfmSdm/0xeEOtx+jWuU6zhOkWJ1MyehySQ6G/2mbAr96P698z3/V6B0TrrXNyOhnIBncNyWVffkqUdvW09LelF3Y1BD0kl47WI23oINqYJn4fjtoawxZUA+K1VVEQrtoyPn3fe2Wfv3Ll9eza7adP4xPjEhRect/u83eePnb3r7F3nnrPzrJ1njY5sH94+fOaO7BnZM07fJlYBLIENsADWAPpnAPn73wb2Lz8Z9p/aKphnNZSuCHy92Ve8KioXvChZHi8veLFda00ZL6dmDuG7dDa+UwNR4GzrTbt4niUzz7IpWzrm8gE6dw3Q7Rs4nbPOS5J3IG81DPTvUc5bVcMm3U42ZzZG8ZB4k4MCMZZ0DVMqUgWZUwU2L5uCIkiev7F4GvuWWKytuSEZq4vVBX0+kxOtWYATxcPLHtTE0RJW0xguZzXpZlaT+0IjazRON95xIpbyU9n6ALVls8aiAt+oTh+Znea5sBRiIy5+Ypwk45kxj8Zk1c0UCcQor80lSTpldon7DNNdTkpp1WYftdsdY8ThiDq2VFb4/W633Y4BFGqrK5KVSZHDxu1z+7weu8sO27VqU81ACn6/Fm6R0AIoLcEIU7AVpdDvGV8lnNlfQpnQC7mfy+tZfPpruZ+re3I/N/6jwIo9zU1/rjSa+WE+/L2Wzd5Y4L2mf83tpZwwqgRpQGk1ERXLwWvDbdVdYvmOC4fhoVWahmhRzh1U7oTgZpGCpxF4i+lfszOM1e5Ku8Nmc9gr3fRh2iOUPdEKBwDGHnEbq6dGR7me7iLjv3S3ogAtUGSnbrwyIa1F7c/NF1GvT1dkU/1Tcf3MDRwPPwhy+TVcLq8zurlcWAPUHORCUwrHMu+FMlcI2R1wFWX2utl3EiLOf3r5+Q8vcxeUSZDEHWgVxkuW1VNoKzF/WyBvbyArQb48A+3k0PM74sewYxudIGrL5DQ8dmugMKE+EJhS6bAwXCi2M0gpaMFLv2psMDas1Gx6yAly1oAzpNtMi4NvGo9WeWwaOwMEnWPjPbC01MP/qOqSjAoWpqm33ObA+cL+Tt9OzmUuiaEd2/SVnJa7YVRHoN/uHxEhOx+xxvaJ2fNMPqzW5MNqOR92+G/H704fRn5XcR1/rl/Zc/zdRfwutD8s7FW8Sfqd3D2zv4M+xvM2d7/DTvIyT9E/0sfNMi/5eG4zs8xLIm9V7lezd7Dq2ZcWjFmgVbZgfJvcr+Sl01ORIW6TcBO8/ZHbteCZB+a6KqS52sOtx8wAeizKtsxz1CCVmRCgacGLE1L/zKF9o/ty57NnJmYely7ZP7qffbIk05VoW5wRxFDXG3MzmdtwWoZgPL06KzIEC/qqSw3BAgULMLQIk0zTqJnLhB3Ysam8AZgn8vDkd6ebii2lyGzeAsxZ+wqZzRtOSdP/BXj8qKknTBP7V7rqUVconZKuUCq/kVZOTXuYfRvqRLq8vATv/6+h/w+X9D9Z53VB/2m5ujUdPwUN7KkoYOe7R3tOopSdq6Ut2KyFSRPGfWuqc0poslZAC9P6DNBDlSeK0cMf9gGC+Hzc/CwgWJRi9GhESzTTDq2xV1ifadM3vbR3r/zDvSWYsnd60UWTfy/s0OQfDQ5ON3PzM5VupI033liMNjfeSOuFIVo2y83P5GIbx0qyCMcQr3RIQJc2gYRDlYKNI8a9hX1zAq1ZeHAlomyNweKKFZnQheIlNo7wHcZQjPcc43O3whh2TZ8vemn2+8j0kYsmXzZeeKnUCpLtv9HsqWlBl71xjjWkuTbNMVzw5UoHHm3nz++41SBRZFnZvxAlxPO78jh6cwqNZGxitOaSnn+0aCCoXHc8Uzw45dHj11yEC3mesRD2Znep3WNT1f+w3WPlQnaP4QXNHrXj0b17tag4mXzjVwW7R+2OojPKNybEqE/F8FEmJTbmYXLxl4MaN4sXw07x6Hvv4JHQ9mOqRRRNCA+zRQqhy9Cymadh44HKFyo1gi6N0ETYj0sO97pU2bFr2oTC9FHl0WJDzkHlvWLM95acumbF8IrsmMOkETNVx6sCKs8EjZnoEHvyXeKHI7B4EJ00ivjk59NQzechNX934uU2zdNH5SNF/Zsek88zbVGPlNs3H5+nw/TxuUbOxTSMj6GxxifLp0jEkIDBIKIcl4pG0BW2RqAly8YWntmY7+ODQAieNwewaO8kBfJVPLpFg9Rf6Ot/WUh1rGRkpXbaOIb+TF+q2qfKc4nYxHxErLGhPmmtCFzYYVzIJptQgLxSPDoY2cz90PcD008ODsrd0x/aWwR445dF44Oh4bKHvxuLwP7OolHhmErH4Ae+fiCzPBp04AHtJg1tjwhGoiQiOjC/IXHvHonx89tAIpCoCMGDfr6hRFpouKz/HTRZgkkdNFc5JQenf/fdop4/+NUSDHqQ7Z/Nzr5a1tfi78J24lf5fndkWh3Uyplb1OOi+MpwS3RTmb+bxpYp9kIu8d0T9kPYwHyGrIQazwC+fgMZJbdmakfXMlXeNgDb88g6ZqPSpo2nrVkNLP3O4Q3rZeW0oQdcQFhaiExUKh8gNqqotgNcDwnooE5i6nGFjqC5inI+oAZVTo9m2rA0UM6DWBx+P3jC8kBoentSqWTSn0qlEmhMzX33auSuzgFZfK6gXVxZ386SCbcMn/CK0WRqzhFNeYbUdK5l/fLa1rrKyiqfrypcWddaW9saj4Qjvo4d65d39KQSVf5I6W35vPLjGgMTrOYNrjHB6uLl6wOxRCxQeOttalq2YdHiRcV3muec23y1PCsrj42W90nwI4+sU34kwF06ZNOlY6LA4puoUM1RIUrLueP0b8sY4lyCHihng7Owbn6dbzOBviSJKjvaEGySqRmeTBXhyWTM3L5LMUPRx+uCAWy+GoWfCJrRl7Pn5R2a/rX0y7Ie0d+WfH+ZJrLlHaRbi7/jfl++ZzTWgigxd8+YmG/PqOY8o9/cMxpNEaaUbZzT76NyNzCFT8oHytnGDylGaf+PI50qYRjp42Xdh3V3tMj26uQ5ZNEmEG3VzVcNfd5ozn2v8M6NsNBngdM/g89jW6bZT08qXvlKxSseMbdUplI+Ojp6fF+pGJX7QkF4Yvk2naQao3pWO9kp7ny+SjE+i4vi5zTFTFTa5Jw4Q7gE+vH9Im+RA8cPFVil3BeKGKUjBb78XhNPnCSK+4EfA9QW9w16qu7RqQad00o753K5oq5oTOxqNg1j/IasDTmUTJXsx6Hpe0eVTrEJH//+qPJkMf/zkQIrV7LrHkHe7d78uuvKLLYvPF+F3Aaw3pL+oFA3pNC7tMQXiffk5hJvwX1JtmW22B9p1pTD/dDuFeSzGTujOruCUjuaxSJ57+Xp44nO7FQ/QHw23w0qoKXX7ZTslNkn8VCA2mx0hF9Q2/kel0OyURuQ+qX5J5luP/gWHh3J1Fz+rj0T55931sj20zcPrVszuGLZUtwB8M/r57pprUYq7AHln/PtCaWfLbTUdwyQ6hOxhpraCl9VZSQuSH5lxG99a0tWRap8fnhLtM3ZJXzRqqok3G5LVEWiPn+kuILib1U+WXcCt1TwR3vcsaippmy3OOnmMd+b7pDRSKjgy4Y8BJ/T9eSWL62lmkTN6ewRk6IyXVIPEKpLOpUOEofmuAG4H5hiyvS9gPOaJo2gn5R2vg0e1mA2e/MPMlU/eOpPjmT869etHFy2VGziqQafEyfQ2hxOcfKYFk7mfmXJsvVvY14qE/VtiaOTKMMLCuyprKr0nBTaVYk65T9gHx4m/0nvp18A3uid/z+zPcawydMg7s7L05TfPkWGBenTbSZ9upy8lnF0wZ66oocVEwqd6ZiFzU4JQMVhm2e1OxzWanec7/O4JAd1YIgONL9fmn+cEaAWb+H5zKD1qJMR50Fid1KnnRZXwZx25ly4hhEgOO88gIb4Y7tGd56xbdPGtauX9oYEvUmYBCcUXJjeDLDexjLBciHCg7iMhibTt+3o8MEMcCzmCOoDLE62WaSilA6Fa+uaau/Oy6NMknTFU1ykyldKaawl0Z6oqor6qhKpno7l6+lHmnpPTEdijSnbDwpCCfBPmuKvqqvyL/gEoMmGZUBvXjFxYwO5PeNoozbYu5oBl4csqqMyFWdIR9xQdKorMEGaTTNphwK0A9eBTawD2/kOKvaQ3vyDjOgHT/1JoDrr1iDVSfnDYhadC8ziclokhPa2S+UTlXvlxBNVXxBMjyba6hOVvuipgZ7TH1qQA/9DqUtUlQOWy2XHZt9J/106nawE6G4lg5n+zZtWr0p3t6aSiZpo0I7coZ/Q5fWYM6XEY5mgwahUJ23BY+BYdcAv84OrVKkoBC9fowjmku4K8aOsUgfQVENjKFxhhnPBg60QRoLooMGKcNpNp4/R240XhBRkQynoSNNk2rPzzN2j/S3nLGW1xoU0UeQ22n/OukWf2z/Wv+qKdppcOrjvs+3dHV20cvzF0U9qOuP2X8CWPRoISzC5sYs2bDlP/7u/k2tjxn+UFNHVVzu133sGPrbtQv3972WRCup7Te9tOzv134uWBo/pnTB2wwlwexPg1kn6SAYjfCxL1USZwmimu6sB4EaJjbBWTI2zyQwvhhHERS5OIo8RWa6Tt+CReSKOwFNOCXjJ7nmh1SjxMDZhwzk/tC7Y2T98ITurt60EOGs7utq7TwCczWMAHP2Cbc3L9flgQc/QO5sAHgALBrD4I8CiiywDPFqVGRwc6E7VR/0+t0N2SCQdDKDRcQs6Wm6ywq4xEXbtBsJYHT/86SJdaEBQjdb/KRhM0cgr5mINvIoAIQHkitHGGFvZK7DkjEvmIsnibJIDAg3RaMBCk6919KtUzWOF1jcXKaqiMofEhP6+90m1XWsWXfuatrQYLWA9fY200VdBcHaAjNKOOStqa2Jul1MltKES1hCM0Of1qLKEaEFk9H0jyHLLIiQ3Kn7pnMM+YT0lrKZSpS7UDY1tc5QPua/8987//u+d9J9Qi0B78mqF46vgRrlKIZH905/g/4+4ouBjBc1B9bFynQHXe+V+TtpYnI8vhnJOzOUE8T0IckR1tEp6a8OSioY1V4MS4YMwvrRQj+mSeZQaLPct6J9kwh/6Vx3F/lVA/zxu11vsXwnYS4GeSue+xfsnbSsBsfGE1b3RUnDePsJxIwO4cRBQfQ05kyzPLD1z3dpMT7o7XOHTqRNVcTu2n7G5b2lvS3MqXuekpxXOWce4W8q2JYtj1RI6pPSGa6RwbzsVrJoWxtB4YeB6PRRlDo1zCfitEYW1wpFc8RDm6qzuq7nCG4/HnIru2+nXlfCauitqrqjdFJPDfYGhDUOBOptuqwts3LAxsCwsxTbXspoyDJt+bc5sfeiMZtUXCNrk6m3Jq69ObquWfM2O5jOaHQ5mC/REL+68OLoxGj2tGi6qe/0aczo+Vo6Kf5g70SyXIxm2hsNyLZ6g9/akORR1hOLaNQPdHIS2E4BQessgVMrpc8jxFgGW+2j5Cnwb8BkaL1+aiPffBHhIJm4BPNA8sasi5NVsCJA16e7mRfV1tfo88FjcEYtylHqL4OigNF2mCM298NbAcQW7tXTt3E9/8Vah0WwcLV1npm4zNXszfVUaICH0Mw4FPTZY8VpZmEk0W4Btt2xWaRc63Bmpsql6nsZ2GZ/YtSj3A3pV6QT8hv1hZvs+WsnPyP4E7aJfCLf1aUjWcOM3r32urU+q3u9j8+36tEujZUsVOjT9p7L+PEWrdw2w/lLyU2f8ZVcL7OmlHfxP9urdxi9LF9WrudUHLLvrn0J/gxh/yu9SFRn48pKOBtETMdwizdetm3b1szWlXQgbf94lDRwxXihbxLnBEruMCJ4/RJx5u4zCWags8xA18nlIk7PBpD9Yojks0UKVKhDfV6KOuqpYlVislhIKxdnZmUtm/0zXsjPQxml2lYhRMP0xbpPpz20mwnfyw+ReeoDFecT7jwlXhBihqIug0iTq81RNHUdLuzEeHENGf4UlUKiJQBmqSAdhaKVlGavezB8gonwGQxvIhMkHF3ykUBrESW9dXV2yLukPJFOBZD1XGJrG+dwovwvN+ITsroLQjgZtxoeji9cs6hk/Z7ipNb2j+Jr9KldNr6tf3hyub6nvaIhECpf70NsW8IP8jP6RfhXGPzb0gI9HsMKEfkSZJDBfdCSfKhhmbEylMGXbojB4VUIl7MG5ZVWrLM7utpGMPZBK+PyphMbHYSoiejBuCnc1k8UoDN+OdcmuhtqaeEVdA/MbHcG17YlO+J6E7zto46qtXGG9KJGNp/hVQ10/Mc84fkZf5f3fI/qfQGhTJkOfVAyjgBH5eA5ijKeAEyDBABZBRxVVQoe5fGGltLAyBkNTYAQeaKeW1KaSOBJ+KpmOo3M7ioAV0Hce1w7lvoZGw8n8DXUV8Zrahq7kuh074DoJ152J9rVBYBwSi3jft67q769r4JepONL235CV3H+jl6wm38/Y66juCFG7ZgUXaMVkU7rimETTO0p06KyNanaqTRI7cNp26QKM3DkGiFerWu40XUSyy3YJcW7BZ4lsl0sezSzDWETUhj40Cz1NFngYENe/KrNieW9PMtWYSgRAvnTmdU/MhFbIl2xUzbADXZ1h3Iws4CE+49EbDyUAtwEvcr9Zv7xjaCBd1bK0vWPt+Ka9V6ynlecOrl4zMByl0uY1yzb3dSzbikV6Ih07pI5UYmQkkaJrl68H+bd/dHK0/8BeY/P5F5+1PXvG2ee4FkeD64b4bfi5d93KbM+yd6/4u2VAA/qABtzF3uCWqYszbXZaRB8lypMalyeB8/miludhF56oNDaIoMWqB5YkfJ/dfph+3ohe4UbVO5MU11Xn0OvofxpR9obxevbFKq9N123eqhePZAGHz579ZzrCXjnlPD7iiIwfsRhns0+PjgJu0atmHqZX8DUxk5i9g97A6zulPPdQnWJmYZtJsOtH2fXjQ5gXZgjX12ehb3fn+3by3D5YGa8q7jM+O8o+nTsH3ZDfnzU+yOntzN1Q3wFenwNPlkpjt03IxWcUxWkYuTcdr5V73Binj7Lv5JahBzF7hdcuHJoL/RWZ/Oa1NygyRim3uhQ95//M3lsjmMkeyc4W9R/jUoH8cUKTBpum89xzTqeVrzDfmr2staJB4Sv3xmi+UdNVm0i5783ezCTYy3u5J8xPM/YwtJ6gDq+lRl1HdL+P6AcwoCBTtUk8/AsFGPFTsof4ic/r901GKiqDYcXrcTGH0+uYIDZit9kvwIipVZvd1OmMOoEQrMeK/Lrv4F9Z00ime82apUsJEX41S1cvBcZ1cEX/MhhDkVtBc1NjKhxPCsvQKiQbxe4DjQ093D9WraUVaMFeI3moWsFvNQzSvJ9AGlgHEePX9B0wWusLvgF32pQLRtraFi9hduaOtdbUtFW74XLJ4ra2kQsUm9FgeQpIo0rrks7OJa2KMczdAqjtEW7tf2jrcLalpdjv9tMtLdnhrYe4+X8sko3GHnnkEXOe6OMwTyKLzu7MeSYUuUsVcIXEA1AMBbxB2eN2MrvDY58gOgHmebcAnotyg/f6ekLqG+sbG1LobYF7ULHDRd6StqIcXqZ3WtJXAM0ioBdaOUg2/unw4T8Z77YGTj9E323cu7doxLQHvddoDx/gIzg4CeODsnTJ2BiwdAwNYZgUCPkk3Sbpe/xeZuNDs8HQSJB6iGc3OqHnx4ZhcNGPBDkdEQh3wbEpptfdAm4jERqK94a7epPGAAyHXpf3+Kg/f595bXydbqtPHKk/sg89wY0nxHj4EI0nWJqPTMzb++h/SptN36admR3BAJM21tUy/TQ8C3C7GLcf83mcEvE6bJJwk6PofINuqFF9y+LFhAh3JtR1QUV5fxlzQH5gg1LpEu+YAak35Ja4G3g4BFKYNWupuJiz3PfGiiYtbVu86cIVi7f01KDvxbUf9xkfsX5aSseMx3D+rMGtmNze79nh6MlesPyRZeNbehwjdfdcXZhMc8zCNwOzPJ1GvvylaoyXt0kwEX046oCDURtazdmIbhvxOxmp8Hlcsh4k+gSx26VdOPEAARIMuneFvIy7d0UzA+XPumz6hPm05AoSV8nDXgqPYRWhMRIKRUNAOJoJ2bB25QA6IJUSi9aWplS8tqrThGgYoySWQLRrgC6n8/i7IKcZL6DOIDoplXq+lID6CHv33iA6wlQvQUeYzGkbM8GVQ8bxAl6dDijF+gv+MBbcz92Lc9N8Wk9cGv/8uv7+dauX9YtfVj5iXGp5xjCA/R08X0KMxMnWzCakDl6VoRi7x+NzS5rLwXSnppuU1SQOhBNWtJmprYEn51s1/jKKYK0Y2GYCgh4sh5+LaQFfLc3s+MwXlvKvn4WRWVQABvXiEG3FC8KmLwPe4qfSau77tz2zrZna6VBtDSwTflqmiRXi0RmB5W9zERtMMwZb3C2WCUFbiC3Fk1niOuab49pn9ZyzigOssFK6rLH0Qvnpy8bKxyLvW7W+ecula/lKSW9aLB8/PP/Qum4dv/e0ay/Y4BML5d6+3Vt6nNZ45dz3gIe4CuYIc6K1wJgnMxdyWgAj9cFIK6iX+Pxe32TIo0skiEYIARy0nwTs/sCkGLWTcteseF11NSE93YvbG1N1LfGW6tpqoHrz+tTOg9TFVG+QJbWFwgaU4HAxFXwdKAN7sDwssIWz+Y9H6Nq5QYJlpBMmHOIkBbP/CyFtdRQjbcgXdMmax2mXdD+grRvghAjr99t2ASUQsZsFZekhHk3XPPrBuY+jv/GCT4OEcqoPOig8go8HxkggEA2ghNKcSBDS2dHSlEglgCbH6xYAf/mWWuZZmsYQahJ3LFU4ranQ3JR7zpUvqTxT0RnaGxrJGGtwDkLKIqAWNYr4KMJF/kG3XnmlcW3nI488W3Chm51FPEQ67U0SjcffRj+fGqJj1JQa4UMl5XJmHIBFpJssJR/OeJdQXY5Qm744XiNpNsmUJ9uJGW4OuVUdGWJgPjTdNsGjBBAeek7TFJD2FSWqANA7T+EBKIpPqWMgE9aBOJhxL+0NNyX8vnAi2MStEPIR5IAyJwvJG1B6wnh0llwYNgPHuSlPqG1c5KiuqupYtah5XWd0tKdt0RKv/wufZi4jdvjA7sHNY6ef0xfvaKkbjPxTtIY+T39qpOgnPQ2rqzGGW6RlRX2qszMVbI28QdXs9F3DY8Nbru5Z9yZGb3tOb0nWoMGy/GY392NC3G4m7aSTvD9zXTP1etBqkbnZ9cRN3dcTn27TfbaDiFbvR+QLqpyPm/BTTzgAlM/jnagMVQAe6ro2LK404T2v6Vs7Olpgijo6O4DqtbS3AN1b1IQ+tLHqefEvop4E/0rjOSuY8gl9xL+5APoZD5XGO39Zfqe86fgXili9PPbtKYoE3ZTNRgSfQItibzdnGl0Lxp0WoioG3eYJh4qDbufVeC8ej/AY28UxtQn5f97bFoR4nI1Uv2/TQBT+HKc/otIOqCBADCcklipxnBRRqZGQkiaVkqYRalEnFje+Nm5cO7KdZGNG/U86MyIxs7Gy8gewI8TA5+u1aUKA5nT29969e+/7zu8C4InxCgaufnVcaGxgDd81zmDJuKexiRfGO42zeGh803gBq5l1jRfxINPReAmlzGeNl5Ez1zTO4bnparyCx+YnjVdhZV9qvIaN7EdWNLI5Wu9V9RQbeIovGmcY9UtjEz1jXeMs8sYHjReo8YfGi9jIPNN4CccZV+Nl3M/81DiHlvlI4xWUzQuNV/HW/KrxGt5kJS4hUIatRkGhdAq0IJGghggOPASI6Yth0bYQcgqetMSI06c94Nulb8zYBD0il5GeWhfMEKjVJt+J2uFzraU8dTRQZT5cirJt24WyXbZFSya1yPGCWMRWZIWWqMuR9MOBdMXYS3rCjbyRFE7gimaQSN/3WqJZb1SZZR7tfRIMODFJux8GtA8YfIoh2TjcgAN5OvQdgiP6I2711EaBEvnZnJuosEBNPWfLXBcpzGSt3JxpBbtoM3eTMVu0Sywko9gLA1GybGuz0qpVbhimBAuaUCU9k8pu+6hZ2LJL/9YoZkVNaxZ30DjNal65v2nFXPr/I+wp2+FMVITL6HOVr09fiJM7dOOsTC8Wjkgix5XnTtQX4ckfLTWP1SQL8Jpd7Kk+HXAIdKgyUkp9FZ2nb09piJnFUZ3tqZOsqShJ3Of6iKl6nu8NBqIzjIbs1SAv9sIgTpwg8QJRG/oy6IcM6zFDwloxtlHkGKth4UzxPL7F00KXmc+5JUkG8XaxOB6PrTOZHCvuVjfkUpuxXcUi1ndzqG5h+tWFuqPpzayynsO4K2t6T6pwtkvKqkvQ9royiHkbh4ErI5H0pKgOnC5feiUvrpuobNkTadPCnFvF088YsZWK6ngnJGJ62rwyO/yb6OCQz8I1iVS81u6o4lYYnRb9KwJxsd3caXQOG4WUwG83Ey3SAAB4nIWYBXhbRxLHZ2ZjObaDju04DrVpUm4qPZCssu041DRpA02bgqtIL7YSWXIEDZSZGa58ZWbm3pUZrszMjFfuyW//lp5k9zt/X/TfN7tvfzMLs/tCQu7fX0fRZnlhKvtT4V4rCyseRIOognxUSYOpiqqphobQUBpGw2kEjaRaGkV1VE8NNJoaaQw10VgaR+NpAk2kdWhdmkTr0WSaQuvTBrQhbUQb0ya0aZ45lTYnPwXIIJMssilIIWqmMG1BW9JWtDVtQ9vSdtRCrdRG06idptMMmkmzaDZtT3NoB5pL82hH2onm0wJaSItoZ1pMu9CutIR2o91pD9qTOmgvitBFdCgdRvfQ6fQJHU7H0zF0Ll1BF9PR9BodQqfQd/Q9HccVdCQ9QG/Rt3QeXUk/0g/0E11I19Bj9AhdS0spSidSjJ4ghx6lx+kZepKeoqfpU1pGz9Oz9BxdR530DZ1EL9EL9CJ10ef0JR1FyylOK6ibEpSk8ylFK6mH0pShHGVpb1pFn9FqWktraB/aj/al2+kCOoD2pwPpIPqCvqI72ceVPJiruJpr6A/6k4fwUB7Gw+kvJh7BI7mWmUdxHddzA4/mRh7DTTyWx/F4nkA/0y88kdfhdXkSr8eTeQqvzxvwhrwRb8yb8Ka8GU+lX+ll3pz9HGCDTbbY5iCHuJnDvAVvyVvx1vQevc/b8La8HbdwK7fxNG7n6TyDZ/Isns3b8xy6nm7gHXguz+MdeSeezwt4IS/inek3+p0+oA95Me/Cu/IS3o135z14T+7gvTjCSznKMXZ4GXdyF8d5Oa/gBN3F3ZzkFPfQR/Qxr6RLOc0ZznKO96ZX6F1eRa/TG/QmvUOv0tu8mtfQ2byW9+F9eT/enw/gA/kgPpgP4UP5MD6cj+Aj+Sg+mo/hY/k4Pp5P4BP5JD6ZT+FT+TQ+nf/BZ/CZfBafzefwuXwe/5PP5wv4Qr6IL+ZL+FK+jC/nK/hKvoqv5mv4Wr6Or+cb+Ea+iW/mW/hWvo1v5zv4Tr6L7+Z7+F6+j//F/+b7+QF+kB/ih/kRfpQf48f5CX6Sn+Kn+Rl+lp/j//Dz/AK/yC/xy/wKv8qv8ev8Br/Jb/Hb/A6/y+/RjXQT3Uq30YN0M91CD9HBdD8dQVfRw/w+f0D30n10N3/IH/HH/Al/yp/x5/wFf8lf8df8DR3L3/J3/D3/wD/yT/xf/pl/4V/5N/6dzuA/6Cw6k77mP/kvuoROpnPoMjqBTqXThIRFRMkgqRCfVMpgqZJqqZEhMlSGyXAZISOlVkZJndRLg4yWRhkjTTJWxsl4mSATZR1ZVybJejJZpsj6soFsKBvJxrKJbCqbyVTZXPwSEENMscSWoISkWcKyhWwpW8nWso1sK9tJi7RKm0yTdpkuM2SmzJLZsr3MkR1krsyTHWUnmS8LZKEskp1lsewiu8oS2U12lz1kT+mQvSQiSyUqMXFkmXRKl8RluayQhHRLUlLSIyslLRnJSk72llWyWtbIWtlH9pX9ZH85QA6Ug+RgOUQOlcPkcDlCjpSj5Gg5Ro6V4+R4OUFOlJPkZDlFTpXT5HT5h5whZ8pZcracI+fKefJPukPOlwvkQrlILpZL5FK5TC6XK+RKuUqulmvkWrlOrpcb5Ea5SW6WW+RWuU1ulzvkTrlL7pZ75F65T/4l/5b75QF5UB6Sh+UReVQek8flCXlSnpKn5Rl5Vp6T/8jz8oK8KC/Jy/KKvCqvyevyhrwpb8nb8o68K+/J+/KBfCgfycfyiXwqn8nn8oV8KV/J1/KNfCvfyffyg/woP8l/5Wf5RX6V3+R3+UP+lL8UKVailBqkKpRPVarBqkpVqxo1RA1Vw9RwNUKNVLVqlKpT9apBjVaNaoxqUmPVODVeTVAT1TpqXTVJracmqylqfbWB2lBtpDZWm6hN1WZqqtpc+VVAGcpUlrJVUIVUswqrLdSWaiu1tdpGbau2Uy2qtTKXjPv9/mnQFq0tfq2tdmVLdySaTiUrI1p9LUvTzt6OL+JKZUuqM5V0VlRGtNa0RePpaK57WcJZXRMtlqvbYqlsJBp1ktnqaKHomxaN9HYZ0zIt338kW9kOoANguwY6rlS3FztyCsXKdrjhaPW16x4dV2pmeJzq9Dg1o9hXZ7Gv3sADhgE1a2Z63u4qlgfNXBpJD+rK//hmZeOJmOOLu1I5C/7H4f8s7X9cD9gseBrXKrNmS3x5zWwPY3mxrH0wg9DQkBWdacdJJiLJWDzqmxOJ5rKOL+EKmrRC23xz9BAkXBk0Jx/foET+xzdXv5X0vGXZ0KBvrn4rqQcuGelJZbLpVE+Xo9qTncpJdlbOQ3gphDdPh5dyZei8rlyyM5LOdSciuezQlPfJN1+T0x6yjdDskG++Jqe1LNBtM67ULPAMT6Z8eIKYqqDpW6hfzuqYF/ZOULZ3ghbpCcrpCVqECHKIYJGOIOdKxaJ0PNlZkev9HbqoJJqc96lyESYyh5W/2OPjKk95V095TbHsW6IjXOtK9ZLiUlxbKFYkUsnOjBudEWiGhqEtUD3jhhnS2hbOL4jupbGI+2i2trlq+QNQA2pCLagNDUJD0GZoGNoCbYX29d8Ona414IeCGwA3AG4A3AC4AXAD4CJcC+FaCNdCuFYA3MA0KPgB8A3wDfCxny0DfAN8A3wDfAN8A3wDfAN8A3wDfAN8A3wDfBN8E3wTfBN8E3wTfOxxC9NomeCb4JvgY4NbJvgm+Cb4JvgW+Bb4FvgW+Bb42PqWBb4FvgW+Bb4FvgW+Bb4FvgW+Bb4Nrg2uDa4Nrg0uEoBlg2uDa4Nrg2uDa4Nrg2eDF0a8YXBbUN+C+nb02x72Le5MR/LbfZWWxXobrnKlanEs7qSdTDxTtaqv1Pue4Q/4fd3xpJuSnGgqGdPWkB9qaW3Gc3MAakBNqDWoPZdO6QftouFvdYfQCOitkdegViNY5WSy+VMk68SqIul0alXCWZatdEu5nmpX0/HOrqyujKVWJXVpaSrbVYVmsaTuLIxOwyFoM9QdZ8PQ+94w9H40kGYM2++HBqAG1IRaUBsahIagzdC+/lqgrdA26DRoO3S61gD4AfAD4GOw7AD48NvG4NkB8APg98UTAD8AfgD8APgB8APgG+Ab4BvgG+Ab4BvgG+Ab4BvgG+Ab4BvgG+Ab4BvgG+Cb4Jvgm+Cb4Jvgm+Cb4Jvgm+Cb4Jvgm+Cb4Jvgm+Cb4FvgW+Bb4FvgW+Bb4FvgW+Bb4FvgW+Bb4FvgW+Bb4Fvg2+Db4Nvg2+Db4Nvg2+Db4Nvg2+Db4Nvg2+Db4Nvg2+AHwQ+CHwQ/CH4Q/CD4QfCD4AfBD4IfBD8IfhD8IPhB8IPgI8PYIfBD4IfAR+axQ+CHwA+BHwI/BH4I/BD4IfBD4IfAD+X5PUsTqegK/YyMZiOj2fmMFkt668FvBr85VOGtBb0Z9ObWysQybz3ozaA3T69MZ3V9IpvpisScCve3MrZCq9sqjNjDYCPH2chxNnKcjRxnh0EPI/YwYg+DHgY97I59u1/nvnZ8DOU1ADW0tsLeCntrn91s6lqTvzInOyD5S3s+iaenJuKdkRGZRCTT1eH+uoZR2hDJ5FvEMyu0re/J025CwslkOpzV0fz1rqOkf91vNJW/LXY4K3ORhGsYWwovcaLe7cv98XQxaiB3yxuN1m70YkobalORPxKuFi3Di6+6z7V9rhVNDbqX8lhGe/vy2Bsj3T1OOpP/HOoolNyKIflbf0f+n/swJl8TT8U6SkR70N9U7V4LpkYjGWdo3xXAfarRXwtuudq9LbjF4cUrva5yvz100f2gcIvDCnd697Gq9+tCt3E/SdziSO/nhbevTCShv0pNIzgE6hpHIMpCpA2exeVZOpMGGKayAWvwDK1ngIf1JHKZjt4fPV79113tAEuh1yWvYag78X1O1vV57W0ysq9JoVmjJ7gBFm956HUDrfNhhXXuPlZEpuZHTS3Ysa1Br7DydVbv9pj/QF3hZN2rFRzWlt7rV4FX22t0Skyj+t52iu/We98tODKmz9l+rLHapZW53qtfqnQvjiqtc22D1zrpVG9UyCvFWKaUed0xYGy1fTvLFY0pwF2eHoKCzbNVRngGUb9ZWC3FJdO7YA2/FdI+JXPdS/OrLt6Z7Biw6LZq+vuqknkrgv3/r9/yfuqw8UvGshi4N/PoHVGey4qL0DtDOsMV/RquHS3Me61+9r5SSILFt/Re8DaaWDgP+hX0Xsk43XHdd6HkTQj9cjPC9+RiWDw7uymXjOWHLJpKOx3FojdblC+5es9hUQx6VPGwKdjGle3WkmjrPEdM4Y26Yi/lwPLoxpdbS3pv/LuKphJTaVXJSTVwVf/DbGx5lfesHcA2zvU7konG9f8klibV8mGt9xg8w1QcNk+6Kb2OeJB13hkr5DHP+BVOlt6l3j/60b3mAa4NE8tDKRY93DJj09+3nzRgVakrf2NvdFGxVCJRtllHeCp03AM0anAbuSmt/8FSSHIj+zVoQoDYrSU7vqRKz+WADXU2KR7jo4vPpR32NzV657VklErvQsXV5Bk9z3XMY/WsmgZ0XDZJtd5Jyno60Fck702wbMVN8KSa3hVVlm6GF1eZ56AttQ0rLFAcz2hSNI3xHAeFw1tv1LIanJp6U3qPzPJjxNtlTyTtJAuvNXpqSg4Z7+FWFubkAavKOq73NPLkeY/Vc5R7GxcHoq54CSwaJ7krxtO+f7IY7V1UHvv/AH8CengAAAEAAf//AA8AAQAAAAwAAAAiAAAAAgADAAECnQABAp4CngACAp8DOAABAAQAAAACAAAAAHicnV0NXBTnmX+Z/WDZnV0+RURcdxdERERE/ELkU1ERERFREREXmstRz3qcl7NcznKel+a8xHom8XLGM4Ra41FrrLGJZ4211BqllkNrbMIZYz3PWEItpcTm5/lLb3bed3Zm3nnmnbHtL/PsO89/nv/z9b4zszOsKAIh5ERLIrYgy8LyylqU0Pz1ts0osHnTti0oB1kFLfrjHxEniAjmiGvetHkb8rR/pe1rET5hzzfxXmQRcDZkR5HIgaIEJhfikRt5UDSKQbEoDsWjBDQGJaKxKAmNQ8loPEpBE5AXTUQ+5EcBlIrS0CSUjiajDDQFZaKpKAtNQ9louuDdDJSLZqI8NAvNRnPQXDQP5aP5qAAtQIWoCBWjElSKytBCtAiVo8VoCVqKKtAyVImWoyq0AlWjlagGrUK1aDWqQ2vQWrQO1aP1qAFtQI1oI2pCm1AQNaMW9BX0FPoz9DT6c9SKvoo2o79AW9DX0Fb0l6gN/RXahv4aPYP+Bm1HX0ft6G/Rs+jv0A70DdSB/h7tRP+AdqF/RM+J2fgm+kPEcMSXnIfzcblcMVfFtXBtXAe3hzvEHefOcX3cLe4B99jCW5ItmZZ8S4WlwbLZ0mHZYzloOWE5b+m33LaMWDlrnDVgzbWWWmutrdZ26wvWA9YT1h7rNetd66jNakuwBWy5tlJbne0p2zO252z7bYdtJ209tn7bbduwHdlj7AF7rr3UXmNvsm+1d9j32TvtJ+zn7H32m/ZB+xeRjsikyPTIvMjSyJrIYGRb5K7IlyMPR56KvBB5PfJO5IjD6khwBBy5jlJHjSPoaHPsdLzsOOw45ehx9DtuOYYcj6KcUUlR6VGzoxZG1UQFo7ZG7YjaE3UwqjvqdNTFqOtRd6JGnMjpcaY4s5z5zlJnhbPGWe8MOludbc52507nbuc+5wFnl7PbedJ5xtnj7HVecw447zgHnSPORy7O5XTFuZJdAVemK9eV7yp1VbhqXPWuoKvV1eZqd+107Xbtcx1wdbm6XSddZ1w9rl7XNdeA645r0DXiesRzvJOP45P5AJ/J5/L5fClfwdfw9XyQb+Xb+HZ+J7+b38cf4Lv4bv4kf4bv4Xv5a/wAf4cf5Ef4R27O7XTHuZPdAXemO9ed7y51V7hr3PXuoLvV3eZud+9073bvcx9wd7m73SeFvkfIKngwxHv4LHFk4wv4p/i9/Dv8TXFs50fcae469y73SfdtvMf92JPtafLs8Zz1DGIL0fbojOiK6KfJaHv0geiz0QPiyBE9GMPHZMdUx7TFvBzzTsx1jIm5GfNlrC+2EI9iq2K3xu6LPUFG52PvxFnjAtifuNy4hrjn4o7FXSPj+/HO+Kz4qvjNeBzfHn84vi9+NCEZjxNyEmoStiXsTzhFxj0Jd8fYx2SMKcfjMbVjdow5OqZvzChmS3QmZiVWJRJric8mHknsT3w4NgVrx+aOrR37zNiXsXbs4bEXx95PciZl4HHS7KSmpN1JJ5JukPHQuMRxxeOeGvcCztS4rnHXk+3JecnB5L0YkXws+cZ4ND59fKU4jhwfHL9r/JHxl8YPpvApmZgzJS+lLmV7yn4yOpJyKWVwggPjJyRNyJ/QMGHHhM4JPRNwPWwTBr0J3kJvi3c3Hns7vb3e0YnJE4vxeGLdxI6J3RP7Jz7EXvl4X6Fvq++Q74rvEUb4E/3F/lb/Xv87ZHzF/yAQE8gNVONxoDHQEegKXAjcIePh1OTUhamtqSSq1COp/amP0nxpOMf2tIa0HWmdaT1pdydxuAsmeSblTKqZtG3S/kmnJw1M+gIfl47SM9Nr09vTO/Fx6e+kf5j+aHLK5MLJDTj6yU9P3j352ORebGXywOTHGd6M4oxgxq6MIxkX8FEZVzIeTImZkjuldkobtjtlx5SjU65NGc5MxIjMrMy6zI7M7sz+TJwD21Tn1LypjVN3Te0m4/NTB7MSsvKzmvA465msV7POZN3MwhmyTnNMy5xWOa0Va6e1Tzs8rW/aaDbpueyc7PrsXdnd2f24Stn3pjumZ06vnL5l+t7pxzFm+unpA9Mf53hzCvA4pyJnW86hnAs5eA7ZZqAZmTNqZ7TP6CTjd2Z8OONRbkpuPvYgd0lua+4Lud1kdCb3Zu6XM3GX2mZmzayauXXmvplkBs08N/N2Hpfnw9q8nLz6vF153Xn9ZHxvlmNW5qzKWSSaWdtndc26MmtkdhIez86evXb2ztlHZ/fh7M2+O8c7p3rOjjnH5uA5bZvzcG7G3Jq52+cewuO5p+bemxczb/Y8XDXbvLZ5h+f1zRvNx/mx5ufk1+c/m38Aa/O786/kP5gfMz8bj+cXzG+c3zG/a/55zDe/b/5wQVxBXkFdwTZ8fEFHQVfBhQLSfQUjC1IWlC/YvGAf1i44uqB3wVChE2sLkwsLChsLOwqxb9bCY4V9hcNFHjwq8hYVFwWLOshob9HJoutFI/jIYq44rbi8+Oni58j45eJzxfdLPCV5GF1SUbK5ZE/JMTI6W3KrFJV68ag0u7S6tK2UzIfSztLe0tGy5DIyA8vqyjrKusv6y0j3LeQXzl7YtPC5hdiWfWHPwtFF6YsqF7UvOooRiy4selCeVF5Y3oLH5e3lh8v7ykcXk45bnLO4fvGuxd2LSU0X31sSsyR/SdMS4vuSg0suLhlemrgUd5t1ac3SbUv3LyVr4tILS+9VOCoyK5Zg/oq6iu0VByrOVtyqeIwRyxzLcpc1LNu5DPtjXXZu2e1KrpJ0VGVOZX3lrsruSsJeeW95zPL85U3LCfvyg8svLh+uSqwivV5VU/Vs1ZGqK1Uk0yscK3JXNKzYuYJEu+LcivvVcdVzqxuxP9Xbqg9VX6oeWZm0Ep8hbCtrVm5buX/l6ZVkpV15t8ZTM7emsWYX9q/mQM3Zmls12HvrKn5V9qrqVVvIaMeqzlU9q26T0XBtXG1ebQ0e1QZrd9UeqcUria32eu2XqzNWV60mfbd69+pjq/tWD5HR4zpvXXEd9tFWt6Xu1bqeuvtrSGetSVtTvubpNc+R0f41p9cMrCHr3FrH2ty1DWt3riXZXHtu7e113DqcTeu6nHU167atw91sW9e17sq6h/Up9aVYW7+2vr3+YP0ZMuqtH1rvWY/P1db1Besb13esJ52+/vj6/vUjDTF41OBrKG1oadhJRvsaTjXcaMDnPNsG64b0DUs2tG54Hms37N9wesPABuyttdHRmNlY2dhKRu2NBxvPNeKrAVvj0MbEjcUbn9qIz3DWjV0bL2y812RFoev0iKYEIpOITCHSR2QakRlEZhFZSmQ1kY1EPk3kZiK3EtlO5A4iXyByP5EHiDxM5FEijxF5hshzRPYQeZHI60TeIXKIyC+x3MQRaSfSSSSJe1MmkXlEziWSxLmpksh6IluI3ELkdiKfJbKDyF1Ekjg3dRLZTeRxIi8QeY3IG0TeJnKQyFEiSVxBEleQxBUkcQU9RJL4gqSuwWwic4ksILKcyFoiW4kk8QT3ELmPyINEkniCpG5BElfwPJG9RPYReZfIESybEZGkH5uJ383E3+Z0Ikl9mkl9mguJJHVpJv3XHCSS9F0z6btm0nfNzxNJ+q/5EJEniCR91XyFyAEih7FsIXluIfOmhfjXQvxqIXlsIXlseYrIbUQSvpZjwn1shHCXGiHctUYId6ihPEQI97TKrUXcGyPc8ybqIiJQqMJxCOfxeVnLXSXHxxEMQv+kcywn3B2ngfaVfsQJMskQyQl33KG1I4F4tFvlkeRXhIAKYSIQXmf+mUaFWRPCFlUoVRTYlkVApQv38vreqe1aRbvmjrAI+Q+gDLFiOK4XgLik6DgRjauL180XYTTxP0XEcoQBQFO+Jyr8UaFN5majYtuk2Jbo5gbCSkdoc/OijDCdmyZNbkoYuVGgKd+h3BSBkWpnnxKxCZx9NEI9+14k++HZ96LmWJq/SOUtxK+OhObfo8oNzb+Hss7m9zH4fSYR0YYIjyHCYYiI18mDz0QefGAe1PMD4lfPB5r/W8w6fIuyzu7DEkYfynMQ4i/R4d9LWdfyx6u2ED+NUPPvlbUA/7/oHKs8C8Xr+kGfhWAkfRbap/IoQucs9BKNCrMqz0IvgYzsLPoZCH8YoZdnv8KGMs8vyVogzy9p+Nl5VvsB59lPIek8v6zySC/Pr9CoMKsyz6/oZICVZw8D4Qkj9PLsUdhQ5vkVWQvk+RVDfo9iC88nD1JGYoTQerhf1gIe7qf4oU7wAFu4E2Ak3Qn/qsqZXie8SqPCrMpOeBVk1OY5WrWFskgj1Fl8VdYCWfw3nWOVWYzW9YPOIoyks3hA5ZFeFl+jUWFWZRZfAxm1WYxTbFPBLEo6CaHO4muyFsjia5R1Nn8cg98swqfx8KCsBTz8d8o6VOc4XT/oOsNIus6HVB7p1fl1GhVmVdb5dZDRXBQ+E1HIV390FK/L+ieKwmcqCp/u/YxPsbWJ21iy1bufgbDSEdr7mddlhIn7mU4YLdrW3s90yp6EcUrf1fcznYxIoQr7NNtYnQqrMRKSrvAbQGzaCr9B94GiEnKF3wB9064HkPfq2U57zUbA9y1KBHzNr0QENGtKlyoz9JrS9URRBhj8ASZ/wAR/wJDfy+D3hhEQv9cEv9eQH74ro2uoh/AbVtlnEqG94v62rAWiPKyJwcyM1FtzYSQ9I7+j8khvRh6hUeCaewRkNIoC5zvAjELCSEg6iiOy3jAKvyKKgEEUsm9GUcQi4ygkjF4Ub5qK4k3ZFiOKN0HfjM9/Taot+/zXBB6hPf+9KSNMnP+OwmjyXQd9/tOgKd/V57+jjEiNKqy2C1eYtklX+D+A2LQV7gZi0la4G/SNvTKyzkxNJhHac8d3Vf7Sq9p3KX6th8orKBaCFUMsovOg9PAY08NjGn5tJyh9lGcw1AlqjN5cPybrGZ3wPRoFzvXvgb4ZRcFeseB46SiOm4riuGyLEcVx0Dd2t7AQTSomqFsChjaiNdnEcb+lipvup7cMPaS7Q+vhkyHgb/+UCPiqUTrayNN4nTycYObh+5osazsyAGzhjoSRdEeeVHmk15Fv0yiwI98GGdnVZFXCbxLBqjdGaL+TeFvWApV4G6k9ZMcAX79LOgkB8etdv79NWWfzw08VsC7aEOExRDgMEfFhhF4eShQ2tHkoMZEH2LpPUWXj8zncS0obxghtL52StUAMpwB+1r2H7Ie5+xntXdMPKH/o2fwD+WjG84AfMJnYdyfsOP1/EhJavd5RRaoX77s0Coz33T+pBuqzJusqV38NflfWG0bRJKPANfhd0DcoijLFtol8hqNQYyQkHEXZE0dRBkRxGvRNO7dtii28Tks6/blNI9Rz6bSqd+i5fdokv7TV55cRav7/lLUA/xmdY5V1VueINefUGAlJ1/mMKiN6df4hjQLn3A+ZFVBGofTKhlhR+EGbdBRnTUVxVpV9vSjeA33T9gK9mukhWNdOGKFeddT9pLQBd7yWRdlx78laoOPeQ+ZiYM24J0NoPTynqh3t4Y8o60bdxJ4TMJLupvMqj/S66cc0CuymH4OMRlF4TUThRewoekxF0SPbYkTxE9A3bbfQPukh1PGpu0VrQ9ktF5jdcsGkh3gLzzhJB3frBVlrwA/PJyU//B26pJMQEL/PBD/8Lb9XtdXnN4vQVuinshbw8CJlXevhRNUW4qcRav6Lshbgf1/nWOVM9AJbeCbCSHomvq/KiN5MvESjwqzKmXgJZGTXGX4PQdKZQ2hnwiVZC+T5EsVvlGeZRT/PEkZC0nm+JOsZeb6s8lvJqszzZWYG2N2it26rMXrr9mVZbxiF0bp9GfRNeo5gFZ8MZKM84l+Tzlb9zZT2eYj+cdDzkMtAlaQotc9DLsv2VM9DAoo45OchGjR1baV+HqJAa/yGnxUZ5YauOGybrngvkBFtxXs1sUF92wsymo1G/RaAcTTQWw69sqUnigZ6y0EbTewTRCPPMXPRQLOxV7b0RNFAs1Ebjd61oH6H6b9Rr12hfyYfD6zQP9Mcy3qTGn4zBOoafYSeh3rvffyM4md7CF9NKjuBlUO5VyAPpfqzPISvN9VdZlRD1hvtrL9sUNrQfit7RdYCMfzckJ9+x0vLTyPU/D9nVrlP51j2kzu9NQpG0rP6v1Qe6c3qfhoFrlH9IKM2i6mqLZRFGqHOYr+sBbJ4VXMsix++rpd05hDauXJV1hp4CF+zKj2EZ7OkY/HrzdWrlHU2P/xmrKSTEBC/3puxVynrWn6nagvx0wiI36nDf01zLIsfzr+kM4fQrvfXmBW6RvFDK4ET2MIrAYykV4JfqHKmtxJcp1FhVuVKcB1kZL+jDWdR0sF9fp2ZxeuUdTY/vJ5LOng9vy5rDfjhlbBMtYX4aQTEX6bD/4HOsXrftcl/4wN1kRojIeku+kBVEb0u+kC2xbhKvAH6Zu4NPPbbkLGqo7RR3JD1jChuqKqvZFVG8UvQN3PPG9lR+BE7ig9NRfEhXTEwio9A34zfI1T2l/59M4SVjqCj+kjWM++XP6LjYt4va9CUz+r7ZW02/ArbZr6X17tPNpeDATAHdGUHNDFB98kDoG9GUbDv9tUYvSj+21QUN2kUGMVN0DejN43YtQiANukoPjYVxcemanEL9M3c+1J667Y2XmjdvkX7B0ah8I+xbt8CfTMXhd7djBojIeEojO5mtFFAdzPaKFjvzqm9h94U0ruG+ETlM30O/8SQX7naBkF+SWcOob1W/UTW6noo82s9DFL8eghWDLQNNkIbw21mDL/SeMg+O8ss+mdnCaO3bvxK1jN6VeEZY934lU4VWd9fwM/c6CsjdRbvyFogi3co60bfX/hUfsDfX/goJJ3F/1F5pJfFuzQKvMa5y8yA3pmQHYUaoxfF/5qK4p6pKO6Bvpm7amb/VYPyfWrorxruyXrDKJRXzdBfNdwDfTN31cyOQsLoRfGpqSg+lW0xovgU9M3oe26MZD9JkTB6q8unsv6JooBWl/ugb0ZRxCLjKOj37+ko7puK4r6mo4yi0H4Lz17pg8yOgs5Nxkj9eBWrPRjvr2lUmFUZ76/BKNjnBPjMGou08ekjtOfeQZW/9FljkOKHKhLUbPX6So3Ry/OgrGfk+TNNnqG++gz0jX0VBD9/ob1WZ/Ezlc90Fj97In74uzZJJyEgfr8Jfvi7NiU/fJ0s6SQExK93nfwZZZ3Nn8bgTwsjIP40E/xpIH+pYgvnX9LB+R9i5n+Ism60rjnCnaK/WkkYvVk0JOsZs2hItsWYRUOgb+y7Hfg3vZQ2HJos/kblDZ3F31DWtfwOpM2emp/OGsSvN4sfaOJnVzFa5QdcxWjKH7qKD0xV8YFsi1HFB6Bv7CrCv7CjtKH9hZ3fqryhs/hbyjr7F37gKtJZg/j1qjisiZ9dRY+JKnoof+gqDpuq4rBsi1HFYdA3dhXh38NS2tD+2tTvVN7QWfwdZZ39e1hwFemsQfx6VRzRxM+uYrzKD7iK8ZQ/dBVHTFVxRLbFqOII6Bu7ivBvhiltaH+l7/cqb+gs/p6yzv7dNbiKdNYgfr0qjhrGr/yt040gv6STEGr+UVmryx+pOJbFX8TgLwojIP4iE/xFhvw8g58PIyB+3gQ/b8gPvw0i6SQExJ9qgh9+G0TJDz+DlXTmEHoZ0ntKO0rxsz2E/9ZK0kkIiF/vb61GKetsfvhtGEknISB+vbdhRinrbH74ylnSSQiI32+CX+/KWd0jypkC/5q1hJGQ9Dr/uaxnrPOfq/pGyapc5z8HfdNmsUSxhX8TvAQpY6Wz+FDWAll8SFmwc/hd/wTx8y3F/l7F54919t8mnznBQxt3Vfj/x8J/oeftNmFvkcAT+jewuFCk3C8FTeg9iNAz6CJhP977C2HvB+LeEvFfzOKQE3Hc62IeOeF80CTy4L+4ihc/v0U+Y+RV7iZB+kWt9L4Ott4Z8km07hER0jFviMd4iEX83bQnrP0p0UaL2h5Riz//gnzG1kPx3hetRwsZThURnSIiTvz8I8Vn6fft8JFd3FWLTzwy9C+Y+RT2sLc2Yb+P+PMdkgu8x4Os3JvckIAMXf1Zhb1eYX9AkRmf+Pn7is/SU3CZ5Sipg184EtsU9lomcj8nNkMWm8L5wPw+UouvitYgrbTnsmIPZjxsSSfRau3Gij7gPYc0e46E94S45V9hkaLFnz8kn3Esv+WOCUd+KMbiFWMsCdv7LvFN7oXH4T0hS9LzabmjP7NMFj21kXyVKeLqDtfLL85pKde2sPXjonWbgu+qZs+gZs/74h4/ie55JK2Ldu6E4vO3gf2She+JFrwaLa71b7hvcw9Ifnxhnp8gvLZLNkaIjYlkzz3iO96DM/ARNyBmwItC/9KelLVr3BUSqY1UIFXMmsBumWKZyvWKulCn+UlHhDQZlkzuEqUJ+VWt6riL4b7B2vOiNlb8/IXi83mCkrx6i7vNfcL9mnjlFOZdyLNUKVrLbBHvJBnDf/fsDEf0ufDfKPeQHBuataGKS1n4A+mDeNXeL8je2HB3SbMSc/5feO74kTw3v1TNTVyJP6r6QXrire4oBd4Sodgj2BX+x4V9Cc95C5710Yo5HxTt41/2CUo+WWzkWIyQOOzhVSl0zKeqyK4q1qyQ9n5YS9Ya4XhnmNcvdYC4n7e4RI1P2RvCfrfwn4ccE9ofRPQKJHsca0kNrzdBXENLwOK3xFnSSP1xHtIkrcg8yRJPtA7CXkrylyBox4gWHWIWlHEkij45hHOAL5zvscL+JITPDDR+nIj3qPDJwv7xCJ+naPwEgo9X4FOE/V7ScXhvMnJa8oS904X/ZlpmWXIs0ywzLFliD4fOxbyAxP1eJHToxnCmckltQ4gynFFLNsloJCr5fzyMhKMAAAB4nGNgZGBg4GKwYbBjYHZx8wlhEEmuLMphkMtJLMlj0GBgAcoy/P8PJLCxgAAAXgQLfAAAAAAAAQAAAADZ8v4MAAAAANphYvYAAAAA2mGWQA==")
4 | format("woff");
5 | font-weight: 400;
6 | font-style: normal;
7 | }
8 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /* ./src/index.css */
2 | @tailwind base;
3 | @tailwind components;
4 | @tailwind utilities;
5 |
6 | body {
7 | touch-action: none;
8 | }
9 |
10 | canvas {
11 | touch-action: none;
12 | display: block;
13 | }
14 |
15 | html {
16 | }
17 |
18 | ul {
19 | list-style: disc;
20 | padding-left: 24px;
21 | }
22 | a {
23 | text-decoration: underline;
24 | }
25 |
26 | .action-button:hover {
27 | background: rgba(0, 0, 0, 0.125);
28 | }
29 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./font.css";
4 | import "./index.css";
5 | import App from "./App";
6 | import reportWebVitals from "./reportWebVitals";
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById("root")
13 | );
14 |
15 | // If you want to start measuring performance in your app, pass a function
16 | // to log results (for example: reportWebVitals(console.log))
17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
18 | reportWebVitals();
19 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: [],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {},
6 | },
7 | variants: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------