@unoverse-platform/studio 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/local/plugin.ts CHANGED
@@ -16,12 +16,53 @@
16
16
  * See docs/architecture/LOCAL_STUDIO.md.
17
17
  */
18
18
  import { createRequire } from "node:module";
19
+ import { fileURLToPath } from "node:url";
19
20
  import { resolve } from "path";
20
21
  import { readdirSync, existsSync, readFileSync, statSync } from "fs";
21
22
  import type { Plugin, ViteDevServer } from "vite";
22
23
  import { findProjectRoot, loadCatalog } from "./catalog.js";
23
24
  import { loadLocalNodes, type LocalNodes } from "./nodes.js";
24
25
  import { credentialsFromEnv, keyPresence, readEnv } from "./keys.js";
26
+ import { readSource, saveSource, sourceFiles, writableRoots } from "./source.js";
27
+ import { atomNames, vocabulary } from "./authoring.js";
28
+ import { buildTools, handleMcp, mintPairingCode } from "./mcp.js";
29
+
30
+ /**
31
+ * A definition URI's home on disk: a component's folder, or a template's.
32
+ *
33
+ * Resolution is the loaders', not this file's: `componentFolder` already knows a
34
+ * component lives in either tier and that names are unique across them. Templates are
35
+ * manifest-only under their own project, which `projectDir` locates.
36
+ */
37
+ function definitionHome(
38
+ uri: string,
39
+ c: { componentFolder: (ref: string) => string | null; projectDir: (n: string) => string; listOrgs: () => string[] },
40
+ projectRoot: string,
41
+ ): string | null {
42
+ const ref = uri.replace(/^unoverse:\/\/(components|atoms|apps|templates)\//, "");
43
+ if (/\/(apps|templates)\//.test(uri)) {
44
+ const i = ref.indexOf("/");
45
+ const [org, name] = i === -1 ? [null, ref] : [ref.slice(0, i), ref.slice(i + 1)];
46
+ const orgs = org ? [org] : c.listOrgs();
47
+ for (const o of orgs) {
48
+ const dir = resolve(c.projectDir(o), "templates", name.toLowerCase());
49
+ if (existsSync(dir)) return dir;
50
+ }
51
+ return null;
52
+ }
53
+ if (/\/atoms\//.test(uri)) {
54
+ const flat = resolve(projectRoot, "rx/atoms", `${ref.toLowerCase()}.json`);
55
+ return existsSync(flat) ? flat : null;
56
+ }
57
+ const folder = c.componentFolder(ref);
58
+ if (folder) return folder;
59
+ // A flat, folderless component: `rx/components/<name>.json` in either tier.
60
+ for (const dir of [resolve(projectRoot, "rx/marketplace/components"), resolve(projectRoot, "rx/components")]) {
61
+ const flat = resolve(dir, `${ref.toLowerCase()}.json`);
62
+ if (existsSync(flat)) return flat;
63
+ }
64
+ return null;
65
+ }
25
66
 
26
67
  /** JSON response helper matching the server's content type. */
27
68
  function json(res: import("http").ServerResponse, body: unknown, status = 200): void {
@@ -102,9 +143,27 @@ function readMarkdownLibrary(dir: string): MarkdownEntry[] {
102
143
  return out.sort((a, b) => a.name.localeCompare(b.name));
103
144
  }
104
145
 
146
+ /**
147
+ * Files this server has just written, so the watcher can tell Studio's own save apart from
148
+ * the developer's other editor. The window only has to outlast the filesystem event, which
149
+ * arrives in milliseconds; a stale entry costs one skipped reload and nothing else.
150
+ */
151
+ const recentSelfWrites = new Map<string, number>();
152
+ const SELF_WRITE_WINDOW_MS = 2000;
153
+
105
154
  export function localStudioHost(): Plugin {
106
155
  let projectRoot: string;
107
156
  let baseSrc: string;
157
+ /** The node the developer last picked in the preview, for an agent to read. */
158
+ let selection: Record<string, unknown> | null = null;
159
+ /** The agent tool surface, built on first use so the catalog is loaded by then. */
160
+ let mcpTools: ReturnType<typeof buildTools> | null = null;
161
+ /** This package's own root, for the vendored authoring skill. */
162
+ // Derived from this module's own URL, never RESOLVED: `createRequire().resolve("./plugin.js")`
163
+ // throws here, because Vite loads the TypeScript source and no sibling .js exists. Throwing
164
+ // at plugin construction kills the config load, which stops the dev server restarting at
165
+ // all, and the symptom is every new route 404ing with no error in the browser.
166
+ const appRoot = resolve(fileURLToPath(import.meta.url), "../..");
108
167
 
109
168
  return {
110
169
  name: "local-studio-host",
@@ -138,6 +197,22 @@ export function localStudioHost(): Plugin {
138
197
 
139
198
  server.config.logger.info(`\n Studio reading: ${resolve(projectRoot, "rx")}\n`);
140
199
 
200
+ /**
201
+ * WATCH THE PROJECT EXPLICITLY. Vite watches its own root, which is this package,
202
+ * and the developer's project is somewhere else entirely: another folder in this
203
+ * monorepo, or the parent of node_modules once Studio is installed. So the
204
+ * `handleHotUpdate` hook below never saw a definition change and the "edit in your
205
+ * IDE, Studio updates" it promised did not happen (verified 2026-08-01: the watcher
206
+ * held 19 directories, all under packages/studio, and a touch of a real rx/ file
207
+ * raised no event).
208
+ *
209
+ * Adding the paths puts them in the same watcher, so the existing hook fires.
210
+ */
211
+ for (const dir of ["rx", "prompts"]) {
212
+ const full = resolve(projectRoot, dir);
213
+ if (existsSync(full)) server.watcher.add(full);
214
+ }
215
+
141
216
  server.middlewares.use(async (req, res, next) => {
142
217
  const url = new URL(req.url ?? "/", "http://local");
143
218
  const path = url.pathname;
@@ -146,7 +221,7 @@ export function localStudioHost(): Plugin {
146
221
  // 200. That has now cost three separate bugs (the /api allowlist, the dev proxy for
147
222
  // /.well-known and /publish), each of which looked like a missing route rather than
148
223
  // a filtered one.
149
- if (!/^\/(dev|local|skills|prompt-blocks|registry|nodes|keys|execute|publish)\b/.test(path)) return next();
224
+ if (!/^\/(dev|local|mcp|skills|prompt-blocks|registry|nodes|keys|execute|publish)\b/.test(path)) return next();
150
225
 
151
226
  /** Read a JSON request body. */
152
227
  const body = async (): Promise<Record<string, unknown>> => {
@@ -298,6 +373,166 @@ export function localStudioHost(): Plugin {
298
373
  return d ? json(res, d) : json(res, { error: `definition not found: ${uri}` }, 404);
299
374
  }
300
375
 
376
+ /**
377
+ * SOURCE VIEW (read-only at this step).
378
+ *
379
+ * The screens render a definition; this serves the FILES behind it, so a
380
+ * developer can read what they are looking at without leaving Studio. The
381
+ * writable flag on every file comes from source.ts, the single place that
382
+ * decides it, so the pane never invents its own idea of what may be edited.
383
+ *
384
+ * GET /local/source?uri=unoverse://components/<name> the file list
385
+ * GET /local/source?path=<abs> a skill or block
386
+ * GET /local/source/file?path=<abs> one file's text
387
+ */
388
+ case path === "/local/source": {
389
+ const roots = writableRoots(projectRoot, c);
390
+ const direct = url.searchParams.get("path");
391
+ if (direct) return json(res, { files: sourceFiles(direct, roots, projectRoot) });
392
+
393
+ const uri = url.searchParams.get("uri") ?? "";
394
+ const home = definitionHome(uri, c, projectRoot);
395
+ if (!home) return json(res, { error: `no source for ${uri}` }, 404);
396
+ return json(res, { home, files: sourceFiles(home, roots, projectRoot) });
397
+ }
398
+
399
+ case path === "/local/source/file" && req.method !== "POST": {
400
+ const p = url.searchParams.get("path") ?? "";
401
+ const found = readSource(p, writableRoots(projectRoot, c), projectRoot);
402
+ return found ? json(res, found) : json(res, { error: "not readable" }, 404);
403
+ }
404
+
405
+ /**
406
+ * THE WRITE CHOKEPOINT. Every write local Studio performs goes through here:
407
+ * the developer typing in the source pane, and the connected agent calling
408
+ * saveDefinition. One gate, one rule set, no second implementation.
409
+ *
410
+ * It refuses installed assets outright and rolls back a save that fails lint
411
+ * (source.ts saveSource). The linter is the SAME library publish and the CLI
412
+ * use, loaded the same way, so the three cannot disagree about a rule.
413
+ */
414
+ case path === "/local/source/file" && req.method === "POST": {
415
+ const b = await body();
416
+ const p = String(b.path ?? "");
417
+ if (!p || typeof b.text !== "string") return json(res, { error: "path and text are required" }, 400);
418
+
419
+ type LintModule = { lintDefinitions(rxHome: string): { problems: { level?: string; file?: string }[] } };
420
+ const lintMod = (await ssr(`${baseSrc}/lint/rx/index.mjs`)) as unknown as LintModule;
421
+
422
+ // Marked BEFORE the write, so the filesystem event can never beat the mark.
423
+ recentSelfWrites.set(resolve(p), Date.now());
424
+ const result = await saveSource(
425
+ p,
426
+ b.text as string,
427
+ writableRoots(projectRoot, c),
428
+ projectRoot,
429
+ (rxHome) => lintMod.lintDefinitions(rxHome),
430
+ );
431
+ return json(res, result, result.saved ? 200 : result.error === "lint errors" ? 422 : 403);
432
+ }
433
+
434
+ /**
435
+ * DIAGNOSTICS FOR UNSAVED TEXT, from the one linter.
436
+ *
437
+ * The editor posts what the developer has typed and the shared library judges
438
+ * it through an overlay, so a squiggle in the pane is the same rule publish
439
+ * and CI apply. Findings for other files come back separately: a developer
440
+ * fixing this file should not be shouted at about that one.
441
+ */
442
+ case path === "/local/lint/draft": {
443
+ const b = await body();
444
+ const p = String(b.path ?? "");
445
+ if (!p || typeof b.text !== "string") return json(res, { error: "path and text are required" }, 400);
446
+
447
+ type LintModule = {
448
+ lintDefinitions(
449
+ rxHome: string,
450
+ options?: { overlay?: Record<string, string> },
451
+ ): { problems: { level?: string; file?: string; line?: number; msg?: string }[] };
452
+ };
453
+ const lintMod = (await ssr(`${baseSrc}/lint/rx/index.mjs`)) as unknown as LintModule;
454
+ const abs = resolve(p);
455
+ const { problems } = lintMod.lintDefinitions(resolve(projectRoot, "rx"), { overlay: { [abs]: b.text as string } });
456
+ return json(res, {
457
+ findings: problems.filter((f) => f.file && resolve(f.file) === abs),
458
+ elsewhere: problems.filter((f) => !f.file || resolve(f.file) !== abs).length,
459
+ });
460
+ }
461
+
462
+ /**
463
+ * THE CLOSED VOCABULARY, for completion. Data, not rules: the same sets the
464
+ * linter enforces, plus this project's own token, atom and component names.
465
+ */
466
+ case path === "/local/vocabulary": {
467
+ const vocabMod = (await ssr(`${baseSrc}/lint/rx/vocabulary.mjs`)) as unknown as Parameters<typeof vocabulary>[0];
468
+ const theme = c.resolveTheme(url.searchParams.get("theme") ?? "light");
469
+ return json(
470
+ res,
471
+ vocabulary(vocabMod, theme, atomNames(projectRoot), c.listDefinitions("component") as { name?: string }[]),
472
+ );
473
+ }
474
+
475
+ /**
476
+ * WHAT THE DEVELOPER IS POINTING AT.
477
+ *
478
+ * The editor posts the node it selected; an agent reads it back. "Make this
479
+ * bigger" is only answerable if something outside the developer's head knows
480
+ * what "this" is, and the picker is how they say it: a click on the rendered
481
+ * element resolves to a file, a line and the node's own definition.
482
+ *
483
+ * IN MEMORY, AND LAST WINS. It describes what is on screen right now, so it
484
+ * belongs to the running server and not to disk.
485
+ */
486
+ case path === "/local/selection" && req.method === "POST": {
487
+ selection = { ...(await body()), at: new Date().toISOString() };
488
+ return json(res, { ok: true });
489
+ }
490
+ case path === "/local/selection":
491
+ return json(res, selection ?? { empty: true });
492
+
493
+ /**
494
+ * CONNECT AN AGENT. Mints the code the developer pastes into Claude, the same
495
+ * flow Canvas uses. Ten minutes, single use, burned on redeem.
496
+ */
497
+ case path === "/local/pair" && req.method === "POST":
498
+ return json(res, mintPairingCode());
499
+
500
+ /** The MCP endpoint itself. Same origin, same port, no separate install. */
501
+ case path === "/mcp" && req.method === "POST": {
502
+ mcpTools ??= buildTools(
503
+ {
504
+ projectRoot,
505
+ catalog: async () => (await catalog) as never,
506
+ sourceFiles: (uri) => {
507
+ const home = definitionHome(uri, c, projectRoot);
508
+ return home ? sourceFiles(home, writableRoots(projectRoot, c), projectRoot) : [];
509
+ },
510
+ readSource: (p) => readSource(p, writableRoots(projectRoot, c), projectRoot),
511
+ saveSource: async (p, text) => {
512
+ type LintModule = { lintDefinitions(rxHome: string): { problems: { level?: string; file?: string }[] } };
513
+ const lintMod = (await ssr(`${baseSrc}/lint/rx/index.mjs`)) as unknown as LintModule;
514
+ recentSelfWrites.set(resolve(p), Date.now());
515
+ return saveSource(p, text, writableRoots(projectRoot, c), projectRoot, (rxHome) =>
516
+ lintMod.lintDefinitions(rxHome),
517
+ );
518
+ },
519
+ lintProject: async () => {
520
+ type LintModule = { lintDefinitions(rxHome: string): { problems: unknown[] } };
521
+ const lintMod = (await ssr(`${baseSrc}/lint/rx/index.mjs`)) as unknown as LintModule;
522
+ return lintMod.lintDefinitions(resolve(projectRoot, "rx"));
523
+ },
524
+ selection: () => selection,
525
+ vocabulary: async () => {
526
+ const vocabMod = (await ssr(`${baseSrc}/lint/rx/vocabulary.mjs`)) as unknown as Parameters<typeof vocabulary>[0];
527
+ return vocabulary(vocabMod, c.resolveTheme("light"), atomNames(projectRoot), c.listDefinitions("component") as { name?: string }[]);
528
+ },
529
+ },
530
+ appRoot,
531
+ );
532
+ await handleMcp(req, res, await body(), mcpTools);
533
+ return;
534
+ }
535
+
301
536
  case path === "/skills":
302
537
  return json(res, { skills: readMarkdownLibrary(resolve(projectRoot, "prompts/skills")) });
303
538
 
@@ -379,15 +614,22 @@ export function localStudioHost(): Plugin {
379
614
 
380
615
  /**
381
616
  * Definitions are fetched at runtime, so Vite never sees them in the module graph.
382
- * Watch the developer's rx/ and prompts/ and force a reload on any change: edit in
383
- * your IDE, Studio updates. This is the same watcher the old coupled studio had.
617
+ * Watch the developer's rx/ and prompts/ and reload on any change: edit in your IDE,
618
+ * Studio updates.
619
+ *
620
+ * A SAVE FROM STUDIO'S OWN EDITOR IS NOT AN EXTERNAL EDIT. Reloading the page on it
621
+ * would throw away the cursor, the scroll position, the open file and any unsaved work
622
+ * in another tab, every time the developer pressed Cmd+S. The editor already refreshes
623
+ * the preview itself, so a write this server just performed is skipped here. Anything
624
+ * else, which means the developer's other editor, still reloads.
384
625
  */
385
626
  handleHotUpdate({ server, file }) {
386
627
  const watched = [resolve(projectRoot, "rx"), resolve(projectRoot, "prompts")];
387
- if (watched.some((w) => file.startsWith(w))) {
388
- server.ws.send({ type: "full-reload" });
389
- return [];
390
- }
628
+ if (!watched.some((w) => file.startsWith(w))) return;
629
+ const self = recentSelfWrites.get(resolve(file));
630
+ if (self !== undefined && Date.now() - self < SELF_WRITE_WINDOW_MS) return [];
631
+ server.ws.send({ type: "full-reload" });
632
+ return [];
391
633
  },
392
634
 
393
635
  configResolved() {
@@ -0,0 +1,230 @@
1
+ /**
2
+ * SOURCE ACCESS: which files local Studio may show, and which it may write.
3
+ *
4
+ * Studio previews definitions. The source view shows the file behind one, and the write
5
+ * route (step 3) saves it back. Both ask this module first, so there is ONE answer to
6
+ * "may this be written" and it cannot drift between the human typing in the pane and a
7
+ * connected agent calling saveDefinition.
8
+ *
9
+ * THE WRITABLE SET IS NAMED, NOT INFERRED. A path check alone is wrong in this monorepo:
10
+ * the design-system source lives on disk INSIDE the project root (definitions.ts
11
+ * `marketplaceDir` prefers `rx/marketplace/<kind>` when it exists), so "under projectRoot"
12
+ * would happily let a developer edit the design system. Installed assets are read-only
13
+ * everywhere: an edit to one is invisible to every other machine and lost on the next
14
+ * install, and §Design-system resolution is what keeps the token contract stable.
15
+ *
16
+ * So the writable set is exactly the developer's own work:
17
+ * rx/<project>/** every project listOrgs() reports, via projectDir()
18
+ * prompts/** skills and prompt blocks
19
+ * nodes/** their own node packages
20
+ * Everything else is readable and not writable, the marketplace and anything resolved out
21
+ * of node_modules included.
22
+ *
23
+ * See docs/architecture/LOCAL_STUDIO.md §Authoring in Studio.
24
+ */
25
+ import { resolve, sep, relative } from "path";
26
+ import { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
27
+
28
+ /** The reason a path is not writable. Shown to the developer and returned to the agent. */
29
+ export type ReadOnlyReason = "installed" | "outside-project" | "not-a-file";
30
+
31
+ export interface Writability {
32
+ writable: boolean;
33
+ reason?: ReadOnlyReason;
34
+ }
35
+
36
+ /** True when `child` is `parent` or sits beneath it. Path-separator aware, so a sibling
37
+ * folder sharing a name prefix (`rx/shop` vs `rx/shop-admin`) is not a match. */
38
+ function within(parent: string, child: string): boolean {
39
+ const rel = relative(parent, child);
40
+ return rel === "" || (!rel.startsWith("..") && !resolve(child).includes(`${sep}..${sep}`));
41
+ }
42
+
43
+ /**
44
+ * The folders a developer may write, absolute and existing.
45
+ *
46
+ * Built from the SAME loaders that resolve definitions, so a project Studio can show is a
47
+ * project Studio can write, with no second notion of what a project is.
48
+ */
49
+ export function writableRoots(
50
+ projectRoot: string,
51
+ loaders: { listOrgs: () => string[]; projectDir: (name: string) => string },
52
+ ): string[] {
53
+ const roots = [
54
+ ...loaders.listOrgs().map((org) => loaders.projectDir(org)),
55
+ resolve(projectRoot, "prompts"),
56
+ resolve(projectRoot, "nodes"),
57
+ ];
58
+ return roots.map((r) => resolve(r)).filter((r) => existsSync(r));
59
+ }
60
+
61
+ /**
62
+ * May this exact path be written?
63
+ *
64
+ * `roots` comes from writableRoots(). A path inside one of them is the developer's own
65
+ * work. Everything else is read-only, and the reason distinguishes the design system a
66
+ * developer builds ON (normal, shown without a save button) from a path that has no
67
+ * business being addressed at all.
68
+ */
69
+ export function writability(path: string, roots: string[], projectRoot: string): Writability {
70
+ const abs = resolve(path);
71
+ if (roots.some((r) => within(r, abs))) return { writable: true };
72
+ // Inside the project but not in a writable root: the marketplace on disk, rx/_schema,
73
+ // or the legacy orgs/ container. Reading is the point; writing is what is refused.
74
+ if (within(resolve(projectRoot), abs)) return { writable: false, reason: "installed" };
75
+ // node_modules, or anywhere else entirely. The design system resolves here for a
76
+ // consumer, which is the same answer for a different reason.
77
+ return { writable: false, reason: abs.includes(`${sep}node_modules${sep}`) ? "installed" : "outside-project" };
78
+ }
79
+
80
+ export interface SourceFile {
81
+ /** Absolute, because the route is addressed by path and the UI shows where it reads. */
82
+ path: string;
83
+ /** Relative to the definition's folder, for display: `manifest.json`, `layouts/idle.json`. */
84
+ rel: string;
85
+ writable: boolean;
86
+ reason?: ReadOnlyReason;
87
+ }
88
+
89
+ /**
90
+ * Definition source, plus the lifecycle handlers a component folder may hold.
91
+ *
92
+ * YAML AND JSON BOTH. The rx YAML migration is under way, so the design system is `.yaml`
93
+ * today while other trees are still `.json` (verified 2026-08-01: every marketplace
94
+ * component folder held only `<name>.yaml`, and a JSON-only filter listed nothing at all).
95
+ */
96
+ const SOURCE_EXT = /\.(ya?ml|json|jsonc|md|mjs|js)$/;
97
+
98
+ /**
99
+ * Every source file behind one definition.
100
+ *
101
+ * A component is a FOLDER (`<name>.json` or `manifest.json`, plus `layouts/`, `states/`,
102
+ * `components/`), and a simple one is a single flat file. Both shapes are returned as a
103
+ * list so the pane never has to know which it is looking at.
104
+ */
105
+ export function sourceFiles(folderOrFile: string, roots: string[], projectRoot: string): SourceFile[] {
106
+ const abs = resolve(folderOrFile);
107
+ if (!existsSync(abs)) return [];
108
+ const out: SourceFile[] = [];
109
+ const base = statSync(abs).isDirectory() ? abs : resolve(abs, "..");
110
+
111
+ const add = (p: string) => {
112
+ const w = writability(p, roots, projectRoot);
113
+ out.push({ path: p, rel: relative(base, p) || p.split(sep).pop()!, writable: w.writable, reason: w.reason });
114
+ };
115
+
116
+ if (statSync(abs).isFile()) {
117
+ add(abs);
118
+ return out;
119
+ }
120
+
121
+ const walk = (dir: string) => {
122
+ for (const e of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
123
+ const p = resolve(dir, e.name);
124
+ if (e.isDirectory()) walk(p);
125
+ else if (SOURCE_EXT.test(e.name)) add(p);
126
+ }
127
+ };
128
+ walk(abs);
129
+
130
+ // The envelope first: it is what a developer opens the view to read. Nested files
131
+ // (layouts/, states/, components/) last, and the manifest between them.
132
+ return out.sort((a, b) => {
133
+ const rank = (f: SourceFile) => (f.rel.includes(sep) ? 2 : /^manifest\.(ya?ml|json)$/.test(f.rel) ? 1 : 0);
134
+ return rank(a) - rank(b) || a.rel.localeCompare(b.rel);
135
+ });
136
+ }
137
+
138
+ export interface SaveResult {
139
+ saved: boolean;
140
+ /** Why a refusal, in the caller's terms. Shown in the pane, returned to an agent. */
141
+ error?: string;
142
+ /** Lint findings for THIS file. Errors here mean the write was rolled back. */
143
+ findings?: unknown[];
144
+ /** Findings elsewhere in the project. Reported, never blocking. */
145
+ otherFindings?: unknown[];
146
+ }
147
+
148
+ /**
149
+ * Save one file, gated on the linter.
150
+ *
151
+ * THE GATE IS SCOPED TO THIS FILE. `lintDefinitions` reads the whole rx tree, so a gate
152
+ * built naively on its verdict refuses a clean save whenever some unrelated file is
153
+ * broken, and can never say which finding the edit caused. Findings for the saved file
154
+ * block; the rest are reported and do not.
155
+ *
156
+ * WRITE, LINT, ROLL BACK. The linter reads from disk and takes no in-memory overlay, so
157
+ * the file has to be on disk to be judged. A refusal therefore restores the previous
158
+ * bytes, which is why the developer sees an atomic accept-or-reject even though the
159
+ * sequence underneath is not. A new file that fails is removed rather than restored.
160
+ */
161
+ export async function saveSource(
162
+ path: string,
163
+ text: string,
164
+ roots: string[],
165
+ projectRoot: string,
166
+ lint: (rxHome: string) => Promise<{ problems: LintFinding[] }> | { problems: LintFinding[] },
167
+ ): Promise<SaveResult> {
168
+ const abs = resolve(path);
169
+ const w = writability(abs, roots, projectRoot);
170
+ if (!w.writable) {
171
+ const why =
172
+ w.reason === "installed"
173
+ ? "installed assets are read-only: edit your own project, not the design system"
174
+ : "outside your project";
175
+ return { saved: false, error: why };
176
+ }
177
+ if (!existsSync(resolve(abs, "..")))
178
+ return { saved: false, error: `no folder for ${abs}` };
179
+
180
+ const existed = existsSync(abs);
181
+ const previous = existed ? readFileSync(abs, "utf8") : null;
182
+ if (previous === text) return { saved: true, findings: [], otherFindings: [] };
183
+
184
+ writeFileSync(abs, text, "utf8");
185
+
186
+ const rxHome = resolve(projectRoot, "rx");
187
+ let problems: LintFinding[] = [];
188
+ try {
189
+ problems = (await lint(rxHome)).problems ?? [];
190
+ } catch (e) {
191
+ // A linter that throws must not silently accept the write.
192
+ if (previous === null) rmSync(abs, { force: true });
193
+ else writeFileSync(abs, previous, "utf8");
194
+ return { saved: false, error: `lint failed to run: ${(e as Error).message}` };
195
+ }
196
+
197
+ // The linter reports paths relative to the working directory, and `resolve` reads the
198
+ // same directory, so the two agree by construction rather than by string matching.
199
+ const mine = problems.filter((p) => p.file && resolve(p.file) === abs);
200
+ const errors = mine.filter((p) => p.level === "error");
201
+ if (errors.length) {
202
+ if (previous === null) rmSync(abs, { force: true });
203
+ else writeFileSync(abs, previous, "utf8");
204
+ return { saved: false, error: "lint errors", findings: mine, otherFindings: [] };
205
+ }
206
+
207
+ return {
208
+ saved: true,
209
+ findings: mine,
210
+ otherFindings: problems.filter((p) => !p.file || resolve(p.file) !== abs),
211
+ };
212
+ }
213
+
214
+ export interface LintFinding {
215
+ level?: string;
216
+ file?: string;
217
+ msg?: string;
218
+ [k: string]: unknown;
219
+ }
220
+
221
+ /** One file's text, refused unless it is a file Studio would list. */
222
+ export function readSource(path: string, roots: string[], projectRoot: string): { text: string; writable: boolean; reason?: ReadOnlyReason } | null {
223
+ const abs = resolve(path);
224
+ if (!existsSync(abs) || !statSync(abs).isFile()) return null;
225
+ const w = writability(abs, roots, projectRoot);
226
+ // A path outside the project entirely is not shown. The design system is, because
227
+ // reading what you build on is the normal case.
228
+ if (w.reason === "outside-project" && !abs.includes(`${sep}node_modules${sep}`)) return null;
229
+ return { text: readFileSync(abs, "utf8"), writable: w.writable, reason: w.reason };
230
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unoverse-platform/studio",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "Unoverse Studio: the local authoring app. Reads your own rx/ folder, no backend.",
6
6
  "scripts": {
@@ -11,6 +11,19 @@
11
11
  "prepack": "node scripts/vendor-sdk.mjs"
12
12
  },
13
13
  "dependencies": {
14
+ "@codemirror/autocomplete": "^6.18.6",
15
+ "@codemirror/commands": "^6.8.1",
16
+ "@codemirror/lang-javascript": "^6.2.4",
17
+ "@codemirror/lang-json": "^6.0.1",
18
+ "@codemirror/lang-markdown": "^6.3.0",
19
+ "@codemirror/lang-yaml": "^6.1.2",
20
+ "@codemirror/language": "^6.11.0",
21
+ "@codemirror/lint": "^6.8.0",
22
+ "@codemirror/search": "^6.5.0",
23
+ "@codemirror/state": "^6.5.2",
24
+ "@codemirror/theme-one-dark": "^6.1.2",
25
+ "@codemirror/view": "^6.37.1",
26
+ "@modelcontextprotocol/sdk": "^1.25.1",
14
27
  "@tailwindcss/vite": "^4.0.0",
15
28
  "@unoverse-platform/base": "^0.3.0",
16
29
  "@unoverse-platform/marketplace": "*",
@@ -21,7 +34,7 @@
21
34
  "react-oidc-context": "^3.1.0",
22
35
  "tailwindcss": "^4.0.0",
23
36
  "vite": "^6.0.0",
24
- "@modelcontextprotocol/sdk": "^1.25.1"
37
+ "yaml": "^2.4.0"
25
38
  },
26
39
  "license": "MIT",
27
40
  "bin": {
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <!-- the unoverse mark exactly as the docs use it: dark, transparent, no tile -->
3
+ <image x="0" y="0" width="64" height="64" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAh0AAAIcCAYAAABSPBxCAABj+0lEQVR4nO3dBZhd1fU28HcmBhE8RLAgQYsEp2hwCFC0SJEWdyhQWmhpgQpW3CnW4u7uLsU9uIbgloQQIt+zyjvf/zKM3DtX1tr7vL/nmWcCTXM2J3PvXWfvJU0DBswKEclSE4D+AAbxazoA/QD05fe2vvq2+nWPkj+r9M9t7/sUAN8BGFPB1zcAPgUwGsCH/P59A++TiDRI90ZdSERqxj7cZ2YgMbiD7wNKgoZG6sGApasscPmCwUdpIFL6NQrA2wDG1XDdIlJnTdrpEAnLgoZ5AczH7y1fcwHo5b24AKYw+HgdwGv8er3k+7feCxSRH1PQIeKrbxtBhX0NBTCt/nKqDkhKg5GRAJ7lDomIOFDQIdI4licxDMASAJbk93lb5UtI/dnRzdMlX08xIJmsmy9SXwo6ROpDAUZaLDfkOQYgLcHI8wAmeC9MJCcKOkRq8DoCsCiAlQAsox2MbHzPwOP+kq/PvBclkjIFHSJdeN0AWBjAKgCGM9iYQTeyEHkiLzP4uI/fLW9ERMqkoEOkjNcJgIUYYKzCIGMm3TgB8EbJLogFIm/proi0T0GHSNsswXN1Bhors8mWSGfeA3AHgJsA3M7mZyJCCjpEftAMYHkAG/DLgg6RakzgDshN/LLSXZFCU9AhRdYHwFoMMkboyETqzIKOGxmAWDCiVu9SOAo6pGgGlexmrApgKu8FSSF9U3IMY4HIx94LEmkEBR1SBPMD2JSBhjXlUjMuiWQSgHsAXArgKgBfei9IpF4UdEiubCDalgC2Yd8MkVTyQG4DcAmA6wGM9V6QSC0p6JCc9AawIYCtAayhKcqSQZfUGxiA3KLuqJIDBR2SQ9XJagw0NmL7cZHc2JHLNQxA7uaRjEhyFHRIqhbl0YkdoQz2XoxIA40GcD6AfwF4U3deUqKgQ1JiuxjbAdiZbchFit6W/U4AZwK4DsBE7wWJdEZBh6RgQQB7cmejr/diRILufpzH3Q+1YpewFHRIVN0B/ILBhs07EZHydj/u4O6HVb9o90NCUdAh0QwAsBOAXQDM6r0YkcR3P87l7sfb3osRMQo6JIqfc1djEwA9vRcjkpHJAK4GcAyAx70XI8WmoEM89QDwKwB7AximvwqRuruPwcfNPIoRaXiPA5FGm4q7Gq8z+U0Bh0hjrMxZLy8A+I12FaXRtNMhjS553Q3AfszdEBFfowCcBOAMAF/pL0PqTUGHNMIMPEKxr+l1y0VCTr09C8AJAN73XozkS0GH1NNA7mrY7ob6a4jE9z2AiwAcrn4fUg/K6ZB6mB3AKXzT+p0CDpGkkrt/DWAkj1xm8V6Q5EVBh9TSHADOYYLoHkwYFZE0g49d+Fq2I5eZvRckeVDQIbXK2TiWT0fb8w1LRNJnDw77cLDcEcrJkmopp0OqfUOy5NCDAEynWymSPatwOQ7A8Uw+FamIdjqkqz83Nu31VQBHKeAQKYxpARzGfK0DAfT2XpCkRUGHVGptAE8DOB/AbLp9IoU0Ix843uCsJH2WSFn0gyLlWhzAnQBuAbCIbpuIsCze+nv8F8AKuiPSGQUd0pk5AVwM4AkAq+l2iUg7DyUPALhEO6DSEQUd0tHZrQ2GegXAlpZ0rFslIp3Ygu8ZfwEwte6WtKagQ1qz4GIHJokeoIFQIlIhSy49FMDLAH6puyelFHRIKTuTfRTA2WoGJCI1aBZ4GYD7ACymuykKOqS0bfllPJNdWrdFRGpoJQBPAjgTwEy6s8WmnY5im4pnr3YGq21Qkeo9z4FpNjJefvxZszOPXH6lG1Nc6khaXBuxs+AQ74UIpgAYD+BbAOP41fLryXzD7lbyvfTX3QH0Y+KvEvdiGA3gDwzmNwCwCYD5vBcVzM0AdgXwnvdCpLEUdBTPXABOBrCu90IyNgHAO/zw+QjAx+18fVYSYFjgUa0eDD5af/XntNDWX9bgSerHjit3A/ASgIW5m7g5gHl00//nGwZnp9fo518SoKCjOHqybfEfNf21Jj7jEKw32ZWx9Nfvc4ciheO1weyrMJRP4/Pz+1zcSZHqTOSO4mEMMK06bFGWoW8FYFbd4P8FZztxYKRkTkFHcapSrGvgAt4LSfRDw7bJnwXwDL8/y52K3IPUuUuCEOtCuySf0tWzpXJv8Tjh9pJ/Z0dkqwDYBsCmAPqiuL4DcDiAo/mak0wp6MjbNJyPsIs+KMoyiQHFQ5wvY79+kW+I8gM7rlmCAUjLd9sVkfJcCGBf7pS17m3xCwDbA1i9wDfzGfYJesp7IVIfCjrytRaAf6klcYcsl+Ixnr0/COARjevukhkALMWn9uEMRizBVdpmu2R7ALiyg9EDFnz8hrk3RQz+j2VlnSVYS0YUdOS5u3E837Tkx+wN7F4AdzHQsKep73WTaq4fj/SG82uY8kPadAWA3QF82s597MapznYsM6KAu5UvMfflOe+FSO0o6MiLbcueq92NH3mb5Xn2dTd3N6TxRzIr8YNzvYI+vXe062H9K67r5PfNyWPSHQtWdWRHm78HcKL3QqQ2FHTkYWomYO1RwKehtspVHywJNKwZkcRixy/rs4eF7YIIcB6Afco43rOKo834e+0+FsUtAH5dgATu7CnoSN8wdkAscmWKHZHcyVbu1wL4yntBUrZZufuxAXfqrNdIkStctmEic2eaeIS1HxNQi/Cw8REDj1u9FyJdp6AjXfYmsz+Avxd0Euxk5mdcCuDqNqoBJM2E1M3YJnuFgnyQtvVzfRhf15ZQWY65ufOxA6tgcmZNxE7ikYuqyhKkoCNNMwP4N5PMiuYhBhpXsuOn5DuhdEsGID9D8dwPYOsK24Rbrsde/LIALmfPsrmaJZtKQhR0pGc11voPRHF8wiDLSoBf9V6MNNwiDD62AzCgQPf/cx63WG5SJfqyw+f+mSftfsv/RmujLolQ0JEO6174J9auF2U68D3spHo1E0Sl2HpwUOFu7AlSFNbg75AulHf3YqBmow9mR76uY67Hl94Lkc4p6EiDbZteDGBNFGdXw4KN17wXI2HNz/4V9qE6HYpx3LJ5F48Ue/JDOefg4w0GpM97L0Q6pqAjvqWYv5Drm0ULawB0DIDLtashFbDEyS24+2Et2XM2mpNqrbEduhh8/IY7pjkOmhvLpoj2HiJBFWWbPlU78Q0m54DjLibELspcFR2jSCXGsSFeSxt26+eQq4FscLdnFyt77LV1JicK759hxVcfls3bw4smJAelnY64Z9cnswNhjiaxBfTRHKwmUuvE0wN5HNE942Ziu1c5m2Qa9vnYP8MJt3dyByy3wCp5CjpilsPaccqKyHP7055Kj2N7cpF6GsIP1B3YtTc3NqxwwxqUjvcH8GceUeW0Q2DvMRvrwSYWBR2xLAzgxgyPU8azrO0IJoqKNNJMbJ61b4ZP9O+zI+lTNUrOPZot6nMqq92ZR7cSgHI64rAX+sOZBRwTWYUyD7dxFXCIh09ZcjoXJzDn1MlyVs4assqNar3CdvSrZrQ7YDtcF3BgXK5HbUlR0BHDvpwZ0jejVs4X8snJ8lI+8F6QCIPe/ZhIeTaD4lw+WK/iUVJTjfrjLMUdglxyIvZmnocdJYkjBR3+9/9EPn3l8ndxDRP5tmHtvEg077EybCFWO9g8j9RZsPFPAKfW6Il+EjsAW4B2Ch8kUrcygEcBzOu9kCJTTofv08nFTATLwZMA9mBym0hKFuUHtk25zcH1nFtj5cS1vEcnZ5Lg/hmPkew4Wxosl6fr1MzArb4cAo4vGWwsrYBDEh4etgYrHXKoqtqA/TwsgbaW92hlNt/6LIMOz9YfaBPvhRSRgo7Gm42JXz9H+i5l3sZpmWy/SrHZ0eACnG9kVQ8pW4bvM7VMTJ/C/iD2mj8faZuKnUstn04aSEFHY83P0ez2xpaydwCswy3cj7wXI1Lj8u7D+Vq1fjkpm69O7zefsp368MSnPjczny6nnLrwdKMbZwm2NLedjlRNZuKrJeDd6r0YkTp6F8BmAFYD8GIGJbX1mEtzL3M9jmTiaar25a6H7X5InSnoaIwV6nDG2mgvA1ieL1DrLCpSBPa6HQbg0C6Mlo+UQ3Z3nZJAbWfoIB7n2NDGVG3CPDvL95A6UtBRf5agdhvnHKRoErsUDmO5mUjRWLBxGHcrn0Ca+vF9aM06Vq8txXyYVIOz5VnRYk3kpE4UdNTXugBu4PjtFI3kC/H3mXVxFOmK5wEsy2Fy1Qxa8yzTv57vS/UwgfkwVsn2AtJkPTweAbC490JypaCjftZjNnwvpGcKczdsd0N9N0R+vPN3DHMZLFciNb3Y/djKauvlGe56HJNo47WZWVJrR0ZSYwo66mMDtiXuifR8wCOhfTMoGxSpF6vaWAnAXgm+TnqwMqeeg93Gc0fIenu8hfRMB+AO5uNJDSnoqL2N+YJOMeC4gpNuLcoXkY5NYYvwpXj0kmLgMaLO17GKvcUSnfLaj1V6VhosNaKgo7Z+yVkO9oJOiVWj7MD1f+G9GJHEvMg8BmuSlxJ7MLq6jsmlLb7mLKZtAYxBWvoAuAnAWt4LyYWCjtrZkrNUUhuf/Ayz8s/1XohIwsZzHMCGibUJ78kcDzsqqrcLmCf2RIIJuNcxT0+qpKCjNrbhC6ob0nIas/GtSkVEqncdk0ytcVZKH6o3cbem3l5nRdxxSC8B92oen0sVFHRU7zecQ5BSwPE1j1LsyUylsCK1T8a2TqaHJDSTqC+AWwAs2IBrWWnt/gA2AvAV0tGDx+e2qy1dpKCjOjsBOCex+/gsWyJb0qiI1IcFG39jT4wvEupcejuAORp0vWt5tPs00tGdSbHbeS8kVSl9WEazO4AzATQhHbYjsxyA17wXIlIQt7G6JZVmWbOwVLRRIxve4MTts5DW56ZN293ZeyEpUtDRNXsCODWhgMO2M3flUVBqPQVEUvcGg33r3ZOCoQBuZOVGo5JwdwGwI9+rUmDv/WcA2Np7IalR0FG5XwM4CWmdL6/EXRkR8WGlopsC+GMieR7LMH+hkdV45/C9yt6zUgk8zqtzd9fsKOiojCU+nZ3QDscjzN9QK3ORGP7BTqBfIj5rHHZyg9/vHuN7Viot5rszOFvFeyGpUNBRPstGvyShKpVz+UIY7b0QEfmRm1mq/nYC98WOZQ9o8DVH8/02lTyPqThIz4Il6YSCjvIsy0zrFIa32dbtfuwwmsr5qEjRjGSex1OI72geDXnkof02keOolpbpC3gvJLqmAQNm9V5DdDaL5D4A0yO+bwBswScpEUmjP8aVCbTZ/pb5Fh7dRK3s+FJ+sEf3AYfEpbCL5UI7HR2bm3XrKQQc77LTnwIOkbQSTNdjOXsKrcCtpLbRbuau0NsJlRwP9F5IVAo62jcYwJ2J/PD8l9nmqU26FBFgIsvZ/5rAe+K1DEA8huotm8jclnnYn2U674VEpKCjbTMyWh2C+G5QwqhIFv7MhlOTENeSjk0RP+J7nc2JiW4RrrO390KiUdDxU724jdiIGQTVOp1lvOO8FyIiNfEvAFtx9yMqG3C5r9O1x3KSbwp9h6zT6jUJTh6vKwUdP9bEs1XLjYjuT2zFHvmpSEQqdzkHMn4f+OYd49ibwgKy3QAcjPjW5DRvIQUdP3Y4qz8im8R2wX/3XoiI1M01HKMedQp0NzbF8ip/nALgCB5HTU5gMKiV/opKZn9kuwQyyG1GweZsRCMi+VubAYg1oIroUQArO/cE2gTAxQB6Ii4LjH7BmTaFpp2OHwznWWr0HhzrKOAQKZRb2TY96qDGZbnj4Okqtmy3fI/In7WXMMG00BR0APPxh7YHYtfy29ngvd4LEZGGu5MNsqJ+qO7HhHbve7QqgC8QuxHcDQAGoMCKHnT0Z+OZ6YMfqWzAbUwRKaZ7ueMRNcfjvAAtBh7nrvWniGt29jqJelxWd80FL421v/y5ENf3PK+8x3shIuLuHia6R6xYm5Z5Fd47xs8yxyTyoMtlGaQVUpGDjvNYRx3VJNbrq625iLS4ltVrVr0RjbUqP9R7EQBe4pyY9xHXFkHuVcMVNejYH8CWiMveULbnICgRkVLn8z0sooMc+3eUeo2Bh82kiuovCbRoqLkiBh32gjgKse0N4D/eixCRsI4P2qunie9dEeaOvMXk0veD77gvgwIpWtAxKxvaWGObyJ3+TvFehIgk0ZXYRiFEM1ugLpxvMPAYhZimAnA1ixoKoUhBR08eV8yMuCwg+r33IkQkGXuybXo0W7KRIYIctawaOLl0MJNwC/F5XIj/SDop+DbWA+yKGjFBTETidrrcjuWi0dhuxyDEMJKBxyeIaXVOGc5eUYKO3wDYBXG9wha5UWvwRSR2Lx97/3gPsczATs+W5xHBywDWAvA1YjoEwBrIXBGCjiUCnS+25SN2G4zcSU9EYhvNJoLRupZae/KtEcfTXFPEtvL2eXwRgFmQsdyDjhnZ4jxq97cJbB9sWdYiItV4BsCvAk5dPRHAQMTxIN93rfliNP2Z29cdmco56Ghi1DgH4todwCPeixCRbFzHXhmRTB9wt/k2Nl+MmEO3fIAhenWTc9CxP8/vorJSt3O8FyEi2TmaDcQi2SjAULjWrmT1T9TPrw2QoaYBA6x1RXYW5w6ClclGrVRZLej2noikz9777uOcjyisV8aCAL5CLNZk7WDE8yU/y7I6fs9xp6MPgEsCBxyWYb6pAg4RqXO+2C8BfBasH8URQZusRdsZAru6XsHhpNnIMeg4GcC8iOk7bjF+7L0QEcmePeBsEyxvYVcASyMWuz87A7gFMasvj0NGcgs6fsmeHFHtB+BJ70WISGHYB+mRiJXgf3rAURTfs4Pqs4hZcLAuMpFTTscc/IGZFjFZ0tJm3osQkcKxD/i7AKyMOPYKOmPK5sY8FqiTaosPAfwMwOdIXHNGL6qLAgccbwPY0XsRIlJIkzgLxRoRRvHXoEPO7EhqfQDjEMuggGXHhQ46DmFtc0S2bbdFwIxtESmOD9mXYnKgJMl/IKYn2WQtUi4MePxjKQRJyyHo+Dmzj6P6I7frREQ83c0dhih2ALAkYro2YJM1cLcjUnfXwuV0TM3Wv1GrVazr3ToBI2YRKabu7GEU5cPeeokMD/oe2dLV2o6mIrkJwHpIVOo7HYcHDjhsgNv2QV9MIlJMEwFsy8m0EawcuDJjCnPxbEhcJCO4S5SklIMOq/X+LeLaix34REQieTnYkfRRAUtoW1hC6YYAPkEsxwMYggSlGnRYh7bzAv+gXsVtORGRiOxD637EsBCA7RDXuwA24S5RFP34GWhHQElJNej4M3v4R2TdRnfzXoSISAesiuXXAMYEOirvjbhsXtYBiGUVAPsgMSkGHTYA50DEtWvArTgRkdbeCvRBOguAvRHbSQAuRyxHAJgfCUmteqUHgCcALIKY7Ehla+9FiFRYAdav5KtPycNIU8n3JibWjefTcemXzRSStFulr+29CABfA5gbwKeIy14jjwf7oH+EfaqSKFpILej4C4BDEdPn/EHULod4m5FJZoPZybD1d+sEOQ2AviyhrEUDvDF8DVgTqtElXy3/bOfibwD4tgbXk9qancmlEY43TgheIAAe7T/OAD2KnQCcjQSkFHQszE5xttsRkZVWneO9CCnUDsV8LBkv/RoKYAbEZE9iHwB4HcBr/HqdH3ivBuqWWUS/DzIY7nv+XNvRT2RbBSsW+Iz3zb6HlkrQYVu7DwFYDjE9CGClVLa3JDn2BLoYx1wvwcZO8weu3uqKsQCeY0+Ep/j9BQATvBdWED3YaDFCgv5/gleztDiHvZiiODeF/h2pBB3bB95FsMh8GIAXvRci2RjCLo0rsh9NbgFGJa+t51g5cD+/wj/JJcwadd0bZECd7dq9idj6MMcwSn7HFAArAHgYgaUQdEwPYGTQiYTglmTEHv2S1pn6cJbA2fc5vBcU1BQexdzHAORe5otIbXcZtglwQ89kJWB0i3K2lvWOiuB5VnhG6imSXNBxWuC+F29zO1LJcVLpVrY9VW7AlsZz6fZ1OQh5irMo7Ou/OuKs2swAXuHDnqcJfF1YDlB0uwM4FXHsD+A4BBU96FicbyRR+4lsyu6jIp2ZlsP/fsHyRBvtLbVvzGflnzdy2OI3usFdshsf9rwdxw/QFHIOr+VDRARjeOQTMmCLHHQ0sf54GcR0P59WRToKNCww3ZxHJ1Err3Jk/URuBnAJg5AoA85SYA95jwJYKsDckzmC9+0o3SF6IVAawJUANkNAUXcQwCzcZQJv6+7nvQgJqQefeK5gvoHVzq+hgKPhpgKwMf8ebAfkAk4zVeDXuclB2mv3DrKOcnzMtglRbBqk4VsyOx0zMHl0JsT0b84tEGmxLBPwNmdzLonJql8uBnAWn0ylfZcB+KXzDfqKux32PQX/ChR8vAHgZ9F2+aLudPwjcMBh/QQO9l6EhCmZ24VlnY8woUwBR2z297MXs/wfYj8Ia7QmbTcM+y7AEaW9rlKxX6BS37kjVlZG3OkYxtrn5sCt2G0iohSX9RDYgx9Y9qYoafsCwIUs01S/nR87JsBQuE/Yu8ZyPFKwIsu6I4ydH8/3q/cQRHPQH/KI6zIfAfin9yLEhb2BrA/gdpYU2kRMBRx5mJ67Hy+w6mV17wUFm2LqfbTRP7FBmtbM7hTEyW36KwKJ9uFuiS+rIfYLMJVoW2qjO3M17An4eiaFRniCkfpYE8Ad7P+xVY0G4qXMhvgd5b0I7iym9Lo7KND8mG0iTWaPdLzSzHkLYW5OK+8DmCfAGac0Ri8mC9u59py66YX1Dief/ov5XEXUm4P5bEKx97GFzblKhXUXvhsx3Mo+Qe4i7XRsGzjgMH9XwFGYN9jfMhnsDAUchWeVE8fzqXV/blcXzTju8nrbE2m5B8DpiHOKsCoCiLLTMRVHW8+GuO3O5+UAKslTD3Zi/FOgBj8Szyg+gJxdsAm4vbjb4fmBMZFzij5EOqbhvKDB3gsB8CQbvrlOQ4+y07FP4IADrFZRwJGnJvbWsDeGExVwSCcGc87GSE6/Lsr03+/YysCT5dfsjLR8zSTlCJbgex2KvtMxI5uYRK0EeJ197G3csuRlZVZLebd7lnSNZEmptVrPXU++H3o+IH7II6+UHgIjzWZ5E8ACnrt0EXY6/hQ44AA/lBRw5GVBfkjYaHQFHFKN+QDcwEFz9nCSswl8P/RkyawbIi1TuNsx1nshnNy7W5F3OuZkzwOLoCMazaY0qljJQ18Ah/I4r+ilkFJ737M/w2EBelvUy9TMcbMBZ15SHba5L5OSvX3KbqV29FO4nY7DAgcc4Bm/Ao48bMq8DatAUMAh9UpGtsqn1wDsFOD9tR6+DfDBuRJniqTmFLbf9zYTWwEUbqdjKD8EoiZifc1M6VyfWIpibr7YQ05clKw9wmnZ9j6Xk2nZVruf4xqsM/TvkJ6V2CI9QvBon8EfNPrCnpH4HwMHHOAcBgUcaZf4/YWtrRVwiIfl2PDwEO6C5OIrNkvztFXwz4+OjoYuDnJMZvmUhdnpmItZ390DJ0zNyZp8SY8NDfxPoluwkqfnuevxX+RhNlZCdA/Qsj7FsuuRzDHz9B0/i0cVYafjj4EDDnOpAo4kdeNT5WMKOCSYhXncciyfMlNnxytXOK8hpSFwpUYFmVTeizlu2e90DGH30cjbjcsAeNx7EVIRK1f8N4Cldd8kOMvx2BLAs0jbsgykvFgJ6oAgpaiV6smfA9tp8DSWfU8+y3mn4+DgAccTCjiS0sRSNJsKqoBDUrAAd+P2TWxyamuPOh8X9UmwZ0fpEb7t+Hvrw59D5Bp0zM7JnZGd5r0AKZv1CridJXw5bFlLcfTiz+3NfFpP1cnO10/1iMVcznkoEQbp2YyYLI9XbOLerojrcw40snIiiW1F5t5EGKSUujEAvin5sp//yfzfppR878kno74lX/bPUp2P+TBmXU1TDJ4+4DgLD/ZzOgsbOaZoVQB3eS+CJxBH5BZ0zMoZK5GbgaVa+10kTWxs87dES+YaHUy8ymZV73FuxaiS7x/x97QEGF3dLZ0BwEB+DeL3wTyvHspeKZFf9xFYUHcUt9yr+fvwet9seEJiCWvIdgLSdTOAdZzX8AnzLcflFHQc3+izoy686IcyMJKYZmCy6HreCwlmLPtBPMm+JK8x2BjtPcaaurHEcigTfhcDsDiAhYLnd3m4jT0obNc1FfOyBNSL/dwviXQtAuCZAPk9+7ILdxZBR4QOdp25h1tdEtNSLNGzTOsim8w32Yf4/UkGGCkOJezJwGNxVkKsxA+wonsLwEaJVbfcDWC44/Vn52dMqs4HsJ3zGj7g7uSEHIKO/bkFF9lv+Bcv8WzOv5upUMwg42kGxTYV9wGvQU0NMpD5OhaArFLgfivjOL8lQvfKchM6L3C8/h6JFwHMzocHy5HxtHO9u802Iujoxs51dlMjb08P5Pm2xPIXfnlvPTbS1zznvY7b7V+guOxYZl0AIwCsBqA3isWOpQ9IIM9jah7nNawKopXbMhh3cHSAnEJLL5ivnjunjQg6NmeVQWQWoW/rvQj5EdvVOJdNlIrgfQDXALie8xnqusWZ8M/EKjx62JQ5PkVwLfM8olfVncXdGQ/fc3pqyruA0/ND3757+lU9d9iaG5RZHJ0lJ0ocA3ickHvAYW+Q5/CD1HJV9gZwpwKOdo0HcCuAXbgzabsfFxZgh3JDvh76Izabd+TFEpLXQtq+APB370XwfQip7nT8nAlvkb3HUqHo25dFsSCPFnJNGJ3E/z7bXbsxgafXFPRmRZM9Za+OfL3BI4TXEVMTk2C9XrsWgG6D9H+W3w4QYC5Zr8Zl9d7pSGGXw35QFXDEsDQTJXMMOEZzyJP9t23AShwFHLVLurTujmsAmIf9LqzhVm7m5qwTq/SJyMqzL3G8/ojgg0TL/Vk+Dv4sMTe5nY4hjMijN3BanNUB4ms1nl17j3uutQcBnMJ8DeVpNLYc144l9uGOa06+Za7cDYjHqo2ed7z+cFZ5pWwaAO8AmM75KHPWegyCa67zuVD0gMO2AhVw+LPEwJsyCjjsie8yAMNY/mm/VsDRWBO4+7E8gBVYCRShUVqtKkWuArAJ4nmB01O92C5iDrleJwdI2t6+Hn9wvYIOm8ewA+KzF674+jWPGrzr02thIoDz2HVzC3YZFH8PcddjAVZY5BAA9mAwa1Ut0VzpeO31kYcT2crB0271iBHqFXRs4VivncqLQ37YDTs3gR2xzkxgY6J5+HRgTX4knpGsfLG/pzMZJKasGxOSo03u9nxfnSd4T6hy2bHGGfA1J3vkJBF0WFezFPoiPO69iALbk9F8yk2/pvBNf14mXtk5rMT3Hqddz88yz5QTyZsZuEd6z32O83+8WAl6Do4F8F1uCaXNdRpeY1UI0V2d0Rlvaqy08SSk7TbmbFhTOQUb6ZagbsfkR0tiTlUTd24skI/Cmtx58ZwBU0sfMqD0tBZ3j0IHHZEi7s6CDmm8bfkGmeoOhw3hWpP9ElIayCXte5nJzKszETJVJwU6arHEXS+57HS0tEb3HOZo79O7Ry6ZtazqUc6lPuVmB8+YwZluarZgX5QUczjsZ+ZgnrOmONFVytOdeR9/DdCOuivsZ3Mzlmh76sbeNNaa3CsfwZps5eB85wm0XwKYhT1Ewu10/DKBgMPcpYCj4TZm/kOKAcdFHIJ0qgKO7E3k3/NQ7silphsbdFnfG+/gx1rWe8lpt+MI51SA6WpZJVXroMNr2E+lbvFeQMEM5xthat0CXwGwKsd221ObFMdnTDZdJXDb8fb0Yo7KMs7r8Aw6csnraKm6stEJnnaOGHQsyEY8KfB8MRTNQtzqtQ6RqbBqhiMBLMZBW1Jc9zE5/qjEjtX68oPKkmQ9k60nOwYdqeaNtcW6Gntairu9oYKOVHY5XmLJnNTfYO4qTZvQzX6dXUQPClCuJnHajv+BVXmeLb4rNQMfsOx16OHTeg0NK8NszOvIxe0Bdty2iRR09Exoup+OVhqjH1ub24s/pacJ29142HshEtJTDDxSKveeheWrNr3Uw53wk1Nex2QApzuv4Ve12D2qVdCxLqtBUokYpb66syuhfYCnYDTLYPcK0HpYYhvPIXLrAfgEaViCTdCanJL2PY8EcnKe82TqIZxjFCLo2BJpsDNZPcXW3+n8EE/BPWzydYf3QiQpNzHXw/IWUmDD4f7mNPfmO8dgKydfsJLO0zYRgo6+CQ3ZsYmyY7wXkTlrJLMj0mBvwmuoMkWq2CGzXd5DEulufLDDMbjtDD0CH4sklsBeDivn9rRZtcM5axF0/IJNwVJwv/cCMmfVSycgjXLIdfhhkVJFgsQ8a/8bH7y+QnxnO1QZPgAfvVg9l5NnuHvk2bPDjhZdg45UjlY8f/iLYBBH1NvI7cie47aryqal1sctlkPwYvDb2pOv0wEFed/N7Yglwm7HNp5Bx4wJnd3b9ueD3ovIVA8mjlrgEdmNTITSgDapB5usuiyAq4LfXnudXlzHKeOtPeK4o5hj0HEVgI8cr78Oy7G7pNofuk0TeLItHepkdeNSezai/ufBb+xxADYE8I33QiRrYzgOwsaSR2addg9r4D3xGo6YY9AxAcBZzrtlm3sFHSkdrXieg+XMWoTvhtizNGyA1/7K35AG5nkcAGDv4Ammf+S05EZ4DD5yTCY158D/fb/hQccs7NyYCq/OeDmbC8BpiGs8E509nwqkuE5mqar9HEbUxCGMNR01HizoyDGZFDwi9iyMsJ3tuRsddGzRwDPBWnUTlNo2ALuYnUejjqJfM8CgJCm2a3iUYePBI7LR85c3YBijV9CR6xGLuRC+LKCuWHOV+Ryp+J5VC1I7hwWYYtmeT9gCWdVKEsEjHEBmpdoRLQfgTw2YlOqVTzU/8nQF8zu8bNDIoGNA4A+ctlgZm4Z31c7KHIAV0XusULFGcCKR+ius4lx10Fl+x5J1/POnOCaTzos8fQngBudg1XbKGhJ0rJ/Y2GAdrdTO9DwHjni09i7zjF71XohIG15g4DEq4N3pzvksU9XxGl4PAjUZyR7UBY7Xbu5Ko7DmRm6rOFISaW0nsUacHDuKZ+fqwSGRvcKdwvcRzwIA/p7hw99cCbV26MrU9M/hxxL16x50WMvz1ZEWbbXXxggAWyGejxhwvOG9EJEyvM6ZPxH7Bu0LYKU6/dkvOu7i2ITUHE0AcJnj9deodHesK0HH6gnNWil9upDqWJXKGQFv4mf8mbRENZGU3pPWDtiszj4Tzucgz3oEHV59S3LN6/CuYukDYLV6Bx2pHa18zJHAUp2jGlTPX2mnwzV4Vi6CBI991wvYx2NOAEfW4c8dB+At+Jgv8+qoNx2vv0E9g46maifMOdAuR/UsOXNXxOs0anXiOjqTlN3P9gP28xzJbnWqZnkJPnLe6ZjivNtRUWFJpUHH0gAGIi0KOqozFcdhR6tW2gnA7d6LEKnRhFpr1R+JfTacXocqNa9j0JyDDrBRo+cAQZuwXJbmzI9WjIKO6hwc8AV7KM+dRXJxLoBjEMuSddjh9Cpnz/l4pSWY88xr26BeQYdto6RGQUfXWcb37xCLBRuHey9CpA4OAnB9sDv7dzaDrJWRjk/j9W717u363IIOO1ZZGOlRVUPX/bPOzYIq9SifvCJP7hTpqkkAfuXYubMt0/F9oFa8ytqbahw8RXS947UXLrcsuZKgw/ogpDhi2tpiS+WGd3WgTx17cVjCndrZS87GcEc5Urv0rfl+UKsmfl7zQgYjb484935ZrdZBR0W1uEGM5rA3qUw3ACcGummW2b8ZgA+8FyLSAPagtCUfmqI4ke8L1ZrMcQVeRyy575Td6Hj9sgLT3Hc6vH64U7dLsKO0/TQxVgrmHgB/QRz2frB9jf4sr14due90eB+x2FyhmgUdcyXaRlZBR+WmDZaoeQnnvYgUzT8A3IY4DmcHymp5zZ3JfafD3OF4BD0LgKG1CjpS3OUwCjoqdwCAGRHDW2xSpMRRKaLJzKeIMhzOign2r8Gfo6CjvjlBdyHwEYuCDik1E4B9gmXyf+W9EBFHlhi4RaD8Dnso6Z9o0FGE45XwRyy5Bx2qXKnM7znYLUoDMMvGFim6hwAcjRj6sWFgNbwSwotwvGJuQOJBx0IJ1zdHKjuLzl6QeyCGBwAc4b0IkUAsCH8OMezK8/uu+gQ+irLTMQrAE46fI/NVG3SkWCpbOvZcyvNHAFMHuFk2iXI7Hq+IyA8sOXDbIC0ApmL31Gomf3uIkqvWCHciaF5Hc63KYIJS0FGe2TlALYKDHUvqRCJ7ljseEexYxW6HV9DRA0BPFKfk2ssq1QYdyyFNlnj1hfciEnFgkBejtTlXeaxI+yy3478BblAvvm90dTfzW/iIkrNWbw+zqWJyQcccCY6yb/FFoIzvyGzL8Tfei2Br5B10rCLSoYnMqYjw3rZTFZUsX8JHUYKOMQAed7q25YAu2NWgYxmky7MHfUosebR3kGmWL3kvQiQBTwE41XsRzAHraom91y50UYIOcy/8rNzVoGNZpEv5HOW9aewJf68DOMp7ESIJOYSzpbztDqBvF/5/CjryzutYqog7HV97LyABv65Bo59azVbR9FiR8n3F14236Xks2pXtfw9dCZBSzuv43unaS3Yl6LBM38WRLq9EpVQ0B3nTut15MqJIqi51bnndYu8Kh4d6Bh1FOl4ZB+Axp2sv2F4Lho5+UBZlPXaqFHR07BcA5oEv68Wxr2ariHTJFM5C8Z5NZANBN6jw/zMWPooUdHgesXRjDFFR0JFyPodR0NExG6TmzcpjX/ZehEjivTsu8F4EgL0q/P0KOvJPJl2i0qAj5XwOo6Cj4yeT1eHrGwB/dV6DSC5Jpd45UTafa4EKfr/XelPeve+KR9iOwEPhgg47z5L26+ubnG/OcaowEqmJdwGcHOBeWv+Qcnl9EBbNt45zWCoKOqYDMBRp005H+wnC3s3ArFzueOc1iOTkHwE6MG9TwU6C106H98OWhycdk0mnKjfoWBjpizAYKWoC6YAArZyt5E9EasMCjn8GKJ/dpMzfq/fn/IOO7gAWKzfo+BnSV8SIthy7OF//4yBbwSK5OTVAMN+Vnh2SZ9DR5hFLzjsdCjrania7Gnwd65i5LpKzrwIMTLRhX3OW8fu8ynyL+LnwimOO4xJF2umotFlNEWzh/KKzipUzHa8vkrsTnJPo7f1l6zJ+n3dvkaINCXzG6dqFCjqKGNGWE3R4OjPA9q9Izj4NENhbQmk5zaM8FPVz4UnHZNJenQUdszAhKHVF/eFqz3wAhjlH2yc6Xl+kKP7pnKg5tKPZGyVJhlKMZNKhnQUdOexyGB2v/NiW8HUxgPed1yBSBKMAXBF8V9VK96UYyaTzdvbBnEMSqef2XVRbBGgGJiKNq2TxtFnQnY7JKCbPZNJ5i7LT0dt7AYEM4/GKl0c5I0JEGtf++mnnSrmlO/jf25xA2gBe022LnEw6b1F2Ovp6LyCQjZ2vf5bz9UWKZkqA3Y6NAz4UFjXo8Dxima+zoGN+5KGP9wICGeF4batWuczx+iJFdYlza/SNAgYdRe4R9FLEnY5BGR1LaKfjB4Odq1Yu1PA9ERd2hv8fx3s/b+sPnBLa6Wi8V+FjptKK2OY2Rp7nQkHHD9Z1/nvQ0YqInwucb/767fz7aeGjyMcrrzpee74iBB06XvE/WnkewHOO1xcpuqcAvBzwoccmmXsYU/BS6nFO1563CEGHdjqAngBWdz5TFhHfhFI74vSyQjvvxV47HUXO6ZgM4DWnaxci6LBzpKJb2Tn4utTx2iLyg4ucH3yGt/HvvbpeF3mnw4yEj0IEHTOqK6nrLof15njL8foi8oN3ANwX6H3IApFpnNZS9KDjVafrFiKnw/7bir7bYTsdXrTLIRLHlY7XXrXVP8/keLzgOYG3yEHHPG0FHVOxZDYnM6O4LJF2ccfrX+V4bRH5sRscb8hCAPoHCDo+LnAbdO+gw0qkZ2gddMyZ4WTWIgcdyzkOVbKW5xruJhLriMVrFIF9rqxU8s9eD7cfOl03klcdrz2oddCR09FKiyIHHaUv8ka7yfHaItK26xxvzIqtGhZ6GO103Ui+APBJlKBjDuSnyEGHZz6Hgg6ReK53vPbyJb9W0OHrDafrDm4ddAxEfmZBMfXqZMJjPX0G4DGna4tIx43CrEGUh8VKJsvqeMXXqCg7HQOcFlJPQ1BMizAx2MNtACY5XVtEOm4UdrfTDeoOYAnnXXUdr/jmthQi6LDk2CLyrFq5x/HaIhL39bmU88OgEkl9dzp+cryioCMfnlNlPZsQiUjH7nW8QUvyu3Y6fIXZ6cgxp2Omgg5+G+a4ffm607VFpHPWJfhdxx1Y69XQz+n6Ol4JFnTkuNNRxCOW7szp8HA/z41FJKYpjkcsNn9jUfjR8UqgRNJ+JZnFuSla0DG/YxKpBR0iEtsDTte1z5uNHAOOIk+YjRB8WVfSaZsz3+Uwc6NYPPM5Hna8toiU5wnHG7Wh03V17PvjtgYT4WNQEYKOn6FYbM6BhwkAXnS6toiU7yUA3zndsNmcrvua03UjmuyZ19GccRKp94ewl6FO132BgYeIxPa94xwWLwo6fswr6JiuJejIeQS8go7GeLpB1xGR6j1ZsJuo45UYQUe/lqBjGuTLkmRnRzE0OeawWItlEUmDZ16HB+10/Njn8NG3tHolZ0XJ6xjMDGEP2ukQScdzKBbtdPzYN3De6eiLvBUl6PDK52hJThORNLyK4lC5bMCgQzsdefAKOj4B8JXTtUWkcl8XqEOnjlZ+SkFHgwYN5c5rpoFe1CLpKcpux0jvBQT0jdN1C3O8Mp91QkP+/tdm1oGCDpH0FCXoeMZ7AQF943TdwhyvNBVkt8Or34qStETSU5SgQ0nuPzUGPgqz02GWRv4UdIhIud4pyIC7olXqRN7pKEzJbFGCDq/jlQ+crisiXVeEqauvaNBbzOMV7XSkz/4u+ztduwhvXiK5KcLrVkcrQYOOPijGLoDXsKFGsFb23Z2uXZTSO5GcKOgorm+8gw6vD6tGWwn58trlGOuYlCQi1b12rV9HzjSeoW1e79ndW4KOlu+5WxX58srLKcLTkkiuct+lVLls++PtXSjoyEdfx26kIpKmL5F3dY7XYDNpW1PRgo4h/MqRV9CR+/asSM68zvYboWiTdCstJXZRtKAj5yMWr6BD+Rwi6co56LjfewHyU0UMOoYjTwo6RKRSOT80KOiIt9Pxv+MVaxFeJAo6aivnNy2R3OW602FTr5/3XoT8VHPBdjnMLAAWRn56O11XQYdIunJ9/T4IYJL3IgKb4rnTUbSgw6yP/Hj9PU5wuq6IJFw6WWc6WgmqqEHHesjPlKJlQYuItENBR8eU09Fgyzh28MxN0XKCRHKS40PDOHUijavZeXv8Jcf/7hHIS45vHiIilXpEx76xdzrsTO/7Am6B5ZbX4fZD5HRdEalejg8rOlrpnNu8tZZ8jm+drn8f/KwJoJfj9XOhoEMkXd2Qn7u9F5CAvk7XHd8SdIx3WsDDACY63vQ1kA+vJxYFbiLp6oO8fAHgUe9FJKCv03XHeO90TGTg4WVL5OPbgk23FZHq5RZ03OL4IJuSPt5Bh9dOx9T8IfHyC8emWrl0FlTQIZIuryfeernJewGJ6FvUnQ77wL8ZvtFeLgmlCjpEpMhBhxVF3Oq9iET0LepOR1/2x38PfnI5YlHQISJFDjrsqP5z70Ukoq/Tdcd673T0YwLkDfCzDoDpkD4FHSJSqekzumU6WilfYXc6WvIBroefngA2Rvq8gg51dhVJ10DkQ0FHQkGH107HtPx+r/OI5W2RPq/7N0i9OkSSZA9cMyIP7wJ4wXsRCenrHXR85Rx0fOccpa4MYF6k7TOn6/bI6I1LpEgGIB83ZdpdNduS2U+dFjBDya+vgq8dkbbPGbx5GOx0XRGpbpcyF1d4LyAx/byDjs8CBB23OB7zmO341J6yD52um9Obl0hR5PK6tfc9zVtJIxdvrHfQUbotP9b5iGVmABsgbV5Bx6xO1xWRrhuSyc27HMAk70UkZpDTdb/xPl6ZqdU/XwZfOyFto5yuO7fTdUWk64ZmcvMu8V5AggY6XfdT752O1ls8ttMxBn5sANwcSJfXTkfqSbgiRTQP0vcmgMe9F5GggU7XHe0ddNiRRinL6bgGfux+7Ip0ee10KOgQSU8OOx2XqmqlSzPPvBpifuh9vGI7Hd1a/buL4GuXhCcvvuf45tXysyQiafToGJJJ0CFplErbbJxPWj4ovPrVd2sjr+MuAB/Bty2wVbKk6DWn606lZFKRpMyZwYPCi5zdJWkcrXxiCb8tP3QTHRuEte7xYGu5EL72TbTL5quO117Q8doiUpnFMrhhSiBNq3LlfzmHpZHuZ4FuwL/ha2iiI++/cDwqW9zpuiJSuWEZ3DQFHYklkbYOOiL1eLAtsyfh67dIk9duxxJO1xWR4gUdt7NyRdLZ6fhJ0OGVhDhbO//+XPhaJdEXpoIOEelIU6LvbaXO8l5AwgZGCTrecVrI7O38+4ud26Kb3yM9I52ua/1NNPhNJL7Bjm2wa/Xhdb33IhI2a5ScDhsN7KG9Zlxfsr2tp80ALIC0vOJ4bR2xiMS3NNJmu+Dfey8iYUOj7HR4BR0d1YqfCV92fw5BWp52vPZyjtcWkfKsmPCNsvH1Z3svImE9WC5d6KBjNjaqacujAJ6Fr80BzI90vONYiWR5MCIS24qJJ5C+5b2IhM3RRkPOwh2vNHeQ12FR7WnwZev7E9LypONOhzUKE5GY+iVe3q4E0jSPVia15I02t8qh+DrglNKLHBuXtdgisfkiXkFHLwDLOF1bRDr384Q7kdr2/A3ei0jcUKfr2qbGBLTxw+c5u6M9YwOUz3ZLbLfDs8fJcMdri0jHVkr4Bp2jBNJkg47XW37ROujwOmLpbBfhFA6L8bQVgIWQhiccr62gQySutZAme0o+1XsRGZjHey5YKkGHdZ67Dv67HUchDe84tkO37dtpna4tIh13oky1rP1Cx67ZORkaLejwaitbTnXIcfA3IqEn+QedrtsdwDpO1xaR9q2b8M35p/cCMimXHRLteOVl+JXx9Onk9zwE4HH4OyaRCbT3Ol57A8dri0jb1kv0xtzo+NmUkyGO5bLt7nR4/sV2ttsxhR/43mx7ckvEd4/zE5VF1SISg1WWrYE0RXjfz8G8juWyb7YXdFjTle/go5wkzWtKt2kc/YMv4sied2wSNm3iWfIiuVm1jN3kiGx3+wHvRWRiUafrvlvatr65jYjEa0rpz8r4Pba+o+HPjoP2Qmy2M3S/4/U3dLy2iPy011Cquxz2XibVG+Z9tIJ2msS8HDwK+w+AD+DPZrIMQGyeRyy/ZFKpiPiyLsEbJfiX8AZ3t6U2Fo8adLwEH4uU+fu+C3LGN00CJbSeQcfMCZ8hi+RkXbY/T81x3N2W2hx5zwUfr0fd6RjID6py/AvAR/C3bfDJqi8AeN/x+ts4XltE0j1a+SBAJ+qcLOZ47VejBh2VnDmNC7Lb0cQueV5lSOW4wTmvI8UnLJFc2OtvfaTnrwDGey8iI8Mcr/1MZ0HHq45bWpV0yzsDwMeI8ZcZOan0esdrTw1gY8frixTdlglOfrbySu1y5JHPYZ/RozoLOr5j6Wz0G2OD4I5AnKh8VsR0N4BvHK//G8drixTdzt4L6ILDNNgtm52Op1v/i+YOcgE8LFnh7z8jSCVLXwAnI+6gpNscr79ymeXQIlL7nePUZq28AuAi70VkZmoACzhd+6lyg44nHftfVFKGamd+hyMGy1/YFDF5D8vb3fn6IkW0C9LzZ1Ws1NzPHPMOy97p8ByNvnSFv/+81nXAjk4BMAPiuQnAROcqH02eFWlsAulWid1wSzi8ynsRGRrmeO0kgo5lKvz91l71YMRguzTHI54vANzleP0+DDxEpDF+nWDbc2u4ONl7ERlazum6X7PBW1lBx6cA3kE6N8ii48cQw7ZBS9QuCnDE0t7Pm4jUjnUC3i+xG/ood2Sl9lZy3Ln6SQv7jj4EnnDc6ai0fbb9hx2AOKx52UyI5WpW/HhOEU51tLZISjbhGPOU7K8ZK3Uxq2Mn0p8crXQWdPwXPvp0cRreg/xgjXLMcjpisYDjWuc1/JEN1USkPuz19bvEbq7twj7svYhMreh47YqDDs+8juW7+P87kCWiEWwaMI/hAufrW5Lwas5rEMnZKomVyVp36T94LyJjK6UUdDzpOFK4qzfKklZORBxWzTI34rgTwOgAux0iUh+pfYAf4TwfKncrOV13fHvDYzsKOr5sK/O0gTeqqYruoBEahrWUrV0caMS7tbe/NMCT2M+d1yCS61b6mkjH2wCO9V5ExvoDWNDp2s+216ahs2qCJxxvVlc7qFnL730Rx9IMhKI4x3sBAP7ivQCRzNhD2t+QFkv+/9Z7ERlbwfHaD7T3P3QWdHiWoQ6v4v9rJbS3Io7fA1gLMViL+/ud12BPY6s7r0EkJ2s4n99X6t5Aif+5Wsnx2vd3Nejw/HCqJuFwCie/2vC6KE8hlsQ5GHFyTbwdrb4dIoXc5bAGYPuoRDbboGNyNTsd1tzjK/hYtcp+8a8D+Afi6M98igj5Hde0Hjfs1Jp3C+c1iOTA5j4thXTYoM7nvBeRuWkBLOa4m245oV0KOjqMWBpw0yqdOtvW07QFH5ESvWxN3izB50zvRQD4O4Be3osQSZi9fv6JdIwKNLYiZys7doDu8ISknEXdB99zymrLdvZALL8F8EvvRQA4i3NrPA0J+PcjkpJ9HTtOdsWujrvnRbKe47WrDjos4cfL2jX4M24HcDliOYfjhj2NDjLR0SpZBnkvQiRB9rr5E9JxGYAbvBdRkByfdVMOOp7mtDgPywKYrka7C1ZKG0VfANcBmNF5HcfB3zSq1RfpcmMtey9JwecA9vZeREEsCmAWp2u/CuCjaoOOSZxr4qFbjUorRwUbCAduiV7unFhq83XugL8t1R5dpOIHsu0SOwb62HsRBbGe47U7rXgtN9HE84hlRA0nv96CWFYN0LbdkjkjOE1JpSJl6Qng7ITulfVMutB7EQUywvHa2QQdtcjCtd4dOwL4ArHs7rztaInCD8HfvAF3o0QiOgjAQkjDWCaPes3xKpr+AJbJIeh4yjEnon8Na9BHBa2WON55SyzKbschjrMCRFKwUGJDE20A3TveiyiQtauYW1atd8r5uy436Jjk3J10gxr+Wdag6wrEYn8PlwBY3On6tzCwjNBz4D8AengvRCSgbqx8S+X1cS+PTaVx1nO82VYp2qlKji088yE2quGfNYVHGh1m2DqwLPSbAczpdP0o3VuXUPMgkTbt47x1Xgk7xt6GDSalMXo4z/i6sZzf1DRgwKyVNHJ6C37mBzCyxhFhxJrx1zj6/dMGX9e25J5ke/IIHVOX5XpE5IeW1o8xiTQFm2igm0sX0nvh4zu2gLAcnprtdLwN4EXksdvREpWdi3iGArjJof7edoAORAzdecwylfdCRALozePXVAIOqxTUBFmfGTxe7i0n4EAXqkLsw9Azcq613wZNcloawLUOJaR3lnsu1wCWUHqs9yJEAjiWO70pGMn3VWl8vs/m0Y9WuhJ0lP0H18GSdch3+JqTTr1nkLRlNT7dVDNptysODHQOa7k3mkQrRX96tZLTFNj76FblPvFKzcfYe46TuKleQcfDzn0uNqvDn/kogN8hJjtS+neDpwU+C+AixNqqnc97ESIO5mS1SioODlIFV0RbOF775UryPSv9MLPS2dvgp17TWU8KMvysLb9i98FG1l7/iYlBEVhuy5U81xYpCvt5vwbADEjDnUFmORVRTwCbOl6/ohOQrjxB3+hcTmmdK+uRRLkDgNcR029Y796owONdBmJR2ETeUx2b3og0UhN3+GxwVwo+5hyYKMeyRbO6c3B6U72Djludf7hsOFg9fMXjmyhP+K3t2uDA4+/BBjT9GsBe3osQadBwNMuNSMFEvm9at2fJ6zOxHF9WOkajK0HHZ8yD8LJVHT94nwn+wWaBx1kNCjy+YgvjaO3i1/VehEidh0Aek9Ad3s+5W3XR9XYulb2dgWfZupqgeD38zMtKlnqx/IkLENeO7C/SiKqW85k8HEUz29gv7L0QkTqViV/lULHWVdZL5xTvRRTcug49napKt+hq0OE9u2TbOv7Zlt+xG4CXENevWU5b7xkMLfeioki2zvqxk+zM3gsRqaHBHDUxXSJ31apUND222EcrEzm6oyFBx5sAnoDvja5ndz6rM9+YRwxR2TnqdQCmrvN1nuOxRiRz8L+9j/dCRGoUSFsy3uyJ3M1P+f74rfdCCm5aACOcK5Ys3aIi1fR/sG1uLzM24GaPZBmSlQlHtQ7P1Kav83UOdZ6705ZlWUqbSmtokbb04M6xzVZJwWR2vozYyblofunQtbqU7bajkUHH5dx+9ywjbUQktydiWwHAAwBmqeM1xgHYBfGszfybVM7ARUp1Y36W52TQSlkjxbu9FyH/szP8jGcfmYYGHe85JxmO4DlovZ0B4ETEthCAR5iIVi93BO2OaNH+6erhIYlpZiXa1kjHRQGPWotq8ToXVHTGcjm+6cr/sdr22pfBTzMb0jTC/l1JmGmw2VgvPbzO9+F9xLMTgCMVeEgimlj1sT3ScS8bKHrubsv/8d557tLRimkaMGDWai48EMAHDZ4N0jqhdWiDmpVNww91644ZfejSjixnq4c1Ak2ibe0oAAfpjVGCBxzHsQFYKqySb3k2gpIYicejHEtlbYdjQFcTiasNFkYDuA9+5uI01kawibTrA/gE8RPT/s2Ook11OmaxzqgR/Z7bv2qXLhHZz+XRiQUcHzJhXQFHHFs69+a4rprKpVrsUHhWsaDBY5/fZve3qK3SW098vKpOZaUHAHgFMe3DoMhr902kvaTRM/naScUYNp+yWUwSxy6pHq3U4nilpXzVdjy6w8ck9m2wY55GJi9emsgT9bMMlCxgqqVhbIcftWT1fB4zRS55lmLoySqrek3JrodJTNb3nCouP2XJo/+FH+vLMYjH+F3SXKNF3O78BGGJhI10eYN3WKqxKBu51foY6mkeZ0Tu2noFZxOIeOnN7eiUAg7wPVUBRzw7O1//qmoCDtRwC9pqzb23mxr9xH1WwIFoHe1G3VaHrd0T2JI8qo0A3AWgv/dCpJBm5AOZ9ZNJyWEAzvNehLRZzOA9fbjqdIpaBR3XO49BH8juoR7VEvaVgm6cXnkV2+fWckch8pnvsuwnM4/3QqRQ5gfwGKs+UnImgw6JZyvn0Q8f1KJwpFZBx/d1LNEs195O1z2Iux6psJkJT9aw7fLnnAMzAXHNw+ZpFoCI1NvqzHeaO7FbbXlQu6vkPKTmAFVP59aiPUVzjUfCe1oGwHIO153CF6pno7RKzc0P4T1q9Oc9zqqRyGZig6NGtM+X4rKpzLfWeDexES5m4nUjeh5J5dYDMB/8TK7VZ3xzjQekWfMsT791zPTelm82qZiKXRGtf/4MNWoX753b05leXOOpgatuJE29+HN1WoKzgK5kd2dVesWeeePp1lodo9e6l4H3bscmAIY4XXsCr/8g0rIhy2pXrcGftTu3laPbnUOrrPRLpFpDOHTRfq5Scz1zBSZ6L0TatSwHe3rn+iBi0HE5O3d6aXbc7WiZxjoiwcBjVk7UPa7KUcnfsWKkkT1Tump55ras5L0QSdr6LB9fCum5laW8VZVASva7HO8DuClq0DGu2m5lNbAjS9W8fM0SudTGPzcxYHuCEwy7ajTfiO1nITrb6bgHwF/ZPl6kXN1ZuWY7BdMleNvuZlJ5Ct2Vi2woH+Q8nVvLo7d6tIo+O0Aznj2d1zCWiT8pNtf5GUv9Dq/ig9ie/H6VSFKavQb+xO1xm+Uj0pn5uJt5YKK36n4AG1QzP0MaZn/nzteTav2ZXo+gw56Un4GvvZzrmcEX9IbBm2d19BR3CP8ure1uV1zLF0wqluHP7TaJtLeXxmtmaf7T/HlJ0c0c4GYPRhLbzOyD5H0E914t/8Dm6EknXTRjgHaxZjyblllDrhQtwsTQY7sYxJ3Aqa8pjYz+DwOmWbwXI6HMwbynEwFMjTRdyq36FI4+Bf/bsa8mxy7kZ3ktBr61d8TxXo1KMasZyTwXP/gj7Bz8O0AL22q8zb4e9qRUiSb2ANgCafmaCVxnJ3JMJPXRzDyxfzIoTdUZ/BBTWWwa+rBEdQbnBNIhtf6ZqddOx7gAXToHBWoENZF9PKzjX6qGMIP5agCzVdg8bTvnoYBdnXNwJhPuLJlLimcx9h46M/GA4wiW8yrgSGvg3gzOazinHj8z9drpALen33KuCniPHxhRMrSb+cTkWdZbC2NZ8XF8Be3PLXK/w6lrbLW+49+bvXnrLDx//ZhIvXcdH8waxSZBH+29CKlIHwBvMqfD80F5Tu521FQ9X1AfcLS4J3si3x5x2Db9fuyhbzsAKb8ojgTwIstjy2Ef1usyCS81dq76R3bdtSMyJZrmyf5eNwfwCl+jKQccU5jXpoAjPXs5BxwtPbdqHnDUe6cDrHz4L3y9z4FfUXY7WliN/EVsR546Ozo5AMDzFcxAWQjpepizZqy6R/KwMj+gl0b6rNnX1vzgkLRMyxOC6Z3XMaxeVaj1juSfCDCPZVaej0VjuRGrAfgM6VuTP6B2Bji4k9/7KVuuv4R0/ZzB9GUcYS7psr40NzIQziHg+IzvKwo40rRfgIDjznq2vaj3Tgc4j8QGCnkazcmqEUvF5gVwS0aNqcaxrNCeGr/s4PcNYJLmgkibHZldCOAwnsNKGuZkL5rtEj9GKfUyjzvf8F6IdMlMfA/xTlpeq56J/414sV3LcktPAwN0KW3Pq0yutPHwObBy6YO4RXgQ/7ktHwFYBcBzSFszK5NGsizRPswkrvlZvv4aq9tyCThu5/uIAo50HRgg4Hi23pWGjXjBWcnNSfD3h8AzEj7mkYPNcciF3et/MPj4XTvBxycAhgfI+6lVL5ZdALzOJkxLeC9IfnJGfSWP9bZNcPx8R07joMmvvBciVbV42DPA/bMqvbpqVJR/jvP0WfCcLPKshLFMLrUP6pzMzKOWluCjb6v//XOeQduZeg6aWQHxBI+PrGJH1S4+unEGkrVyfopHvTn9XUxmpYM17dNo+rQdFKDT7bt8YKqrRuR0tDiSNeOexrNvR11KgWpoY24Bt/6AzsEXAE7m7ldpEu1UnFBs82py8yqb5f2bibRS/zEIOwDYNePjrm84lt4CKknb7Dzu6xkgifX4nIKO/nza7RNgTK+9IUVnCZbXMNE0R7azcx5/yN8seTI9LcjcnHqYwDk8FoDcl3ivlmiaWFVkLcu3DDCzop4sb+MX7JMj6TsnQD+pL9nXaky9L9TIJCo7vz8d/mxq38KI7yWW8KU4pbYcfXiG+RrP2pdn/o/lRfwZeerJD8R7mHhqFS8LeC8qcUPZPfQNjpv/deYBx1XMF1LAkYfFg4zrOKMRAUejdzpazvffDnB2dQfLglJ40rTA8E/8gMrd0zx6uYRbx/8KsOXYqIzxS9j3w7vSKwWz8hhu64RHzFdqIhvwnZTI+5aUtzt3P4AVAuzADuGQ1OyCDnNckNkj63GAWSrWYwdTG0SWu8+Y//AK553YGX1RPM2fyxtZ1aMJtz+8OS/KI4UN+HRYJO8xCH/UeyFSU1vwYcPb2Y1soOkRdAxkbod3+++RPGaxlsEpbSVfxvK/oniXiVZFZEeSN7N53P2NehIJYmb2cRnOctBKJhvnxP7ut8mkc7H8n978DGr4B3AbuxxD+T6bbdABdqy0CY7e9ufOS0rsvPpv3GqVYnmNwUfL1zsZbbUPZF7PcH6l3qm2WpN5rHqUdruydCiAv3gvAsApLLtG7kHHYFYseCd8jWEiX/QS2raswSMIayojxfQxj2Oe4ven+bqanECAsQQHQi7Br85m9hTJR9x6z6V3jfzYHDw69t7t/5bjN2xMSPZBhzkVwO7wZ5UTmyFN/VluVe54ecnfN+wL8hq7o77Grzd5XDO5QTkY1oxvVpZ8t3zNx+8zNGANqbqa/UXs70rydBlzdLwd49Ew0zPomI1vihGqE9ZJuMmOvcHvBuDYAJGzxDaJT9Ef8unmQzYrG8tdv5avscx1auneWfq9N+dD9OX3fkxuHshdt0H8tfcuZmq+Ygn5RRkdmclPrcQePREeTub0yBXyDDrARlD2gentTY64tu2mVC3ETOgUepCIyP+5nQ0LUzzmlfJ1A/AkK7G8/dWrH5L3hMXDGtWQpBNzcSBcyl5kM7FjEjjTFxFgHI+Y11bAUQg7BQk4vuDOOIoYdHzUiKl2ZfoDS4dSNp5ndEuz4ZSIxPQwP4CsS7OOU/I3CyuRIjjGcyKxd9ABBh0NzZ5tR08mt+YwhdK28JYC8EcA33kvRkR+1Bfh9zzbt5w2yV8TUwmmCVLxZl1tUeSgY2yQeuWWMtRUK1las0TAfwBYDMBD3osRkf/N3LHdjaOZ1CvFsBk76UZwBD9z3XgnkpYm2DwXpCGQZfTPD+Br5KOZZXhHsepARBp7jGyNCC/WUUrhzMjhndZh19sHAOb23v2OsNMBRv1REjkHseNnTiZze2+hxObNiKRsCo9s7SFGpbDFdFyQgANRjtuj7HS0sA58KyOG4Zl2BGziLIvjAczjvRiRTD3BdgD2XYpprUD9nx4HsGyEnbYoOx0tfhfhptD5QRJ/am0KJ5guxPttTWJEpDasKmAPvsEr4CguO8Y+C3He8/eO8tkaLej4L1vERumPn9owuEqz6P/JttTnRvmBFEnYBTxKsaNMJYoW298DTce+EMBjCCLa8QrYmvWVIO3RzXoFyYNYktN/f+69EJHE3MEyWBu4J7IcKwYjtF8YwwdLK5AIIdpOh3nLs1taG85mBnLubCt4BQC/YpaziHTsGZ7br6mAQ6g3d44jBBxg24QwAUfUoKOlL/zbiGEgt0uLYArL+qwz636adCnSpncAbANgCc5NEWlxLI/YEGSm2HEIJmrQ8S0TX6KwMcRboDi+ZXXLXCyz+tJ7QSJBZlbszw8VOyfXjCMp9Qv2Q4pi/wglsinkdJS6LlAnty84iXYUimc6/gDvq+ZiUkAWhJ8M4Ei+D4i0NVvFGlzOEOTW3MkO2+FEDzrmYDc3OyeL4Bb2uChqpUd/JsxZSeBU3osRqTMrJz8FwAmcWSHS3omBHbOtFuT2TGK7fZs8Hk7U45XSs1PL74hiHQA7o7g+AXAAW+mewidAkdx8BuAQljwerIBDOrF/oIADnFwcMuBIYafD9GCWeIS5LC3j463xj0bHAzMB2B3AntwFEUnZh+xdcxZLDUU6Y8nEjwLoHmjOzwKRjwGj73S0TEu1D7Yo7Fjhiky7lVbqUwCH8xjMWj5rVLek6G3+/M7FbH8FHFJu19FLAgUcZq/IAUcqQYe5j93+orCS0nMC1WJ7s2OWM5jVvwkjf5HongSwLZsnncFdTJFyncDPgiiu5QNxaCkcr7SwSX0jWUkRxd7MapefWp75HxskFNxK/iZw1MKpHIJV1KRwqc6mwT7gv2IKQvjqypSCDnAL9LRgRz8rRuprH5D9gO0AYEf+WsTDu9zNOJsJ0SJdNT8D1n6BbuHOAP6FBKQWdDRz634pxHozW5wZ79K+biXVP1Z2rN0PaVS/glM4WVlD2KRa0zDgmC/QrbwHwKpIRGpBBziS3c5ieyGOmwGsrw6FZZutZPfDmuqI1NLHbOd/Bo9kRWrBHpSuArBhsHy6hQG8gUSk+LT5ImvoI1kXwIHei0jIewAOBTCEOR9XRWzXK0n5lpUE6zKQ/a0CDqmxg4MFHObPKQUcqe50tARL93EqahSTucVl65KubVva7IItObXTjmNEOmJJoHezsu0aAF/rdkmdrMsjuqZgk8GXTe3YMNWgA+yKaQ26+iCO0QCW5pO8VNd0bBMGICsFe6GLv+cZaNgRygfei5HszcMP+GkRq4hhSc57SUrKQQc40c9avkbyDCta1GCoNmbhlN/NASxToz9T0mMVYtdzCGTYFs+SZQOwR5lLGMnfAfwJCUo96DC3AlgLsdgb48ZKLK25AayAWY9HMJFK1qS2rFHXHQw0buQuokgjNbGny2bBbvtz3FFPMg8uh6DDnoRfCNY0zBzNiaxSHz2Z0zOCQYh1lZS0Wf+MGxi0W6nrOO8FSaFZccBRiJcwvSSnrycph6DDbB2sTXoLKws913sRBTp3HcFdL+uGqtk48Y0F8ACAe9lr4MnUkuIkW1alcnXAfLI9gjXILGzQAZZd2pFGJBMBrK6KloazypdFmITa8mXJqeLLdi4eYoBxL5PzLCFOJJJl+TNqwz0juZ4VfknLKeiYiQlmNqMlks+ZAKkJrH6a2Lq4JQBZWU3JGvazb7sX9/NN/L+cfSISecf0kYAPKR/yQcomeyctp6ADjAJt0l401hVxuegjhwsWhAwGMIxfi/P7HN4LyyDAeJI7GPb9HQ1Uk4T0Z8Bh7Rii9aNZk3lOycst6DBncr5HNHex8kLbyXHNAGCxkiBkGBNU1ajsx2+AbwN4FcDTCjAkE73ZaC5iWf4/AfwOmcgx6LBzuIf5gRHN2QyINE47HT24AzKUW69DS349Z8YByScMLFq+RvL7GyxnFclFN+YERsyXeIq75NkcS+YYdIDbY08FrWCwEqw/eC9Cah6Q2AtpEICB/BpU8j3ScMKWgWgf8mtUq+8fMP/IjktEinDUehKAPREz8Xrx3GYI5Rp0gJUsFr1G9IeA9d9Svze1aUsCkOnY5bAPv5d+tfw7a3rWveTPmNLGr1u+23HdN+yA+02rr9J/9xUDi490xCfy/x0A4Jig92NnAP9CZnIOOswJAPZBTLsAOMt7ESIiBWWjFS5FTFcB2BQZyj3o6MFyPau7jmYKB5pZm10REWmc9dj8yz4jonmVbc5tdzI7NiI+Z99zWNhniLntfiErWkREpDHW5E5CxIBjDICNcg04ihB0gGPmtwlaMdKdP/w2lVZEROprOGf72OymiH6d8lyVchQh6DC3ADgCMU3NKZoRS3xFRHKxAt9ro7U3b3Fk4OKHmsk9p6N1LbZ1dFsFcfsirMDzPBERqR1r+nUHK8Miug3AugAmI3NF2ekAp1da4uZoxG3Bexd7PoiISG0szg/1qAHHm/xsyj7gKFrQAQYcmwD4DjHZttN9ABbwXoiISAYW5g6H9cpB0AZgGxVpLlfRgg6wRfqOiGsQAw+bKCgiIl2zAHePbaZSVDsAeA4FUsSgAyxV/Tvi6s9R4LYtKCIilVmIAYe9l0Z1XODmZHVTpETStvpkWGOuzRDXlwDWBvCY90JERBKxFIBbg+9w3M1+IZZrWChF3ekA+3Zsx9HcUU3H80irahERkc77cNwdPOB4jS3YCxdwFD3oMN8C2ADA+4irHzOvV/VeiIhIYL9gTyYbmojArRHWAfApCqroQQc4znt9AGMRV28ANwFYy3shIiIBbcPGWr0Q1zh+1ryBAlPQ8YNnAPwqeJ20ddG7HsDG3gsREQlkLwD/YQPIqOyzZSvl5ynoKGX9+A9CbDYv4EoAe3svREQkQDHAIQBOQnx78zOm8LTT8WNHAzg3gRfaiQCOVdAoIgX+7LL3wMMR3zEATvVeRBRFLpltj407vhnA6ojvCgDbAhjvvRARkQbu+J7F6sPoLuWxSsQp5y600/FT37MtbQq9MTZjSW3k8jARkVqZie95KQQc93NUvQKOEgo62jaGE/9eQHwrsLX7nN4LERGpo/kBPApgpQTu8ssANgw858uNgo72fc6OcTYBMLr5+GJc0nshIiJ1sDrf4+ZOZLDoOkUa4lYJBR2d9/BYg9+jm5mD4kZ4L0REpIZ2ZVvzqJNiS33B0RXveC8kKgUdnXuTOx628xFdb/by+C2rXEREUtWdlXqnB+/B0eIrflY8672QyBR0lOcF5nhYrkcKf6fHcZKuBSEiIqmZhg9QqfQkGsMjlcizvEJQ0FG+xxJLDLIyLSWYikhqhgB4iB/iKRjHY+1HvBeSAgUdlbkLwJYJTQdcFMCT3PITEYnOcugeB/AzpGE8h4ZaeayUQUFH5a4BsGNCtdfTMwnrD8rzEJGgLGfjL5yo3R9pmMBZWPYwKmVS0NE15wPYI6HAw5JKjwBwOYB+3osREWlVeWcj6Q9N6MHImkj+kuuWCijo6DrLqN4p+GTa1jZlrftQ74WIiLC54dM8VknFJE4l1wC3LlDQUZ1z2I43lRwPsyAzrK3Vu4iIB9vR+B2AewEMTuivYDLf823ulXSBgo7qXchKkYlIqxztagBnqKxWRBzyzK7lVO8U+m+UBhy2u32R90JSpqCjNi7n8DVLLErJLqxuWcx7ISJSCEsAeIoVHyn5nkcq53ovJHUKOmrnWh5ZjE9wiNJj7GKqnwcRqQd7b9mLvYOsD0dKvmWPJhtTL1XSh0xt3QxgfTaLSUlPdjG1TOyB3osRkazMDuB2ACfxvSYlXwNYi+/tUgMKOmrvzoRaprdmTcSe09A4EalRsqglXT4PYLUE7+gnAIYDeMB7ITlR0FEf9/ED3AYApcYa89wI4GQAU3svRkSS7b1xDXsaWeJ6at4DsCLzT6SGFHTUj/XhX53Rcor2BPBfAEt7L0REkmJdOl8E8Auk6VX2DxnpvZAcKeioL+uHsRyA15CmhRg8Wb5HH+/FiEho0wG4AMBVAGZCmp7hDse73gvJlYKO+nsDwM8TnkDYzMqWVM9lRaT+7Dj5BQBbJ3yzHwSwCoCPvReSMwUdjfEpP7DtjDNVczJJ9hw29xERmZ5NBm1Q2ywJ345bEs7DS4qCjsbWem/KsrGUbQ/gJZ7bikhxK1O2AfAKmwym7Aw2K7P3aKkzBR2Nb6O7D4D9E5pQ25aBPLe9Un09RArH5jfdA+A/rFJJ1SS+H++W2BiLpCno8GGJmZsD+A5p2wTAywB21M+SSPZ6AzgCwLMAVkbavmYjx9R3npOjoMPPFSyp/RzpZ6z/C8DjAJb3XoyI1MX6PFb9A4Duid/jt/leZXkc0mAKOvyzpe2H/y3kMcjpQU5gnNV7MSJSE3MAuA7A9fx16h5m7yGrtBEHCjr8WSLWshm12t2KTXUOUUdTkWTZjJTf8/g0tYmw7bEHolUTbtiYBQUdMXzMklprPZ7L2e/hfMPalJnuIpLGZ8IWfBg6MpMHhyl8CNo6gzy65CnoiON7AHtzQNJ45GEO5q7cDWBR78WISIdWZW7WJezLk4NvmbT/N++FyA8UdMTzH+Z5vIN8rMLBSWcq30MknEWYVHkXc7Ny8T6rbOzBR4JQ0BGTfUAvyR2CnH7WdgbwOoDjAQzwXpBIwc0O4N+cN7I28mIdUodxaKUEoqAjdut0a8t7LPLSC8C+AN4E8A8AM3gvSKSArcuP4TTVbTPLubIGjH8GsC7fQyWYpgEDVN2YgC0488QSNHPzNQOrE/hrEakPe//YA8DB7K+Tm49YPZfTDnF2FHSkde5qA+Pm8l5InXwG4CgApwIY570YkYxMC2B3Tovujzzdz4ezD70XIh3T8Uo6nmOehzXpydGMAI4G8AaAvTIp1RPxNBOAvzIp/R+ZBhxT+LBilTcKOBKgnY407cYjiZw/mO089hTufOhsVqR8gzlUctdMj2RbfMGclBu9FyLlU9CR9qTHS3jskjOrsz+PQ/JsF0RE2ma9NQ4EsD07iubMqlI2y6y1QCHoeCVdL3GGwIncYszV1DyPfpX19vbfLCL/Z36Wvr7G3Y3cAw7bAV1BAUeatNORh3UAnA9gZhTD/cz/uIUlciJFfGBcm9Uo62RW9tqeUQB2AHCr90Kk6xR05GMAjyHsDahIuz0n8JhpjPdiRBrA+tr8hrt/uVayteViAHsyj0MSpqAjL02c33IUm3AVxRhOkLQ26097L0akDhbnrob1oZiqQHf4UybOX+m9EKkNBR15WoRP/5ZsWjSWYHYWgEu1+yGJ68UpzRZsLIfiuQHATmz6JZlQ0JEvexo6lKVz3VE835TsfthsCZGUqlB24AduUfK0Sn3NUQl2XCyZUdBRjG1Za6G+GIrrce5+XM5gRCTiPBQrAd2GlRlFdQ9zVlQKmykFHcVgOx2/4yCkIp0HtzaejYTs6OVm9gAR8Tw+scFkWwNYrwClrh2x1+IfAJyceQuAwlPQUSzzAjgbwIreCwngG86ysQDkTgDfey9ICpPs/XPuaPySOxxF9xCPk0Z6L0TqT0FHMd/0dmWFSz/vxQTKkL+SAcgD6v0hdWrgtRV3NSxnQ34Y8nggcze0u1EQCjqKazYApwMY4b2QYD5g7se1AB4GMNF7QZLskaZVnGzAL9tllB9MYZ7ZHxh4SIEo6JCt2ErdJlLKj33J7oc38bsGz0lHbOdwTQYZIzg5WX46LXs3BvRSQAo6BAw4/sYSPc3jaZu1W3+UAchNfPPUlrDMAmB9BhqrFTwZtLMGfn8BcJJ2D4tNQYeUWpS7HivrtnTq/ZIA5D72FpD89WVJ6yoAVgewhPeCEnAl+27Y0aUUnIIOaYv1CzgGwBy6PWXvgjzNQXT3MxlVZ9V56ANgeQYZwwEsWdBme13xBuelaECb/H8KOqSjkfLW2+P3AHrrNlXshVZBiE3IlPh6MwF0OL+WVpDRpX44NgX6CP5a5P9T0CGdmZVvIFvqVlXldQYgj3JXxIISvSH76s5S1iV5TNLypbyMru/4/YdNCN+r8d+VZEJBh5RrBeZ7WFt1qZ6V4r4M4CkGIU9zRoxyQxoXYCzGHT2p3i3cFX1eN1M6oqBDKmGVLdsDOBzAIN26up2DtwQizwJ4DcDb6pha0c/oLOyLMS8nLSvAqJ8n2ODLZqaIdEpBh3TF1EwQszcb9fdozK7I2wxAXuf3ll+/XdAGZtMzqJivJMCwr6HKQWqINwH8EcBlKh2XSijokGqbIVkp3P4AptWtdGEBx1sMQqyMdzS/Piz59eiE8keaGcgO4tfgkl8P4i7G3Ap23ViDvL8COAPABL9lSKoUdEitnjqt0mVvlhhKzO6qpYHIJxx6N4ZfY0t+3fprLIMbm9uDMr53Yz+LvgxMW3+1/vczlAQXA1UtEtI4AMczqVx5R9JlCjqklmYGcBAHyk2lWyuSvO8AnMuOxSr7lqqp5bXU0scAfgtgHm6/aly8SJpsd+tYTsTdXQGH1Ip2OqSe7A3rEI7z7qFbLZLEMdzJLI9XV12pOQUd0qgGY/sA2BnANLrlIiF3KY8DcBpzfUTqQkGHNJIFHLswALEqBBHx9R7nLJ0N4Fv9ZUi9KegQD3bUshWAAwD8TH8FIg1nPV6OZNty5V5JwyiRVDzYm9y/ASwCYASAe/XXINIQj3COkrWEP0cBhzSadjokiiXZ62MT9nkQkdqwY5NLAJzC9voibhR0SDRzANgRwA6a7yJSdatySww9D8DnupcSgYIOiTwVdD0mnq6po0CRskzhxNdTAdzKcfMiYSjokBQM4e6HTbjVdFuRn/qCnUNP56RikZAUdEhqux/rc/djDe1+iOBxAGcBuFglr5ICBR2S8u7HTgB+o90PKRibKnwRgAsBjPRejEglFHRI6qzSZRWWAW7MibciOR6fXAHgAgAPMXdDJDkKOiQnPQGsxQBkAwB9vBckUoUJAG7ijsZNnPgqkjQFHZKr3sz/2ALAOgB6eS9IpEwPcUfjcu5wiGRDQYcUwbQANuIOyGpqPibBTGKn0OsBXMX+GiJZUtAhRdMfwLpsv25HMZp6Kx7GALidgYYdnXyqvwYpAgUdUvTBcyswABnBeRQi9TIKwA0MNO5SjoYUkYIOkf8zd0kAsrLyQKQGngNwHQONJ1V1IkWnoEOkbX3YgGwE27DPrhslZXifU5Pv4W7GO7prIv9HQYdI+c3IVir5GqobJzwyaQky7Pvruisi7VPQIdI1A1sFIT+z15NuZvZGM7hoCTRe9V6QSEoUdIjUhnVCXZEBiH1fVDkhyZsI4CXmYjzOQOMV70WJpExBh0j9KmMWBDCMX4szEOmnGx42wHiRAUbL17MAxnsvTCQnCjpEGvh6AzAPA5BhJV/WO0T8AownWGWiAEOkzhR0iPiblbsg8zIoGcovq5hp9l5cwj5hzsWrnMba8mtL9tQcExEHCjpEYg+wm4sBSGkwYr9WQPKDcQBeayOwsC/NLREJRkGHSJp6MfAYxK+BJV+l/9w/0d0SCyY+5NeoDn6twEIkIQo6RPLWDcDMrYKQviVffVr9c1v/fir+WVM6+V76a8ub+KaNrzHt/Hv7+qokoLBfiwjy8v8AMuOEaPakuY0AAAAASUVORK5CYII="/>
4
+ </svg>