From 7b2d2125495c111ce7c27554524a625f4cd68cb5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=88=98=E5=98=89=E4=BC=9F?= <8473136@qq.com>
Date: Mon, 27 Nov 2023 15:03:25 +0800
Subject: [PATCH] =?UTF-8?q?:tada:=20=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 30 ++
.prettierrc.js | 6 +
LICENSE | 2 +-
electron-builder.json5 | 46 +++
electron/api/config.ts | 42 +++
electron/api/frpc.ts | 183 ++++++++++
electron/api/github.ts | 121 +++++++
electron/api/logger.ts | 30 ++
electron/api/proxy.ts | 69 ++++
electron/electron-env.d.ts | 11 +
electron/main/index.ts | 136 ++++++++
electron/main/index111.ts | 127 +++++++
electron/preload/index.ts | 92 +++++
electron/storage/config.ts | 37 ++
electron/storage/proxy.ts | 74 ++++
electron/storage/version.ts | 38 +++
index.html | 14 +
package.json | 62 ++++
postcss.config.js | 8 +
public/logo/1024x1024.png | Bin 0 -> 38069 bytes
public/logo/128x128.png | Bin 0 -> 2171 bytes
public/logo/16x16.png | Bin 0 -> 352 bytes
public/logo/24x24.png | Bin 0 -> 488 bytes
public/logo/256x256.png | Bin 0 -> 4801 bytes
public/logo/32x32.png | Bin 0 -> 600 bytes
public/logo/48x48.png | Bin 0 -> 819 bytes
public/logo/512x512.png | Bin 0 -> 13186 bytes
public/logo/64x64.png | Bin 0 -> 1062 bytes
src/App.vue | 23 ++
src/assets/logo.png | Bin 0 -> 10430 bytes
src/layout/compoenets/AppMain.vue | 22 ++
src/layout/compoenets/Breadcrumb.vue | 28 ++
src/layout/compoenets/LeftMenu.vue | 54 +++
src/layout/index.vue | 15 +
src/main.ts | 13 +
src/router/index.ts | 82 +++++
src/styles/element.scss | 9 +
src/styles/index.scss | 21 ++
src/styles/layout.scss | 86 +++++
src/styles/reset.scss | 275 +++++++++++++++
src/styles/tailwind.scss | 3 +
src/types/global.d.ts | 9 +
src/types/shims-tsx.d.ts | 22 ++
src/types/shims-vue.d.ts | 10 +
src/utils/clone.ts | 19 ++
src/views/comingSoon/index.vue | 15 +
src/views/config/index.vue | 203 +++++++++++
src/views/download/index.vue | 116 +++++++
src/views/home/index.vue | 199 +++++++++++
src/views/logger/index.vue | 67 ++++
src/views/proxy/index.vue | 487 +++++++++++++++++++++++++++
tailwind.config.js | 20 ++
tsconfig.json | 49 +++
vite.config.ts | 105 ++++++
54 files changed, 3079 insertions(+), 1 deletion(-)
create mode 100644 .gitignore
create mode 100644 .prettierrc.js
create mode 100644 electron-builder.json5
create mode 100644 electron/api/config.ts
create mode 100644 electron/api/frpc.ts
create mode 100644 electron/api/github.ts
create mode 100644 electron/api/logger.ts
create mode 100644 electron/api/proxy.ts
create mode 100644 electron/electron-env.d.ts
create mode 100644 electron/main/index.ts
create mode 100644 electron/main/index111.ts
create mode 100644 electron/preload/index.ts
create mode 100644 electron/storage/config.ts
create mode 100644 electron/storage/proxy.ts
create mode 100644 electron/storage/version.ts
create mode 100644 index.html
create mode 100644 package.json
create mode 100644 postcss.config.js
create mode 100644 public/logo/1024x1024.png
create mode 100644 public/logo/128x128.png
create mode 100644 public/logo/16x16.png
create mode 100644 public/logo/24x24.png
create mode 100644 public/logo/256x256.png
create mode 100644 public/logo/32x32.png
create mode 100644 public/logo/48x48.png
create mode 100644 public/logo/512x512.png
create mode 100644 public/logo/64x64.png
create mode 100644 src/App.vue
create mode 100644 src/assets/logo.png
create mode 100644 src/layout/compoenets/AppMain.vue
create mode 100644 src/layout/compoenets/Breadcrumb.vue
create mode 100644 src/layout/compoenets/LeftMenu.vue
create mode 100644 src/layout/index.vue
create mode 100644 src/main.ts
create mode 100644 src/router/index.ts
create mode 100644 src/styles/element.scss
create mode 100644 src/styles/index.scss
create mode 100644 src/styles/layout.scss
create mode 100644 src/styles/reset.scss
create mode 100644 src/styles/tailwind.scss
create mode 100644 src/types/global.d.ts
create mode 100644 src/types/shims-tsx.d.ts
create mode 100644 src/types/shims-vue.d.ts
create mode 100644 src/utils/clone.ts
create mode 100644 src/views/comingSoon/index.vue
create mode 100644 src/views/config/index.vue
create mode 100644 src/views/download/index.vue
create mode 100644 src/views/home/index.vue
create mode 100644 src/views/logger/index.vue
create mode 100644 src/views/proxy/index.vue
create mode 100644 tailwind.config.js
create mode 100644 tsconfig.json
create mode 100644 vite.config.ts
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f798fbc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+dist-electron
+release
+*.local
+
+# Editor directories and files
+.vscode/.debug.env
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# lockfile
+package-lock.json
+pnpm-lock.yaml
+yarn.lock
\ No newline at end of file
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..16bb32c
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,6 @@
+module.exports = {
+ bracketSpacing: true,
+ singleQuote: false,
+ arrowParens: "avoid",
+ trailingComma: "none"
+};
diff --git a/LICENSE b/LICENSE
index 1cbdade..8d3fda8 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023 8473136
+Copyright (c) 2023 刘嘉伟
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/electron-builder.json5 b/electron-builder.json5
new file mode 100644
index 0000000..5ba2076
--- /dev/null
+++ b/electron-builder.json5
@@ -0,0 +1,46 @@
+/**
+ * @see https://www.electron.build/configuration/configuration
+ */
+{
+ "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
+ "appId": "priv.liujiawei.frpc.desktop",
+ "asar": true,
+ "productName": "Frpc-Desktop",
+ "directories": {
+ "output": "release/${version}"
+ },
+ "icon": "./public/logo/512x512.png",
+ "files": [
+ "dist",
+ "dist-electron"
+ ],
+ "mac": {
+ "target": [
+ "dmg"
+ ],
+ "artifactName": "${productName}-Mac-${version}-Installer.${ext}"
+ },
+ "win": {
+ "target": [
+ {
+ "target": "nsis",
+ "arch": [
+ "x64"
+ ]
+ }
+ ],
+ "artifactName": "${productName}-Windows-${version}-Setup.${ext}"
+ },
+ "nsis": {
+ "oneClick": false,
+ "perMachine": false,
+ "allowToChangeInstallationDirectory": true,
+ "deleteAppDataOnUninstall": false
+ },
+ "linux": {
+ "target": [
+ "AppImage"
+ ],
+ "artifactName": "${productName}-Linux-${version}.${ext}"
+ },
+}
diff --git a/electron/api/config.ts b/electron/api/config.ts
new file mode 100644
index 0000000..44cb054
--- /dev/null
+++ b/electron/api/config.ts
@@ -0,0 +1,42 @@
+import { ipcMain } from "electron";
+import { getConfig, saveConfig } from "../storage/config";
+import { listVersion } from "../storage/version";
+
+export const initConfigApi = () => {
+ ipcMain.on("config.saveConfig", async (event, args) => {
+ saveConfig(args, (err, numberOfUpdated, upsert) => {
+ event.reply("Config.saveConfig.hook", {
+ err: err,
+ numberOfUpdated: numberOfUpdated,
+ upsert: upsert
+ });
+ });
+ });
+
+ ipcMain.on("config.getConfig", async (event, args) => {
+ getConfig((err, doc) => {
+ event.reply("Config.getConfig.hook", {
+ err: err,
+ data: doc
+ });
+ });
+ });
+
+ ipcMain.on("config.versions", event => {
+ listVersion((err, doc) => {
+ event.reply("Config.versions.hook", {
+ err: err,
+ data: doc
+ });
+ });
+ });
+
+ ipcMain.on("config.hasConfig", event => {
+ getConfig((err, doc) => {
+ event.reply("Config.getConfig.hook", {
+ err: err,
+ data: doc
+ });
+ });
+ });
+};
diff --git a/electron/api/frpc.ts b/electron/api/frpc.ts
new file mode 100644
index 0000000..95c547f
--- /dev/null
+++ b/electron/api/frpc.ts
@@ -0,0 +1,183 @@
+import { app, ipcMain } from "electron";
+import { Config, getConfig } from "../storage/config";
+import { listProxy } from "../storage/proxy";
+import { getVersionById } from "../storage/version";
+
+const fs = require("fs");
+const path = require("path");
+
+const { exec, spawn } = require("child_process");
+export let frpcProcess = null;
+
+const runningCmd = {
+ commandPath: null,
+ configPath: null
+};
+
+// const getFrpc = (config: Config) => {
+// getVersionById(config.currentVersion, (err, document) => {
+// if (!err) {
+// }
+// });
+// };
+
+/**
+ * 获取选择版本的工作目录
+ * @param versionId 版本ID
+ * @param callback
+ */
+const getFrpcVersionWorkerPath = (
+ versionId: string,
+ callback: (workerPath: string) => void
+) => {
+ getVersionById(versionId, (err2, version) => {
+ if (!err2) {
+ callback(version["frpcVersionPath"]);
+ }
+ });
+};
+
+/**
+ * 生成配置文件
+ */
+export const generateConfig = (
+ config: Config,
+ callback: (configPath: string) => void
+) => {
+ listProxy((err3, proxys) => {
+ if (!err3) {
+ const proxyToml = proxys.map(m => {
+ let toml = `
+[[proxies]]
+name = "${m.name}"
+type = "${m.type}"
+localIP = "${m.localIp}"
+localPort = ${m.localPort}
+`;
+ switch (m.type) {
+ case "tcp":
+ toml += `remotePort = ${m.remotePort}`;
+ break;
+ case "http":
+ case "https":
+ toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`;
+ break;
+ default:
+ break;
+ }
+
+ return toml;
+ });
+ let toml = `
+serverAddr = "${config.serverAddr}"
+serverPort = ${config.serverPort}
+auth.method = "${config.authMethod}"
+auth.token = "${config.authToken}"
+log.to = "frpc.log"
+log.level = "debug"
+log.maxDays = 3
+webServer.addr = "127.0.0.1"
+webServer.port = 57400
+
+${proxyToml}
+ `;
+
+ // const configPath = path.join("frp.toml");
+ const filename = "frp.toml";
+ fs.writeFile(
+ path.join(app.getPath("userData"), filename), // 配置文件目录
+ toml, // 配置文件内容
+ { flag: "w" },
+ err => {
+ if (!err) {
+ callback(filename);
+ }
+ }
+ );
+ }
+ });
+};
+
+/**
+ * 启动frpc子进程
+ * @param cwd
+ * @param commandPath
+ * @param configPath
+ */
+const startFrpcProcess = (commandPath: string, configPath: string) => {
+ const command = `${commandPath} -c ${configPath}`;
+ frpcProcess = spawn(command, {
+ cwd: app.getPath("userData"),
+ shell: true
+ });
+ runningCmd.commandPath = commandPath;
+ runningCmd.configPath = configPath;
+ frpcProcess.stdout.on("data", data => {
+ console.log(`命令输出: ${data}`);
+ });
+ frpcProcess.stdout.on("error", data => {
+ console.log(`执行错误: ${data}`);
+ frpcProcess.kill("SIGINT");
+ });
+};
+
+export const reloadFrpcProcess = () => {
+ if (frpcProcess && !frpcProcess.killed) {
+ getConfig((err1, config) => {
+ if (!err1) {
+ if (config) {
+ generateConfig(config, configPath => {
+ const command = `${runningCmd.commandPath} reload -c ${configPath}`;
+ console.log("重启", command);
+ exec(command, {
+ cwd: app.getPath("userData"),
+ shell: true
+ });
+ });
+ }
+ }
+ });
+ }
+};
+
+export const initFrpcApi = () => {
+ ipcMain.handle("frpc.running", async (event, args) => {
+ if (!frpcProcess) {
+ return false;
+ } else {
+ return !frpcProcess.killed;
+ }
+ });
+
+ ipcMain.on("frpc.start", async (event, args) => {
+ getConfig((err1, config) => {
+ if (!err1) {
+ if (config) {
+ getFrpcVersionWorkerPath(
+ config.currentVersion,
+ (frpcVersionPath: string) => {
+ generateConfig(config, configPath => {
+ startFrpcProcess(
+ path.join(frpcVersionPath, "frpc"),
+ configPath
+ );
+ });
+ }
+ );
+ } else {
+ event.reply(
+ "Home.frpc.start.error.hook",
+ "请先前往设置页面,修改配置后再启动"
+ );
+ }
+ }
+ });
+ });
+
+ ipcMain.on("frpc.stop", () => {
+ if (frpcProcess && !frpcProcess.killed) {
+ console.log("关闭");
+ frpcProcess.kill();
+ }
+ });
+};
diff --git a/electron/api/github.ts b/electron/api/github.ts
new file mode 100644
index 0000000..fe896ae
--- /dev/null
+++ b/electron/api/github.ts
@@ -0,0 +1,121 @@
+import { app, BrowserWindow, ipcMain, net } from "electron";
+import { insertVersion } from "../storage/version";
+
+const fs = require("fs");
+const path = require("path");
+const zlib = require("zlib");
+const { download } = require("electron-dl");
+
+const unTarGZ = tarGzPath => {
+ const tar = require("tar");
+ const targetPath = path.resolve(path.join(app.getPath("userData"), "frp"));
+ const unzip = zlib.createGunzip();
+ const readStream = fs.createReadStream(tarGzPath);
+ if (!fs.existsSync(unzip)) {
+ fs.mkdirSync(targetPath, { recursive: true });
+ }
+ readStream.pipe(unzip).pipe(
+ tar.extract({
+ cwd: targetPath,
+ filter: filePath => path.basename(filePath) === "frpc"
+ })
+ );
+ return path.join("frp", path.basename(tarGzPath, ".tar.gz"));
+ // .on("finish", () => {
+ // console.log("解压完成!");
+ // });
+};
+export const initGitHubApi = () => {
+ // 版本
+ let versions = [];
+
+ const getVersion = versionId => {
+ return versions.find(f => f.id === versionId);
+ };
+
+ const getAdaptiveAsset = versionId => {
+ const version = getVersion(versionId);
+ const arch = process.arch;
+ const platform = process.platform;
+ const { assets } = version;
+ const asset = assets.find(
+ f => f.name.indexOf(platform) != -1 && f.name.indexOf(arch) != -1
+ );
+ return asset;
+ };
+
+ /**
+ * 获取github上的frp所有版本
+ */
+ ipcMain.on("github.getFrpVersions", async event => {
+ const request = net.request({
+ method: "get",
+ url: "https://api.github.com/repos/fatedier/frp/releases"
+ });
+ request.on("response", response => {
+ let responseData: Buffer = Buffer.alloc(0);
+ response.on("data", (data: Buffer) => {
+ responseData = Buffer.concat([responseData, data]);
+ });
+ response.on("end", () => {
+ versions = JSON.parse(responseData.toString());
+ // const borderContent: Electron.WebContents =
+ // BrowserWindow.getFocusedWindow().webContents;
+ const downloadPath = path.join(app.getPath("userData"), "download");
+ const returnVersionsData = versions
+ .filter(f => getAdaptiveAsset(f.id))
+ .map(m => {
+ const asset = getAdaptiveAsset(m.id);
+ if (asset) {
+ // const absPath = `${downloadPath}/${asset.id}_${asset.name}`;
+ const absPath = `${downloadPath}/${asset.name}`;
+ m.download_completed = fs.existsSync(absPath);
+ } else {
+ console.log("buzhicih");
+ }
+ // m.download_completed = fs.existsSync(
+ // `${downloadPath}/${asset.id}_${asset.name}`
+ // );
+ return m;
+ });
+ event.reply("Download.frpVersionHook", returnVersionsData);
+ });
+ });
+ request.end();
+ });
+
+ /**
+ * 下载请求
+ */
+ ipcMain.on("github.download", async (event, args) => {
+ const version = getVersion(args);
+ const asset = getAdaptiveAsset(args);
+ const { browser_download_url } = asset;
+ // 数据目录
+ await download(BrowserWindow.getFocusedWindow(), browser_download_url, {
+ filename: `${asset.name}`,
+ directory: path.join(app.getPath("userData"), "download"),
+ onProgress: progress => {
+ event.reply("Download.frpVersionDownloadOnProgress", {
+ id: args,
+ progress: progress
+ });
+ },
+ onCompleted: () => {
+ const frpcVersionPath = unTarGZ(
+ path.join(
+ path.join(app.getPath("userData"), "download"),
+ `${asset.name}`
+ )
+ );
+ version["frpcVersionPath"] = frpcVersionPath;
+ insertVersion(version, (err, document) => {
+ if (!err) {
+ event.reply("Download.frpVersionDownloadOnCompleted", args);
+ version.download_completed = true;
+ }
+ });
+ }
+ });
+ });
+};
diff --git a/electron/api/logger.ts b/electron/api/logger.ts
new file mode 100644
index 0000000..f76f024
--- /dev/null
+++ b/electron/api/logger.ts
@@ -0,0 +1,30 @@
+import { app, ipcMain } from "electron";
+
+const fs = require("fs");
+const path = require("path");
+export const initLoggerApi = () => {
+ const logPath = path.join(app.getPath("userData"), "frpc.log");
+
+ const readLogger = (callback: (content: string) => void) => {
+ fs.readFile(logPath, "utf-8", (error, data) => {
+ if (!error) {
+ callback(data);
+ }
+ });
+ };
+
+ ipcMain.on("logger.getLog", async (event, args) => {
+ readLogger(content => {
+ event.reply("Logger.getLog.hook", content);
+ });
+ });
+ ipcMain.on("logger.update", (event, args) => {
+ fs.watch(logPath, (eventType, filename) => {
+ if (eventType === "change") {
+ readLogger(content => {
+ event.reply("Logger.update.hook", content);
+ });
+ }
+ });
+ });
+};
diff --git a/electron/api/proxy.ts b/electron/api/proxy.ts
new file mode 100644
index 0000000..2ded76c
--- /dev/null
+++ b/electron/api/proxy.ts
@@ -0,0 +1,69 @@
+import { ipcMain } from "electron";
+import {
+ deleteProxyById,
+ getProxyById,
+ insertProxy,
+ listProxy,
+ updateProxyById
+} from "../storage/proxy";
+import { reloadFrpcProcess } from "./frpc";
+
+
+
+export const initProxyApi = () => {
+ ipcMain.on("proxy.getProxys", async (event, args) => {
+ listProxy((err, documents) => {
+ event.reply("Proxy.getProxys.hook", {
+ err: err,
+ data: documents
+ });
+ });
+ });
+
+ ipcMain.on("proxy.insertProxy", async (event, args) => {
+ delete args["_id"];
+ insertProxy(args, (err, documents) => {
+ if (!err) {
+ reloadFrpcProcess()
+ }
+ event.reply("Proxy.insertProxy.hook", {
+ err: err,
+ data: documents
+ });
+ });
+ });
+
+ ipcMain.on("proxy.deleteProxyById", async (event, args) => {
+ deleteProxyById(args, (err, documents) => {
+ if (!err) {
+ reloadFrpcProcess()
+ }
+ event.reply("Proxy.deleteProxyById.hook", {
+ err: err,
+ data: documents
+ });
+ });
+ });
+
+ ipcMain.on("proxy.getProxyById", async (event, args) => {
+ getProxyById(args, (err, documents) => {
+ event.reply("Proxy.getProxyById.hook", {
+ err: err,
+ data: documents
+ });
+ });
+ });
+
+ ipcMain.on("proxy.updateProxy", async (event, args) => {
+ if (!args._id) return;
+ updateProxyById(args, (err, documents) => {
+ if (!err) {
+ reloadFrpcProcess()
+ }
+ event.reply("Proxy.updateProxy.hook", {
+ err: err,
+ data: documents
+ });
+ });
+ });
+};
diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts
new file mode 100644
index 0000000..b4c4211
--- /dev/null
+++ b/electron/electron-env.d.ts
@@ -0,0 +1,11 @@
+///
+
+declare namespace NodeJS {
+ interface ProcessEnv {
+ VSCODE_DEBUG?: 'true'
+ DIST_ELECTRON: string
+ DIST: string
+ /** /dist/ or /public/ */
+ VITE_PUBLIC: string
+ }
+}
diff --git a/electron/main/index.ts b/electron/main/index.ts
new file mode 100644
index 0000000..a0f03cc
--- /dev/null
+++ b/electron/main/index.ts
@@ -0,0 +1,136 @@
+import { app, BrowserWindow, ipcMain, shell } from "electron";
+import { release } from "node:os";
+import { join } from "node:path";
+import { initGitHubApi } from "../api/github";
+import { initConfigApi } from "../api/config";
+import { initProxyApi } from "../api/proxy";
+import { initFrpcApi } from "../api/frpc";
+import { initLoggerApi } from "../api/logger";
+// The built directory structure
+//
+// ├─┬ dist-electron
+// │ ├─┬ main
+// │ │ └── index.js > Electron-Main
+// │ └─┬ preload
+// │ └── index.js > Preload-Scripts
+// ├─┬ dist
+// │ └── index.html > Electron-Renderer
+//
+process.env.DIST_ELECTRON = join(__dirname, "..");
+process.env.DIST = join(process.env.DIST_ELECTRON, "../dist");
+process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL
+ ? join(process.env.DIST_ELECTRON, "../public")
+ : process.env.DIST;
+
+// Disable GPU Acceleration for Windows 7
+if (release().startsWith("6.1")) app.disableHardwareAcceleration();
+
+// Set application name for Windows 10+ notifications
+if (process.platform === "win32") app.setAppUserModelId(app.getName());
+
+if (!app.requestSingleInstanceLock()) {
+ app.quit();
+ process.exit(0);
+}
+
+// Remove electron security warnings
+// This warning only shows in development mode
+// Read more on https://www.electronjs.org/docs/latest/tutorial/security
+// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
+
+let win: BrowserWindow | null = null;
+// Here, you can also use other preload
+const preload = join(__dirname, "../preload/index.js");
+const url = process.env.VITE_DEV_SERVER_URL;
+const indexHtml = join(process.env.DIST, "index.html");
+
+async function createWindow() {
+ win = new BrowserWindow({
+ title: "Main window",
+ icon: join(process.env.VITE_PUBLIC, "favicon.ico"),
+ webPreferences: {
+ preload,
+ // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
+ // Consider using contextBridge.exposeInMainWorld
+ // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
+ nodeIntegration: true,
+ contextIsolation: false
+ }
+ });
+
+ if (process.env.VITE_DEV_SERVER_URL) {
+ // electron-vite-vue#298
+ win.loadURL(url);
+ // Open devTool if the app is not packaged
+ win.webContents.openDevTools();
+ } else {
+ win.loadFile(indexHtml);
+ }
+
+ // Test actively push message to the Electron-Renderer
+ win.webContents.on("did-finish-load", () => {
+ win?.webContents.send("main-process-message", new Date().toLocaleString());
+ });
+
+ // Make all links open with the browser, not with the application
+ win.webContents.setWindowOpenHandler(({ url }) => {
+ if (url.startsWith("https:")) shell.openExternal(url);
+ return { action: "deny" };
+ });
+
+ // 隐藏菜单栏
+ const { Menu } = require("electron");
+ Menu.setApplicationMenu(null);
+ // hide menu for Mac
+ if (process.platform !== "darwin") {
+ app.dock.hide();
+ }
+ // win.webContents.on('will-navigate', (event, url) => { }) #344
+}
+
+app.whenReady().then(createWindow);
+
+app.on("window-all-closed", () => {
+ win = null;
+ if (process.platform !== "darwin") app.quit();
+});
+
+app.on("second-instance", () => {
+ if (win) {
+ // Focus on the main window if the user tried to open another
+ if (win.isMinimized()) win.restore();
+ win.focus();
+ }
+});
+
+app.on("activate", () => {
+ const allWindows = BrowserWindow.getAllWindows();
+ if (allWindows.length) {
+ allWindows[0].focus();
+ } else {
+ createWindow();
+ }
+});
+
+// New window example arg: new windows url
+ipcMain.handle("open-win", (_, arg) => {
+ const childWindow = new BrowserWindow({
+ webPreferences: {
+ preload,
+ nodeIntegration: true,
+ contextIsolation: false
+ }
+ });
+
+ if (process.env.VITE_DEV_SERVER_URL) {
+ childWindow.loadURL(`${url}#${arg}`);
+ } else {
+ childWindow.loadFile(indexHtml, { hash: arg });
+ }
+});
+
+initGitHubApi();
+initConfigApi();
+initProxyApi();
+initFrpcApi();
+initLoggerApi();
diff --git a/electron/main/index111.ts b/electron/main/index111.ts
new file mode 100644
index 0000000..71dd9b7
--- /dev/null
+++ b/electron/main/index111.ts
@@ -0,0 +1,127 @@
+import { app, BrowserWindow, ipcMain, shell } from "electron";
+import { release } from "node:os";
+import { join } from "node:path";
+import { initGitHubApi } from "../api/github";
+import { initConfigApi } from "../api/config";
+import { initProxyApi } from "../api/proxy";
+import { initFrpcApi } from "../api/frpc";
+import { initLoggerApi } from "../api/logger";
+
+// The built directory structure
+//
+// ├─┬ dist-electron
+// │ ├─┬ main
+// │ │ └── index.js > Electron-Main
+// │ └─┬ preload
+// │ └── index.js > Preload-Scripts
+// ├─┬ dist
+// │ └── index.html > Electron-Renderer
+//
+process.env.DIST_ELECTRON = join(__dirname, "..");
+process.env.DIST = join(process.env.DIST_ELECTRON, "../dist");
+process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL
+ ? join(process.env.DIST_ELECTRON, "../public")
+ : process.env.DIST;
+
+// Disable GPU Acceleration for Windows 7
+if (release().startsWith("6.1")) app.disableHardwareAcceleration();
+
+// Set application name for Windows 10+ notifications
+if (process.platform === "win32") app.setAppUserModelId(app.getName());
+
+if (!app.requestSingleInstanceLock()) {
+ app.quit();
+ process.exit(0);
+}
+
+// Remove electron security warnings
+// This warning only shows in development mode
+// Read more on https://www.electronjs.org/docs/latest/tutorial/security
+// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
+
+let win: BrowserWindow | null = null;
+// Here, you can also use other preload
+const preload = join(__dirname, '../preload/index.js')
+const url = process.env.VITE_DEV_SERVER_URL;
+const indexHtml = join(process.env.DIST, "index.html");
+
+async function createWindow() {
+ win = new BrowserWindow({
+ width: 800,
+ height: 600,
+ resizable: false,
+ title: "Frpc Desktop",
+ icon: join(process.env.VITE_PUBLIC, "favicon.ico"),
+ webPreferences: {
+ preload,
+ // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
+ // Consider using contextBridge.exposeInMainWorld
+ // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
+ nodeIntegration: true,
+ contextIsolation: false
+ }
+ });
+
+ if (process.env.VITE_DEV_SERVER_URL) {
+ // electron-vite-vue#298
+ win.loadURL(url);
+ // Open devTool if the app is not packaged
+ win.webContents.openDevTools();
+ } else {
+ win.loadFile(indexHtml);
+ }
+
+ // Make all links open with the browser, not with the application
+ win.webContents.setWindowOpenHandler(({ url }) => {
+ if (url.startsWith("https:")) shell.openExternal(url);
+ return { action: "deny" };
+ });
+ // win.webContents.on('will-navigate', (event, url) => { }) #344
+}
+
+app.whenReady().then(createWindow);
+
+app.on("window-all-closed", () => {
+ win = null;
+ if (process.platform !== "darwin") app.quit();
+});
+
+app.on("second-instance", () => {
+ if (win) {
+ // Focus on the main window if the user tried to open another
+ if (win.isMinimized()) win.restore();
+ win.focus();
+ }
+});
+
+app.on("activate", () => {
+ const allWindows = BrowserWindow.getAllWindows();
+ if (allWindows.length) {
+ allWindows[0].focus();
+ } else {
+ createWindow();
+ }
+});
+
+// New window example arg: new windows url
+ipcMain.handle('open-win', (_, arg) => {
+ const childWindow = new BrowserWindow({
+ webPreferences: {
+ preload,
+ nodeIntegration: true,
+ contextIsolation: false,
+ },
+ })
+
+ if (process.env.VITE_DEV_SERVER_URL) {
+ childWindow.loadURL(`${url}#${arg}`)
+ } else {
+ childWindow.loadFile(indexHtml, { hash: arg })
+ }
+})
+
+initGitHubApi();
+initConfigApi();
+initProxyApi();
+initFrpcApi();
+initLoggerApi();
diff --git a/electron/preload/index.ts b/electron/preload/index.ts
new file mode 100644
index 0000000..ebf1276
--- /dev/null
+++ b/electron/preload/index.ts
@@ -0,0 +1,92 @@
+function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
+ return new Promise((resolve) => {
+ if (condition.includes(document.readyState)) {
+ resolve(true)
+ } else {
+ document.addEventListener('readystatechange', () => {
+ if (condition.includes(document.readyState)) {
+ resolve(true)
+ }
+ })
+ }
+ })
+}
+
+const safeDOM = {
+ append(parent: HTMLElement, child: HTMLElement) {
+ if (!Array.from(parent.children).find(e => e === child)) {
+ return parent.appendChild(child)
+ }
+ },
+ remove(parent: HTMLElement, child: HTMLElement) {
+ if (Array.from(parent.children).find(e => e === child)) {
+ return parent.removeChild(child)
+ }
+ },
+}
+
+/**
+ * https://tobiasahlin.com/spinkit
+ * https://connoratherton.com/loaders
+ * https://projects.lukehaas.me/css-loaders
+ * https://matejkustec.github.io/SpinThatShit
+ */
+function useLoading() {
+ const className = `loaders-css__square-spin`
+ const styleContent = `
+@keyframes square-spin {
+ 25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
+ 50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }
+ 75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }
+ 100% { transform: perspective(100px) rotateX(0) rotateY(0); }
+}
+.${className} > div {
+ animation-fill-mode: both;
+ width: 50px;
+ height: 50px;
+ background: #fff;
+ animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
+}
+.app-loading-wrap {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #282c34;
+ z-index: 9;
+}
+ `
+ const oStyle = document.createElement('style')
+ const oDiv = document.createElement('div')
+
+ oStyle.id = 'app-loading-style'
+ oStyle.innerHTML = styleContent
+ oDiv.className = 'app-loading-wrap'
+ oDiv.innerHTML = `
`
+
+ return {
+ appendLoading() {
+ safeDOM.append(document.head, oStyle)
+ safeDOM.append(document.body, oDiv)
+ },
+ removeLoading() {
+ safeDOM.remove(document.head, oStyle)
+ safeDOM.remove(document.body, oDiv)
+ },
+ }
+}
+
+// ----------------------------------------------------------------------
+
+const { appendLoading, removeLoading } = useLoading()
+domReady().then(appendLoading)
+
+window.onmessage = (ev) => {
+ ev.data.payload === 'removeLoading' && removeLoading()
+}
+
+setTimeout(removeLoading, 4999)
diff --git a/electron/storage/config.ts b/electron/storage/config.ts
new file mode 100644
index 0000000..625e427
--- /dev/null
+++ b/electron/storage/config.ts
@@ -0,0 +1,37 @@
+import Datastore from "nedb";
+import path from "path";
+import { app } from "electron";
+
+const configDB = new Datastore({
+ autoload: true,
+ filename: path.join(app.getPath("userData"), "config.db")
+});
+
+export type Config = {
+ currentVersion: any;
+ serverAddr: string;
+ serverPort: number;
+ authMethod: string;
+ authToken: string;
+};
+
+/**
+ * 保存
+ */
+export const saveConfig = (
+ document: Config,
+ cb?: (err: Error | null, numberOfUpdated: number, upsert: boolean) => void
+) => {
+ document["_id"] = "1";
+ configDB.update({ _id: "1" }, document, { upsert: true }, cb);
+};
+
+/**
+ * 查找
+ * @param cb
+ */
+export const getConfig = (
+ cb: (err: Error | null, document: Config) => void
+) => {
+ configDB.findOne({ _id: "1" }, cb);
+};
diff --git a/electron/storage/proxy.ts b/electron/storage/proxy.ts
new file mode 100644
index 0000000..4b85855
--- /dev/null
+++ b/electron/storage/proxy.ts
@@ -0,0 +1,74 @@
+import Datastore from "nedb";
+import path from "path";
+import { app } from "electron";
+
+const proxyDB = new Datastore({
+ autoload: true,
+ filename: path.join(app.getPath("userData"), "proxy.db")
+});
+
+export type Proxy = {
+ _id: string;
+ name: string;
+ type: string;
+ localIp: string;
+ localPort: number;
+ remotePort: number;
+ customDomains: string[];
+};
+/**
+ * 新增代理
+ * @param proxy
+ * @param cb
+ */
+export const insertProxy = (
+ proxy: Proxy,
+ cb?: (err: Error | null, document: Proxy) => void
+) => {
+ console.log("新增", proxy);
+ proxyDB.insert(proxy, cb);
+};
+
+/**
+ * 删除代理
+ * @param _id
+ * @param cb
+ */
+export const deleteProxyById = (
+ _id: string,
+ cb?: (err: Error | null, n: number) => void
+) => {
+ proxyDB.remove({ _id: _id }, cb);
+};
+
+/**
+ * 修改代理
+ */
+export const updateProxyById = (
+ proxy: Proxy,
+ cb?: (err: Error | null, numberOfUpdated: number, upsert: boolean) => void
+) => {
+ proxyDB.update({ _id: proxy._id }, proxy, {}, cb);
+};
+
+/**
+ * 查找
+ * @param cb
+ */
+export const listProxy = (
+ callback: (err: Error | null, documents: Proxy[]) => void
+) => {
+ proxyDB.find({}, callback);
+};
+
+/**
+ * 根据id查询
+ * @param id
+ * @param callback
+ */
+export const getProxyById = (
+ id: string,
+ callback: (err: Error | null, document: Proxy) => void
+) => {
+ proxyDB.findOne({ _id: id }, callback);
+};
diff --git a/electron/storage/version.ts b/electron/storage/version.ts
new file mode 100644
index 0000000..ce9a80c
--- /dev/null
+++ b/electron/storage/version.ts
@@ -0,0 +1,38 @@
+import Datastore from "nedb";
+import path from "path";
+import { Proxy } from "./proxy";
+import { app } from "electron";
+
+const versionDB = new Datastore({
+ autoload: true,
+ filename: path.join(app.getPath("userData"), "version.db")
+});
+
+/**
+ * 新增代理
+ * @param proxy
+ * @param cb
+ */
+export const insertVersion = (
+ version: any,
+ cb?: (err: Error | null, document: any) => void
+) => {
+ versionDB.insert(version, cb);
+};
+
+/**
+ * 查找
+ * @param cb
+ */
+export const listVersion = (
+ callback: (err: Error | null, documents: any[]) => void
+) => {
+ versionDB.find({}, callback);
+};
+
+export const getVersionById = (
+ id: string,
+ callback: (err: Error | null, document: any) => void
+) => {
+ versionDB.findOne({ id: id }, callback);
+};
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..d1364fd
--- /dev/null
+++ b/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ Frpc Desktop
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..71d4896
--- /dev/null
+++ b/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "Frpc-Desktop",
+ "version": "1.0.0",
+ "main": "dist-electron/main/index.js",
+ "description": "一个frpc桌面客户端",
+ "author": "刘嘉伟 <8473136@qq.com>",
+ "license": "MIT",
+ "private": true,
+ "keywords": [
+ "electron",
+ "rollup",
+ "vite",
+ "vue3",
+ "vue"
+ ],
+ "debug": {
+ "env": {
+ "VITE_DEV_SERVER_URL": "http://127.0.0.1:3344/"
+ }
+ },
+ "scripts": {
+ "dev": "vite",
+ "build": "vue-tsc --noEmit && vite build && electron-builder",
+ "build:win": "vue-tsc --noEmit && vite build && electron-builder -w",
+ "preview": "vite preview",
+ "electron:generate-icons": "electron-icon-builder --input=./public/logo.png --output=build --flatten"
+ },
+ "devDependencies": {
+ "@iconify/vue": "^4.1.1",
+ "@types/nedb": "^1.8.16",
+ "@vitejs/plugin-vue": "^4.3.3",
+ "@vue/eslint-config-prettier": "^7.1.0",
+ "@vueuse/core": "^9.13.0",
+ "autoprefixer": "^10.4.15",
+ "cssnano": "^6.0.1",
+ "electron": "^26.0.0",
+ "electron-builder": "^24.6.3",
+ "element-plus": "^2.4.2",
+ "eslint-plugin-prettier": "^4.2.1",
+ "moment": "^2.29.4",
+ "nedb": "^1.8.0",
+ "node-cmd": "^5.0.0",
+ "prettier": "^2.8.8",
+ "sass": "^1.66.1",
+ "sass-loader": "^13.3.2",
+ "tailwindcss": "^3.3.3",
+ "tree-kill": "^1.2.2",
+ "typescript": "^5.1.6",
+ "vite": "^4.4.9",
+ "vite-plugin-electron": "^0.15.3",
+ "vite-plugin-electron-renderer": "^0.14.5",
+ "vue-tsc": "^1.8.8",
+ "vue-types": "^5.1.1",
+ "electron-icon-builder": "^2.0.1",
+ "vue": "^3.3.4",
+ "vue-router": "^4.2.4"
+ },
+ "dependencies": {
+ "electron-dl": "^3.5.1",
+ "tar": "^6.2.0"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..20d42d8
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ plugins: {
+ "postcss-import": {},
+ tailwindcss: {},
+ autoprefixer: {},
+ ...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {})
+ }
+};
diff --git a/public/logo/1024x1024.png b/public/logo/1024x1024.png
new file mode 100644
index 0000000000000000000000000000000000000000..f60b50619c8c467dfa070c57d343b02150084933
GIT binary patch
literal 38069
zcmY&<2Rzl^|NmX~yp)V^aZ&bG=EW^!RTL#7M6xr>x=5}htL#u@q(U|sp%5X;$c(Z_
z_U3=ySD)|i@xOWKdcV(ko!9I6TF-Od_l958*QBFnr-s2`blO_@%P<%UyhXt%$-#et
zoWdb6*i%1kyqb{@eE#D=-18~_vCXBWd+F{~yK4v;m=%SVD_qMJ&42k=-j80NXJ!*c
z6YPU|zlHwX_$?OLcBB2A&@W*YRZ9%IUQ_COdLa
z>&4&3B)8|{;`ljQ1fLrXn1J3&bhK?a4YEd(?9m8O#8Iw>KgGF$y7y^?vjEt8Ms|Mqbg{2Qc>r^wY}f5p?vHf9tpju2aSD+uT|21%ZvTyX{Z;6=CZ+Nw1
z%N~SxpI-IM?h%fY%|OV$2oeE8PT}Cxj5wZCjBpfpai+BC=}XeLrQX|g!9s}-fw{C7
zaLA_*$Zs=IgDV+4r%`klDcLjN>=}&5pxg+gj9UvQu!fQ#cq}
z4UVA15MV$y41NqlK%f}GV&Dq&21fSp7PyiDhT1SOl1Wh_h+sG#7W!`tGz$jQ```5c
z&4R8(%h0QJMeAZEIeybyiWe$b*d=oaZhppuMbwXPf4uAKRgvmcrj>a$2CHq_u-LJ&
zxYQnK+s9eM;l)^q?5Ee|Un5XiG9|(U5CTEr52!2=w-G|z?j9?+)5F=cFC%q>W7VEw
z!cVa!J&ms*wh&EkYV|B)a4K^y}KP+`1r3|vKVgK@kSSb$z|)+L$@%`O$JwOB|xPk?`BGL8J?LAIFeLok)HkWk_YH=0TuO_}0C$i2w>=
z)mEf=xA7kh_*MC~Y(5IoVC^#@6j5c%h98SYUi3ZDN}c4tW@riZErWym;8pzi|e1<^x4
zNTG|-*vLkDUZJPH!6xCSqF_-72Q5CxuYC{9lZZ*SQtTJv)W{TYWb7GSOPnL?zN?!j
zYyEfr@&s$ZDWt4b3)9D7g`C>m=cQ@dncVCcZ&f8-@9!Btpq*Pwc$n{`6CopXTszi7
zuflrPb>D7qan?CsS}`nin97$pM0Z2;^E)K7qWAnvhf7#LdAmS6V>?W`{#a~Qq@%Zb
zrSDF5&f(q8zOq2?UwI$PJ5`ISlx{f1oX-N;CigT?>Z6e~8cuhSNBE+utbf*~#e$Ee
zZD&)5MhKJqt>2OO5!4Xw^G!<*HHuSOvF~IRgE-4K=s8v7JbgCrR+sIBgxdARUpvSC
z>=9P`=f%Y&&&a#|@CMp}~R;xO7(
za%AvIniVS7?(NCz0nc)J-unUpd+e7n33xSoJ-cxaR?PxX`
zhmQY#p44a`tc?M)jw_ETSdyR8;xhtHHEzexd`7yL>jipkOgqNPA0N;5wR1ijjiw0l
zhwl=UQOX%1So{1LL~)WXB8aq*%+*kg*!WBHq+E+3ujcu{@+I%hTV41(MI^{j-IP}V3jOUehRc4Dxs)
zOo;JFA#3SD-T2X=fT^Z*i_JgVSQWZI!eonI9HW>of%2pstI`FF!%)o7IpI&$3@YH*$U*)4@>$t#xQ-2WRyqB!+kOAz~Y&y<3z|_D2Vx_N8A5a*Z3x@
zrdGQMzB=hqry5XP)j*T-&;fswUh6WgDx#
za|JIDBy$(IX)UTO>fYkS_56fO%t!Uy9%q*!uyAwf{A(Hp&J<4%~44Pv={qr!>@PqT?YyElxFNruQ(arQG%{eS0(^VO(>jb=t*7n6
zXm{VnqD9qsBTb-??;Tm(n=i@>spd+oUjJ4Pj-=k@W^t&Ll%8MP~evL-u$e)ut3Z{Sz$f-aH2V<
zpw4@#X)lLhN*MM7VWD015b~VCaQE34d*m-+RE}}M&Js`Kc
zw`_3aN5(Tz2zWg;&EAL?*W(`bpIW1npHCU`G|>G{aq7<*SIWe5v01|W%=X_w0|+0?
zVR&|E+nJ!CDabJVzNUR&4ZkmgE{7=wX@jLNOEKHif?-ZKcdHe@#I!1ZMHZJYR6Fc^
zp7ZuB3#=XeJjLWp?uB61iuJg~A598gdzxzNot+)K>RKhY67R7x^Q7(TsuPP3OhJ$V
zfQ+y5Mrs2ML~mPoYr5xFSA#0xM4_VW-b1HY<<3y@dp~Ii){f)^tFIvJb)m)8yw{(2
z9N0`HWIN9+R2LiP)b_@D!!-$KGt^?U^09mu$b)38K+u*&*F4|04fNKmaoOk(+O>`1
z8t9!-j^&`>-AIUB0=erOQfXC`UY~pBz$;i
zKz`GAUD|!_`3Hxfk1l1d%7vV{;Lb9N5!KxSQsHOQRl6|x^ueM47u{!_m(Cy@k&re<
z+R-Rds{FJGw)B0TmRukmCtX}KA*}e&VgY}WVgm;0`VNLT3IHkomqV)Fb3vnNhwXaJ
z_bC?bU;0t^lYy-J`8OEY53va+CN&9T->**;diFtO=L4yVOs7~xIFlfiL!r8JA$70{
z-fQ>7$*m8J97CuH|AH3#*@==*MjnZ?lAQ-2H_$2JL0IDoc;0gjvmL)C`oLv%C6tIC|n3P1%tVz4tmnbpo?
zj`T5N^w4q2PH8n49Ui23wWnYIwq6A68g|YCkltOYv3u-eY~1K*gO
zr64F5J-q*X*_VjPz0^O_{$1Rq+WpXC{vcA3?+pD(Pz1Eob9uG9Wo@&Q9#PmjzuoK{
zQSi1~dRUDJps9cZ;r?ojTGkhVHte7*eRk;>{;|JmW-$;?=Z&x+6Nmv6t_4Ghi9QcW
zDYc7ja5|=<=gRjH3stUcMjPJ98xWqI*w6_XztAyC3D7%IGyt_Gm8lu_qPO~epp_==
z1FG2A>>K9tb&THf1n{K-8!1ts5lQdrWDjSyRR^|18}-6XgA@tDK*$C1ZdOp4evWxb
zwe1fvrAK7;csJ9f&&KckdHjJ3G=BS4DxeqGVsw+2M_fBd+#Qdq477c=*UrA+qA?G&
z`YH%`vHfo-ay(=Otg9Bqq}S!LAIjIeGC&1dPdNJgTWEG)Z|aRC?xDg$YgfC$-|P5x
zC>NdJ2
z(f6eE>=td4Gf@5mZ!Ab9v@l2*HCcSQv4XGNh0V|UG892`gwx>u5Q2$RC!H=fp41m(
zh-VO|XNf8;*~Lr}aKHdz;sYEh!Z_OXz3vKn-xV*{=N=pTrbfRgV8sBrVDbd^C{pDT
z_vfcNw~DufHowZGeh{5#w?laGXm_(jYh$~op~SGPD)sQz|52$dJsi?DJo#5>o+2Cg
zca;Y7FNiqW3yY;nKde3ri5Q%8Ve`7WN#;laN>J>Y15^t>+DK2+Rq4oY3Z8hPaf8AG
zcwi3f32^K$fV((4X8MVI^p6gI+s=H>;JDi`+rAXiPY1FC$d>w78yI_T-JP9m^{05UPS2%X
zA@(8+EMka(I%ZZRZ=yY4RGBNsM>$|(aO`6|dks{H2W});Sv-$2t){S@y0A2fjUp?e
z15S<91#|M}adw(}s{_6%onN_9l!pd94eLegPXe`+)fjO{a`PQcrH5;Zy&LDvYWRF#
zdTk)9p$czm2nZUmY(^;Ja(3omrKCWWye9KtIK+OtdygN5^c`VI4W}n~CuiQf9DtSB
zJ5XbyKLe7~{X-r(gt{j!0os(Q2^OvWZz-Fqq{q)a<>>Y1lNV%%8|!acl7nXRUuiVs
z?=zTp4hTF*Tl6n(o}~BmUO;ZZ;($Y*okIa_0H2!?8W)nl<_+(#VIBb0MtF=8}*
z-=orQW4irCM+XuOq6@GK&?KNNu4;I4N?Wmm{L@YVdwPH%D6+yX
zLVO!cH#EB-(o@rp)h1<}M<~B8x=1j5jz9%0(`3@hk^~zbk5^=I_0GZnU2J)PCpAN3
zEm!S}#M8mU^RHE9K(+du)S}SHE^))k|#@5BA+4-UNpF{0W$_#S=tA>=3g18~ngpIWV
zUrSe(_}cbeaJgSAs*9kD#m-ESZbNBhtVM3<)U+tqp9IpNP5ZTYnN1bZzbgTrAL$G#XKbcI3Ou0vdOg#nTv2@Bvw-)}Dq*
zGb#g4KoT4SLJ3^`GJ}3(dOw9Q=Aab7>1|<FBW)ROMzTjWVcY&T0%QK=|pFuNdXcglIr
zv7krUk{UQD%K&H%5exR>!JMe3z`8?*wr_d+&%;kCTQWg@=T3-$`Z(lx$&}f(*~O9C
zk-gvV%tfDzsX^p-O&hox+PTQswv{jow(V@pNF~mk>rIAa$Fr4y#$ya}puIPwHIzv)
zo@VeNME}k75u#uxK$7a9BYie^*eoQwSeXyPx2=^R_Ya7~TTQvqUyp@r|L4_o>NO^TpoVj;{7~NGAuk=dPlYphhEgC1
zptcjnFDik0_)oE5`YxmZp@ARTUe3hvx594!+Xqg>%G6`K$f&t5s6e$Xo%#P9yCs8-
zOY#UBdx?b#+YcsMs{a%!ZxG=!>cFR;1=x8N
z@J8UU5EPDta(WTC1LR6CA-IUJBHNsw@??M)7hs$ov3TP_h`--bb|v22wAFNElRNq
zpc4RDsWvf>%Uex=d!hYsq%jp#3EQZxP7}+6&oDqs14`r<+zt-1A;68^i^yCX5xLeu
z_V1RX6aga*&U8t7sdNRp@7H`PH||Wk=eZy#9O8Ji&5kqxr`Z8QP+HV1AzB+Wbi`8+
z^EU$uSH8?D?w8IBl6p0J%GgT`?-pAtKO74
zIH(Yqny1@1S-CAGB}w7;D;fVuhMHXe36ErthrG!-3~&YV!qulZRbn6YpO2Uk^{NJvY#Q1i7Kmk{uJUvk#OKN1
z*IS*pk#?h=p@urATnyGpzzGFGWGHZh@{Hljdzs-K5o$fWW;X%7`7FruP_4x86csp{
zfV89Dsn4HtIt=F)Q(Bqi(-AKX^gc+!|62(m8jV|tAwl_AC8^+D>3&$P|6)=3K~DK$
z*|{p}|4aq&y%h}dlsaLhZ2sg5S46;v5RYd^cwNuI?$AJUj#3t+iP2C}E*+?%)$
z9xu!ee+5?lU26?=KQIlEGtJ6>1;S?w>du($v!HUN7}31ZoE&IvXKp;H^6mFu!TpmM
zGrc3@2eE?~z-_ShJ#7(JX;PEEY`n^LN>6N==QFznjSygFB`?9Pya3hQp4pUUyy=VU
zLQ!+PeyX4C@~8D?V{@07cke-KoQSJuyuF@$t}CUJDdJof-MV0>e>G^A?rv
zAAsH4`(3xBqz(H0k`^HS=3)KpitGCKxVD1Yt*vn#1Ld;4mAvA{<5`gzNBV2Al0!&2
zi~b&OaNT}>=&`YU;$U`-Aglg*;b25VHw0si*_rJj
zW}Si$E3>l(EYWg_q=jO?b=okhfmxF!v48gl4!Wai-6p>=2fAX{;M(>M~j1thXU
ziJj8MXd?R5TjABJO}Va;hjYcsWNYbWZ;V{Vk=jVh7ELB8fQd9KY%1mUNaDMi<|&0@
z25%j2D*KS5D$~Wl3e&!Oi3xChJsnI?D8Y^;Ojz~)a>Jmwa`DRNnK*|WrvJv;aNmW+
z>=^<5WSuEf=~pP0);j}MM{73sjuPfu*pZ5jQm%JFIu{umkKS7e
zf0bb2=lZKM)XvykCyNeZR$C?Yn465C{gj+d<=_5QJvVjOcIdtHV(M^pJEx{htaFz1lN(^gHy?xM>O~R6s;I=yRKhhHTiFaVY(?P&n*r
zi*Wg{GV!dhlgCVhWROmmASy1-Q
z;rdTqYFHvv_X2%p1xF{Ex0&o>-pP_w4V{jorDBTJ3DyCU<#GY>%n3?}f0!SYHYcvM
z(h_Q%cDV8P)d62BzKZ1tE-RQML060#8eXANNc(}Y$)c{-YxZN^+10LAZc&xdK&J(x
z2#-i84&=V6uLbc$`vF2@&vz$?DA-O^6O|7A6Fg?rS*f9XY?%Ier0gII0h&g9P-o^IK9Bv+%?
zs2*Zl&&9OvLRvjyxgAtoZ`#)QqnU+V_;5S#k8#@mMx|gv+O>Ocqe`eq)l5!$H|gxy
z%P|cQ?;NcdX>t5tNBmw+g7Ij;7Ka_T@BXh(CyYnTRi*Jiv5=#*1)LgStcccgYD`dK
z!>CHIQ;3mkZ10q7C2nl@8nzXv9CCN6RBi2jY4yy~15W|F`)zb=)7#-_XaybBU+O&R
zk#cpbD=R4sffIYoE=uqHthR^O-H};(P(p8&VErvAu}LUE+dTo1?SLNc3s56?SI8rQJf8^K(v{wOSS`$3
zo{o`&PGPNMZOUv?x)5wQEM&LtjQ
z;4GfgRG_Cp^~$)Ok(K`LR&NHUih07UcXE2usEWLg^>W}u^@3^Yxt|wE4s4ZEaH`8&
z$asNGz^elvMjEDQlcy4tmixzbq7E#74QRUCZ;jiSUge%Dy;_i#@#DkQm`}_uhjZnJI}1O&-s)kYEQ}*CsnOjgV79@|qnlV#Z{M>l_-V4pvdUiqNn;?&GAqTL7ZGyT?GR
zZjUypv&5--Ypk0lq|~d%NgHekpb%i{!b`Dr=}{tLL3f_%OGrXmN(Kw|4IX&c)%|U^
zKAe2+qC!Q>M85A8EgGJc4xLf@2@r?kf6H`X)i~62E<&MZp^fk#JN8$f$NZYZuDELL
zUUw9?+5MRT%@YQxH`w2qT-D6UTzf&UipVjU5
z4m6MM?Icl*lJ&;Ui!&HAYuAxjhBVbYr4`HVpOoZ!jfKag5Wo>CQmCszWU=LKg}b)i
zx0)S<^B2OKSBnI>XH^5nTmKk$)D5bZKghDsfvS2E)>^PtE;t!Yd!aEy#s*~9K45YY
z_4#ns*K=`E`suX?m7iAk`|9Ej1OALomR~&)Cai(o{|NhTzYSQXn}#K)31(Ycnpj>f{3mjCuB||+b*{6vCg9hrk(GRxv}+ux
z_nvZ3DW;z5R@DRApcOwX_-xO{99C7^4Sw7<_nviaSR^>>6pk3}yyD>lkM`~TWBJE^
z5W?oJE2;QbtJ7dv)Rr;dVQtV*1Y^GjVb*exx9^tgO-!+K8BzZB-2i=$Pl5_{q7=QB
z@^C%-eXmiK-m5Z=3j%;P+R>T7JCJs>JKys
zHk(>HCGL+s&wn_Hx2IM8`NM7DGg}kbnc1ews#4+iIxv7t+Zby3=%lpN(OU0=Lpuij
z^!X}=)sDdQ(X*Q)Bb(>t*?0AWg>i0i(=D-%rB6<@e?Z&*rS3>oC{M&Ro4k#Y!Qhl4R^g*3cAP)
z)KXNCJzN_yHrQLv`;*5kzBU`vN8qcctPAF6r*?QQ#{F3D{S~3f#i8swrjOJTiNrIm
z*LAro5CI&^0)GWecS3vb%{Aw{m>;S-2X1a?etnx#|CXWL`JKg5fgD)k610SAGKXne
zBDZPUnL3~C@ybs+;*Y&Pi}UEinf<7c@%^vwIO{m(4|T_@m(LiM(_I<7?D!y@NPHn#
zNahHit0#A*Fv>;`o=v+h?las>SG5{$PQ8-_Jn-UivH&
zZrw@Sb__Ll$1R?lr70b(I}h)&jGvNdD9C%(FLbZ8Glr2vEeZK&1o=JV438P|Lb`8OSv`rYeMUEnG
zCxogI9!m~BR?BUn^MCjDpu2DBg9{haxzNgQukP6XzNp3}{TLBf-?aTn$3UraZ9H!H
z^(NE!HOmL11ay4=n@?A-6kYov^Queyc#5fP%>m0Z)7ND$EFb9=#Y9THM+2xGB)VY!
z84Ql_lzM?exTBTJ+VGpY^7U_}`KIzJ4Lh%XQBRwValTzGTijZxtJ=SRST}OeY}@0{
zJO1l3GkQ6C?kZnZ#nzqmp_RV;3qt2!qt0EnzkMM;oAUUJZgu-h=wlhVxv{6>Bi!I8
zF2<;3lU;i$>uhgNC;IZ7N%p4_DN)NurYB0X@4UDpDf;$$sg&5;s#j)qZ}&%357WOg
z1S|)pgyGE;zzTP#KJf*f`5C|AlfQ9hi)t+VQ+TGZXC4C|J)AQ@=RBrgL{`4q`ppmJ
z*|JwX8#C+W)8I$eU%^Qu$7#C6A6&h|QPv7_w$=#wX26_m_}CE6@6;a8w2PfdKP?v=8Ev~49Y9Op;nbrVq=KcHX?1fh}%KEZ%s4Kl@PB2j1sSQT^_2+
zmG!iKeKq^l6`jnhVsGzX@eLbX&@3Olqf)i+)!6(vh(*G(o&e{2ENNL!X-&&2(ZGr%
ze=K477>=CeLb8Bu?(Kg54A>{2s1c5A?f$yfQo}e+T&Gx|)9ce;^Ri2F2zK?ITFD3j
z__CtZ%hNAUlT8=Cy<>WQCuMENbS+t>Cffi23FubY3ug|SXb{WCCcSw!!r{yJ$eWaH
z>;01Z1-w|>bHWXzk(0&}iPcTbO@x7)i`MlGZ*_$ob%lpms?pB3uK&vJr|W{zbx|aj
zdSxCSOs*fio$Mgrc>POmx@wGl<;uq^UE-kyB~HG7c6t{WYQ0wlQx3OxqRNOwO?^Fl
zQ^K^7nc0o|_x8GDM$-{d*B{@k!Z8e;^9T~FaG1VXl_AzeR)sq;4Lg33Mo0rrT|r5m
zOEUUc2IYwi!lM_k<1bl^hH`{B;|2APd5YlTe_*I=M0kR1$R9i))574$Z|Y$9s8Ib<
zNS1!fCOJOZ{svmDeoO2i;d+~#<%7HjA5GrfvQSz(F8+i|%A2iB!W1A&sLgQs25f3}
zX7pD>u1V5e=AK5N$1cLK!rR2r~@^8G2n<*o8b6
z27}a8hu5|pD_Kog35sO0Dnh^D+otowFD!(5hc7!m()+_QaE#Wr9^VwgkCU_L@YAJD
zG+sQ(eDak-Ttu%9TN9mU0fyL1!bU;&1jT>-vL3d4n5IZZEZ%f2xi2FulG0
zWpds0urKUz^ecBkZ4vnzooRixC36Mx4bq#S*zYYj_%<@#<%WGYQ8SiEY*sI4|6$~!
z&$sTQwutG&MdlHxa|!IZLE!hDEa{Q$oteX1lMaFhHoaCS?+w|aq=S`$r{T)346H>n
zLFWlv&?j*zreUQS2%!n;Q^5$M4201`#Iax0r*LG)G7;aIWG}AX9w-?=?cgj5$=1;R
z8r;*?oBU-2m9FNsAeC+w(tj@*)+9}F*3(E^W8n{EZ?vn(-&j#+lW!8zJ`r!CWTG7V&
z+amDKe#w?ZfSB!j0%s69xc&~+%0PBn9ih_BnD)OZFIkYgjE=tvZ#&d
zi=q^%jTVtegj;)26>E?`w*N@?63(t`eVdH#F3lZUx@`D$wUFB=x?D+Ty05dJ*C!`;
zN>!Zd#DBhh?IbVco9Ubq`P!*(uIySt%A|g|Q?G(v;M)n?(w?&XPYa@rdR=XiL2n*3
z`CL)37B=1eK0JCc4S`9pT?d-lgd%oI#z-_$&jV
zfIGoT@tMFyp&{&h#pjBx;f6rr6<3Qw{^HQ#7UzmK%mWx(xY^IoVr(?gk+5*MHbI0O
zYYhjb7mm0IDlvi$MTCc7%c!E!mN!w9Sg!lzc?hAwj<(m?UvaD
z`-!ub+Bc)U07{Fuu5pqC&{-~qV@W_X67Nb=)JiF&V$`{Go
z^wNwR8+ke)V#{1DUfpT-F!&U$2@@t77S8zz69NMPM9G6q;zUrJ5ZdLa_(BN5IE)6|
z21|}b#mC?fZDkKoObp62c~akTPI=^aG|0YdmA*jS!7kr&v}VsZ=O~>B4-t{D1sNDy
zBotz^m5*{)jAW=>GzW*lqgRcZhWc(+>SFr(GTmvm@J6K5?(=HcO*_>!5
zN68lrUzMa;l?>W`fF=*=zu6>*j32NqZlt6Uu?`uyRVYc`E_XA7OvIYel7qwM!7WWkWpI%45=c36Djpn*6eZ(J0;(kz#)e|LNQSfmH|2#fqEJfKB|v=6Wm9*(k|pmQDSR?EPy)%%UaSE-7!J-+7z_D
zVe6%JCkd9LjO=&HAIvn?
zRg*b>89}kDA!Qmwv(YlFA*PGy>IY4P3eEZ00iH!PcU%*Fgqal57H5^vFHjb4CWY}L
z!izNsJ|2{e7jX#CeG$~^z&fJ1T|Fz`qMeFuyIfIQqHnwMq_tIwq<2!aNLhGwJH0#i
zDj8}rB59ZCWE;j7{n1aIuzj+q(eKf~O-AoMh&p
zr7>QKoKl01kUJKo1|hv{aYQq#uqJ}qJX1CJjghouLN8$5ElURPbf2r(>{7b2-eWsMYYvac(R8I_#>WN_y=BRf
zQN405HN^vtAEkT$ro9_<(*3?y~;lXR~DkT1{<3_`lyRb!i(zM=N
zN^Uaxkod`FhEDy+c0D!&81tB$`)+T4w074bV5K86@IFn&
z!%Fy2Rq;LF_2HDm5`B{3ULY99p4TkX%_goc+XcQ!GPSnX6K1Zkyzp$`o5mD=7wgL6
zL7hk;@R1YW)M!Oh(c>aFYBG9#e?$>PLA9#ilz712&X2l#QDDbO>^GD}yJ4PR^C{b%
z|Kh;sIrGl)2@kjINo6I^l`a!It4ls`8H#pTPyMNj=w;W+;O&nT`AY$ZnoIthzN$4l
zA?fC3{!;>ABZ4lPf;5}{k?C2^o(Jkos+mu~Y-!Ko>US;Kovei%LXLebi?nGq4{EO@8=NfOlgEGz{;X%PZa68VSZvqS6%&
z<@}T5Cx{dEk}vEn9}t^6MVnMPMi56zzsL(GD$5T%XRGx1y%|;Kx2Kp8Q=3O;4!)`8
z<`qtC?h%~`JpX}ih=(LUTYxvfu7{6iy)tx4GeIEgE7DHTw|MG_Zo0DgGk2mi_?!z8
z-!$RGxkXt4Un8LcEigLJKCQk#UZk4nxA@l~N2FGC#^IGwGg=3H9T3CI<^GVaQMQrJ
zG6F)8H948M75Oz%Y-Vk~_?ENdz2t^(mCG7}3RILyAaX&&aP)cM$(%O>cUndj+jo{K0B7VCmjqnA&64cs2C9I2|c
z|1S7Hdz@CS+cI+|)00)|T<`t)+rBKFS+l(Qx2i$T2Zu{#8wt>S?h&OeCPEk;D=AN$LRQhS=;hD4LrkV@}X-d96v%f!mTWm6{
zKWE9t3ReI-uc8e5pJ1=nD_Y<(Op0$wBVe&GXPQcWLHeQhk1xMlZ*4C5m0rGgFH1Ur
z?F{u}7$*+jbV!6)A2`)PqcuQ$rWcy7t5T)UJKa1U@o_7EsTiV6x4sVZ(BJ4560
zod+(RZ*|kRXYnqx7A7Z=(06*!=sMYyUXtMPpxkhu$DC}+FXJWzFXedOE*8`5U;2!16-S#;Ek(4vX!?5b~zBgDnUJ5}A@aHRxzT4e>4o>rhZi9Amv)(8~V
zlbrSU2VUu+euq+yanwP|Y|C1A==yTFs*#z+omV;FI~F?ld1cdi5^|CrKyZvkoMx%a
zy$?!7YYksoLj|8a*{mv9Q#38mQlUGEsKPe|0FF!&jJ(9W%zCzb%WvJ+?jYyB^ID6c
zHD|5ig~MyD1_(o(9)6yWij?0ZENDfZaGs8s(EPF;+NP=Bsp|gb>eRC`CyUSx)p3VxbQ+PVjS*eFm7)M!=xJK{M!rf!Vnd@>N-(MaRud{BF*ScejO^Ir)G3(YpkXsHeS1
zB;I7kKprF6)MWIk5p=sdHalUTnmI*e3%_IE4`b;dJCL;$_FXXX1bYziA(t7+7$Gf4ULaO+{b^5fnHvuc9KN>PF3mA=
z|LrGnz9;ZZU^~S*3>?ydmy4K8)Wly=`15xtG%rVg?_Kwtq?^G;=y@u|6z`{9qI>l^UF6
zHm#Xnlg8`ZZWHhK<|i$Z{H$hPty-MjpeUe9L+Uf5DIy8fMn{1JMQGM}MdlyHVHPR=
z_h)`gG~0Gs#BZ~YOQ78t?{V}WrRvrlUM{I~$NAPP=p(roR7%&rd}(X0bN{wdT<;Dv
z3;9Bh1SfT%oxBqm=aC+H-d(Iy;3>v+|K3K^TIdPULmpRh9^Ub>Sg>dM4`+s?Z}(r`
zzq|Y?+&F6g+oSvqvYr5>eJb(H7WV&Cb8G^qhh)YJ`SIeINF*okI3^AjL_&h%CF62*
z^rNaDTEqF8cCV{yhgbq58m?I$%LTaVzxqDw)jZ?z`P3^&E8d4WyyHBQvQmm0?O*!z
zKlto1q^nA9{$A3HY*%4uhbaT<*0uKepvZlCJ}|G@w&2ZMvs=~^B$gw2QWi~-iWT~i
z%w~4d_n@V(v({&x!5o%`dcurmcs5Wet)#s9DDlScFVE^GK1Y_5l>3z%er7ED{^~lj
zjs=@y-B!alK0`Zm!~vP>1ESBg-`|g_E8d5DQwIajmfYuht0A8?{r!$V>1xR5zWyh3
zG_}@m&9wP}1*+~mUg@dI-k4{=UxvdG7qi2qGE>MPD<5h!#i-0iL{|W_ACP%lG&21g
zz_EIEWrh3MObkDSGFros+3l}hkVVshKsOrLSSsUozPMTJD)ahXPtS#~2VmPsTG|yX
zeNmc^Ce!XjnxcpEa(7%?oS=dBb;S8_V%XJ~nWwe0-CFNqM=jwJvm5T~W3eYSKJ$Zu
z>1Ov9tgg())$?M2eEw3QTZXS3FC2w*S~3x7>t&;zW0#)aA#R?gs^qz|t^dQHtq64t
z47Q~*J}34}gk%lz4ljM;z_&juE8kRSluFlT{e~yVB8R@#n2xBibe79pz{nJma5b>B19fi7y
zw|E%?p3#6DR{{=gYM-|9zdISe2p(0mxo3u@p##Y6=^4?>w;9#X9V7J{*4IFPtDBCm
z0@k;<^;C+s8h)93&)bik&d|gwNr`-W-6Hh<|CG99{2Y8~v^|cQueh_yl~qz7uXG11
zQdkd}$NMl_V0~BJW|<^XFO=NWLV{vA6iKin635xu}+9i#4TvP
zl@Cbv=)9QF_cEUEQ?L6tJHL_Nf3UF16o+rh)cuS&;#>e7ir-z|!&iPt2lgsTAFeex
zx@=JWz1D?=Kt9?Xa77n&H1+>#jnmO#Zq{#ExH;5;IF4R!ks5@a{(yQ8x(Lpu^O(HB
zmxX)3?6mF9T}WSa(vD^WEaRZyOMFs3n1P|)J+
za>0|-G~b>6_UUojmlQ_eKn-WYB!nQT-)P7zX#=t`&TcYY#nW+e&n-ebLx7iyL;c_V
zE1+hdZoC#TQGW`(Y!Vat_^6M8l#|3x)Y$7qiM>B_}rhDwX^RG84$wV3BGeaR|ue
zw5>5otM`|Ga~tPOrlqE_o1Gy$2?xx9^a8sANvykd+i*l%!DG%erP{8IC6NhRdLw3Z
z6rDAj_2VP(fU!)~sx!9x6)BN?V{RAE;gzmFwja**L>`en5a*5EWrogj2GtAPq-LvR
z?s%P}`q|NRAv|p-?WBn7Nmde3Cq=1M`ueP7q?bU_^jCf3S1J)_9|7!0FJMAOgTEgEp~C~>vC2alpj2J;Bv=cRuwzqEaoYsR<;?uqi8x=nHy^!(L-%I(*2OD<@R
zDAM_yLn|pCB#!fLjGo!JIj{=Yj#8OpA%6cMg6?H#kdgoobxD=8@^1PGwqi5C8~!Y}b~H&z&;UYYt)peqf;g&oMqqM>UTRM|
z=YFx2RE?C|+eHzlU)Nr!60u3k?7+G
zKFD<(M;Zd{QvBavGM?U#~~ObjY5h0y!RinHJ$-fN}|Nse%J~;5(Q+*fXAvXkxPO`7X}7mN-;D^&}vZn
z9k6|Dw-Nv}O=-g>nxE|m%`42{1FC`sz0KL3-W~0#irx8|zD%;9_L7reRiczd`_BKK
z|J1RRzFj^MCUEtWi4pjGgmQ&ef*jmXF*qvspjvID`u0nYxn+QTSAqzt
z<)0YFqCQ=QL^#AUo0@Id9*7i@KzsoZt3kKZ9!Zx%-XS|;E@0Uu&me6})G0iEh@bqZ
zF#+DQVIIkfybpX;p~G_LPdAGV2?Z$199|;80}xP>(s_>{8a%E8R7b@Y_ZfOz
z3vkDW5^IbJ*DGLvxpHwFL2kxlwRBvK&WMB<^qij9Z;gP<=0ZsLi&zJJB-z__jCUhZ
zuP6_)0JZV%!)e`?UX!`>Ah6P|
zpYZW$KWqc+5qw;D4&Ss->+DocII>bEln5SDs%!m2=p#VL!72=#wfXT_lPpdI7AK=I
z;EA|B3#Ra>wSy2>7Qm51wRo9oP%2*S$hA3)Ma{G(l;Gk$=SZg`MK1AJ6@+ts2EI0s
zdoNl}!rK+Z7ZAw<0SlgjQ63epm&uUO^HA?ekA6UX83Gn9c>_dwJAn)cb>klNF?As6
zQ~_+(ql^wGZqbHtxH>qJE&_710VHk7XluR9eNT!(s9abhPyj^f2d7@&F#11s!&Uro
z9xSw3R45rwTK*UC3k4rQY^W1wS{V9oUk;*=p#fmy7jN40^N4O`Cxd-w85;m%vnUcQ
zeuNScXw*pnAwC{pn?MrCBaEMygd;*oe*o(K0;C%ok{smxt`@;D{Ad9vycx)K@l83D
zxi*g;lbk>cAPZ!@Sr1lycw_~TBzyCyl@p1`Odu%937WKzmSC>h6y-
zj7*+IlIHya8K{he2HLeCXh?82f-1`mByG{Mz3zQ?o#S8Wg?yd@Gy@il!@!?@4{wS5+k^mrZ-VFQpm=IP!yGjL_ctjrrayp
zSF)rmd6)+(4=rm5$yNwppbaf6aL1xGN(giFf5Dpw3|l5Mx_zbc3LR-IYZ|Dyg+<}S
ziYWf%%isQAS62cL)%yO;n6YOaEtYJjvQ%0`B^g_0P$b)pP$@}B3XwHh6pe9Pq(Y|b
zON%ILx+)dAlaPIj?8%nF_&@J(@BMxLw@aOK-t)ZA@_m-~J?Fgdtq-^UQyL9kl+-hv$_LBL7;bU0tkmKDd46P)OLtdniENDR2F$*ZNs0MFG|y;Xg+)&Q?y6
zr!e&-(`E)l(cEg8mAmK@hac*e1
zD%Kxr@^>u{>ksQk@NpRP>`un!2$OJ3_D_pLYOlVLlh8?*oeZU06Zg#Kxu#V^
z6amppGuU~=+DZ?RO&syZ$e0`PSNjzu(?Z5PcSwcxh
z`+;~ibMt7-*3p;)@yG=IXOTGi^pP!AKML%t`?4>=au0|_DPtU{Y15}`Grmts_Scb7
z<0?*LJXpcmWB$VZR~L@^k1{jgR1d=&!Wy5B&n_{|6|vQ+$gAc2IqviW%3Pr5U43y
zDmRO%JC^=xtmnYQAy`iUbrqcw3L0oCIDfrqj^t3n(=7WB!X2TX)?@W=#?EbjLCs&%
zBS_65bzyAR9DB#f;MEUaUIIdE>wr*_KseU=t8Uxx6u*uMsa^1@F8y&GSV~Wl!~pK;
zn2x9v;64dN+u6aR^_wQLG8baJfP{$Ndc1gb
z3j7kIngWxKIAUdtKHZmO6^R_*6heU`&H&tc7S8|_fFqY&0zF0`yKdkF0iYrKl4Joi
zFC#=8jJkAoLSQ90yK0!V~FR~j((j{G;%Q`}@_TaOniiYN
zobWuQ!?umK>mwrbG8^~H{7Jx*0y7FXC{#Wm6-XA*Puo4o3?=ggM!kWzh*BPfo|aP}
zm6;XVhlb;kZhGXu11rWy^qg`iaeswvcDy}Fr`sqiZrdd`{39|$ub9T
zV_auU*HKbYR1s-5N>zSHIW5nk#nPhXQK$uxkk-YHQY3rZ<2qj)NlpqqO?i^C(!cBD
z%+M(Nc4mW3u=}~;7QYvo4d%-O(t^fD=hh92EPPpbG1e0ouk}MJOOsja?7ruq`Kt{{
z&L<*xvSX#3@5CiL^L?hcf7U-JZA&K^w}Gj4i{uYh>L35vYsa&
zf2XYf+gh@K6j(7YiGHR9{7Af#ypcMUc2T?rW2aS;1fq^4+woOys5DmbC5iG`$XHl(
zIsy{SBV{4-oVIT~V8OY&@l;WRfS7d+x$5O1F2amw>TPG8>X+|zc2w&>6H^(|^GYBI~84;HZ7bmQmQ
zi3_%ec8^hCxVk)g{bW^qt@AS_kAy5wJxL*jPZW0xqe~tudA4Yz(KIhEXGSude}siG
z#H6g=TWp$O@SIR^V-OD}$r~gESn4ohH7v=*CUO%Ox628PEU4m3P}Ua{P{qjljhlrC
zlu!bwnv_h5jKs*Xs%eV+{MxXH0$-REMe;d~i)clOatRes@r@wSMCFIl@KMk6Xc1OV
zXsS~YTng(WWL5O_z&LgkNh|m}FGU|uup|=sv7vMti9sO>;IYHDdZ7$E$`dt$a)o|u
zL?oD5aVSl6N)j^)jR;a0;#VM9iB;r)v1zK}@}l_498rCoQDdyAFNr{jIz$XpRIuoz
zSxIU?-D|h5C8^Nls7GnV%LLmRhemjJ;B9^U7p>5QZ9-ZN>WQsQ4n=v<*akiDz8v8<
zLw{W;eX5!0CyPZ~jN%sHyG#-!5+h_gY2gn6MiG&!d9Dytyx9~8ug6IqHs=FWis~!l
zBV_rL`2wXjDG|of)fc>*B;HA7{|F^b=vc+(TqcnU)^JYXh)oIVX777
zIb2&QMDZjr6bY=#FGzi&2KfjJvuUAo0qe1NOLFKDg~R4x2Cyvwe>k~Of)DF_k(a`!
zT@jN0U6hmGy{v@;6V@Piw~C`v-aR^}6|%pvnd=n$!TjS{|VT4Imb
zM&_vxDOVtW5t{dU&lV9oXPV9FJmGQM~cKdGrne%1A-PzM+fENlq(+OH4@?tg-U8e_|n;v|WBZNwBh{VuR5W3na%H@_vq81)_9_*pQ0y@+f=N
zI=4w;EIk^
zkJyGrXbUL&
zO8TkzM(P0bq*?G-h_nJii#TKu9mpBl^wXw|5*7OJv*ECLq0$f){#IDLqz~xF`fm^7ow#Cfi3!=OXTP-1
z++aT|&>ef!9++_Jc2da9#nB7r2IohE&&8?2myj?@8$8@7b;ouydF->wNbYa{%6V&V
zr^-W7H*DtyqMVEsqSdNQUcI3IL7_2>TZMjW{B-M>ZvHSZHy$!8Hy4<=&>iw~G*#)d
zQ%h-Z|J>JUC!GTC8Sz1PcZ*0Y`WRH1yJzBcXNJu~Mn;3q*XxX3j?l%&C{eYW#$vw3
zsbyW5+Mem;PyZ&NMqwU3zUQ}L$Mznd#JR4q$@bZ2y6mL(wk~F~*MT>oSoEXCVNdHC
zXqkyi#O=1OG_o$BKyl0>A(2jb?R6OV1c5G<@S}Gp6sUqwNtz
z>h$Cng+cJqjpcHTf!eS#ehtjcaG4uHMO+-!f&1}utAE}&uJ*39_*a#A?xz@8FO0%m
zxf8}J7S;+dT#WpARek<)2s<+CY)JQn;6iUpyj+lbc6*ZyjiFQz%ez@YNs4N}EG{*t
z-$e^!eYy*&3%@_Uxez$mV%lEB^zu-o8fOEanA^7pCS%&9M7$JTBODD>7>`WGNI#3p
zQ$yvcN+;k;(3@PiaI8PCJDqhZZC%*X$z)j8_r|(3mP}dq`~o3K@M4LbC8Koz4--^FE;c?nc0Jv4GCyKvp_ryWKZTR}ymTk5K+u{Qid*>)A)u@&+FK4rWGu?TR$T-znYzsE0Yc
z*&zv5H}V8vZWxR}AB)4g&w5bQJJs3f-QZAQy1>A*)mY@z{s|bcAi-h8@?v+TjdRT6
z^#}nfGiaiG`DZ1nk{hVyi9Y@8?c@qyJrUABnmabAU>~s>P_QV-q(Xv%&tiud^`j=d
zwzygub0a|iYnNkGzy7cd|I)6Akq5n?76?NeE2ysIvnT4nV2V!)SfSs_=6w<^<7oq&H;{wesB!rP)Yt9-3m5fT5$mICL>Z|6QBg?
zC^l~$g)VH8Rt}RkZkQ}$l|mn}v>+P5kVUB<)|5C2Rv<%pMMN|fStY6-<^z?|h0!3$
zR5Goc21=kH@Kc_og4L}o3|8_WddQHZDFT&QDzL5G0*x~{cztxl96%-?tq4QcJ%2+V
ziigeNHu0!E;N>R}9V>wLE2U)dOA$+Q9S{f%$K0Hf4z9#6$zlm)A4sJICnWPk=>r)K
z#L_Q4$T+exKw!~E2L$O1ZYitR|H`(83m~!r0V8KfehN6LiP|q3U6r={p!||Eq1O7$6a33K0Ao}|fDrb2
ztk0_8ItVjy0tA4;EqFgYTRohkO@>OOEYhzM|AuI=2(MjMXgDw
zpwT?ccjnU?t(0zeYr@K4~;@mcuSaAA%opq#PIIr8EE>eVjg4sO|!RSxje8ME$W
zUZg-2GGKmrL_J~^$}8Zb8p(OU(PfP!zQv^DriueRG2V#ZAwot($o+_}OTv`*k^`Oc
ziTWpSqx9KZzI+oegapW53>6}}l_daydkOWZZ>i7?Ns#*?qq$iwMQWR*e{M;KFJB-=
z4u*LwB?Cm06^G
z(z;;O!l|wJ<*{#xVoxQHR@NMewhb$#JjUA>Ug*)}9h-wnyEAYSR&zp64pF
zd~$acu~`c&U5D0J!N*eC+o0qma8e7p4s#Kc34>Y%IfquWva3O5Nb6_x=!Q70P{HzL
zs2mM9jQkfe%O=2Y^bGVio)rDjH
z8675Tf(;0uI7}gS8|WUehP+R97DCzHl)y)f)#*q2~hvP_BAg
zmG4>hrfJe=-Nu$()9n5PSMyWyR0)*8^b^nUeAol#is8ee(t5ZDy
zCT3NTZPOvttv1j(U3#fA8)ToGtCHJY5(y6-fd2>%|NBMm@i<<-XG~vP4>T2_xjpd{
zb>A2Ni>Fsp@7e*&kAS(At3o$Oxt@$n+}y1Q1sHJs7#`qwYglC2PTtnaaoiO+{;Ntv
zE7zfM4-i7i3CDCi13FHXy&S9Wm)jl0{>#Uy*)ec-5`?ouY83#p-}@A{_03d$?Z%Rs
zUo+E*Y+Wd;#8Itk)+mAXZ{DK5-MVK|aRrUBh7-0SLh5W}g-2&^T<4Yb>&x&ebOmYR
zNF@|;H(sqS+>z)#R_FTrXHV++Y32b~2>aGzWwVmUIooyn`3KVL+S7ef)1)J@4U4tj
z=2Odyt+hPadBMM<4*Z&+(SW~*(=I|dbVG(TR-
zT1^eGKp5hTLUc@e%O|U2L~3;^&1>N^%G)=p)t5EfQ0GAMl5WiZIXd8C>vgz%_ey2e8UvsXt9^<{zK;ZEb?^Yzz>HPQC
z914HlXARhz{~FFMXYHgri?ZJrJ?OYKaaaRkU);k
zFA3^X>g1_wmcCt68qlS^z$y`858J9&H+TtwiZW0eBA*~|V#&Q}odb40dcdjW)e~P|
zpWzA_YCtF$M&qSCRpd#s^VZY5pA}bJUm;!(bK4Hc7dfNGH$zfC>)+nIrk-6gK2X#b
z@@w9-!Mv<28iTD%0JWA-yofyeXys_9>&N;w{eb=9^16_pw)P3O>XP({&_RwInT=fa
zn)Mrt^QUSCbMFUEzSL4Lo$2~6?YbL=P?yOZzlXT3b1KJRzN`18L~hA+_QLSw0bN#o
zQG)vl%~-}|WKs!!B*+%XKlePVOyAVA1hr~{e|AnD7;PidS1Y2Z4EdaK`GfE4{N-&4
z+a4rr3tm}x1=DdsoF0S@2m;C-;y+nPoVHyv^|dwgzh_gmHYN^24`v*Z(eosOWM!7X
zx@&U@?y2n|BlpYZR;1Rji$oS9x7oWo
z&9lFL94*}pN=fFR0Qtn54u>_m#lFAp%N38`UKlUxb3A{1xc8fc5779E3Y-3C|A3?Vn8=O;iw&i{;E-XDa_DE1SWUTe0SIHSMY5;))Z1Nf!v`QKzre0d-pT4lm
zd^)l3oXyMYn2sW7iOzV}tO<_xMj-c8n?qnHtNK8$gU8j4|xxCIiXxe=|Q;z)*h*>NW`-3;f%0J!fJgY+8F>tcZ
zZ(;P~3a5qrWlwx@)Br1vrXnyv%GJs8;trY4lIlr!^(?2dx#4HIx*@FA(K#o3E%P#6
z2zGg3M67@8bV^iy^4Q$xU-z%+hRo#0)m|88RwnM!Z7&UY3Ed)ZkwAmGy~l+=snl2&
z7nB5d&rC*Vwio_njrZyP?p-L&4`%m-VmdqsiYEGzhlm8~-M^$x$8_;#*KWNX@~caF
z-Gc?z&1<;}CrX35O%$j%t|9Awy0eu|LEXJC8y?8@4mJo<13saq$$3hm
zn45-=bDNjvy&qZ)PQhJq7rc&bYcFQCiD<$Ely2ta$J?=tcl$XC{joB1I^);1XS%^t
zmtn!biSbPHiL&ieRxfIc=fs-_2Pw?%49<%TbdCG?0Qj=EEjaLw*FrNCPvb2KPBW>s
z5H@w_*iMF+RZ^M&{j3~3F@IF*EaCs`QfKuu^^noZt?I$El|H#Teh*o}Wcu?=11UW{
zsFqr@X^a$0vg9dTgZs+hUF}VCAGbpD=)^hoh1LY9Wc!zE3
zR(=m}U=0s9)Rg=xv8^qgE77Vg`Spni(?36GduWOZP~Li~rIk7>!wUskf7^N!{mWF%
zr6VyjhW6xg}%j3$toaTV|p-Z>1->4XO@zWoL=)`?t19Kj@kqelf4x2^jnd+T{$_ha3W2fQx0v+K)#rv@z>*QZa!
zqE|XNFM6mtD?=P~`pR|6=E>AT0q?rnTe4G!+sn$v_la;BGBIM*pIvr;-alp4R(aDU
z*66ACn#}cW{;i`DE73ttDn&HtSDPgNW|2XfTJ6ruF=uOG!
zzP@`-tAA4P%gDs>QL}ZY+0NsOd@>WjnbO&e8X7jhCmgW^YpHbvFrj
zdF~q0*~0u9<+wcj`Wneycc=VDvs;VjMw+6vmcJNr_?93%1@kk(8`fA#(+$LE6sGzy
z`d;`GPhq;gkC1ANES7xTY$WFO^r7{09G?y013PGQBZU4Sf!s^7LvupZZ{ES_T^FDj!zr
z&`}INlUM40ls(&Scdtuzb_0jHHW||EqQujeQ3^Q#1
zVOXbdtb<*^c40evcuTJ|V`;wud#!}qKrVUME)XEO;>gB3MRt}B_mrZ@%&S)1+dlJ7
zNla0vcrpb|K^)!e)NXCVmV-fk@5~E45B(wSnSDJLlNE$DpU+_*!n-QzUHbe+^8JQ)
z>DT#|(=V94y@;*$YS16igEH
z2!MESd7t2i+cjIKnL8e?zru&fy7QZDS!I~V^^*3NzWBI%qU+4xqfc9kS-qhlA}c3~dRu%X#5;#*Ae4pnJ3R~a&T~v%k|2Vc!|r>TfdJt
z(A3@5>5yZ|uM`*)r|tphY`FR~snE_cn7ym7bOQgquSNoB*{s>5@Qpjb&{OZ!x>I_m
zaQ5e8hFPXVG82J;=4~_er{|4zrSMA5d@>qeC!9mZUW-zh>6VIAOVg+oCN)7bMEEiK
zP@9-qXeTa4VdVp7b>*bRHA(K^B`spjQZLTGKJETUOQ*42@eFq(O7giH?H@cCXn3JcpE$OY4D68>~5EWZ{qVYHFW?z>ZD!6Azo@8Q1~s^cuu`0-Q8
zl}6%}{FJz_TceuP6wkHs`I*GInY`&Ay;t6g{Uy2*r{w&n&-Gu5-6waxpD*%*2peyq
zSl!sS@p@GILf5{ru4{WqMg|0WP1<-_!|BdxC#>vj*g(tqlSzkTu+LX-!ot-(N>$J3
zu509|4{lz-&fCC=KdOxX_e2(_#SKi38iUbfsPa4L>y?i7nv-DfuwM76oqDnL`48xak-_`ll4E)-JCQr?l!b
z7e7W7chSf5yO8nx8dw!?I1ov~ZHdDuR|ryDq{4a9!r=eANebyyDR={&`!1|4#DCXp
zEq`})v#oF9-0y@EYKWH*f+Wvd+R-bUU$0ylXBO(MaoxCN@>Q8;A_b!L8s_z&VL|P&
zTJ}%xvBcnGIsRi6+k2x7ncw2d_npdH)x2v1kHh<@>fX%m*5AyZ^1-gVy1Je}KDAw%
zUVH1p!C!9i+YrpTHMWe0v4
zjR2N_zc4C|%#9%t(a++)sdPA{$Gvp4c$P*spc7Cdbc+kzh;IJp9Oq^r$B*uU=RZJO
zbQ#sa0=LbA+nQ&aMO+;Qp8(Y1tjT-fM8)>FPF8e7!GpO8b**^ipNPueOMY!yEtUT+T_i^mb-!C*APD(Z6nRW7iNNGzE~fU{IswVNM{r-`|cdK}35
z^~?Bs9B@Xoy#~k0B#iinZO;iW-P=wJ`%2g~YRf_jcus7m%T)Ei8tcY%16);d1faJn
zwuf>C1!d1rBLA=t&o+}}=
z<_peRWp(xmv*w;!Z`gr1ct4Grmp!lTk0bEA#A38%Ej^(vGXkUp0eWgkY1waCE9w
z6-@$$5RG=|w!tf>N$y00jgxA1guG9gWusYazWw$A;1r8A-{Q!~qY0YKS0hRe=
zzkI$T*qzgRz=!~}wlow+YYS5?T{%ZwV(1=wA~VZ~fT+T!`&AJHSm~Z>nJU2eCWy!apVCCjz$Lp_DCR4M1493Joy;K)RtDbS
zZhA~CK3x{=J_DuzCEqQ9M1ZU$IhRR3X`qOj?A#*&&_LHY
z#yTiQ{+G;2<3#{!LJO#f-KaFfwQ$FP703aMGR!$vD#52qiW&ZU6M!2u!f?en>t1t!
z77nE}OK26W8;+$PC0eR&k&k7#5{;2mzz3b-xrPRdDbpD>B&fUMBR*kUmz1E1$suv6
zhA6ASt;`{!8~|S;TxrSjmTHRf+esAWGqhs{y)GR{?u?UDj9ZlV02C^&;RaN+mkwi}
zBoKdeg`<~q8RV13qTo|m5j|)-oyCjs5m?pYAuxi_12SiIScw1A7nmWyMm(0oDp2!y
z&j-wHw~<#;L=9-LsKM@CcbEX6cO#8^uaYs@@())A$g`>_{~jB0g269Sjuapz$X!dvadf(9zW3PnKT+>H
zk!fx5SIiV8I071;H24Jpb@#@t5Z)J0ffICaU&VqB(p`{EJa1Bl!CVqB7#3YL=o(8k
z@%LbmU=>CFElP_3ibsM#FgFC-+(*8?m4ENry^Ekn=>hC;1q+3R*-_MuMS&recsUBg
z^C2bP$Gc786;YfjjKCzez|a=7_85xV8W$$bWyx~@CsAt^kev%a^8+RrO*Ee47}_vW
z7pJnG3(3>3FM78M?O?3lTKWV(bvc!1(Q=Q;C(Tj(q;hV7pgjcJo4|lxajO!?XE;FE
zx12zPP)}!UMB|v)@T3#(A715zwMXEpuw5;4ouuJ%8n&oN&@x&Co<#w4!6pbC^lm`F
zUSqiIZ>}X}SKE=f5I(L`u1GyZJ9bf9Rbmguip9IYNz=Zy%T~NS;5cCW$r|>uTO6n!
zI{(CJJn-XD$F}&>!F7mk&7a^@qI!_jpyFtr_b$;K**ls&WPNe-zZRVpe_HCJJ?K?x
z#2#AvviLf(G=e|qcPIJ{Q@?dEXGZQgpp@+82lB1ERKurO~#>52LOTmdS
z^g2J>N@q;%mG@O({nG|gCYa)^i_j202u7<*T_Wp*h4R1N7#oy?zBoX9h+*`z*d4>z
zB0`X&J3{D7@pK~W;X+|1pIEtKS;@mA>hz^fI3-CeLuA|as3^z%&x|c$rkqi8lahTU
zln=XqPI6PpBt!Srg%qv%{>kj)L9XbVu~)X>gn=1m!k#swH`reb63x77M;qc)kxHil
z+$U@AI2W9Js(Y_)@4-z2hg<-Elo#UMa(;&=nW-SzE$UyI7
zQopgLDY2TThx9Ma_fT}-#8&mNWxgJXojhJn;9OaQk_yOpNW%HI|FOcrzOkCNgwavq
zX&!4Bo1He8l3GG{
zb*kEYwzl9!_Gt$wI6=*7v7lzS{G5Az>8x|ejC8w`O!4%IkfuR%4xfGydQgsb-78X9
z@O0b3fbRO56W&1_uZ3!Y&iM9C;Fo3|!3-=5)uAFP+8EEUuEVXDCQ-C)9e>&?pOi>+>7kw5k~
zDlc*PblKR?55pI~FPwG@Iy3C3JI%8V1l+kB(8L<{!27M<7-L%+tx*|mp`8;=B}C}$jdk6Crz+uMrUvr%ttsv0Fx2G?l0%hI`%Z6KftR$
zU)uhnX+z+behmZ|*}yfH+Tj@7zB;}tprRVCTw54i7%$Y#zAFXS@AE-{JAd-7@)6vx
zZQ(=|nweLZVDsg%f}MQVHbqTNa9+0uCPG8`lxb(B>dGw-^;Ou9PL7nEb9aR|YnyrZ
z#vwWPT^EPE`ZQDs`tv`xcH9~s$4v}Ol`IU`uJ$m`SUU(?R#TZ(h(4Jl8peoQAI9Gk
zyuWU`xA$?b?W9YxZWc^W)6aGRJkjRGOzKT)jJ#iORDbJJnGze)S3blt1;qg{};x{@_lFh!UJ&$5IG_aIbPFuFmb~Y!7
zuxq_^gNn7%R1uYjDC){9($3Pc&*asl)3-l0_|ATLcTGA)3}I~yfeN_5ACvv8uIN=`
zG;U%D8xz66GTHE?F(5O}MQH5A_MG;zqP%H#G;h(h=?6l>$h7G&59Y{4gxz=P`=TyV
za)d8Uij>Vht8Q-p-TAAnw}yf8SE~M1RFMLi!vaGx*9YHCFK3M&Dk}3Ej-K|l8QHzc
z3OP}oa%__rAP-wlGb
z*7^2LYpSVq3I}2)06_q-tT}brDX|&W6uXBvWb$oqO4<3K?BG|s_aRNH!XpOCl?W))
zWwn%?zdKvCI@oC!`VO;4s!g?gr`k)OwIf$Mu;^;kcV9gX9ly2QcVK0jx=)wR*1sL8
z7&whG&Axn&t3gaTdK5pj`g#WMwZRm>wvvLt?_UR-O?8X%x1yYz>d4U@c-+1z#b3P#
zy)MkC2fqrK_|7a~39V&*MMeL`YLUIcb
z=4&{Zr@pI_KYaMS&)MMwT}RlvVbs&HWZ`Tc`UJ4%&n5l#rSq)C-2eV7P+NQQdx6(*
z+l9%dDLhi54^m##WLrzP$=8VnTOaag$S%jZdxFW?@z
zerZy>WRmsa%cH-uU+djRW-@~+SBw(i__o{=E4a3{tl;_P!a3X9nzq4bvTsk@vN+5#
zAVunl6X>tLaBcf6=)Y@qL&KEsIQpezyAmFG5UkcVtLX(gY}Ak
zxtzQ3J=e+UP{1V1X7coRPW|M15Jd#Cr7g_(zu;c}HmF%~qdEiatonq(H(KqZ
zUlC)UI7v-0iEO!or+g2oMfpRaMwv$O)O~nr>-g%0j-#EdCbXkoMj1ksa`M13GnIkp
zJDH<<-Geoy-cEaN9MYYe&do7vFU|h~D8S|&IL3R;i>G>g<5}Ka$X=TG%N@nDqVs|Vkxbfbk{85>)(vh*OBw4K7F@?f3j^t`fD8(avf1-
zX8j4F3+98!Fu9UPXbD^xW9Q42JUf5Pr>;oC>CVvZ0Q|>~h|5ICE5Iyt;lT&+4%!ZhRy4kpqZfKDR|Wm-0N!T8&fp4M!&+_D0lvV84T
zh_`tGl#8Gm47TSt3p*TlzNl($P+DZFZ8i>Ovwr)3@8E(~{kB#p7NBO*v*nJ4ZK@rt
z{J9?E7Ti=`5XMPNudKu#6TMPR+nxo#`PU;K%I13K*j;ba
zYEW2X<)iA}g@96{lQI5U`4O{g(|s9TC4Q@?JWIV@kB+A{6c`6y%&1yyjYebQeAxT|
z9Sf_xejz0-%G8Iq&nam7ZPG&7!sW=5kF-9#LP3?$Zpg8Vo_xRUW_aSE6CQZmYqZ6G
zaK3FaV5Lakk(`kVL}T_zC_tg)V!qwjmwXigy1A|Lqy;
z$U50u-dUr1d!ku67Sj>g2Ai-LzV251^>?l3uw%&u_vsPyTu+bTA@j>|85|Z?LCFB^
zdhn+oDf_3~>8{rD@|<3%EDJ0y3$fVtWs%QSQ1_lo#=i)wzT%LP5ioYYD9|srwY|*#
z)0n(Ky;I4nLmne$8H;WIP(y7^>;(MFuJlg6df>>eDw4Ke7*JnP5XS$S5n^#b3prNZ
zC4i3|krK
z?|s&l?KjS9TF1X6?ow8Cl$Ei%UAFKn`g_Ri#Yg#kd*XLA_1ki~h=ndrI-6}G*5Ja<
zvi61rIj?g*>Sd7)_2Fn$_sxr!^;%kn%|}bmWfn}kcog11dXqtYaK}Z!psVK6<{qij
zx^zF^skK@Qy_5NoUL~caLNaydgbSwEqAG~#W2so(rg3riCZ1s4{t2qH&=5|?`Dm){
zp^|N73odR!6UrTJO;f-B{@z@aAhMjd2>HW1G8p}+v&eR?(ko}1Fu9d3J^JMyifxAm
zqetf|&24O*&b#^uZogyZQ}{$X9^qbA0)opjZcXl~FsXhQ(S
zL!SSg(6X*jr)7cNwFryARZn$L&k8o&^v+fDsloP%UerrF9JP?+f
zW=#f#Hy8BILSFbf=u}pwh4Si7oC)I(LEo_R9XG-R`d#|DO=&l{R~zovy{oqNMxU*p
z7JGCwd$j$6f1dg0cHOi+AEi@Jnb;oz4tUBts4%dfH8m#}GVf#Rw9sU#y`px7I(|hc
zzW?_fE(vF@32v6yH>Yn>xw{Xp-U#{We24dbD?y{pL9D6!B_#!(QyKQ-Z;B8^bCDIH|GviMx$97?7js&*U3H={Z7rkdrWserFMhaU!GC##F@L_d1
zJ?{y37N44W8^++@Lhi1P{Mz}J@qyUkEjHu7(f>mK+o>9NtrwTg8;m(%QL$9aU*oSI
ztBak9>5s-|Wz)P)!JWEg{-t;HFYp)g{YjReKPi49l!x=6
zQpEyyz!`sC>8|&29S4WRQyX&3UxaOQ)(Jo7T%vq8|Ka=m2G0o|-O^X*@}4