From acc64751ef236bf64dd8fd2af82b5be609139190 Mon Sep 17 00:00:00 2001 From: O96u Date: Wed, 26 Aug 2026 14:49:46 +0800 Subject: [PATCH] =?UTF-8?q?release:=20v1.1.0=20=E2=80=94=20S3=20multi-back?= =?UTF-8?q?end=20storage=20and=20admin=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 19 +- README.en.md | 74 ++-- README.md | 76 ++-- app/components/AppShell.vue | 35 +- app/components/MobileUserMenu.vue | 21 +- app/components/storage/StorageBackendCard.vue | 215 +++++++++ app/components/storage/StorageBackendForm.vue | 330 ++++++++++++++ app/components/storage/StorageUsageBar.vue | 91 ++++ app/composables/useFileSize.ts | 59 ++- .../{settings.vue => settings/index.vue} | 0 app/pages/storage.vue | 349 +++++++++++++++ app/types/storage.ts | 39 ++ docs/README.md | 53 +++ docs/screenshots/settings.png | Bin 61345 -> 77048 bytes docs/screenshots/storage.png | Bin 0 -> 52887 bytes docs/screenshots/upload.png | Bin 24126 -> 37569 bytes i18n/locales/en.json | 85 +++- i18n/locales/zh-CN.json | 85 +++- package-lock.json | 400 ++++++++++++++++- package.json | 5 +- scripts/assign-image-owner.ps1 | 146 ++++++ server/api/admin/storage/[id].delete.ts | 39 ++ server/api/admin/storage/[id].patch.ts | 129 ++++++ server/api/admin/storage/[id]/test.post.ts | 34 ++ server/api/admin/storage/index.get.ts | 39 ++ server/api/admin/storage/index.post.ts | 83 ++++ server/plugins/startup.ts | 12 +- server/utils/auto-delete.ts | 16 +- server/utils/data-dir.ts | 5 + server/utils/db.ts | 4 +- server/utils/image-index.test.ts | 14 + server/utils/image-index.ts | 418 ++++++++++++++++++ server/utils/image-response.ts | 51 ++- server/utils/process-image-upload.ts | 5 +- server/utils/serve-image.ts | 12 +- server/utils/storage-backends.ts | 406 +++++++++++++++++ server/utils/storage-capacity.test.ts | 32 ++ server/utils/storage-capacity.ts | 92 ++++ server/utils/storage.ts | 404 ++++------------- server/utils/storage/index.ts | 3 + server/utils/storage/local.ts | 116 +++++ server/utils/storage/resolver.test.ts | 26 ++ server/utils/storage/resolver.ts | 115 +++++ server/utils/storage/s3.test.ts | 45 ++ server/utils/storage/s3.ts | 218 +++++++++ server/utils/storage/types.ts | 85 ++++ 46 files changed, 4005 insertions(+), 480 deletions(-) create mode 100644 app/components/storage/StorageBackendCard.vue create mode 100644 app/components/storage/StorageBackendForm.vue create mode 100644 app/components/storage/StorageUsageBar.vue rename app/pages/{settings.vue => settings/index.vue} (100%) create mode 100644 app/pages/storage.vue create mode 100644 app/types/storage.ts create mode 100644 docs/README.md create mode 100644 docs/screenshots/storage.png create mode 100644 scripts/assign-image-owner.ps1 create mode 100644 server/api/admin/storage/[id].delete.ts create mode 100644 server/api/admin/storage/[id].patch.ts create mode 100644 server/api/admin/storage/[id]/test.post.ts create mode 100644 server/api/admin/storage/index.get.ts create mode 100644 server/api/admin/storage/index.post.ts create mode 100644 server/utils/data-dir.ts create mode 100644 server/utils/image-index.test.ts create mode 100644 server/utils/image-index.ts create mode 100644 server/utils/storage-backends.ts create mode 100644 server/utils/storage-capacity.test.ts create mode 100644 server/utils/storage-capacity.ts create mode 100644 server/utils/storage/index.ts create mode 100644 server/utils/storage/local.ts create mode 100644 server/utils/storage/resolver.test.ts create mode 100644 server/utils/storage/resolver.ts create mode 100644 server/utils/storage/s3.test.ts create mode 100644 server/utils/storage/s3.ts create mode 100644 server/utils/storage/types.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3da5a1d..221a990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ 本仓库自 **v1.0.0** 起作为正式版维护;此前历史提交已归档重置。 +## [1.1.0] - 2026-08-26 + +### 新增 + +- S3 兼容对象存储后端(R2 / AWS S3 / 腾讯云 COS / 阿里云 OSS) +- `storage_backends` + `images` 索引表,列表/搜索/统计改查 SQLite +- 混合直链:`proxy`(默认,PicHost 代理)与 `public`(302 到 CDN) +- 管理员 `/storage` 存储管理页:多后端卡片、用量条、添加/编辑/删除云存储实例 +- 可选环境变量覆盖存储配置(`STORAGE_BACKEND`、`S3_*`) + +### 改进 + +- 启动时自动将本地磁盘图片扫描迁入 `images` 索引(幂等) +- 上传/删除/直链/auto-delete 按 `backend_id` 路由到对应后端 + ## [1.0.4] - 2026-08-25 ### 新增 @@ -68,8 +83,10 @@ ### 路线图 -- **v1.1.0**(计划中):S3、Cloudflare R2 及更多对象存储后端 +- **v1.1.0**(当前 `main`):S3、Cloudflare R2 等多后端对象存储 +[1.1.0]: https://github.com/O96u/PicHost/releases/tag/v1.1.0 +[1.0.4]: https://github.com/O96u/PicHost/releases/tag/v1.0.4 [1.0.3]: https://github.com/O96u/PicHost/releases/tag/v1.0.3 [1.0.2]: https://github.com/O96u/PicHost/releases/tag/v1.0.2 [1.0.1]: https://github.com/O96u/PicHost/releases/tag/v1.0.1 diff --git a/README.en.md b/README.en.md index 775c218..1d7ed18 100644 --- a/README.en.md +++ b/README.en.md @@ -6,7 +6,7 @@

Lightweight personal image hosting · Simple uploads, clean management

-

Self-hosted / Multi-user / Docker / API / Twikoo · Zero-config setup

+

Self-hosted / Multi-user / Docker / API / Twikoo · Local disk or object storage

version @@ -17,6 +17,7 @@

+ Branches · Quick Start · Screenshots · Features · @@ -27,26 +28,43 @@ --- +## Branches + +| Branch | Notes | +| ------ | ----- | +| [**main**](https://github.com/O96u/PicHost/tree/main) (default) | **v1.1.0** — local disk + multi-backend object storage (R2 / COS / OSS / AWS), [`/storage`](docs/README.md) UI, hybrid URLs | +| [**cloudflare**](https://github.com/O96u/PicHost/tree/cloudflare) | **Cloudflare R2–only line** — streamlined for deployments that use R2 exclusively | + +Use **main** for the full product and multiple cloud backends. + +If you **only use Cloudflare R2**, check out the dedicated branch: + +```bash +git clone https://github.com/O96u/PicHost.git +cd PicHost +git checkout cloudflare +``` + +--- + ## Screenshots -| Login | Upload | -| :----------------------------------: | :------------------------------------: | -| ![Login](docs/screenshots/login.png) | ![Upload](docs/screenshots/upload.png) | +| Upload | Storage | +| :------------------------------------: | :--------------------------------: | +| ![Upload](docs/screenshots/upload.png) | ![Storage](docs/screenshots/storage.png) | -| Upload preferences | Stats & gallery | -| :----------------------------------------------: | :----------------------------------: | -| ![Preferences](docs/screenshots/preferences.png) | ![Stats](docs/screenshots/stats.png) | - -| Settings | | -| :----------------------------------------: | :-: | -| ![Settings](docs/screenshots/settings.png) | | +| Stats & gallery | Settings | +| :----------------------------------: | :--------------------------------: | +| ![Stats](docs/screenshots/stats.png) | ![Settings](docs/screenshots/settings.png) | ## Features - **Drag, click, or Ctrl+V paste** to upload; admins can use custom `folder` paths - **Server-side WebP** (sharp), Referer hotlink protection, configurable public base URL - **Multi-user** — username/password login; optional registration; users see only their images -- **Upload preferences** (card flip UI): client-side compression, auto-copy links, per-user auto-delete +- **Upload preferences** (home card flip): client-side compression, auto-copy links, per-user auto-delete +- **Multi-backend storage**: local disk + S3-compatible (Cloudflare R2 / Tencent COS / Alibaba OSS / AWS S3); admins manage backends at **Storage** (`/storage`) +- **Hybrid URLs**: `proxy` (same-origin via PicHost) or `public` (302 to CDN / bucket URL) - **Gallery** — browse, search, batch delete; admin sees uploader badges - **Stats** — upload/delete metrics, folder breakdown; admin sees registered user count - **API** — global token (admin) + per-user tokens; in-app cURL snippets @@ -89,10 +107,6 @@ npm run dev Open `http://localhost:3000/setup` to create the admin account. Data defaults to `./data`. -### Legacy migration - -If you only had `ADMIN_SECRET` and no user table, log in with the secret once and follow the migration wizard. - ## Tech Stack | Layer | Technology | @@ -101,25 +115,10 @@ If you only had `ADMIN_SECRET` and no user table, log in with the secret once an | Backend | Nitro (Node.js), SQLite | | Images | sharp (WebP transcoding & compression) | | Auth | Session cookie, scrypt password hashing, API tokens | -| Storage | Local disk (`DATA_DIR`) | +| Storage | Local disk + S3-compatible backends (R2 / COS / OSS / AWS); `images` index in SQLite | | Deploy | Docker (amd64 / arm64), docker compose | | Tests | Vitest | -## Environment variables - -| Variable | Description | -| ----------------------- | --------------------------------------------------------------------------- | -| `API_UPLOAD_TOKEN` | Global upload token (`Auth-Token` header); can be generated in the API page | -| `ALLOWED_REFERER_HOSTS` | Hotlink allowlist (comma-separated hostnames) | -| `IMAGE_BASE_URL` | Public base URL for image links | -| `WEBP_QUALITY` | Server WebP quality 1–100, default 80 | -| `AUTO_DELETE_DAYS` | Global auto-delete days (0 = off); admin + orphan images only | -| `DATA_DIR` | Data directory, default `/data` (container) or `./data` (local) | -| `ADMIN_SECRET` | Legacy v1.0 migration only | -| `DEV_BYPASS_ACCESS` | Dev only — bypass auth (never in production) | - -Environment variables override database settings. See [`.env.example`](.env.example). - ## Users & permissions | Scenario | Auth | Ownership / visibility | @@ -137,13 +136,13 @@ Regular users are limited to the `images/` folder; custom `folder` requires admi ``` /data -├── pichost.db # SQLite: users, sessions, settings, logs -├── images/ # Default upload folder +├── pichost.db # SQLite: users, sessions, settings, storage_backends, images index +├── images/ # Default upload folder (when using local backend) ├── blog/ # Example custom folder └── twikoo/ # Twikoo comment images ``` -Pattern: `folder/YYYY/MM/randomId.webp` + `.meta.json`. Back up the entire `/data` directory. +Pattern: `folder/YYYY/MM/randomId.webp` + `.meta.json`. With cloud backends, blobs live in the bucket; SQLite `images` tracks `backend_id` and `key`. Back up all of `/data` plus bucket data. ## API overview @@ -194,11 +193,12 @@ npm test # Unit tests | Version | Plan | | ---------- | -------------------------------------------------------------- | -| **v1.0.3** | CI and Docker multi-arch build fixes (current) | +| **v1.1.0** | Multi-backend object storage, hybrid URLs, storage UI (current `main`) | +| **v1.0.4** | Password visibility toggle, legacy ADMIN_SECRET migration | +| **v1.0.3** | CI and Docker multi-arch build fixes | | **v1.0.2** | i18n, mobile menus, stats/upload UI, Docker password reset | | **v1.0.1** | Settings version display, mobile upload preferences layout fix | | **v1.0.0** | Local storage, multi-user, API, Twikoo | -| **v1.1.0** | S3, Cloudflare R2, and more object storage backends | ## License diff --git a/README.md b/README.md index f5c6bca..921f1fc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

个人轻量图床 · 图片放好,管理也要顺手

-

自托管图床 / 多用户 / Docker / API / Twikoo · 零配置引导

+

自托管图床 / 多用户 / Docker / API / Twikoo · 本地磁盘或对象存储

version @@ -17,6 +17,7 @@

+ 分支 · 快速开始 · 预览 · 特性 · @@ -27,26 +28,43 @@ --- +## 分支 + +| 分支 | 说明 | +| ---- | ---- | +| [**main**](https://github.com/O96u/PicHost/tree/main)(默认) | **v1.1.0** 主线:本地磁盘 + 多对象存储后端(R2 / COS / OSS / AWS)、[`/storage`](docs/README.md) 存储管理、混合直链 | +| [**cloudflare**](https://github.com/O96u/PicHost/tree/cloudflare) | **Cloudflare R2 专用线**:面向「只用 R2」的部署,预设与配置更聚焦 R2 | + +日常使用、需要管理多种存储后端,请直接用 **main**。 + +若图床**只接 Cloudflare R2**、希望更简化的 R2 专用版本,可切换分支: + +```bash +git clone https://github.com/O96u/PicHost.git +cd PicHost +git checkout cloudflare +``` + +--- + ## 界面预览 -| 登录 | 上传 | -| :---------------------------------: | :----------------------------------: | -| ![登录](docs/screenshots/login.png) | ![上传](docs/screenshots/upload.png) | +| 上传 | 存储管理 | +| :-----------------------------------: | :-------------------------------------: | +| ![上传](docs/screenshots/upload.png) | ![存储](docs/screenshots/storage.png) | -| 上传偏好 | 统计与图库 | -| :-------------------------------------------: | :---------------------------------: | -| ![上传偏好](docs/screenshots/preferences.png) | ![统计](docs/screenshots/stats.png) | - -| 系统设置 | | -| :------------------------------------: | :-: | -| ![设置](docs/screenshots/settings.png) | | +| 统计与图库 | 系统设置 | +| :---------------------------------: | :-------------------------------------: | +| ![统计](docs/screenshots/stats.png) | ![设置](docs/screenshots/settings.png) | ## 特性 - **拖拽 / 点击 / Ctrl+V 粘贴**上传;管理员可自定义 `folder` 目录 - **服务端 WebP 压缩**(sharp)、Referer 防盗链、可配置访问域名 - **多用户**:账号密码登录;管理员可开放注册;普通用户仅见自己的图片 -- **上传偏好**(卡片翻转设置):客户端预压缩、自动复制链接、按用户自动删除 +- **上传偏好**(首页卡片翻转):客户端预压缩、自动复制链接、按用户自动删除 +- **多后端存储**:本地磁盘 + S3 兼容(Cloudflare R2 / 腾讯云 COS / 阿里云 OSS / AWS S3);管理员在 **存储**(`/storage`)页管理,新上传写入默认后端 +- **混合直链**:`proxy`(PicHost 同源代理)或 `public`(302 到 CDN / 桶公网地址) - **图库**:浏览、搜索、批量删除;管理员可查看上传者标签 - **统计**:上传/删除趋势、目录分布;管理员额外显示注册用户数量 - **API**:全局 Token(管理员)+ 每用户个人 Token;后台可复制 cURL @@ -94,10 +112,6 @@ npm run dev 访问 `http://localhost:3000/setup` 创建管理员;数据目录默认 `./data`。 -### 遗留部署迁移 - -若旧版仅配置了 `ADMIN_SECRET`、尚无用户表,可用密钥登录一次,Web 引导迁移为账号密码。 - ## 技术栈 | 层级 | 技术 | @@ -106,25 +120,10 @@ npm run dev | 后端 | Nitro(Node.js)、SQLite | | 图片处理 | sharp(WebP 转码与压缩) | | 鉴权 | Session Cookie、scrypt 密码哈希、API Token | -| 存储 | 本地磁盘(`DATA_DIR`) | +| 存储 | 本地磁盘 + S3 兼容后端(R2 / COS / OSS / AWS);`images` 索引在 SQLite | | 部署 | Docker(amd64 / arm64)、docker compose | | 测试 | Vitest | -## 环境变量 - -| 变量 | 说明 | -| ----------------------- | ------------------------------------------------------------ | -| `API_UPLOAD_TOKEN` | 全局上传 Token(`Auth-Token` 头);未配置可在后台 API 页生成 | -| `ALLOWED_REFERER_HOSTS` | 防盗链白名单(逗号分隔 hostname) | -| `IMAGE_BASE_URL` | 图片直链公网域名(反代时建议填写) | -| `WEBP_QUALITY` | 服务端 WebP 质量 1–100,默认 80 | -| `AUTO_DELETE_DAYS` | 全局自动删除天数(0 关闭);仅影响管理员及无归属历史图 | -| `DATA_DIR` | 数据目录,默认 `/data`(容器)或 `./data`(本地) | -| `ADMIN_SECRET` | 仅遗留 v1.0 迁移,新安装不需要 | -| `DEV_BYPASS_ACCESS` | 开发用,跳过登录(生产勿开) | - -环境变量优先级高于后台设置。详见 [`.env.example`](.env.example)。 - ## 用户与权限 | 场景 | 鉴权 | 图片归属 / 可见范围 | @@ -142,13 +141,13 @@ npm run dev ``` /data -├── pichost.db # SQLite:用户、会话、设置、操作日志 -├── images/ # 默认上传目录 +├── pichost.db # SQLite:用户、会话、设置、storage_backends、images 索引 +├── images/ # 默认上传目录(本地后端时) ├── blog/ # 管理员自定义 folder 示例 └── twikoo/ # Twikoo 评论图片 ``` -路径规则:`目录/年/月/随机ID.webp` + 同名 `.meta.json`。备份整个 `/data` 即可。 +路径规则:`目录/年/月/随机ID.webp` + 同名 `.meta.json`。云存储时文件在对应桶内,SQLite `images` 表记录 `backend_id` 与 `key`。备份请包含整个 `/data` 与云桶数据。 ## API 概览 @@ -197,13 +196,14 @@ npm test # 单元测试 ## 路线图 -| 版本 | 计划 | +| 版本 | 说明 | | ---------- | ------------------------------------------------- | -| **v1.0.3** | CI 与 Docker 多架构构建修复(当前) | +| **v1.1.0** | S3 / R2 多后端对象存储、混合直链、存储管理页(当前 main) | +| **v1.0.4** | 密码显示切换、遗留 ADMIN_SECRET 迁移对齐 | +| **v1.0.3** | CI 与 Docker 多架构构建修复 | | **v1.0.2** | 中英文、移动端菜单、统计/上传 UI、Docker 重置密码 | | **v1.0.1** | 设置页版本展示、移动端上传偏好布局修复 | | **v1.0.0** | 本地存储、多用户、API、Twikoo | -| **v1.1.0** | S3、Cloudflare R2 等对象存储后端 | ## License diff --git a/app/components/AppShell.vue b/app/components/AppShell.vue index 8a8a92e..53bc43d 100644 --- a/app/components/AppShell.vue +++ b/app/components/AppShell.vue @@ -16,44 +16,35 @@ const navItems = computed(() => { { label: t('nav.stats'), to: '/stats', icon: 'i-lucide-chart-column' } ] if (isAdmin.value) { + items.push({ label: t('nav.storage'), to: '/storage', icon: 'i-lucide-hard-drive' }) items.push({ label: t('nav.settings'), to: '/settings', icon: 'i-lucide-settings' }) } return items }) -const accountMenuItems = computed(() => { - const items: DropdownMenuItem[] = [ - { - label: t('nav.changePassword'), - icon: 'i-lucide-key-round', - to: '/account/password' - } - ] - - if (isAdmin.value) { - items.push({ - label: t('nav.systemSettings'), - icon: 'i-lucide-settings', - to: '/settings' - }) - } - - items.push({ +const accountMenuItems = computed(() => [ + { + label: t('nav.changePassword'), + icon: 'i-lucide-key-round', + to: '/account/password' + }, + { label: t('nav.logout'), icon: 'i-lucide-log-out', onSelect() { void handleLogout() } - }) - - return items -}) + } +]) const desktopUserMenuItems = computed(() => [ accountMenuItems.value ]) function isNavActive(to: string) { + if (to === '/settings') { + return route.path === '/settings' + } return route.path.startsWith(to) } diff --git a/app/components/MobileUserMenu.vue b/app/components/MobileUserMenu.vue index 8f3fb1d..e450fea 100644 --- a/app/components/MobileUserMenu.vue +++ b/app/components/MobileUserMenu.vue @@ -1,7 +1,7 @@ + + diff --git a/app/components/storage/StorageBackendForm.vue b/app/components/storage/StorageBackendForm.vue new file mode 100644 index 0000000..2ebfc9b --- /dev/null +++ b/app/components/storage/StorageBackendForm.vue @@ -0,0 +1,330 @@ + + + diff --git a/app/components/storage/StorageUsageBar.vue b/app/components/storage/StorageUsageBar.vue new file mode 100644 index 0000000..c722e1d --- /dev/null +++ b/app/components/storage/StorageUsageBar.vue @@ -0,0 +1,91 @@ + + + diff --git a/app/composables/useFileSize.ts b/app/composables/useFileSize.ts index 7793e4d..cd28a95 100644 --- a/app/composables/useFileSize.ts +++ b/app/composables/useFileSize.ts @@ -1,10 +1,55 @@ -export function useFileSize() { - function formatFileSize(bytes: number): string { - if (!Number.isFinite(bytes) || bytes < 0) return '-' - if (bytes < 1024) return `${bytes} B` - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` - return `${(bytes / (1024 * 1024)).toFixed(2)} MB` +export const QUOTA_UNITS = ['KB', 'MB', 'GB', 'TB'] as const +export type QuotaUnit = typeof QUOTA_UNITS[number] + +export function formatFileSize(bytes: number): string { + if (!Number.isFinite(bytes) || bytes < 0) return '-' + if (bytes === 0) return '0 B' + if (bytes < 1024) return `${bytes} B` + + const units = ['KB', 'MB', 'GB', 'TB'] as const + let value = bytes / 1024 + let unitIndex = 0 + + while (unitIndex < units.length - 1 && value >= 1024) { + value /= 1024 + unitIndex++ } - return { formatFileSize } + const decimals = value >= 100 ? 0 : value >= 10 ? 1 : 2 + return `${value.toFixed(decimals)} ${units[unitIndex]}` +} + +export function bytesToQuotaInput(bytes: number | null): { value: string, unit: QuotaUnit } { + if (!bytes || bytes <= 0) { + return { value: '', unit: 'GB' } + } + + let value = bytes / 1024 + let unitIndex = 0 + + while (unitIndex < QUOTA_UNITS.length - 1 && value >= 1024) { + value /= 1024 + unitIndex++ + } + + const decimals = value >= 100 ? 0 : value >= 10 ? 1 : 2 + const rounded = Number(value.toFixed(decimals)) + const unit: QuotaUnit = QUOTA_UNITS[unitIndex] ?? 'GB' + return { value: String(rounded), unit } +} + +export function quotaToBytes(value: string | number, unit: QuotaUnit): number | null { + const trimmed = String(value ?? '').trim() + if (!trimmed) return null + const amount = Number(trimmed) + if (!Number.isFinite(amount) || amount <= 0) return null + + const unitIndex = QUOTA_UNITS.indexOf(unit) + if (unitIndex < 0) return null + + return Math.round(amount * 1024 ** (unitIndex + 1)) +} + +export function useFileSize() { + return { formatFileSize, bytesToQuotaInput, quotaToBytes, QUOTA_UNITS } } diff --git a/app/pages/settings.vue b/app/pages/settings/index.vue similarity index 100% rename from app/pages/settings.vue rename to app/pages/settings/index.vue diff --git a/app/pages/storage.vue b/app/pages/storage.vue new file mode 100644 index 0000000..69ea852 --- /dev/null +++ b/app/pages/storage.vue @@ -0,0 +1,349 @@ + + + diff --git a/app/types/storage.ts b/app/types/storage.ts new file mode 100644 index 0000000..93d3077 --- /dev/null +++ b/app/types/storage.ts @@ -0,0 +1,39 @@ +export type ServingMode = 'proxy' | 'public' + +export interface StorageCapacity { + totalBytes: number | null + usedBytes: number + freeBytes: number | null + percent: number | null + source: 'disk' | 'quota' +} + +export interface StorageBackendItem { + id: string + name: string + type: 'local' | 's3' + config: { + endpoint?: string + region?: string + bucket?: string + prefix?: string + forcePathStyle?: boolean + } + secretsMasked: { + accessKeyId?: string + secretAccessKey?: string + } + servingMode: ServingMode + publicUrl: string + quotaBytes: number | null + enabled: boolean + isDefault: boolean + usage: { count: number, bytes: number } + capacity: StorageCapacity +} + +export interface StorageListResponse { + backends: StorageBackendItem[] + activeBackendId: string + envOverride: boolean +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..4d6b4b2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,53 @@ +# PicHost 文档资源 + +本目录主要存放 README 引用的界面截图与分支说明。 + +## 分支 + +| 分支 | 说明 | +| ---- | ---- | +| [**main**](https://github.com/O96u/PicHost/tree/main)(默认) | **v1.1.0** 主线:本地磁盘 + 多对象存储(R2 / COS / OSS / AWS)、`/storage` 管理页、混合直链 | +| [**cloudflare**](https://github.com/O96u/PicHost/tree/cloudflare) | **Cloudflare R2 专用线**:面向「只用 R2」的部署场景,配置更聚焦 R2 | + +- 需要多种云存储或完整管理界面 → 使用 **main** +- 图床只接 **Cloudflare R2** → 可选用 **`cloudflare`** 分支 + +## 截图一览 + +以下为 **main(v1.1.0)** 界面截图。 + +| 文件 | 说明 | +| ---- | ---- | +| `screenshots/upload.png` | 首页上传区(顶栏含 API / 统计 / 存储 / 设置) | +| `screenshots/storage.png` | 存储管理(`/storage`):多后端卡片、用量、设默认 | +| `screenshots/stats.png` | 统计与图库 | +| `screenshots/settings.png` | 系统设置(上传偏好 + 系统设置) | +| `screenshots/preferences.png` | 首页上传卡片翻转后的上传偏好 | +| `screenshots/login.png` | 登录页 | + +## 相关页面路由(main / v1.1.0) + +| 路由 | 权限 | 说明 | +| ---- | ---- | ---- | +| `/` | 登录用户 | 上传与图库入口 | +| `/storage` | 管理员 | 存储后端管理 | +| `/settings` | 管理员 | 系统设置 | +| `/stats` | 登录用户 | 统计 | +| `/api` | 登录用户 | API 文档与 Token | + +管理员顶栏:**API** · **统计** · **存储** · **设置**。用户下拉菜单仅 **修改密码**、**退出登录**。 + +## Cloudflare R2 配置对照 + +**main**:后台 **存储 → 添加存储 → Cloudflare R2** 预设。 + +**cloudflare 分支**(R2 专用):通常以环境变量为主: + +| 配置项 | 值 | +| ------ | -- | +| `STORAGE_BACKEND` | `s3` | +| `S3_ENDPOINT` | `https://.r2.cloudflarestorage.com` | +| `S3_REGION` | `auto` | +| `S3_BUCKET` | 桶名 | +| `S3_ACCESS_KEY` / `S3_SECRET_KEY` | R2 API Token | +| 直链 | 默认 PicHost 代理;桶绑域名后可改公开 URL | diff --git a/docs/screenshots/settings.png b/docs/screenshots/settings.png index db66787c732e38ea7c310db4ba9ba018578759a3..9564598cd922112da03af682d92f528fcc04e5fc 100644 GIT binary patch literal 77048 zcmeFZ1yo#3vnV<^36c=p6C?z8w-7v7@GwY1fFJ_{!{C+#f(8f-Fu>sM5M*$7cZb0p z27(6&$tB8{I&ZDhd%C-(s=8YCZmV6rf6xA20X%%Eq^bnKzyJU+ z?k>RZHH=wRdHFZ5bhMOI)fN9H^aAc2HXi`s0CsWKQGW6GwGr?!-pb!8{?N=VpiY0{ zf8p-rUQGXy4ggGW{|lb~9r~W771ZL6;rpFualXs^uCQcxFq!q=@UuVgyT4(nKd`%t zlgk~?t3R-_o{sz-Yv*8G@*1Qg02P+T$Q8U)%pZHKDo)Q9$edb%1~Z2 z3AyROyQSO#hs;bne8@xB80*^$R8*(LVG=`fU!Mp7w*p!NqWr!$_f*T~NiLU{cOcQy z2Lh}7>#H&kkhhYzTC?6CD$}56H!ADlL%#w1l5g5+X_z)!R1fuel0#2OijYVI0#>nY zlV>?9XEGx}Ny2;{OuwG+E)vetem>Au6k>K8Gc%}Gx+*(iLg^^=XtX9`|Z$`%( z(VCEcNt=*~NJBvq@{~zODc`x;b)sX6INbr__@L%hMRNOdS27|_qgp613t1EiT~gW*+@kFCds1p}ug!NbmD)Q%V4 z-!9Ue(cT`1s5DUcwx~AghuxypVcuW{nMbHG5?5b-l7}QD82`xu8QbDID1?XyYr1jY zzyo}$TS(Vp_<(HVmDf}%ZR13V(Q3nxu-tX6M;=qabn$o8R*KyS(UNW9gQ<=#aWV#^ zbxLl zxTlfXoU5&-c?lte286mvdl1Qv5Tut{p9O3jHpE71ZDbQ1l46yi)@JOA5rqv z<+ReZ)G`c~YV_fdkEm%o`!AsDODA{ocU2ush{Mx<%6kjoet>-~ys~f7Gh)r5-Dl2u z|1871R&Xl*!BXom-pP5{PBe<|(Damecg~tpkZ$|)I%)UQEYEdW<*TE)+x1#++S&`> zc)#uj@c2{kyQi+8eqPQ{M1IIu*(IwQ4To{4b;>BvVdbu7t7fuCE&f+G@BuD5m~)o0 zcFz0!Dmi^+oayMOe)JjGPtM{^aui*MzsJf)1FdK^^zd;Zuju{j?LNEgJYm;k@ulQp z9>R#|YsA_mu~@4Oq^%XN#JM~px9PRRb<*n{ghz?a^Zuv$qiTYjCEtS??4!2f+v6;b zw%Ez^bp+?AGGs%`SxeP=gZtpg2zAd=NMj~%V7KncQj3=_!$gz$(oI$O>uGHx6N_KT z??8IP{8j4Log(@&JWGB(1MpUYLb0D|73u86EQNJ+1O|9xBnHGCG^5Mvz@<&?rB_1a zG8`2j|4>rEwACvJSIchJRU|WSygHOeSbKm=1NQ8*6Tob{kJ;ia^ITvzUW_?XiyFS0Zc*Eu0x#ab3d$`6)+Tl!e;^<6ExtyjLWQhut@+L))#j0OKRAyN0S$oYNNnTrcY98 z?yIW56F0_6aFfm!qB!an40q~7Q`|zWOCplbP2A?=_3MK(4OMzKcZs?!upq?l50%E9 z@!Mf&BMU2KzWt%5%G0%Ygs?hlW3K6R##WOjh4RM$l0VDVpr$@xMlK+R@O6%35;A9<29_Ibn&dr*-MM z#ucWDGrlp_09n{Y(9#>}6DU-?eMlRe%EDOabX4ck(O@yRSwwYwUe+;#@{L%$iFcrH zF(;)~D)siB2XhAhDwl11Lu*X}BBRT;z20w{o(Bt^Aw{l4#F3=k!>)~;&_w35iY;6f zfdj&~*R=(1rjWdIqU!rMu3KS+FtDR#cuLlnVXE z0WNwqrXv8e9-(|u5HTJ$>?hJWEu3_@QEOGZ4iD%EZC$H|B&_3fJpDqdSY+?c5%M^@ zabYhnxlxgfT)SzU!q;h(SaaD-QW`WezW=a#1hQY`ZaC35IAl1!zLgd8*dBWN@gTZ2 z`Ivyihakw5LVP}Kvd<>dbvUBJAYLRcC?heS`?{S!O^u<^`aZX2IdXJQ&a$W=5>|e! z$fe{-)jeqGJwGkT#^Yz<4v>Up*S;UKNXkd^6qjT>jE78V3nmwS)D!d}>UNi|8&DG4 zSbdQ`gK*w=51LW*NiQx-C5|EE)0|0U?xRJ>wH-*O^y#jc2=<1Q|LaNSe>L&^d)&0} zR&(a4P<5W3uHMWP3QS`B%%*^5g5|UY`i&q2B>%dnG5#T6$Gj79L4l{0TSWcdK*Y4a zeAa>BtI;7N^`UV^u99uGlq+U_{uo_M8iG$tPTsh`0r^6xGJWGNqWLh6Yi-V_AXe%* zW;(i_ste?h6E=xIJ`nX4<}FPg0l#UW%{N;ZyqpjQ2@vVUsB+TRu=o{A38!ykL@4Paj^UbEq{K12+tQ=UG z?tzblZG_O&scxt-t=$|~8OZ=?CG7RG@{^pd*1*OS@7%q!$oSkUy))b%)r8$S$4Gh-Y@p?sL zP}sp};5P3i3UpAhz+pK_c994!U(hh6f|-FCT*oM?t))K;F{)Q4Ntz;u#qb2PJPmTE zDd9kz%=%@B6*l>l_6WCRpr{0DFrp0>pZrn9!`2#86c2NZDGz2UZ2X4RRDhX9~u$4Xdepwf>(pUyM!8!mo3e3%9IDdGthSR1_Y-r5Bz?97ke|)ppC#m&7^^sOs+5 zv9?SsA0nsejkN4@%1S8lboCajQU>GC6A8B8@z3WK)3TQp)iMrTh$ zQO&b3BO^Z}J1lJ0X106q#~bF9#B3hf{I~Q80}UP)jl<-oKFLk{z{%uH!`D~O^D6r2 zxp4@;bzUn^^WT(CNG309X|Y2sA`L|NR%R$Ej}B~b28eQ-qt4<}XhAIown{&sN%_i z*pR2lp?G4@Cychvw{BkFs;--rStU}(aGDW_<(wUkT^?Me#XUx$3f)d7%lC8Vog$*F6;feFHkh6 z?b-mSZ2~v7n3O)8Ok4(&NTVZRp^Y2b;>Z`I6Lbf3U|Z|Gj^@ggn+&(WVV-2To zbs@rCyXUD~ZD-&6blm4Bd)8Eai@r~SV*-$K15SR{4%CB6@oT;%bMB=RbRTN!Bl$^n z-N{!w{64gVJ4kT+251*>r3mCc%ZkzxqD|-F?`4i2Ttc-zB%YX;>b~D87>he*a>U)J zp=O^~Q*ETdQsR;CLfA@9#1=4nC`BXo2!q9+q9ae}Tn15755H%XmW)u{KscjLR4ysS zdu*_(u0LuY0(qu9yi?u~A_MG8K<`ry2_F&4Ixj$MYBcXPT%q>ax1c&AVTx9qsJ9l;vPV3 zp(0`s>A>dFkvERy#Az}6tb#Tj>}g?2a&)x0p0l%NZ>Z2~7!dSNGF$YAzRlYQww;R? zaoR}Zf)4w{dU${tH21OFymR$sYxc3|Is^UZA2b^%^uhFB`-+&FS zv?Ing@A8#fYx7@$^|^!Bwn-YyO!-_);;iQ0zyl}NbpPiv#o?EMrz5P5-}cm|o2N#X zT#16XCG!-fD7`|_M|mtt+X{^8hbe;CRDp*+p8+$N?WlzdUy8bIj>ed-eus)F3xOW{ z3XzP8MS2Zd?2|6WeWt*qge`kP5w=>fhYB8T!^-wg#nei*rOZbyqASYFECOa%2uvoT zls$lHob_QfDkp};yzrT7&cON!eSOW#Dlriyg>9iI54wk9pXyB303?=#dg@#|sX6EsI*CL#O*iuI;HWfeC6~+`BK}b#+wC zk5eCSYL0O%nUAnvJ-u6)c0Jj2e~q~nOebf5{`EKDX{A)u%;MhJc|RQOl6tP|&|vUN zq`4rv!Zs^Xhsul@WYj=KO+^1iul9u2Q=&R3bGaD&eYZEM0_0PZ6rH#Nw>?^5UZQ{Xo9uWJ2K<_+GMNdGI>;No4^j3+Bo`f|B9>X{9JPn zC{d|v44U_B_(;-J-t&Z*A;4^_ugd@4TkL1uWv*I831B6#;ryQ3fhe${X?$4&IE${4 zMAxJn=ykrcNNeT+%Me91Wh^c@{Ls{fjmZ+wB8Hk(Pr(Nk)D zb}VS);j)YJ>h)To!UCJahr%x*0orJNn_*)Gp!BTBpiOyc^{D%9 z(+b^*83<~tASbLoLhrXR7_%PC#1?vxfxx&az1X1|6umZ(Zcpqg|5R^w(+66YR@}`r z*L2lQvt_~B%2j#AIg$I)(;_!D)yt#cNsx_Qw#B*wUzEH*{T`{xgL&e|8=OKuUw6Hz z^I0P!GfqYcb(-z^vibaX>24d6{aPfx{{~d^(MOF%Tv8uB7KPJVje`_~=$TSq&T_}C zjJ#wZB(IQ;uKXMTI;E}f2>LGO{%)yMKXXA|THD_%npAC2Z|o?#)fXPga@+CMd9rvh z($<^oo2r_sj;R(Ay_D3s_q%t|4zIzI4|4Apnm?;ufXY8WIlbUJfS`0N~m~m)bhp!wTu-YD8%Qy!Vy{*_qD?+ogzb#}ZPE70St8$&&N2C1+ zRN5YYd)q*TUJvKZA2akklCJSa%NjOXi(6?^O4*6YXnFjKy)g9X7r2$?C&{&-&L5x} z&{y{`yU{zTH_FNm^M}#c7*|uho}FFXCc?rncU_95nbAkNqzOLjV$XxaU-qZ7KGk&UV@?z_ zq${8>cC= zWpbbk@ni^W60PQ3E;GI#%bjN_d@L`j^i;=Am8I9+>p57QvGKVSf1p9(Mrz?NN0K%# zS91EkL+{@d-ISW_Pu&>H?1k8t+Mm*&* zGEo+?MLao?gwd%|Mh#VN>qo%h+;QlxZK?iu8Bm1cM4lqOkXwP>JR$lsz>TyrT*~Ap zUx2@Hq_piL!2}p2Sft!po136uFSjHjPTE=(>P<^=w~~CSn^0pNsb&KG4Je{IFJ#>a z@+_G~=(>dFF2Cr1yQ-;ql|j5yNz!mhu(Y;p)Q#W18tb#}g?{!rIx-rqcVLH;7x(lvX$F>pGxF=F7PBM0JPG;7rVL>h zZ9+V5t(T2h!yCAOX$z_zlG$iIw?w7EeUO*0N{>EpA|U|#3>r>A%@YzTFdjKwD*bSS$9P3 z>q934eN_DrIu{l<4C9hDk8PG!lZm#GR7-&!SSG`&H;lwLB}LWoLo#0c`z`*zP(S~N zS@4h20}pw)+oZd6r$*fwh1n_en3ZJkn@sT_r<$b!wO0iyafG*0QY2pW#Oqrrf6zM7 z91kWs+9-?C4`posPUJZohqt@BQbR~G5%A4II=k?)2K_*oKDwgNNIl{Kq1!Za@%aid zWVy?5wBRRxQ)^)+C5iEi!;9ODt)zvEU2)thnnx29?bqd)ml z{*JPlO#U17SA+a_OZ@jU@&CuJknfB0jImPwdpo@3r3YCZ2Zh6+y+cZ|jfFxmkxeTd zX6~-8t{N6guBCu@8LraM(~+rP$9RpzQXWp{93{6gzX2g1MxW964``YEDD*0%jj+K@ zmN5*XTKkao1m&-sX*w$a4J_|*bDs0B+&lbMR%%70P&r_BF#@*g#%m+UAhIyqK?O7t z^Y&o6yVI34SLExAj_3fQ6lt?t#fCA&Moxe@En0dqxi%UmH5aoZ&m)5hMV^oMF~46k z6C9RA_>HAx)kxPF*HyM@Si%n_B|9boCw^uN+++%w zW%x=9&@M~a_zNcXxO1UmEc+P^BBLe|&B014+Ec1*3(a>g<9jvDDV%<<4f^gRt!8avs z!qcrZ7svL}JU?FTQE+6vPDLwbyJ&Fnxhkb1O?J%dhj8>X;o>$Mxu3>EpIE&94VV^$ zQd#nJpx~bZ$|FUFjC6~pov(5WghHB?_7LODS#f8vF5|toaRRu?%)Spanb55&i^v5P zY#8RwcW+~tbzAOX~*mLgAbm_e@k_xK`gem9Qpra}brG%%1 z&K>e?Aw*&k3s}wqN?JHGyY!N~=C10H4xQH%To4sYi#L%^O1W*F@WfaMFNwQGo^@B7 zyb+#Mc$Go&(5M(Nu8!Y3>+ym0y~KSTyk9hoLF9WQb5DO^{rb|Ym5KZ+Q6E2-@x%Nd zk%~ec2g&vSO%%4m>oIMvl23t0VmA848r|}&uipDo5Uw9_zT+_xRQp;lJ$%MY;XxQn7j(d?q=6v1m0*_ApA6B-% zb0k%#WHoE%Vxc+W~LY<+eVgXQ?={cV2etD^k6 z_t78(&kdwm-i1<(Jv~3G)R$-8kWwfJna3ve!TYect+jtdUrz)Ew_Untsm|-<2nLMc z#E_C|gw`S_fef*mUctHxRp@qO;rRi%d}`Vfq$qBy5kE|jnaZ=ka&~PL(qhB*ktK>^ ziXx|SaK4g$I!I3SaaekFMsT91r2LznF_!Ob=h>ZYq>uWTrivG{XUpP2LqyWXSPqW4 zkuQ7t7?l$zzDe-Yc0?X*JTPRy;Q8TE4C$2MR3JWark^0pYRLetkXwXOE zRMVl)6_8S+Zv#YDGg%Q86J>eHbf@LlI*EcwFm|D^}X2IXUF^1mJuvlU! zPS6;X^wONGq$)mv&TL%W+Razwtu@|d(yTLOKn2=)hRQl_1T8Dw{AS?9x2NS|YzcDM zw2chyceLqvw#v9E@vb_RYDiIGQ&3mXQ)!cFp)am_#u%Dcm>;$d%|$BADmNarpwM!$0<=p zeQ0ci+YoL*svJROkt45w5!N(kAbGXHL4 zucAvs!;L?5kA+D!_4~B=EC>=QUB~DrldL3R&A$18yXj3q(vxW z<38@SNffPi(UoxR{(`y$#oeo!ij-)TeFtw%%cYs7hI>@EZ~LfLXUlko`4hkFFI`Rg znDfVO>dyRX;YKXIbywWmx z)>&d7Q~hjUz&Oo}rkQufB=RKMxbOChnwg7B&HOd@vnRRrW7;|flgj$TDytf-3WtMS z5lO}gO4|%QZd|2J8)@1oPUM%PpGhW2g5=kJxi3LYEP?orq#$+PZ{F>ejevK5f zdlwk>VyI!mJ9Raccx9|>R_xq*`xe@Z15T6|tRXYm*JTvdPH`YnF$=@E67{ z^HP;6482$zLeC)4^A!lSoukn@`3P@!RaFcEsdqh@**G&H{qk+h#%5~^l2R)utl6sZS04Sa(b?+~`Paf%f zea}jB`IWFzf4MdQADFXAVVv`ZU9oM*>jJ?MlqhNMoGBOnB`=Mo`}e_Lpn^Soj*C-8v&bEb zK2-%{Ywprec{g#b`UKis%jzwjZb*x~O1g zCP1`mO|O+xfR&N@F{;kC44Gq;^xE0>?rL>q<4$nt6*cA|uEl{RT4vzAR<@v{#=PBi z*&CXz()#6t8%?zmvKPtC6)FB%bbT%*bcfy|$LsTwl2bY+1x!U&oO>Boy4^-t*%*h6 zP-x7oVYg4dv!YuI2v}HfCcS6M3z?V{g1_{3sD>A2_LK+v08|AP1?Sg@FCIx7i9NAj z{=WWdwmAlVgUZub6b7UMUwR|gwHJ1mvH80+r)df$%Go4(qVV|-I+9KI+Lf(I`6*{i z4K}v)qhn0;l*^^e*4o$yBl%BA8BwHCLjG{I(A zp%~C?OOg4x0B$7DJaBQH4QGJ&-EH)5z#8Kj0ZW~!6{skfYtoRDXc$;nBI;U-xB-qz z&?B6eqQ3J@JZmFa!H7@E!$LX^)=YNgEXZ@^mX=rfbmTzRIWOMU#w>Xp9~nId98j5+ z3~+{oSXvm@nnjJ%R3G(qe;$vU_cIl7S=1Al(l;r58;2yFP$IPdtUK_8OQ1Yw*sr7J zqb-l53lOa2x|JRT^{JZFygIl@axf8|cqMX=Q411gYeXot!v78b3YBy#Ab^iYeO*V< zW6R%$U@~Fa?Stp!Y>uy%SX7H#T9UgYPi34cEJkb2Mpzmr1K^M7uq+=_NZ;@sfDd=V z1p1weOce1$a_%gLzB=6oW&ITWkl7|TTUqm9)=sv={hEu6{!1=?pDzieLRe^X(T9k% zNpib?01xf4zVR9c-1(HrzYM@%1Ire2e_{^(W9l}O(v8QdQZRkRVG$ZF3Zj}O+$N&Z zXX1NxWZ)$&Z(WKsdF!CxT#q-Q_k8U+OtcC)B(~o9NQ~P!j>Ge5R(bC{RLaTZGwe6u zhf5sV!ZS3fZnS4t%xZ{E@*WOIq-FfZwZLTi3O|m0J}su4 zrr4E*Hv@_2%5^R-m^hgBl9sl~L@uN|aO041<>h_Kj;6D>0RaVjJ_6XkB+Qt&x|&!Q zvXqj=oeSvb=;@CIscE+Nri}E_Cd>#459Wj1tE{~R@E@l+%%BXp9bnbDG+PzxoOJ_g z1$4t9Lqp*0m&{P#hvREAaPR|36BQ!kTx(CbUC1giqD0jYTd)H=MyW4%M3goeEA3NE zmDhJkr@Dyd7DEH=QEE;et^!vpR%sueYN!&U>dz~SMO2yf6m1gxBL?xBfw8gna>V$E zt{h20ah`tz0*+tH-~EsoRf}FUolC|v&=elh7VWFtYht&Zq4BHe@yWF6o_akxx0}r( z@|=zi<8i<|2Kt=+L`}msgBPx{Ysz=5^4KHsR+XDxibm$P~Y)7XaEG_B6A(rr{M^Fhkzwc0ft+bz-)Z{ zv#59oYt|TZC^Jc6Au@(NuOP?Q5~(u+36r=hBT0D#Xh@ve<{Vke<3_xm{Jqm)x@cpk zxG)G9`qpx+*&{$bTDZ0Db{h~#Q2%D$*u-hrFeJ@ z^f{FFtIB%F`K9?=2bv-SeJ#4F*h?>OK3}OgUI*k>uJqVTMp~+O4?OHkLMTO~j(aik zn9dx9`|oBSn`Ut1m$V^h%cjhl>V!*Kh-VedlC{VtKL z7M+>;@?%pD`$vW`Pq!!^VtobGaWUa ztwu+px8~4!rRUzjLp&!`IL82y)b*Y&^)lOYgCb6L^jXE^loq|P5H~N(HkGPd$PK0i z!1FY@K)FxGOO_y!PgkuYGdVmhK91JAk#r8uu@c%u%&uOywoB6qH9!sE?5?ae>f@&n zv)tN_JRYGJ?9b8!H9%@~-L(`CSDs6nIL}xBl2@__5UKSmC{x#C_iu85fGA&cO0il! z5WcNlUb?Tz)YqaS|NBO7!xd(`1W^Rd`K!aokCc*l5WFgj2R!A?GR6xfF{pi}Ofs!7 zc*Ww6J=UeZ&*4wL#rMPu2Sc-%xzB|c;7X#6uJ)6A=QSK^AahqMJ)shWK?il}^VTam zr)g2PFJJSCSyg}{mFe{}OG1IB~+0&WPXXS zr0x5L`_hxICWnk)%;woI9&(8Iw-Sh0@B+U1?KkLZrJ2wcrAXLu3mVUuIm~eCQS22^ zlaJCm5k{2y2tJw#?UuVP7jc$bjankWW1TU?w_A8v+q|ho588X@N~-|yQc6njTU1%Y z;-lGiYw#d$HKJNH64#r^%iNxfPdY!<)2UB3Dd2Lf?R_Bq!u%=jShsPlO6ykDP)leV z8*{mSaoNm`>0UJ==>C;OepqmNGdyI7){KLW!(B=!$gj9^KZ^g{V^hyuP)Ji7m^5W- zLPj>OH>E)@ETzE%GfJiDCO**ukWY1<<8b7%O$(iP5n0-td_8Gk$5Y~f&j~%pM$vqGb&nwt&|MB10HXM z%=o#c8ggI&mSl{5gWW>&;=C{~Pa~;ERWjlhPEfOP0et39*j_U{c`bIfnsy8B4h0h< zv`_P@Q=?AIKaB>^Cf*r2x~xMHe6L5>mY%3?jA!0};eFOc*~LQYl@syQk6pO4z0Jd^ z7EKvuVCbGPzZGW!J^=(<*=BuK+3Ey&!QM)a3pc`M)n#OB?U9sF(<~bZI`4F^^^h}l zk;g0Bl+8ar50R@sDWif?k$9Z7l{Q47T0`u22pS{iT{MNGsUPydhg2b@-*nZa+YPE; zO2h!K-N3nVXXo~Di&V5b(IjnDI31JE?{3&nY1RHFUwR{oygt<0X1)WGlH-c6r%|^H z@D?L7G$Te*tZ%SgonffNmOjTJ>^+BVxhMq_Q`bLU|NQk1=>K7Gf3Ydh#9;H_nE(av z9OJJizX8w7Yf#$8iatSOAY}C>@EWT3%PsqPw-O!g0&-4VI>CsO^5D*3<>7BYI`I7b z+`!ZwXT=x2bnYY2;}Wv+&80Da+)z(XPRpukNLVs})q)*^n*~#F z*ZpC(O%Y2K0$0*^IHYd8I(URRweOVu&`=|!e(p{7Q3-N#iHEX)NW(9+G_Sx%51g8= zvddHyE+OIh_G?mjsJ#3fHPK@jfr1C(Zt5y^iMMmtQq-l_`>J5H2!GJ*KTGY|L%~9Dd+PAZnya`&+F&#l6AyePwte6EgDVrk8j1U6(~ZEg|z~9 z&rFVD7RU`!X6nab5_Sk{J9$^Vbz}dtW9?{%*;LVDWpPF*uQ=2 zrcA4x*}Je)<+~9Be8;q9tVBu}OV=LEqyM6=t_;=}mZ=5}0&MO&| zNj;DgjrH0qo>E6`&qjAfmL@I_fXM?){PV!~L3671 zkM6&;DY%+({2I#r%yC;eIsD%vflav zN^=%a2CRe?pxk-j;qjtR%w4`JQ)H9h#&mmdq}2WK=V^M^@7Az*Zvk$cUxw})@#!5` z^w;fm;ybtegpP``=H71tsq?JN~KwQh&c>&D960$woTG@pM{xWJKst@5JPt>gH|A^x9LKVKlQ? z9ajaj45-!(ItDs2WTs!V=}+_T%6+mbrN8)oFmF_<*XW5>0oiuRm%ACUePYhSj@Iu) zs^)5FpeaJH72S`6+Zb7HnHqj{tbB#p#^`!0{!bYuR=*CksDA@)UtK*-6c*Y45@5q2 z0NEk=4X_IM?8U*WQCLhw{-|!nZwHKb>mN_jdLZ-iI81%0jOm{pgH_e*pW}f3U6)~1 zDDAbzI-lG74VZjr&-~9l5c*uDjmBvygKGDQ5fD<7*yw_(x?d}(qC zrWB+|dcYWICi{Y}9lkWl{}n;5$NnPmj-`_BN#UsyWo`SP)1H}*h0E*K=QzNB0NAvE z1A?iQJHPGv*Qr*DzjfP{Y6SW>F3TSczmjdXIana+HxW=RQ=W09-K_K+c)?iJN+W+@ z=Yz&oO9MtDqpeBciz~Za7yJCu^i;U$OO?0Kb*LN!kZgI}%9)MQV56z6F379%7^~4> zd)s8ndr>0Fy5vvr*+o<>oL{|nlOa+*S+Iv$`m=wf!={#Eeb6RQ*fnHq!ECn34yT_1 z9Cs)NvUEG(gW&_##Y}LP6}go~;)-iD^ey0pnp(SO#X2&o1V)B*fm`Yc70lPQPP7#= zg}rZ@BkTLWyqY#~$#F=X(a@EDpN&A=4`pXftV-$}vM&zoek7kj%Pi+a#Ejv^MklPN zCEKzv)y>9H7g>EX(za+Pt}%Az2ITC#F6|@AME*o^`@~ZjV;>HSRJ=f!isOCcKq+(7 zbHMzUO=ZfybtpohiORaH{S-YqTMeMkV4jX%cdYQ0?{7G!ogt>ZIGT|Sc9NyZg#|gq z%`%d%{lLBz7)@q70V~-NGzY8%+}d1;qEC(s-Ic(9dghaPeApP^^rRbKl=t2*JTky@x-+;p6dL0DK7u1sf zh?Y?Dq|VG_1#Z(1p84@L3^7n7UOvNS$Old!gQOK}*L8R6g0E4O?i6h@Ci@ovFQMCk zq|M`GH|wyC52~iZx@xu?c2l-lVLR9KMsj4Y7L&IXA4lGk_nZl;{pcis&coiuE4L8p z8FM=u`jB_deO2#g=VnKXyjZsh@cH(F!*-60zoVXyh1Zb7OXS>w1seMhTccBfQnkSkQg=BQ~uhT1IT|=&tG5Ny*+l@_?H?EhINf@auEVOLx>=FANa;2Cx6pILC0v9XJVg#P^7RYVYeu|u6_2s6B@)|8cebe z$e+)O^Od#8Yt|iGDvrAjno(voJ{U)>sMFI243gxpJ!CbTl2z7laIXb<9Gp)Na2IKh z84BEKt;44tE^=&z3rIv-wn{Kp2%PlvMelryWY42X`RTQPqNEF{F+l4r7VnDGhr!kh ziVgM$(SfDa&HGq6mKFSE_7>x3p~O_JXP>a0IIP~VBVH`~?MamATje%6TAG@2w^K|y z`iY1p@u1=@-&#bzxcc=QaHca!=_}f=&um>z3(utB2N%Sh_+@1%6TDwKw1FRPE!Tjd z(a8`X&UBZyYQGeaXZ}Vrqa-O7ZpY5So{_&#<56=MR~h_*8N^I|`!+V15U|7&*^(Dz zWFR=xaG4g6Y@KXg^lbk_mCqYUCeumN|6!P1n;+Sg28~{nzT)7a0CpH<7wy`lDAGmn zmXOn`^M+r4B4)!2nK!gG5q<5;$my!gJADhZvoXzPjZF1MmcIMtm05w12-1~K7qzt^ z6-us!ggbGR7cG}&u3uSxuiY<|?RZyp`}hn!{EMB{$8AB)<(wxy_4JJ{ozQoIjLmX| zwKcvq4Dn$~MDQ+7ZZS>6%U3d+AiuO0Z36?M7hvsb*b|Jm-FJNv-SuO5)n6JErt3@O zs=@OGkvQ)U^G0)D1QiC3O{oz&8{5oY=(g@ESdH~#m$|LXd|X+YO3Aqv*^7|$8F$|o zCJL%9oCi-#FO_G(vK`rk{t-L4PypH6Jo z3^VzP{_;s$e#WSJ-+?jT*s*GNFXfHO0s)fce7bN}yjojCng8hhCB_q4Xi%#7NM_;f|Ex_Bqcmj$se`ZLnISLHDb5oCA#CpUP|*W%|Ktyz1-NP z5gu0PMYC6EsG-ej8AnVNUg?8TL9sf#{t15R@(bw6UObHu)L0OjG?-<4+c0KQfS&3a z)6pWAQC5an&?6hP2!%dXIT^*8lZR``$Qa7LwAa*ai7!!}Uhu7q^=0!S+gdI-m}<|= z%6ws4v=)<+^!}GaEo9)r%UGEXa$o?KDzs^kmeQTs+b1V`G{VZ|tY?xDQHBt}j`lb0 z8e+k`Q*V)<@&s)37%U%MNA5agSB#t8eY@k?iD`C?2G_SS^&TDmua?dsUuvPrj6=td zVPmtoi!$NKxl^`Z;$oMIOS9@+J7E*LCLFfRmgmk#k;>xV;s;xoa328|KRW;Fl7QqV zZTdya8hu@@5#`@Dt7KG3+nlzm^K$XYup{Ed$&9|$b*>t9sV}P1Yj>OO+!f`MA{u_{ zh(E8Ew%1MJsOWr^A$)7<%blk^Dw7G@*nbrUOqjn8{n*P_<7PHvzF;C#-DB~XN6_J# z%WlsYP2_XQDHRd4n^TnCc}gh`pI4TDCDzq73>lE@ndX+F5RC9 zZGS_z&!H^0RvCNbP8m@tB1cy&<^$$tBeq?UTO*swaTd-<>05qUt;V&{yfPc1=u;{# zpx>_^(;(~f)lKGrd&;Sw!;Uh1@pVX;-v4REM8q7&pl@>}n2S`LnsOi5+{()U7N5~7 zAayO+#vw!&G-7p!Vm50f3*vhIGkNc7oddKK`M3;0c+Q6H69B;i1`VK3sJVGQ zX9`Amsk-BJ^Kq7|60Epp!p@h|Q0QzF6>!b10976updG+~s@hOF;Sm@#pE&d@rB6@$ zIv6{QA!~pf5(C!SBSx=msj(LbtVpToOrOdJ1dEKmcEVpXRDUOQb^eA?!OER{<|o9} zxP}8L&}vR8ZTzl=deut(Md2o)$%dAgWyIntyw)Ep7%TF=@%a)WGPNROK4^0G*_M^C zdTuq}EUTZ0(rE(`v_=$gEKY@fBW`z1u-QP6mRzEK9nrErIal#d9;?SX14V&V=h!khpF61G5s7lBCb##8`WM|R+p_Ygk zXR=f1`qap{v^hUqccAcV@wHk_q^^s1A-HN?--z%6E}{1M#VYZhNY(0aEO-DQb`;&k<)JjYQL{F`&HeNIaWzTUb326>Yu@877q>f);$q|baC1$tt{C) zn>kCVGg4|@S$iAiij&Lo1E@>2-Fbk|y&?)twYb|kuLuh&v<^J~{f=APi9pzmqP5lyIOC+TUHJML4x%hbi)P1(l z4d3Jzl8=qK=^doIVKjxR>xH?A{wleFs|VgHy-*XehswpgY2q(7w1A*%UsA{pTtn7* zVnVON*a~(kEkxSEMbgPg|2zjp8`LfW1=A9X@^tk1MSP7p)H(pViG3!lnZU2hovhC( zPlf?mp;TrVESPsAl^AhnLN~K5)BE>4v@e)=K>x?j*Jz7QIUZMD`9EDZs7M#-y#&OB4xx9DqS6TvFmzCQ@4fdS@K@&0&YBxBEvwX(75RhtD{=&iowF_ z`=EygufjKr$gCAaMiaZ?eJcJfS+sMNN({DvPT4B1!Q~zwD3|$T8clZXzgVJIhZlKO zQo{H6ex^KP&lYALR(I|0izN{J7(c?+_eg06Kd%$Gq zkSh7i$vBx~fSQLXh0x1gAuNN%jd}LXW*z2WX_>c7L~lAKuP8SpJ0*FEtSam&tOjth z`dLb0_iC!0*s!cTL>X4#Z-tU6kBNN}^8prRyd%-+d3#-ns)iwa19T&~-<{6|b0U6( zXqh&K8Jd@#AB24OZn5*!s`j?Hc};GaooW4+8}n89P~1sU%JfKl(JKmFKP~Bnx?DkA z7Td|lO|s>PoGKIEI@J@+l*p($UhHOGP?cLYzKd8_)E^wXU%;M^&ZsAay+ME08+wov z4LJ)9jZhw-TfNoN0C3UStX|r#YhU0b7eVh-_FpT>Pf@4(85>1E@T*Ls0eTA+B-W;u zsTK9t;&D7pPRH`W92|nwDP%oOkK7KTT?Vl`TO)b~wkBJDzAD+~ zhEC-dR$y{VLXDtOn;x1d(ZK~q#%`WP!<&MA`1|=5!bNuV>6e3+RVMI=w9BgV_&8iA zF)XYU^7$$hx%JLb(7G_Rn88s*TTbuwuw?jG_3nkwh{Jes*yNj)-y$c#SRd8MnC7qF zPk5awo{aa62&5O;jaZdR!qb5JYO`OJ`LZMi}$ znZCFlw8hR4oqLEqF5!daeHu%t9nFnzo%TP@UgwPhk!FMmv;Nt|)p>E%$3USD#f5pf z_PgZ|=5(F%U}J`nJcj+!nG-ASaq2Eeg2=In-4cbW^W&U4taf|)t4Ho42Nu!FtGl9c z;!a>&Pbp=>OYlsLI!@Q=>_>I}#*d$4H=vM#;kn0NWA|x`@+JjmO!g1KP1@C&qR*5~ zcY5V5$_m}~G$0(vP6v)=rdHDa<&RIm32N?>$L@!WuHPiW%$g&Ms+t>elgT+^gb=yj z&i1lN&Ti>SEAyb6So+VVHNR+3;r< zyYVFU0$Ta~khDtWi^y`N9O?N@*-7^T0|=25T^AF-lna0bxivQWj#K4M+xkIt>`fr~ zp;oi?qI$$rl@W{Mg)H&AcdmmEF2FO)=X1;~ELA!U@43RGP`&^9;bE)`ITzj=o-c2( zEfUbu3)R$2o$Fj++n$$oknMbZS77MTyaY(JY=$y{jslqPF#`?x`f^r5y$eiUm0((& zZaUxdIb$jJD<8u}j=k*i%9C|dXWFl-We~!T)#~8*dLGAjE)Q*bW7)^&HIsrsK znIi`2$IIMB4A5HAt=ByEZ;O)qsuBk>$b3@1e7&hhU6C~e zU3eA?{La>!Ks=n|gV;I(?RY#D1Aptk{K;rh&|5$OWTX)L9!CY}lwqLOg<^H|5V{fy z(+SOAuxYw3XGs?H0sKfeB2Wtu#R-fG6^?%MlSgz#XCGk@Oz5`cy4U#DvQ)6YwENm{ zOXz4vFf=JWBe};V{nOM#Nm4bL`s{Wa#?|YEYx&;vtzYyQrj;nh)SS+u?`}X1p6k&1 z_ZrPs#wi9K4O4TybCo@yqqwyR!SOVy)Tgclkh ztJ}gDM~A>4!I+V6WHngtjc`2LrZBSLaXZ=6GXX;gPNATb{p01n>C61K15_QVRqxf1 z^L$Fm9p;0CAdNq0&ka7i*}3A8K6bk|dHH#nn@fJXWKVY1xjyJp!M}#cRWCann4CV2 zDAZG1l55{zEJ5U=gX{G7-VX2a8h*25cPT1HTKBW?k$9ggiLc5~2{Q~`kTG%dSE~>` zQAUUITsNv3&o4Ai>bZxnEwDrRW2d)yMiWUmug_@znhp^yxwAfKP;FdU8h09qvvBnWOK$1vwKdTy3V=a}0b zip0;TsCkv+rM3;7KR$z>&IsjJRSgr2-=cI+K3AWQrCw}a4AZd{H;mh2aL_8g6 zA+t}Poc!&JnOqyF6u&B-qoGvBiU|ie^qKlxA41jSdF^-}Aw!Mkf3P(8e;l(?X02Pg zB@H4O>FR&M%N*V^JCs{?I-+6Zp0W)+BOb`4Y9%}N3y_O^TU_EManvB6Z}^YyD~NQxTy4TG*qQ z!}S2l-ZJg->r4EK_Ty<;s`9-ClXAgj4wuv~l~eQ^)W-q|QvvGqre3t@)kA>iCX}6b zGe!zmE5qX3BzoXDcQ0Q-YC%NgsJeYcqZWIq(CgO3hq%;w7NoiZoHq$wdm&FW%6Hms?U2;S74Wr&h<&1vY zfGz(_CCw#}APi8lD3BI)xIxHy9J9&~d#JVa2P!N>;vdQK^zosyz(7ovIb zWi6}16FrTn^kom}x$rFD{TXwKBd$-dsL9|OzA zU_Pa(+_`f%tu+A&jH;TSqiM>&ymr8vnH{51U7d^EG$fYZg#y%jR_Wa^#y~XZU7Y59-5F8fpR*~X7 z%0nqQ-$yw-D)ry*?y?lhOFCxXHi0>Ci>`tij?Cwm`un>UdD)pZIg}$n(#7qQE&^Wp zs#l9h-vK7?cP;Mg3zh!BTK7?}vN=gP}4a+Wbm= zJHHVY*J&-&5{V(z@YLuo9d;u%+NUskhrI{0<*JDfe68 zD~7)AVb5H4bnz5Y?(DjF=SZ9%Z1kN+V-Gy8iM?i|0zEeE%Dh=nQ+)^U1It2 zIf3*LQGr*^WCbX5q2$0Tl9BfwpXn7NZ={&s)MTYh$Q!RrdJNu;oXJ5}sC{a!q?DUw zSry5Lzp6qZ`R$o2J$$Qxx~?2nVhapdU}ZJ|HRp2Y0_H_I`xg-Vp4T%Xi=|+z!j8$r zpty;8>(2Q=9}{U|xmum7I%uvvQ#`YO7@CkV4qKTMBY>ssuY1HiDSsj|7pbaO%;yiG zxsnQKHy-X+6XX|_(>61~1UE`@HDDJ>Fy0v>b=td2j$E0cy^G@JHj#;1_ax|<7(NJF zbx(CRIFYev=QBOQ|1pu-d}?Gm1MF7t(tA01|1hsu=sX5h<5s67?V3&|g*LapIv_G%{y4;{GWAE^(H#||6Oty|aFNPnXhbYG zZ@13Uq-sZwD5MCW}s3tZ~B5-epS_TL5E_ zR=P@jRJ(huiJiLU(Phlb;Yca}j?Z-s$~IqL{Y=x)<#a%xq}76c z$0{r<+k!Vx`r{T`EM_AP*>y&TE_T3AEgDNl5GoaFL;EZ#Gvl$-nWqEYY-EiI0xnXD z%Tjp1X$ES1)SXm@h)$_MB<|eTN@=VU+St2HK_;+K>pV_lnaZLr&m+e#+V5`I6fQcc zC{cKp5wq6Sv=Q8;yA^W?eCL`~>VGfw>^JN(E6RMcLx1HWF%_M^X~UNDxpmff_j7*m zVS1<_s@vb;GWPtgVm`KjSD-K^ z7aNa6J|| zSuHn@fnpoqu1UB4lAM|hA;*OdPvI7)oXlnC18B?Zs+3Z1`Eks%H0d9(M1`&XoUP~3 zc%jKiF-x_HaWLWLP(;o zrH$e!RxtNG3e*-Igz)q1dab3+VmGMfwIL9g&uWpk^%=qH%wSYP-~X1qWC`mQjluh3 zgdT@CHvbROy8a`){lAd!GV=~6wo0?nC*f=&uP}p;v-Hfa6n{K0dHgu1=u_f)zAblp z@8IGNZ6z6`H;J}mCWz71J9;U>$i#NbU1xY(29@f=0e5m&OIB*Fd`{2J@W)@un=dIw zWGT;tk7s`$3^}wGv9n1Z8;`~RD&Y3>iYly!gBu}2Zg+5}JTH-$#EgOKtJ3770eYsk9QMfktOP$OL`1XRID2sQa{+^& zTRl>a!@Olt<-BFUidG;DRk-H}5ot*KsAnKEX#~<5#2`{oqh>xGFF^4PoLlqjz#sz^ zYzcxS#-oGQxF&H-3X$n;w;v|GjD~eAL+6GrY_5d6E;+GjbEB!jhbPSibN~ z-?#$H)8A)41TJfxaOCsxN*eof#m7f=GqqRu7G;e|^YSAOwtr4`V%!n7&Ylx1fWtw; z+Dtxbl_8mIH1IW-Z;C~OzGPcG`eobk^YbSLLjXv=vgj4n)b#RUP7b5*yr}44`0s<{V6F^lR1Nu)B z%JUzNcUag?Ld`_Gp^goKcAYO%lmI8;Zfs zS};P@3|9c8pN*y%S2~uEP?<7uQJ1WENB5kN_nGFQjwqS|D#f?GZGvs)58j%rI<_G1 z_lyUn``wvBJ*`j~CPL9K@qZFhBJIfll?Sh(Al0H`5(OY*(xrhKVy`!xM)juw3QpxA z&Bnt^AN_>{4~vTZNH2vX*x}ap#-g=qj}Deqt5tLdhQ?}@q^ULZ^c44eifnhS_!m?_ z9nh)=6G!3zCjnM6GP3tI`A6cE#o2Sv;b&P+l0@Ea*@dW2hYpCrc$5+_ZQ(L6+Pd;+ z+?oce^q7{{Bn^C~@8*H7AOG%k2|N7o}$#>zO zYxww)-=1A=Q2%ZkhxUt;OE=m4mnPAx#FaanVcdZx~f&3=HAzRam-tno3QEg$ihy0{KyD&iA87v@nhNX>yREn^?Yt-$yvx*i_h5^*heIIc z6Ve|X$}pNu7aBTzI@qs#-8aVWWWyeQSLgFCM{#jUepy+=+_=77A5?guU5q}3I+Y-G z8;$N?p|EfKPS5%led=Kw8shs3ruAxwy#U4n3TozldG$p;T#gZlpQh7F4OZ=dRdWi3*KaUfZ<1a5{t_>oF}@sEHMAwG8PO(N9UvD$ zu}mNCNHO|}HGCf@D}wmiNrh&L78frToz>w4`$8<(@t~?`k*5z$c!r`GZyRCZJ1jba+I@k}zcF9DZo}8EY`3FBYgA zF+*-iEdTLT(k6yqI4MrJ=c28Zm=-zJuk>4Ss#8;D?oKu1kLc+$8KgZk7MRP;;oL*N zF>nyS)MezvO}qDkygDDBP|8fEjcrSbNFUWq?({RAoScWPuBt1_5|c7Y&`gyU$BaT; zCaTueE2wI_cka2VC5hJ#2I?OK_*=)?N__Q*FG-Vu&D{POP@Wgb!=4|4MCTzhiF>aY zpiiZlkn}O_<-`(4w58X3ky{`|&r}u5pjpANCrhtQ*FY7l7GJuW21+VFackJ>+v0hq z?_J89bk)dokI;1o96~~;odm{Me4xowUC7&-n`71Rcxe6W6p^FtPBXgIR zC|zF1tu2w*zc*t%a3OTQJLN}N-0(VQOG?9(Gm_V?i96N0cW@nfmlTH)JhteUB7GaT zdFd$8{Yj^SKQUfZ#a^@y6CdP;z-+&sHzgS$z=(^*I#JKx)irTffV82vCEJ@TO?Iet z+`}r*-pKz*W68#%LkEwz1-Kbpx~raZi>B}%)3`KerPQaEkqm1Nk=^y=NMoHq}xtCkd_lcHx%0|{msxK?@1P16qA zWr6f)ka;DcNc}$Q%DHw$VPF*!v?_jaow#I`5{h%BBi7TMnPt?Ud}cHvBU*R#Zk=&9 z$n|UnG-?kQ=QlC{`Flm@Lue^lxq_5l|p(pBWTawuZ zYP!an+cxRyOhrnD4iGsMJRO`9#4-j%dqR8H-B9A5FnS4G@o{3U7m1B2JU}--Mczem z2VMC9hv{4u3J7QvKM?PnIkj~tFG|h!+50Loni1=H8GY7@ISX3(IxWl+@v92mWIMQj zG(AVZ&ue4%)*A9Ce<3owf!NJydCnQ*-9W)0&d#a9AL#fO;Q1Gjblh+d%>DS1zU-*n zLpM8Gr@1WQo)4*2M7h1{Mo)lXQccYv!MxV*ocTwPGIX)BxdLc%T1c70K8I3NOqvoo zu>fPG;1GxLa{uP9YsP`wF7&E3&)rm12{OlOHIpFy;^9`#stkQwy?260Hd6FJ8H;=N z`Ne*F@-?Ol9^Ur>3_xo3g@p@}m=DPBixtc8z0C$OUP;7VZrDENn+ab!kQA3`+l9In z+*^ksU}|f`+`IUlZL)iGb&s*oj;EPh(I)Fj7oX=Y7F(HuL?N>RA)o-T4!+9kQw(jYrK*|SFoOe-OmsjV#4o4 zh;ABV-F$Nizb%a0uV^|JHO!@!A7TU%EaG3rihf<|_m}lvf%6wM;C0xgUB>On$(Ms{ z_UWc_i~vAF?+H`hD;LrX-^E8fu}Td_ukIFTv#vkFGXmO=hLpXh~tthJ^HBDY;&7clsI)w_rLAXJZ0*2 zh=Yt#ta_igh2E#&y-?8e_V``!`YQ{|K-XA*{q%Ul?2NoceB$hs4)qxDy97j7aVSa= zT9XR11ltUaQ-H{V%bavQ^uwn0O-jlxJk*2Rj962BpnR32tP9HofL%(x9T^X7#r;TwHiL4P)ngQ~MpW z?okU2H%F1Xezwa znH0WIW+w=D?st7CwGx@zZZKv5R@1$YriZy%@B<_n$l`{DCgtX@#F0|!&a12CUAaX@ zmK6=U8d@e(BN=*n!l_&-7#qa8AtR%l@q}n_%ooL(Iq1u)dbXeCoS&O@WLdI9WQ+9E=qP{U>$l=DoDK`T!L`biMCOWpg-v&P|L%cDO24c6;(v_-`k$^#ht zv?n5a>M^W|%nm9Ujn^&8QZ3@a=_k4^=0^03VyO)z`|LY!0!0pFT9CpT}XA43_e29~edd0wZ>sbi!VI8`MQ{hEHNM1B5RjmwZa2PYbDjH@n zkC77c=C8vmFm-H8-b;F@vgLlme8Se>Qkw5ESGH6$=+uDz`qT7T0m#_`}~NCM@9DSIx=I5A|-otMKuk4Hn7c{VDb{b4QVNEgZL#s8=cXVIvtDm2+`NpL zIh)fq$xhU`->$WU`w zi_SHDW-wG$ecwnBv*ec0!11Xndo7{7-n4j&qDRgE0#S2PLNj%%yNUt2=c6%%8?}me z{}>*w7FO=h>`|l9bCkaQsTRVI zR1DnUSu~Y*-6{yn|HHp{ApPBlq*jj*BBBF13)42xaE?aM?{GTAI7>e(=kO$6>&>;@ zRxf{L1KG;nQQRx`v36&pFDA%PV;56+=-W9kAzHVG4iTIX&Nk zUTvrhs`&*mWt@_zpWpXAULpg|%i&04k`z574NfwV$|+0_FdY-yk><%fAkuPy#271C z03O^+E2st2CKnZ?M&_526af{#ZMvCp!pyEJ8_`G0Q|v!Ngjmv}^8T)FN{cU_ z^&&hR5;fao8Kxt&pd%}hv$tDLWl%NdX2|YlY%;dv7O^aKOM(^P9nY4gE4icMfkY$i z{Q4f6j1gH)tnP>;pBnDmo_iDhevdu)-pz)}Qbk<=Te(=foGO_5JseCC`f_qRJ>bBW z@J)y~4c7uZzfMg@+VXUI8l6(i<+;;g?^#Q|OkC(YB$Jz9c9ivee1ZA00uPVLRv96d zAe_;ievOL1tk<1HyyHNXb&K6|eSUZCV51K=d@+EwR|)7w&hhaHxNF$BJ1jl+rlIT+ zpdX+MAw3~@?**bgB9)-yN_|H=S(k{oow|v@{;6B$rSQJI(dr>T0Gjj!Xm{SOcT5_QJFBNZ=hRxPgMCJlrlkT;R)feW zx5cek>B}zw85Vz2UAE=YHZ9+`kRY{~Oii`lFMd;+BSKT9KqOn>=_M)wTb?-hbK)Ey zc5LNsefxqUwti`U$rQgNUh!4INu#KC+79eN8YO#GpD8*4-SLQ;ds20n4UX1>i+s^& zvD@PF%aE)%O6;)>XI?430?8UCcfqM>>;(eXFF3Z<>BPWZGdVuvDJqzGfxNYz5vI!3f?{1O#TXdECt1m?u-vuOJ-HK0#^Lrelf#BkM)?G9n4k*({ zpV47m6-=iINjH%%3WQf-;!pXp&V;t>ub?e0HfR2l!z1ziaInzaaO<3S3g}^CogU}HM=AMrn1fPv|6db4d%Y&Q{Rd& z<+8egK3Nxb3)KwAaAqldhW>;$bV->Yz?tb2v$KrD$pw*QFZvrLklW)D3_~M1xK3xx z%LQQRA&`%>xKl*$DOUHb*jj?>*qP&Xs~cvZ*BRKjLChdN>Vj-!tom++wF7U#UJprG zSX#bD$xK#r4LB8fN&mprD=OjTYlho#uCtq~)X`+6BXxRt%{~o1CMUeo>{-{XWAOs-4VhvlF5?d?|BiYd=piVz`me z1MrHTF~R%!LL#%vUqyyZ>1cpOPGuFOySWE$@(-h>jK7-1=3JFcN%N%U^i{E81J~^t z3d?DKvPHycFB{3vRjTqQ)MDM!`v>O_T59i{3=$KI-%*U;(jF^z!0NY6E?J5o%2Ao+ z4D8^K1r&a!+afYKq4>-@w&|-^SjeNlRyDsUFJ#8VHVn0^u%RZcGxB=9gASH=Q=bTG8dnLvOYck4?|AaTx*MfQtM-~c=;cD8J8=4*GoI<3vSY4~} z+7-S?UcdL~^sAF&nJ`;PXAGBrKEp#ZOI**G(9zm<+3tU8Z2oOXD`bcNi{dv*rgUdk zmR3uT7m+d5H}!sD+i8}+9Ioz<(c_gS|FlF$E*FkiQtx54>douNoqta`bU0?N{rvp@ z+YXvoF7Kw?fU$wU0Nz@gxVm#hx3Lq{BDXf`>uD^eFdBJM(X zbl&J-Q_qzmhzKO+x|p7sjy-8FUFr>CbMO30UxKy@kP z7HyR|tPeS+hP<4mJ?odHUlulsA175(zjsC`sc)D1IgmM zfL8`RPfdq{)!GFedH-hR9Xeem%@eB3rBzJaOht1X=8r%)(w zTB6Q?98}IkcX4$3B~;|e;>2Nr__G9p)FYzY>l51Z$cwGV*azD*rioBK2ikr4Bx5Z# zr)oW;bw68NySgV={{nRP%^MNRoZ6k(LO7p~c)5q(ptdQ`MsDNsH-t{aTer+9jnN~R z^NXIQ&^MW*nZ&FyH&kxJRy$?K+hg=I9^LI~yhB3=^$Blq#zT_T&Gl9D(MV@?%N`|W z`W>Z>kE%Z<%V%2! zD>qySW%A!h!-DXImEW=nsN*NrPk;-*hcCQW#<#U%wI~_+=w+l%;ODKDhJF1O*QYC5$~98B``3aaQB=a$|X_y?HltFcI&+rJ@>pS7jmj4U#} z+#61Q`&0~Gqe5*3PfI@O-*NMHVHo@^duZ@nvFNJ?hmMApCaAxciM@;e4QpsL#ff-& z!am23E51&AE;8bL<_7Lyx2yO_R_jq4XtQ_?r z()9FoL?6}j5SoY{6W8VSQokRFighAFFsYen@WCTS;W0rvVRtUYJddSIu9#bnQge8- z7F-iFWY%HAY2BGBIh3tbH?6mR_505x6!5u-&I)c*nXnt4sN$ct1MhB~$_&>OaW5Xm z>GW>^zu3=DlG3XygZ#!Rj=OxvGz@6utYhN(xFs-y##0f{5TUiqb#F2XPU}XzKY%ft zYe=N5pKf@8&B1?yv+o!HU<7F-gbe6wjmT<$;{qrzGZV8D-lhn=P5aVO1FH1^X;8N+ z|HD;@3Z$s=>&Jc0+x6A2uHTC^t{Bf=vlEcp*!%@lk_t@?4bgai4c=&rFmEtcY@LSoBw7{l`{mkxkV)mJ0Founq=VMv|c}1b9VmJALGa4uhjFY2< zeuz2N>x~gTf8zN`4>jp!pXN1s^UtrSh(AU?X4T{BohM?~9!wmYcqQo&Vh9)AY`3jB zraN!p_}W=pvLK!64IO!bwZ(-@!E-}NI`)l8Vo*%^EJSb^JG>qh#@^1ZyX5~LAUMbTZ$F8}3Z#)5b zp`4!|85lsO^#R2L4(vZ^4#BeN%5#7P_ICpSSK%P+laE~^FngogOke4F!(vbu}q9VHJnnaA5{jmN20FELC@n+U&9y)mVC zd6SF2JHBOKCl05pXU4}G-0Bs(ci#A7ZYfqjE)21n?gEu!@DTywr4XTPBPVys$mQW* z9xsY>{Z{&iCth&I@_Pyq!%`Ikyw|$89aE@XoYTWA4KiQ~R@0(>z?{tdU3Z7-9t{Ge z95NHa>Lv{9o!TLKcFqCk9(4+GYps{nW;S(9nW6iLbls)>H6Mp8f?id;ELb4AHCGJU9P5 z63KsWvkUp(dXW1c0v=1~H=SCmIMmH=JBR__oAd2O-4axsB+jN#RN;;-yx+kd=D70o zKuY3Jc%gw4r4l7i9Ty zx`3Ttv(<^q#y2M%a2NVSx%WRVPWE|B?)yc$$Yi`642G2LW+P@kN%#AoZoa6& z8>W?aIlttglw1V-!5n`9(maLLZ@D9ddO(-M;Mf~xyeE7Fe^=gJI@Q4LoBytl{gctb z8K;oya-#I@(tLyE&hvBMxxWB#&R;-*)6jFJvcS{D=hvRU(X!O)&xrrGby3O74vF|! zd!nE|>1skvAbVPXR1FhW5C*gI|8!ObY-tsU0<)-WS#nbSh|60nK zR=FBiT-@EoZSjppaq7Q-H@Vq++f}m<+o$gL-otfF2AIB&;z)A?Ns-KBv!3eK8cW3T zFw*`7RMdSce+>VUJ5_(p7&eH|Z4=-vj#&Jr>Qk=<42E-=E2qC}300PIkMazk6a7Uq z%((w>?$y;UL+z+`myY_1uQF#w(hBw-=bbB+p-2PIhj z;v!p|nu0u!iU1rzHB{qJRVm%{p1ezXR>KxbL$kA}?j|ga4W;}buf%@SN;R{+&|&ubakqq1<#NCd$R5%ctBiEm!D`{;$o-E2GzEV9eZ4kKY~_vnuNL=D zn%lZ})Qps%3KJ6bQv(lQ=roPnW|WRt!se&y#^QTj{AQV#2FLnQ_{zg}{0Rg2a?!Jr zsrLT*J7Ht|hwd~A2}c&TA7p}7m2slc27^=Et7&H z{c@jO>iYS@T@uO$dm%+&937SY^W%HiLF(*D<(^s$?bl{p=^N-zHro{!e^cPZ zRE^@H!5N7n@pLkW>vvJyo6bXo8-z4iO31jGn(v5zNN!}67r;AUl zT@2Q~MVABV5V}s0RSY%0hLhm!V%_iMNv_qmW zwoivsGp3G4m|+t?8;0El0PX@Z$wAIC{8?s7q%zr#%ZuWfV zFQ7Z;pF1gHZTgkIz5@efY0pQ3ha@zykhx#m*%{Zc^Za{l;x=U|D8S>r_xU@39kau{!`hiv?cJt8 zrBW&#A;}o5obE#URlNZOpLw4+A@gxrP2~M-ODy*Z&i34+nh)0=I_CzrtkE_}W)Dtw zS*rVjAs=y8l}o?Hf$$V^)1|#O(R(Q3t~-I9e9<{rw7Wru6h(HlR5AzR#~T*u5Rv(#R( zoD5W-n^gAiwhW*={?SKKzED8h_a7ZIEX2G|wc>e66EiE*T79?;)mEev;jjKb|FY_* z)9*vKwnF9^j}7;F1O)e4?&8i2I64$ckQp<&26&>c@{ezv4ky~$O_OdYk*&Pm;2;VL@g3&8 z^Q(WJv*|C2!v!w~_e zjb~C@Nw31^e>tK0AE!Fp`!55V+1SXf4rLt2b<*VNY#p#lii=-3MO-9phx}4s>=<4i zTFoYe?mTg#+@F2yquV6(m3^m9PMe37hK7cTwbwFyMiqb(b#RtSdk}YfmEuH5N*|wL zRJruzq#BsfjM^Y&YO0#)Tri$mhy&fHF`f0jhjh#MlAn|wQ`zAUmddf^ER`0orBY^k z&#Bj^ZHKtt#PS>ho_9SNV`s5f!}M{>l;=) ztOW;u0&ON@N4*A_=$?x~7}gl9>8ykOG_qYjNn9I8T&3RoV?`?(mKh)|*vkmyFStN$ zzWYwJ@+)G9oyzX~ro6qKcdx{eNzFkvTQcPhEZ1rdfdDBgGWEhLznch0kL9yJ4r6K9 zPs5QfqN=xj7P4d~&S**kZ&4*;EX6$(z@6Kbt!dV4@Uzl&vlyK$eWqlk3XY|l_qsU<*&;TV_Rj?ps>a1+I8(oJwEhLW+F^RtedHImFC1IZ=?iVWoup-#U6QUL!z4Wg znBYKhHFZMP4jI^|S3y$Y#C1=A5#>d75WxTwO;C>Rq~#T#Bjh26URA86$u^fVC*|Sd zyjvJ`XApE=bhEJIyRj{ispjHm4=4!YLBpR~g;kt=VH7tf3GN$6ce>pl{61kJV>Z7B zj}OD@niO$IbideEAuyffkVs&SU6IxZEPLZY%yn5Z(^UIlN=sAwj&c{s{Xt8bTPhGU zfWD>@AtQDHdA|>ij*-UejC~YJ8{6d+-lEFI=fy%_uYP}&M;-b-TA0|)DC6fyO~XC= zx_yRRc-y)6%8z0O4Tu!n&!>UGgn>dxaqc#5fwj^z5Q2_US+IylMd<+xEXDJv+wU~X zJwV_-Zo(TtPhWMAhc0N?d6e^caOe*}Mm?xmZ9dd3ERzaGgVLj2h8NPEY~YG|t#uZ| zOi<`){hWe*0m|5R@9hq+ee`SRk8$Fel#S76Kl&*|t);!+o^N-QHJkGhK_s$DSzcYg zvK#t|)~TCzOzDi91Ko6j(la~jjed$v)l5$|E*u`_1Sg1njuihH)~xRI1l%uni~5Dk z2%ky*A>HF0Wci$e9_Yw^%VGM!JARDb)|owmAc(t?ztvxIdl8+&!tahmqG0k0Qn=A+ zNB55>Bh^Zwu`>tGY|^o>spbfRKhdP;81(oFPiyzi?>SBj>1g4LdKxoHPeRIlD7OlN zn4_TSWK<0`d8PU7yc;&3Aytr4WI>K_IzAQWg{KhU;?{gwUry-*Ssg-8DF^52n411Y z{kYy`{s${%0ZD{8s09n}U7 zo3!b7y)#hW^y0nzR0@=o7x$2&IQ79!1d!D9mh%!Y%(fFR-oLoAv=a#kEtj;?fH%Az z)~EmMkQm?ivS)GjwhZMJk?&!C8QqoqeqM3p=C}803?&8dyx{reJF70~WLIlXfHC!! zmOAZ+Y+s8irso1R3HiE zGLt$F9F=zf`%vcB#`czwHuSb<7(vT>B<&gmh0|o@{G694>Y_XG!`S8NL7emTU%+*X zUbBptO+!;ZdPdzMPsH+>#M=!#oryzsbJte|3H>IK&^eFgR5YN zoVxZc-9-xncH=8C{wLrb5?HVo!9|pkHM8_1sYI`-cBPpKSv6%f`$tK+ZTKRydxZ3E zKuUHWcAyutc$Wz@0EP2NO|@IRXFODt(SDcvV}-l=T3>Fg{0T_OH9Nb@z9e{J+%Y$m z?k+(N>P}Ox^5NudlA9eDFQ0XN1^vCWw)BhNpK*K_5Q4(pRhtmxHg91u9}5}h$&flh zLvjP0n0b~AA+v1p7tothWG^75BvhV#=U!yvW1J>C%y20{x+;8aCcnCEtL$CYtjD;H2UNV7@jc0%+Hg@4prGd;Cr{^9(ZgS7ALFM| z>s42hkiWnj&e}tYi}gYbKX-1lcSNpPeM3pOB`$oA_sDrv{ewrhZ5DS1ITLcVl%(L! zR>nIBdl8EZRyLj5x-TzdP)O%(tjk@Y*=h)SgW!TkJEx z#m7qvR5V`sL|R+qRl!A4)WrbHwW#eQauZ7dRMkZ7J$p!dIUo&@gP!{ZzX zLAglX!L=8FR$b_RD0C_ng+?Ikf`>3^c8P#NOMfWI9 z?vjlQW0l0+(+CKYo&R9V0L0dnZ$bp4QzEv8s;KE&2oDj6W@~wKLh8wj)`=19S`k1j zKpN%fv#?tWt05l(TvysOxF)!i7GxKtj;N7~oS5vHZqGFx!985*7lKEln{ufXU-Uoq zt54HUYB1*aEYWp8%tS9dbkT7SjR1{B&(FbQ%Np3(Td#`1)k_Q1Fh?R`m_1ue>4RFp zJmwpoT21d=>l7_-|+*&aQ~S5ZdngH zd*LAyCkN5gi)E9pc?Kq=DMax#cYMf+=A~Wl$K6W{%{0yYv2uG-r z#C!TTL#x1f0CR3ccZ%Sr3fmj)|AW2vj%upw_kBT{N>QYDkR~7?y{ky?(mSa19teac z2q;wu5Tpf&NC^-+BmsgTMS2e%LNB2s0wO3rCwsr|KKG7u@7`yReaE@y{PFy;##)(K zYmJPVIoF(P{=UEOXTN6|?^|B#Lv7C1Fz0ZQ%S9W$Fb}X|LoEhS+N~|zBE8$V$H4$y z@hPt;FaAWZ0H|2RXWg{v(?3#ncsi6y_w^!ELVcFM=aiP#>0b?*|JRuZ{TE$;2xu=_ zTJnPGpbaD{Ncrw$QZXifbT*798R}1I`28t`Qr$SaJ@T9Tdfnlc(_dW#tVrRwJ+%BT5gSqEHfMnmhOp z4FzDacQU8)&g4!08-0l#hl8n(gDwk@q%FHe4f#)HLF)RjqU!Xf*XD#_il8p1v0DeF zcsgRP0Qn3D!1mV$GQ;cyW)<+2x?zPfaQYYgD9qroGYA z*4%Hq5YLgd#PE=QxUkek`OZBJ%9rlW1aq7b<_-nE&ESe8fflj*P6-L`>HlX=afBqr z|MjQ*C(LoHUSS+N-h6M=;|MGbfA5yy3)G|x@2YYw&FH+vKve(%(45748QpjSspb7F zHu_3HD?}gsEh;g6nU41}_kHK!cm2mVZ{~8WNh)>S`1{NMBPv0dpM{D9+4g0MsWvk` zxuvuCSopfVVA>RIDUBBJcYpFZd`5qQ*1O|?a!RA~A5)S@b63iuBN}6#DCm#-g7iS1zqZpT%9^Z#L=B$|9y3-dQ3@ZLp-#D>%x-p{u&RY z);Hu4fGgCNHedj1&Ohk(7m@2s?~`+?Of4hCFaPlPQ;`R)X7{(k%cdim+qy3^{<+Wm zpc`jb8+cXQ#MJyo^56CiiO>Ho{JTQ_(J20I41f2I|AU?kC6XH^li5H&j?R)d-_7=H zdd*2vH8uz)U=MZAAvpqI)jfd6?^|8D|Hdz7s?7MuLj6B;F`)mXB7P1j6QlH^t@3T0 z3rsn}>&~>plj6d1plfLk{kYYeNBxvybywI8m5*AEUb{B4(zF8Kbe%uKuCT41eWmQs|qNQ^b$h(o<65SHwt^|DB$Nl=GG7@~1q3>aEi#)yF z(qf;Dzx=DO0VFOwaGGwQy)@I~Ivf_<_fUr}# z`}xFm!v}{cco6Hx+c=mY+3RY-iAi%@i`a}rXW8)w8(5>E$$W8>rI}4pNjXr^`D5iy zPvHR-;GV9HcI2Z7>;2=2Bsf8WGpIhbAiS`rd(A-IJ{GOdHY1vVJoE@I{I)tabC=>y z^+E%nS0H%p@>f`cWNyM@b^D*xO3!_dod;W$6)@p2D=TM6r~_;7BdTmBP9owy{uiIN z#I?YyWrH`b!e8Qkn%C?Ny^HQ3}%lpy`_{sqvltk)kdCsy!&H zE{4tCj0)kMo^ZkV*z#t+%{HC}#DPpcv3rH@9}^BvQ7J|o$?gj7si;|+?A?~HPQwY5 zyw(S^Y$j)D@6Mxex0Kl*1xQM1%1OLlM0g5(RNsrVKYN`1#UtE+fF95sIA^oxr>cTU z-H~Rs zQYk6qzjCWgEg74T$}*;t;0RxF|Jhzwv&E%rbEM*EUvV&H$r+~Tq-6|?3~*j~cuj2*3zZY5m!ogoEQ z-b8fePCieYd6n}Oe>xJvEK22pW52b^qzveLkaCf8<Rk&huUo^$gmPgUaNhnkexIXl7`eO`C{(L48P%~mobxEDvFVXOm(2VczX z?bTFBr|LS!o!^SfNa#=Q#GwlBN#%=|eZVI>by>%mW;4^$0{;Q%`JbA1|39;aXo>$n zm;Kj7mG5rc`oDa1u0qjM+a%pzdQIv7Uwm zSfYzSbqIgxdeVFUjk{KLGj`F*X>adksnb$@P3{_j$u7@wC{GcOGoy^lt@E@Qa+$$Q z`e3FfI6*P!BB(-1nONr%T|a+Ix1o(DfZoT$HwzO7(G3y1H*GR?>r+I7l@m``Jf){+_4y6?x)^lMz z+GeHf3M4dYF{ZZEk1DeBWTa$a;_3z1yV~p&G~Hjn*)3hUS(q75j0Huw*x@$TE6PyQ z4T4!{9`=9~{7ceAKGA;O0*=xg^8(&TreEQnJc#B7=svYo%ZMFz!f;NZEjE|*3&`&2 z3y%R1a0fBTDNyn4Byd97H*A+RuN3iBC_w?ct*{Kky82)hZ(0$I?E1y;G zEsj!$dYS0J*w)rB%t!j-b{GPIa4%Vsk%G?KB2!97XxJlO-#oCimJVpwl+JK)TqYAcVPvZe>}xypQT896@lB%v<|pU3PAsfzZtPU%JN0$SZ%Oqk+Y|gT zl&8<>^)qCo51b)V9%nY+S0`cJ1>l1t6DFR9T~TAhLCtBbIa~e*Gx2k%#ejJPjaMu-JWw1#R!9yDYyv$cqEUW?3ONYw-xC(D znVJ=jZ}Q_bt3s>I$jspyMHM#K+d^?~U4n7gosL2Vn(S{<}idjFL zf0#IRILGU4gc#}DYznHZQnN$R*3HG~#L7=G2w1_^?}J}0b-ezZ2?GNpB$w$QO}1;s zvoA$9@0rg&W)Z9MLQsb;yN7*NKkbORl_%EWuGGeT0|mrlMq3Tdwf7QZx8YDYFT0E_ z3tOyE(!I&_^Ad9XA2EOAy9_1P)yLje-p8AVIb1RfjAL)7%}yB)82}i{@=6FwMQOj6 zAUxB$VaMtbfPyh@>uL3bVk+XnOE{$5h|h92R;+eQ&^KorSV*k0ihlcUK}hYcn5Iao4>v)2_Zn%yZAP^o3}VNJ$krVnhLE&L|JFw%AHSc%-pkdfO8FiB9_01fd>;xA=>l2Tzu*j z^);k*>FMcvYdsdolv`!KvR;Sl@D2S9{@3&n)_Pt$s^sYO{w}G4MW~^FGUtj(SF54t zPK7;(mVzxk@HX>COvx<{?cSl0vFv6V|Iyc~Tl3tS+N7)RHfV>F;jebFm0q8kz77bA zQ@>75@J<<1g00ScbC+k~6|4j0lNm$|Lg)G&O$BA=;m%Y_Iz!7KB!IIaJe*0f(Zj%) z0m>rcQXa7wQKF1NHhU*@dA6h_?k|M&va{OfJ0YW! zxAg3?B3LRm5kB7fuzcV>niu90lMRBFlD=S0Lg%5gQv%(oY-P8wSuCqX?=L}YTycEG z{M*&po;h0tV-VgBkCau^27rjXA3M&fz1WQG(Ihab=f`c0>F}>bGr_r03McW@BNXy{ zVbPu6g{8#lAr1m4wD)W`iLfK%WS3mah*x8>|HEJ^eEcd>MX$^c;JJ7>IPV)|Dzf|F z{=GgClBlHM(-G>x_n93@bg7YV2vC;8{@`6d|fq!NQ@p)qHOcuZUneP3F1u?Vo=zun?hA%28%B&FbtV z2U_O2w2fnE4Gio_Tubx9jg&8#J~O5*&t%KLxDpJH$$4L@Ki#x@Bz=bZfNU5*hZU_+ z%4W2>4>kXw;n>sN%2pAq5ZQO#sWZ?5BsTGIEZcNA`R6F*Jap&pc3+^ek=>ZU-4YI} z)v_Ej*Wy?6{WkH~!x;v_tae#nMfI*eJMa-B-4IMI?SQd;P^?z+74uW zVgQh+MGqk%bb zmo<;jrzZG#0hZmf;viVSt@ZT~H)8p(KyMeR0hqc5<^TFQ#eaVO@V3g|C$RZtF!6ls zzjy}F7Zv9Rzx#_w;?&T8neKPrND$B>3H*;kg}xA@Q!Z(S|M3(u_i{YP+LPhelmEO_ z)kgTA=l-L7{O?;3>h15k_`6;F@9ra>{}hDM{j;Z<^?wK5=#!iDqf13TK4ZK4Z*Wr3 zzYiy+WitC8RKx%KKmONruBeo$i%;;grG=v%gQtZhS><0jnj(9dd`4-cl3koXuwBDM z)m}9(lmT>1<2>Z~wa1Xwl;i`2pXM*iw(> zr!u7&6<+ev_63ouFvd8zh&~8mZEb3WH^ndy|H@Yb)%}oC5K-U6@9b8!sBPBjwqIC! zBh`GcvurN#bmXLl$-EcuHqP^HD~V=^wEtn(nMaqK=#Q;$(DPJF%0X#dkbh*x&K1p* z67r-blimK>tC$tn)}@^aL=QbAWB7E$!}r38inNvJi*JOU+#M0<;|Ldl4=#T`?2%J! zCuiT#V{192;q?G&-5U=eQeeNm-c(!6SQSBD*tk%eWw9V*UbE=;dBEO4WKSqXS~PYP z-boGGPLBHh0wElk%sP5p>A{cL`&n5PXpTySLv)kM2wd_o4{qmYl7C#{9-Z`5li_q8 z7v*$qm({dCRyQIQ9U=e?1#kRv9b>z_H1+mSNUNzQl%^I* z4&B}MPk734e(Mg=+FZ9bh5xFg1!|y-AKI!BU|$|sZSZ|r48wC`3yy* zeFuu+Np>LePE$;heiKh@?0E=ohUY3R7tn<208{N<&wbtq4f>iimf>S@pkpieCiNIW z=a|z+Sy6!gO_=;L4sTh~w*N&G?_PeT77(oC(q!*COR#PNf zIz*FPi&KR6FCvyRt=&&p$C4?E=kYn1yP;$9d|>q1q> z(l3-vMBrDK4;-~eh$B8t0<|KO zSo3~+NAT0loyyu>=Y-qPOniBW17K|chD>^na^3wj?U3Cfuh>bWxf+nJu5E<<_6Wa| zr>Douv<#ABG1|dCq})E3MvOP^#+9n8fAX8Mk(^P}uj(2j>C5GBzK;Hn8cD*R%kax?_^H2p!kZUqlj2y+H%84RmT9W#?E?Gyd!GNSpdzM>`S zAFzq(EZ(oR5J(ZU*0OX$6f&5NRy^|a71rZq@3Q)Rl(_gdE}+(s^D)=Qc!an(P0Q;D z4u{(8BO~`}i`HZe;(ZZrXi?j`5UjLRyR#`xVK8Z1dGF?VnQ>zAF%Dw)T5F6hhG^_sslbFvNQ3w&bSzkd2f zoK@1Ql9K$gx_>C~+&>}ND|Bn$^t}9!kEs-*#j*6d=`66o@(i(w*WVSHzG6EFZmu@z zBb`D|v|H+M^GCOIaK|+tjpC$NGdkTKw5PGdW@b#Wa^}x9ER`juWLa=Ksj@$p9)#k^ z2_CzAFcNnEbl98P_LOWD2bornqd@7D_T8ZfSiS;S88?MxIs#ElDO1q!wrB@W>>XDW z45}|AZTOT`%`hs8SgLrZ9MLzZv8RMbMc#TT_Odr5?xbrz`Mv;V{JJKR z3XHsZzU#?pLue=Q8MuNwXwz5+Av#kX~W6pj2#c-=WpnWXWK4Nk)G`tv37RX=F8ixzJ z4*ivyZsAxyB>b|>$63-$x0}!ZaCAEw5(2u@=5_bGzZ(5LnRBL4d`P5cDd|v4H}NGE z2~hK}j9pGiVKCiod}>CqciI%QpPq6CnqFrujNx^;`199K7SEU zN%dQCeI?mNNT^-7R{Ud64l`D!9V91N-~b=Q+Msl3&q0@%3xMM(O^s&MwJ>EMR6m7dzCyQbd<@?iKX)7 zZ1MXr{FT6G+3UmR+__bGWA8M{O-!xwTGC+WIcQ z*>Ev8e&`Q^CKL+}JVokew)fz+j&i33SKq zg_qs{Ps$xH;_G##4BW))AD0@x1^TD2GKWr|$ivrMuAT-2my2Eq?@?Q;%J8FeQJIit zj0CBnzn9&0#|y|K3XlGdbt(NX+J=Uo8Yk?z5IeRSwtRJ6r(gUb0!I+ZrB>)SEX^#D z;aXIH)M;B;hQ0OKJIwxmsq@s#4|76_4#BbYx%s&9l#ziFe4)4BRx0NaahgwOaB)G4 z3a@SidkuQzWeJMG>CkLCYq8B`bbY~daIeoCh)jtF01%!JyTGvb~HY_o;f1Bi||GjqKf+YS4kxRA5DqfywvLnqr=y%JG zi2WXB!n|VVRr7?_=#(jB-D=dQ!HxSx%q%h)v`L)N2?+TUzYw>m;}WETodrNCU3`k# zli&^Q5SY6_WrUh`kks4-APqGfTm+B@koq27J>FxJY5Jh)@%~UW3G-+FMUxRFoTiM> zZr1~i(l`MW^bd}2;a6B(90We9e0P`u-ORYj1Gh-;A9RL{h-xnYyXT8gImT-p#r3fB@8J7J1C3W`O zZnja$9YG6Qm-aR5N}rPnJ0V9~Jt;eUVv*BZ2U{e2Rz}S7#M|!Z-eXV9#EQ>{Y_F zrt?7Fvepc%W6E>G6>UZO%F3GH;N6<~a$}cfBDPE^BT0yTGys0W%R-xyo~Fk9{czhN zsuQ&srTl1Oj+KIJYwY1V=rzsh!bQH4(QkkMU$eHMvYsOb9AtdcQg$!AA2k7SMDomI zFMhQ-pmNdXLniaKV%TlkcxfkY@X`60u!2OB0l$uSleSY%zIuPOw>|*$b3SK)8g#*l zOEso^)7bkX5w^3$X7|+hBnF@asJt~8uw*}mI#&3BTGY@wj(h@kbnpK+m(Txh_vsM7 zp%Vpat8YfGev{`0Rdpo>qt(KZGm8kySQWcFWf?EK(QTz=6ko^! z2e$<>Qq+4|G_)SbagdcEIj+vbVEcY;_33uD=2W86mql46BV00UMl>!RNc`Id39$T< z`A{3SReOm#1_Fj0cvK*E5iAaaaT-*UXu7tM!~++#VCkIE?JH}VNG#*E$7a?ffejoi zj9Jni6n4_m9I8(oE=}ll0yy!eq~NyYk!>AcJa|F3$k74z>W30CF^EG@vn@N1v&6Jv z*IvkaxI?c@XH@fznHc<$Kgs)Sct960x4O2rtTtne8PBW^tp1BAvK{6Vxi)0~ICsh{ zEUbAPCt}ZDuQPsR0D~(gRMk2CF}Mo~e=6f!AHmCr<0mZ)>?<%{Km&tMv09nOF8 zqp9h6?c16YrJ&Cd;+I)k87bBgQ?rLY20`$mIPhdh5H;^^84za06Z;dWN0FG1N2VuB z)1_8!)xfeQoNHw(>=sKS24k!6aZb|Si12(v`JCeFd;^5 zNFv9Wp|ljw(|YsMS{i8^P$FAXL8_xFqM@ z8Nr00$#+Is_EQc?2u~lD^rg?e?|oE$LE%1dgGY<~b&A^rvsN47F`^ zzhM9X(b6&7x68ct+!q2WvQ#a7`PjR3xw3n>()gydDVSkJlr{?|cX{1rCowI5+;?(u z=IM8(weSjqb0<@g8FW>n`XDgouz)sexA~+y=kRU3fAe1ZqwRQQX?aM9|9qf8=@$nX zSVPnD3m0CW(GvQp3GMm&zCZQyw{_}kfI7bnNbhP&EZz`%irja9S(4STZ%QUg=W`|s zDW`c$$)9AUu3Al`DA&z$2ZU#TxwT3p8zJ&(7g^nV4DbmG8e9tsG^(tqNv{zP^q#}A z&Y-H8&!vekKou8(mwzI0=EsiNYlSK|l~ADSr7dp81oIGC%qcPnS$|O%7#nE~YH7-v? zL>Z9ptGIP>U!bh!jcdpM1$_K}1;X1Qepv^hJiST>6+>Oh%35E^NgRuZr?gAZD?RO# z-gW%Cxi+`f2Mj|9CzcG`rga-fOxIam&4lS{dD$7aDYAmK<-%SXai zLx@ADkvuRyCgh6k<6H!dpLjp_Lz%9yAB#>vmi!j<|!% z;^yj&L-F)_!uZa-Jlwo0ZocW?DR_scaX%g zOKPf~gUPg77KLi*B`0RI#!PjI!sQt86e@*p7Z^2-jp;}?lwaA%2o$%?IB=K1D|Rhr z?Hnn^sA5~n6Os9AL>unczShlDPQ1u_b#etpT}^ z{pO0N@NuWP&G`j0Q=ge(YWFd;5R~7HdB7hFF|{!4>Y8bZZgrLyTg3r9ILqAX?pOQM z(P`$c1WVV>FkxKHF$IS~fu`n~KCe5j^{Jwdl6*({fO#yVAQ;e{g~c?my`*&{B4#D9 z#vRX7QvNbXU{Yx_#!ZJ(dv$zPo%7ZJ$pjzd_DbfRvLqd+%#(VoRzWIIRdkHx?cU&s zvAO0)+055dvx>sk>I^KXhP3ZxxV#TmRZEEYz1R85p7<#@straSU`6{qZa4 z>&&9C$VT+6g=zihf`=PK^9K2t#zfDqQ@jFlGX`VwFfn0MU;i`!s^W+!DMTffa0skX zw|nq`Q{BrXr%nSTsu<7n!dWZ=L-6~o35}DeAAil8}SGb zwjMx4*74%S`8#ujG7J2*jacf`LfmPYr?6mI(d#rb^DfVi52G0CM`=~6XlVenO=4_P z3l!|45my_gMt&u6P#bq)*(pigD?G80s2Uq%Z|~2I5QrR8|5M^rCCUKyS-0x@BgU`U zs zsHBvF*LMgkfli?;TJ8C#cjjg?M(<%)aW5Fbya78?j2JJga{7AUvm&=Con1PNmcKtC zUfm2Z$r?2_ryXQr9ywOt>Sd{}oo%rlH6K$|V{mCG{`ijQhnCdx{QVCH)NG(8V1#H% z8otGarRrQE4g%Cg)_vD4uLPkv$TKK91Cuf0DKr1O4m*^TIB6b=j+s>upXlr*Fm38PZOV4IYRwW;m z7w@Z=NUZb>_ySPx((7D~-jrxQ8u?-sgL(EazqWSXCbGRo12Bz&VFm(XBEukS#xAd? zCTy)p(*}ixaL!)w@?G{Rr7a{%hMbZO{(E_w3~^ST_Kv=Bh!y?bBmmxU06Ot7cGaJ| zmDio<1zuqdn{LtkO@B?vca!n)a};M`tC|j{$F8tTQJUcknk+`$g|~dZY@qtX?cy_? z$gTs0u>(uZhH0eiRj~dJ0$yH%=S!d3X5Jn0oTvPl#{P60<`7Kr0@=zIFfo&ESl-Si zh6cKyr)#{sM8m7x_&OQMF{pKbLn}!q?D*eaSUdGhkq&U zu9nq?w+vqOHOMvuby9(Nk!>vMlS?y7Ra$cHn;cr;%{&PEt-% z+C59w@o^#|;tt|HYbj?9h2h`=YfEG12s`=`Gh@@ie)oLqL>`)g5%KOg_6UydiB25H z+@s0;Yc&oHp9hu6IFAQT5NK*qnEB~h#aibPjkke3bMN7Nx9lGl^Sd6e)}S z@LjL@%D8?Ms3`>|JK9J-`OTnd9Q-J;*DKWmPq*i_T~LzKWAQ6f|AKY#b>MW0hlJT< zEmq0(aY`p{r&KDH<_^lTj@bt0Mc&H+lBd9q-VLxIXDKUsOb1X|Tv47pB+5IS7IWe4 zmC$?GvOtg*rD+fJO2UP56Jyo&wG)yoCS|r%B3$rBof-xlw$4G?5TQLTD#v+%7?5=% zEnFUS)zw8H=PW_mvpOxGd+zgKOeTP}?~Y*~ChEuXzDdTXEEjj90=u^3$sr`EFUIb* z9avU+<^c2k^4_UB22@U5xy}vP2b9L5Jp>o^ounb?2J%z4*>jRaiiCL>@v7*ghGQkX zrnC^TT}?j>PduBd#~v^k0=MoF|K_PavyH(WfmDeDIfJ6BK|c;bB!(gEKVF(4fX#?) zkGxi3gomR!X&82eewuc62CFWbUe~Os)e`#=!8^mXm|EG9Pb0#i?JIqS4ok`~re?uR zcMZ&4vhT8cEbt~C_L7E*0fM;ZR1?^Zb#huhyJhD)l_ zh%F)0vmlIyAD`o@&cE62d`g1Eo}bg2<#>>=>D@(8IsYcV6~h1JebWz_`Mrb&sd9uM zTV9jQ?0s~qg9Z?OC$zXx`xUU_>u?#cECr&=O1{3v0<6Q{qUw98kfB;$8?=CctWaA% zag{NZX%bNHV@;SR^&w?d_Y4+N2`P6-HZUvX!Vu+tSU@ zu^$eLq|@8;LaHQ(t;I|C7jOVySv;`e=52attA!3nuhmvz;$itVn?CEMYncElT^gg3 z(ygztxrGOji-xz6lehc|z&?6b5Ih~c$Hw8j zZ8A9EOMpSnCCEOoW>ToTXLX_6>fS-kk0YX9-@>LlZ~4=zYZwf-P|H?N(Q4RQ?3HPV zx)_iYaJ-zSW|zd)j{%Kj*LzP3`&v3jNVjkS<^=|?+w6Rv#o?_fJ#BtDZNwxMjJo*l zW##gF;l}lzy04DQu4X$cR5sk-tr6R06+qzjt#1|0ewggj6}FyuuyO5dJ16BIFL>h4 z-*J^dF@@TP7f&@&(i=~8?VZ{hh+00HQ>gLP_WI=PO)=d9-e^^}O^@ybIUBQ>sfXBn z?=!!gEzZ`e^-ZJnMrAr{DF@t6=Mb0|5}=7(;}bh8VE19h^*Q_CR&7?%d9Xs*1Y<5# zH*QG7#s(F+9M@;2` zwGiSb0{}V#>|E8zVeRJd;}m_ie9`Z z?{lz!z|%Zn`~Vs!zrUzz^J2r|=T`xC@??3)E>t#(Yh;sb+!qbiTN3f8e1-pXukVrX z`ih|(>Og@Mq)`22D3wf9@Ig+&XYItql%MiHKDOd^f+EhkrBXB^zGvh&L0^UT+p8J! ze^5e_9||gTJX{E{lQ2wfwOA4IAEEa zG#WF%e>>BEX}1tr{l((Bo<}sw{&o@q7Chu2^!`-DYLZ>GOiAzi#@nw43-X!eI|ZsC zO}hn8G-sI+g>Y0SS;e;(Q_p^G;7j?g9Kg;8+;Y&f=)wK|2FZlYie9(8RIMu1I|~8a zM>)k4cR4}RGj1s<*Ts?G!-t1lw#up`F|D9krZ7T0q7CU?fzHxjqt;hip--FcW(yd{ zNy{sY619Adnhhg~*-t=ArJzste+shXRRBkXDr(Q6F>#8gqb3%5K`9ew5iztzCM>sT z4$pG)@lDapeVlO#aMx+tXZMBN|7-r9&yQs?77rPw&5$+K6)_Y;3{o^yW7K?)A~uuX z$VeC~ih5!X;84GUcQ}+Pb$JB+y;%G8Xt#1 zSsWZ@<-oQJM59u1`R_iLVofJoCKqY|=5$8G6DsMzNwkMTGYqGLym3S-k8HY#S}hy+ z^zhW|Eb#@z^Q~0bYltqMg?*<5!qGSoM4KJE+I=1sb zfKls4EBk}HQsAY?BYze(hxb`ag!gYWZoVYQMZ|&17$d}a$^4^t=dI26?JwbPHNTB- zoa@CiWE4-I%UDDh+ZyZeSI@L?&S)6ZIr5Tt6laT3r=fm3L{@h2l5lTU)J`yFPnw%D zZ_}eBAGw%i(&iI)r)Eb-!kBnxS=IOg5yOotM`;+@-jIy+ z9aJ$1kyFe-PW30_;8r?~K^X?~J&~6s)gBQ>&lqY_Vkpdj?0-6Uv;AESK^t*WnYS+o z`F+~gfF*H#&o!uB807QY<Hh;)Hd6}`;&WTo*UR9D45;EsuJIm~?bUIs< z=cvWu6sfrxI?U5`vVT{IKQ<*3(oliis`)T9(liN@zk1xZN~5r(R^jPGJGT|HBBNG` zXvq~~oym=Qq{r=k_ExP)q7ZUe=$2Y-CN1+RQ@k9^azFPlwrbh!z3 z6NN*u5HTYC4mDK;nziW6)L+&bl<_m_QBB&*`$c$GP+(GJFZ=c?)uMnNRpHBM;H+JMA>wd z-}yv~Pc-XR0UP=W+k&2UzmmgJ0tHqf+ZA4|@0SsZ*LLn1(OYpeUt8Y5I*aK#_@HPg?p&9VFbrhjRmoo| z29G&S$ z+aR0jX{v(J*I-F$O`N2x95VpGUn3Sg-QV2=C@5|1n${8uq)&l(?0Bsex&FMeo#&F> z&1Hk_JY8uGp$-`(#Bntb)+{XtRoY>d#acSH=4Q6~)>O2}?A%435c1U~h(@XYOc+RfAW+{16)acmqdbpu^5;d`FlX(MN9{&1ZTejUZ z{xjI6*4ruZ;D**8g@FNE*3ipHo#~;mZyDj!W*~BNOlbz^do|H6RW{XYyEj%ePi#}& z+?MN5I5^gH%3>0o)WA!Ho#?ZcHrP3?aMm>Rd)wsiE~umy;-s+J7<|p|JN2on1(J3Z zD#SHB{PW`Lb?LAYp7#RwX=~!L%p|ZN+w%C^ovpms5cVhYD1iSgbzT47kDQPVwEG*~U!ET~B`KraUi0Ug4B~R?N zBS~?fH=ZzrORQ@uMFu-76mEVriIo%hA^cWO$w+GPK@KBpqAVC>F(g0^poH}js2>Nb z>GD7k;n>ulnVN%hh0sO+vX|vP?5KN^QuGXDdBcYH)xOFl0#*GgK_L$xDmO12)oJiU z;=Bfef>d~OjZBTQ8hjS{`qQEdz-R0>Yri@XK|@c+{HN=t5-N79^=o_vwPX>Nk3-*S zs=#k7rSx-W$Fpy+1?Xi3pNTC_Pa*lt+F_X|I)+>(7tH#OrXGUIPmcEoHFGLHijS=| zR=x}U^W@>){|$)v&|a@*eS3n$V}xdVvpFo}`q*(}&syE4H=|Zy%DcQg2#RQcDcU+u zCxfAxkR)gvZ37dNL>~#K zY<&)x-5gop?cP`1ejn#rl>5PMG28Kobw;WB`b=`>OcS?S3{OE0Qzzw4O|+fX3Q?f=NS5 zai3Zish@s!EEn#<(tse~=oII>=whdL&RuwI?3gwL@5Zu4Ez9_j7cgwKB(QRTe+5ivJ|w(ThHo~)}nhCXpkIjvbPs= zz(HHf=vrE-v7JnTN_+D33~p2A%q(C^U$j2=0Uz+kAg>eQ8BlVMlM8v$E6HT80W269 zOKCLF2vu0lj~MOyB;J4)DX{miMLcRd>r%{BEA4ifAK9oo>Q7F-p>}85K{a&R4YEJA ztyqf{wlv~+2!^jbT(rMxiUS(QJ9C-&P;k62Dq8XnmRQ>JSjqH8l!>lz`}@LuLp&L}V2PWU`OFq?I>z;2Rb{Og0=^4tvFUf*HT zj_-DOc2^_y=qMY*t;{-{j6JBia@VQYdLzp!_F=s|2{Tt^I(YjjF2qer{zV$=&9(OX zBMt_HMXnqU)$%ScA1BK`~FDoWGSUA+6-N5rK)DQ z>&v7y)39P>P9Nb&>tb&@l$@q-duMMB%RCX&N3^b20OsIKn%DD%RG&@U{Aejro)cqu zDT``tz8-P^qieEc%>MbY{-hN9>qy_{*QSWWASTbN(E|RWNjb&d_UL-(o|?#_NIq!u zvA~I!EwZhO=B#P0jmaSIeU+I}>kGS*WE~~8WKDdS##@pEU(nkH(B@|4g_42jI=PxP z3zSiNKD6z~%WgYArGvKF4Lq6N0Wg^w?lBi`s;8wd|BocJ>MQ17ddK!|qyBktQjqd%c$l{;M3)d{h${)L5h-x{7hGG^JI;+L_h{$ywmsp5|eC^zm?7ewRc1RWk zw11gp5ibdKlm2mU*zJ;?!D8S#%a7;On(}bUsw$f@y)3KFcyi#m$10GGua(sv1=o^^ z%74M6P(85zbiG>6)TZTU#b&cfr#1JQ9={rC%L2PC!4>~*NLYf(dl+h0ef~+q$mU~@ zhBq#G{jaB*_QIt>J2iJ&pQB?ey}{*Eb?O)Xs~q`s9p8Q>wc1CZ7X`t$hhq8pu+lB* zV@g(d;)SPz3AYfhsUJrEow%ri3ukI_bEFv@#;-=^kI&=Ezqn9r*wM}%^ zM&vUMG^PQDW^Aq-AmE+c0f#K4C%-bxezID%{o~?1!5`M2<44@mWuR6Ze;37%xE7Zu zS@!p8_q9u*4!xou%8%X&zH#SNyDSOF4c63l0`ec$`N zFR3JZWGuI`)EpLf8Bb`U-j)9BrYdW)Gc7^~_s7;$xdYGV=HC2@% zQ*P}~QqeC?z2b<4U7-cSRu1tG4VG|)*lkt+&*(SzFWwjOTy>~_O+8Y721x)>EB!ju z_;sPMY`($jKi9)Pj-L#UwebG4TK0dd?>YTn)&CbeC%z$q=Qd1^O6ZwNRYET%jtTiP z^)@Vzkg5SfGIpq}NQ?pcC+SnM%jXopYZkxf+q}{0Wc+-3M+K<#i54qU)N{hoIt6K= z^m}T}iS>Y#_f$x!IGxfv1uY=l{yd}l*U$IgW^98kf^_sWi3!)cffJc?J-ZhIGW$jM zIo?x}>r(n+brKk4K5~u!%JDD&e#PYdjgN%-Z!eolRbt(ruNMOJqwlV@I~bH+p=1uR zZkt)#meR3q%Lvl$#r5X;-5!?cW77|Vk!zJ2v%(&1Mn_$cf-Mi@^pKWtM5d1+YLG)> zs(T+wO6MBT%R+L<9cn5a?VjWTgj|{yqj&-E^v*(Ezhc zb8|22fbLxRg*md|?yg5^(}>s=nAK;pYT=bX>;SO_FR@zbA9dQ?0V!%xNc(+C_EKKW zi4$*DEn%%RvJFXYois7}X6ajA9qpjd0=u86qTiU3_c~@Q+3Uj)jesbA9n{FI4y_-> z(HoMFo*d*OJw5cRG2onmiV}es<#h3+gw(U+L*xW=DXYIJ3<@aLkArxvY4|D|PE&U^ zznUKDF+*!rwEGgZo5x9L?emiot2}Tb;pp|E2b@vDH6vdalM4$kFJDON91*{%v-8=L z&ao;e%kf=b^UJQEDdve8k;s)}r|2{@;0_vuX_1k=x+${s-Ofn-#mM|fNlD6Y`4Rd$ z;^}ednv_v)^Stn7n`BD*cD;w}h_og#NgT@02K1^%DmI(GKI*3rzVrTgV21V$yCQB; z<>M;)dc1UcKi~1f*YkY8CmZRuCpwk6*tnYhXHibHG*O*}7WmhbXf)^lq`aVc^Iq*c z4N3STk3VZt`PJ2s%$rNG3h*6_Q@KpLN3Zyw9iT3_Xd=K_KKDO0KqdbTafkx7#3x8g z=QRLtf&JHYLPKwz|3z!=Cx7LoyKAujs}cWp9aguU7neiWKX%|4)tYv?XPh zvh18KnWQmv6ckM^jE^L@KU@LSw>s1xn{MxdfYfFkFLTie6Ixo2YeWzm%^R+3UgtwT z&5jSk9{$hIQRJqk4OUZcZuT3b`Cj@=*R6VaX;A5|lf8qdPbQoDsWkuxS^cb-_M_Ut zy~G0lBlMNz!cVx?-DZhvKtkr>I(8nqShm+4zL|78SJ(+Bb$y*}UZrB6-_){j_h#(H zPVxl5wkY2Qf12_Jvr?FykJ!3H&KAE{(-taK-OwVTB~xjNuR^;*v^DcnNrGZaBt8gb zmq!X_yHVz9ie)MTl8>enW%}*E;+dXnG)K9n{W#QHsUG>_%E(S45qL?95etm*k?<_+ z;6zqN3PfZwuM}QUrP-ofqO!gG`L6-Y{pr1Kg@LVxcyc@ydJ|i8`BcCus%OjDNl8X~3tC-k zKb28NVqZa8|@CsC(qg?)&Vei zoksHuOF~5M5hh~S|6G$N=;4w{raudlJDnOMZb zR`y>_!A!pDHm&BpR2N36%Sb-gxJ!h8u zX~e=p)Az5?C!YvzIE{|xFF0pZ)L`Y-N3^y&aH$xW6-XuS?SMF90$X!hRB!^op53yT z4=x_ZAI3elcD-MHv&(s>JFc&#pkoNsfG4>Qg?nh%2rR382|H3vpT!Y14?aH3%3}#s zC%fe=9k9|E(RAs%U5*V|bq;yFxVN}RHy2fZ0qyvzCV-?1OKM?e`03V@)CWy_<@xPO ze8)pzjW|AEp@7?RWKx`{F&^)fn8PbU^eM(rH5K)_iu(LzG%mDT8RZ#_tQR}iw^ePH zLKW!rT4d$sRqM@)tFpe)3ZrwV(on6%?bmP= z{}NrHXXIV6M8%MM@*BgJj!kp;1H01t1f@=ImOGE%%;! zMx|oSfooWRL{f$le=r>4k&R9^uAcRD|7x1`41MFR=52<~q$%moi;UkR$z)uzFdx%_ zkwUN65O+4JI%mZYgzVtx+mi9t{E2+b2A?zwjv`+{OE0AEU;4Fnq_}AHall4;9z38) z%~PmpSAy1Hwpc^bsCAH9lF%A)&l!Y~sF=`A4Fbqgd`yj9%Fj=nmLB*ZXw27;MZ_VAszuq<)f*#*+mzi56aheRp?L$b}uRxCo)8c ztoGQvAtK`5G(Gy|~%4c6mqXX!e zn*8T1O5qf97PC(_UfEdrTIM2T_Ni^E?hpcEr=068t9G{{G%8HD_dWU^T9)43E2%<{ zi=>{ZM!+w#GB4FvW;!4FN}aFSqQ&7Q`Xc!i2}_dDijOq-IFCttQ0{xqZA)#zt>U+4 z4U;hLz9v=PGO%Fpa`X$E2f1m~0=^#(qHk=F;X+x?@Qkv*yV*xtf5N0xn-w*pkp`P; zR9{E*X!8*Gay*LrHmPnR`tz#DSj$#BktU;yLZrJXWv@!ZMHIOVt$zDh5*( zl1^)T=o(r=#^*cpS6C-{(u9YkieRi=Re0Sf9Mf9_?hUn{ogh~R+sVpj~4Ad_F-lR$m1Lc@ikX>iB*w`8)9G5>hG$ z=m};A9}G5RJm_KCOxl1WsbNar%xdln@B0`GpSjnOaR~H1h^0{1_)%)|a2IWuBT8=< zgb>ms5S^N+TXc;EyC_SybT`6+dCMEnvphm>Qg;wf>lTF-+M2YaI-|;XI119(NRI1n zyEek+Nhrkm>7qjTf9B;dsSGII0M0yy##VVZ-Vx{q3B0mSO_}vlFQwAydeRLF0Wo6n zS)Xqj>i|Kd_IW>jMak8K5VO$+)Fl@m=F3Eleea^Rus-LTKL#F*SYZMKh#UR2=ViPW z8$9zv@5rn5E^CZ*J^6$_PNd)QeO}6XEFi9o85XIlhg;|JcfRaAuPcuHT9mH=@~nPJ z-?M)vMdevsP!O`ELz1mR-!@5|6b7I~LLRVKPvk+Q^u5Mt_=k+urJ7Pgdd?bH4Axbu z18%T-Et2gG0DZ)G+K&&Fzn&=~CEc;2YBbrjGpsTE*>adgvCQ>+*Ra*8ZlgW-;*g@0 z5#frOv{C)>6j!6e`X~&OGEgbkWH3;=a%YiDwtfb85WrL&)`T-ANke|LBF@Six4wnN$^Zk~8zlh@oI zak+TGMVAduZI4F3jQh=rE>f*@ft-!y&7}6C@=&a$IP96)+wVSRyOl7a{kgnkxZd9_ zNq@U5k)ns@(SCG4f5JWeUJq%vm)AB-{%+jsit(|*NXJJZ)D21@t9Oh>0U$A*NBTeg z@@kb#9SR+E1$!Nz@VElSK+%vJ13sDr{B5Az+fUdCO*)U|4Mwnxk@95?Xk)7n;U9+V zcC0tQhRAQgF5!NJHmo^zj9pWVfgl!!c(>!Ro0PgbRF6{>JDA14&K*f?m)Gw>Pi}N( zwS<Gh4RaGE#bhtOgn7#;<72w|rjrz~nsZ`OeKOoltCZ|gzXSi89R&ZDI`#1l6kcJ6E zG_^Vf4UO!7mHJXk^i;r~5F6n3`(n;1j3J-G*4yE9&b{45uq$*8SgO6GI@yH6iC!>p zI8IhcdWcyXZ$7`mHCxn=#mflRTvXV{{qq=iB-!+~k6Nh>)XJBI?1|X^;+yb7o66zB zi(AcY*qw4)#4J($gK(N|WKXxak^|Uekr+kTb|8j(4Y*e*daT!NzgmH01%HD0YT$if*--!{r_0XN6S!CSy1xui%8_KbA6+FaOVP_TRez)kGElhteCRi~(dfyd(Zu_g(liSzEK&@5w{!k5TZ_ zw$(B9E3wd@G}yn>i&2jmXYOWR1GHs!Y~u78|Far8`CuQ5*X^bT@5j-_Q)LpE-JLaZ8e;V~jCD2O)4 zE_a5LZPbQ^*0_<5ICYyVC7k!!uKv@hPgT^ar`d&t+vg(BMZB`%Q$0yr=I>5|#FZmX zDPlwtg}k5m-u|FxXyZsjM6?}lvtFF#x}YlfE{`&86iM;DD)wTcTbF_nTJzjw$mHR- zpc<5xo$WnX{uZ^ew4xFDZqh$E-Y;6CxZKxR`omr&w7}IHvHj-raGTg`b93Pntnx!mLtv`qDa# zX58@nx%CofylrvEO6U+dq`*wxXvis!9`;jkvFDxxs#1cX0xusl}vW_FxfUM zwe4P2?G|OZg|2wd9gAY$t4qQRaeZ0q^KqO#a;9mSH8^V}bUD_e#;QMyWZztGwQDq5 zuLHp$KX@yv9m7|NS~?iEv9Uc5$-WZw zmmSHjP9$G$&yNkoy)uB~qouC;Fozmj26*^qIlt;zM>2HnHo=g4IQ^CT+AT7>jm?VB zYLig|3sqv2_3cx*qMO_tu!RCh9&AAkHx~?11F%l<4Lv_V+@i% z_tu{&O{Op_KWuFbnnV`bpNcmmJuK7F#k#ZXI120JUKsUpv9iyiXH;=8XWeoJ^b;|& zJwQ*qJcANUbsTkZIwt%YAoYEV_@suuT}gX7Sk+cGpBx7og$m26^l5%X0z%?m>P3LX z!*_y^W9a6gsPmZs73y3!*f&j6>O65o_KdvHan)YodUkwOY~}a0%}mMZcgL~@!#p8*GSNaYeNkY}w9siW8uqJzN>-1ZVM^Jk5pgX+%~Nu)))is%Mh|#8F?oxh-#$ zz>9*2QV`7Cw)e>RtA|`=$!zG@tDq9S?oSa*#pmk@2@T%i_5yTf;M|RuJ|?4Esr4~2 zKy-a+|ICSjvY&?cQ?BvVUgThwZK|8Y+C^qdf=}%F&X4KhC~jj(2O(XYv{-nwy+!>X zE;WF-JiwW{Sn$LbsWHc8YkM$u2HJg&s37uI&r<8<*a&=DnhUWrUJOCqIO_dTOpF-4umxu7>N7pbCl*^OpVE2|iEn{KR zK7eC4awbllb2a#`xQ za<){;-1`DU@X@1WoWWsIx~Zr8jY&%itWiZ@@7YL1dP_Gqj<_xBpMCM0gm~EdBx@o)6Kxy@6Z%Ut42~=+IUC zN0uN(Myn($I2v>{(%ufgT+rU!EYJbdC#6DFquxIopVU{yYbncoE}s98lRFpU1KILs zz{wU|aRkjfQ(JR%N;s7&=a-{}$558A(iHPvl?frTn%?uRAmMvT4Wy}g$8)On?YVc_ zT-*^7^gNT3re?e7DkW225py9b4okAwT3Hj|^c2zo+zsqGqhz>sU?&oI7Mv-sCFp9~ zE!6qsKUPWmFX)#2;Sl~u8~q==d4U;6>BYPvDSce3s&-iCPQ#kE%~m=nHd4$|BG70r zTc>qY3@PGSLe+1osru2PL3@12(81z_ieD#ec4{ruC@a47Bw((yvGOhZ3U^s3*uD(A zlUD*vN|LdYEK;6X{D`-e@O>l!YOpb^+}?{M@n&t3xx7uV__$0UG(1;LedbBC%X=4e zwX2S%99~E2Yba65llAT&mLWM%mui(qQCjXJqBCduq3ks}O-bwz zB6kB+xC!^|dZVRwxuFHsBicHlEV(dRoK6_$vYio-Oty^{^#t@Yem;U+;Cp@2k3vx= ztC8xN3e(sfl!njIdwx-^$ro7qTuPeB7ao$SamKS$4mBqZQtI81EDOct4jcQpgG2@k zA5k&dw1pA@vR!9Pj{Xr+$0|X@#DOOONy)`6@|4}41VK$3;OYW2l^A#eCjf_Uo=`W4 z*DU~#W7HW=4=iGZ+oyJW*x*u6YlqP)>Bol>Ja!uEI5Fsi=r$&{`g_pkrT26$2{$*2 zxE@tbEtZDdS}hH+*4X=Q@1jI5W`y1WGG4&Y;I-^B9x1%hq8Un3^$MZ}8>4H$ZxHsd zXwMDT>QvD~yEX~m$I6T1!IpT!Uqq0)npA2I&d&A&49b-uCa3W=_~$ zU(F5O_7K~f$zUmt$Dmepdf_<@==v(rMmM3KQ|GTpB?m%fAmA|E0L5Fp5L_mfCC94M z?Wc;0Jr7?7msgcX!5cL0 zI)2D4JWj7ZlJ?3C%Ym|%+Nt71L>Eh?=eWYra(9OZ`o!H;i9sK8?bXSmGA#>$1e9&u zpK@VJ${QUF>D-`N#IWiNaztt+Z0Rh%HW~&7W^>Ihw_>r&Er?~V(lPh#Mqg50pL2^r zp7dDIhf*hzvk=P0(f;(Ry+scaeT6=}=QPQ$gqr4X_7#c3Yx_BrgjT5dqZ&mmqo8q7 zgADXW0{9oDInOr3NfjK!-yZl<5!0{Jdn+&7nM{(1-CVyj5E=Qo>QGY8z;IJkenkl< zowBxjLyUpcK<{D1rq^DV&nv>D)f!c7yz!17+A2AE}h{Oz?PkIr$qec;KjChtcb2?CjcyEfg z95{9mY{2FdsDjMfl7P4x2d0&t&zWac8>h-?s?+A#o3uq?>E?$~Y%7>yLI6`+c}k+} zOtn;l#GAF_iPP@y>8k2Lb-SshccL7@13f+c#F~`Ia+9?n)X1|?tPaE4CtDtW*3Y^M z6mX(xi_*NCAY}wUBl~-TXah;M3!L_o;PAxwltw8YNworp*Y-67``+Mc)W?uWjYSpr zjld~oNBpHN%9sY{%3(rNzTXNqOok_wUpa1&IJArPE-GITF-YBrJ)-9Vv(1zbt*0iE zHacraJXXFSyDQ(xRer&v<20Q?4U#!7gVi)w2OjCSkHX6EI_!=XZ?Q$o7UjYk2xPTL+-^7SC~@PY z_}|veH~N^90h(6Qn9jy4i7gib1+2!9iCI4wC_Hq~7^I&3_{K>gKXGnh-WaflBq=KV`0!&g+rzEpw-sW|dtq-sprmqF zsxG%Hk}_@&yaGqgq^tBfY>mjOtJ_$rFMS>oHs$Vz{7CGc3Jd*)5CT);psJd{5R6=% z)g$rw4WDtUyRU}Xsugs#6m8WG9l^`UJ99tbE162>Z9E{BuhU&{%oJ;Y@s|AK)-*Uj zv3vOfBnL=7Pj&hXt)zFJkM6`2R0)5>2b|rQB5qVRE@sAu^mk>Oc`27xD``E)7}UP69TwmU*I;1h{d3L&x#lCYu&2P^gSaZFPXxQQW~XvDn(MViy%RF+MZ#5H(NQ<1VM zhXoJu`iV;ILtW>0%`H*T2LPyao(s1WE=AIEc0p7Sz^EtX#*MCy5Gnch-x0csT9gu= zZWk5MRgfG7&o=r^9g=)ln!f-+pW7jeCv_3v!r`7xo`1U z&8>Z1`w&VY1ir#8=|E$)BmtFWjoDKYYasM}f@K99WEC41BchTv*>}hw>yzR#x&mis zV&gh~jMSus-xm=f>;7y`zM8#UsN2+6C9nN~;`TpyAVbSA+>lL3CaL^D@Unj6$vYcX z6ff(ms;e-%oH%I;NN1E^Ys3VA6heZj znY_z1qj_lj4ZFSntLA^`jlWsIv>N{GDZ7jvUsveICI!2S4*lV!k0Ihk4I;v$V^UMj zAw2V;R`UXC8uU`sYse7{VySA_uS4^S!GGk1-d4_IbQj&SK^yu4R@Vr65*qruG4a2y zLGw>$Qqll-yzQ^p^sON@G$1sT_-{xP!@R#)OSzHZE{ipx5k5s zRtS0nZ+*o(Ql*F*v3bE++6|yPPS-0L4PnMy6&~}121*W8nh5=q$;YzXh9p6!P&(cR zq{!O<0x3hPZe4iFq5MkoLSU?HAJyS$--ezp^2tL5e|}>izSX!iul<+y1W`}c{l$r@ z-ddh+qSkZk;_Wgj7>->IoSMm&Yhpkx_L!f|8q?FuvjHa#tXaxST7Y>AueU4t2gn&c zcrC_)q*D7_y?FW#U(Au9ewxU~h<145k042NVmAe6tK=3QV)X?s$3!1IxsCM7F`e;t z9Zl{e;|C(^6x^y(G{R*ZANFwWX|OO6`{{WD83pscsS8Uk2%}=R@U10e_lKBQCF0j8 zMLvd8P`nC~|CcN*{|VUiKjb|7KR@wY{lxgf{wk~ZS0>P$@vV2$?`c2#597n%tlrcR z^BoH?ci!xJZNQ$`A9$jtT;lR(Y*LHq_xLC>zM7hhXXoT9EDO#Pj@~<@JHW*X0Q`0M zS8x0^82&HMgqE>Q3E6T|Zv-nqK=08V9eWUM91aR`;@%5^-vu4!#03- zFFRiE!f{G1K}W5LdY+1irUqiljddA>F?PPOXZ z^1`M1uz%0CpM;;vHDGLx-~wK-QoCC3?=y$>gsuN*J(H=BS%?mKVGU8}YCTWQx(39K zUASLhX?+VD$bT?uBAt3=n)k6z%WpFK{uchN%p@(S(TE80+__enzny+P$5`TUK0h!M z)r^|1t5D9Red9#SQ83_D)|~0N+h~Xm(dYAbJsomBeFr%=ovDtjSTRPTSkrzj^8dEl zH} zUmNPHir4nH+>0B=WoDVsFr)qC)h{!4;QI}7D(+>`BLOY(FnZ2z_b!buWZeNi zD!kBM_OCdu-HKFe{9h;|HR)Wv%XH_vZEL?^lqy2P^UJ2Yrax}34P<%_na2n@cgw-z z!IR_FXAEu+eewEI@)sd^qrrKf)#fAEk*N#|U1jDFi8KixvL~o97yp87hpJ=pn#vqm zoGk;|GvV2p75?VDws#Udk}jG+reUIfU86!fE>5cyo}&?E%L4ah5wY@t2X9w4n>*k> z+J{A@!GuP5rZzJhm4mzbiAwo zG(6lp)v!jgriiO;@GzjA#UpFuCNYp4CsPaX)vQ0eR8UKQq?hC%x6E!v3#Vx9k>BCF zG{;3v2pa)=#^}>tZKFaPu3}sB2#fJ(`}d7diP;I^fW-`^2qQ5Mp5*R2N5$f!SD(%> zNnCpqJ5B=J+&KwWW6$|VmF{YV?7MfzY`kyI5~9Fpc$)8=06$OLm(`A%*rsY3OUt7Q z%mWlblL}2;+?|<*Wq6U~uFkF8yKZKzAa?-Q!3MfQ4>lsEY(ioLYZ5N>kot)z3Y9ZRd!n!8^k z0vplQWmLD1bK|qrgbc#;N}7jmp`gC?{-V|1k>_1OIs zJk}f`H@%dblW!qdL;rw(taQuO>gGg!>6C|MOGMf{W7gs-Pm`qhdS746=84IPxE^=z zpiqU1%VsJg{)TXVZ4#-g2MJ>x?&{*|;tB+kp=DF8&|3w4HmN0Uke8BTRMSnT-IGhv zV_3b7Rvy+8hM_ALvHqrVp7b@OiB$)J&NOtY>Jo-<-2pfk z%G=(hZ7>_dsy~9bs`a1s@eHRJO-gkk#Pb~@?>t9J&L}R^o2*;2IE!`PP1)CcSkO9C zwDh_ht;m(Gdku(4QWspacOdX|U&aA<6E5OFr#rx!1%=|xzH|*zT6UjmG2G@we%8y=5)#3*7nt1`rPpP80I(qAK{sW?=3W{hlt9Bu4W)v$;iuX z-?R1P$j=6J9v~$(w!vDpHI6>+YXcH;&nf(FfXh+ z?V@V0AgmMF^t|C#JG8yqeRa_rC8Y|k-8yH`dF8;y@iw+sC|hLc{^$=gv!nI>j*1Pe zT&i`*-50}+N_r9gN-WK!lFdAw#Bwm z4LOPyr0gEfUc!_Vzbs-QcsCUVVfFCZG4W+P88(B8X|AZzF~M!-j;rFofjJ1ShH$PhY}E+r%wM^ zMO{5X&9%db`Bng~nPt6O?fMK;U@03hS&sVXOx7;Oew%QY>v0mCtM7+MIxgRsNrs_f ze%zQXrwtuC&(mB$jbZB!+p(hN^kTh_Uo6f2BXE8h+GqG}Ge1>sP5CP2^ zhA|D)ykz*iD5nYZ@i>2CAas&30&UcqFj^@qO@Mh8ZREm@=)j37{$gmEoyP|_>0-+_ zh$YC`jr52<7BM)!PYeCr$!I8%%!El8s}j!pX#`pqp28W2qlm_5V$)fD+`vV92(bx5 zW355GAsX@N`x_aP4xT*%-UhpLEauL-k*7Yk(F>na1Seafm!ds*6SyGTxuw`JkKJ+w zT}hAL<;ci%5aS=TCpeQ<2cy5_zB7e1sI+-rrI$_Iw>Y(n?x@ z133os&YH#;(9?Ufd%J`sc&D1cuvDuF;tvh=b=cQ-UES*&GnRKLwuij-IOHdi4^8}w z_}+=c+OM^w6s1gSndTpe^x48A7#%F~(~nQL!=us$1nMhk#2V8hMx#F2-1*#8IXd$; z|6J9uw87hZrb%0sovLDHPyx#ln2#t?<;CdrN)Fh>DKLvIFHhX)aE6i^?}>6?9OHhQ z&_ZoyyGg?6?5q)@Eu>BT^GM0+eaG&XDBN?BUI|4ug`V};f+DKNMhlu^X}a&)^(8Gd zh_PT#k*1g5(jPrpbn?n!w#m@xj?oKQyGFfd z`T}7Gbx7bnBC~kz4?!x)a$y2OpT9YH0?FLd`~4cRJ7Tmy-w81zZ)l~)5B7uRJnNed$We23sP(|iM#k_0yPcmQ;$4fc(ay&PBj?{b8v}N)0q7II)s{Zb&TO9- zR+gd(bpBM#NM@tpblu!yq4?*w$6G-1hu>mbM0y4yV_c@`CvjqTY)&X*8l6V4sITe9 z)@wj2^DxcONqmLAGBevN2o7xswY-GNt_{v4R*S087w^~xLT|OZ%b5Qe;C~5dHtjBG zQt)ayIIcSN^0A}a%`PvgS?Qhn7m;#&V=_%O{}iEZK4Z%3YtDRPn0IqcaVyX=0=Ofk zG$9cW#==;aZcw$vOr%#>Js=yt`rWo=cz`!O2&HO?r3oMMe4rfBW!-e!M8OGb?5h03 z_GNg0OG9D2Md4X;274Xar03I)*Qs|dZ^r$Hsf;S)tL$GF|J7mtod!wQ%G5Ni(IODt z2K)FA)#Xf*@pl9Y|u~k&3Ez@ z%9v&7V}>5oL4jD*MM8!B{Pp-SX~1SKVJiOn5}}Z0XM3wFB)E)C(8)i*V19%g! z@-+u)=l`y`ls_Gj7i^3EBfhEEfbNcKK#6Y(%>}_o=Fyh_w#7xiYsJ*&Yb>`rv76vS z2>86bvUTEjuRoXm-?g9U>u#eo%8BF^Q`y1p)z+Lq+Rs)?xS-_nJ!#eOF#Np$_r%&u z+2<)A-+?=#T|F~@KCC$x?dQzUOAv0=dRKdh{XmlrQmDxJ%%?>|@C%In?7QKE2^*Qv z=dYFDi2UI0fep#O>u^H+b@W$H{51;xSMy;8vhuPAEPFxKDgS@&jQfA`;rg#DWxk&N E9~`61!vFvP literal 61345 zcmeFZ1za3YwRXYKEI{DrfCxV!#! z{&&JdzE`t<!9R-t82|=4`d|A)zA33^YanItdyE3EH1t0PSDR2@CD7miR9L{Xs-*99%TK2ecM301X2J4HE|w z8xI`=2OSd!4e%G9euL*E;0Bhw*QCqCb@GFP;xrF_H%kXc*`i*bn8!!1_xr zG!pbD&v-CSF{L%JNG+hblZ>%7UD&)I)}NAV>zt9jj$`7}dW+ong8~p>JlsrzK?0Bh z+-b|oV#>ZUk7MO!`ac0Vax=+4J`eg`G)7`H& zhRZM})I{BUln0phbotO#!}kX?iXV@?3;hF#`b}|qZAOom=a*?1krEaf8n&@B>z~(j zA3afEmc2|9n3yg-d3S|-oEUG)=_W4d!%km$)+o#ci6&MEftvoxsSesvz|!T9O2SK7 zpj?BYLp8cKeMn=o=g zZ=^2<=wD@$EnSVN$Tm(Y2#AAng1x%;!2Ba&O|TCbSZimeW>O@40*^~FGifQ37j7V? zCNAVKc&Cu17jg=0ZzY#^>t94H2}iu?AE#9z3K53hnfVH1umv3oWo06$=K@+CFJNl+ z6{uEE&KWI(w-pv;jKaf9{7{B9A6MffzDSdNt|DQG$d;J=(H(_I)5Wlc%Y0NaiqA3e zY}KBj*h)A7120~fc@xf!u<`g<$JybP}~Zz%*i=zF4fK0ZDr%UYCgu4Z8#Pp1;_!NF4+6qhIM9RPJKFJ=nvriufnbq?n_g~2KeSvIN?=#w(p}oC)DQi;SVI_CI7Dc zMhYr-&xoR~Bpp6EIwsaO$*2wl(Aum7=J4trH`DL&)KGxxcK-l$i`aB#->A&`wMr&L zbrBKL{eTN!SKSdMc(ycMY};>F{`dpvrPZK7Ug?L9cfLFB=PPRdHPFxG^RtO-@WfBI zY2}1Q+f3iBu%UNQw9X1N`-{G?66X$d!iMHVqcBN6W@u|yRwYKgn_F^&Q*S9OJVqo2 zC;u>^=KImcPxe51!kICDmKm$Cqe^n43UPZ8)U0nnHx%4nUiSF6m@9yda{9N=%gGy4 z{Fi8(uBTw}@)s#^Yc_vqN?F>rZ}N|m_xmuRhklu5I#GH?YmkxW*3X-o=_|XRk!KSc z8XetdTAUvUFKbe%b>kQN;LfGBPj4`eHQ*Kg2k`EWrp2P!7~%Cxk0IY*OMZ}_`ceoHNS)r<`3elzM{}HKUs!JlGD!1BqI2I*{2v!>69yCZDRwi%<<8cj`3=={4bOA5zkYQ zI}$r+Io@#rU4b+kHidL-rIIwi1k|$=k1pQH{`A?`kik9p1CWsWeekY)D3j?vg!_$$ z|E{1wBhA&U$;(;4+ssAVJctmj@?akXj23Cqx4TRWETR(%lf;7U0CPxA@m(YnUI&3k4t zsp`pcz0BE6qq~JMvAZRGqxeD?UrDKhE$X(AOg0ZqQ$a%Pqa_!T(#VR4EmYW6oJd!q zaL;YL_F5T5U9z%gi<(pRTV^)F$M`8|n6l$4xqTNg_XjX_+Lo8ia<6-3%||*FHeef8 z7Mc=T+SYMgxy>HH3WO);2Go=!JJ^=m5se>S4h84NF4{ZiA%v!d|;T`GbM)E=uu>X*KNS_)Y7Q^GV7nAq2XDK>_X{bPP;W?5)EDf<%MMA5t*QF z2}0Os@ljX`kXHcUHg!plUOW6PrOHg{TWACt2{8%D|I-mRg@af7LXBX&Ys<*)hDT)3 z^E`_794iO!_QX;zm>rkLWsRuVL%pRHwb#Tp9~b$bH&;GEyD*yl z($*VoNvw{(fGD>-O1HC#Tl6YkooXJ9t#XK94wuhB$csZED4!6Fm<9(MS$?fWL3r9P z{$vWXs^Iqe0j7DDs1LC{_+xI{xOq`kv}2-8=H*cWE!Mx&ttXCHSjQY^JdeFyp6}T$ zYo?5OzbAOV`ostA>T_;K;bNQhxPiS>tBCx;sQ=~D8L!*Knd!4hB@7cQyTll!a;#5} z%`swpK;sTxT#JY#G^-{&L+2Garre^y6N9MrQ41z`oDmuZP-5MI=Hwv;6gyror41hx+ zP45F^T<9=qx*2`dwRw8ljH*1Y%fYnWv9~DYCPPj9vnW+}nKakHJPNGpJFZH-vep0z zT?})7rLVivv(kJ9X3toHQ92~^RKfEXpim`T8kp!+=aiTt7h9$dJR~%mn0@p`wQ{U%11PaIR4c-wOA*NQ1>-5-L<8wS^>`*|6-GDC!^X>aF zrE7rRl=~k*5`8aub^Wa15evKuwHJx*^`U}an1I-){*)<%MpsGi zO{)J{m1yPwU$@CxW{$%x`qa6Hc6DkT`Tce-Z0b5YLjU5VZZ6zstof=&rI$k>tN+u zRi|Q6cM*i@v%zOAvgyZnzAoM$`qg6_7yWq4>UE{T8Ueg>vODAhy2{K2&hnXRk;39q ziWr75@dFaS1$riceWURWptpq$=*1QIs_CO|)!rRLS9@BUU57+38}~2n0+SKNl#`_1 ze81-h3Vm-@78UyD=GU$odW4ynm~hq*V%)=CxnhC_To8Vn z4T5DV0a*S?yJp7tVRC%oeYFU0WbW?V-Z*M~`!?6Kjq5iN^Z%YK~`KVGWjRVx(Q3{|m zzR^S=-z=3M?C#~nj(g|?v+=ralP9SFYuciLZoEqkoy5}kWDdKp7p${TFI>D+cAr}n zW+IMUDXr3Q(@B|qmmLNjXkzAq;|I`~)ST7J7rfP`;5{?#JY+XQA;-gJO$mG|tGTuI zoShc$YAQ0HE#Zx0B0L%(aCUoG!%9SbQS7D~rn@9-U4de; z%hL5j-!0c$!1dY7&Op{P?KeY4zBYk*-{wt^2)Q2SR{$v6{v%}CAAn0`Nl{Q%mB?1f z$BN$hu}V!#LVt+`sqyHbZ=3@^7LjXD&ZR=V}EmD z_en##3R3sJ%c^bCprqnLM>|=~>A)rPN9vCOpHT+f5CXdMk~tH^@LR8J%V#Z4gjiQW z*B*j{=J1&rGx~e-JPQVkwX1d!%|F1Ja@u!P6Orek zC7GKJEhxyC)wc4xiKAAEy5w!gqhq7;&ye+!aP|~bKTf~ND@<=2&6D+|ikx|(I6w7P z15m4+ine|2qK9a^Q7P(7mx*o%hnq-*VKd}$b+cbC`Ok5RxwAXFGdebq;i$>)y!_qE zHnVklo1N*%8}}s2X?cU*dte^x2J5l!MM~4cStW8}atWI(6FI8j2Y-H$dq5(8#t=3p z3S3_<%~|jCUhj;259`z~`KnIuIC;1zqH9~@Iq2kE-k~+4+nyR(O4587m-o8Ad3~M} zxt+--+ILX=tmOrp@Xp zu126B6etTGmzV_WQy+uK(GZ+Y%UCW}bZMdu19k;I*lQY(K zTu6^<4=(Z+nHaz-c)vUtEjj(YDL#Z)5vT+prYMT_gd;SC-h}P-9Y3)@RBlqx+DY3>o|JqV$s|CFzC>Np-$X5BQe&=T?&h z@5X+PQ+%ht%o;n;w29x4Hy#jsyk2x+RV&a+YZ(r&^4*uxeB7W77Lb`=TM|h#-4Vzw ze$UB3a=aej%*e&Zm$1|#tMeu_f|`*Losr?cIEt8Gpf*~M2^gBIdv@mCXUaGfrn(6? z=^lT$eAg$&OHc7zcZ211zzSW2`OkX?uO?_c3gwpoGRm%=RdBzD()lwcfMC@@^`tl92wqG{u&3*(Pl4jlv{s%#3xl}&A$S)2ZHExrt*1r>|&ty+VXZ%hqV@=`G2mhUb&3=4$ zU@TC1hE$b;qw4Pj@eMW?kR2%&`>-PX#JRr{DE*5xvQtf4u;Ek1-KNlK<866Q!d5qKXiIL6_Qj|zS%p=1R?(b{=2w2* zOSCF@f&9_SPCG6a(jKNzW2Xo^P^+5iO@x)U{W_#xDn{6iyEpG!K8KW8P zBL+A|O|)e4)UTglpElu;#8EG0qw8tp6E;?%u1pLKL0Gm$JW2XSs56YqM$Kpl(h<9=QRdy$s9 zq8Bzvuk5s^6=Azi<0LpX(5AC6ta`hAyeo8>E!W~+zI)a$HniXs;c#gDjDXL~Mel%< zPqM_f3+_^O0t}!INw|8 zfKm^rAcn$)O@|3Q?K?D9=+!dzLVw*O_ge#Q$co5`4qyAf3*n zsSNJppSD+UpKsXY5Ia%35UUox7AM{AK4?K%P61rs*gnr+Y$#++5LnG0@0d72d#p|g#EP%-gALfj+-bFSUw9oSM!}4h)g6Fq8>d8%KQQ$W8#@ygw&*S) zoQaxQkX43iYJ@NRjpi46U3lferA4M9w-WQ<+D!$P@nWZJSh>w%$h47zvv-+onxRt# z`E{R)@|wXmy2*}|CZ%0s&WEJ!yA?j|l|KN7#o0Wewr%&6reAlg?d|WR*!^UzMD6oX zS+U2)q<;X9O)HbrotQY-Gxc7W5vck0P@55)TMT>^qV4jK>wc#I%1$Dz*puHs7`1XQ zK-rZR_}5HrlI~ z`kwp`z|+Sqqe+^CLgx{W3=lL$Lqp@9!EgS8k@xGgJ%@Rrhf|uh>>eJ$V^^$Rf*5fh zblq}9@O;1grfU&lytKtiw1cLtOK)4s{j-gR(AzAvNQtS1Z+Tetwp*+q2!`k$JP+pMfo;MDs1;J!TYx4e<3> zXlugAbg$@lF`2p;-5E)a&j|KX>IEB6VpQzrE0BBJ&U_cOLP9PxZKHnrMaGP|03VS>4|KY>Il7K(>_CBwFpZNyy;@YTF8A^ zB(%lPC@*M+CoRa&Ug2_CTSbl8iP7HCIIChMKAYL?$AA zz~FkTU2vg>wslD?+4!A2a^aU#Y~cve!^4FQ3)Lb7c<-uuMw4l>gH3~kL#xK zt&YQk3_Xq)wE6NNE#)ZcS^4wrda;G8R~DE=Oq5YP0xg$nr^iqElMaAyCN!nhT*CVL z+3mdq1>;u^`OO2Yr28#p_$u;W%(i2Vw$#3&oxjw{gj(e+BROv7=a5&FG;(!^H8^-ZY8FoX6>OX z`8|`_j!+0}`HCPqd<(`_tKV`I)wF29I`?K2D(6*b>46lf>auK#F!RA-6LCSyc$v;r zcWqmJsb*ESYLg@?g0erXM=6OoQ+gt0=Pch#U$uuF52^D|bYNQX8H+ZdTKTFf5cruq zP2$0b3`K#aBnQA_TDrM~J_D(O%%o`1lWEYv*Abp2jSI}X5AGalgkyfU9-h-=_2f+l za{dh@T0?WS;7JSTrqlD^aZ6{Bd<#?ntb7wMXBl0vCRtjo0;0@d1G*q;BQS9i>JTQ( zoMgVgY!eODk5*@W%L+R=&ZtX?2tDbjn|bw}L-cm0efEkM(UKKAjx|a;G;2vuPU*U0QXlJyvUTsw|E&< zr@)j&Q$oNmMmGsP_MWGBw36xK;FKt4XHh_$>U?3Y3WR`Ct7d7U39O|oarjGyW$?)Q>@+l3$EnKDfl ze#m>JbZ2&7Mdj_7Iew2{RGeQoqZ>>5sS@I?<_Ir}%^GnN)4qtCu!N3d2NSr`^7ldl zZD)kkdsG?(?RRt#X-9Hmr62fk_bE3i0Z+gmJ0BkOEz#7%KKo~WZ)KtBIO=Dlji!p2 zgd$^#41WLuJ%VQDC$H`HPWT0M`Dc;_^Wm8d+QRjs2KAOAEzdd!z98KwmFTwO>x`|N zl9UeujuIa;R$j#MjD=_R+ItmCL`@%kPgx%zn8xcTw~B;NoH~oBYnxHnhi@r~d{WS) z89_B#RNP$C~yLB(nD$yS>IvB{9eYM3JpxW#u8SqEpSCFL}T6uU1`H^c)&h!bWv9#!0Y)x1+ zdvIaWa*5;Iz-FvITWLxlp<~uu>04GS!7F=MY9`#2>jICTh!gUB4Ek|tvr?|4OTZ^X zy5+!}5=(=(@tYvy9Tx}O$*XV1$!Tnlueho*FF>M25iC$85=^<*^8v0VBGQvtjI-Du zM?}W%6xpO;T`wb%#nEbSw~t|q)sAaXmZMckDaTeuyT>uY)K*~`=`RUu&y zOR+p6g-g^(Cg)c@aAqzhZN<%!vJ56aCXpYWmJh09+5e_G{#PaPe;q{O6^)t>$iLxC z>o<~e(7cM?t#yE#v?!o6zOen94)0&Tf9VFIJHboVV}TK{50dPsTOzLE&s~hyj%Ci( z4*Y4q1*(*%+2*#nd~AL^C{%gs{p=S*+W!u($Qn*F`%^6Dcf8*!jvIX(US9BJJJEM3 z=9R{-rI7DI|GfF16&CoDPLi z`FG9g{ud#ny>0I*6;M)ngB4c!R}J*G_YtfQg(>(?3xki-X#8e5biVv*qCI1#c1A|P zuGry%3e%BmsvL)^pYL;qQMK}esZc{3<&jm~(rkPCrbB5#vBPp?v6Lpx3YP%tM2LBvhb7{+-$e9ks~L`yY-Yj20Lrsojp5di(V+0_D;gs zu3B@ktb`><$&tj2(cNT^;PdwnxP%s)J$nHG!=XBX;kTy4ufmh9ueg!zL3(O-Wlc() z5?0e2&%}Ac<>j9CD2jCh)o~hDG>qrRLdxwC!9_>)m8ChJ0%hT&uZFEqe%>v@sbxxZ zyL2P_vAx)Fu9S}n9wem|Zj(5YYE$X&b}wnajz%p|J7+)NnRm)=Zy7cj{){Y2N-`Ea zmeExH^4Y=91h-J_J^BNffDbO%msw^Pa5$ON_4!wQl^Dr`!fKe7k6PYBL;+p>`Mt1X z1V%27;}O#Lq?L#(qh;22QBdOa^ttp`$LQFq7{$3bnS5uyAR^u($m4+v(=$!6 zqsxw!ryNqM>}ijT9j6YXm;-!cB?Z%aLkQ!P@-a6Ot|@7=OB$&*;d_(Eb3!f%@~--d zB7W7@H$6r#mhP;dF=5mvo7+&GbeihF+wG|5aakK|{kv~_F~ z3L$Oo2Pb>_Ry9s+idSJyZq2u}&H@`wKMAW`k+h@%*_I*gf;-U^0V??yr8P_i&TvGC zbwn!7v5rfS2=iEyQe@2lAd3iadTPR$f1&cACTe|hTZvqhSi#Sf)0*o{5rVo??Z3o(oSu>s;xBnIc1_(HgnK>uz$)DF0m2#p>cX(@ zZDe5BqWoCmabv81LB&E@%T0L&_o%Tx@Ki?yBZUWF>C<{I`ndqPr4JVVSdA1S=Bp zcRqJjf$*@2ojp7$rB=lQ!bQt{LA@RX z2g8TuN?qPT$Kv|=>U^8;Wldn4sPk#zUP@W0mP&BbNLQQ>bPR_qV^<+(Ib#=MZO4Ex z+%0_>H`zrt)}F4ttj$4*5$b-IgFK8`!H9M>32}a>aF8|O*szfW4$EylVi~u+wO!ic zuOfbBZ?N-f?wjXXFaz#`hL7QNP+#(7MF;u^a7o{A-|*0s?v}@;Q1AS1f3*ZIcwDMX zCiYKCH(Vqn2*EC*L-O12x>?D2R7FtpFAq%g)Pim#`Y&2!hvNvRT zO6ECzQ6sxEXsm?!R)#}Bpwhx!16#a4s6HU~j`A3$Q1dnk9vnUh&CRJQDvV?Id0v$6 zujFWz04I4bpVWNp{sH+;2fu-U?QX?SE2>p>-ntmnHmN@`f{OTF4VM=e6m|RQ?I(XM zVUaae(+NXUg?#Eg=}3Mzb#Sh`<#S_tcYV{6D6}V~W7KlwhoRq;| zx3d)mtJ>36zjUVFuJ^u`0Xlg_TomWjIVGO=5DFA7Co;RX`;})Ltw>BZ90O!jo>Qwt zsGATO`GB6ZUKloyil1v8j>1)(tyTm#>~5`dPhgg-ays*L^D6iChhF_lQW5D(6RiNJv~K2#=m7{-_-oa=AO2h zoEJ4Jle%PSO15D4vKDAFBbP*kLnK|>uJxKq{0u{@g(8;=H4~$ zOfQ#74JWRosQA1l$EetfMlIgBvI-?auy&8#h>f->WEwv+iG+KCQXpL{*a0nZB@WdQK5WV4r2xXW+SdV?6&*Kl$Q zC?m|ME{4!(kdf7V7;|-eHQ1Jr{0;3%L%y~Tkb=`pQ3B&ga)~D6%&4@qbpPdr08~>y zl>J*_abr71nZ#QmQl?;<>Zb3Tl$t!z$*{ZPJ&v&9@u{oZ)yd|=>OF9su||W=EUDes zMF$SHqT<5f+Jpf?tEe$nd7v#J2*gsjBC)`Pc**baaxz*OOrv7csH?i?#OX;HM?TwY zXSd;C%4TaZj5)zrGp8c1p_heHIO*4o9r*|q5nk#!2bi^|LfyH#XZE9uI+BI9s6!^ zfPl)2FAESa;8Js?u;gZt-tiY0w06=7F=%q=uDb7~*9}>zxBT**7<#gS?g<^{$}-p- z>20&A7X_;~emwAKks41Dk@a=q5$G6Q7gdigTW^|`ynAVF{5*nQ7kn$t;4_TL>>4`` zH2xR~WdTUb!+nA!{J- zI^6mqt~p;ey|M44uQ0%$X)6sfW8kUj&{QTsgp*dlh z#z|a&XXeeq-cduXΞxf)LTHqxf@yN#$dH963S*@&>sB!K{*Qgz5T~M%aT$R6Lud z4c*Ff;ubW_u=pC=d$sw+N}H=3mic8Vu#@65GUD3e8c;;=E9(W*n|%i|0?%qFY+v;m zQaBE&1l{nd>9EFr!k6CuyM1H|vHauO7rtfQ;?dDQ0r@Jb+d(#lYeNJcQMd zX*rozuIhT|hlxrEIv0x#XOaSt zY#*Ys!2Cp7uUg9;Hy<_J+n8z%9+a>QO=wNysy8dp?nHI$Eg%+ zdjKpcmuqq>?A*gIdRo{P*mUqllSqZ_Y-8GP-ZMYpD{qPs4v@AiM=*A|<%9>9jmk*? z)nHVZG4)???s(uQ4y&6d+ElXwQuGz8lP%thQE524Yr_S`Ag|yb0;6;6_V?BN`ZQsc zB+Xy?-|21iCw9Kpxm z308%{xn0N}ARGhGmJ`EEB*{SkU7Tz)5h8q3^mdzVas<@M)^Jf3)X?(cz@E7%z_G3N zmkJ6w@w?X!h4iqeSEM^;Lvynf&bVj^Q#kk4c(jLBK1S)8SXu|+ci{i7Awu$%PdeeA%fu?vY9Wi1P`sUD(2bdh_xnfShgvoHW0#ePJK?q6^KDMVKQ}?0bKJFte=^1 zx=g%4gxWF}mlCAqIDY*?Jn;I+XodO8Jp;NPhj-j{(WVx(V2Jrf$GDU&n0yZ|!r+kB zwT1@7nXk;9Kfn?nN#dYIyf9eXZcB&_;fHVF0w0y^KNv(R%U)bnrv=ZBGQby<7Yd4l zYa3$JIA3zQIs=cM^@L6v(Zqf{a%EVDHIJI4bEa*-6S#5%e`OHv1S%=897f(&rOT==`wpJrP8>EvuPu8 z3u|kZZ|a}m-h>%R9ABFb%zDxkLaJ1g3cxw=(lLIIZZE(Y9h8d19>jp|lf}`%I6sui zi*G)TR0;Q=ZBe1ZgFWzah8E0^Yk&-=VL0KID92K9L(r1iF%3)9Is$T zr?_4~zFy$RGnJDYh2OuwhgRJ?vMvt*TWNhPC)@!f_UJ_+6qu`BZ%JzrK~c9T@L(bpMPr(D(uJ+3n& z#^7oFXxvGR$1wpa`W)Lw^A-Y$pg~~2r~2^W!*Qo#Y3}E~$=+{E@^}ufm@PB>m83>0 zuj4X_X9fNAbv(@EIQFwmc2FOqRExQid2BRAGgp7B-)i`6(XuYCeK~X<)YjY{v@ZEv zqd`b3>!EW=1jEQ0#EY#!>#7>$`KDfl@s;jwCU1Y6a8Z_}MabNu1sVzI@`uOa=6{F` zi*f`AJ}cfOfP{n);WLS*W=CoqTsvpxNie>1sS7>?ZEtxl&x$3Htj4di{yJ~ESa0W@>|9M*rd!ceqm7$vBRRoa>|Osi*O)1+T0SwKzZpkgnI|q zexz#^Y-Ppci;2rzX4!|@rS;OnDjOYvCsXZtSr#EU0zH-MXZLjsn?%zAvF~eiT5%K< z1Q0Y0Ro~7HuC;Zv-}KQa_BQ!fihksK5%p1%;Oadp0FM!*SfGyoLf+T9VL4Sl+Ej94+@-KmMumtU+P zze_C@Xlx07_w)1TMC~EWxDPiy9Bw^1=H{@r3Qn&1ljj!A4ae!VM*aMKF5WgsY+Puw zE8(;Jmrx$@)>jzfCdwCN>VpOcbo83mjbo=1oqI1JP(lm{!;Wl ziQgM`q&{v0iuS1VC)0Jwcl=P~v=W`Sbn8sWB`U9uHsE%lwJpK#At9x7Qa3XJhOurs z!ZM#07DU_De>V@p1!0n{{anccn+6+LQ1F?RZ@mlqO>Z`E%Td1mRgy2s&<@4QXwCFI zH8gcGV(Yt~nBlyGBSqSNcp1mVsN-ylFl}p`;0~j&(gx)#QRB!~k#8>Vc6onL-H|w7 z8?TzSIhlC2F32YGI%iqM4}?h^Oht5G*~pYnL~e@P3s#qJzYCIx-wa5mW1=TTW@Yge ztE%>n%$BE?W^Qj@oHz0<-oQpRl*Qc z@g&BZ^z+q$N8ArYRdFkI({Y0C6J+<9kAQ2pT_owC4)ap~Rj>lbPwWs1Aw@~##jgbw z5&yBnA(K%p)FAe*#SfvD*J2F>L=p!yY;w0uOw4u*tq`MYfnEjG*|kRDe!u|I+dD8n z>%0Z_ZKe#;esZhKub$R^TTEG1*>D6pZ;!C?D!b%w5K0r&8YWbonT~c))l!{par*x3 z5Cn@bl36VHZud=Ba68`?5k#X_IH#N*T%L)9yUYlOr!d*tI>t^-YlV1cT|+t+I9l}` z+lqFy9v(ifb%_Udvj6t&TShkn+7&bg8`__AkNTE@hhGzP65QrNmrv`X{bOV_)fbEg zI`*1yFnVvt{X&P>ThDLVi%vSEIwt$BBvp<_&0fCIAJ-HB8cT6bYK}@+JvDTU1Ogp6 zfIT1IZ)3+Ue?0Edb z`&2%SDo%bj>D=Xv+XbK_q{MjNxz+-kKMxV^_G&&Z?P6#t2SPB3cEC%7?C=-xH+oT!gQ0A2X{-I^dD7~yLo61p~cqUrmPBr_T8kg-1^gx}w$F~9>Gab?9ThHwtU9Rb3sc{S z`9JA%ZPr5e4z&7U!j>mJKJ+#>d&M3J$8qx@JLJ>KzqazY7FQQ$Wi2ZR7c`zYW7kB6 ziw`tIBAv!g6z#mgr3=UXiFETYzi#qB9@h#dGO!qaSGUW zz>1prQr~t|Qfy&s;S2H~fxO4~x;{W}N{(UQA}d&O6qR34;-qGBPB;VRk7S9J-2=xK z#1O?SzXDoj04=N$(N+a0Zpwq1?qI;r?@L+;LeZ4dtBiIwx~u3w;g|VVsz@!#mLoH$ zCuhHH-efpYAZ{3Zb`KN&~Fw(r8*DK74ek3*x zPj0pN**960R>xPU5LBgJVh&A`U`0A+D+)0QkAFSes63LOSOjVJJS(fHoB*1ldVuLW zap!bD@>zO(4xt#W!9G>-No0xg_lluo8(YPofiaPsY~CTSoro5ndkuQL>*Ir(3^3@PUV1nosh3Ky1%EbiB#QgO*-$ zQGhwdiDkJ>FGWihIi!UtLpOfNcON^@CUZYr8HB^Hs~7$eM~{rl!x;uygBFf(vb~v! zLwbq~h;Xn%b>#J1^}l*V)z{ZZPv;b8z3Lwpc{(1$Q^0c*5Qm-D?TY_?kcz1da#dUy zaYX3ZE@pY5TVzD8Gpt6)*`Gs!(ypvtdRoS_Q z(|ry47wKS^JQVxjc6p2u9fW)I^*4+zM@!-6t=uNwfG0kCH>VYc0WVjpZZ*`D=U=ef z?z0Pl__}zJpdm@{=^@W@cJ$Jxc;~;Bn@gn0^sy{n_@y zX=^+*Vxh-3LyggS_?R#yveU~jj`hUa-e17EBw`a+pm8~l<~T45s3*HlO0jq8*sm*a zaB=PkllDwoB;@p{sWn0k1y6M^s8iw%x<`jPQ13uiBQ0&15~eSj-6QNccIowtgxPIA z*C40l_^aHlV3hrhzMU}>0c~tOAE*u}7R9PAPMvL@HY7|5`uPqEG(-cU?V736q}>(2 zt*PS*bLcf)NIA58+tD#`HZn0W`Sv|3b>>;@ysLF?a;70BNe?F0I92GDq3k$ez#X#_ zM{ez?iUy$$??lbirTq(bdQGu>hptG6s1Mmn9jY0zodmH^uRZw_y8^_wpGS-~Q_43* z)9pnWcmQTQYzi2tb9&?C-9pC9JDFLU*7c;dF4vOc&tIe^)M%NONndrYH}^ET_I$ut zLfdGc_K>s9N}E!fb$C{c=>NGMPB`r(ngQI9fzX`_6A10=^M$;d;i2-q?lE2nOmRTu z1qp9BVJ2$p$*7_tJmNHfoUk!7)~3y_ergpc+^oQzt<^H^12W#9Mg)n^5(N7kq=*G;}>#@3R~eoAx&I>b2TgIeIiV)kd@J zbzi%xV-wjH)5mS27k|go+#7e51!%5h24*sQzukLyjZn{xT851*{PMn8)@Bwm1Wpy~ zPOScx!m$?tSQ4-N7ABkGY$f7cuM}#nv}9zHeVeA3GKXWCOuOVHTB|gZX?R`k=N#$e z;i}z32p+7F(b~@&n3a@Q1aqA>9C;bZ2Ugtq4(dMq&t!#)C*$n76C9=g@W%@|etykq zNqY=XlW=T%*%Lt?(xz)tX7I|9DLxIoYSNH!pWKRK_9m7`MB> zL!B&fF2}LqDHG`?DHvQc=Ij$gEtN8q(fZ=4F{@zUDqo2mCS$rOLA0a7$M?*qQ0G_B zF}yJ%Zp7I+Bim?dab}{{Z z_vMJ1>)U0?F(YdmZntOsN?fm`az`j}b4!?eYE;u=jv=K-Ch$3`fQb)DE&i~K_m_uxImWH;hZ2b+Ld2!^oGs@vmIy9^e5MyBcO``5X15Cmr zC}~lRR%dFPf_T7zIVnu+edP5M4LS^8;tf{SA7Y_m=(gdV8r7BJ7p(@g)iF5*rh87^yPy~^t)X;mE-h-5c-jQBI z4;`cvibxRzZ}#42pL>4yx#!;Vo%7sZd%k}bS#z$r*2>77lR3wD-**I$NddSU-nKwhz>CBN<$)v4#Br=eh&BOuD#6c2I{LcndLCFG*4Bl5fm zx9z3&Q`^540$jR6nw8`h`p}@FpUv*D@SjwxGVt@qDqr5bRbT_S>Fwae=ugiPR#&kd z&?0?|J^Ff=<|dGZMdU3FVPkijsVPBu^zz`-E0BDTZ#|S6rteS!Q_fj)NrPG36E{#Q zYEea~*=T-~B!7)n zOP9f`reQy3Ur0w+@8t;hs;!k?SVNjF-0_Vkg9SA1HH)SLE8x_h9N6A732XPNePOit z&}N@Tj0!lZxV4ezO@GgOn4#dAvXNGk|K1w2deF)vr5{@2(<-iY9hdJZb=wqjycNLh z5dQa;dEbbv8G7lvZ~RmnvqYuR37sG(AHgb;ydQ=eO_IjY227DkP}TX*lgoW^KPte@ z6`fkMtf>wjcCtQ-3sJ^mci;n|mO+ovYzR#qlxyMr1~;*f5sJvV_7P)Lt^NM7sjV@j zrXei8MTkIu+|l%ynvE5Id$v<>Rg0Xsjg!o{dTc>Lue1OQN{K{d#K+mCLJio>x+UIuul#G2v(yKo&U+YtS$2Bc~BY!@jL z+e!T`GSFpo6+|4p<0SjFtrkC|Tif@RnUo&J#5R>yx04i}E`fpK#e8n@P!yAXKM&S= zibHS%JrEzUAwecTOY-Zd`(JB8K-6#$N~LEO%@79EkKJINQ109nxb?@CS}BFrnFcuh-C4C}>9)z?!*Um0!?8(m<$>l@-WT1?gO3A% z>NuGAjtsMOlaHyxBUxcHNKt;~f|5gN==3f20$ALHis-1?V>?s!oy~hS(&Tngks^B; z{b{+j+%L!F^<5iGXSF#BLnjmN4_g${M*~Igmd^+9L0KMY}SszF*sLhtdC8EX0%2 z)S8L|Dsvu7Bpqw~*#L)A4wV^N|20naE^XlF=l}0E3}17N}>|rf1%Vw-?0~tPhxE z!JqTq*J${fl@ynmQ$*9M^H<&MKCljX?u%AJM1d-a!82UUU1fa8&jTHj0(k# zxym)q1)Wy)&KlWz#^gPS)5x9YljT>81{j1^JM%coNznC+Y`f(ct;YO3 ziaqC}qS0S8u>TpX$7qTF5K5Sdn0vX+K&WTM(?AFDBx<`1Yf)sr{t!}RS9%aofY^8- zc-LuMLTRxWP3nk$V*8Gq-rI?#$m4LWhX(0GrZ>MYVxVKT5i7}Tao1HvjXqN2n`9NE zF*%Vx!*9SuLkZt>!a}ZwBy$G2>HVq~I=74Vqr&J?GC&;k9wT5QC*xZK_#RoRRBogR zaXDKTM^?=WZn=?zJISBaES$Ty@HMit%Q^9j%SVAu!@2Vf&jr#q-dVh%>>MfEqP=99 zD<^RuxuqywN%5+pyWFe#2Tn?GZO82U#|cIt83=4?;NNU7$mKt?hVwxPm$DC5r{}PP z=({xk#ntTZoxaKt$?~n>&q1O5UmhzaX8RLUOi-nc6+<#E8fmuy(Ofc{^WOl@l#uZF z^-P@Rs-+m^U`33J%5^-`jUL+5njRvCyTZ|iBbQ_L6xVi7uDkVDeZvoN6QIh*Ph51B zt@1Zu0r#bjd-iuj>e;TgP((Nm4Q{y&=Jj0+H}^fp;Q~*E3(rR=uJb-v|BRYh+Q0Dq zxpwB(Wg2z;1BCw@kUH`kP?_-LKS%=BlS<;<}Cn)1DZ7hj8X{st7YrCftgB`#aCu05|7oas1z1Da{u zegihDe*^X`wf}l~iR*5?-vEXc+^-%rh(nlNGF>fc{|3l>*slvGscJNa&VdvJ-EFr@ zZRS^ppl=8fEZBSLfn82=e2@N|g{BkW-8!DbBR=Szm8lM?jEC^bj8(8_{FuU@)6-wS zk;&FSK1+?(4@$a|yjLN;m&(pz_>Zte{&t5oNotwi3n`v>ZvL@JLbnbe0*`fiQnIsA z6c)tW^yZv~7n1S7(_7&+Q%xOy%sj=9`!*%vhM4OZfkqY`8qSGY>5O69cf97c7#psV zRHK;hCzQy|>J2J->Vt?X1)aac)RbSa~Z!^GTWiJrU zoUidUsaYImV!rKdnf5-fX2Mij3Q<68Z6|0is$~PtbzttRk)dO_nPp8NHt#9$?l&M$ zHd8h)anRnddCx=0-c30SI?Am}etCLP>(wn6+4=!4FfRLR_r$WtH?4$yz4J%#>^6ed z^C9%?d4pX%x(l-*OshZwcDBopmNRG)0W=Cg?w51=f2uONcu~3ZaG#n;??n}yK?p<~ zIxgR-sR%uQX^jwpvG@U6B9yLVbq&y>`nyYc3B^+;OkZyfU6{Ws>2 zI1pzs_(Vk41z%ONQ@w7@BMFDNpiD_3wnAy%-S;2Uf3}R1GTMl~<*IHveS@Nvh~N%2 zyoU>AVRGGpoh;4M%nUfPc^{ddBsVyOK4rThtkRZHDGUUu@+8zViq*xWlUi!M4zg8U z`ltiWs7X|Q$|dM7R{tr(L~~6Y$2LxN69vfk0i0R+9``<62fykT8glBr=kZA#^Fqma z^8SyxL#<+pf~5f4fW)m&jrFYU>G+*|S&I$&b+eAJ(`lGT&B;rBO-|8jdD&`@7e>=E zi96*Ge08+~;%tNwQ+2oR+=n9g2WIFA$S|S*heRYdyh6@MocIK>pjX%c_wgIpRMHCkdZ_ zr8%?Adhwq6&%%=131NKuf82`a&q6&KzJGgv?jl};2NjL{7lF$ee>_z4gp!0) z{YPQy=l}ZS1*i*$qph~m-YRv(z%$qM9~Tr*I2iTZQS)wkir?7vY5gkxiu-hU=tEe^ zwzJ#f;%3AZXXRtd`nAt~qA~kto)u=(r@<5)GoL)nfauHi0f7bYpWo|MkDlD3MEIxwt1ZO;J=@Ww zVT@MC7s+Y6x~hurDw^C1383_${-t%DulbCly>pS-7oj&6z|LWEpjs=rmqz@dURO_B zQ;dumsTrFxCU)Ja{)M#6`=&EIG2PNhAaL6n!h8C8d|o`#w!x)8Y>e4(KujI>m9}|4 zfY)iGW?S+@0bb_OVlxGbNpz@kw)9h?38wTt%@{pbDqN*$bf4U25vNxuEEm-pL_w1? z;|0i`y}Tk^%z`-H%!%kiW*E#dy+S|UkoHiWzN11;z;=Uz#&g0fmoL?|L>v9=7se-} zJTX%e!UreEz1)dT^y>MS*@ti9$y^|EqYy|0mZA4mA{beTIASm@Zq3b)C1VPoTuD`b zvY{kkgYGPjxKTiii(|Vs`;8}M0?EjB${iAeHi7j0^Mwo%&8m~$EjjEPmCjLUw2DZM z-&CF7N#-BuYoU|@s}cuFY#Wn z!9xc+Hz~J%R;+4>?07SMeKQ`O(kYPp?eO~}1OyyzN@7ZWi-X`%_;Kw~V@b~_R&2Ph zN-S}wBv|eBoy;9BfiZPT;TS%xn@j}^-p5~Cjc)hvj^f;KJHG}#(LUL2%jwMc0Z302PFa(Xx|pM`MattvI3%LX{ARjL)8oTqNu2zp>M)QW?jwjb z)#;s@U%LY5IeQl^KVs~Cg!eVYhV#bLdrteW94aV8nlzMu1G18S9A8&mBp=E3j1+uHd`{?gr*iwG*sK117on%vKfm{vENoj@Yt85_4a?2Y;5M(FI?809(B z2_4xK9~#|{RU=5jYr+>+ck`hlVkI#P%p1xp++C@?S)Xv? zVyLgeC8}*PE_3HhGC%K*i%+__husX0~6>1BMEAtkq2lN(k@>azpMiMD@ zAyU=Rp6gl&c-Oqv5!>^IUo9oS0n~fO$ysjk`aP6gJBug2Vj7ORP1+q554@OK%~IqA zT1il}O);H@+YRGcu0Y#3lN9W>J;s{m893 z@whpy342rmb#>5u7a&N3m+gN(9+d+aUpV0$32v^)3Z^@~_{AzNs}1fJ5(#pZd#`sf zbw(rOD6Msjx+qOGS)IOd3wZA`>FYz*ZPv}Y^Z#A{>J5T)ZPiVF* z&lZP5dpcIAkFG4QpucFwM+)~S$f90yb60JmUHie?`iGR1-aK0m8!Oa#XyBW z=f$Y6A!oE~onWu~t$W~Iv$t5AycV4kldakxm%STfMYVGgSZ{K9qQKPgUv-BDhGdb| z3xtFEItNOR&D$Xl#@;&RN1fxNfV~?IQjitCOk^HpGK`3_WE0PLiSv!hZ zE2d+V@)$qh6;{@PlI_CE5&OU9ozP8K+l1lRXlkn|%GvYS)o3Wct%*@2(y~{?5X3DsXY*{o>AV)eDr#aC^zg%*3e2}&H0iK*Cco{W` zWXH`Wy2ZZ%NlE7vKlEv&A|I>DuICZZP*M^u{7nI@O#GXuyp|*w(-snD@+T#W|L>5i z)zse`{t9y4q+7f+Z6_{IdDK%^0ex*wJQuuM4fW;TCc#(hz)MTVA)g$lm`SSIaLzaZ zISzH@5BEyrO@JdB8LWEHC{-p|qpNg@T{nR$1W%lb!poWb_g`o)#Vkar7_SA^+U=%Z zg(FagSf`r3ua+suDGbn@_Uxre9;`g`Gmm4>4IhrHpEwyJGr#1JLwfNH@aCnOqVzM_71z9^ffcKv^J}1 z8Wo(X_HbH28{Rgx4necJ)PZkp+>1ZkDhZ@@(%LEn^JVvdt*6L_7SJ5+@@#}+5yQd@WS48Au&`d>flQ!i^vPlRGUw*3v z-}cVV{#7wFAijz|4^j|B3{LN6%k?}ryMM~;Qy{%<&C*V5mx}mRc=`(kb%aXf@S23b zYQvNxVF{vcdQKr5P({nnO_y3@=~Sa?fN0_#Cs2_+0vXg<`2KCDSS(YzQy;Sm=I$Lk z^G;4zBERY-Xcoakd99Ux8=HQmYl@izoCF+=oqNnamQ6|=gr;~*D z65PyOyeZNZ>lv#93r+Xo3v91%6R4= zkb@}6+buW=7TC0uXZdOA;FO()Luk1W5oFPDv`$59k!#TIUVMZC89mE9k*AW^S?CW- zFPSvWs-%9#h=TWXY&G%6`lAy{gi8F*6J5(2H&MdA1{*gR+l*O&7XpKkZTMBvZv{O{ z5tFY8^@saJzsaj;l4NsRb+RV*E^kOPW}5kDmL4#8DF_6?CdA~M+_Aefa0vf^~M%< zy{GK#ql$`@us1!D_s%~Z!+cb(Z`%YgXW-z^3yX5a1A1^c&Ewc}L-o#>5SB9v^Ub2!08_Y{mPol~3n)P7#X;84}vc zp2#_5`!_|9C?)d0s@)zm5w*QffGKErJ^1T$T(Q!!9R9b`97gb%!e5^=oQoOn^>-a(xqr;CSX4oBNHnA*6?qxba!HS?Rv}&-{|a zAOxMRaGq`zIr##u@Iw>Ro>l@|yrG^Z!4%I~JA+Ym<|N(+yAohki+X;iUL2umrSl&1 z9Zx@>{;BFW{BD)g!p~`u31ta!LXl2y7D-hVMDpnHy8Pn~45=w2-W^<=&F8$e=8Jq% z>x-#f75B-;geMf>JPoM*W3AIkqN%B`j*-Bn7f+>k-dYUDDhKZ2W%22qO0g7`&OPrp z7Nj<4PceaHeLy&VS>evIBarZOtQm~)kNO~mEzD_7s4|?;ADew}A2uAZ`e6I5g>!uh zdLyXjO{dOj;twBd`?#jG@C9iJmjgkh7L^3So3?>2^UUi#MD;|ESFkL@yMYY* z$$~S1j;}(Ikhf`N&(%*GR!@5wIAF2p!8>@%(ixe2EaKx>GbLHKZl-2$GiH~>0S3Xs zFp;FnVK8*W@#fYOjYhVe)ka^-mt@_k?vLe}vO`2>N#6iy{iK5!erlxU^OF`)YXJw#{-=D!!RBXlEvN= zdW=@NASa}!YAnkxsL+0nrK+?hjIN)L_ia(Q=cPB7J?6{LaMu_2A&{{E{O^p;k@wws zWm7)qJf;l`T_6au$ucw)P)J@ycgV*KKT+xeH-^=unTtCVObJvQKPW(LFSq7J5Ao9G4+UMd@i7)N(#%*_ z?i{f@QfVb_?>1spL9~n{Oibw}qc8~|K5#9;Q5*Lwj_Gyks8vmQ@g~3GjP!a=zY-J4 zdbj~l`D*IM&b>+rv=q`6Fqy28C>i)9Lt1w$4WrNhabj-Av@ggKFX)BBEr5kH4m3iN z-!5$-80F2h8Gggy%N3QfXXH`lH7_RdtGtqmf*PR+dtvsy@$UImVMY_7wYJRP0Gd~s z0hzI0#n~akCWWEgP6ytBCZjmNZGJghpG!(8EF?Kee4CMb{!#o7RN+E~=}rg;BrCR2 zl9-wCX^6~<{tEA)m!6E1OiRW&<58aX(%Xh{G8&($;b|4Q>%?96I+W_S0b%SK^VPBq zzMnJe%suAV#uH6WF*w-D+}r51chqMX()*;)2AfVruuy38-cQ^l)FtZuDAAb8mojGmRj9HD4o zPd@ljXxc|D22H)1Qo-@ppEC<=F(uD!Rk!YYoADoHpe4~0V#yFY1=CeV3hEzE`$ouA ztS(fV7fQC5<}?x_i$!iNQ^Z6(dLzYbItBFaAzK~Q{(%2oCX)U-+lnUmx?+mytM?swDA6M6kY!rT7%(<|ll=-B9;Aq0!j6K+l9A;Q2Z4kgN3FuNTjO$9L3~0z#{yJyN%bjM{L8o!dF{kNT{OMCq)0TyQ z?voe#Tr-y%HQnT8=Q%K;@j3;Wm?RYjXFFm$e~vkK`GXPWGI$OXa4xndl z{u>Yyr5jk~WES8Y)aYmSp%u*U0m=~TubNcXbt)LWR}%Z!B3P|=bpQ0vOeS8)tlW&W z@<7+ukRUAU`LNPxL$xr3bV4H^C#HwrIbUk(#VwSayf7ecfH-kLzTzn^sV9;fmzg*o z0_lprG_Mgl3e9&tj`iG%l3c#kE`8c>Rzat}*J0cH19b~t69+1R%ulVa>5>K$#6>$% zjFIr*HWK|WWQTKS=b*&y5pN??z7TDDdb`~Oump>;O70sUsgT&Q=+P4@00&k!0@cCpziv1Y-)mY(>N7Y3EnUt_0NoZ1zu+{QtO@ z+?GD#sCgz)#mYM3bopE?y2d&7^OgAr67KS%hhBMjdFz|uQK5!>ABX-Fh5Ad28RxM7 zIPpTul@scG(NQ?7ml-H`8+*sZU7zRkNWfpmMn&3;1hp-Z<-d-IF~UO2mtLf}ILRMJ zz8ZPRpUsH>+4!FZ`)4QlhX+RKS8Zl|$}gfm@_PS9JcRIhPImlQQqz^6M}q!aL16|y zm~Vl2U{u-WHx&DNoT%K2&i&qT`ZH3CiVXL{N#>F;!O2_o2D?bPqIIoC-JM8%C93Qs z-i1Hvil=RTboz^O^yvP1{!e@Svp@XbJswK(3?(_9l;iE4)#noU;xeebm`ikyvX3B# z45;?rR|A(*NLfbB_c&eKvDXdiAtWd%!HR#V2oe6B9ia@_H%B^ zm#P|7>)xGRRqdHT3HUebv^vbnTVS-$BNw<@j7zkVBR6o(t&vPj@Zcx@2R#$b+S*UP zM*aqXE^M!|vd$k-`e;R67fPzIsP$X>%J}SI<$Qx|CG2G%K^Ih%FR2ZE3k!E63wo|; z;=gNic5RY^jeB%}t4>7B(e)p?U_X&e@2^sV!uY5WVv~eP(r(4w4eIvh7AbkKN+Et%5+?t<2ae8JU`=Z_=+Ok0d?+dEse01rCin=kO#z9x~>EA`*%-(ftY-sCkZNWCI zen;rU1Znll30;vIZm43eGw1z-^!;RI`c;P9ZRSnp+v{ih@PkG}B%Q=>KwC$a<-Ug( zC}Qdw8bMPtY|WT#lh#N>@o7Ia(AZAP^f)j0+vWviro3#MC@(xFXLb%b@^%BI`?TLA z?UNayXl$@d(|&H4reeV0uAO&qlT&XGTc;0}t$BLno9V$)`gM%q_n!@~a$5UlJ5m zh0DS2bnD=iXM0PYwD07L^4lI(!vg@(23M)}v2ZrFH(3P7ai-W%QRM^-rcTjKnV zY6&l!w#bPBX^bdsi^SttF2dkeW|Mt#j~fP2(5+k>?3DWY!-`}*1L4og7pg_8y^FT? z7AWpUij^K9ppLfI-w>{(F9=-y5#Bi=i)w^J5&Ii`*2TuS$` z&9BZU5&ND)UtVd-tC%d9X!7tO7_9Xes~gf8%(`LwnX_gkQi}k z*kZSqDKoQtn1F^g?YBz`;Mw7ed>qXjVk0QJ@c_c&7|&@?NK>pd*@g6ah zWNvEmJX5m9AX;pDveAQ;V!UYWH$ZK*K4==IqcgQp5s;aK1sU^Y@w|vtC2)MQ>*NDw zBsxScbr{S!%|6E#mf24tLHw6L1fBseY0}6Cq-IL}w0#>n&5>-rn8LbE62h3?VbmQh?Z1M(BiW_2zbztG4aSfs z>@f*XyKPP$fL=i0q7zkmGC;8kcaJwSWOh}q>+T|-NRreWrS0?ci{g-$VZ{-#bPCp` zPTs15%N|C(cBZSuy<{@9DJUNrvwi()O-({ss2a8YL29Q)PcN8^awpPVroLO86=*)O zmcGnOxhFWG>3~FIu#>cqZF8Z12jMZP3@qNBRT88)rV8R)10a>k!cw{H!kvniKKus2 z77c~8{6T29%*YY4-l{LUnmUsyQ^iBXkI-f~RJMl#J!CEO`nG#Ztbhbaz-L$%8W8C| zE}Ns8FBsGQsT>lq58?3~TUs;}!wki=F6_CNc$pVUi&$;6j1yx{V9HBP6O;vOy5H8$)InR}K4JlE?1Yxe?V&p$=wm8W8eGspy1 zLncK1>)kZGhK*RA@FSflm_6>ir%h1!ax$g1`v6p}SzcHs`?dg;YO~!Lo~oW3fsLsW zp^A1bWMp)X^Rrg^l4O?Apva)^(bT8U{3x{)wSKp9r2KN6&mv8CF4ui3Vy>NZ6_`HQ zEo2+o4`T8%u2DB8eLOy?&5Q`m$&q+wG|u@c0ym`{5X2PTbLP1HX{|tAAadu@P0ztn z!4js(h~ZwYvG^wCWZY(r!CyEq(R`#dhf_kEl1t?+kum}o3aN`e)f=xeR0TUEiV`!M z`qYXw0s01p!@W0^ro$J<=O0lZO;>BxMXTJzrRHr17Tbn2C? z5DQnlN8~)A2M3@fWrN*M6&9Zh>(L_$FIc}nwB?o953OHb@LGylRH<>gbxCVUuQx6< zAd8TMs)p1x{tpO<|Kq3s2sw(#)KpN!+=$NYei?&6QR#u+eL3Q^JLO zX#%k;UPog1_-sjIWwB0iPSZiPWb^|nt)V8?&)M?1tooNw1%cMkL$jd*Mibzg8;f3) zTleSKZeTYW-$XQ^2jc7+LOuY?m|7YOng%s!G-7kzgZeKT6Iw{TwrP7);-U}r_)W2*`H?cb$Tb5+OqKDi?+Xx z1QbZUK6iP(fgaHz6?zSliCXC1OjqQ9c;eOHqwJJjsoPPo}QW>6W>mn%ouHb%$Lji5i#FYr|E1N z0Kp!SOQ5k!R8FTu5k_6Se;(Cxi-5{-a*ojAp0(gusXAubM?kWTX9=EJKcaO&f8aab zs3nx3cY#|JS&3wm$2g|F)AnI{CZMB-o=O}Oo#KcTY+^lPA0>7?vGLy8) zpn_R=3D>?_@v)$Jx!@2xpWgr6p+Z}Wc}lEOiM0>{Rv>C^N@zwfObjnkjCn3N9#K+0 z>-yizNHaz~Z?wOTIu%45F=gY_A;pCz^-ieTh@IHV=7sjnIXH|NgPQ1x|P9I-kBlG&-HHt&qHm&^ZNd-||v~KYDUPDr0P)W0B$U93DlfFnEPqZta55bU7`)GL)iBE|5C}@}E=WX3twy_qUST~fs zJ8FJOO~I)kmUq|qy%R6n(TXi(>0^_dVL!ncV>uC|I4B&^ov|SKaXyWH;s|~z$-|lW!ft#B_+qgkE7qC@t zh~K%<^(Dq2NcA9((@tcM)ccVKvTJRt{B+?02${W{Ol&CW?^n-wQ|hBsH8QzFzxZW# z7T_Gy2}6&L&U<(YcwcXdX2fxlH_HonFXHnqC~n;LtVQ=K(DywRWH-bm$qE9rkvJBD z&T1E)L|#)UcO7$TI3EZQ1;qYjlCZZ^xifm>qE5Zcx$b|dg@FU>-J3iOhZMWp&e1?U zjMKa};7TIKg}Q-H+g;zc8|y<)3KClwH5crwdQDjBGpZcgg#wGjpSh5hFW*${1Nc=O z%yG%r#2=2El}A-Xl9M{1k}umQ^LrTUz?>F0p2|G$8X3!|GTE_3sd$9hLg%u%%d57$ zg&BVXrX(>Lfq|v>R`LQ&(1LtJBy(~za|a%M&=)Eo8VxU`=N8 zgC&BXacy<|1;%-lL2OX0g4Z!Yx#`-xl^NCay>R!GOFw2CBra<`*_uFK#uw;*v7`etdX!U-n|C|BE$*#^_i%m zH+!aPEpGaez6lUnDg<&6fi5TqbfGA;0#Z*eC~|=ef88=xLJB%dK3UXgYT0vtIAX}z zAu-9E@LNwq^X29xu)E=4yoC*MbzR-CW|)VW;|qgjiKXe78Y#jVE+tglJ|?{_=7$%_8PI&M=?q6_&hbX^Ibx;N z958{@513f)6r2qNk)I@*&>2ehrJy2Wo+8l8-Fd2u$8UKz8O$rmM2c8Ax3tRC%!mMw+_OO z`f$6aF=rR#K%@Vq$2}nD^H3}=d-P-VxO+D5oEQY1M?5E%(fH|PX7O8?VDPuP2FzS! z=||GS_^IJOM#cp%)t)=bwLbOwcl*vI@)S*{8vk{zDEzl-gsICq8c0d0{=W=R5Gb0x z_(z?ay!!f1I#0JtxFa)ZuO4za*{NWjUux=-S&*h?8fcSrgE$???OHl0!$Qi71z`!t zdSA75n7`{j@encEOI}!hf4lyvA}2s?`Nr2d_kuCcNQp7iV%rqX0$Qnu^5mWlIb*A^ zqn-dv)pooRyciQM%N!sTp3fpZSimAYJjG&eE3oR_wpt?m8vuh8lG_L5I=3LKW9YXUfS5!=f`#D|5wA1(t-6?eURGr=)A zFVsz?+YcY=a#a_Vb2RFxlku0Ag!Je+<0C1in61fP1EJ9T*OWBK)ai_w&X)!u2i)7N(ZBOLb zOgd)o8kw*lzs&Eo++Sy7*|*XqrCFH>?9+#-sy z>%GCChKs2nrSZVK&jDd3eSG&V`L$t=DI<&~8;xoN=fOARCbep9um;oPRfu&pjh&#R z4(^QdMNyB$Z%53}+M|_j@&09NZk(-SC2)$zU6hOEg0*u#@Mm_r_w%TMDB0c0GXGej zu=j-D`32-KGrakkz>2$C?->>&E|uXdJd!fNL*2qTCK^-6_jIYmm?5uPeNtye*HS;Z zJ?5)$TlF9_+8Zn_v;AS26*L2;un2 zPZLAQ_^(6ZW3>vtLViqTO|M~?V;W!?4+(Z64_Azpzq!}AX^=l zQyyBSJJ~Md;O=*xZ|Cn3B!6%bZJLv7XiV6WF+MTadmn;_R^1b|Shl#z<8^2l6P5~P z(Z6FUeda1{)a9_!cFxKXJQZQa-VSo#wXUFn=VdqR=ufmVz6~|oF#%aV|t3PKBhbdNVk)346%X8 zgEGh?)Xup^&Eywt`VbA%o&Zsch}YIto>xjfnP>9qnq-Q|=pKTh$-9xB=%WZ@qNUET zcCT=NJ8mN|$6O)Nz+EN4{mc?yt1M`<6ZnKsimd*N=}sx67*X?mL|N-9wK5kkFH?4z zs5WG!hs4&0!D*Pi7MDa;CMr@Y`PJzi=BXwsVorc>G6$B>XaI?l@~DhmeDKzL-ejsJ zJAyMno}OnR@X%<^GRAn~mCeIfOkx(4b;u|@G+z5K8RU{A1iz~k6gg##6>}&ktJfW$ z5GA6|`~}3HcS40*#u&{VOzoCAw?v{C6MIdk=oiUzmhy;ZW8&#{?XpYb4R!a6g>BiI z=))O^bk4wP=QQce6*0!3ip1U4UlBe6Z_sRE?^3@LMd8m^7)-jDg@jc8_t4gVI>cmB z`#e^gLOJc_gzJ!FhE36XmM@0JBNdKz^+Bf4#=#4u@uR|Ssr=$+9Q>|HZFyGnlpwzmlV*b zwmN0)Ptr6mzFKX~ciJ)QSz#}sDnJ$+CgnB#+;({XDYu1{cmrLM*k>fd)n&umB#H%- zm9vmiP*GG)TlcM`s4zAWlJD%D7x_%RGgMNt?>vh-wPI_rUBHdZqQ=U$M7g7#KUQ)$ zUXgs)OYO=kV0D=#9k_F_+OVD|`PE7xQN zf@r2ou>S$DV}aM5D#&=3tFW|gF2Z!-t<~t19Cw3;c_U!ix@oNmepafPzBRm5RhAbP zuke%h|gqDVQ_Qjy*80 zZ+SwNXx$o!dB7tO@v*=AagwM%gF})8{@dnO8&sNo`Qg;mt8LbW)SQyo*Z}W%|0*@H zr-GjOiBS{+c6ihD`)&Bl-bx-yb0RLaCR;mD=jQ?rc3Xv#OHUO7gHq(8SMj=?oJ8hn zKs_q%>e#EZ)ViIo?GuA}!n)JiTBP=_^P`H6HZm3-)T^bh#b%o*X?neIYFSn@-`4HN36 zm1ilile+bwlq;{Swhxsg9g@mHf8`B+Pj0nGSA*O~lLVy)80IjH8H}hvD{4bZ7F5h$ z+Lq+RHdr4lzO{cf>l*3`tIWj(81`TDH{Z3t9uX&cp`@d!ONxlsS9y8b|0`OZS^hMo zjf}|e41kh~p?2M7%TV;01ulxQw)*bPQ&Kv){Rf;}b{Y3fb}FQCS!FP0(j~>|w`|;_ zXJH)O z*vnkd@XCpGecEzFO-C`aDO3w6gMhl%JOi()HYWrp_l-MVT;bQ7?P5N4%g@>oOtM$nL>sPycZcM^P%|72}4``dBZJ<-IR@ zT9k_X*$+km_>Pq^a9-CBngE4rjZKV(6KZz&r*ha2`6xYbh+}2!Wa0QBgpfQn!#ae@ z03ct=YUY=m3rhp$IHL_rwktISVm{SuKC)YVpO!=`=KpyGj?Z&HMaG9Y94r+QW)qQ^&Ff^sR6))PWYrI^8BANba7iPVXd%maS#3 zyF)JASS6Tw{BADZW-f_4A7wy~otoJwjf|Nb^5G-IUrUK`7R(rTzQ2x%eC4ut`E*Be zqC)!NyZkVS?cOBab4flstH|3nZ&RO|p08QNI_9?Z^>xa3y2$rfY7*sH6gUguHPWQxOh)G!vC27DlEaJ?PWV_Tas7N5tx*Xd!rDR`s}$J$J^d(Y;@{eiDXK?OHz;9*33`COSN_ zQw+*lpOah+L^jV#F=E_@y!EK5-v9Q z-6r@(%bw=&B+H_Z{%f-r`&HmiK`ReDcOvoH`gAx%VgbE^BhI=xvkx9%&me3zuqYqU z=j_5*^jjF`S7NHCQ`MB~zlfK~(>Dd$4^&GRwNrqK<3rO|0SyZLXeKdz4Hx|pe$mNz zjXF9pbc=!P_ulWPq^v?Xp+l`Qx`fQwA(zB;0{6@JQ@r@{__arK7DcHD2_g{>4ihj` zB(0G^{7G)vw!p-*eVfehwkbfRK_oV|!rT5d441@8r}+@L6_)<>?){Gd5>qvZBt5gw zFN4Z)pB&USq?!bJ_XPQ@uG@n~><+E++vvKar^NXD=BsGnT98Y_vj@#sHzZK7%cNpB zw^8cy`6U?{c)AE?Sdd$7y6Uu~?x6`ARO z+8?5butEuI{#d`P+V~bbPsJ=h(a_JAzJIC}W+Sedh2itT>>4=m7gr=4sYzQ0Ys$3M z!tI?HQ|WIqdY;9~bGan$DILy2zvvmgEpf#n2&9reKrs7&q&`-o!>61=2qzXy4g5mq zZnrMA&(Y&@X~Xw(BWF;*h9Q)`1iiW4TCr7JSH_tva8vzo=Ky62pGPuOSQQzz{GeONLIwH|4awyU|5msWibq8mm=yJ$G^ z>2k*Ii)II6Qeo3ea$3t(Ao}^jAa_$pNZj0ijfXbZkBu0=Xbtk)ZG~wPz>;HHZ>zMX z(fF$r%yd>pAbg&<>~VU_Mg|j4U5QV98k|XC5Dd2yn zRYtmlZ-$%O{qqdro6#-m{K;0@P=uI)sC(`UI^*F4W^&$4UtM1xF z9xpNt2_vMW-Q6j>%*xr1pf|+*)i$>bsqM0SR@`@E70_^SrqYxh7z;=^;B8Tj;#Gro z5siw8S8PTlps+MAfqkBjFpgvt7$N4oKNTp@3DY-~J&&6|xe}Hip>Md^7k3cqnt5D0 zaY?z+_gY{qm|wZq#f^>_VhkMJ%Z-(Z%ZjQ=lkkOmfl}+bt@Ge_#f)e^ehkA!PP`aj zQq6lP`Ypl^1KJFgmltRgeFeNP7Nl`jL< zmB>#cVrgQUjsj|ry+}Fds0u2q>jSVT*RQhT((~h1Osm9T*1G%XnK<Y633Ui?4f=bSC(zdn+};rTPK7q|fzOv*M$oMTBKt>g6dXr{V(U z&RrfRl9+o=fii37R|=2=yp|w|dt_BSd}28(baS}@ZS5!h3Zy$%Gj&Ig$2Mjq%cR61 zjuZ~*NF)c%TdViScw4NI$Fi<#SH5~rsW{Wt>Nf?=L_}h8GBiKi?|P8;&oW0@8gVco z6gzumZ4nSR1e(#CSaukTvz*EqibXAr8+and0n4%PqnSpWxRLeG`=;GJLqnNwdd1FU zS2c#Ve{)0Q1pA~!h<0Mpx_Ym=TgXva78opN%gS;78nZ}<|8w8AKiln&JD77ydH4MQ z+-x1JiaG~&?6MAn9}Ubv7jiu;P6j0n7Hm7JF{1_csP-inJ}alHNqD#)wFr2$bK=v_ zECZOax#QeOmBMPO(@Q3{0~(Ds7`$t{o#PKCBwsjM>=|8zPt_6fiEHCuYyvJX0&|2$ z9c0?h?-J&r9#iR?Zi(61I;eX3ch*VN_GP(A_tNhO+O=Q=%IjMOo7+E=Be1XjbjPjU zvznrE3tEM1EfxTA-dQm-E8LmlH1>EfB1C1h^>Vc^GBQ6}p&_QP?SWl7f1e9}QV>K^hgg|J6k0l7C?q(hOZUvOl*xmZ} z$bf8bZr9JDH`KRh(U7{7d`4fzxewrZQto6GcIenMx~?N-JTCF9sLDiJ<#45g}&AE)-CrcSTwP*H$0N$p1w zf1IWq7**$O`~JwYSJWZ7VPT2{j?iz;V`{XnqIrzNd}V|&Q!I%R_m#R?b=ITueDY*3 z&npJB9O5s~7-c-4!xiI*YQ*Xmj7_q{-bvRlF30xGx$P`Bjk@dF7lzijd1aLF%j5Op zQMF*-l1jqeG8)`SguH*8`x7JB#*6ZjrjfdGbK-~hPmJfo=j)snImi)B9o+$J={Vgk z*5vOj4K0yo5`HIDSy-*xkJG$YJ6{XaT0L6L*v;wLCPS@jqQ-8$gVkI8r=M^Bx!rl4gh=Dv$Lpg-0yl^ej#a-ZlPNjrp7Okk?+yTMkt<6(qP zwxwvANvrXGPDqsvm4spk-tt@?Z6?Q$`u)0@Osnn2tBYfmM$~*s!d>ce-`Ip#p$3%a z^5e-b93)B|nUZt*pmPJ+3-rqfoNDD(`?MJckHu;cCKPq(BlxT)i+YJYHO(`Y$hXoY z6-rCaK=Mckb1?lndFNDCrXZ}kRg>gpOh=IbXaE4<=6T7@J?y@M2xzSj{7&C#xLlZ% zzh%Ck%ED(k)W0&4%LRbTWj+>Z+J#;wi|?e0I@HH&?vjt)JvO{OTs-}*zT@i_ zoGLRFPP;IUmFylA5*`{Yj&iYaEi#*?D{^;JGCr1@>YM$fJF>k^G}q~>XWn!;AqXi_Xb5aX zD%oyvm|$BVmCYtB_`Wl1`!C*PwcCO{s;=~=Py1nWUAy04V#>}I?!yPsJ)10$7v?Xe ziU33%@|exo%dgY3d;oH8PzKh?F+ott2h(k7h_SRAIJ6;J1>Ed>rr>XgYww$8U>%kj zT2-7zxw%SK<{=#ulSqzBg&+E`p_46&{svQ4osVQb-MKb2C73_mmODkBOJHnNd1LEw z;mWPy-VbVTLSI0Q4N1Z59mb%HJeY#_Ga3ayB#Mc=J2y>!jCvrBRL!#XS2lHjM`{hj1+S_IgZj-qtewPCJDQ94q?!m)zqciKT}Um^O@>nMX?FBR9Z#rYU(J+uk&mBlok2} zF?;4uGs}x8&H)&gK{HRkd>Wh3+Kprp+^+$Bel{l20%q-t=~~-EBl>>ZTH^QqMB483 z6XQTG<_xV5HgakprI#rsir>?&&2Vp>$Lu(O~bRcZ;M-yJWKKbl|i!71j28D!pzOccL))s9RZI+OjkTzN)C7S*s+m@1)I>Mz z8X%!AnLYMKb-48ugDxs(>1 zjN)pMvAia#^W3t4oVWi^wa}==xq3`bLy1sLaO@ex2ip}m5CuZPMWWW=jRgO*UzUGN zm;SHcP<64891)9%mj}=Q!|UI=o)dqC4>_hQ6tvG6Ib7cCayyIplEu$8G?IEzA;qo0s%ARkYRPp)S4m9YovnF*)SUPaZTm9VRBCj?9QCEA*t`tznTKgZ z7nix`^Ni9rX|(Q@2(Me-0I~X$x0Ce%`|+#Dj-2?D&m+TQ)KC-SK{-u9t_1e#l8|cc zX=cHQl|-Cn{oDQg$ta3k#+ow^r-bx72hg#wKV525c* zNk$c?)xhb>W+F)gl;OtGNlT{Gpzk_Pbf6u%u!fU(ecMo2ZURg>?A6`S&8)PKpQK51 z(T6q-)06$NJN^gim_3t-Jewd@Syz;}Saq+)Iog@u&IR}=?&w&z<_!8$E zbIVnIxu^3LMX%9-a^Cxg>is20BiGAK?N*vr1&&GP_5Z$DOHv)UdC;#~X#er3;RiJ> z+aQ`h!he+&P$`$v@mJ1&w1g~KcI?sr<|4oo6ABKQ@ItfW2LPToyYKg!_nT=oj(!u9 z_+P{{P;%6A9{fY6&=ZZqBPXY4H_ZYpvv=HHo!(vb@7W}TlCVq^yayanu zd1~K%zET0411EwsHoBe>yD}dcR6GePzw~fpy!m>j43a$74fUVh#BO5P;_jEms`X$2 zQ?;oLj+2NinP;nJJ6XT?e^ekQSHAKrEUsz=0YM!J z$?++5<#gij)1wOa) zwSJba?K%|6P}$t#h30v*Qp=&}$|;LoF$2P&Qv!EF%Sp9Qy?lrlt|5J$xn(dL=ZF5% z3)1I>6Ph}GQe=?{$G6WS#{_p{%8`bQ;vEaCFo4J*746;8v)tuML18$`#}}k};z1%S z`z^G8MiiC2L(=;8t?}LutTcS@ut(A~kVy0&C813-L${i&s_ZQKwNBorF-FM`QyU#@ ziCRYMDq!_)(U7JI)-2=dL54BTYL-R7gh4+sUP7IO#Ty#S%Vim192~E_MB5Iz-Du|a z?%A+7pWO?b)ihowrDan;cVMuA@-W*m!RV`QGf(3M8Z0Fru0P+#nx=K6V#b;VV9-Xr zD!1%ERso`{WnLBLJUAAcCN&5694zNcB*fCqWjJyg0-rc>y?#xhq1jKzeHNL-(rdL_YM?Mpxfx;wMJC)r69ZgZ3=V zl3RZ6yV^)74P)eh6@GaUhDF-J-5&evwZgzwO?^W$UpGS25OYheHBUa2{GFHxAAgoq zg9Xq!zJnZfW?_7u53&NJx`SHpG;k!$1w<8u8CbewA5MI6qkbaVIr0!fd0rwNw+qtD z2oc*SQ%UREYinEUq>1Qb$Y-r#RzF6W0|+tV;>R?Z#g6RTD{mq#zDQ0P@ZgjTBdr|! zAo#{c<`MN&SV3s}$MxuHY&lWOLWgp)2TV+V`l0*f{8LrO^Ig%;9}oj|e|{AG69dD9 z_gG%Jr=g@F;&dp9_rf2`REFpi2c;D! z7hv8l9h`lLptXSQzabZ!P|=yqt2ExTuN5+y)*k48RrrFfDRHXbI@Sa}wLPkuu&-iB=KM&TcECLJ z6mXX1VhcqW?X9h>OOV;sT{VCTAHy9-o^Z0cKPb)1CnF=?Y{i=EpitN)4R}aJ+0R>D z2TyUZkbk_E1Lv2i1nn|E3?!1w#Jo{1vmN@0QU7+I<``TwE=(5K7XBk6^$|4K zrx)&=@LDa}KFv>G0!2Qyb8=P>Bvb43-nLYYbij6W%PpFqr$53R#!p*PV*KKb;A#W% zcOom>PfnwXJQ{}7c(jw6ry7DWrrd3@HqU9Tx4{C109c`=68ys`Uyx9b>8x~i;1fyg zfP=e#X@b6}YA1aY_$s$L^;*iQdfe!yXXZIwu5A&G2YmH`(N<aNgf&+GqYBMg>(> z;Adf^r8J=l;rnJv zSD#d4d04s3#Oo}$PAQ>p@T=!-QSN?w3s)UK#^gPIVn8{UR(16`e~`tZVHvzV408%S zXqZFWhn3NL74GkZG1G?5QiCqyE7YDF>=u^_p2S;K8VQqh*{s^*WaR7(vjB!*%rIx4 z`teaahoF32s)AQt<`F6-b#Ilht0k#0mbY|$)T6Fv7`*4<>Ru$}B-&n^SHAGvZWHL{ zZsOgZjXl!Ol#_dfmoH}}KM^&EcTE*ZhzK)-I>TxjCUnA2@e6PFaEfnM3Fo(P&Ngw= zvXTU@`OobvSc^*Bn!Ngz^DpfNiZziJIymSJa(nnZu`0EcvJ+Yu(f( zr&g(`Ul=w?HNN2xtI`e+NahCGFvy?bAr`ow=#ODFOFqv#y-H`kqL;L9spsVn88 zKXf2xt`43%rHifI$rUHR0iT?qbzO||LLr$P;`mf7WKE*~Sv%U>>V7v-*EUU8s6zH$ z`ZNBSgmkxHZ^@;)41Gr-=nem5@9Ep(Q_0bfX~D%d6LoX)Yjz_s2)llM0oXyM^$AVE zMBMcAFHQ;d13&V1E)hCIdpGHHzF@H$+ld*M&esmZB_$Z`1n>NcG#&jC?p~p)Oy5a) z`?b49xOO;7GAPwteqCozZA5qcCfkX+pQ7{Z<6M+7#q{3&*+(9P<6WE|1C1J}Ihf3o zOB`ewO9|s!W>`G2T$P%4d1UGhHME3;+Cdg6;)Z@zCR;0M)1(Fj_eYe(r*)e|BEsxL zs^u_0KwQ?CpQLGHdp4GA=zH;JaMXEuaeS)m8t%M7Q(%Sts)gaj8s{ZN(OgqxVwQCoiE<&kMrBVKzDO;l7U^!tpy#C8s_=hB{|%Wq z*-wV6<(`9v({S%518t#cs?J-YFC!cJM!gYU#g}crgf8Pz#Qcu;?Q(JY4ifTsHHMEC zFpr+H8~&QIQ=-;_DeA#9Du^8jtCG7i*Y&F7PmJRI8@7@F=+0jS+y>%b{Kks#Z?_RK z|8DTtBm&GdzR|SGc!N=Az7`Q5LssHD44G|Xm_lNI!Dq=4;(Ymw&3e;Np0v1TInmIW*X9;^=X66)F?7feOSw$F8|JY=l z?G)2+Ip^fL7;i7qu+zr%fE5M)cvGR|DT#qNC)qdb@4KilpvUBuxOLHqln3@XyBYJ- z5=q~oA3hpf%*Rl1YrFRo!~dCY^8sGK!UOEX1l&++19hw_0#XEIDL3Hvl6hc02E*&b z<1m&u{|BrEL72&`77(R3rVAnj7T0f?mjZKf{Z;Q_WYI>zg&KvccAUH1)TJ3TwVwQ* zDVc!woqS>{12(`Y^C*yH8TR9G`EE?cH)l5uV(Bh+$%WtKbf-~)*%D&fiCf;v*e8nZ z{qgGek~x^_l6qO5W-_xtc&yMg7572C3pHA`pK5fzg8AAk6nT#q<{4H38-Fjqpi`S* zSc%rP{7C+Z5A}k|o1r~jVsyjN9U)6_4L z-U(C&(y&CIopXyynWdy&N%>51Vfh5C@et7JbBsvaf7Ob*G^phts1#N?O$eHmvrRM7 z+IH}eYuS%U>UN`29G<+mmZ0_ddi>(ii{-}MlQ{VXl5V#O-OHV_qZnZ2qQ5}rH$_sL9y;pfHY0BlJ1VScS$hi~NicQl=`!;Von|qdx zJyUL-kxyvozpOtr;;+lz3TwPJNu}T`KWPX1eg?BdS*=loedo-yWN|Z(@vv33><1DC z{7{d}E-zhDsOMbOP>t8SYb{eLMATE;?y!p+Jn_}m6wIR8Q1U@!^DKvw2g5sN4*qCk zjP|@w>=gi~B1ahxD6F2E|16w$x5w7nqL0K5M}R&f?>Wh3ODE_CEejJdXbpHjMMl0a zJu5b0x(SAtT_+l=!R4Z@XmsXyCo-=%AYXKSQgVO{O+w8e)oZmCQU;r^FUlN$C5b|% ze*Qc=knYWY_X^FVK;1*?CTw?_m`>4s8H6>^in6>hPopU0fz170fJlgft(Wpq!Hh1 zH$qZeK&ex&R7^+42BsXtBKT83NrIAm)WKzXX}HoIxS;tG$38#8O9}$5} zrkfSC8T%5wC%y!T!}qYF=p?|q%U_u0UkW=6(eff~a(BbuU4`YdTLz{Vh2~yA$Jmwr zv_KisHI{ZmkN+G6imTP%4XGPeF??YD!I?zEV8h^|t-|RPSV|oN>A+jU>veuQ7vt8j zqzl%L)jILg#cAud_ia40v)Oz45t!JGYh;#G&s$&YBWm8ykcC>FZ@1VH6i)V>IugPk z0}=`r+XBZf7l=Ld-W187qE_Z;lX(}L7uQ2TjAYQ%T`|VoGl!nfy>^vq8rn2$`7x)% zx;mkAUDW3y!rmhHk25~{z>TGb%M6vc64CVInbWN1klSOp}TfI9vwk;Mh>maVU6sDYR~9rRCq12Rmc%n^z$ya5w4%f5+XA^ zGJj$)Xw~(3rlF^$p_YS4D)V`ZyH-M;j)=)m_wF1?_ogD!V#7TIR8s7(>unxGkxCQd zAYENuTJ3gy=lSMqfD04vx|qrG!Qikuo@l>PPy4eSy)c%jXv<8*CA zvpPM~%lLX^plvtQcs!a&U>9p52EAR$f!LZiN8urZ0;D_uU5nY= zA)%!{-{<#igMrha`1;-3Ys5Q;3r8Seq_(g1G7{y5g-tquo6W+#rz>!&K^B zMjobEE1Bm4Y2#vw!j){^6Lu~{!yPq&N0T1(gpN@sCdzh0=l$@;2x66Oy@9-Bmu7BS1 zfld8GoW8qK<={x$_Z-2NmIx{q+{!ctGgc=XdHH+9)dj&{zB-?VdD9EDc7`9!Y7h!& zrU^t|8f!Q6@4ipydr(c7R;}XHG#1IjkFim58O51RF?V1<-nmHuI=E`KKihOF*;3MrJR4ZX%?oW!at;rkEKrJ zzOX>cbv=@iFw3CgT9A=bPwV zX;|%Jscp6((URw!CIZ>+a#79Ugv}8vep{Cb^v8qPiQy%>7729e{4E+yUYc@eB&K59M7Es>5R_D6b#u|E2Rfu1GcG2lmi)xCr$+PO##_pwb?#@Af0kgtqy+`Bk1LdF2DqeD&Hk z-29GDw#%=%tm$s(Z&o?0mB0coVv5V^TUE~$%`y92SQjj+{r6jNx3NiD9FtG1@=xu% zKIx{oSXMVgq?LU0nW7EI#G`%kvXvjp`@T1ZchQ#0>1~hC;CBLRv4B@AZnU#vZbc9M z<#~O3IUt@h%GYEiCgZR`{|OzPc0x+b4zC&<441d*M2rx|>AuuL`=EqVIn#~lg2TaY z9P~I(`jd`aVnvqc;#ph8!fzAJ80HlR#aHo%g@%%ppM0*+6Td=<5S*7qJIu!B42EpY zt$kZViV_NDz_>*yT^vPLTzX~&HhQDT9|DI*`Zk#&6crA zo3Bl_ma6S`6TZLGR(tGXmki$-7^ziM zOyEW6THMJ1-s5k(IHcTn9vzA~<4UyA#_eepr{2iA-<`j#Lgc)>0E#|M_ndh@8Q+g*k)PTU&OIY19N)&m;FC6mR?Pge>sA{@!+#o6x+j~ zh04ksB2MOM_Z!1tH1)Mtx4fpn*K)W83FOK#y;pcvd?mG7V>M+j^++?2T}8n!jNLop;oCuKxW|@(DK;w!zJxgKQ2X->t6l zUdO3kQV;}k`TYtaH-A+4jx;Le`r-#klS};y{#MP#&3Tp)$@i@f!x?@BckBGP=H2&W zZPTAFM$1M03VQyzrh)?*|AQ+Ia16iEVpzausG3lT?4e>W8I8wD9U@#tEs`YFqP)yI zBkZxbV;1ZPrHXoQU{Ax+3pHDCDABMNyd9fPm@upv$sF&tPWI#~3t7FEzq|DR+X?^t zs*pRMgajwP3Tfz+qUIzRRoe<;rrmrLx(CDIt(qK($r&aMNLUF#MBk%htUM((~nwpo6%SAQ_90KDlVFC}LgUp{*k?|F9B9(RKQi%1xgkbUEL_eeX3{ga?3zWu}iA z62{&|#O+Dz5xwLckbllS6P72Ml$6fT7t*WqdWK0YxXJ$XD7lI(sx^Bt*mYu=Px4R9`}wP8Q>Q|b?MT9X@*i267sRSa_UeLJN%)5A zQEpXEZV9#!d3l%4FJ5in(&|9xngUnuFr;hYyl%t^|Q#bx4*A^O!uW#o0O%im1DMkmX@AskPRaSCUPFJvytms8}$}K-4s4> zY#{VapGppJOqV@0A*Vw%8~PTw_g=Etu~}p+!q=EwD!U=R)Wdug*_12JQS2e@VBMb;e< zDO4Z=<^v*p$%1V-*tvC_a*MNyVRM)G1TYt?Fy{rO1U(AJRmSqS20LOQ8`1IO!L*&$?34ppWGBla*OCZ*Wp(0oq7b;K0v9za4a_#Xn8ppnJ@19+2vC{_9&Fi(10%3^HQ>fr-8zb^dE z2O^9e>KAvY=5+Livj}dJQF5cv4s7ba_z3?ap8Zi6_58{YZSaruFf?Rf^>?WU<3BOj zmID8E_rGNF-=!x!B@9*hR|+SF!0?J#i~O#ZuEie-nl8PG1`D!sfZtt#$bT^?S3rMR z;_)O5QN`PQF~w3Gc|^wrMPRxd&~s$|WfS4Q^(s%BH#aXqi=Wxng~6<+ z3D%n+*saNZOg|ohnO+pw4PRNc3CsBk4;oQmHSy>g8t&l#zQt-`Z*w^#f9tzL(EQ6R zOOwXUiGt^;0wb#3lsjIF&BS_kL8BkT&dUvNmi)rsC{3cteO;v5(KNremAlFIUsPPT zp;y2^;(nzU-i?ptoNKqf3cUe*XZs^rhm4a&mor}t+de){QXUF5o#OTB$E2iZ0Z`=3 zRu)eB#%+I4vn@*V9O+n(2)a+J)YP3fG(FU)UR9p$$k>)ZBxsp2MY_#{1V zmSH{J`X@$S`f3#VvxMX&&kf2GO`#VF30BSX+A68dX^z$JE!jF?8n;yRkC80pRdG}8r7(2Mv4o^8$>EE47(NIt! z&4ue*1o36D94ya(dRDnbw6fi!t883#~b%066|^- zr%o}4+L*&1Xx=em4pg#ej%=Tb?~6#Rtg(hs7*zACI3y%jRp7A(;)Lcdd5~A5ycTpy z^uomHNNw34PpPO68&gr9$Hi)dO0t?1^>U)9BJ_?H;-|GMHKu0sOVVJQaw>jRqk7bdpwr}dL1iD1c*X_{x>&9Vy9at$`#tE>}$lBR4;+xt-? zI=M$(!yOXt@m-c#Zl8Z`TDG;~O53YeoB!!Wfo%>X4{qH;x{{2x{V{XK2G+0;I6Kuh znC*dW(KdV`#qq5LI!K}MVVZBeQUyXxm(PDlTOHJVx^LwC!{WX%H6C3&xx6mlo971w z9T%=v?(tP*gf)_s#e0PLVRy_&m~-ChYBPQ-xwnK*yZ(0P{1%Xi({(Ckc4^h3#6u0l zb*`+uSwOTQmPQ5d=B399?{%FM>0S~a?+hzxRvs=ua z8r5u0PAHz;`38$AwZZk`7O@U6YUQR=u;MiTY-(%ENTImNkzMd0Di2Xd_u9%|+Fu&8 zpQR=y;!t#N?as`#l@P^&Sh&2mHN)r+?V|SLCeJ=ORCr}XT!i&*=k1!+JUY;^#j>04 z!SY$pcFqxc`#`RW(Cs}JAW24Bb%PPwn-XR%KUC{X;EcI?y~`5IZ)b1$brI^Zm-+cv zYfBNmkG&IH7}?S$Po!u8aFGy4Er@t|m6mJEs2NNMWu`qw?cm~3(h`YRq~EshORbV! zSOc?9F3y0}oBcA?HTk5-ZBrYGNNoA-@{6h8C|OUf-RtvS(u4IKN1T;8x^pp8^=}kn^V}zKXMfEUp+GU8~5RZ z%l7I9E_r@p>~aeGq@Ez{zGr%dGUvtYI-e5{OB=wu)YMoGvEDM%e!I@XXGM*AJXnpZ z5dgb4vQ=JPeMMB~wgxp>6xr1Qt8|;8Ihi$+kkUqzqEem$@rG&E*UmD`2<$TEckm}> zY*Z2yDjRN=02;QTzR$5Ov>%XRxzv|74D(oQ;>`nPik<>#A_lf%6dQ9!*4kpzY`Sh< zR!PPjZAN^NjDc#5HH&$aodnzg8ZOmZ+U?l)V3aRxAj}Zw@k#;=91#rC$H`%DDGRDY zIWM`&vTD_j!BwA)Hk?S6CNfY^H*81J1Qqee>Es9W_vxrc7tSM6EmGzZ>xA-ZO?dip zEMZHiyOXH^IbdWt60A2CI9$z z8Uq@Xf7S#f_3Xn}NZe_eRqD542o zAR8K%_@Qr>&7rWEg}}~xqhBa0qlZv7g(X7h!FTx1V>73rusbJ$zWxLp*A)ue)eHKM zlY3l~VLmtWbA?Npd}Z8MKQU5{u<~KQUt2 zrG8>C4ct7YcwTGFRPsLhiR$|SC}QCbAtB@ZG((@8BjU`e-u4VoKD`7a9nnF4Y@6eD zlQABNDnkt%nX-j+SZ92Ybv$`9zqvks=JcEhVZ6CSi=UO)M}}%R*`Mv7e!V9AHj+(a zw0couqBqq;$~YrQn%%M|jdJTra@hxYKbv#mw}?B<*bqiHsBXQjy>4b<*t~OIz71d$ zrB9D-7KraGePCF25$=RhFhU3=e9~5GSzF>(v;IW@w9L8w>ZNT*F6%{Jz#-q_CExJY zVxmCXYoX&PNzYWI!HM~8!Fpe)!xI;%B~Jtfk?Ej{+o{F9z^!^|Q|l?BD#6cktezok zJnq9D&LdRNSrXQ)?N+~0sq_a?);uwa^t~o6QM58C>IVu7g*4oQh_%2qH^ds1$a>T4 zzTwE0We_RsgEtS|*ufhb$>7@LF`xZ)68dzhzKJrgaJSnBdU7fNP$v84L+31SXSW)` zScbf|Xln5ZJf{%`yKj=+mHhG>^U`kaj-GjZgI&^+ERY7%oppu8*^l)jnw<^>0cGCz z;5Nn`@-v4nEIxsVb;(jqlJKaAt&1qzQ|C^ih_?MzVPUCC9Y2L~5)$9=OS%7aNkr&k z9@eCJ$uEfUru-+ywPF0z()jooatY^ihTG*pt|TPmgLJ22|HZmT)-`c~Sz|>YoZ}mn z(tJ6^&_7)({W;2!N{h+EZb14AT8Dmp8zwTS_e`~#UPtWY$)mBwqyQ-(VclA8ZKCS|AWTj-J00;;G0K(G;@CZkk zl@S*=_^hlbB_l8SS3w`(DIl=}03cgO2W9CGuQj!_U!$)6)#8sbBNK@IANPL|p735y z|A8F3 z0XwKFi$B4pPw-ode}Iku0XDIB_@jQPPe~+X6jTJX=TF7TH~<91C+43&M|^>dh=hQM z{6}>pTx67&cyCbgIh2%DqoHF2RGi#mDn>CCV23p{LTWA^h)+gk`v)~gzgXh&2_hPC zbz0-hst!6{-{U8oWKXpIr1*D|2!GK;dn)C_1w4BOKte=9L;bVf6I%$+a1md=;Xo2o z!ZQLNBU5okXH>M0q2N<lOY>#-Q6wAa#E}cQcNm6=XJUK zY#;0&0ZHo>oV{klbz`(e-WS{MM1LXv3oBc%=tXie`)!?!lDNOxdp!tN|9<%v(C^Bd zv}}wF<9NZ>e%MbzDKJpkm^(?*3@P_8DDA_<16?vH`C@XL{)lJ>?2t6@Zu0p4Q?t+) z3cie1!ds?^#MDT1-n@Pp6)&=M|GAZa?f_AcXSDP`@xZ*Mb6U7M=p!^pfsg{eIUZ6w z7g3(=^jDh-m;gd}OC33cxUEfm>X+<-bnZ$z_0>mi(n3+%tQo;dI^Xz6bGvp5Xq=F$ zw-geeWzO@g+!4VlVCnG702=r~i0d|CYy}y8GX) z4<(TK6`3~A9FGO*2|H_C%d&sc&t%J6(1Aj=C+}rGVaWBLI*a#L1+Ci1+~vtX8=*^j z{G$e!0{#5kkvxc|Xk!1_^8d%7?EiOZB*~Mu_^1gB9q@ZsKJ=B?^sQWa5$uE?0lB59 zgo+yctR`dv+S9(QVw*Gzs~S;i$82grd|@W_X^}6wcBP4-(Z_N^VN<`{HZ$9Mu4?X! z`XOd!`lR9N${K;34J3im?+gYg$i&O;Y+LnJ?SuX8JySXQPh>rd7$s~Xq-N{0*=<+$ z`G`aY-8JUdzHkwZ%__moORDD=Vj}r~-|sp*PDJnXf_mWhqCzlxs8VPRF@t$C6@1}B zx%Osn>E^QW)H$P|^%1aguFxt%s*l8WLBRWaWlr=4#28<%M5HpzuRU!3P`VTY6HIAwSofNKJSu zHQN0r)~^-Rka0_}CwIuk7vd{QoxiS%d6s}1HoYQ6=tEasKN_D@>V2D6kol10o+l$* z+Wae{qj#)HgrVXK!>vP10F;V5WhJ?VE?w&3>gW_1X#8Ip2fI4O|{)1;G~W z^uga9oL%oyAR^|gd%pV`WL>E~_Of&NSwZ!`W$WPICMUZw2PNhy89=40i0_*d&cQ_8 z#j@j(6<|>;vx3u4MytP=BQVcOjfZ$nO|C6snJR9R3g-$K^~h?SL%t&FImjgr@(q&) z*8PMK;-;RipcDI?p4VrwhZTIj6|6NnnGgunsv_ypB|?9R&C+L({~j?)%TwQ$52M2T z*Y~2bz|K{Vbne^V-Mg8#X%G}!FxMoL?_J-HzLCwj^nKctu^AxACRzRVb zNl}@{nY6e_*N$h3&4Q#&~_)aMv) zon66!67flFJHRs|T>PYO67O+w0TL1jxHw4v?|~i=KjcMmX#u9))ob0=%d&6Ge8?ft zQaU#-0>76~-zuf-F49+`8Hs$JrQ$ts#Mut*RpuO(FHs}$WHbJg<{?AP zxX^JO*rvPuK+A#j*1;vbck23mCx?v=4cyiIxj-OZ@i%48{6-m{vNw^54sJtMkgsIf z2nXt5nW?DcH$>0BTNkk@o5aGhfaMJhelm(Y_{yrUacXY@)0R(3nkW0tfhcnla|W~?!@lffx+FXe!;{W-Rdx;}>!5+A<_<5l z;jzzyz9zAWO==Nyy|j3jCBSnK_eUKt>G|T0R}AcMQ6u%27aE6rcZUddsCZ8Rx&Hh| z5IICb9zxN{JQCcoer|(#rt1_%vfV~9(h%ymKguFP!Q@b_S)(AreATob7kd$>%Os5x##gi^S<^bCNo(@Q?}ICRez~(W4Ehn_b~KJgpQpcKCFrOg zT59ILsxE2uPb>LquC3d_sDt5zKZQSvkrrP{jb8y}UGUqdcBNXs_ATf+>oFK~ic&UC z945e&AHg)`lAmsLrFkjZvBG`R(>dq47upY1(Cc6$%Pj5%+W#_&45^gY51R=85f^~N zhW{lzl~1_Tjjh!ssdAbrcnKeTK07r<7o#C&0)CG@4T zf44P6JRA;o)d=<@g~oxbN*hFyC%!BPte^b?crq2f?k)ftn^f0ND!&M)mrHNr+;A%f z|0YpAPFer9?iwzMOfHvQ^fj8s)>lfqJv&PGC)!zZlkGB5%9OoTq+)FOnU>IX3v16H zSHZ5e?izksUX-09wCUx{ej7_U>2SSl-)id{<2e^DRXBedxdQrb^1DqB)$@!BnUaz) ziA%seov=<2Vv24dv%CgPiladJ^&{ZpASE`ID*=)E&UVsC_Q63J*X;33v{E*CmmRuw z4Y5lJ-#(LVGx5IX2x__gtGxglJbL3XL;RUuh^Kfah}R&?6|d>zo;6qmNHQHLJOW;v zhE_RXznH~FQ*h!T7t8K_9VDaUt9BFTXxoxdvwykc%0gQQx^L__{q*WIM^&qc%h)v= z6>H!+ql4Ph{nEjx)R9wEsVovimQl(U!Fl=Y=X7}^M3>Nep*8-0cCuS-uN-|E416Z=$0TNl`!Q?twyaCs+bj23)<~K7bf!PwR9H60- zmI$h;Un$lVxrBWHakZ@-vWIgc{YN=acOrWOr+@i#&q2X)cgO@LVxfNSs+DLIY z*(A`_3Tsf=uR!?~y6HMAdqh##v@oRHsQn{JG|>^92H)3gSY97(@g64p6765`Yg&k@ z@|UlFvU^mP&mk;vEb8doD)FsqOC^Wzl)y$?iWO{qN#7zXO@IoSa$B94wUnSApI zm4=>~Q+J~876LhR1rf+x)oAf(*1orpBdL1?e2e<1gzQE2DN??Fky$SBOs8^-CHAB^ zFQg&(;`{_2ksDouQ?YL{sE0JJSu@TBDe=@PGj6=t(4rFRPve*CtZJe5Q(A=y5#yiZ z*nsJ#S~4aSk$gJkNJs$R{5-FyT^KK>UdjkRkH~OaV#7?-(+3lCu@`IfAC-CGQixVY z2DQ}t%Z%Z!gvO)5PUb;uqP4{k}YdVE{ zdf~n^MlMT3MM1sEez`QWwW*JuaE;h)FU09TrmP|vG`_}9ZQ^eI9HZ-XN<$@hfBL$U z#PmE31q!PFAamFcDv7>*MdYJ?Y?f?I6B41zDL5c2YvW8eu)Eqba@&wAC%g7QxBYCh zOV7W>(ivWU((?SQT1$@o^TdZC72az)frqH+)!>&AlUyh*vOl+|?v|*GiDZG8jW=G2; zOHzS=M$?lFzub4$FubB%pu3A39X+P$kSs^{M^sSIGpg4@jo*&?d+Cmgf-t1<^71pg ziV=5@ZzSwuu@nq_Cobn70R-!JJjXn#6+>4BTK-0MUFAFC7%w?p7qU^lYKy0!@gaLv zNN#(12gSY#45okEX?m$)VCrF)+xq;LZ^Bi%qx;jV?CeDb-f^HFiF=EN#aSS)muwTQ zvDp;k@pPhw0a`PLt*Dq%o=h zYOZ$_@e=i6=a&}}?KctnnPOU%LvuQx3)OR3612SAykQtrO5_>=4QV>#@8hwkcJ;xs zOj(0e-LeC0NdwbBcHBb9w@ylu;);PP(uy*Q1S~?vLm`AH99fA7Fs7Li7VMgHwk*m_ z{ST zFP>6*=a}R$uGA-X-ceAm2wmH>5f+)eNSd`|KR4MK!-J zDX!gBQJwnI>96+{o~Q^By(aPZ`6c##*a z8nQf9KCe2W#+#`(n1+T+#=j0yv+Lwg-`;E7wE8~+;>Yd+u9>&jP3197w0j~=Bz>n& zNR~WgopmFJPGv;6V&|m&)VprKFq6r@HSshcB#d`MuMrbc03}krh6!GoeQmfr#IoeT z;Cr4Qh@U32_ifqDzL1&Jzgo~OhZQycz@GG~njfdZSw)h3h<}N{5OKrYM(!vLlSvVNl#B1Vc#B(Zv0`A^7+49aczf1 zanhF_0qiLsyj|`(7O6}ny!7ePgA@-R0g0d9h&DbL+2V4(xp7V8=2rB--B<(}5;{;- zs)C5~8O3n%6aHix@mG!Kzh25As2AKXdb3~C{dHqf`p7sX+8DV-iS*YE+4}XrGyW}) zf2+fvM)7ZJ_@4#?w2+s5vssWuo9WOvK-_*-jm1aj5}}Efg88(Mb`@!(>mx;Tb(!x7 zzrWbxOICLbv<7L7lNU*6G~>=Dk&clEM}_sAS&u{=C=>~b*I1tOWxm4`|3Y2CiSh63 zf6L_G3h{4i`1erozcoBwbN>+#s%nBiydb6+WDJ=U6R@fe zAM$LUL;&3P;p))4VWeYdPF>-Z3 zo;79t0Y%hdS|E0Gn2;JjNQ9*5&Jc!b9EX?&HjI|`uVR(Ff%)KC(^y%f}-U` z`3k35eO_W>5HJl*5Fb1v+LVTJUNZ5ZjMu&Nps6|g{9&d$-{I>ofx6$4z1=|3-QP49 zLcj+h?;E0ZFSUvedoA!6k58H#CZu9=J!$0b^^H=To$GE056B)C4|RC-Iqs2LE{}jd z{p6l5PpI#Td?U+EOW>aOZA-tfBFQ5llJY^~5m2-rGkH>W{q!6*48mt}HbqcqhQfSK zjf?Z}lq#!+aG-5)aj|&?@0dy+@4}G^lhdC+Bec7geS?FgcjL5*VuBOdI5(pRiqACxMU{x5G~lT6fRp| zGda5AmnI{1g9UBG8X56$XQ#$g#kj-W(fyM&ZTCX88Uh($&$fuN`KME$s*Oz-^w=lf za1XsV#H~9H3N4;^-WQbloEn>?F;zWWgaisaYRINtyo57d#I)cIpq%cSw@=M|=p@mZ z9aHnzmd%@C+ES1y4ekI|d24p)?71D02qWhKpY(RC?;`-`L_fW)$3t7|$8?e0ryG~g zTCPgf69|3pT`nI6an07eD%h4Nm7A?>yrSxOErTk!(f?z_tC~jP+AY3c_sWo6!ntI} z#kDoJpWQ-8EP(i{bNF^kt6}QmSM(D|n(U;^eAo#hvay?-0o8>shs3JA+9jcz2qQvq z1#*LEd*~n$d%7_wr;U!9&f2xvGmZOhqBpyovE?Ubq@eM^;e}-1bNZSRtddaBagdgl@xO4&B~^Hcqm;3uL+`1_nBfqldX z$iF}Ag+g88Zjs#rs5^TorFDW-#J{PlH6*!+lZB_DaOxp<$n8c6L|{d{Cs}0`7vS+9 z3nW6y!jg6-#OL>TyV^MyBIC*uDbn8xmBdn6zBHdwH}yHN|3z`5kqpQ917u^sIP2vb8}iIvD=qp`a)VNNmLN zQmV;mTlae?i-=P|+qU)5NU%~@9`Mwvo$SdxaiP3`VyA^$!4PDub5ES3rq`kbjMxb1ba4>Euc~CX^H2AzCiP^va7DHClg68<3{1+v&Iihrlh%Gw5kAJ5 z&0FSmnecHqb0O3UxC^x3Y8Mt?-jTOg?yrje)qz7P5SC} z!dD)>Lqei^o$v$TC7$f;8;`O##U^qoG5~-Gy}c66dcEmnEnVmk`vZmjKs%G^4`>|R z&&%H8R)qJ7BX>bE`pC5Q$3f)o~>YvOiRx&pnN*%i(SZ{Z= z!*I_(^U{B`MrWKwKmx2xOh{lJx;=kaPq_YU$bQ`cuDZ%AI3)OEV)6rJkxV({BQFt_ z73!+3fY|4Wh&355o6tV?l>tJ*Tfu?|lp9*ZKod-)akYd7X?B!G2@#827JFMUM9;D% zE};pT0Qg18w|)C-9VVZ+q*AT?UTV8SdOuiAoA&C_G7_x1By3=NM+xGVF05px`ynDC z^SOCaAp@O}%}k=~w(qyDM|Pnp958c^l@D-myiLa$-k6p#=88 z$MiS5ydVKeDdd$)NFx3BIEef!Zxdv{NUx4A?+KZES_`%aOLsY4#3aceA%Ai8{Ot8K zR$;}h4~LuyFRr(=+E188gp_OfX)?Lbqr28}iN0&z8z7iBc|Q!UR1s~s`@&s|FZj)Q zl>Aj>;cK$nNbTQ#lN`KXShc$>vAZXFa#XtPZ(YT{3x#WBxef_@357>)VX(EQ2fH$B zuu@_d*6?U0E ze(puT#*rpMfn9u4FLu;=D%68T{FXKyzc9^L+iEY{aasl}beU+4S-(>j4O>riqn)1Gz-4BYm_3v&kXEM|3q%$b(k8GzOq|1cJZ_jCPW!>Xv zMn_eCMwH4b$iC@{j}YId2>mDW-eXQ|j{uwG2f)(|EbG+6Q&zyybMH_>81cH{dAAW` zVcuIF)dT?%xt@!~yM=h~M*vMn>s0r>k+)1jz=mSK9MXW6Z!$q?H+_$R>Y_l$MnkV>ori66zdJ?P8kpa5uts(6mU7kZ{YlHWww}9vRWsfKZFl_(pw2F#KGELG z`>_kAzfT&1ZM@R{>q*0t)0Dns>NSZ#77yID=$H(VVEvO(gvW2w6JM8F@ zlKXo)lId79XmcMc!oM13I818~qDE1)rmuScE{eU4%f6 z>sP*Emo=h+$(!Ktz_BXb?WMxiK(46MVzU0WC0mvtjaIB~>h_&qag|crH4Rb?=iMXD z$7FWs!>NUvZOOP!yZL!SC9-`3g^_|CuA)P+v5v(>LsW&8�YDBN7`dJM&!9bN2b( zeZ2d*hnZeyuL^IX*pgjJ)(M>$*n-O!*&IQ^mHx$%=OB}mv*vx)dqcqN(K1m^W+2^u zEL3QL=@^p8nZ_mT4!+@#8kFI1ByhqL$j#}p>Bj7^(~xvcu;ZR3cm8-^%wGwkZQ@6~k(pO04LIH$4MZ ziDV*_&9xUzu<~;|%kxy$F4&T|SZSFInBQQw|Q(0{(qKDVSxtfXK$uI=Sx-uf+)SU91 zN;!R;Wmu)K42xQ5*nE0YU$z52rz4v&U$aO*T=FLFx-Rq)gu4$e|AHY}1PUBl0lw<4 z0m+w?-#Xa5&35C(Yg^_cP2Xf*F;F8L2iLzUD4kGkL*nG#CISSs^xi3z+Yz1q~7q!%yLw*xCmt5fN&yz&2x321b_ zbpRuw85{O==d8M1>&sYZM-%bB5rhlu%-z@3> zf#nc<|F#k)=kR=70J(meaj@gw*q}iqM_q?dCz)G9GYQ!z?6yeMbtPYG?>}}M{$YQ$ z^_$03@G9{L@4WWI+sjpRYyP>{kQIK;oe8|eEf|yp>rl>4(=CR>9D~r%xVY;?W(TYx zPKG1)Y95;&ztqCD*7j%YM@FA!bX)&wq{JSbbu)K(3Gn%D?@}D90u71ntQ;S-(6=ND+nx2y1SXz#!g zdJbG6>4lt9_Z&4NujjH~lJQQFF~KkR57}hYf?8e(%9uv`qH@Nq_G)g=_6iKS0cye< zKi1QA&CXdd6V~4}ysc0vVYenU4EtkdUHchnoeokM=70gqe z{W1~orFc$iKxW^swGkjYAEOFCmVe(M{c`0adgB7b2<#s2g%D=!I5&_PpZBsLE=D~> z_KW!ly=4fUTlaL(&PwSaZJxV$__%f+mC-Y#uY9iXT)MA*w7BSGw=3T@z zYS8@r9K4%B6XU053rls+p>sLn#XDD6#pD6M=7>#Km91)iyx@>*(nnxU?ulTxC6fO_AqdLw#VWDZm-k+v#hhPULkHRzz#JIy70 z=dH@hNUPDSt<7JG_1kZo{Q6_3h=0@P+^rY3@fU5Fcd{F!Xnl1T&jZTOK#+T)#?b@v z0DG0EL#~Gm(oE}t^>%GVofV$Uchkz1mFf3PiVqKbY$Urg))?`j{vEA8qE3d;zl7wL6bxZ!X49M zN42fmx7iUHC9H-VA6Gkm>h|E}+@CM|4g@_A19flY%ScSVVbyrCs{%*EDSk%4Iz{}UEA6HZc6H7@RF*#FxnApX?? z_dh#iq2+2&EO#Y6SuC00f7{jS=qX!VOKWR0hHGp|_#eBL7R4AAQ(C`!?mxseyxBLT zqFNkkD`{ijmntPGOyW1s4<2@>g?Oy$6iJTO$v}cy-|&%M74w-kz1asEAeBu}=T6=h z$faEIi9u5IT*xUv!*>f2;%1GKypq(K`xX9)L&hO**VuUzy9wrAg|~#t=@^;+=CiG@ zDOK}MB4#5Dz@}5NS&JU88X}tB+XD+_H*D~y%WhrO>p038M;C-Ltez;4Ei3w;NedL% zREFEVq&(4^;oI6OuPM$W)a;9u(UwUMD&>e*O4S}rXZbmY;|sdaU(d}RusQtmA2 z0?1YK8$jN#4AolxAko%M4^SF!i04&pi2j-6#l~VfE?Gr2FHUv}s6FU(80A-Z)r~^i zokakr#(sraHRRF)_00Ue@AhI)lBEuoZDKSF;>DBe-l3WkFFD<*=YuTkuR~hhQxs1$ zxi;kmzYK)@U^Gn1Xg?#-?*H+UpPQRMK)?sOty+x z29la?sHitVv!x`*j>M`XD;B&YtK9>(2+Y1;s|EALUrX_m|3P?kH#NDn+azsyoG*j_zXZ^y-d`QLB=>3`e&{^yN=B9`F)`gfiE zgiM*4Cv2LWrjk?|4|(zI7#%n7RUt|1<)p&9&X1h$u8$C;Q7tITio0mef}+~7xwMyh zR;(Hp3)ys3;u+Sz43BRl3{9Tsnv|*BMRxO-a?BJwNW*LU@)%F&yn2KUn(v1d33>!C zMEmYkLe|N@q;GgJ*#yVA**J6T-X9%WyZ)_3iU#rCD}KKd$fW7j>-A#t{RaVw z1k%pHw?c*1Nzpl(Vmn;k$CpJuFl500YGT9upYO5aoKjIp)_>0%NU!u>PPiRd#4~dG zzY`1n+wb-nD=w8reSWha8&GSpc$B%Z2pauTo5L;|S|Ss^Jl0J7}3 z4x{T!v!P=$IjoWn_F_Q`3MNb^Zm9gWPn9Y7CFgueKDsRus}3x(%-zcuQjPg=Nxq;)3WT;Wv1J%VW1Xd^)jGF&JK`}D>nhrgNpXv%WtdJNR`E**ux?^X&?1aU4JNqWqax<))Q8-)x>c zS7iL|@s-gJ?^wo7;n+_Wp@f1-nCS`gy+&jkI<^}4(e;W`r686 z|E8_fYv3kH4&`0Y5r_M8~2_NSuadp{Ef-|!&Z2Ar^x6tUOUbniWpWh=! zi!#d>rB#pT) zmQ95mY3KY_^vHA6Yl6i3Wvq0h??i~)v%q6lSQaWSH2gdO6~Thf^q6e;YEim(zJYd> zJ2puw3e?T}FIGK7xS{5khUb$t1aa6_g~-E}`2jcrm;qx4t!$Nhb-J;Mvm)`m&n zBZ5Q-Yuej(THgxely?v77i@u-ibD326(79wM1$<>6OI!mII9Q?d}Y=^?}0`hKE?LD zv_rt;eW7occ3R@!WQj~H&<1yAl+Zmc?3kHZ^3rG@Q;&Hq;WGrAQg)r`S3IA zaT4-Y&!g>dx{dke1v|aa@*8-^PI8uR^w5rOPwJ&}$x5fjsjkdUt5U+H1GzQsZz`m66+nsmlgUD>UBX zsMNaLfH3#$aqM?U5#bf zVB|3>=i@rUR;&7elE>{e8hS5bFiE&%%_Y5m-8w0GF9v$@_lbFD^1JfbKsm|#lU?h* zZ@r4*&hXVEpj?BAG^u}Y!!c02y4pc!zGywhy>qreH=GyLeNsE6wh9v#)_;XmDba5Y z#86fvBxmK|=IEIB`^suxPto4&Fo-J+Y43rr`;I3&-_R^fcoJ;4EY8h?{~Q*pc|Ii_ zcwHg5pOuO-{nDl8YcInp0_Fk5bF!hsX{5g?M%0rJ^`uB|uRK={2{L#X!Or2=`c(Wk zVD_9Owj}2eNC2p#l0vHsf+COoyBRs9 zY&*8I3$pWkJ+ysdhw|6)(2}-Zp1BjD;=JcwZV(;7^J8owg3vd`$a{Fu|h!HOlySH!>}#948AV;%3TP+4G-^AoX@ zgk=1PfAJ9TveAB0N2{UCIqB|>sp*1l{!@dfH|?)&_md%F%^noIO-511cn~tyQnIHo z3(=Hac&y=&tHQ_JS$M4SNdYB32_vy{8R&v1UIa|YhY0QV(sTj5>X6^#VHo5#FPNJ- zi?aW^3#)CCe`i26cDuUg6{%3|0cuu@y(U_t2*vGBWWI5Nd7|j`f zm2uIiKteW9JJB+q!#rnk!3ta5((eEZkVdN4;IjLO>DmN~S@DUVS-o{;0knl=C%4Eq zlTTW*+r$RS3%as;vk=%0(IxXznbkmp^%!J@A{TV7_GXpjXA~qAK1bUF!DXB+Tzd$b3NFgt{~6*h$>K(|3)yg*4O{RGgDMP&xeEMp^jBu015UwkSG( z5x}?4{s`#*{cf|ksQt`dzAQjG)abhkVOE!6f#5#8sB zQe(oGWQh8Uo$U-;j`!xJ^*LdxRcexToBNPet?{HR#w31@!X^~U zC(Y?xpJvz;vW%ZVgM*S=+MbD#F4{Ke?%i-3Pm!@0=SCuzI4gBUkg?wuKl2bwTrK$) zuJFt?mD#8aMX9@QD&P0{=koQWl%}1lYg7RhD~gK_3k+fx6ygAb9|av?4ksodf4buYru|<$JUY7|IRlmC*jf7KX|&03|)AV!D2UW0^P6G<1T1*c1Is5 z)qtKjg(VjjkANq?l9nJn`dZ~tUGGIowJ#ZIutxw)r0^UCSr54I#?<1Y9m6lpyFX^J z_|~4ricK1HT)ZqH(;8LC9R<5qZ*$p2ax05F=%O3bW;->HB$utgwa1d-AzQJh&5A@p z@OX@P^Mie;-D*T*gu)TFaerp92h`GS%IiZtSyk%s(1-yN9vTO~&pNT=$TQcE&I!*J zjsS-+*s?&!PQIsAdhd2Fo^l?cK>*w&B1?!t+f3jWpRy@ga=5wdcJ9+(GG#+6EeqN> z1&Xb}m`FaJ%i~1QpI+*-BGdsdW-H_Qx3o^Y#+bexY_R#qL&mp}Cmy2AlTab{Q7B1QK~B zv`*^^p$|kLphZM#LM_tS+&Z7tQ`1HFHgTGRfVC#}EVFiCLp z%+XI~=E|;+aEH8`FBOqO&iD<&f zlkJ9nlIxrGbB=(u;i8n0oo}y$*IF{+V_U$P0YOm;$t5F5OQwHh)UGS5m5XqS+(g;1gbO-3=5+UwvYxbigKz|XQ>2)pKlopds?H!dh z%HF00W<3IU=H=?s3{6A|N0<3_3BR=Uc&ep0*-bL&WK#4zqmU5qtK;InQMpw7J^r*6 zqvIxw>gjLInYW*QmGe+phoq~eE;1l+ENV`Hz5%|;!bl(;)T|{(PwFq+q02K#?IXJ+ z^z{zbhe$^2d^DOrcJWt5o68s;lg{}ihkU426j%E0>SpU<&>g>;vtom%VPXG`c!8^){!%zKFZ?# zHd4R$E3Mw`U(8?awC<~dWt!)-CRKY-phq&xBW9(9B%NdAi>SyHxTe123+^m;FZDNM zD_hdB!(>#rUK^|aK@JTDJnLhk)lrPR}51U{=eU=TKk@1X{!3v8Sy&tAvx29b$3u{F$|C1{g*fvUr4<<6m$lon>q&%Q72-_ROHMRaP_uKzrSuvgU_tl2R4Hw$O zeiPATZu&&>(IddHRCVGO;`s>JCm8723I=n#ikJ^@^o#p;2f$%Q@iK-WC-I%lufpBk z2|x-81aQUT`=!>nFG%N{Jio-9xKDLvi{LTtmGx$U-B4?Q68A(- z1OFl=TF+h;v*rj73gw4eYb%C~0AGtC)0YDN-A52opiTEz)N!V|PH+9A#`}(ajG$5~6pnQWiJXSkySTCj6|KsSvKYRS|PM_leRc$dUV6;>^szWI8WF_1GzsP&bxVW|@Zy0w8?(Xg`fk5LD z++9NB(6}T>a2j_D?rv!`xJz*N;0_5AB5$9Qd+t3m*XDVD^UU*MzU|)Kd-qyZwW?Ox ze;HOJrpw5#7~!P;=&cxl;W}qmhZuPT(Ua@iY=h+a#L7rGGB4-L20J#pIJabL0qn49NvlX<>NnikcliXDn7}g)D zTYjKWGtTy^)k42SP(Yf%iEe{||2@0^9!LPj$zK@WX)V6lg)Dl^I89|-r`eYcwf~w4 z4ecusp~;u0y3A^QMY*Pwr%XAl7MYpx>7q?#CuM)KlllNn$1X`Q1d$G)0mB#s{X{hQ(k?{*oiP*ZN+QzEc^1q>bkf_=1LQ03lO6lyLCZ* zM8b?Y=RIt(K9+PY_zI}|^_%JgUU_|F6?3i z-;p0LmvBtTMlBmc(#IW-4B^V?SKA*=KHoWsU$^y^fDxxWsv)z8MQ!vK`p>RR!2O(N zbAsfH7PwkxrsK%Nc#NQ8D`N|;hS>0+rF!=elzq+iMyG|mC}k+Xh5OYI%U)h#=0}nh za*cGQD_B7Xxtl$w{?%5gbvQ_HVgpWsO>aP{(aSSVLrPA5H!He~g9swWyPR6`h?S9g zn(aK2>y@f&G!@aBCd=7{@tUk$2EK|`r>G^DTp+wz@nc8uJ{nN)3!8IVL=o-HyYD>1 zcMteOwIL$)BfxDZZ7CANp%(_!Sls8B>xcTNq_ZMM{>cS>yzdU_PVKIUEcgq>MUzO| zJ2{=IwZ>M19_dntBrg4lWkR#|USUdwTG{nVT8p%d+{>DR!mg|&u_G-Av};0j$m1AV z_yl!jhsP^+8Bs{-L|rT=;R7S75s${e3=JO3vH;EA|GbCC!GTqEse1kps8jR#Mdka2 zci$u{7r)Y=u)L~$et})t_3|%UT6k5hQ{2E^NFDuw%6mOSZn>R@G%7s}hL<7juX?_E zcb@n-WDQ3&L(*MfLe!Z*U7j-MS#z1sQ;dnEJ;%@9NF5g!pSXI+BiJ1k1J-11o#cK7 ze%YXlF6eVRGvnS_P7_j%c*%#gD$4kAUebH)u)aQmh$qbsAsc;tEotq^p33mm^;fPO zO&2d+RRX%bWV`Fq^cRz7PtEAs8x8(5l}mgycd_eJSg zopC%FpZ9CX4`!2H>IpKVp!pOkE&ErCIAoD!;NIGl;I{lE7^Y-j_?)mq z0}#>#ds#_UWzRSjgYt%vqJi#Yli@D^nV?I6MPdxLNKf|^Ju=Q~NR>qC(OJRNGX0kY z{@4K;UafEvtb$@994i9G4G*}WLp7nW0?IYrB~+^~A5TGM;Dr3g_rfevnEkKhA+4#~jnWa5Eq za*YQadL6k+@W<=+6*^E%v)o4*f0K)~nU*LlEV!@CgEa>7EKKin)s4m?1m+rKP}fg*j0#%)P5;cBaw zN9t~^ALK3G0I(Q`NR`CF`quDVg=y9$*CF1HZn>h5r`^#}m(+4^kkz_4S1xjP(hY%Gd`{?L) zv`Q5|obtN5ETsqZw|#l`z9eJvIeA$x~sDywd1WQ9p7nEUPfFqJlCXPrlMv-+sP2hzH%Y;3$;wP?M0O+K0Nw>%B!bn1ns!@|D9vs)kh z!qZ=7A9+SbmK*=mg3-uGw39$XGsx>`jUP21EWzqk%J5uTMN(*>n&78kYATYDt445})00OiW5@e^37L`NN{_4)ErWT`Agc>6HE- z6XHZ8wDsKnT2cpEucQsx__3@%iX`>^%L{bm1%@E&~sJzOh8Dpt{wSkK8WF8 zUl3w9$gCDq;ph|NNgZgI*AQeO^Fp8d*B4=-%T=0~a(3U}5q*vPP|ndlZyUdI{iZzT zYsgyVPZ&lEs*UuJuPJ}sm;4b6)nA9s%E-@-e2kr-uu19wI?jK6QD4uDWBL2&IJ)?; zYzWb24DvyY!4ql!@&hYKcTl?MD*v@1-slg;4RQ$C2f zQR%-lB7=Np94qVk0DxeV=Woyb8>ak?acBtoE9_J_s?~&>-ADh9Eq`MjbSi?%h&q4v zqvXmKN}eK)N~liO?pP4g{;uWX-&b1r;$TAyo#yt{SDs%gf46a#XPvY8+jVtd3h3%5 zOcOTZ4TmfMSxQzG8Rs1+SJz;OJA-abTAp4@TV02eM8kNEyP;4>kT7ZT#f$A5Knya+ zi1>wApN%O|!s#iiy&J22#%WPmT(>w{p-@}#z+rA|VlG)Rh^)?SRw&82qtl;BWA7F% zVqAbjzkUzL&UcoKj{hjJ&L+mjhfeM38{DFLZ!UR?Q;4`0URlBjW0sd9SuS5MLoLX- zZzyfEU&hFMF;1=-Qwz*?Temz}VN{f~{U%KKHeAFAupeMV2n?%>T(S^@B|%(`gY03# zEMp5_()Rk@eBv!bnqQG?6^(W@!mYq?SZS=}ZYvfU9NKR{_LI#s)mbW~i+NohVt+(trUaC|?41+YX2iD9vCy1mX0-cwF60JUD45^vx?K-D@&G}%$ASnjNunfG89pSTMOA>9WASm2130X z?Rp(-<%cRAnb{w#BZP&tmLL@$`fr4{DWZ@&7ax(w~!VV;A*NX*Jls)+l3l= zAbyTQuxrC?UJxeKg^V-;9bM|O#EWbj7$;*!#&Af>)Z!r@&=oyAcR1-B>9lf)SCAxr z_wz#>&HIYHjLqCq&BfjWkute#!sP0tZBJvNz#uftk!ICBH39)6V3`0$0wFB=qoEAE zhj8;_&&U$lC4KIxQJi*_ndF-y#*(Z{8I#^kP&TjT(CBD6cC0FjRy%xP)Ag$R4^?~;#w!Wx zE7ns+WVq)NTCpd2Ph+Y`caTP!9m*Qf(IeKlvUiuSbT(fQelpC2@D;qn&dXi+QfSa` zl~qy*b2zXDjBI+=d%KAMQtqUicPaK!FwB$Vi*x*4 z;rAzrhz!uUbz;7b*evUURu^pv+`Q~dC?1@M@rr?=TQwI}Vfn!EmIlVH$g>8wL-y zu-Y*84|NMFi#t%pfk>Q9CmCU&suvT!(>v@q()Q(}H8$B6*rafg8XP~SZvk)DpNO2m zHZw5`vpR!jdf|&&f^xims+kP&a}~u3!>t1iWl2wMx-Felv(qtJyZWht&O8Px2gGK2 zjdtchPIwi!dYmfd_aV+hmm4;fZnF~`CsXK*hu`FJCy7<*8pbM*PI#2s6{&OQv4G*i zr6BAhx%$(Fbnim%wG=K}<9$iFqO!r`s%#zIgSiD&BMP<{!52W8{;i{vPl-L4x}P=z zPm2_rPts{Evz))al{Fwjd;b-nU-hO~J6}IC`Zrlfx?1UTHXqSRd1k>s&#S3DV8Y%w zS(eUZHQXf)$hcWM%2SAEXLv1?Jc-{Zf|<8YAeul3_t1A zxfaCfw0WQsAAT@aso22LxtpYL5RSuw({Sch(hu8PF3W<35IbTR$>Et{Zq zsX5#AWyB7H(bCTh2(mit1Ztn7TeL3~Dx?bk>h0+KHJ)EBtt{d86UH@A=Oo;3@5lDn zs*i+mQr&f-30#$Lp-rin9Ziemy%# zE^$0{Mc%9YrZchMVvx@k6~JgQbdA|r${KALiy=_U$dp(-NSH6voO?$q&E#s9zl18& z%fW%fICf_R=}d0*(te#X2O(-{hyAX0DKxp^G;mvcZiK6mDx4aUbQkhle7WXFQiJYK z7)2$7VQMqI-Vve&7+PBRjHxeQv?Gwpiu1Zr9jY)TtHtL|zpEq}pJyB-etjOiHGU?3 zK@)9@@c~JK{fkdkg?Zn^)x0H-rb)6r-K2*q#;1m+ADe*Hy=8QsMNQL6Kh|i)hXDfo zRe^@D^SRJ#VJ~ zaRkYp4o}O1YIRa>Shk3@1M$%2IVX(cs^hHC<~>>3v978>Au9hD6zC?Nb?cbGvgSvk zW-t$Q@>lMNU!6EAk@NStrE9?(-FFZKtDLa#?yfJ`f!eViNSIL>!Npy5LO$Q=g4ykwa*T?BFmNwc zc|*Vg6)7n>H4<3<%&t*dF;=PC$<6ANTb56gDpOpXX~MkRD~hRMb(C(mE%1;~TR+yr z`dD!I4xU@u`KuZ}l1Za=x`d>~_lhgw@8#0C?ONaQ5ErEhwd>(Pgfzs1i*LMQvL^#5w=)+z z&}s&0s{}HPiajX+lr$QO^o(-hoe?s0s2za!TW;d}3c^rWdP3)(hIO2z>9+v#Wk<*& zL&zg1kQK;69aief3Q(}{&Ac8OAL6PQ8kRQ5A#}}*vvu^@@%b$58>nVhFrj_3(;xwk z7L8DvQ~R#|`aSm$2lXsf`6GDlLs#(_#Dv1J#i`7rMRNuap(vm-ojEH_;%jAWs(L?m zW9;4j!%dK|XN;V}<}A`ym;t@w9lM5B`nU%Bg(D9%Y1B`bV?ZYGLB*-bsC*ecHDG2* zUGyhRu2-PaVxIqpEx#k*1EPLG7AOeF@`et8f`QN?zoNE}d&oRBnT!{e5!|HL@`R*d zK;j0T1g;-6cc>M;`gGin#nabKA`Y3x!^4maX$v72EW=2<9Xb?u^Me8#$Sw&^lq1XL zxjKEu^=E#d8LMB2Dk`Y3fy;?c9vI5|Hwd@?h#D*t@LnZ?k7V#UM?vg991Z@LrK_Qlo~IJGELnJHk71+K7~($R}IfEVfT!el2@jpg9lIHpFSePhDTVoi%0P%E!3uW*F(^=d-NW{-1}zHG`N zMm!Sctuof<&rUC=TW2ix_?AA=9%xF_{dHQBe@NjPw>y{%c0XT3JWJCq%9DauZZ2H zf^!;i$ytRY2{#gwP#dd)riiXNagN-gcziN3nzcW-7;!P^SR~i0lI0v^`6Zbi@HdA~ zZSlUKBo*&}z4#&2@k5NIZO60H@&W7RZdgnwPub7s4tqS}k(9T@pdwD7k8>gA^&63; z4{V>r$(SZ}B*_q^D=V$tRCSDuB&?^c{aPJYRqi{KJv+lUd%A{}7)+XXmqb6-NJuiJ zTZ#)>5eu3Sw|MGO5Xdp5v+_(Zp~b`31V~Sm2AqQ*f?oGtU|L1RqO8kZw-0;~$8e2^ zI$U;}iE3x+4liVsf+8wt@8C%a^)1;4GAQS=A$3{Qd4~2Y!u1_Wd_(>8NPP4zVnI=P z@Sr#j{E(|vksqx;p(#kL(oWmy43Y-da9~jzp8BTos^O(j=zbFwO%^b)-7IWv^&8D2 zW*<6d^xr6fmiY=IPEnf-oyHZEmArWa?-v=v+pAftT}AyTf8@_;Cixp%obM|>TotO% z%Y?Rm^cq)`eLjFj=a$)0!Q+WIFA@7(J92q)qJnoIaRiDo8u*YJL`>x>%I_*R0PANI z7?1n<3pRmsDZz&D>LAK;$uh9L_z?X^hCZkFk(%6KB=bj_Ly08cN5I-el8+4X2r=nLx?cAFe89 zu3BPs4n+l9N$JvAZyD6|oJ4}ptK;l|`w8550ocyq3{m4H`*p+i)mk*(L0ch% z7|{^B)06c_n?MOqS&r3qJoqvP;;Ti~Muu$LU~00^)D&`Jn@%CBc+!w?GiJG4J_-)& z8@Uyg7j)d=lCNb)7OOEp@C6e-pd%h)X!3K5 zJTBfS+neXyA9Gb2YRrDZU|Cb&{-6@JiV0CEqfbfNIpkgF)nFu3k5VjzZ?J8%P8y)V zBwR<~1TFVS`_N?RQO^^U`CKhAkX`#*GldHNW|$~ysHK+Jy3R^ zPG5`l75V0>&08m3nH`h4+%q&`zSPJY-jc6iR5eLk;L@QBoElh(Ka=^&Gqh>=`8%yb+Be$9SM~U!uN=~@$IjdvJ20G7 z<%AT~WlZmtnT{=kXC*R*Q%{5T*+`8gCGBZu^)g1*$sB1vdu=_U91 zI+cVpPh(FKGFRtbrWopkz9$2wjd*N$oZPt5iD(pyO&Q2gAH!YtpexTIXNy0kWq@KJ zY;wBCRIR)$gh@lMS$4JcoNma$fW5HfEKn*sKy- zmu$@2IU;emLrW2|S9fGF7q0tM1b&rSS*)UV72DwYUOWW1p;+AVmABs*h|n zFDf<@bt`j^@nutVzcxXWF$;!UvaD5s20vl?PikBW2_pYS$ee1_x@>b83Z& zk4IZ?VqdR&qlUy4mSGm=)wi}xVa$=Mzo7%HD>?vbHwLmV8cr6m#6mP|jq#rK@i6;L zQJJYTaHxo`Zxp)#9kRU=0KK%^A#W+YSN84^z7pD#UCWlkwj zRsqZj0^nC9k;_Bp(4;RX%HU)G$DS}FZ~)SY&jj1Ylf#Bk6{2AJwK9s$1YI_B=+6(l;^V96%mFKzdXxYG3MzE6H~Dj-F;$gmV0`7Nkh%6rZ(b zIovC$A5U030ExZP)e?lE5(tz9r+$o)p`Y{!Yf_lDPC^!chIs+kNv>nNWETGJ@&hEw z$h1yICl}Ps2Q7wyyvAb?z?8&}(ApJ9)!Y#fZoWY+&Ww#?ReIGbZ)ARBjNT@NDK@fW z_ko#R9dAc892OuN?cI`; z-R`m&(@e09SS*=paxSqIf6}b9gX#sWZcOxDx_Vysn`2zn^F#FydGgNC1I~7S3nu&) zgDQcuO5m#6{)B$=TPStPoGo?KnAm0tTk3{^K~~L)U7J17BoJV zD2#rD26X?dLo=Y{co2d6nvdA;I-1K9hkc{HyosYIqy6wIL;4n9G)?i9bFoHs_c2; zb4pkZr4t~99({5zMu@Ev12j5)k`ZhXo6U2mdxfm52Q@P_6YhJBJ5$6dji|)|o?7x` z2RLWhJW+KRypd-!oVn&mK|2;MDeFZ0tH$>42yFzks?1jiGu=ot>HPQNtw*iFvxJJw zHrXvcTr@Qu@6ineN*-5#sBf*ywsdw${)9Q~==^j$jP@mi!WMw<9t`_s0C($;E{`{bV&eC6D%mGm62!x}p^BJ388TVGq_i zs);Srulp2L8j;X!-vjecTC$Wr$1e4Tn@%N93$m!{nrW!moe1aAX(_N;Y<(4x<>gkG zskp=LYH44bchMHdw~6E<876`1La;KMplx^loYM-txphd+Xd@O*0~i@Fl;G=K4(4*0 zekUQ@vJBn!XeU6$T_+kEdsRaG4~2+-PN1>DDgOZ;6!{09kWSh_&14od3J3Q`B_jKm zKfp*d-^)tGB8W#uBs$yvE!&H(p8DMSCrlUDmsE&Od}G{esymDAZrRWg^ozOe`XMGc z(?v80`UXpI%_@2(3@1C;cWPL{{dzXN*jMWf=~b$fEE!ihg4O)Oy#^-unlQdt>B3|L ze%*3+aiPE~)v{g(AP9-bBs9k`8@H&uFO-6&Hrh)RlcT%L0a-N+JbABQeIU`uQ)d;L zvEzD>9U?NXgU-GhfCs#KDO;#`E{WRwz)bs+sri9%ZaY6pD$$pfIT95)q5E7C4J9aV zZaZ5&B!mIZUp>SLdLsq(A3Q(%v-!KwS zyhVn}tdJ31ZFL-x2o%QG4Iw5@Tj`E^)9JSvbJ(J1xK@uUomA7|6%Ddv?GCE~UXEsv z!_X7)C;H=b>^8AfTGT*Klg-mrS7$>=9LEdIxJz{1mfJ{i zM3jmzi|~uN`H_E5uD^falojPSf)j0&^-4d(`wM6C)qm%$G;XlOU;YZ=?j6Ev06q>g zE*F$TXbyif);itQKVf#h=)Rvp`TIaQE*4GTY5*aD92-FH72cv&4{#T6f1D<67T6i*QobAW$*PnJnhQb}mg?LXWqj4v~0^ zZ~q>(tReX?QYM@H_tSipDW4hurCg+$7nHJ6|9t*`BawlI8C3Fi|I3uaThzvRM)2p* zDCs-h^b4xOls8o_%RxzhzzkWkcXBN<_-VF@;J$fn$ zUrj8zfAd`EKs;kznRh2qTU~?BBG1hrN;IqDdSHh^VM(lFAKZ}c0A8YY-ZWlB7ptx6 zZy5982yaL06h%9e6kKSp?_=N@gyDs4NS=gz{^H!Q?)1T%346sN9wh5|39~Fx-#Wa@ zd0Jb?cxcQgKC3QC`iRuq+bGZH(e?h5SS`C_VD~`(?$xJxEBbFM2itx!!MhD&?Bq5q zlJO?pOlek+uRlJW&_k(Rv&E7M;?bXs+zE37|`hx4z)zB;1m9UvsUkobV)BcV$= zT2k4!TfnE)w>V0+E`YSz)11WhLBpGcvjO6ubm=9Z!#HvqRvXCtB}JeSrL0kWM^HY8 zmGl`vBJmIv`Uw_6YlYG^n{Mja|Jbd)0A8DKd*B1|4_Y!2FL#M4$)a}__^(>K?GDzW z)0`O61beRZ7meX7ClN2G6V|L;Ya$NLwv2)-^-XaQO_61yUQVkkYKebdcu~*-##LoE zHrce&M?uyJHN8)`6x>|B)>*t%)`v-pRx`R>M6Xat@07+QaOosoP17zQP<$d7 zH`##L?`;%n*}TkPLy5|+-$Fo1p{U9MVl^SNO?ROivDr)M?mh3?a0}cYI<2kPIc3+H zSHuH2?!4u1sT2^MkZ4>Jm`Q3Kb5V)0^|u+^p`N2&@JzCfS9@ zj&8UmpoP=I)7Z!)N@}0gpTr^i-?Rkyc*5tPRU`tSwP6FEDscYnoZtkVRh7TOJiDR3 zRM)5p5kdy0E#a77)YZswK~}Q1JJQ+aq%-!%gV5reHCH?538ZFZpi9|Vys&EGA-dyN zjT4(L1=_rk+qX~=YvPo~zx!Pp-y-0sTl@Ao#tTdRkKH1Qz1k1av-AOL1tc59YAxEF zOgu+-Vh}r6wp0gsxp3Nv;YvA`dZjfumJ^PHXcE05#cpB%km2iX|I zZuDNtv+D@Iz1dD)=q@y1ShEAs_yk(Jb%wyh%a9f1x#A*Tia0lZcs33((Pfk6K2!74 zFRm(Y=IU#e(NUPrU+bd^EYp6a zTo*9D41e$aqL(j@9WGv{5i=Q6A04aISXeMKOd-KPD=Dlh=8>>$xh!ilgPSlAqO0*7L{Dd($ zC8l0Q;#pg{XV;?QOoT(%7=R{?xy5OGd=*^Y>iI_8#rY5vj7X7weZ;uWp2)@taAVua z9J)KH&m2?isPT=I*o;;jHqtk9k^-%3P%vD9nRQ1-jY^pTEV32N^9!pA}Kcv*RW6R9IBXJ5oE1*VtrfCb)Z2zG-xt8 z?vAfnSotTzSfq6_`@R3OX8aeYCjxZbKe}`N51_MXKFNy5fcXBl5r#r0uNrQX&$3H@ zV$=AK+v5Q2m57i>a*rJBeN=1EMkqHCS;ES`-47Vi{s)d9rsMjy+9iR41PWuqdFRfU z41>eFiu8BYXtCGQiVO%^T1&iz)ro@-O}X?^WFk{=KkU2`jMw5t$`Uq=B!OZEdxd=t z*NQYC_(9>QAbaP6ZoPVAIL@^b((qHV%3&lNZ{lK$uwK_3@!K5>Io^}P2t3Lm}G@#0gG@(jKwb`-zf%J$4nA<#hwweT8YO zSd2b3qeS(qz^y5f7fWnMUI%hNVeBqER>gANiHXow%yRtC+&Q8sgYd#FPMRJctqcC85EHn{YT06DT^Gwsi^68 z++8D-xCwVZ?qs9_#2{@B26)7Wg68}pC6^8g9ia+X5b?sb#X6LoGPof=4+D4RGFv+O z=^%IkzJBAykv*?QJ#pu#Z#C;4o2y~_(p}89%32=9@Z#*ilGsleXK5}D`e&R{UA<%@QchmKSe5tD zbt!n4VE%K%kn%xhLMSDjG!GCID$NmBGS#xqDWjTl@?hCtaavd$gFJZ36!6028GvAQ zV~Ji=$|I)r5~gX&DMy;9Bh)x@B6I7UVk%o6RHKWYQGUG$t9G-}ilZ%xyryM9%@Dqw z1|h@oO5&X5g4ARc=@c=DEvL<_A2)l8M$^#$lAC!ASdOB$IlBu_d@1E$bO1cVg~|YQ zN*b~V|K>+f*Xo~{`hVpq<$(1NGL;%j4fcCiniB`7X82=?#CwEczY*SYL6aV=vy$0p z#Wv(zUhk2hoqgBzN&dPNuRZhol?}voz?lo=CiQ4~>5x#%gBagQ%-f5#$NUQjjgra= z`e7a#Dd~h-A@LerVVM;%V6s{z&K!8RGGOd(Iy4`LL3=C5@!HIekc3F`T#!<-?3_ zZ><`+lBjVLu2N;Tc)sHn7+x3lJ-fEKsCDL})9?JK6-^H7EHTTV?mc!JBh9K_l4#S%UyNN8fOgeG;a7Bc9rGjYLDlN! z&<@-n%uhEQ;+dDo@i(?HQ@8^=TN)|Z0O;_myE#+Jj2D8;F20p zor8Nt+`?@1~NignWE-JrI)q>obZ%eyWP#x4jTuDZ#W?Q(;P@&+b+ zg&rZog&3~yscBzqh<2Xh*+Ngo#teSh>;fIrZ#IJm$-l($%eAGp0EF$RAg<6OScP?8#U3c@(Ty1_MhYJz6SHw}?6r$|A0l#A#zq(y z^GkEM-d@clTMQmw-R(UDT;|lH=2t~sH$xyW3W1MOWWlZZP#IjLtP zQgrt7`lbC!ODjoPf%dEn*4(oF^hpYii~Q(G>h&FWKZLjEv?auiyZ9_b}@xjD@~- zTOC?W3{nzljpGoh*$XchGK(f&fBEc#kbqOK@&XU%Uo4o{;ntJZ5>Vk$8f7T;iL>Qj zZx}4-p^8iA{|SRnKk^rucVfbVkb5nt(-|K`v@%%|``b_l8GVHU`mTqtqjdiOyo+&d z3%784aksOa>y}yzB2mU8Hzg$r6!KYgp>|H&SPdpzuS@*Ql4(M*_-p8gSLFNhag8~| z_RLJVvyGyD`5UrBQa&Bz2O(TX$yII}keZt+Z38iezpTt?)IY8wQGaQ;@0~h5Fz8=yXR&h;5get>O#tANN_q{RXCIJ!t+>1xU-am_iRVzm7C}H=z3tY^f^Cb=SaC^d%x1TGUa*A|4qq&d=Ws; zZjIAVcYqa=8`eA9(p!23frngn^?lYhWD%$o7|ZODPj_fwX5OpoL+L-RJmHa_9j8}M z1ETM*$P^1Vxw`r?EOsYwb-t$}9M{idN^>)`7Z?11T2WKY`*Bb{5>1lfE1?_(TYidf z*d!>%pl%jfk!@y1%@$u%{2pUme-q{81m$W>MFwkN-JZ#oCQ?y*NLadwM^q6uac3}* z0#Lhdjo|EbZxGMYL^|n%M8_azUr@Xz&&TKrA|007X^U`USas?6MwjV{lDV;!7s^^G zUb0TE*DuWpFCV-;(QlDiXX7#HV8(}Vo*D!M3OUGRY;OzT&XNm-RgUFQS*7IA%fv2P zd`Xz$HK}$C2%CsZo0>bgzHiA#1vd}*s%DL6KN+&r4(r?wwh z^R&x{x3kb;VcOBJ%nE+%J6vvaGzvM1RcAgxBbp!PWYAq#Z8nlmL?8!VQfCX^HwDp0 z7+zqmt9~aT(5i{y&pXVvG$z|6SeASqL0o@79;skDp)n`XBB~PWHM(fCx)`1oLiL02 zf^xcLoj@6j#XuusUa~MvcSd-27Xnt*UGJx#3UKi#*{`OROF$f4b>4iV01(Nd85B^t z4Zre??AHpPpKHP@OJiMO@(^uJ(CWLd?`B>;0$&iTm1c~%-Nl5}_uiQ3q9&YP2_KiQ zu$5%akm~6xEh!i;2@}3ou9l*EQz(?WRv0@~Zlm@z&67-H1$oWAvJttk$34Hus|RRJ zw}Cg(A@Au_)jU`i2m`OkZE6KB8s~BJr14~tscJ!w)go}t6NHs!iHI3EPt4YxJOjF9 zGH#O}5;gfdD%}_B>4UZCXqP%a-TWXBV6+UYDRklxqzD7Nu(qwi6;JTs?(om*ftEiw zzBNv8TBK!PYy4vRCOTR+#MD8EE0iJQP~f46DLU3w8T&0ueP0;Q!g& z2l;&@UXu_wP=}<2gx91r$sHi$@p$mTd^EoHm;A)%BNp#@ban zLemnXI-1q52#QLhOH6!7)HR*izDc>%)=D%h<1O+YJ`il9e{CNCqbLL8i7?X@ueMSEdFwuC0B3G!bdLp~XN&Bk#i)1f4zkg@i7IU-Bls<6)A6dw-!PI1{t9+WZK z($zwcvTkTC%vgR1+Q$UgqQf8W3Lqu5rZp;%&K@d~g9I)x;eB0U5|CN!C>QFSWeFZ? z`ONhAO5#czTIRKP_G7IE>0KifHmK9#mYnH>A!{!;R%oYFefRGGk1Gc$Ib^%q%bXli z5A|aer>>i9gf#T=t8*REf|%KI6F=g$lxtBwZyao;`NSh!Iyvn3R~~w}ThtXNX5=yG z=+zwP=aGGthqMZztSlrpu!D#!J@_i<1pshFc{(OX|_r_cygoEHk}A}uSwFO1>GNw zjb)U7tW&r}WtSVH`5O`Lc!uLMV&IjZQqW^&Gl+5tA^=|ep(m#3pY3jFD6|~n?*TAS z9M?4f$jkoLOY{$s!T*b`&>=(aadeDU6?v>mq&aAX`X?tq65_QsK5 zmQ1v+>Y=M>C6?p*PNh6?fiF6|eM_wrB`V2ELxUYYIRfC>HM~w3p-Z~n95y!=i}8hw zKyYFT{}2EO=jKI*mlpKpCcbfQsyx zQDC|A%Ugj9hfKECVtwxBP%?@S8q~?--4l;;Dh^Cq7H9p(z|xRfwD6gkQ9Go}{&+S+ z>}+rSc;#JKlO)DhWv=5jd4YOKpL&}(R2~G4`s|OdS;+Y3v|D2F_ZyBt-uwxaq&?^| z)ykt}_gBJo2oFDDQm&SF`b+V(o6kdDGtfBlCgKqrFQOfVw%Bb|3V!p9;d%q$$&MRP zLX&$Zs4;IzV&9Es*SdfecOH}=*h+o0)iKrjZnyd6rk}78!J(1`*V(~;(@gkoxoVIRHW$L-g3;LnG3AIxP5EUZiz`V33x->j@{-bnZ zYRYV6Y2sH}*xpI@t=inuOYb6vnuDOO2K+-$S!Vh<`Mmv!Y(|}uu?KqoC%KtLbrS|< zm3Rkzs|4R^UrAq&9>b>|P!PQ9XL^9{dT!&WCe+}Rg#1Hrq5l`H%i%Md$KQ(v1>^43 z&na2$s-0!*E}JdRm)*q^@JszK{ahLu^ruGYnm_eV*WK9i{IlfDh!e%dyH#2KImH=L{jDmTtsi)M&;eVcg z>71K8`fDg5P5uOIpz$X^F;elr#Wf6ZBz(vEdh{oZpV=v!6Vu7Y9fAH6^Wn}m|RqfxiFzgzl;6 zsPyfh$4wX`{K_69Srx$H;i=W#ny)$6uZWs>GbT-w?~Wdxr%6Qd6Q&$;pthIM3hLhv zg{i{KN}Ye{L=_RTJA=PdoV2@^Xm&!uF4oyE04>#deD5_0SE+yX$^ zENYflgg#sz0*ff_?%SlINaXyT_9>>CDBs4{jivE`jFEe2I9fC}uEI(RwyyQsTmd<6 zAm)a^n=rKzPj#l@_W`1yXPpX%VAE*6OrDFmd}CRz0E)>moO5tb>OTHWI1yfzFm3+m zRs4@kb%(a13eVfDH^t~|CrHOwrtq}N&z>9kWEk(%OwOa%6x`WSLt1;{G=7t;qm$-8 z342V)(rB~=zE0BYB!K;@RbFeJUr=F`X!an)Q67qexo8>m;=+PRG)uLcCeR<;Z*)ur zyra*He3D;y{)oLC1eD-g#`~Q}EVOWdQ^+dKx@YH5ktCyH*V)a_*m_!xb(wa*&>w zRIL@%z(2rYF9fE~l|F4jD^2y7*JqDeS$1zcGyO?^!q~g{icWOJ{)A~U?ksvszL0ea z63{j4p->I&f5B4r4kxKy_)}&RDuSbV5>ZQ;v0C@80^DNzI3A7#o7XpNnc(efpKtOJ z$i%a)`-%*}fK~096Tf((_ObD=uaAmPU78t2v9>O)hAB1Z!OKTpHnYVeV$h;wp$*H zEYFh?r>lURdxZe&a!?auJO5&o%ykKUIR@vhq7wLA`*QR7*XX&-k&S_WSXqsqnew0G z&gkioV`$?J)2iOa@4BN~=7_5EvO?=W=srW^tsK{HiD83gk}RD`z#U%Uky~5v zdb(JxBd63|*sx+Bp;%gJ)o>Fbo*t5Ro{%K-URZ5;e<%v&ZumsIW6g>SsHIso>AVTS znd0&PG@d4mOcvsZmKFQE^uJ8FCkuZ+D>-)t{a=5$rytL^ zyYuP(m>$%$uyDEvA8gTVLz=F-St6}GJZdX!p#V~JqJUt~1t5#d{Y}v3i5#;*TR>7xFy$yTv{3wyPpojd%5v%@zDn<7a?-=WZh1oFOM~sD(&amI zRqwV^SR_`Z>f0E5<=)II-mA|9dKXW>e} zoz1qWY@D`@_(0p1wJZ}YLH$}90DWpa8LSbB zQj`)Rr>W~^y7XnjI7~Ly;_RZ5-=~1SgXMhX2|I?C_5Zc^ol#9TTiYO2qzcjkBGS7Q z>4J(#C-ja;k=}b#5CkEih8_eY^eTY>2~E0yG?CsxA@m|h6?~)kJjdsp^RDl#_x;xT zz8}w@?3KAQd*;4o&&*zvJ=f-ae{DcK9EYvi-GFSD5=pkG>lp5H$*PdgO(sf>n(_7A z=D9L7Yhvfw7p8Ule$CzyJF0JA{_Wgsd(kkF7A`Z5O6lNdSirrVV39B?B<48B0;8yq z;Xp`t(evL!ieCC`%&YW{NFr^j#Wl6znZT3qq2;Mu$llPzs`MlEDgjH`O?3ed!xVVd zK{;h8!$9oVuv(~PRDNkPG={s=VtB+cQAdFnQgcI&#nQ`&W2Ub{6cBdkPxMNnRB>4z zbBlm7q%#?xMXlT(YPXhkP(Ajj@LixV0*CRQ$IGK9w(yNhb1s+={=VACH*h;CFt8%J z-MfTZl6L5_dK{>D=nto-ZA zVMANk#{1ZL^XOKGTo@@NBYLqnT#4b)DqVx(yKx%Z>oH%jNbwi2r)-p!r{%aT_ETLG z$8?6eaoOn}04+0z2IoJmtTt}HuXkl{ahs;y@i0pggf={^$qXgF`mu&a(_FY~&Cq(! zJBcK^1tv7#Nh0b-ua?oUHnPCLZAq~p9>ZeNl`9hd7;}N8l20&|-gzaBHM`CDl@s7O zYX9zxGes+@c_>j~`)&*$5NEBJk&z^V=HDZ5wN>0bg}V828=Rikj4s3$psBhh2{Nu8s*sfnWgC^=#KI>qNHLnn8ML}i zE?C+DwaG2DGErOB!Z9SDpf-Fh*q7F^DP8WJkk?+bJnQJ~Zk&*D9U1`^z8evNp$+*B zMwwr((0||({kNAMyR^??vUM^bdn$}m6T1})8?l~Noj)S}YN50^fqK!cJ}S{i|Bmz7 z;+*-xcsjY`-B8LGj&HT0m7-!VK13jAOoek^^ltKDXzVUGMn{O=7#|PT3sJ33-&J+N zN})@@D`_iS@3VyEIl}A6nUkVjvI^BK4l>MgZqyuz&GgE?@JEF+YXehDHcq&^=dSF` zj~9BXwg6xOMN^2Oq2+Ycp;sy4EWA>H@HqXJ-ebX`MT*6JT`3zCO&vp^)O%wJPk(?q zz?C>G)5FVPI(X)^uv3t}@5ZN3~Uxc+mxv0$IY>on&+4Byy z`#^Pp#xqd!@X3HOu<c`Bv=%5!rcjD<#AZHxBehN+X5HjM*79(a{!P@PY`$ zT$eq5Pn9_^%F)FaH$=q<6CPyw$i>D0$D0Bz9Ng-w+neZ%%xkwNhHE`kIoRJI#boX_ zXybb96qdK2<03eCE{C9k=JocdalFaUI?3Du_f^U~>)g|`&y3Lw|UX5Pz0UCY|qCpJgqI+!eu@1ITa3 zF1!PV>T^RW?+8<;RI#^7gz8R92UW^8Z;;vJ48Y5Yg-Mf&?0ZbIn zu;VtaVdPPixHl89z1X@94YF3=>pq(fj<679aKRh1hedC1r zQI8#+CQhhls@%q+>$uPKTws)Rrb8IGjy@4)4i)qcfF5l>I0LW!{CqWC3{+3w4mx8q zDN6X$50%gC65t-7vn2O+FK-8=th=odb=j)mGwR|3!;O4~0n*CL(-h)0>0hp^jct1* zJypM!xC`yM6KGGQWEIg#YchrpAtc*VL-}mYY(5eTcFlsS3xht4=;+FbAEjG9wGx@k zsc$oLO5yTFiQ$qQd_?kJ=b$8!PH0U3i1NwOUy-2%CAx`cGIr$UXgP_k%daM9#$>MB zG`;g_d1(Bm1>L#Mb8Dg^!XBARg-Fs?;B6J>njrU>q2oi{_`+lZ_b1wsu5DB2op1%& zr|}5-G-+QSlH_wNA&u{=&ja1)TqUXUe!Rx#>YZiN#3ydos!=LqX6nZmh=oM(Atr<0u|(qETh_bBX})qTf=}l~ zn-BMfC_{_6vcsxx?xr!c0t!7$z~$EqxNIEnr{LtYGdYlL%qk@$O zCcF|eL;8>7MBf!ZXFx#Yt!0FCebYRmY+=z0tpWV zO`U~EeRSD<=np&%s?bnk%j+-i1SAOp`;O8@MIPV8#19zV&KRq&bzYjQcb^fEUGR`x zoZt_K6cB}Tnc1-h(TawR@;MmoS@@*!<2WkW&OM%YVjK*hvYI7%0xD0gX9(wnWqUWs zUXHPpVvdWIf!QEQs^y~lVbW9SCJ_CcHv>mZq~c3@nEym0M>MqM<;_&6Mp6UW8!E*b zm!|Ia@j0j`KO{jh_@o$m241x}_RzB}$@8{qcZ-{}V|&V{M~5eTxh zwV#_HQRzPDw8SU$gI`DJ?ob@W7DFx;4hu&NCpFWKnf?IAZt%VmGE2=cT)h)tr(d00 zBN}%lx)%HC)HzQ#Qhfd`pc&pEOws-+Cnp(7LsWzz#0pGJi13XX7E>{@vi@-Rbecg? z6o5ErxM^XKc`b#lur-%_INH(_iZ-)8V=nW_^|Gq~Y&Z2|A&$#67FdMz2o&`7MRrBS z*IR5fXRRe7=dHq1Hr2fTk=Au4EL+Czv=NQzE9h`WgWOgRjPDQ>wQs9zqel0!s!ubmP+YHEJR})0L zQo?v!j@!D>)it1EYzUtkpJ^8ocoqqjA>VuN zrmQK-Vpj1TYxj#HYd&)g)cxJ;ktIF^@8v2z{^=dG>uQn8$j5Y8wOseR+(u+(?D_|BcFz~KeVVHadI<< z8{~%P>Ik;?EQbs^in&bt@e}PcJjz#=X0P#uqE)EK0_dkq;)dkk)K@=y_Wl*G_2FZx zYA;<~k8aJ|YFyA_qI&>u7kZNqw4`G;c4Bxnms$^bz>)1CUpWG6wg*qL?(UfA@uX0; ziJz1`%$=-PTQyouJ~W#x$aU3Cad7o7Mp?J4RVdT$tVxR|6Rkz$r!CS9-x+X=NDlU% z_t=t3fe8#CX^4-8brKhidU`EFvRMAJW5nPdny6{U*|VDU@fiTY zQ6d@>Vi_}gi|Oop^-X~ql=37Aelv42T$L_T_2>@l|J~!;h3#LGDWk@Ad)7op)GEm* z^^sMIBbKleFB0{tGPlBUFecvZ_vRV;fcO&bb!j`gngcpxsBIw~y#dzOs4~kkmP`}= z(cAU0Ic{BxBtx!#!QJ||HFA@=mau4A^hNv?sYxF@biOqDr0wXFaoTtCm$y)Bg2ne~ z?Kp&JHq;4&7_~*S`x0nNsAOAL%dfY7z)&dUfr$eaxs=5aZ#S-i+8{W zB``nsq!hBZSK>nB0-K^dw*Zb|Z4V;C3-nhemM@u-%ijeYf! zxy;1_$&7$CvFVN9ks3Wo${YOH0;y0oqAQ$l`xKI?FSrE91Rn72i;z3_N#`h0v&LH} z&lLUx-&t<{?&9l-^RYXK&~Hxj@qXET(%J0?=|#7*Va*0CsbiC$d=E}_aMp%DNvII? zUh_;ch+(>>tHx4&kEdmX4i1R;Jgq$OmtJU>Y1r>uhscl#efFx#NSzXf@2F~cZ^A@WH@N?>D zU(6`u$*w$~2g6KFip@*-=!&EKpZHy4uejw*t)WDnuDJ8G{+$K%X!#|W!i({z+#UO*|aj zz2QJtqJp1~n0W2alFFZO(fkEsTIt3@^gvHLTX%kf@G(t2?ZI%}5Aa@)kMf~RJGn}v z+h`!##j9&{ok@JoKl@^;i>jf00*`W>wv?3|pT=$39zeu`sMv?dBC5wm;iY0Yeg_## zPj84DfMY=+_usYOItkQD8XnQyH0O5!dr>r&lREG2VwHGfwD?}UGu4ej4OgF(TRo-s z^V2VfaD$FTldfJ#kzOOM?%p$Yy=+?rMDR{jb~4Ja_Z+?xpmUtI>?@2f^@P$lqi;#6 zL^v4XwN#gjLG<4L{%YEepa;WN-pNd`=-8R%r5|yw3^Wm4?`n#q-+h)jdgFBM{E?)E{%PWpQ%^AWznby9bXNa&IE!B zWWPnqZW+EO753rG_cC`gKvKB2b|h-gun#?3b})lf7;-NbKE3kj=+gv#13%P{S^<&0J=S z;utT^8R?iri&P*}R(&{8?C+}O6UnPv+GXy4bVI}sz|k~pRy=f~kX-FQ*)ogDLzG~$2Olnt$(rcOOnbdo?e@}a4g3LfWIO^{9ZiC_#V^Y6d7EQVzwaNL)w_<%G6{ZI z*IV49MdO=KeZsz$5mR4kVHn)C;D61eR|R-4{>aLTuYJX$l)T==`d(uU+HQ$pk-F`7 z>$|Vmx&XoaQHpxo1yYSsy?7tC;obxq$w-b=^IdHP@ za=4zPnAYa!#f9>k*W+HcPW#{q*y(nU%iG`gQk@y1#=dL#_nIrZxvcVG9<2FEL!En? zJQ+pPb&+oTfH3I#^Sl}y#oD`nSpf3Jvqg`l=N!U9PWCK@{P?4z%fxVkB*eH)im`O{ zwB#eSfGSd=aX>&$d~$j}BTw?6hl*|}Ylw@7d!-w1$_tAT&0(F7idNRGt=y58mM;W67D+8l=;z18)5-50 z%(ggREJ}X>dIDl^l4vPoJ*E1w%mg02fw6}MTz$sAh8?X>=Y)g(9qW^)vsdkw!*kiL zYl&k72#H*Y0$-N6Hzf=UJ6BcYKA}()ipy8dg2U)1V0NXY8BbVs_Y<_$XIy*VJv^8O zBfu1H4bCQwvUq;>NeM#S%E<~M;f5@>ghvPPD*8Y zIev*!S>yUei*t_!re)78i6*Y&ffqq(LxIajrEl0id^LDx0Q@Bkp4Ixacl@bGdoPRd zJC<jU%7zq$5%Xhn=?ihk{A6a1 zlI9<<8Vc)C5G>Ml!&K2lEzokJ0S(Z&)m28vJ?(xhp&nVsmOX9#qc~?l=@np1f*NHcQTti;r-X%3Z zipHy6OOaC@t0$BSS4$TtjnYk)|0trg=nvkwtGy(tUeQ1NC4L7%Sg>X zvXD&bsR8wfa#AS_Kpnf@!*1=)>HJ;1V+4n6s7;rb?I;Q{|Pva zke8X(+uT3r-cyQO?jpGOJh(}a>TSCi_Vnt4#7y@p3i7yr-c!%l{*fhU`~^NNY11XI zM`i>PLHbMrH~KRi00we$tC;JDDQKMKD1p=yo|e#bePzVtt{tZ?Rvh5rlJ|jdOLlGH zevwx&WNa=FqUzB!{S1n!lzJ{5uQ{sV%5KG#2&?bD#%Sep$?s-PY0SK8m-au^N{h?a z0&TPEc*Rd&zNjnAteSUiR_J#>1>R+e?Uerw^dcs+L z(UHuHx_@^E1Nh(Q{B1k`-^?rxG`H`Y4est9EV#Q%aM$40xJw{`1otLDaJR-?g9Z=omf-Hek|1A^z4!aR z=iYJd9e0fL1-XMXKyo0-r}qc` z1DkUm2*iH{0>Q@r`ZE@$9l19AXafha)=ASaLohz&?_g4jVE zAl}DikQ4~^*|Vn|*x-O29tj>E4h|j}5fK3i6&V#31sMee4ILX34IK*|1qBly6AK3y z4-XF&gMbhpmk=8l5BI4M7+ByQIQZxA@XvA4P|$GyKW~p+AS@&pC>#hDh8pw?3kDVo z=J5-N45)5Ev?opcXM;g_1_uj|_#6qi{0b8U1Bm?$c=j_`I0OWgr|iHhU?E^r;cy`0 zN|;frYq%oeadHLGB;ZR1KR2(Lq@~kzgX9uOYiW0%@$gx=zfb&_l$SpZltv2kukBwC zdnye9@#$GYSRj~Zu<-DRKpg=hJ>`JGdd49Ei$kTJ3x|y>sR7T)W#*bNsTov5ZGMK} z7TnFfj(CnImG`&=LWKnc!-B;EiGh9mp(vNw z@PR*j5(0x*y=4JMvDAG}*JN;%;HaZl&UlDsIKp!wmhHv&AYF4TQpmy^lj#7VvyAfh z{|@JD^mg%gE&(amfLhmWfkV02jzthkLLj-^_45xVD7VSyRr)?QWx|}(g7n`l%3HxC z(U;E*&p$~#w{-Lo2;a@fmf9=L!hc|CKxpf*JLK8_Wut7IKkF^R)&25yG#QrnbXsTtqEDQLF{|=7g&e3OZcv>Z zZaNdHW2ADC&e=fL=A?ev8<*T{9osUQjvw3+|d4=khxmb9DaMI#fSRiR>7%a^H zpCu_W-r|V9oYx2Ue`?U`h{Jl$Odo#i-{dy;OaPWN!+VfIc)Fc?qOtAUr@9p;R<-Yy zH?0xNgq2Chp$M|a#i7lX+2Ih?oG!z+QB^3X)|bbI_W5b+65Om-7Iq6Jx8&XLHYUfW zCIl`icbkn7l4l|Nui-Cwx~m|_J0m9MuwhPk6lbp+7k-Nl5158Ie}83eH{@-uqtkDj z)#-lseF@W`_g<>;II{s|aWTJZZ!i9|E21@W?-02{Xt;r;wN7%uqiE$QVC{9Vb62P6pQdkR1~? zpZPcKYFFg@!rS7DBDVYs-({;YrnB!lw!Oc>e}Boo4<$)b7EUP((bkU~Qi2%mvd2ux zOAML1b~(cxd&z@rt`+=iyg0oIybdC0ZS%4r!r!7^SsJp{lGNv>deyO3 zalbl5i?Pc}-Z8k}i5l=AMr!%JRX3n0aMl&f)ms|zm{L{M=~3D?bBOaIm0cs92Tga| z!3Um((o5h_$K=-3iivf1M)3GG$xa1JgoAC1X&Gnelc8X;ZI1^ zOG4WsaA=wKs{5ky@@k8av+9LW&P8r*yf+whZ;e?Uyp}${EUwhX(@jI(FO0cj@8GTH zWlL94+1c72{auNH)rtJk+y5qn`xkHIcwv3k$CBpO#?aIv#AL0hPu`Mq>|tWALoaDW zE);LlXLr)N9kN<0`l`Q(Dfzmus1WzZ>GH>eu4v)$g=PqTTWZ&@pEvx(2vviNZzmPx zd#6G=g+wFLbN5UJo7(cNf~ETp@{lvwzD%LD`fu1P_8yKZX^m4q!O9!S_9cf*u=a+u z);`fP33ntnj_w!79ov46H(ikiF;e+uDxjsphs`Xeb@{8up zJDaSBP13c-ZpQ&+t2XJhj9+hOM?xbC+SKg8o~*|o9h#@VP{pf> zj7OoZ7Z8Bl#Np0Yy(5#9Ha=GteI4FaGhP(xlU?@kcv(^^LU0r)Ewm)c0#bUn<%SnJ zP_lQ0>kNSi&T-^cjvAw0E+RI5DgI`E{ zr??#R61e9xUnt`D3N4yAW%)68n#srP;I%y-LGK%f1Gs-%=W`s`S$&>)U6gDa?K-6P z^E-=w{)*HGM|Z-B4Y|uD7k1EIdPPLYw_K;0*5E`%yThcN&-a2%$i7V00NykdWMF6x zSnX`~rMfu05xug^y$87!UhpedIdM(g;Iq~4B1c$}8l!BV~vn?SeY zrxmtDAAV>}Gv<;}C_N##{gVAjSEE~wgY8z(rB#;;c4Eh7>%K#&QC(m6S^msR!h&r)B6lQCxWgg=hsF6bK|UzdY{^z z!@m43A8kJg%?h#D_wFsas$OIv&z^Zhx{dSKBAr#!Ja|-jDrIT%J~0*OU@vZ-&H_{H z=b+eA`Ta;^g2H1jrWf?dv8yV>w%KZHmhJ}ZDB&QSYZw_lTdV`G!_eAFk^tH(ZBAZd zAHsdhgw?dUXh>ien^kUL{lMFr6~{!8$v#76I<9Zuj=@U?@(+gyt9)#-$aqN7DQpWj+qJ&I_HrE1K(TJdYNhY+DDLKK&!@TmG|)ZYh50An$prGLpZgD@|Y=+F2|4p z$R|NVH>%v=Z0jc)eEZ34p5N2z-iixv^n5y1E{c%UmMC5_Na;E%YeN6XhQRCM+ zOxl>j*nWy;L@`7X&j(Rs>eMZ@<-PpiWjtfuRF8xK)VNrEj+v5+-OxsAgKJsY-C(IS z;zO_eGZHy_XbaSdx7BVZt(AD6{Fk72%c^MBg10F3I@UgFJyz!UE<6~V-MjN~-s4%Q z*Q`N_A*eVudCHPU+l`zazX182i@(Mm>^zkg}mng_{(7FDaN9i)tCPouNB*!I&{7$6Xf z5WH!T9uJx%tD*YRQ1H%3d<@a8lXdF$9D0Q}Ht%WG6S=rUBtC4YOk-Kv#H$z?Val<> zz|JdQ=n>7qEloNnNWE)S1qv)Q!Fhe_+dQ%2zSq!$dFh9vqPbXNmF4GSnQT&_;B?cWV9Y#(3N}F4Z9@Z#KShPw47V7Td_-mX z9__W>vm?gj2chx{^biS}5vheWJtG(_F%Q#sYh7!k)GTa;S`l$V=X`ySAVLR&C3`fE z;`)s1u*)ycjtyjt{PQrr_6R5$td7RRE^K#>*WQ}>ci4-dftwEm{gAh!6{|0 zWPeom-;WKlE|2-|*5(~%_jJUc(`YIO&SZ{^u z!H>E|S|ipcHwzRaMhC-?ue~HcXXq0A><-=SoLHr4lA z-93|SNo5OqPgfiq;7cC%%8L7(IJBlN89m>EwqA&IE&e&tHF8_!omi7kkeqX5YhGid zzo%YhenpcOAw%&e+1neQ-kl*!J2(XGnfY)h0rv=jv856xVaA3E?<#&3SH-!85X;6L5?&-EnEnvg6ed?^I z2qN1&twj2L?OyCdp|9l>tsT_IxpJd|&iPwqlaFl~<_Y0qKI7%nPbSSb=TyFFWJ)zp z4i1*6l zn;1t=XR$vKs@@^lCSEAvL5vjmg>^w*Rb@$WGFAt@`se5jQtamlvI)UbL4(Z5*~A{l zbjr%Y!F69X*_G6B%!Wc4kiNRLA%=EByQUAM`Z)(I&?MFh->bFOoTjbIv05;jl_W!4 z<`vLP#@gjr`N7ezD5Lou*P=5flDH zc(2dc?v2Ot(!lT>0WYNrpBP19{_H@|VNcb9@XH3hmu@B`n#P9i$xwqKd;4v>m&x+x z5nn>J83 zTqK{O&LMN1b>UY7<}SX*kolaZsinP*g8br0NHSF;znQ58v8Xuuvju&|e>xlRNWP3C zm4AC7AwBXjiF-=DrT>K;>0BFn|C$>TP0)SE!9|pO8FDMRNS(#v^>3s`M<`q@Qc)D0 z)!Si;Y-Ta|R>@Yr0oJ**9e&f;0;5OJJy=!kfuPKzF}8kkkEBl%>}Z`^{=+?d3nHN4 zo;sLZlpN=R23x}q7#?KFBoAygTZMg&Xz^i@oV4fI9C zOLAkg@K>nFjW>MF7N?een!Tw2yE53pzy*)CSDDKc)JLG6P_5~zBHT$;w9bsGRdk$iEfhr`P~jW3o-YkoZW5N)R>ZF}z}yMyB;BLz*G!2mo*Fx4A}U3505^Knvd zA|qnitk9pYb<3#AAfKd<2~uF}{)^H9#>^WQ6q8aZBtjDjI~ zcz&$PJl|@O)6rOYI(VC>=XmQyrZHtF^y?i5hfZ8W zzLi`l_XS?;pE-xcP=D))boySr+_EDO_S)=-mn>cF3T2lpL^F0ZbP*zL*mevN!| z+h39|UAGK{n3<5;8)0;zcs+ttU3O_R{L4UNTF^%O`|atqy+-`VssP;YDa&oNS-xd> zpBm_^J!1`0_S1Ncmf8B%qQY7I*+oT*zM}6&`+09$xy<;rA}e`(Oml)%=2<6dqf|cQ zQ1R(PU-G{37WTT)k=8i&1LP&;sXTn$NB?#@<9D6D<&?CMO5BfQ)d!l3>;#Pm)kjc! z``+p=uHT04zd9&pPmh{Q8t}_fQ@AvXOJ8KdlG^ryd+(-Oy2Oz7chRg85v$Q ze^x5)E{l=_bfavnv2Zj&SnMYlgW_h&D0r#O-pJtrYEH5iT!E}+s7nGB&;W!Xcz?eduEGg1$&fu zw540kAnb?QPr-{>q6 z+?b$_neAZ;znYB_x<`= zx>wVDJnF3*gfGTPVe3z?c4NOC+T2N%ihVKByijx==6D26iMnr$tvt-FJX8w(u3W@% z1(Y**Wr_OprKU=nvQVvlg`1XCUjEEdt+i2%Y(_z`2P=I--WtV@DnHyYi4eMq|8F)v z`xtYUm=a?1=#}VYG>jc7+c=7<(mJx+ez7ETvYD+HUQde2@2>;CE%+>YzgPfC&8uXP z(=WNw;xaT;EiSdENv+^<=2_3uA0T;0qH^ax*zeLpGeexT6l|cou&-}tE5t?)XxVax zN8^_Jz+2Ci9Ni~D-+SZ7-b^YJ0Jce&Hcuzbe0{7?&Qx)AF|(hhXg~PwVy0(W7AHXHp8;*Pm4OagrB~>6kV#$P zC6ic^4*&>88}+6$(mK&AXQLzdbdTX{`@Pqf0q3Qmamk5z6n+CR@I4%eoc%tVJg^se zF4Fkr6Ka2`x9*Hp9!l@@@)#<)O*$wWD~bH6stg~S>~GPFY7yqbv#ltP;$~- za+=m?Kk~to*bkDGh2K8)mAQ9R0&yL_O?I?5V{3C&Kf$nbrS%ONzuv6LuNQ5B*-nQI z?mx`S@E$>Wt$jR7m!l+Xj`6&LFs|@bHWUP!)`;%?A|f+72WS>(te-CdG%xZ^Q`S;x zK|prz2YK~&&TlUoOe!@1_KsRPU_1JjG(Az^#=3T>AiO#0hxg1QXoE_gi&%?8x>W=(Od99+}JqeDXXM_1>8NEgk_RDuOF~iD$WYyXT(Xvf@$5<52 zrKat>43CMybQ^7&E{_%yMK)M4_Vi8J$09 zZ$t|;Ie;E2rz}$~Eoy1M%NH$!^F%o0=!P!vha{cEd3oK#Jb6ujxyHS=8ddk5tyy6` zmnkl#D@ZNp(MD+Jo^H+#M>xK{jx&n>&8+yhLGeGdYwZ``&b4wF6t~%@zVzh&p;v0w zSX0S9k36xf=+nhM2zxe*^0Zp6<%X+3f0)Emd0P7gKYh#c@%)(k%hNhKm@>r_l$CHy z?4TqjE2)$YTGU%8md-@pe{0rk3vSz!LAoR~ z#o_sZ*$gywneducZP3U%6dgI_G}YPZp#(^ZXAW7E{ z&cnD2m{B43*yNQg7op!}#LR!-NYgUr+b;lv?N)cv=INO{Qa)QSv(e`(t53J$C(2K{ zV*Fs+1>Mou6bs+zrJvN{)6c|k$vRmZhra(oRnPT=ZxsD_e@ixwNWydHW|AFvG^Oxr zQVTj~((s1UFhn%7pu}?24(eVBlcoP9rVFh&P+&KD-L2Pa_T>z=#*jr}?9_yp5xgy% zH^sG~o50{nd5L!0{YY_lCH8+McuYb&wQ0c}1~EKxs@nL%4g7$T#r_;=jLFIhYwZDF z*IT7!<%ztMqw52_rnHkOFYk}A=e%Z%o6&w{fyha0^MF(2>R#e{JwU%T`n^ria^{U< zpZ0g{KDV4u6nNT8w}#N7f_a2AH`On&ymvn2Ekug`9Z<**ZB?F72#JTIYiPYLqM!yx zJ>m78!0yZ@@>oup_iq4zjs6Rk**Uk48Oj*_*pK9A!)nPx=5RxI=(>4=GJ#70#SuWM zrsYILQ}auz;@iGzki?sY0bbK=HM_c}$^8SUeDx2L6DBJ<1mt`>ncnG{1yDKc29fXD zoHwefc%cwO@X|D^YbBb87@qs+$p0nylSgl(NVpU^lpSO#v*>?>tT> z)9Q!5l!9}BXj*>me;=qPj_Xq;6247b#);v0ZvE?&`p7-zN@m<8|-hLB?@ zNr}GV2pRJ+?HgSRLD-<>?4{+zakBfl&t#{1%0E*+O?!CU<2O&41OC%^4CYq$>-o6I zYz^LjwE1VAVlM^mO8XM6w<2?I6MVDJMk#gzjI*SEP&HYLz1FlAi)JZ%>DNIq(%zAg zUxLF?RK*7xXYm=Nc>X=scavYIM_kQ_-S;n&DNO1xe?DE5bTU^zgjpfuJ99GgvM?Yv zDLG!0B)kHC?AHA^pmv*`t#kREhtEsdu(j-{P1a+gnYwf{%A|7mD7yu#H|BCu^p*6yUqq+R_*A>k*f};^ zbxKv4OV7TTr#mzw@f@^T9Ed0|I9yBphqe%{)BKB6R%(80c$Lzc=Ig*+W2iSzSSDlZ zUhr?{IiEj6JPcO0yaF&OtAq3VOL;bl<8+3MPSfSMn&F1LrY9Oi@IKc2ACgw8UBdHG zCsJW;8rOY((1;BPLCd7!m3NwfPdPVaIcqMpOxXpT#~|=bq{WN> zifFhd?++y6Si~L5sHCYTH%vxuvmR@a&`&=pqt7X&LVw5$P@iiUFBa z`FHD*l0>D+0aweQ8g-O-)`SeJRpn2FKv=+R{x@RFKeTJ_SKUsfwP=@j9wOP=(iTr^ zdE#lj-!bLG>{eYS^qTIRpYSr|GVv7Rf%B+Y*te{Y=UYK@BK-!$0&c!trw8SX6;CVz z%_TwQ3BKMmR7UGCp0>8ytjNe_78ETl?&0Sh>bXl*fe6WOB)} z1sDfE!B^ei@Kx~_uns&2;LCHr!oR+dONMFI`}j2uYf-lH!1RmMCDxzv1BRxOJ3g$u0~3vr%`Cp(?-$Xo z`AfQS#3#zoY3+V8N|&gN=gO>|GOCYe{Q2XrKuF&6v|jBORjAxsPwr9ZOvGn+3}vh| z;KI+&L951)@Of;rPMDxnP|);pLRLTkUc}#~_Ae}8`7xwdI%SV$#R_foz3d~!o5cU= zC*g*H6YM*3{f3|PT0mX9#4|$&DzG+n={_%S#8ALTIf;b0PW4-qCC)6wQHCuAz*dg# zC8vfvfGQIGO%;tx+7H4{J!U2rO0643az>26QpQC8U{-5S*3UB9s%9Cs?1IQ-eBr4X z;?DK5lKb869sQ^kl{+AOt zN~us=l^&K|C^fdD*=YVbK`Q#hY_b4m(_G@bRO{~cL(mWMNphZ8a_Q_5#JQdUdlr-0 zwY5uht)sar+4Q!>O6!F1?lSYm|z`m~!6y{@0|7f*C?t$RJ7k{x(# z2_V;=q3_Iqx?ud`3z;0$sJM)gh<`ZZTp@n$9G8X>g=UfMesM_7RL^p}Pl%jcS#Rlz2utsLc!%*>1J8N-i>@eL<+nSO;uXB#iLt+9)Yy zRUgfTP zX(Kxho=XL~M_4p0`AU=UudETBM72XANA0@tBHOjf@TQR|lz5*EoS~PCXPZZE6^s@0 zfITBVr{LyK7KLleYgXmWiF+ zT8+lIQ$huoPTIu0w2B*?zuS@P0auc#bn6pcq8|cE1cKm~JRasP$^o-(Mu)lSs#8)j zPXsK)$2GI-jM+G9|6xDv&qKk&>P^>eSA{EB&F2$ zEO5vZ@cBVKa#tHw%2p$a_S*(+YKMfZGuTu6P{Fhoi67i2&7nvbguRWKKX(Bbd)Z;{ zbwIeUZ(bNs5Y({-7I#;93N@d!t6YTdp6y|1+I#A4t?>iU&(i+XMw&&5+dBZDtR>QPD774CLEduy)WdKkF>K@CC4A719SY2u(OF=_Un)6d+;!U9iCBbV)ZURX zww~k8gCH52l?9u$S(w^I7W=sP?q>d8%@PU@(9xz2s`21#T4u^CS0d%qmG;m!B0!K; zT-)bM^#CV>jbjImMg>rEFDH$LurMdTT+~BX!wViktvbu&@U}V>dz}ya`r$teD&eJi z%0gJDC6?`NOGq4wd_VCf#eE}`9*5h7ZGpSK(wQ}xU~B7CP$M}cA)Ee{sng`dG2=Bp zC4Wz%9m0}zLLn_o0$Vg>^;u-WI=zf`ySR`RAB87)&%b+%@lwN|dMF7gJXG80uQ3fq zk&jj|z+^R?kXX0EL806vDyg;?A~Qm@n0-&Pe!fT+8iP?EN3~_QuK^}Pr?JH;+2M@L_Z^S)1|gx zde>;@MorYO;?HW;qJpSU#uOr$sajtCOAy)4>B>5(0mH);;mc2>jX8l6A-yS=IgT+l z8qhNZl;)TX*|3K0Q5QIVFRDQA8S0`qZF^1Uh{oS#Wzg-znO-~*sr?Wx^fxn;_@Y>p z?qAKmTn?0g)2hf3iG3|+eoSik`aQR|`-&bMs>oy$q)O^Y+h&U zow1eBTCVFN67@n5Z`?cCz1qGGWw{>pNA&5{J=1sX-|vq~(*~Di z^Cur%ly}?jvXqKr(tlw)e?P7Z6gg{8{`~b#Cqq;{@h0hdS`+76ET(1xAEQP+osV>m zHCiGQY=}(MtYonS*T#;{M?=@lHHo!{WpAxjYxKe|3|Xs6!E=hZku@>;R?1T| zG6Vj>JjQ3a>*V#@v8N6vMB^;8an2USv|h0A`fhaE(?l@!P|VA9;9xb*f!P8w(i=93 zfoM~EYCnj$sTJD~Zf_@&{4=ahGl`4^}w81hQ)hpM2NaE-Nhh_+&KKh^xWi+EcUMwcphga0ZNKyQY&A%xH zXq<_RqOa5t@RhF&q}mx3shK=D$v&0<`x-TMA)pd!e>4S!z757a5;k6p0a=&it(sA) z)_*g-{Z)|~Ji6DcfL>>{xM0@M%(WghtY~p3NTE+8iwyRtAWe6k*6T_LXAfE!*Uk_#JJzQ&Ugw@zWl)9(-R+ zJkqsbu$tF-_?m5AgBwRzA2|@3G}^pZ<|*YET^B|qml%_7@Ky0V!!)qm>G;9QM*BNP z$eJB8#(1n#=gh%F?5do@Jq5nMsTrV{D@8@%O$?VI(WqGHCz&w5cLDewSDE;s&2Ol{ zGc&C@vFy*tQu$ty^6Rn0Sw)gc0wchYq#r16{!4?13 z#jUH~dVCA`hy!`|L0S^n^8f_^4u5G1J%Cx1($ds_E{S(zVGjN?-{GHi7A06%NPoes z9?KlkGwOsPNz6L3kB%De!o2j#2Zv)yP}Q+)bFrRO2wr2(!{drLOn#Krr!Woqrj^)@ zYeymEnDw+DhV2og*a!`6Nd8WqKl!yRSf82VHD!K6Y_G*zNy&}%ED2MWtF4fp<)`*{ zwr#?QQX%Tpurew+x+Rbgz+V=@FA$A#%nA={9FyauW+i_gtTIwrai1#M2h8F%_WXks zSn?N!^3dDY&czLTU`HO-z9v0FE7mg8u0Sha8X}Ph*3-QTCScT~aj2fI=Gw<5c;(e9 z|AG8VMCkoux7f~#oJe&=kSDJY*_`8o! z9;m(+T#E}QZ(A=Z0)>=k<#F0;e-mGPV?L0(H+1m$dYP7cPfyY0_O^C1pjWf&{$g>j zIBvH7$|g{xi5uf?Hunc;Ow0v}bX5@@U%DT@XnE?{Myz&A)QcYG?}a&YLw#*62wm zP&2=TY++$J+yKVK|Hp&?i-`SWH_zYw`461^i>|;tLGXVpV*-p(Jjk?8k5a9`8`vd@ zsHtHx7zgFN^m35Gp^U)y$sLv3FF`-OG%#nhCDMkQt#r6Tk8YmkmcAgx`02Jrl6Yk? zt3PX_BjK9-qsX$L)PdVor+H5L^?PPiRWy*_`gZZ{@HTkm`YsEtX9eC1Pyni2*(AWM>h|l%`FT#**OElcq z3s-BACBgW{EMqItfr6Jx@kr=1R974eOQNbU1bX{FJ@TnFIarC(0{Km z!_TzZh9%RPelA9RamR^hdGkoCTyqT1+4 z3an=JK7x3*mRQ#6BGyha{JENrc#qT0sbwN2*+<`I_4g?odz#$t?HdrverrEn`o0EI zFq)8b)QZ=Xi-|TRavq-tZqK+{-v#>&4B5@P z`o@GHPuRF(ia57n)mt>pW}}Exg>>cScgO{6n=A^6R!}BW3&j{P)Or|&mf{#@Q>VOV zoTpWMQGDvoK$9g3E^S(J;*E;&NZz;IDJV;?+$+Yg=nV(0MaQW^4(mHgif6R2w5An@ z^se8x#18Jpx6RGqC%hlNhfm5ofzmS;pOyoQc`6AVb6(3xIWdo!e^Ny$wYRWyjo1|! zD4_`^$kOTh^|Lt3PZ9b_0u8)R%EujhNB$5w53O^lQQYBgguaZ3B^x5^+-fznPW8N) zRdy@P{Q71x%$g4$GeGX<#^|_Q*Oe8E-AhyzEaBL&BxQjrG#Gx_54m{Y*deGOULI?! zn>9E*$ZysZz6u>m6v^|t#||pXEl?)J$o45 zsjXh@MRuZLeKT`>&$Sp;E#YimQe9D?vM`NkrQK(bg=KmVa5=zZ6Mm4EVE>ywQjth#I1GSi+6S?RhswJJttx#)-$PBn0==T9WS2>vg)_s%4382AvOxu> zAfW*=1$3ol+*dBTg9q|bZVbM`;ya1^p#(zrOe;VJXegRY0d;8s*X-zP8asOQuAq(Q zC>e5D_Bw+-sn^G<3LB$8gMuwUjmx zL4?apF*f|o84!77`8SW#p-$LpOi(||OAC$@U#4!<5$uR4G9M1>*E4!G&PFK4fGrl{ zq%DTRw8vae*9f-4YMSI9CQ{i=!B1fBja41(%HB2@>e1&}5e@fg6KH1sKWsf9=4g^%H4v-`?1szqZ-FrsUlw=jm6VGTgLAEHD@u*pc z2e0+=IWXh3N}bV=PPDX;C8;y8Yb7z=bY3}=%}GFO^g@G%M_?2Oy|gA{Ircv3Qxltp zN2dR)`n_dXPeRN>AkYq6jIbk+aY%iNmd#&qmHymX7ypJU5MWGPMMIWQuVj`gd`H(^=>fONJ=B zNmH*T&-qCqf%jp69+i5^=gp=Nx=!1I4GGEKi?3mkwsb6I zUYh49Rk}(ONTfexhe-Jdaw%>6tV+Y=r~Tekq{x{{FJ|S-gjDZ>KYm8Yc98eF#gAm; zDznA~88~ooE0coi*Fa7mUFF=%^s=qe)X{kPX-Xj#PL6?9?K_$?zW_A1v|3;|+kdfc zZHVm0!L!?%BP_OAWLk=%;vZ!BbyQ)_p(8eR+O;|Z!7}3Mq6AASq3O=4uQXLDV z*VZ~@ezJ!oEd%&BEqMLe#1-oH{ZIQZ(s@W5D)Au6mOb4#Bl4oDKDG}o<-;{)cJ{@e z?IO&otXMGTTBfI4C8sr}ak^|Nc6!6TfCarW{qq1p7bbCEVHRFbxuH+}DP(T=$F>dP zf3Y_DF@{0^U-)^YkH5BC#pET9@xGy)$w>|bzs}Sao1ZT_>ljUJxW-JUoi`WSwAbc= zmx5r-L^8_;wJZ`pv+{*?-CD!^?qH^0tzCB_`IBmU!MoXzbvIzaMu2u1@oTi1qG{ez z)2q80_;#YBymqz#pwOX=derBpg6jUInp7@7<1GO$* z_<81qv&RbvH5JfK&w=K$rKSe@bNgTYa?Nis+o-^)euB|d5A)hlG*v=E=*P{S{-Hw znzU5aUxtq$Iy0GBRvLvDqe3`!Q#uMKopg@B$Wu4T&DF2LZ?}rFB@OeutUPAP$Gj#V zLFX`flb#!n>O|B$80Ob+m~LL&3|zQOO-V?hm=#CF_kSBT?J^pS`U-_s7B7b424NGb z#{*F(pifBQA3gy+3i=>?X7;sreO;)G8Y)_V$^owl@Gc++@C)pq|BF&msr}=Sa4ndS z))n6`A{8-)f8c~y{6dN+fb{&8m8@jOHVm9gjXu!D@}!x$T0G@CKY7Sw1B@>E^oD<; zkvcdT()_|kL?m^AMTuL1ch+XL(N|S1@rdS}KrSt=@;57CAlC~w**Jak&hVkI|4-a# z^Izje#(&3+kyOWZ317pbKM>(GX6aV)};H>+oJ;naC!~Al--aa{H6n z*467a8rZrf^wr=7Xk3VA{S6wTLss&)Wg~PhgsFzU?oUZ;8Sz5##&>1rjV7e55OUEm zJzet}J^d!L#5@Jss3^S z{8N#P;y~|^7E*W)l29J{0i;MiJWN{lAM5^8!&E?D`;*Ec$p7aOULZ;+5&V6f)39%- zEHicGmS1o9qKivser%8`92>;u9}Y_!G;|_4*9=w`v9l_?nm@E6VY}cZafdR~MC*L+ zb=OB%y&{#PqCjPu1;}g_}YvoUx?$jlXIW#v~*+F|dsICw)U<-STFy z;jFnjw>98_uY6bvGdR&op`TVf@ExT+{wPCvGT_Jn4l5C^O3Mz3ad;!&zQpL!6h}^L zbf-~Lt3AUxT~byEt<{+IAfKQ=fSo~Q1BVi>#|u^APW@W$!@%$eg+``);agtT-Vm~7 z^ANOJPtiZfB4;Rx$&GQ1`7Ncl5}HzvmTQ=rpZMlCo%PJMqYO_EiS|2jjIRx$k3`D&#P+{-ABv;iGkG=Yu2Q`Lh`JV!o49t8_1 z8HQ}Kdws_Vi+g0uXP=W~1ItIzpsSY2KXl#m3+ws_Oq4#%qFM>j^n0g1 zUm}S!T6bGI#U&WA;u&5wRCQ_6>Zc2?Ox78~Ef>j{ua92~G_6X<$G{VIo_qU2$vX_+ z8Fa#Pt$5ExR+*wMoyjDaq!awqn^^b#*!RTL#= zDRxKtqBaZr`q+Yap1b9%?fRYN%~5ruvZeyj??+G-#eKfmIef8q=Q8)bu;wZock%*# zQnehN<#9w+!s1ewkvJPaKWt*i`CDZJ?vu4s1W;H?POcA`iF_c=$ zQf$6b#?6fsnx+=fl&Xfz0ZqfJ!Vaarx%J`-a;AkBNA=!<5tbKPp9MeH)IhK;ZVgsq zh1{Clca(n|!ppqkj&If9w&dVz+j0TRUFhoa)>LaAAPb>3jW~D@4=T&>)Lgknrfv|8 zEaH0$*SuV!{cW+!qA}`u@ zH-ZyoZ&%kk!|?QR_|YsU$5T^TdrLTDA8%TmD|j+ z%9S212~P6NJ%YS)YnIko9g^mmk~O<}b`k0YPO0+QC{eQ%U}S!1>wAnQ+@$wy{GayT zGAPcjSsR@M2qaiYaMuvrVbFx&?k)*oaA$A`8r(g&1b3GKg1fs6gy2qa0t9kylIMNi zXTRV6YFC{fr|MMgUkt<4)S_3fYhB%ab>F^|rti!gJ79%TlQm%P35Xr~dK9w2Itvhi zgk(H-{2r{2{6k|U{Yz%88Ou|F*R8KFCp#`-^x7}_5ETjx`VGTkBod64>kfOFurmle zgpb7!R>)Rq-?;b`o7OsVM>s~L6Bd8?La3?38P5bw<`$o@C93h8tI5{le~-(kP=qDj z{}~e9QYC&wJX^C4ChfwRGS@T9Khx!$G$%T!>c&y+NPo=i7(H2MCfR3*+PApo*S6u3 zzp{M%;ucz3ix=RQ-M7S0eD>3BP)d@PgMH<-=5Rb#b8Vx5uQ(WkjMlLC7ighLeHRnG znY$2zS?Ix6Mn{C^RrH1;>VR|$UsIBl+Fy=QGHBa#UHjScLEPmhre}+0R80!c^80KARMU^o{|Q65mRa+&s z?NeWwCTDjEe|lQwo|QO1c#Eio=1@iUQ2^rGW6O5?CDoJ2kx9PbC}CDfM<}nPI6&%M zLb-;H*q0SvOj_s)@q<7R${}I&{2|JF=;Sz-J@-G-87sfDv_Ailfclg00LlhHbOQV> zOL#w5c;6c+vH`y!#kw=9%k6vrT?sKAQ)|&+noj%x9>DJ`Ak$pYUq$XDlj{yl_kzA# z(ZbQtoFELni2J0gB}rc99?p^Br}jyIDbpm?#OvC=0q!i=N;?eyx}vp(7T*{vJ5lH> zTmN4IM7{qZK-3>&X_Cq*An`ENf(ptP>(t}#@|*0eogJv-s2>e{8s83ekZ-Iy(l!#Z z=Sd9Q)JTQIUPJ3|=_g7P?Sb-R)03k8I_a{TieWuStZKUkSAnS5)Wt_pMWkSBb!qm3 zXd=O>1Le$u$3Qu-Krlh|V}bH#2HD4A1WF@{l)M1ddqWwvqA2}xM0Mr0B!OI!1Vpvu zck%D-5^o`(WPGupZ{g!=a96^ zz=}Fv#9EAb;(TI$sOx$E){ZgkXjIxIO-xFn*x{f_X+l|UliYVhem;r#6VoDs4?#QC zmE7Bvh{)rIgkVSUxT;-n!RbblV|}+Wc^_yW(+r-9wze)L)6AVx5M*HB2;I!^uhKvj-!{qnKQ|CCeFlwDOH|sw)fuE0Qq^V9k1y*ImqKuWP22u{HR_L zc3W7cZQ0bNrDfKvRbbpOH9Mg!ie@W%0p^MjGI5D*q$UPq2$d{&ARW!LlejbKlkKD^ zmgmLQ z&!YDO!4c$s^Q*@&57CyMnPslrI_Yp3qseLwub?o(k)vz+lw6Ptqe%i44vsxZ69lQ# z*Unpa+4$H{dq;Y?>s4s2hMOFZskOAyri(B97P zoL0w0BHKf%IJ0QlO@6qb$P~lOxN%&9e`SlBv*Ee#YicL3p5tsACc4c+dXti#18I7b z{$BFTl=>pmxT?_WnSgr!1&Ifx#BNB%>snbn*jHRDZ$rKko4tB^HsqEbTtCc%Bl51m zsOLNSf^RZM6mn+WM!xw3OBu`0Nj{KO9F z&?KLP)uGEm8-FbMF*;5~tRoG^d(STqu5H zWYJmto3{OHfru6RW(U*o!>Vr{X>~u2Y4VSmiX3@=it`3kjiWQvrfSpkda)wp{sM`g z7z;?$+w8TCj5>ZeUhTD>;7NOJG(+fg>@<`9Ld)h%UB^l%!!|E>Uh!a>6_>M1vLI+J z`q=rcyu)4Sg`ow@(<@W7B1nJ+^RDYMql;r(K{WBYxu1 zO&#Ux{q40aI%~5DGBujrK;uq1{sGsk;L!(M3PDbfYtR#hb93sdvZs2fhC^sK9nW+i`w zk}$dz%gom@(8;7BSf633p~zG=R2NLvg9&;)kXV$GLZV6*ui{tV2G?N{d0{@?1GwrY zyGxVWTNUfEx6#T8GYV*qtS~x~r(kmFd4kpm;%{TRuEqKSVl`%?9p8h$5L@&ey4m!! zNO@U(-MQL8STWLR9qpB` zea{k8X2F?fX3Un7`Bc;*{lYcD$~=PRkzQhjNeq~{Jj>0doeY!HQT3dgNmDKQmUdh~ze+OCP zuugQB(0Wuii}s|bDfZj7k<}kIt905RGrDmcv;FuuMraRzOOq#uP9osow6hjc<*Y!Y zdrTaI9v(dswK75^OeKJH(*hIfm__F9w?-uPrK4H{0{U{1OoxMNP2E~On)H|qrNt~G zL8Ia5RkJl?1i~#hpBlscZ1JcsJ9%nJ$2W?4bU$C!X>vY_ZTZo;qFzw|ghEOdOfn?s z2}?v7iNi2(HjCM!{5O35jL@)K`=eMXwQU7-hl2pvFOXVMv8m}i@oaKGT8z5D351zC z=CJ@IovmLlplXcIrikJ2++Fx?r8o9ym&)N@t=7qAW86#YwteBrP#oFB0i1mTcsI2@YJ8~BSIYFf$}44 z$cr~1fM$pu-AgS@vs7y{0qY0V1g3JhF2ACU*;zBb`KKw z%1Vm4Z?L39#U9=#Jl@2;K@>xJBJImwW!kra(h*A+gs)ZdWEl7(pxgL6Qw$On1@O}T+}znbr4UhP_Egf@ zSX^XvqMhx&D=YZ);52X}cjH@A9)c(7$l|Is>^f#XulOYw&O!q1+|u*yGKVzMVkn&% zq=_xjV3?8op;Mp2I~67$pp9 z6KTnkaiu3D)!6lH)}`}^Yd?0OZf-f@U$^H-rPRuv3VpWj-od0};9l@~moMx47ITq> z4#n0K=Yw0{%|d4ElZ5XWb9R%UCh*xP!Oc-a8k(3Df7agPRW_TQbA+BHc-_J1s5Ac6 zQDfp3VUwQKZp6Hptw(l9T~*B>(#Q=B4mgCbxADN;Y!2VZ4opV$1JTE)x70=RMXa2Z zu3?Fhpk-cmGCe%Cb_DAHo#-uth#yJ43u4A>I)~2;gW!}iZzoZqgV_2PLNh&{qO)Pr z{H9LyUZvqz>P0K6C+2kYI<%~8U5CLX&K*?N4@lY z)o=;0+~;QbAFLlxbfO^|A|t0W!HcpP18&%hNWbG42~0m%=@qb7!1!7s=fWIGMP+-) z&Tj-|0UsScDYC)hYF3+;D=UGhZw@N=!QMzX=p{aa9E=XqIPo=U=`p&ME9mSn*2b$& zLkEc?gCoLg^K?H-_%UiT6g~XLjED*H9?m2}iRNLH_L|nzjv5j*UYnKTm(rK;>G0UxeQKs@ zgM4bLT4O<%VbH+Cs=ngACS__(!vVFmVFP?Lb2~w@ugv%qS1=}0bIzLcTxCMP^xSr$ zx=@KmP=y+cCnLD)xljA|6Wz-XVrG-8R1S^faM4Dr2Qj@i-z&;a4y`)s!xdl8Ri3yQ3gM%5c=)Zt%f7egL&$o ziS`Q(WYj~JE1{bByT=D|hVnI*O3Jp(fSsx=O9^TPf? zdQy>8cVtnS*Py3gq1&7lQ>uF(EUA*W&@hwJX9Cg!B-*_su zWYTas6?7v3=;T2ExA?661Ri9r0|8%@Z+@q`A>}K>=p?u?wRQ9MrRBBkNl_CaGd;6h zVMG!;hVm}wR}61f3kG_|-#!fr`NysM-@n8)zJYYV* zWeBf_!cRPITMlum1Ed%G-wvp;7TN`lTUC#ni`>_Nvq_tHiTi?T+%O*h>(ZU;pLd*Y zt)`tT5u@t2qHJ2(V?N$_@@QF~%z;F{*vD+G;2*vz9O z8S@2JvLt=UL-%Jo%j4SdRYxmlBK;Muk0GZ^0p@$u$vL+ctU;lLhbWl^1ODuyVv^W& zf8{thJ}N3ad9WeDKtRbT_1Yd|fMlNF`i;PG2O$Ndfm26j${l_=>WDBN6r1Rg9_07;`{~)8d)ycxgWXF6Jl^*N>9T+Fcp)4w}wMdNFimV zaKVv^{1+z~Ihu~8+Hw{n8lB{MBPa<$+SP~=x_iLg&(l;MlA5o?T4W3)*cdq$Nca$k zIYX_DbUiEtV&Z3$r#4T1od8tmubQI!OvXzB@s`=UA3puv2+UQUgTPJ6xLKvcj_o% zbJnP?x>C8ge0`s9z-wzp%e&UF#gn7qNok!WTX19|GPSr`>KR-0-H_TK@?@J^QeRsp zmD_%$-fyRInQLj>3{?Q?O}OOzb@i~&h!XOI% zSW`_)d>0)Lr%V|jz^Ge!AF|2DnA$3)8X zso`>V3KekDgaka^rOt<5N?@NPAyR>?3bZP#K)A6VAE-kH%eFO#dD;QmtNQe<3U>EaO6W8a%$>pJj# zTyMJ?Er~GhqKHrW8a8J(Pof9R;uQ9)ys%^1i0WjOx;=EnEwAWm_tD$jXp zXbzu{jG^-@%e_EJKf=m=w+59Xavsw(NfWkv0s-9}`{&F_@f4OMjBC6 z`+=%sNy4VNAn8IwUQFfr$rGL)+nHBc6n4%6;VIsi6gu&5HPohr-6xL4UJn$QltPUQ z9gKS7P}0AB^BRmnu=gx+E8||em&r5WY`)8qY|9>h}>SGUDWeDq# znFg4ig|5m`0nfwBER^H8-PD!gw_+a6wY~SlufB)ydV~zXxLU^4{Q4y;aGJ^G)ih-Op zn-C=^JMMRZJM-V6ozKI2s0@0z3Z$ZaW6YBS&%XHwNRbNjYd?}&u`j|4nvoGOs7-t# zqpKP(IytQ_BSTcAsk_LwZ?_&D_6`z(vag;c8muMTRT80ZTo{u6oU@Akr8i>BAv(ep zy+@Z^0pyYT}u`oLpHs1d7h+?vzYLv*#XQSHZK4IDOHFJ98;I z$)nRgg9lyu!0^)Ov1K|7EvQ3GFGc;QBry&0@KGiw;=_7sD`JeR5j>fip^>W{mgwj; zcRza(ad~!6nE-xTpSr$gb2JY8RK8qPK+g$bMNWSDExsEew?Vo)`q@Y2hZO?zw z>yf1At!4UK`l8TnlQ+P4dRBLgnAvM;ju3Q4BoWsGE;^#xLL!QAX~ZCc%1?I9GuO~X zl>_dwdjk!>`^O8aa#=C^nr@ncUjT8Bm%m6UM|Tb*S(0iyQ{lwqlP_`N+;z`7eJEI* zbXxN;4Z_w9G)wE1gi0F5_J-^++36E1p2=Hzzf6Sv~RNo$!L-<2vzy zUh!1~ePUKtI@mar4-8v-@LqtrbF+~ClLLjNI)t`bJqg;`{@=O?JN zeSH^)w$^JkkTu%%W`oITGgqT35#&#VAFA~XcOPmC2WKVqdI)Lh-T5yr?=n-JB&dBY z+{kwvh(4OO4xf~0sd^}Bp}mw3s)=$Pv46NUoAfcV?+g~wKu3snDuE(^K;;L$ou$$x z9ovlBjE)lSqi_vNS!83mFXZF z&TJ(+b>RM~hsQ#X;j4_H=^0TfA@U$90|K|X8KpC0Np#FZ&CTYA!V6Gee*dn8YZFH{ zC9ECKO;RDu)_&Epi=tBgmCi!5&|w+E-sye}=O$=p5vicPu&bW>m&OT+djUv*bPjFT}R=cyx=2hFJSEue^41l@Hz@z}Fy*TF3j`JUk387NPH znKvqcA)L5dUYRc|sw$l;7?r0wb)WI;A+d$bfDeu|*=90vDO*deMCgHDcEkGvJ7bH>mI3BPv|9E{NmB^>_HxQUo;hr0Df_=%=sU z9-)g|wyKQ>A*LHpu&rS`(+vQzMxA|kR=G%#S-HLlt>2?>j23Jh*pmGXQ|>BG&)R!~otHNjaX9FobhHQOk*FFV4ZKE}jr zs#lD@m~;Lg>!rceZ2cODHY5vIgeZ6H%`v?Q!>j4Z(y+IKIUaBD_b8c7UGnqU@G2w` z(tIB&JZO7o)Jd55qV|;FVMrr z610#qD@v!fMwoczuth3DTBoo;Mpbt$>lc$Z^O`$agP$>wIuCIal$AP`+;izaTAi{u zilF6q=!jDLWF$y|^|(_Wj85bjo*HOrTi8Y9vC(RHmGA91{{lJBKSP$d)A(ldzW4Lv z&+MN%7a*qn?*x2KJJu3l$t1VZL7(SdJ8k*Yq|aZmaJ8!c0tGe;H|?TV3DM)z85Twe z$z;X4(2|azIUi{#t?$u&M>@rAw=Zgdn;Wy&G?ql)LTkc&MZ}51A_!{|a8SN9e62EJ{KPaXQk57B|=XkjbOmZ~O)pxXy<29X7Km z5WG}#*GzBHzh=40;;7ZQnaU8PXWZpF>dNBSbA{P%_SQP6SQ}X-XLL@~K4P9X+4q^_rzlB(T@xxHowl@ffTD(oRzx)4w6QgN>>O}oJ zBm_PQ$hG{*FEmR=>AACFB#bbWQYm;v5$R`2d^ecZ1J-=OCIahuV&@?4IJ5x;q5X7&aJ}Ty- zR|6c8_a99XNTCVRkCmhYWN#^?13aU0MLDss)c>J$zUPrxPYZqvSwXt!^%Hf4)0D|r z%KO%z#Cg(L+1)R6x4rkw&z7nhH%`VSnj3gW!z`gk&_LX84JKpjWHFoRqf%-*=(?4O za;Wt@*AeJTVGp%2Cd6H+p9$iw((j7DL1QEMn8!`c&swvl&nKe}GLk>Yd=nSRfa ztG^=T)5p>P^`SN?kYIDo&DxS>HW<|>4!bfiVDin_ao~!#_07*&zS19o_A&E0dypWt z+V2l3xN!NR@xYv5qhY~5V#rC!%iS(Dh<#UI5`mos;l!ct>mJX&08~doE~&u-)Q z22qll6GyBh5wCB`@?~e)w;%=)Yrsqe>k#QivuhRyzjF_yOf)iCw|86dDiJ%jj)*no zpRuv&jndUgS0Lk3 zFuGo|`GT^Y*Px(4;o-hMRQ8ciGpXiDV9g;0c=6azIsT;X1-Haqet@^aVwtESK0cM5D_c!s7&uCZ;;=Fy=JBPYL4@4g$OS^G zlWu|cWMR#OT8J<;l@(*Q>8EA?Qd>j>pGyW?Z(&3TnW(6(BOVitX%kyXVYcCCn=7gp z+)eZCIo71T6qfAWHW5Yi0ZLu{@B58szHvr9u(h$816lXj_1M;r>un5$1vD|89nVT> z_W7AZ-i4oVMmJ;3lSBwUUT69!2DDoK!6+c?#WiRHLOphOpN{Uo-xL8b{v%M}wEg|# zPfb%XC2utfdK#_K1*{SDuF&Q zc&vD@mNvb45$oaS9OqbzEQdzxMakRl5s=$MWP~s@212Iq-AQRv;6gKlth|Wy^cTX2 za%d2x8m>}=3Xl`3@U`*6wb?h$<;mpC=~hfzK})}8;*ETACkm&as@;Gjbb1|8+-o|s zf=lIK)N~_*#oNQfpRK%3p00ZQ4mb!!h_xK5>Im4C1O57-Pkpx|(9YM$8(|L)=Czbm zgB8OEy$;M6G*2ht{6aHU2InTqIoBYZZAbn4#y%*MScfWPDEA#{l2XuR4?)px?M`7 z_DVEWjU)PF|l168uKe#JO-m%(&gB#3*WktFBQ{`F|E5* zh0uSb_JjV%*8HtNY)7YC0vlggOCn$1IciZ7MU(&m@#{rbFe2uP7RkJt0KG3$=&u}!y9HGl$5*fePS;I?3CGau~2=n z;$H?~0Y(7i2FSKSxdkX4VT{1gB8cdl|Hi5SSNZSEY|KyD4w*vC{#(A_PL5ImOn7+w zo6FpP&wKZR*T$h(OAv3$?xiv3*O33h3LpMoQjHXUCYxye=@EVoa7SRnUgy;Kq|${w zJi;`BA&9smJ?ZhKb0Lv<2{hy5P$4PJ)W+3Z`$={RVkV`#UDiDbpF0_O+R{CDJ}7K+ z9p9+#XgMo+cx@G@gghn%h%rwd%;SdxV$9_!G-8igBK>59*As@(VFTNuibjpw%6S~9?*>p=&=LCTOIi~e1`RRAgMwZRs4^P)BDD!&XhfbpN zn8pF)UfxhL=QmJNKE%%cNL%>wU$YEyi?C}Syu5E}_&*vLfwnrJ;VG>EMT}oDjGW>> z4+r+v6RZckbTL^^2_gm_x4m|GWA7anAJc9W1 zwJ06H4|*(+7+9_VMI|Ohm7JDW?1_6O0bNXriups~fq0O?KjoUB|GKrbq&b+dl@BsH z@YOfGL!nCNn9er-cD$p`&d&nWU+{#5+c<+hm$cu#t8sefpF{qY`{$Gw$8GGe#ObSf za|7w;9`I8}l}&C8q{X%>>0}u(#D$-IPW>xJZdwP zvb&0_mtX@DkOp`(-b3Mbt;f2+sBaYD!9;PteHuqWQ|T(@F#gk`mej@`An#_}dN*y1 zSO1P+i(q;0TCGx*D}`xGX$O;geo31lhw}rYCFb@kJFmLoxyz2V`=0O&o*JUSUa)cL zP)3(@*%RG&e_{>biL7l!q;Ov1f}k2_us&|;@QC_N$4q>ZmP(lE#=){te|fUeoagkxkbra2w6ow@mS9==82-C=ZbF5Y^{DS zt-+Q)e+nZs&_0#BcDn25=XZDBf~|*Qqx3}t4pfK0jYO+`u|tr>yW^i#JDba((S>K> zPzDMUeIRk=wMV7`U3@HH~=zPY}&2Fbbg zIeWA)kp4oLIRyyoL*SG`RQ+0~U>5M{elXF*a#H%I0)P0w+ zbKE^9t~s#KLE|ciV7Ak)g^`pAoJjJntv3`p$P_Z;OoxBbU;m(^KikXHA<<2G8e?bx zHX!xSd}oW(5}LEqqr*>=fSD}AYgPyqlL~x;&sdAw?%B#m_m!IC%LYRcp*Yww=g{J;jK%RB%%-DiVm5&{mWTI}Fs=u|laALt zJ9MeU4{BtZY;DN$!J`c6^`fxGgiY9udyrxcTL9|E=p?%cBl3u>AMQy^S12#0#)sXq}@IB`Vn{o#Nb)u3m1Yjs*$T0vfJuV3#9v^;ztVXDNJed!A4gUBMP{mo;LvxK$&9z8OauySh6((S;)IqQksI`}*APo86golo;#xxU zRRoO3&+IoZbGc5K`--ry<)z)%XT`skeNR>t_Lc61r8Q$tV(}x}2%lb+^1a3UnG355 zhWRNn22Ze#enw8|^WuOaam!46z_bp97Vwlr$@-QU_q4~N?IvpuvU zAox~~f2J!t#?LsRBc9p%DT76GWG^+(*7gx%<04V-&Itd{i3{d5sh$He2_YME4F&eI zkXXHJlD9}O+GU$)ZvQ6DZDw}y9e&RpvqvIFmrm{(NxCHyEJnAp25!v+X}>_l$+O7@ zM+d9K`d7Lj*B$Wlf3XbaatI7v5PKD?Wp4{!!)8XFhq7DkT!I= zAg(&K&7o$RTMx)dJt3-x%$DQ2xscKRS7rD9G#48oB&`o%Z90#sN#k#<)oh(F(pOS3 zcYyAe)zlugd`+U^*PiLbaR~FjQ6O4Fwg?`j_RCPs86W-JFf+q_=@xx%1=S<29ldub zH%nRzlrts-(vYv+6BglhPO$b_wK#OEHelq0z>Hodp2EJ|3E` zRe-;rO1B`IZ<`kCdA0To$R5Nnq?W|Urv3scecQN$2mgjylkf$CLp$rD=G+993-iPr z)vKDg*kwal%JfpTI4j+}_Qhm6$|kuJ>0OrX)5AYhH!Sbnij_%3U$se149-<#V%tUalG7?Qq3d9ggad|KT838;Z= z)^4Vp9o(k=_NzVLND){F(MP$~w-B7tg-bcR=)=g?gBU2mR+HV%$^EkI>PA=8?vcvj zlzF=#a*RYqz-g_33ltoa^l=y_O-qaTuD{97=2cEAvs)VOZc(Vm$Zrp-EwNNPVXLwd zARxq`rk!fyJ^!;Xc)edD`-dGlkI-y-4>z&nw=^)+>6J-I6jr|4%~V@ttmhR@XCyPF zq`sW$y93;Bzn`!dTkjY*4pNu&j-5DjESnMBe-R6j^&N&s|HVCkx`~Mwj2Y*R%2_+! z{p`zhbvKUKU@&olcIF^M0|kSxJQBIY!#>DO#5B$hPEzMt5YBxrkFIDn>thKcDW?^P z!T6vI999V;o8JgZmk|vWlh2*N%z>$Ea9_H{Nar<{g=lYm!G-u8iObKBf|(Fdwb2na z4!F3F3s??AoNEu0E_wAhBzw`L(uRrcA8g$tvQiKHmiQ|6Kzf!1IQ*e%nc;e0Lc)Pt zn?R7Qra|Co{TXn5ishZKz+e9sqxHYqzdOt+j(_esw-4*!ExnD=d0B50K z&FU^fxBoI1SevbwRPH!NZee*6;7d!|vy}%4D^zgOwHQM)a9HG(Mw%gciE^APz0M}Hn{}@`lu>_yHh+QA^!es`6Hm)emh)})w@g~Ce_jInAkzbXsKn|Ev^X)P!u6TW~58-d&W@KZ!d zVbLjJjtTDXVKrK6sJ~tO4Zx#BH9UTim+EmW#dyxDMJ>43{Z=P@H|}BMT&3?qR;UnD z?1sF_cArPcRHxfFQcbW+e2Cd)arWbAe}t^wJQ=~(r8W1;XBpUloLM5vJ6w!-DK(>9 zC^JgIl=jmx_?{V&SL?t)1wu~p24f5d!EJqv4A4&>_c22_Y5(kE(yO)Zr9R|2`xb+V z9q-BnLStuo*IIr5{nY@hjAQXPUNXMj?>=%8lPnmwE^Jg%hh4ct;z|v5upLjhX%Bdj z_K_|6&zX^hieEZFzroaIQI|F*RBMME6w2JQj6Y}*@N2q!t3jjL*L%?bH#w^C%t^4H zRNk#n)pWpoGp=Mq=%)zqDN^GPBO0<3Y@HBVs06XMt-6}q(;1!ZLK3f5@q08PiYtk} z0)|kz@OoVb|2bF(XqW${Q614sD#jBm$yYdIU(3;xSwTMW z$E^hljT^!21Hh>Da0`{b1#P#Xbl>sr7C+m12ER`{(ZWnI3`GG_QnPelHV#!5e_S&*Y4ad=6P5vbo$rvT$l1w2$8u z`q?9Om-Jtt$YdxcmmxpNT};H&kENP@!4c`F=)7CD9W+i}p>Hs)yZK@SkI6XngXP&X z*p0`%+ZY-&NcQyCtP_C1QK|NNsn@ytdVEQ1R`B-0B%!msFJenL>?Bmwc#YP+m#z*I z5798o+zWfe!9i($^_kjd$xp}Od!}%&R>^^8>lll;@^}D6t6h0bt|jm7&h?LDei7o& z?ZdG^cd(^s{-_&auxkk5=Be^74#1Ncc)vaOuA}F4-?esdEc>FLM8lvx(@o+L9(?1D zR{@~VR0Hwvg~f*dnYc)+r$JTx{^ab^1&*qPb zr%$TbSZ7XQMmQt$nK9eLjMs>{-#ByH^&$T!3|)r=YGk1s)kOr)-u$$i*s`ox1Yl@% z=E(Wgt>}uSTn;4t9DV5Pccuh#z+Xl!G1_{&pYkhsDgFTsug~|uEp4){howx%sk6On zv1N5}NmWev$AbV#m8=ylR`j+`m>cs8Qx1}38Ph33@2)GvJQLjw4!=JbQ~|!X8nIQA z3-77BGR!rxOI5n%$!_0E%D+5_k0GwaA+6QcFA0-mDdTPOyi;uS?26OR-J=%>q`KX}3YCas{1XlTfO*qSof(uyF_)nTE#Z2#BukecY>?b(r#8TS_*J{-5YLeGf+nr8zu2~36YqK8p{B~T1JZcCa z+fLCDL=^nq09QM#DjuV(O7KN~Yg1VlW)K;&#z01EvuMlfWpipWeeZ9y1i-Zt>ZrEC zSbV{ZQvA&HQp)qM5YyFZMnHv>ZHRZytKL%siTUG6#wV*UZ>dj9nY`3jsIFZ{+17#Z z^0eKWtwSGRkfHTnsL1-}N=CYcn>x`dk%4XV{`IGOmy$9wd!cbH9&7l!TTaWDQ2&0F zERU&~D-w^EigB(mL&JZgP=@mU*7WZ?1rJzOB2Nold9;-l_Zv({bw7zuELBTltNS}S zi1=ihmnspURZ1s}uRi7zKRgzs2^Z9tZ6?^ut#@b|rLPG5GIp9%tN&s|T;1@AK_J*r zZ*)ye7){?}J#xnpN$Fe>%uZjtPii0x!a0DXhV%1u5_0}j-hFvwGMY+_&m@3&s({cEARkU*(s|>fxv*)Cx+^yipmkpl zyY@r~*P54~iq=B17x6))Sf^YTI#YJhBK-F1SQX4WDPtdhT8-!#{CIJ^_nk!f@csWi zVFjE!2_eXha_!(yOLmspHkFpu=WM6UDXdqy*5BSWQ8O3`poeFbnwpY$lSHe(+zj(qt6O+48qQ-NCT8N? z(eQ3CgnhNIL2~{hQyx+$5o;Yjo~lp@N{?hv;gl~s-t*+fIIK9z&&iRY3_fF9<2DJ5 zsJNP|6}+>_UPh#PP9)@?bFdB5#V9Ps* zJBS7jrmhYrdtN!ubFlpX_v8O-ng4H{K~5B!=n*K|Lq9hf8m%#XlV4nk>@MSAHShS>JMFaO`$!T;s|G576HE&BgfZ2B{$ Z>p!X&|Chql|9tq3lHG%0JL6A+M2U`2ri451@ky7U@)Syzxn0!WQ?si7k^K!B_W zLApQyLkN))O6Z}Nz#H6qpL?JCzR&Zy`}@70=dWi{zB6;q%$ah|nVB;uIsJP24e+C; znuZ$S+&KW?9PI@-ojLbamWHrF``q`Y5vMhlnHFbu{4*|a z7Pt8`u6P#r_x1Fp;pm*jy$$pp)8cltxPaq7#clp6ZtLlNh7Y6RD7v{q&uE>6vld^s zcQ@3hJ+II{oB$ty9zY$Sa@K!Znif3r007w?0N}#gf8^Pu0RYHI0Dx`cA9?rR0{}N7 z0Dy+Uf8_mROgycz^sCIA4_7XaXz*>@Q2>%VB*Pc#rGtz90p zmm|OxU=R2SpaF0P*aAdpF$ur}fH*+<6c2a|xN!db*_#%ApuI0%xp?u1A1+?IeEHIq z>({Pdr@KZ+N6&EcM|uWE20FSQS$V!n0j*7X~#Y%I)dH<@oSpH*`102H1_d$FnsP?# zhac#!{@I>$jOQ=hyz_(j#YeiAnC@ENGRWPy%*?_nq34x1G(4@M|MItl#On5$kDI3x zfa|ojooBqj2v7!`l>7<+gaw^DR9mmmim4H@64mlqi0_fav=Hdzw%4MQR)%5BH8sv* zrvT{0)nf(!a3&JawyZYfz;erDYmeBs#REJAxGzj?pf8VaKHZ(}2tvON7@TG;+L%I# zMiqU@GHG`aBsAO&nUT$?Qkp~~0Q)G4&2dpZ&$6G)FFSlbeSh>%v89@8OFVa;>O zEr~pFUAT7#2Bfj_yss9QmxSgD(EaS2;g`uFx2-0jj8md~3Qv{a@5{hm1Q4~jv|+}{ z9edG72Zex52=}o+`37C{$fvdBrMQ%Ka;Re&MZ#+_F05$7xp2hz9X_$Ne^29B>B@cr zQ!-X*r5{wzBe?H%;Ap=eGZxBPXc3jSji`5Y;c#e0ILlZhgow3QXhK-;HkMjAwUUGb z)>;|X!c+EshF*%qnq)DRj*m9U-%5EbR9f2h#x$$4UcgFz--HX>km9bf+II91QxIJ4 zD1v%CScP5kose55_uImr_8sS^t@chn`m~(QH&U>JeELFaNE;l~A(lxoQn4H)b!v?` zwjCXh=kmqOimC1}RJo_UtePdl%^mqN9j|CS-pb?`DDJ66-cL{yH7ZX54vTdH(dbjfZ zXgykbR0NOUAV=^EgvFSlp$$`{Da7Ice2J>Ja{VM?nClA;dE4Dte`?L)I!rwP(I8gY#4vz+|fUK!EteC`f>{!jb2K-q<2*P3s4 zu=hSe6nZ^WoO}niC9_F?MS$M`JL?6d1lCX$txD>z!XVkQE=nxWu`D(RGr!odCth0* z5Yo$@$qn8bhZ-KucvAIp+a)MpZB*`y^vHKLWA&50e06=#x{JtWpZe-grZmEe8*uuC zXnxD=u@H-qI`bCJxFoD+Wt_!V_k{!_d#{l6h{7r03Tkb{?C@b|i=xk`W}szNyQ03_ z;I%5nJc-!xzP>)l;;7#{O7O<`v8@@Rw_sl?U#PLm)rV1bR+6!SJYVE)<1%;DE|#os z8S5T!gT!?pV=+4z&CTLSk{4MD%kJqz8g=ciz@BDU+bcdV$x_gedYG{{i(gm_rkLw# z4RKI|EjyR`8Xt1P7B28eHXNK0=rUH{`i*Lm9+=(T~la zo_X$+{u-VXoNXsm?V*q>lvvWJ1}tt`=R3+@q~0*=B-6{c^l4~CAYsetv+_k~d8YWq zZl!*X_2seDW_{t}%yLsdg(jI!IY@8j`~8BWwOKs6)8hj+dbHh_vZ|;UY78zMU345F zpTv2IE7#Z*E`W3oK;Pzfq@$DGe_NVhxfqZZfTMI)^)|RqG^*)pDrmV8%Pfm(wKmL3 zfn|joQZE)0N8}w)W6HHSN5wsXMvOH7jg^^GfR>|}QIM(<|Fj*HJE!0=xIb(PnJ?Cd zp-jw7t#SLO!yQncMnP>3`NCaX%B%+2_v^SE3_l6uxXnZ1y`*_hQY|`%i#Sc(;kN4V z%&fe(-n9`TLe1V-!uINkXZnNt)ET83@=8lLFyU@QrUzTk!n8y69krl*@arpJ1y<1> z(UI+xEO%{?WTi8Nn9Q7)ZYJ7)j8}TKw7>A_NqZc^ka+=9csn=7Hp^KHiQ-Fg*)8yt zUx+ugjI5k<(jSsC&nWTpu$Q(jmb$u;+)i!9a)$7m%dKrXmBXba-9OGK`jdHL6GY`-b?d=Z!)rl_#9*_g}l>C z-%$LXk1rnXUP8wUe~@5N)eV*tsISF`zft>R8F%>qlahO-puqFA{c~S$GuwXSP7mI_ zdX-H@*q`s@>V0ind(Y-3V>??}4cQ(Ev!XWexdrKfCaO5XH_Yf2f@n)tTzbrsIWg*o zkF@Zq2^4>o=mpEavbj)uGS;TyQ9bGlF?FH_k8G1C{Ikq?1r7bA!S1Nd%2%`Y(jQM; zr|*wgNo}et=LLWYd`|%ekM-Jpx;|n?s{Z)nW2ukUjX_lZ=M-XLdDL!O?x{{eRB1rJa8Yt=)Bf8^f#LJ$a3VGpE^c{7bH>D zt*PLVJ7H07hKk{ffdhRn^pfwjl&0v-C9bdaAEVOKK6Sr?wcx*;>mKTTL46EQo4KL4 z#H!8R`6?#TjZqg@Vz%HzI1ZQKEBlDPRefx<1d`FAfRgl(Pd{NLHuF)DmP1!H#bG%M zVXo+xrOP*N1SLsUx{W_{a`K;@*r=rV)VSyAAi3~eEq8bYG|UK~*6WHK8-perV=gR{ z7DBPo@?p}~QbpayCuZZ>)he*$H`|%W;A0e*gE(IDh^?tETWEhsnSN*D!&ru5x;4V2 z*a|v|yB1L5r8TIVY%>D!9r4Ht24=WGhLw}A*Jtt53G19h8z~-s@Mej1Fv?|>J5-fl zbe14=^0(y5394%OLaAQUqL(dP_9u#r3^v^m>@(h&ykde-VZRBFLj1cV*n}?R4WTu& z!E4Me;C_t|nggVo6&<-+?_e_;ZGKP0@p@E3jkg}S3DhKo6U*_&^pNlhn#h=3bb5t)@`3QM~g56Uo-1`l&tIz)A)FW{)9ye#mw`2vqE1Vrb=&xjVp}0Xaklzjt=W~n6LEy%qOrIL3?%ppucBMrJ z#y5bPayk)}C7u59tjIuM>%7agHj|#^$oL-=4Mye3E3snw&rBoVnL4mIZeud%%RXt!ys^#uE^wh|dzz}-u&mZp{r?ioe+Hi=pp)4Ykz&3pyWQi-Nc?9oSA zj8u0$sL3^B0&*>c7x}(MWK@bvn|%VKmKBS@8*u{z8k3E+GV`xZU+L^t#oaFikve&| zh}Pqlw`O+65ZZ3(+3p#xUer|Yj&CFqWj<4`M2(45Yb4O5?%~zBCo-VWrm^W3sB9Fb z6RMxT9mn8sz0O6VyuY_2?)`>@_eSk;lUA>9@clJtn|MgRZ$5AY=b9lO-o4(r8s5>8 zWyNDv3r^XuAGZ@8@ohP_0&425+SUA&N2-Z`fh1^6Pu64G04CU=OG&wdB{eR?+bz*{ zw#B3J-uEYYc~(X9CJOo2yBiw3J*J3lM;=#?Z(H74Aj9=5?3^Du|5%}&nP%mhFW^y^ z0=IB&z@hy^(L0j^w;fQmjH&BM!;tQvuVNc$mnN-C9V!4b6UMhMRg}84GUbxx$9O(CI-25gxZSGZAV`5fO*G-t4@_E?gn>$aE5yFlF?Ow1#egRR-lE)tS z+KuJh%0-Ty@+*!+c)nTIDu;uTwkmw^W}2aCmidZ1IIy&v7p0+buV59gOZXiHsR&3L z301&0rZW^4w}7;S7@)#ls|I!Xf)CebNu8Up>;aA4Z8>`7E-I5`N&PeJl{Z1*tQW2w z)`$-K2sJO)02>%}U}btWH+7;|uhii~arI=LA09H4kX`Cy)sB}9$JCY|7``>=aYkq# zZEQ(-uWO9SSyM zRVzZ(tU&b@?ir_bHg=-b`Os1xcz?WKn}fVxeN-7f<+?(udM%`<#uM^eHombWNV-iS zD2QdPs3qOe@I!10Shk1Btc0JOe(&QSm=SRoOh;aeYtY!2(&A!_@Xmefx^L%i+#3>D zc5m$-+u978@4w8%W6<*vahXT6t64l!-xEC0F^9+dAA#c`R-;^!GisM!mG4NB%|Yr< zyL(}ke;_z~wKD|Y;Kovy%Ce7+Q!()!VwZx73>M6$*G-&3%J7^N#WJs|-Uu(@%&D#9 zdvn^s-D|tv8XovLU+TH(RAb13?Q+mAs>EgDSlltDSCWrqB%ZxTBE2lu5ZT>+y9r30 z!gCebR15$Y0T*K4pY0ODG=;pp67?XXQcZ_|tsJHh#-U>{^5$eY3`Xbmei>FBP%txf z^l)qnkwNB35Cc0KXqVL!!0i_4vsR~ol5I~ARmK9l9q938&=oC)Jre&$b<~9$R*iI! zA%P3MyIS*E;gpc^7Wt7Nzmv%RZfyCmbHMJXeM0H7v!C2bI?jJ?go;^i52rsw>TUAe zs|flCxyFq7>@)#|AJi5}y46i=Ye>Ua_g#a(DK<|u?Qk@?IQNs1`}z+1_TV`XTv(J= zSzMX6_wS9p_Qwi&LVT#>qfrV6L+Mg8G=y_`8528^{|U*%-QBw-$A5ro&BNpU8a(V# zD;^&Q_?n`Y=Yc)WrhE>3BE0$5?G+AhN>ULABe8Ue-vMy%i zFQ!?@6jFu zWa}Z=SOJ3I;3gOyh9(n}ZdtLP0-S7Xbl>QiA<-{Zah!Y}zfQCn1$!>-k3{p!1$qwo zj&`DKrI=f*{2IU>y}qarNb$!psha3U8{f@r?i(4?5CgJ&r`)X<6ChIy88iEUaq^z7 zuYX1|msh0pc-+{Mhdv3vHSShEKJBPa4tnZYld}z$dX6*5<|_+s!1KD*>gYwhFXSph zrevV4{QaAEHz1)2Yt#MP%G#vuwk!t*ZPli?u%xPc_as(g>>Abz=QYj!w)ZRt$L)N7 z<#cd&x40_+VEp-vf&PcWk4L;#{5#5xyP9nfa=7<%v*ZG$ZsTq$WDS&9m2{Xh`Hn8} z?Z4oNYCc{`3CY2p%;JO&luM}HzcvZps4=93>mSA2mCcPQX5?ea1 zeMh)w&dcd2r~GV7%i1B=)%x<^64AfkqU#lwqO-jsJ1f^+6{I*kBZ39Oewu|X@U<>( zY0==9Mar?ji09L=`H4OF!^RjWoi&@Rt-@`K@rRA?Ug~)F5LCVjY+(GPQI$@vqF7)Q zH`_q}cz7rf_H(hEy(4`S-+tncT-R?YlFhQFRQJY^#CZMqJ|PqmUVpW04P`3x4YRk%yF@Bg!M_gp&> zO}2s0$Fz*d^vHyIHQ52r{lRpzu-PAlSW}IhS=2_^X5~ILY|%!d5u#HgUEIaq!_|7? z6n^1HAqEqOPQyQP&Vf|E5-_p@I;-njR-eBQTRq*aNh8T*Wge;Hh(F_=$06es@$>IV zNm!As#v@7SI(0$nIBGjG+n>iF%<>d4<#Sdxk_7+HSwNL>e^%IN@F^g$Km6X0LQJVz zI`xFkS*gDjQP-)SoY_pL8EtdE!&y^xu_FGmEsktqN-hUioLMx-*==P%ZEivK+0Mqi zRi)c>R>bj2n3B^e;GAWyYlT;;WP~!$!&AV^vneaZ0<+>IF`n@=^En&0oLL4i2-q?$ zk@owotGz-OH+kfhMC{~73PzL^y)I&~OK zR^E>KFQUY_gnAvgF2+>?9E9cj_hK;Y(c`O&RQkl%+gb$`D{Sa^6h-~DWK)C zPJ^MQ9>yzuJL=&cmDVfHnz&q-QoVrh7^ss?E|qbEg3U~a);s4lVw2kj zwlLC-u70s^{Kd~IY2fcNOe?KzLX$i5yz0!{=DmZeIo@K^%9-knD(Igg|0)%tQ*Y5} zEAuz01gUiT-+~eTLuwgJ!hbPY@Y;1G{a0e2NRwKxfPQ22UgkK3@o$Nf+Vm4zx4v^B zDhA{AdYw3re)~Gtk`?CRhG3a;jWsx#0Fqw}8o4$`yPY;QtX@g1{_@-sTccsKx^E%hH zEV&Ri%e%zww0?A^0t^aiDxm5YpTDaBGK+knpsnV26)^X;DWUuI+Rak4Rg8;+@!ije z!K%152}nS95Mt{Vo%h6V@*-P;uqHtes(z8!@phhegAV{WS7*f~uM)G@Lr(!NUnkPb zJtVs=-H-Qz*N-fTe@uz==$tF~A3BHp6B&A?*wJQ)j^o1ga|iW%{FltdD%7-MNCIm} zOA6^KaBMlC&L>+!2wm!{7$p_M^_YEXIH{_4i&a@%ReHAJB!`Zm`~1SIJU8ja@u({p^D)XAx}#Us zLYlFPff07l6&T!m{FFv*RsA_YmFD|kBvh@}LO594Bks1MU)D=KEA7-YS|9XpI|bCQ z9@EeC6#_VZ{->_|g(y$#3NkEGp&wFWhaK0K6(=e1!}=2k-O=Eq(Ov3O_g6$~^4AO3Dq_Fry&|xu`$e}s{|m(b163;B z`00`w2`~1v3X9d7{n){2hmtjJou>S&z=#TEC4Nr9`CYF|A=0jLV5odN~ zVkD9QPX9srFGZMl6wHNS#_o`Pj#}%o@C|hM$?Fyp+<#y!W8mk&1+$%#3WmI@LV0zD z%+)Z%0~{+!y7*JYq1-B&_%UoS{ozEM#~To1EYM7vAY8J2XgzOlV>2$~9$_3P3qq}+ zPXST!qWNrTzem4|+u{k|5A!g_JoK#{ryyNu;F)UuHfc3gISBRN!G?c4SfjIQUAd{Co{HAlLS7pRD(ZrNN+mq>uS#a0)# zp91=gkHABC?m=Y1rbPCv(n*U^U z3YZR~-LlaKc8RWMR2+I!+>;Z(wD^#IhSt(R!UdeLYD1p3mAiuqKi^*MJi}5`%0h`4u=XPMbpeP64#H z)pppuvXSLu$)Zeou3D7dnoR&~D^5sZD|xHamb>H_yfISPY+E4Noc}nlmDTWLRK3mo z%*0Hg^jPn>Ltz^}tai*-1Xa5YR_4FUfLiOS?EPtMK*9@Lfp(EQwxaXp@a2+G|6EE{ zzpVW^knKxf(_jQLn7^UB)1!uVyERsjr~yn=aB_0(@h<-TUEICV?L&swL2e;C`PY3n zdpW~6gWY4ARv8zJ7Z?W%Ad#nl^s3#Ae67Dn2vLs9LE%Gh0d7lw=YYTV1Q!*nGhAyR z(USQdZ z1k@`^x!O9HvA@3Qu<4-pEB#M@jr!lP&&QQqT8?OA4CWXKl@-1_%iA1Q)VwO;6+DK8 zcOPa_;$`B^v>p6a-im~)6Ze(h@C@yp+?atGwBRMSjW5P8(s=Firkc)(dlIhj)|RV5 zWnU}Xw?~6~3Ly#^zj3<0n0;(dKpQ>v!w7LxMWk)@0N2R>N9_b;fDOxK1Kk=uMAwfY z;WIRa*Jk=DDh2hLvU3VxBJQWg`4Ea%09W1v&XshQ8Vu*kv?PV))YrN?dpV~t7i+BF z$FCmZX+xzC`0GP;?z3G$)rf35-0(TESjb}u_RPUK^BeX>sb}&mkjnEy>q|8gC)ae-HcOi)c>6bUgiZm;hf)lahy!fYa`%65W?C$K{o@?J2Vs zx}R~~CnulWEnhG01U;AQXX+5mAuAPPY2v6?CaE{Fh~U55j7+w*C`<9yJI3*$b{%r8 zHc|OOE<7b`{i8E*uy*>+Gh+jih?5yIH67n}R0YQ1R#iWtF6ERQBA+gvzz4`&#`kb_ zq4{ADr)QcUov-k~axy#K$N@V0Lkg1t|1dryO?fXv# zZDzOvE(^Zj&l(|UjWwSFG@m{5n;Wqk8bu7y*KOlHy zoObhI^Krx}3py~)Di-9U^G3scijuf3wHtsKfkFp(egQoK`HL}_>2GVrSX>{CETo#99bGU|pHl%BMF z!jIkraxS2Bzn&SaFBrz^=cQbIIN`R2Hgl!zBu%p(7Hd$P+Lm`~18N0-zV!C5Qddsp z7F@8ghx)_Pv!fV3jQUQLcNpVRl4oJ3fCgu%y=uK6b}rLpW<&#&MujLjlX<$~C5LND zVuR*m+bynfAD-2HQ{)htRt*?Vs`y%fcSA7mhM`s|APzYUlcaK(dA)dMX%2AvVYP40 zEhA~jYpU?8>jPz-?QxgvYtRbk8GcMom8kRrt16ijftiW2ZFyxXXiM_s3cZYeTr78a zyWyNmbu-ykD>nAi-E8v({st;)lfZ{m!chcfXR;lgoF_ZH;GONdwPmfm^n_%dZ!ex~m0DJ#?+#X#Zdl(MdT|PPOSBpt`N}2-QXk$id5yT{ z7DqpLw^6ej@}})(MEVr?q@q7~QgnU2C%C1oHvKr0+9&Qir*2dqm ze|4JPl78Q|Xq$w5tgIv&$les}v0(S?KwB&9$OGp=k+Uy>`4Cz>djdi_r>h<<*z8{{ zcl7Owa-?Eo)$SPb`;;|dg zT{2C*`0guqKu$i>5|pYXS~8fFIbu=BEo|S=9TXZrq;zS=Uwt1m+A994)QN3ks-n`4 zEPEjLtW1o98E?nKJ}$e;j}iMe9LyR1Iw#>2VCZYC<1C+@J7y}HtA=ocJ9GHT`$bx6 zWqH4`kD*#j(P9j(a$n_s45^IElT9v%>rTuTr=$&N_3?A5*XrnMLf zc=LkQm*%mG$HhV62#Mwxp3C5X2|nh+Xd?zi^N{-6GG1<8+rdSxek);C2S1Po5zUD* z0ZFA~)2XU>3-6R|OpAtQn-J3838^GRa|glCIZHi=MjdTrNaTp2MHMkXzMUKiNH5SOMzQW`R zI_+WJq%;Rj(p5P#0R_%ht^_Nws6P&GDkphF%gItLitV69@v&HfLG{Dql}VASrHdov zhmc8yg#q!|)F?uJ|~98Wasw*#jR?yshc&7tL(sCYH(1< ziS@`f&rK;}&pPIpeShqs2iVSNW6FGoQ}t~{e3I2I=2>7vaCWyxl#;Tusi7yTGnW4; z_MUG>?d2z>I$7pM-6frSMEHhm>kW$WW-vm&%Ys~?fVplS8f>Ox`r2w6sJGR4KYy*U zvC9rVU*L_g5p|dI5J;=!?LW~CJh@{-+Ls&3f`o}86Wr^KvV9K{{k4z-2kH2tfQoSzC@_rMDUdDA`;UP1xcCx$lw3Pln{2D^CUbw!2Oj>L`b0YM zW%gaDXw)zpRb%W~sBXy%f_{AYphVvn=fZT6|*yz z3Hm&@b-PUhvt%mlOu{mfr0(w@)#yiX6J|23fN}4fZR43l>>~z6k0{nD(g6wuCegra zL7_U+s0oRNF;u3S-O=@gon#7me(i+~ecfy-jnZ6w038ke`%KxuD z@Y#AMT`&?lzDir&GS5yGAdtq%a(L-6LG#(iOH|4uZ>lDv|pgBcaFr4 z9>RfkgSiWqPl|g-xl9cnu5xE8mX$Qfja#mC!~9|Poi^R7(oMjEJ#{O8nWr9c>*kkJ zl+feGzu3(6gbkJwqWzwhrKUjFgn$JjQOt=r5Q%5lEVpn!x(rg_+TB-kv~{PjOY#(8 zKkNvOA%MRUMhv@ycFq0Qk)N~Iz#BU{0hQsuQ&nG%L5F}U;vG(=mNP24`v(0!c-CeR?iqGfIQ;aKkq9gFAV7%E1 zD%i$7cjMmDKm(bxG6HSXffX37sjgZ`yIz{>pJ0&bZ-{#wAu>%rf8-$s@&7Wpn7*g0 zp8_{*Zpf&bo;IL(OL%z}lskian7d-*L_?&5R~KiS*Bm_GJ?r6Je7=F0A?oNNtj$DP zxl0HlE^e<8#g93@DtHikVTL|aj1?FL565NERqnw&var!`be}Vqi%$G1V_iB!CpGD%CYu{|PEsfYqxE$uO&E*d`oH zhs5zjn_|>;iF8m4Nse1RE&i_@=-1Mo&EsLBt1@=h2i;@&ZFn(Vwx^v*$xlYcKOY~S z0>;$IYI0Tt-VL8O;nm%I&OtF2pNA=R$MiCgG99ds`Mz=JiK~Vf>_qM~^!+eEH7Ky?13D*EM%QRySnm_!*s&F?b3KD6z~P*z4qJR)};w zwE96SWy}iqd~VulZBnadxuca-CSFm}U&yX@vx;cBH=tY(cP(1EHz9@SS} zr+})7wY0o#%o87BZ29NWt!lAst6lN7(&H1(EZ^=f_aDPn6jq6&Sr8Q{1G>`D`DTAy zlHaw~%~U?OS|qz?JjWz&td;0d8~6m?(J?LFgV9w~@I2T5;V`F%59wlRTkJm!bMk9`P^d7yVSs&&qH;9+MsyrAN>@sXW zbfEy>%z|t5QY>bu*kSxk;@2vx$VTXRg8rzK@C(!>4xh->M~y@59vGmiRPze4vdj6> zN-OxoR?Fb!7yW;ruJyY}JlnIR@bh00c9db|BH3s{2<{3V4TJ`Kb$uAeH)3>_nznmr zj9}$HPO|e`if)CfP^Lkmu0>(f_)D~yzN?%o_itsIBa0tDj@FyIzEF8kttINO*`M0$ zWJ>z9``F`KJmJQBFL|@xvL}6gBOeSV7k*paDoyeTbes3~a@z|rT}SP>lF!L#uD*WF z#S=|`;j-qIi0_P<;fLzg9&Hr!aA+IQ)gl${xH+{CmOtugGb#m8_+TsE=cL^4$N1772u%euk*R^It)l$9U zqFD#e(w@~Mmvnc!GsW}P!F@--Ask`R$Mk>P2Lt6++TefmclS0=!Z5YsW74Wl^>lh* zL8-2-29M^iaLX^%^@m_&*^s)gdLS7~DY(^Q*!h4BwrB%rVIqa(UhdqYSO#&AnQMpd}AA>4xy(n)r!L`atfs|MH1?^)f# z57pLOLfTK@@C*!=jL%gqEaWQ@+Q2l~1Ern{SeZ8b@|B|@?^$Z5aaC?*_lD)JyhWbSe65cVfMrvNL)KfqvTx=}ph3IiB_$?{OC6^`2T?%I!*_^7TNGY$> zuaxT4JhGRAyrBYfxTw;^nn}X9rCZ;eIpWOuX(*4@73FN)?OuLrennT2 z?1&hLmKH`Z(1sI%u99t$3Xgj{pE2#Q#l%brit)SgQZLHx8qHi0&5?e{ z&)0XIUTMWyxCj;nMUKBErcZpqXf6h=HoS2UGEAx81K)tb7;K1*HH@SSGBPaP5{aMxjUlRXVOmBZ$xW(5gU+ zi{BC{DJE{FS!jGkg}G91%B;Isp8=G<9ej*8V|VbCYA!;E^p&SvR{UzG$wtZYh!BDn zm++;B?zx4vk1;CWr4)5RmPNoui(RZdd_ALH4fynkJDToVQ>6Pxvb~{6+agOAk1ea@ z39@OPm8I)?QaRH;2B;9YkGgCSWs}m zAL5RH#QxS@tCDn)9LBm8MHdM%On;gz4_rCup>Lb}+{(zIBnxf@ z4>LSc6V<3U0DTo5bf=O|0gQ$4D1+9!6SrDw5iaSUYkV@TS-q<<;%>$3H4r!C*C)0i zNvQ4yRK>$mXCqX>a;l<)wLn*xv$q$_2JX){hQH0FppzQnICz8qnx{*ET_@j&MY@-p z@)jXPTaU;8Jm7pb3%-wYRfQQGU?uF14LB>=s6?>$Y&L+=ckdg@S8Ui(+o0~s9$~az$e{gCR6(}oo^`Hp!+I-U z>-be8{1aONV`EWKNZYz21)Mp5JF|B1Pf5b|Zf~26d$(v_=^4atsIZ!~ z%TW(29@#nKINw-EMpQfdR928&YTX431;n%^2w{0+jPvm11Nl&halR&|ayhWy%vG@R zRs~5;TSJwXWN}*V&V;}%(Yv(YBK}+{+=H10`*}tdh<2rD2R&W1sovVMWq-c)zD}fX%c-PZT@6R>n zaI}q?F@39_SyKq=j(9wCf?Ob?XMCw^^xn(sA+QYWJ^Q$K{2clGb>>KyQN$8z1!rX8 zQe=###<}I1rSY;VNUgTG@ICASe1zg~wt%Xuxbsm219`W*$PjOETQod-K^(Y|f z^2Qh2h;?HQ0Ri>b`z?s?s4=2VJ_n#ln^VBx{4-cccV_tzEVeXOe?6hE~2Aq1Z~b1iFuhqE@-4h!?ha%0JY&7GlcZO%r|c%9qCLr6D8s z#MJ(xE=CjcWvGP@w#gmm0NWBQ=+i`M_G6_0OlBj2!fV zVmB9YSJfISGi^n6Uacb?8`fv5U6@<4tUFxV_;vH0n*xMy%9vP=jtbh37z_ImFq%Hv z;&{hix18MZZ%4){$}W_aL5UDUg_YdyO=5T)-s5mU-DFXChs!jx5dv9HT&u^&$nZ7F zR-6Lr);CrfkGo&v_hCrm_Q87{E1W?uP`!~Uc+Gk>){l7RG3qx|Atn6g0Ajpusgaqa zD7TVa(vy87bJ05;Gfrtu}?93MBdREF%$SxCFT{+gEt6emo-^su3 zj*Ihn-DJ}2#%BJIod0Hpvie)5y=;#Huc)XPf#_j*#5exOlBuhu+f>X9%cUYpXc_I6 zJ4G>cWn9Rh$)jP)&gSICH_M^s!C%H!YDy(HEryzXF@*`2g=JfjZy?QK#q)>i`Oxgt z$YGf0>gO8T4lrd`%E}M#<#g7&!}}+WkMdr~h01xP#JhTLiX|cM8uypos%n`>J;dgO zff^3ddLG0SRcE%0m6Ofo>zkZ>j8*oBMBsMhWS40crUHvKwACpTJ!<5cnQ5!uVBfaF}I;}GLn7d))h`X zQb_$Ek7Cjp<-RWXIU+07kj{~9BXb#_ZYF)?vH8pVy=jB#SfTvvWZoE3^L`t5*klL+ z{qZZ#DU>OpXt@@94@!3mc+q^K6Qf@Zb}fT{dK0WX8QW)Vt|cyafVza$cUys%Ida`# zu4^KM$jV}x8Zr7kT^0Re!_x}3_j5i$39cvn#!dqiDt&OO7Tm93&kM&}V*&S(Ra2gD5$rt@MJiW#ZsUwC_76NirzE`U zioDs-ks0VcYbB~nyTu?jX&G+vT)xyIH#<8wFA{CSEzJFOfZ;ZO4=3liKV_c>zU`;& zD$>#1b0@|I=IC}6;}sEl=Su5-(b($RiB&mv$2e*tUSB1HZ+qWv$e>j3O^wjl0=svJ zDSj)Idl@AKv-WcBTbLs(wf*#Me#wMAb;M7xwDn6h>8db4?=9edw`yVSPNIe@-pOC= z1xUNTcQya3x42u8kdd4wN;oDwf8b^!X_IEuYG!0r-Bxr4)(>?b4#F110#`S1VsCpM{GNW-yUpx& z5bAtPz0gXWs>k_y`^p~Pi}&$bua~^n8VG*jmmK4jPOpP&Rc1=UGwMj!xL0L-7A4+o{u{&BF;|_fF zMLt$O%QSN5+nn7P7TMNqCRjx3VbF_Pf6=(gkD3zL7SFV0+IUBN2as#{cec72;7tCg z%U3qyD66!SFp##x#q`N)GEQ@m@HEF3xk11D4}iPB0xls~8b>u@_Wj@YwAH1Zl~Q{7 zSAF8v(d+*g7vML6u+dbAG%lViJt;S zl~>}o307;ir-0IMoLngHa2Oh={PEjy({Avxa#IeaE$?#gKUc3j0u679KPfo{*!>Op zkN*kW!7Rpu=T0E4s#&Mz%4mmZ%crA;B$2`pW<1W@{9X+;bnyM4LtGnN_jX+oF@Ix_ zbuq+N8ydv*%fMadfOqex)#6OMfD5RVe`mh`6^C!cHMY?+`T2V!y!lVZuTFPPUN!;X zTw~Ry#6P*s$bN|REp9iTMeU)3r7;MWA1hPXv@-qS;Zy(q5$9itU+&CFK}p;Jfc+|^4azS%}O@%Bks zIN66JoW36jq!oQ=n`pfVz9u6qCw1|#0Bc{nUpG{p^INE!B}y$xG!uI-A)K2<(M1u_ zVt`hTBOo=7a~%9Endh8*eM78^olRBROe9cW-D|m7EnrEorQ{Van8Dn*lUA5QUD{}u z?vR9h6r#Q*YGj8j)f1W0R`^@K@fEPs& zqZ@MBo-j@8Tk9zbd{eScO)J@MI>0_r%p+b5is5GT4+csG?9TO|M3U{V7*iLN{a?to z^=lwdS~JMnm)}^8oC6xHVpQdXiVYR+JkF~9twzXm>OoBC%*g7}rb09AV!y!l%R#^h z*89#?do`3V*|msyg2diE*uCpe$gK&QNCK-9t+8+zy<>!72W2!K*O(xyqs)^fTqU^-O5nM!hau~Zy zkhZ%Ka0gp~X;00ynI9V!^a@P}YbZ^cP7aepmD38G4jtT|*c4-(Q@*ZA5E?PsV$hAX zwAI70R;pHWzk6Sn%)~fkG}1_vFIPBZ*|ol5ITN_Wc{9d6gi13$$#OR?3)c_Ce_5^4 zZ7zp3*?m%mROx?izOAA|$;@@DbqSq2I$S-B7cGj*W>nmwT4gP_$mB0oWAT|7T~;>Q z+H)5Ka*X3^I)GfIG4{eH`Y_?-#YqlNALotZT^hUVDfjb?%&rEAxj{Bg0n3LT-%#5@ zgOu<|08e2!^k3!X-${duzE*sS$iH~81%8XB$%U30ggvKuZ(VFj{m%}a+9KB1^eSm` zy?%sikdm#A5Y%k)e!cE^gljQL{^PEx#o6DZbS)i|4;LV#)NBt3>Jqc?@RX2O zCLVmuJ*@un`NG^%n&I4xok>iB*%Zr&47=({QT)=Fnv@*0dA*d?=xC!xNb5LF{}5ex z3aIrw11E-B$bd%lF|~~ja1_U&3q?GViP#3=tkpHG)9u?Z3_&Dw; zOC9Ez#qWkM9nURJWn$&4N_592hLB;N7Y%)%@02Vl#=%Tb?*M1 z^wm0i$cn6ns=7aK4OQ<2%_+uh>%%ndvbzZgCnu@?vt7*g$}sKUH0Y=LPsW>Q>{Z%0 zoC0oi-}TsS{Xbte{+%ZN_c%XHxK$p@f(ww|W@4j!F*JAQg)mP3`$_Iv$bV_Sp>qmJ zdK!ffpV&L$qgnP2!ZayJPr@pCHJG+vqB-{b`qv$M{`UE+wdI1m!k#n^1xQbJ?IIgo>exKph0+bNMatY9w zSP&sZfk6lo5U8XfqCp_sFQVKu&|vo@fVML`JG(RexZj=+|NnWO^X8oAck<5rzEL-u zl7};*Rp-`zgRDofq73{~|M2DFhrfGzQ55Q;qLPfR*-u_;8)gB!q1q87bpW9Wzs)cwp$JEF2xFZx}W+-*MoILQkS~#9dwWuKY3Q&538WPeFAe@k!_ow zrE&NyE-o+qn}V*BVH>yjdEAbxmq)iTuB>SHRQ&2Lk;=}Y-IwXzF2~cb9qx|Fj}2`O zY&|YA;A`!{I|$s7r{fr0zNV);pU+KV7w?IWuPUL1^*2^=)y;)Hq6t}aVjL&zyx^ER zAE~sBqg+-Qi2nF6kLirE(Gn^zm z!=0TabJ28aw(}ODpj?IzE!NjSnhVd4IYtT^ws3TL*UNEPniggVA8&M)HYwy?m%;bz zQtC%0!f+1rU-yg|M5ICe!}W0JYp5e)a>&b)0pzbNCx0;iq{$8(rbiMY{hB9Xt^|Sts2Kkoxwrb{-RXpBoVAdcPk=z zOFu>uj2NJP6EYMQxHL^0lc*n=3zVm6Iw>dJYMGfc)Nh^l7PzT{bM!d!RsF*c=F*TY zp==c0)d#arQQOWtW$iy)z_5{s*si>~gk7^fV2H6?t0RWB;SGGg?F-}P;zq zzWeHLiaR#A%KeerQ?-d!Riq~=NM!@5@VhI{(CR@T*OZmWSM@Pz{OqT9JYNEX>kuux zM%LlU{aIO9t#gn$FVAwwpdzZ#LFR6X!K)5Dgsj8RYaKmxEAYfd$t~7FG&ylv{m4{P ze+u$GtBZ%%{R6^z1(NjNdh7sZjW4p12M%FKXNY~`+t!IC&|AC59f>F7 z2q%rW+9!lkIDCW4S&WeKK_awC2b&a-9{578HYy3h+(izjHw|o2i9}$V#b;2Z$V!Vz zojI)Ri=rMApe-$gzl7c(k{*1D*=h?#mU$m5ljefj3T4v9j^i-slygsG2>EhBuHD9k znrYy$cXNz9%Bs*;Z-k`0w*kxCK0Zs{1X3(%UW|?Urt15mJSGii=XW*D(yLv-y0-NY z8WLyq)pjahOFJyn^Zb95+qzvqzE~VmMtNp@YjU#eoKAp6J1-gRdXTJ|D+QyM4>W0|Vdi9+tkb z8{tH=gF`M|7S}*0V9-jN>m{&w1}C%9(dA)v^@%=8m^n$bG&k{>0Aqm=>Vk?Xq*i|j z8-*tJ=sunzD$GbEr>$yhbHi7uQ$DN@!yuK z!Vt9%E?iC&h&#-2Hv_Z=Vp$vL`};-2dMxPs>jVG>Ha39RGu+^(q{F$Nmv*Sh!j(Nx z)0Qoqk^OyOno}?sBTwilZR~}52e?byyCCeqJaLV8v%Y3CB-M*9)U8}_ld1r?V1N`K z^9e?GfT7!fFaQBFU>zPZQD~~2qw@$35%nSGjTG=Q%xH*t8_f@PdYBsi{rpfb+o3K1 zqPpp-B@w^_X#Gj^cI|<=U8;qn~Le0f0MFgWcTF%ZaC z`zT_4DGa{sU>bcFAkis#^6?)Qrmg{@Smu-E-9*&_(ewW*$dOQ$ZbK0J=c!>5|$d zFd;lZNBOx3l(T8E7JYLT3+9S=CI+S;vuQEnc?BqpR18e`>25Q~g3lb#LIDS|O{Ww9 zWAPA;mLtKQ`%qDfYCDK3nN){xA2t_$$Vfvfy@5=>iVL}N8Q{WnmH@m*U|QujNy6R@ zZ^?lI1b<)N0($?CIPyMQ%q|!dd&Wg$FUHKq1en_%YUl*KdRt@b3nN*YnjdP(qMc`%8BmnE zd3XKxRFm2a52FXEY4yZ)Xo){)s3C7YeEZ+3E!W9}0Of~h${d#X1K`7#W@Lz`(_zbG zDYpxf3i>}2#_}LF>W>awpO7{t`f^Mi)2E;ErPvIXmGl;=isxBy%G&ECb7xv=W%oeQ quvVD}knu$Z2fV^%*h#nN3~jCV9skQmfAYVB{}1;+UxB3GdjAEIf~rIS diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 4fb715b..bff476d 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -10,6 +10,7 @@ "cancel": "Cancel", "copy": "Copy", "delete": "Delete", + "edit": "Edit", "search": "Search", "refresh": "Refresh", "back": "Back", @@ -17,12 +18,14 @@ "notConfigured": "(not configured)", "countImages": "{count} images", "itemsUnit": "items", - "imagesUnit": "images" + "imagesUnit": "images", + "more": "More" }, "nav": { "api": "API", "stats": "Stats", "settings": "Settings", + "storage": "Storage", "menu": "Menu", "language": "Language", "appearance": "Appearance", @@ -191,6 +194,86 @@ "saveFailed": "Failed to save settings", "loadFailed": "Failed to load settings" }, + "storage": { + "title": "Storage", + "subtitle": "New uploads go to the active backend; existing images keep their backend", + "activeBackend": "Active backend", + "backendLocal": "Local disk", + "backendCloud": "Cloud storage", + "backendS3": "Cloud storage", + "switchHint": "Switching only affects new uploads", + "provider": "Preset", + "providerR2": "Cloudflare R2", + "providerCos": "Tencent COS", + "providerOss": "Alibaba OSS", + "providerAws": "AWS S3", + "providerCustom": "Custom", + "endpoint": "Endpoint", + "region": "Region", + "bucket": "Bucket", + "prefix": "Key prefix (optional)", + "prefixPlaceholder": "e.g. pichost", + "forcePathStyle": "Force path-style access", + "accessKey": "Access Key", + "secretKey": "Secret Key", + "servingMode": "Serving mode", + "servingProxy": "Proxy (same origin)", + "servingPublic": "Public URL (302 to CDN)", + "servingHint": "In public mode, configure referer protection on CDN/bucket", + "publicUrl": "Public base URL", + "publicUrlPlaceholder": "https://cdn.example.com", + "testConnection": "Test connection", + "testSuccess": "Connection successful", + "testFailed": "Connection failed", + "saved": "Storage settings saved", + "saveFailed": "Failed to save storage settings", + "loadFailed": "Failed to load storage settings", + "envOverride": "Storage is overridden by environment variables", + "pageTitle": "Storage management", + "pageSubtitle": "Manage backends; new uploads go to the default backend", + "addBackend": "Add storage", + "editBackend": "Edit storage", + "formCreateHint": "Configure cloud storage; set as default after saving to receive new uploads", + "formEditHint": "Update connection settings; leave keys blank to keep existing secrets", + "sectionBasic": "Basic", + "sectionConnection": "Connection", + "sectionCredentials": "Credentials", + "sectionAccess": "Access", + "sectionQuota": "Quota", + "advancedOptions": "Advanced", + "servingProxyShort": "Proxy", + "servingPublicShort": "Public", + "backendName": "Display name", + "badgeDefault": "Default", + "badgeDisabled": "Disabled", + "badgeEnabled": "Enabled", + "statTotal": "Total", + "statUsed": "Used", + "statFree": "Free", + "usageRate": "Usage", + "indexImages": "PicHost images {count} · {size}", + "setDefault": "Set as default", + "actionSetDefault": "Default", + "actionTest": "Test", + "disable": "Disable", + "enable": "Enable", + "usageCount": "{count} images", + "quotaTotal": "Plan size", + "quotaPlaceholder": "e.g. 10", + "quotaHint": "Enter your cloud plan size; used space is calculated from PicHost image index", + "quotaRequired": "Enter a valid plan size", + "notConfigured": "Not configured", + "created": "Storage backend added", + "deleted": "Storage backend deleted", + "deleteFailed": "Delete failed", + "deleteBlockedImages": "This backend still has images. Move or delete them before removing the backend.", + "defaultUpdated": "Default backend updated", + "deleteConfirmTitle": "Delete storage", + "deleteConfirmBody": "Delete “{name}”? This cannot be undone.", + "secretsRequired": "Access Key and Secret Key are required for new storage", + "manageStorage": "Manage object storage", + "manageStorageHint": "Configure R2 / S3 / COS backends and URL serving" + }, "stats": { "title": "Statistics", "subtitle": "Overview and gallery", diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json index e42dbd0..5214a0c 100644 --- a/i18n/locales/zh-CN.json +++ b/i18n/locales/zh-CN.json @@ -10,6 +10,7 @@ "cancel": "取消", "copy": "复制", "delete": "删除", + "edit": "编辑", "search": "搜索", "refresh": "刷新", "back": "返回", @@ -17,12 +18,14 @@ "notConfigured": "(未配置)", "countImages": "{count} 张", "itemsUnit": "条", - "imagesUnit": "张" + "imagesUnit": "张", + "more": "更多" }, "nav": { "api": "API", "stats": "统计", "settings": "设置", + "storage": "存储", "menu": "菜单", "language": "语言", "appearance": "外观", @@ -191,6 +194,86 @@ "saveFailed": "保存服务器配置失败", "loadFailed": "加载设置失败" }, + "storage": { + "title": "存储设置", + "subtitle": "新上传写入活动后端;已有图片仍按原后端读取", + "activeBackend": "活动后端", + "backendLocal": "本地磁盘", + "backendCloud": "云存储", + "backendS3": "云存储", + "switchHint": "切换仅影响新上传,已有图片不受影响", + "provider": "预设", + "providerR2": "Cloudflare R2", + "providerCos": "腾讯云 COS", + "providerOss": "阿里云 OSS", + "providerAws": "AWS S3", + "providerCustom": "自定义", + "endpoint": "Endpoint", + "region": "Region", + "bucket": "Bucket", + "prefix": "对象前缀(可选)", + "prefixPlaceholder": "例如 pichost", + "forcePathStyle": "强制 Path-Style 访问", + "accessKey": "Access Key", + "secretKey": "Secret Key", + "servingMode": "访问方式", + "servingProxy": "代理(PicHost 同源)", + "servingPublic": "公开 URL(302 到 CDN)", + "servingHint": "公开模式下 Referer 防盗链需在 CDN/桶策略侧配置", + "publicUrl": "公开访问 URL", + "publicUrlPlaceholder": "https://cdn.example.com", + "testConnection": "测试连接", + "testSuccess": "连接成功", + "testFailed": "连接失败", + "saved": "存储配置已保存", + "saveFailed": "保存存储配置失败", + "loadFailed": "加载存储配置失败", + "envOverride": "存储配置已由环境变量覆盖,无法在后台修改", + "pageTitle": "存储管理", + "pageSubtitle": "管理多个存储后端,新上传写入默认后端", + "addBackend": "添加存储", + "editBackend": "编辑存储", + "formCreateHint": "配置云存储连接,保存后可设为默认写入目标", + "formEditHint": "修改连接信息;留空密钥则保持原值", + "sectionBasic": "基本信息", + "sectionConnection": "连接配置", + "sectionCredentials": "访问凭证", + "sectionAccess": "访问方式", + "sectionQuota": "容量配额", + "advancedOptions": "高级选项", + "servingProxyShort": "代理", + "servingPublicShort": "公开", + "backendName": "显示名称", + "badgeDefault": "默认", + "badgeDisabled": "已停用", + "badgeEnabled": "已启用", + "statTotal": "总量", + "statUsed": "已用", + "statFree": "剩余", + "usageRate": "使用率", + "indexImages": "PicHost 图片 {count} 张 · {size}", + "setDefault": "设为默认", + "actionSetDefault": "设默认", + "actionTest": "测试", + "disable": "停用", + "enable": "启用", + "usageCount": "{count} 张图片", + "quotaTotal": "套餐总量", + "quotaPlaceholder": "例如 10", + "quotaHint": "填写云存储套餐总量;已用按 PicHost 图片索引自动统计", + "quotaRequired": "请填写有效的套餐总量", + "notConfigured": "未配置", + "created": "存储后端已添加", + "deleted": "存储后端已删除", + "deleteFailed": "删除失败", + "deleteBlockedImages": "该后端仍有图片,请先迁移或删除图片后再删除", + "defaultUpdated": "默认后端已更新", + "deleteConfirmTitle": "删除存储", + "deleteConfirmBody": "确定删除「{name}」?此操作不可撤销。", + "secretsRequired": "新建存储需填写 Access Key 与 Secret Key", + "manageStorage": "管理对象存储", + "manageStorageHint": "配置 R2 / S3 / COS 等多后端与直链策略" + }, "stats": { "title": "统计", "subtitle": "数据概览与图库记录", diff --git a/package-lock.json b/package-lock.json index 93e71e4..2fcc5cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "pichost", - "version": "1.0.4", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pichost", - "version": "1.0.4", + "version": "1.1.0", "hasInstallScript": true, "license": "MIT", "dependencies": { + "@aws-sdk/client-s3": "^3.876.0", "@iconify-json/lucide": "^1.2.114", "@nuxt/ui": "^4.9.0", "@nuxtjs/i18n": "^10.6.0", @@ -70,6 +71,314 @@ "@types/json-schema": "^7.0.15" } }, + "node_modules/@aws-sdk/checksums": { + "version": "3.1000.29", + "resolved": "https://registry.npmmirror.com/@aws-sdk/checksums/-/checksums-3.1000.29.tgz", + "integrity": "sha512-Dtu0gr4dnATZAPwEYbpCsG+MpLM7OAliy2gTepEFQwl1vZ6DL3QMH2FveMa3HLvPsOdhJsPRB3KtxVhph9T75A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1118.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/client-s3/-/client-s3-3.1118.0.tgz", + "integrity": "sha512-nh46pLKeRNFvTOZAJMfYR7kZ/KzhSqzhXVIJSwl4jJ/b2D+KOXL2jeOCryOvGgwpUQo9YNWFUg63cm2qr5MX9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/checksums": "^3.1000.29", + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/credential-provider-node": "^3.972.81", + "@aws-sdk/middleware-sdk-s3": "^3.972.75", + "@aws-sdk/signature-v4-multi-region": "^3.996.46", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.977.9", + "resolved": "https://registry.npmmirror.com/@aws-sdk/core/-/core-3.977.9.tgz", + "integrity": "sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@aws-sdk/xml-builder": "^3.972.40", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.33.3", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.70", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.70.tgz", + "integrity": "sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.72", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.72.tgz", + "integrity": "sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.15", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.15.tgz", + "integrity": "sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/credential-provider-env": "^3.972.70", + "@aws-sdk/credential-provider-http": "^3.972.72", + "@aws-sdk/credential-provider-login": "^3.972.77", + "@aws-sdk/credential-provider-process": "^3.972.70", + "@aws-sdk/credential-provider-sso": "^3.973.14", + "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.77", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.77.tgz", + "integrity": "sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.81", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.81.tgz", + "integrity": "sha512-Rml+WitoFvXmv6JZ18U/xGdGDGGvB/mOin0ya0lTnTrdC0Z1lrVxTYh7iNklZBcvcRMrs4DoEf6xy1KWyrLQQw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.70", + "@aws-sdk/credential-provider-http": "^3.972.72", + "@aws-sdk/credential-provider-ini": "^3.973.15", + "@aws-sdk/credential-provider-process": "^3.972.70", + "@aws-sdk/credential-provider-sso": "^3.973.14", + "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.70", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.70.tgz", + "integrity": "sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.14", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.14.tgz", + "integrity": "sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/token-providers": "3.1116.0", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.76", + "resolved": "https://registry.npmmirror.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.76.tgz", + "integrity": "sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.75", + "resolved": "https://registry.npmmirror.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.75.tgz", + "integrity": "sha512-wMIsNumRVKaNMKhvU/s9VrdEwE8S6gSzXp4RygFG5BEMnGkkXf8cjh8zf7cKJBpUDpqTWqwbz5isEgp9rH6Lng==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/signature-v4-multi-region": "^3.996.46", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.44", + "resolved": "https://registry.npmmirror.com/@aws-sdk/nested-clients/-/nested-clients-3.997.44.tgz", + "integrity": "sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/signature-v4-multi-region": "^3.996.46", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.46", + "resolved": "https://registry.npmmirror.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.46.tgz", + "integrity": "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1116.0", + "resolved": "https://registry.npmmirror.com/@aws-sdk/token-providers/-/token-providers-3.1116.0.tgz", + "integrity": "sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.5", + "resolved": "https://registry.npmmirror.com/@aws-sdk/types/-/types-3.974.5.tgz", + "integrity": "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.40", + "resolved": "https://registry.npmmirror.com/@aws-sdk/xml-builder/-/xml-builder-3.972.40.tgz", + "integrity": "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -6508,6 +6817,87 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@smithy/core": { + "version": "3.33.3", + "resolved": "https://registry.npmmirror.com/@smithy/core/-/core-3.33.3.tgz", + "integrity": "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmmirror.com/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/@smithy/fetch-http-handler/-/fetch-http-handler-5.7.2.tgz", + "integrity": "sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.11.3", + "resolved": "https://registry.npmmirror.com/@smithy/node-http-handler/-/node-http-handler-4.11.3.tgz", + "integrity": "sha512-2jY1tSpERfPfWqyBV2pH+iGFaghVsIJszJNsT7hxtQYhVJpWDyc0LqOWI+nXOxOAHaEfZ4PXXtp1wW1TGpHhkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.7.3", + "resolved": "https://registry.npmmirror.com/@smithy/signature-v4/-/signature-v4-5.7.3.tgz", + "integrity": "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.17.2", + "resolved": "https://registry.npmmirror.com/@smithy/types/-/types-4.17.2.tgz", + "integrity": "sha512-FOKpVZob9MPTn2znRzGrnsMHv7BOsKVw3XiP/cOyYLDVZ9qKp4nifIiSCuUU/fIj5Vu0UOAxCFr+qRAtG0NUkA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@speed-highlight/core": { "version": "1.2.17", "resolved": "https://registry.npmmirror.com/@speed-highlight/core/-/core-1.2.17.tgz", @@ -9263,6 +9653,12 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmmirror.com/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "5.0.7", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.7.tgz", diff --git a/package.json b/package.json index dc5b45a..e2f2ab3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pichost", - "version": "1.0.4", - "description": "Self-hosted image hosting for your NAS / home server — Nuxt admin UI, local disk storage, server-side WebP compression, referer protection, Twikoo/EasyImage compatible.", + "version": "1.1.0", + "description": "Self-hosted image hosting for your NAS / home server — Nuxt admin UI, local disk or S3-compatible storage, server-side WebP compression, referer protection, Twikoo/EasyImage compatible.", "homepage": "https://github.com/O96u/PicHost#readme", "repository": { "type": "git", @@ -36,6 +36,7 @@ "reset-password": "node server/cli/reset-password.mjs" }, "dependencies": { + "@aws-sdk/client-s3": "^3.876.0", "@iconify-json/lucide": "^1.2.114", "@nuxt/ui": "^4.9.0", "@nuxtjs/i18n": "^10.6.0", diff --git a/scripts/assign-image-owner.ps1 b/scripts/assign-image-owner.ps1 new file mode 100644 index 0000000..0c3b750 --- /dev/null +++ b/scripts/assign-image-owner.ps1 @@ -0,0 +1,146 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Assign userId to image .meta.json files that are missing it (shows as 系统/API). + +.PARAMETER DataDir + PicHost data directory (contains images/, twikoo/, pichost.db). Default: ./data + +.PARAMETER UserId + Target user id. If omitted, uses the first admin from pichost.db. + +.PARAMETER Apply + Actually write files. Without this flag, only prints what would change (dry-run). + +.EXAMPLE + cd D:\Ou\PicHost + .\scripts\assign-image-owner.ps1 + +.EXAMPLE + .\scripts\assign-image-owner.ps1 -DataDir D:\pic-host\data -Apply + +.EXAMPLE + .\scripts\assign-image-owner.ps1 -UserId 1 -Apply +#> +[CmdletBinding()] +param( + [string] $DataDir = (Join-Path (Get-Location) 'data'), + [int] $UserId = 0, + [switch] $Apply +) + +$ErrorActionPreference = 'Stop' + +function Resolve-AdminUserId { + param([string] $DbPath) + + if (-not (Test-Path -LiteralPath $DbPath)) { + Write-Warning "pichost.db not found at $DbPath; using UserId = 1" + return 1 + } + + $repoRoot = Split-Path -Parent $PSScriptRoot + $nodeFile = Join-Path $env:TEMP "pichost-read-admin-$PID.mjs" + @' +import { DatabaseSync } from "node:sqlite"; +const db = new DatabaseSync(process.argv[2]); +const row = db.prepare("SELECT id FROM users WHERE role = 'admin' ORDER BY id ASC LIMIT 1").get(); +if (!row) process.exit(2); +process.stdout.write(String(row.id)); +db.close(); +'@ | Set-Content -LiteralPath $nodeFile -Encoding utf8 + + Push-Location $repoRoot + try { + $output = & node $nodeFile $DbPath 2>&1 + if ($LASTEXITCODE -eq 2) { + throw "No admin user in database. Run /setup first or pass -UserId explicitly." + } + if ($LASTEXITCODE -ne 0) { + throw "Failed to read admin from database: $output" + } + return [int]$output + } + finally { + Pop-Location + Remove-Item -LiteralPath $nodeFile -ErrorAction SilentlyContinue + } +} + +function Read-MetaJson { + param([string] $Path) + $raw = [System.IO.File]::ReadAllText($Path, [System.Text.UTF8Encoding]::new($false)) + return $raw | ConvertFrom-Json +} + +function Write-MetaJson { + param( + [string] $Path, + [object] $Meta + ) + # Compact single-line JSON, same style as PicHost putImage() + $json = $Meta | ConvertTo-Json -Compress -Depth 10 + $utf8 = [System.Text.UTF8Encoding]::new($false) + [System.IO.File]::WriteAllText($Path, $json, $utf8) +} + +if (-not (Test-Path -LiteralPath $DataDir)) { + throw "DataDir not found: $DataDir" +} + +$resolved = Resolve-Path -LiteralPath $DataDir +$DataDir = if ($resolved.ProviderPath) { $resolved.ProviderPath } else { $resolved.Path } + +if ($UserId -le 0) { + $UserId = Resolve-AdminUserId -DbPath (Join-Path $DataDir 'pichost.db') +} + +Write-Host "DataDir : $DataDir" +Write-Host "UserId : $UserId" +Write-Host "Mode : $(if ($Apply) { 'APPLY (will modify files)' } else { 'DRY-RUN (pass -Apply to write)' })" +Write-Host '' + +$metaFiles = Get-ChildItem -LiteralPath $DataDir -Recurse -File -Filter '*.meta.json' | + Where-Object { $_.Name -notlike 'pichost.db*' } + +if (-not $metaFiles) { + Write-Host 'No .meta.json files found under DataDir.' + exit 0 +} + +$patched = 0 +$skipped = 0 +$errors = 0 + +foreach ($file in $metaFiles) { + try { + $meta = Read-MetaJson -Path $file.FullName + $current = $meta.PSObject.Properties['userId'] + $hasUserId = $null -ne $current -and $null -ne $current.Value + + if ($hasUserId) { + $skipped++ + continue + } + + $relative = [System.IO.Path]::GetRelativePath($DataDir, $file.FullName) + Write-Host "[patch] $relative -> userId $UserId" + + if ($Apply) { + $meta | Add-Member -NotePropertyName userId -NotePropertyValue $UserId -Force + Write-MetaJson -Path $file.FullName -Meta $meta + } + + $patched++ + } + catch { + $errors++ + Write-Warning "Failed: $($file.FullName) — $_" + } +} + +Write-Host '' +Write-Host "Done. patched=$patched skipped(already had userId)=$skipped errors=$errors" +if (-not $Apply -and $patched -gt 0) { + Write-Host 'Re-run with -Apply to write changes.' +} diff --git a/server/api/admin/storage/[id].delete.ts b/server/api/admin/storage/[id].delete.ts new file mode 100644 index 0000000..5d58044 --- /dev/null +++ b/server/api/admin/storage/[id].delete.ts @@ -0,0 +1,39 @@ +import { requireAdminAuth } from '../../../utils/access' +import { createApiError } from '../../../utils/api-error' +import { + deleteStorageBackend, + isStorageEnvConfigured, + LOCAL_BACKEND_ID +} from '../../../utils/storage-backends' + +export default defineEventHandler(async (event) => { + await requireAdminAuth(event) + + if (isStorageEnvConfigured()) { + createApiError(event, 'FORBIDDEN', '存储配置已由环境变量覆盖,无法在后台修改', 409) + } + + const id = getRouterParam(event, 'id') + if (!id) { + createApiError(event, 'INVALID_REQUEST', '缺少存储后端 ID', 400) + } + + if (id === LOCAL_BACKEND_ID) { + createApiError(event, 'INVALID_REQUEST', '本地磁盘后端不可删除', 400) + } + + try { + deleteStorageBackend(id) + } catch (error) { + const message = error instanceof Error ? error.message : '删除失败' + if (message.includes('stored images')) { + createApiError(event, 'INVALID_REQUEST', '该后端仍有图片,无法删除', 400) + } + if (message.includes('default backend')) { + createApiError(event, 'INVALID_REQUEST', '请先切换默认后端后再删除', 400) + } + createApiError(event, 'INVALID_REQUEST', message, 400) + } + + return { success: true } +}) diff --git a/server/api/admin/storage/[id].patch.ts b/server/api/admin/storage/[id].patch.ts new file mode 100644 index 0000000..a4375ed --- /dev/null +++ b/server/api/admin/storage/[id].patch.ts @@ -0,0 +1,129 @@ +import { requireAdminAuth } from '../../../utils/access' +import { createApiError } from '../../../utils/api-error' +import { + isStorageEnvConfigured, + LOCAL_BACKEND_ID, + updateStorageBackend, + getStorageBackendRow, + setDefaultBackend, + type StorageBackendPatch +} from '../../../utils/storage-backends' +import { parseS3Config, validateS3Config } from '../../../utils/storage/s3' +import { parseQuotaBytes } from '../../../utils/storage-capacity' +import type { ServingMode } from '../../../utils/storage/types' + +interface StoragePatchBody { + name?: string + config?: { + endpoint?: string + region?: string + bucket?: string + prefix?: string + forcePathStyle?: boolean + } + secrets?: { + accessKeyId?: string + secretAccessKey?: string + } + servingMode?: ServingMode + publicUrl?: string + quotaBytes?: number | null + enabled?: boolean + isDefault?: boolean +} + +export default defineEventHandler(async (event) => { + await requireAdminAuth(event) + + if (isStorageEnvConfigured()) { + createApiError(event, 'FORBIDDEN', '存储配置已由环境变量覆盖,无法在后台修改', 409) + } + + const id = getRouterParam(event, 'id') + if (!id) { + createApiError(event, 'INVALID_REQUEST', '缺少存储后端 ID', 400) + } + + const row = getStorageBackendRow(id) + if (!row) { + createApiError(event, 'INVALID_REQUEST', '存储后端不存在', 404) + } + + const body = await readBody(event).catch( + (): StoragePatchBody => ({}) + ) + + if (id === LOCAL_BACKEND_ID && body.isDefault === false) { + createApiError(event, 'INVALID_REQUEST', '必须保留一个默认存储后端', 400) + } + + const patch: StorageBackendPatch = {} + + if (body.name !== undefined) { + patch.name = body.name.trim() || row.name + } + + if (body.config !== undefined && row.type === 's3') { + const current = parseS3Config(row.config_json) ?? { + endpoint: '', + region: 'auto', + bucket: '' + } + const next = { + endpoint: body.config.endpoint?.trim() ?? current.endpoint, + region: body.config.region?.trim() ?? current.region, + bucket: body.config.bucket?.trim() ?? current.bucket, + prefix: body.config.prefix?.trim() || undefined, + forcePathStyle: body.config.forcePathStyle ?? current.forcePathStyle + } + const error = validateS3Config(next) + if (error) { + createApiError(event, 'INVALID_REQUEST', error, 400) + } + patch.config = next + } + + if (body.secrets !== undefined && row.type === 's3') { + patch.secrets = body.secrets + } + + if (body.servingMode !== undefined) { + if (body.servingMode !== 'proxy' && body.servingMode !== 'public') { + createApiError(event, 'INVALID_REQUEST', '访问方式无效', 400) + } + patch.servingMode = body.servingMode + } + + if (body.publicUrl !== undefined) { + patch.publicUrl = body.publicUrl.trim() + } + + if (body.quotaBytes !== undefined) { + if (row.type === 's3') { + const quotaBytes = parseQuotaBytes(body.quotaBytes) + if (!quotaBytes) { + createApiError(event, 'INVALID_REQUEST', '请填写有效的存储套餐总量', 400) + } + patch.quotaBytes = quotaBytes + } + } + + if (body.enabled !== undefined) { + if (id === LOCAL_BACKEND_ID && !body.enabled) { + createApiError(event, 'INVALID_REQUEST', '本地磁盘后端不可停用', 400) + } + patch.enabled = body.enabled + } + + if (body.isDefault !== undefined) { + patch.isDefault = body.isDefault + } + + updateStorageBackend(id, patch) + + if (body.isDefault) { + setDefaultBackend(id) + } + + return { success: true } +}) diff --git a/server/api/admin/storage/[id]/test.post.ts b/server/api/admin/storage/[id]/test.post.ts new file mode 100644 index 0000000..077d75e --- /dev/null +++ b/server/api/admin/storage/[id]/test.post.ts @@ -0,0 +1,34 @@ +import { requireAdminAuth } from '../../../../utils/access' +import { createApiError } from '../../../../utils/api-error' +import { + createBackendInstance, + getStorageBackendRow, + isStorageEnvConfigured +} from '../../../../utils/storage-backends' + +export default defineEventHandler(async (event) => { + await requireAdminAuth(event) + + if (isStorageEnvConfigured()) { + createApiError(event, 'FORBIDDEN', '存储配置已由环境变量覆盖,无法在后台测试', 409) + } + + const id = getRouterParam(event, 'id') + if (!id) { + createApiError(event, 'INVALID_REQUEST', '缺少存储后端 ID', 400) + } + + const row = getStorageBackendRow(id) + if (!row) { + createApiError(event, 'INVALID_REQUEST', '存储后端不存在', 404) + } + + try { + const backend = createBackendInstance(row) + const result = await backend.testConnection() + return result + } catch (error) { + const message = error instanceof Error ? error.message : '连接失败' + return { ok: false, message } + } +}) diff --git a/server/api/admin/storage/index.get.ts b/server/api/admin/storage/index.get.ts new file mode 100644 index 0000000..f38bc08 --- /dev/null +++ b/server/api/admin/storage/index.get.ts @@ -0,0 +1,39 @@ +import { requireAdminAuth } from '../../../utils/access' +import { + getBackendUsageStats, + isStorageEnvConfigured, + listStorageBackends +} from '../../../utils/storage-backends' +import { buildBackendCapacity } from '../../../utils/storage-capacity' +import { getActiveBackendRow } from '../../../utils/storage/resolver' +import type { StorageBackendInfo } from '../../../utils/storage/types' + +export default defineEventHandler(async (event) => { + await requireAdminAuth(event) + + const rows = listStorageBackends() + const backends = await Promise.all(rows.map(async (backend: StorageBackendInfo & { + secretsMasked: Record + }) => { + const usage = getBackendUsageStats(backend.id) + const capacity = await buildBackendCapacity( + backend.id, + backend.type, + usage.bytes, + backend.quotaBytes + ) + return { + ...backend, + usage, + capacity + } + })) + + const activeBackend = getActiveBackendRow() + + return { + backends, + activeBackendId: activeBackend?.id ?? 'local', + envOverride: isStorageEnvConfigured() + } +}) diff --git a/server/api/admin/storage/index.post.ts b/server/api/admin/storage/index.post.ts new file mode 100644 index 0000000..f5c9bca --- /dev/null +++ b/server/api/admin/storage/index.post.ts @@ -0,0 +1,83 @@ +import { requireAdminAuth } from '../../../utils/access' +import { createApiError } from '../../../utils/api-error' +import { + insertStorageBackend, + isStorageEnvConfigured +} from '../../../utils/storage-backends' +import { validateS3Config } from '../../../utils/storage/s3' +import { parseQuotaBytes } from '../../../utils/storage-capacity' +import type { ServingMode } from '../../../utils/storage/types' + +interface StorageCreateBody { + name?: string + config?: { + endpoint?: string + region?: string + bucket?: string + prefix?: string + forcePathStyle?: boolean + } + secrets?: { + accessKeyId?: string + secretAccessKey?: string + } + servingMode?: ServingMode + publicUrl?: string + quotaBytes?: number | null + enabled?: boolean + isDefault?: boolean +} + +export default defineEventHandler(async (event) => { + await requireAdminAuth(event) + + if (isStorageEnvConfigured()) { + createApiError(event, 'FORBIDDEN', '存储配置已由环境变量覆盖,无法在后台修改', 409) + } + + const body = await readBody(event).catch( + (): StorageCreateBody => ({}) + ) + + const name = body.name?.trim() || 'S3 兼容存储' + const config = { + endpoint: body.config?.endpoint?.trim() ?? '', + region: body.config?.region?.trim() || 'auto', + bucket: body.config?.bucket?.trim() ?? '', + prefix: body.config?.prefix?.trim() || undefined, + forcePathStyle: body.config?.forcePathStyle ?? false + } + + const error = validateS3Config(config) + if (error) { + createApiError(event, 'INVALID_REQUEST', error, 400) + } + + const accessKeyId = body.secrets?.accessKeyId?.trim() ?? '' + const secretAccessKey = body.secrets?.secretAccessKey?.trim() ?? '' + if (!accessKeyId || !secretAccessKey) { + createApiError(event, 'INVALID_REQUEST', 'Access Key 与 Secret Key 不能为空', 400) + } + + if (body.servingMode && body.servingMode !== 'proxy' && body.servingMode !== 'public') { + createApiError(event, 'INVALID_REQUEST', '访问方式无效', 400) + } + + const quotaBytes = parseQuotaBytes(body.quotaBytes) + if (!quotaBytes) { + createApiError(event, 'INVALID_REQUEST', '请填写有效的存储套餐总量', 400) + } + + const id = insertStorageBackend({ + name, + config, + secrets: { accessKeyId, secretAccessKey }, + servingMode: body.servingMode, + publicUrl: body.publicUrl?.trim(), + quotaBytes, + enabled: body.enabled, + isDefault: body.isDefault + }) + + return { success: true, id } +}) diff --git a/server/plugins/startup.ts b/server/plugins/startup.ts index 49f2566..ff7d696 100644 --- a/server/plugins/startup.ts +++ b/server/plugins/startup.ts @@ -1,5 +1,7 @@ -import { getDataDir } from '../utils/storage' +import { getDataDir } from '../utils/data-dir' import { runAutoDeleteCleanup } from '../utils/auto-delete' +import { migrateLocalImagesToIndex } from '../utils/image-index' +import { ensureDefaultBackends } from '../utils/storage-backends' import { logInfo } from '../utils/logger' export default defineNitroPlugin(() => { @@ -9,9 +11,17 @@ export default defineNitroPlugin(() => { dataDir: getDataDir(), nodeEnv: process.env.NODE_ENV || 'development', imageBaseUrl: process.env.IMAGE_BASE_URL || '(request origin)', + storageBackend: process.env.STORAGE_BACKEND || 'db', logLevel: process.env.LOG_LEVEL || 'info' }) + ensureDefaultBackends() + void migrateLocalImagesToIndex().then((count) => { + if (count > 0) { + logInfo('migrated local images to index', { count }) + } + }) + void runAutoDeleteCleanup().then((result) => { if (!result.skipped && (result.deleted > 0 || result.failed > 0)) { logInfo('startup auto-delete', { ...result }) diff --git a/server/utils/auto-delete.ts b/server/utils/auto-delete.ts index 6721a44..d31d01b 100644 --- a/server/utils/auto-delete.ts +++ b/server/utils/auto-delete.ts @@ -1,6 +1,7 @@ import { insertActivityLog, listAdminUserIds, listUserAutoDeletePolicies, type AutoDeletePolicy } from './db' import { getGlobalAutoDeletePolicy } from './env' -import { deleteImage, headImage, listImageKeys, type StoredImage } from './storage' +import { deleteImage, type StoredImage } from './storage' +import { listImageKeysForAutoDelete } from './image-index' import { logInfo, logWarn } from './logger' export interface AutoDeleteResult { @@ -73,23 +74,20 @@ export async function runAutoDeleteCleanup(): Promise { } const nowMs = Date.now() - const keys = await listImageKeys() + const images = await listImageKeysForAutoDelete() let deleted = 0 let failed = 0 - for (const key of keys) { - const image = await headImage(key) - if (!image) continue - + for (const image of images) { if (!shouldAutoDeleteImage(image, globalPolicy, adminUserIds, userPolicies, nowMs)) { continue } try { - await deleteImage(key) + await deleteImage(image.key) insertActivityLog({ action: 'delete', - key, + key: image.key, originalName: image.originalName, size: image.size, contentType: image.contentType, @@ -97,7 +95,7 @@ export async function runAutoDeleteCleanup(): Promise { }) deleted++ } catch (error) { - logWarn('auto-delete failed', { key, error }) + logWarn('auto-delete failed', { key: image.key, error }) failed++ } } diff --git a/server/utils/data-dir.ts b/server/utils/data-dir.ts new file mode 100644 index 0000000..07916cd --- /dev/null +++ b/server/utils/data-dir.ts @@ -0,0 +1,5 @@ +import { join } from 'node:path' + +export function getDataDir(): string { + return process.env.DATA_DIR || join(process.cwd(), 'data') +} diff --git a/server/utils/db.ts b/server/utils/db.ts index 467f5a3..acb98ae 100644 --- a/server/utils/db.ts +++ b/server/utils/db.ts @@ -1,7 +1,7 @@ import { DatabaseSync } from 'node:sqlite' import { mkdirSync } from 'node:fs' import { join } from 'node:path' -import { getDataDir } from './storage' +import { getDataDir } from './data-dir' export type LogAction = 'upload' | 'delete' export type LogSource = 'web' | 'api' | 'twikoo' @@ -29,7 +29,7 @@ export interface ActivityLogInput { let db: DatabaseSync | null = null -function getDb(): DatabaseSync { +export function getDb(): DatabaseSync { if (db) return db const dataDir = getDataDir() diff --git a/server/utils/image-index.test.ts b/server/utils/image-index.test.ts new file mode 100644 index 0000000..b92a70a --- /dev/null +++ b/server/utils/image-index.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from 'vitest' +import { validateImageKey } from './image-key' + +describe('image index key validation', () => { + it('accepts standard image keys used in index', () => { + expect(validateImageKey('images/2026/03/abc.webp')).toBe(true) + expect(validateImageKey('twikoo/2026/03/abc.webp')).toBe(true) + }) + + it('rejects path traversal keys', () => { + expect(validateImageKey('../secret')).toBe(false) + expect(validateImageKey('images/../../etc/passwd')).toBe(false) + }) +}) diff --git a/server/utils/image-index.ts b/server/utils/image-index.ts new file mode 100644 index 0000000..de32a0a --- /dev/null +++ b/server/utils/image-index.ts @@ -0,0 +1,418 @@ +import { promises as fs } from 'node:fs' +import { join } from 'node:path' +import { getDb } from './db' +import { getDataDir } from './data-dir' +import { isValidFolderName, validateImageKey, DEFAULT_FOLDER } from './image-key' +import { + ensureDefaultBackends, + ensureStorageSchema, + LOCAL_BACKEND_ID +} from './storage-backends' +import type { PaginatedResult, StoredImage, StoredImageMeta } from './storage/types' + +const META_SUFFIX = '.meta.json' + +export interface ImageIndexRow { + key: string + backend_id: string + user_id: number | null + folder: string + original_name: string + content_type: string + size: number + uploaded_at: string +} + +function keyToFilePath(key: string): string { + return join(getDataDir(), ...key.split('/')) +} + +async function readLocalMeta(key: string): Promise> { + try { + const raw = await fs.readFile(keyToFilePath(key) + META_SUFFIX, 'utf8') + return JSON.parse(raw) as Partial + } catch { + return {} + } +} + +function rowToStoredImage(row: ImageIndexRow): StoredImage { + const uploadedMs = new Date(row.uploaded_at).getTime() + return { + key: row.key, + backendId: row.backend_id, + originalName: row.original_name, + uploadedAt: row.uploaded_at, + contentType: row.content_type, + size: row.size, + userId: row.user_id, + mtimeMs: Number.isFinite(uploadedMs) ? uploadedMs : Date.now() + } +} + +function buildUserFilterSql( + userFilter: number | 'admin' | undefined, + params: Array +): string { + if (userFilter === undefined || userFilter === 'admin') return '' + params.push(userFilter) + return ' AND user_id = ?' +} + +export function insertImageIndex(input: { + key: string + backendId: string + userId?: number | null + originalName: string + contentType: string + size: number + uploadedAt: string +}): void { + ensureStorageSchema() + const folder = input.key.split('/')[0] ?? DEFAULT_FOLDER + getDb().prepare(` + INSERT INTO images + (key, backend_id, user_id, folder, original_name, content_type, size, uploaded_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(key) DO UPDATE SET + backend_id = excluded.backend_id, + user_id = excluded.user_id, + folder = excluded.folder, + original_name = excluded.original_name, + content_type = excluded.content_type, + size = excluded.size, + uploaded_at = excluded.uploaded_at + `).run( + input.key, + input.backendId, + input.userId ?? null, + folder, + input.originalName, + input.contentType, + input.size, + input.uploadedAt + ) +} + +export function deleteImageIndex(key: string): void { + ensureStorageSchema() + getDb().prepare('DELETE FROM images WHERE key = ?').run(key) +} + +export function getImageIndexRow(key: string): ImageIndexRow | null { + ensureStorageSchema() + const row = getDb().prepare(` + SELECT key, backend_id, user_id, folder, original_name, content_type, size, uploaded_at + FROM images + WHERE key = ? + `).get(key) as ImageIndexRow | undefined + return row ?? null +} + +export function getImageIndexAsStored(key: string): StoredImage | null { + const row = getImageIndexRow(key) + return row ? rowToStoredImage(row) : null +} + +async function readDirNames(path: string): Promise { + try { + const entries = await fs.readdir(path, { withFileTypes: true }) + return entries.filter(e => e.isDirectory()).map(e => e.name) + } catch { + return [] + } +} + +async function readFileNames(path: string): Promise { + try { + const entries = await fs.readdir(path, { withFileTypes: true }) + return entries.filter(e => e.isFile()).map(e => e.name) + } catch { + return [] + } +} + +async function listLocalImageKeys(): Promise { + const keys: string[] = [] + const dataDir = getDataDir() + const prefixes = await readDirNames(dataDir) + + for (const prefix of prefixes) { + if (!isValidFolderName(prefix)) continue + const root = join(dataDir, prefix) + const years = await readDirNames(root) + for (const year of years) { + const months = await readDirNames(join(root, year)) + for (const month of months) { + const files = await readFileNames(join(root, year, month)) + for (const file of files) { + if (file.endsWith(META_SUFFIX)) continue + const key = `${prefix}/${year}/${month}/${file}` + if (validateImageKey(key)) keys.push(key) + } + } + } + } + + return keys +} + +export async function migrateLocalImagesToIndex(): Promise { + ensureDefaultBackends() + const keys = await listLocalImageKeys() + let inserted = 0 + + for (const key of keys) { + const existing = getImageIndexRow(key) + if (existing) continue + + let stat + try { + stat = await fs.stat(keyToFilePath(key)) + } catch { + continue + } + + const meta = await readLocalMeta(key) + const uploadedAt = meta.uploadedAt ?? stat.mtime.toISOString() + + insertImageIndex({ + key, + backendId: LOCAL_BACKEND_ID, + userId: meta.userId ?? null, + originalName: meta.originalName ?? key.split('/').pop() ?? 'image', + contentType: meta.contentType ?? 'application/octet-stream', + size: stat.size, + uploadedAt + }) + inserted++ + } + + return inserted +} + +export async function listFolders(): Promise { + ensureStorageSchema() + const rows = getDb().prepare(` + SELECT DISTINCT folder FROM images ORDER BY folder ASC + `).all() as Array<{ folder: string }> + + const folders = rows.map(row => row.folder).filter(isValidFolderName) + if (!folders.includes(DEFAULT_FOLDER)) { + folders.push(DEFAULT_FOLDER) + } + return folders.sort((a, b) => a.localeCompare(b)) +} + +export async function listFoldersForUser( + userFilter?: number | 'admin' +): Promise { + ensureStorageSchema() + const params: Array = [] + const userSql = buildUserFilterSql(userFilter, params) + + const rows = getDb().prepare(` + SELECT DISTINCT folder FROM images WHERE 1=1${userSql} ORDER BY folder ASC + `).all(...params) as Array<{ folder: string }> + + const folders = rows.map(row => row.folder).filter(isValidFolderName) + if (!folders.length) { + folders.push(DEFAULT_FOLDER) + } + return folders.sort((a, b) => a.localeCompare(b)) +} + +export async function countImages( + folder?: string, + userFilter?: number | 'admin' +): Promise { + ensureStorageSchema() + const params: Array = [] + const clauses: string[] = ['1=1'] + + if (folder && isValidFolderName(folder)) { + clauses.push('folder = ?') + params.push(folder) + } + + const userSql = buildUserFilterSql(userFilter, params) + const row = getDb().prepare(` + SELECT COUNT(*) AS count FROM images WHERE ${clauses.join(' AND ')}${userSql} + `).get(...params) as { count: number } + + return row.count +} + +function paginateSlice( + all: T[], + limit: number, + page?: number +): PaginatedResult { + const total = all.length + const pageSize = limit + const totalPages = Math.max(1, Math.ceil(total / pageSize) || 1) + const requestedPage = Math.max(1, page ?? 1) + const safePage = Math.min(requestedPage, totalPages) + const start = (safePage - 1) * pageSize + const items = all.slice(start, start + pageSize) + + return { items, total, page: safePage, pageSize, totalPages } +} + +export async function listImages(options: { + limit: number + page?: number + folder?: string + userFilter?: number | 'admin' +}): Promise> { + ensureStorageSchema() + const params: Array = [] + const clauses: string[] = ['1=1'] + + if (options.folder && isValidFolderName(options.folder)) { + clauses.push('folder = ?') + params.push(options.folder) + } + + const userSql = buildUserFilterSql(options.userFilter, params) + const rows = getDb().prepare(` + SELECT key, backend_id, user_id, folder, original_name, content_type, size, uploaded_at + FROM images + WHERE ${clauses.join(' AND ')}${userSql} + ORDER BY uploaded_at DESC, key DESC + `).all(...params) as unknown as ImageIndexRow[] + + const sliced = paginateSlice(rows, options.limit, options.page) + return { + ...sliced, + items: sliced.items.map(rowToStoredImage) + } +} + +export async function searchImages(options: { + query: string + limit: number + page?: number + folder?: string + userFilter?: number | 'admin' +}): Promise> { + ensureStorageSchema() + const needle = options.query.trim().toLowerCase() + const params: Array = [] + const clauses: string[] = ['1=1'] + + if (options.folder && isValidFolderName(options.folder)) { + clauses.push('folder = ?') + params.push(options.folder) + } + + const userSql = buildUserFilterSql(options.userFilter, params) + const rows = getDb().prepare(` + SELECT key, backend_id, user_id, folder, original_name, content_type, size, uploaded_at + FROM images + WHERE ${clauses.join(' AND ')}${userSql} + ORDER BY uploaded_at DESC, key DESC + `).all(...params) as unknown as ImageIndexRow[] + + const matches = rows.filter((row) => { + if (!needle) return true + return row.key.toLowerCase().includes(needle) + || row.original_name.toLowerCase().includes(needle) + }) + + const sliced = paginateSlice(matches, options.limit, options.page) + return { + ...sliced, + items: sliced.items.map(rowToStoredImage) + } +} + +export async function getFolderStorageStats( + userFilter?: number | 'admin' +): Promise> { + ensureStorageSchema() + const params: Array = [] + const userSql = buildUserFilterSql(userFilter, params) + + const rows = getDb().prepare(` + SELECT folder, COUNT(*) AS count, COALESCE(SUM(size), 0) AS bytes + FROM images + WHERE 1=1${userSql} + GROUP BY folder + ORDER BY bytes DESC, folder ASC + `).all(...params) as Array<{ folder: string, count: number, bytes: number }> + + return rows +} + +function startOfDayIsoInShanghai(now = new Date()): string { + const shifted = new Date(now.getTime() + 8 * 60 * 60 * 1000) + const y = shifted.getUTCFullYear() + const m = shifted.getUTCMonth() + const d = shifted.getUTCDate() + return new Date(Date.UTC(y, m, d) - 8 * 60 * 60 * 1000).toISOString() +} + +function startOfMonthIsoInShanghai(now = new Date()): string { + const shifted = new Date(now.getTime() + 8 * 60 * 60 * 1000) + const y = shifted.getUTCFullYear() + const m = shifted.getUTCMonth() + return new Date(Date.UTC(y, m, 1) - 8 * 60 * 60 * 1000).toISOString() +} + +export async function getUserScopedStorageStats(userId: number): Promise<{ + storedCount: number + uploadBytesTotal: number + uploadToday: number + uploadMonth: number + byFolder: Array<{ folder: string, count: number, bytes: number }> +}> { + ensureStorageSchema() + const startOfDay = startOfDayIsoInShanghai() + const startOfMonth = startOfMonthIsoInShanghai() + + const rows = getDb().prepare(` + SELECT folder, size, uploaded_at + FROM images + WHERE user_id = ? + `).all(userId) as Array<{ folder: string, size: number, uploaded_at: string }> + + const map = new Map() + let uploadBytesTotal = 0 + let uploadToday = 0 + let uploadMonth = 0 + + for (const row of rows) { + uploadBytesTotal += row.size + if (row.uploaded_at >= startOfDay) uploadToday += 1 + if (row.uploaded_at >= startOfMonth) uploadMonth += 1 + + const current = map.get(row.folder) ?? { count: 0, bytes: 0 } + current.count += 1 + current.bytes += row.size + map.set(row.folder, current) + } + + const byFolder = [...map.entries()] + .map(([folder, stat]) => ({ folder, ...stat })) + .sort((a, b) => b.bytes - a.bytes || a.folder.localeCompare(b.folder)) + + return { + storedCount: rows.length, + uploadBytesTotal, + uploadToday, + uploadMonth, + byFolder + } +} + +export async function listImageKeysForAutoDelete(): Promise { + ensureStorageSchema() + const rows = getDb().prepare(` + SELECT key, backend_id, user_id, folder, original_name, content_type, size, uploaded_at + FROM images + ORDER BY uploaded_at ASC + `).all() as unknown as ImageIndexRow[] + + return rows.map(rowToStoredImage) +} diff --git a/server/utils/image-response.ts b/server/utils/image-response.ts index bec1474..3197d17 100644 --- a/server/utils/image-response.ts +++ b/server/utils/image-response.ts @@ -3,6 +3,10 @@ import type { ImageItem } from '~/types/image' import type { AllowedMimeType } from './constants' import type { StoredImage } from './storage' import { getImageBaseUrl } from './env' +import { getActiveBackendRow } from './storage/resolver' +import { getStorageBackendRow, getStorageBackendFromEnv } from './storage-backends' +import { buildObjectKey, parseS3Config } from './storage/s3' +import type { StorageBackendRow } from './storage/types' export function resolveImageOwner( userId: number | null | undefined, @@ -27,7 +31,8 @@ export function mapStoredImageToItem( ): ImageItem { const item = buildImageItem({ key: stored.key, - baseUrl: getImageBaseUrl(event), + event, + backendId: stored.backendId, originalName: stored.originalName, contentType: stored.contentType, size: stored.size, @@ -47,6 +52,42 @@ export function buildImageUrl(baseUrl: string, key: string): string { return `${normalizedBase}/${key}` } +export function buildPublicImageUrl(backendRow: StorageBackendRow, key: string): string { + const base = backendRow.public_url.replace(/\/$/, '') + if (backendRow.type === 's3') { + const config = parseS3Config(backendRow.config_json) + const objectKey = buildObjectKey(config?.prefix, key) + return `${base}/${objectKey}` + } + return `${base}/${key}` +} + +export function resolveImageUrl( + event: H3Event, + key: string, + backendId?: string +): string { + const envConfig = getStorageBackendFromEnv() + if (envConfig && (!backendId || backendId === 's3-primary')) { + if (envConfig.servingMode === 'public' && envConfig.publicUrl) { + const prefix = envConfig.config.prefix as string | undefined + const objectKey = buildObjectKey(prefix, key) + return `${envConfig.publicUrl.replace(/\/$/, '')}/${objectKey}` + } + return buildImageUrl(getImageBaseUrl(event), key) + } + + const backendRow = backendId + ? getStorageBackendRow(backendId) + : getActiveBackendRow() + + if (backendRow?.serving_mode === 'public' && backendRow.public_url) { + return buildPublicImageUrl(backendRow, key) + } + + return buildImageUrl(getImageBaseUrl(event), key) +} + export function buildMarkdown(url: string, alt: string): string { const safeAlt = alt.replace(/[[\]]/g, '') return `![${safeAlt}](${url})` @@ -68,13 +109,17 @@ function escapeHtml(value: string): string { export function buildImageItem(input: { key: string - baseUrl: string + baseUrl?: string + event?: H3Event + backendId?: string originalName: string contentType: AllowedMimeType | string size: number uploadedAt: string }) { - const url = buildImageUrl(input.baseUrl, input.key) + const url = input.event + ? resolveImageUrl(input.event, input.key, input.backendId) + : buildImageUrl(input.baseUrl ?? '', input.key) const alt = sanitizeOriginalName(input.originalName) return { diff --git a/server/utils/process-image-upload.ts b/server/utils/process-image-upload.ts index 003c822..6ce48d2 100644 --- a/server/utils/process-image-upload.ts +++ b/server/utils/process-image-upload.ts @@ -14,7 +14,7 @@ import { import type { LogSource } from './db' import { generateImageKey } from './image-key' import { buildImageItem, sanitizeOriginalName } from './image-response' -import { getImageBaseUrl, getWebpQuality } from './env' +import { getWebpQuality } from './env' import { insertActivityLog } from './db' import { putImage } from './storage' import { logInfo, logWarn } from './logger' @@ -121,7 +121,6 @@ export async function processSingleImageUpload( const compressed = await compressToWebp(bytes, detectedMime, getWebpQuality(event)) - const imageBaseUrl = getImageBaseUrl(event) const uploadedAt = new Date().toISOString() const key = generateImageKey( compressed.mime, @@ -173,7 +172,7 @@ export async function processSingleImageUpload( return { item: buildImageItem({ key, - baseUrl: imageBaseUrl, + event, originalName, contentType: compressed.mime, size: compressed.bytes.byteLength, diff --git a/server/utils/serve-image.ts b/server/utils/serve-image.ts index d0e7e13..4c3f041 100644 --- a/server/utils/serve-image.ts +++ b/server/utils/serve-image.ts @@ -2,7 +2,9 @@ import type { H3Event } from 'h3' import { IMAGE_CACHE_CONTROL } from './constants' import { getAllowedRefererHostsRaw } from './env' import { validateImageKey } from './image-key' +import { buildPublicImageUrl } from './image-response' import { createImageStream, headImage } from './storage' +import { getBackendRowForKey } from './storage/resolver' /** * 图片直链处理(/{type}/YYYY/MM/file) @@ -82,6 +84,12 @@ export async function serveImageByKey(event: H3Event, key: string) { throw createError({ statusCode: 404, statusMessage: 'Not Found' }) } + const backendRow = await getBackendRowForKey(key) + if (backendRow.serving_mode === 'public' && backendRow.public_url) { + const publicUrl = buildPublicImageUrl(backendRow, key) + return sendRedirect(event, publicUrl, 302) + } + const etag = `"${stored.size}-${Math.round(stored.mtimeMs)}"` setHeader(event, 'ETag', etag) @@ -116,9 +124,9 @@ export async function serveImageByKey(event: H3Event, key: string) { setResponseStatus(event, 206) setHeader(event, 'Content-Range', `bytes ${range.start}-${range.end}/${stored.size}`) setHeader(event, 'Content-Length', range.end - range.start + 1) - return sendStream(event, createImageStream(key, range)) + return sendStream(event, await createImageStream(key, range)) } setHeader(event, 'Content-Length', stored.size) - return sendStream(event, createImageStream(key)) + return sendStream(event, await createImageStream(key)) } diff --git a/server/utils/storage-backends.ts b/server/utils/storage-backends.ts new file mode 100644 index 0000000..d6636d8 --- /dev/null +++ b/server/utils/storage-backends.ts @@ -0,0 +1,406 @@ +import type { DatabaseSync } from 'node:sqlite' +import { getDb } from './db' +import { LocalStorageBackend } from './storage/local' +import { + parseS3Config, + parseS3Secrets, + S3StorageBackend +} from './storage/s3' +import type { + S3BackendConfig, + ServingMode, + StorageBackendInfo, + StorageBackendRow, + StorageBackendType +} from './storage/types' + +export const LOCAL_BACKEND_ID = 'local' +export const S3_BACKEND_ID = 's3-primary' + +function migrateStorageTables(database: DatabaseSync): void { + database.exec(` + CREATE TABLE IF NOT EXISTS storage_backends ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + type TEXT NOT NULL CHECK(type IN ('local', 's3')), + config_json TEXT NOT NULL DEFAULT '{}', + secret_json TEXT NOT NULL DEFAULT '{}', + serving_mode TEXT NOT NULL DEFAULT 'proxy' CHECK(serving_mode IN ('proxy', 'public')), + public_url TEXT NOT NULL DEFAULT '', + quota_bytes INTEGER, + enabled INTEGER NOT NULL DEFAULT 1, + is_default INTEGER NOT NULL DEFAULT 0, + sort_order INTEGER NOT NULL DEFAULT 0 + ); + CREATE TABLE IF NOT EXISTS images ( + key TEXT PRIMARY KEY, + backend_id TEXT NOT NULL, + user_id INTEGER, + folder TEXT NOT NULL, + original_name TEXT NOT NULL, + content_type TEXT NOT NULL, + size INTEGER NOT NULL, + uploaded_at TEXT NOT NULL, + FOREIGN KEY (backend_id) REFERENCES storage_backends(id) + ); + CREATE INDEX IF NOT EXISTS idx_images_uploaded_at ON images(uploaded_at DESC); + CREATE INDEX IF NOT EXISTS idx_images_backend_id ON images(backend_id); + CREATE INDEX IF NOT EXISTS idx_images_user_id ON images(user_id); + CREATE INDEX IF NOT EXISTS idx_images_folder ON images(folder); + `) +} + +export function ensureStorageSchema(): void { + migrateStorageTables(getDb()) +} + +export function ensureDefaultBackends(): void { + ensureStorageSchema() + const db = getDb() + + const local = db.prepare(` + SELECT id FROM storage_backends WHERE id = ? + `).get(LOCAL_BACKEND_ID) + + if (!local) { + db.prepare(` + INSERT INTO storage_backends + (id, name, type, config_json, secret_json, serving_mode, public_url, + quota_bytes, enabled, is_default, sort_order) + VALUES (?, ?, 'local', '{}', '{}', 'proxy', '', NULL, 1, 1, 0) + `).run(LOCAL_BACKEND_ID, '本地磁盘') + } + + const s3 = db.prepare(` + SELECT id FROM storage_backends WHERE id = ? + `).get(S3_BACKEND_ID) + + if (!s3) { + db.prepare(` + INSERT INTO storage_backends + (id, name, type, config_json, secret_json, serving_mode, public_url, + quota_bytes, enabled, is_default, sort_order) + VALUES (?, ?, 's3', '{}', '{}', 'proxy', '', NULL, 0, 0, 1) + `).run(S3_BACKEND_ID, 'S3 兼容存储') + } +} + +function rowToInfo(row: StorageBackendRow, _maskSecrets = true): StorageBackendInfo & { + secretsMasked: Record +} { + const config = row.type === 's3' + ? (parseS3Config(row.config_json) ?? {}) + : {} + let secretsMasked: Record = {} + if (row.type === 's3') { + const secrets = parseS3Secrets(row.secret_json) + secretsMasked = { + accessKeyId: secrets?.accessKeyId + ? maskSecret(secrets.accessKeyId) + : '', + secretAccessKey: secrets?.secretAccessKey + ? maskSecret(secrets.secretAccessKey) + : '' + } + } + + return { + id: row.id, + name: row.name, + type: row.type, + config, + servingMode: row.serving_mode, + publicUrl: row.public_url, + quotaBytes: row.quota_bytes, + enabled: row.enabled === 1, + isDefault: row.is_default === 1, + sortOrder: row.sort_order, + secretsMasked + } +} + +function maskSecret(value: string): string { + if (value.length <= 4) return '****' + return `${value.slice(0, 4)}${'*'.repeat(Math.min(8, value.length - 4))}` +} + +export function listStorageBackendRows(): StorageBackendRow[] { + ensureDefaultBackends() + return getDb().prepare(` + SELECT id, name, type, config_json, secret_json, serving_mode, public_url, + quota_bytes, enabled, is_default, sort_order + FROM storage_backends + ORDER BY sort_order ASC, id ASC + `).all() as unknown as StorageBackendRow[] +} + +export function listStorageBackends(maskSecrets = true): Array +}> { + return listStorageBackendRows().map(row => rowToInfo(row, maskSecrets)) +} + +export function getStorageBackendRow(id: string): StorageBackendRow | null { + ensureDefaultBackends() + const row = getDb().prepare(` + SELECT id, name, type, config_json, secret_json, serving_mode, public_url, + quota_bytes, enabled, is_default, sort_order + FROM storage_backends + WHERE id = ? + `).get(id) as StorageBackendRow | undefined + return row ?? null +} + +export function getDefaultBackendRow(): StorageBackendRow | null { + ensureDefaultBackends() + const row = getDb().prepare(` + SELECT id, name, type, config_json, secret_json, serving_mode, public_url, + quota_bytes, enabled, is_default, sort_order + FROM storage_backends + WHERE is_default = 1 AND enabled = 1 + LIMIT 1 + `).get() as StorageBackendRow | undefined + return row ?? null +} + +export function getBackendIdForKey(key: string): string | null { + ensureStorageSchema() + const row = getDb().prepare(` + SELECT backend_id FROM images WHERE key = ? + `).get(key) as { backend_id: string } | undefined + return row?.backend_id ?? null +} + +export function setDefaultBackend(id: string): void { + const db = getDb() + db.exec('UPDATE storage_backends SET is_default = 0') + db.prepare(` + UPDATE storage_backends SET is_default = 1, enabled = 1 WHERE id = ? + `).run(id) +} + +export interface StorageBackendPatch { + name?: string + config?: Record + secrets?: { accessKeyId?: string, secretAccessKey?: string } + servingMode?: ServingMode + publicUrl?: string + quotaBytes?: number | null + enabled?: boolean + isDefault?: boolean +} + +export function updateStorageBackend(id: string, patch: StorageBackendPatch): void { + const row = getStorageBackendRow(id) + if (!row) { + throw new Error(`Storage backend not found: ${id}`) + } + + const name = patch.name ?? row.name + let configJson = row.config_json + let secretJson = row.secret_json + + if (patch.config !== undefined) { + const current = row.type === 's3' + ? (parseS3Config(row.config_json) ?? {}) + : {} + configJson = JSON.stringify({ ...current, ...patch.config }) + } + + if (patch.secrets !== undefined && row.type === 's3') { + const current = parseS3Secrets(row.secret_json) ?? { + accessKeyId: '', + secretAccessKey: '' + } + const next = { + accessKeyId: patch.secrets.accessKeyId?.trim() + ? patch.secrets.accessKeyId.trim() + : current.accessKeyId, + secretAccessKey: patch.secrets.secretAccessKey?.trim() + ? patch.secrets.secretAccessKey.trim() + : current.secretAccessKey + } + secretJson = JSON.stringify(next) + } + + const servingMode = patch.servingMode ?? row.serving_mode + const publicUrl = patch.publicUrl ?? row.public_url + const quotaBytes = patch.quotaBytes !== undefined + ? patch.quotaBytes + : row.quota_bytes + const enabled = patch.enabled !== undefined + ? (patch.enabled ? 1 : 0) + : row.enabled + + getDb().prepare(` + UPDATE storage_backends + SET name = ?, config_json = ?, secret_json = ?, serving_mode = ?, + public_url = ?, quota_bytes = ?, enabled = ? + WHERE id = ? + `).run(name, configJson, secretJson, servingMode, publicUrl, quotaBytes, enabled, id) + + if (patch.isDefault) { + setDefaultBackend(id) + } +} + +export function isStorageEnvConfigured(): boolean { + return getStorageBackendFromEnv() !== null +} + +export function getStorageBackendFromEnv(): { + type: StorageBackendType + config: Record + secrets: { accessKeyId: string, secretAccessKey: string } + servingMode: ServingMode + publicUrl: string +} | null { + const backendType = process.env.STORAGE_BACKEND?.trim() + if (backendType !== 's3') return null + + const endpoint = process.env.S3_ENDPOINT?.trim() ?? '' + const region = process.env.S3_REGION?.trim() ?? '' + const bucket = process.env.S3_BUCKET?.trim() ?? '' + const accessKeyId = process.env.S3_ACCESS_KEY?.trim() ?? '' + const secretAccessKey = process.env.S3_SECRET_KEY?.trim() ?? '' + + if (!endpoint || !bucket || !accessKeyId || !secretAccessKey) { + return null + } + + return { + type: 's3', + config: { + endpoint, + region: region || 'auto', + bucket, + prefix: process.env.S3_PREFIX?.trim() || undefined, + forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true' + }, + secrets: { accessKeyId, secretAccessKey }, + servingMode: process.env.S3_PUBLIC_URL?.trim() ? 'public' : 'proxy', + publicUrl: process.env.S3_PUBLIC_URL?.trim() ?? '' + } +} + +export function createBackendInstance(row: StorageBackendRow) { + if (row.type === 'local') { + return new LocalStorageBackend( + row.id, + row.serving_mode, + row.public_url + ) + } + + const config = parseS3Config(row.config_json) + const secrets = parseS3Secrets(row.secret_json) + if (!config || !secrets) { + throw new Error(`Invalid S3 configuration for backend ${row.id}`) + } + + return new S3StorageBackend( + row.id, + config, + secrets, + row.serving_mode, + row.public_url + ) +} + +export function createBackendFromEnv() { + const env = getStorageBackendFromEnv() + if (!env) return null + + return new S3StorageBackend( + S3_BACKEND_ID, + env.config as unknown as S3BackendConfig, + env.secrets, + env.servingMode, + env.publicUrl + ) +} + +export function getBackendUsageStats(backendId: string): { count: number, bytes: number } { + ensureStorageSchema() + const row = getDb().prepare(` + SELECT COUNT(*) AS count, COALESCE(SUM(size), 0) AS bytes + FROM images + WHERE backend_id = ? + `).get(backendId) as { count: number, bytes: number } + return { count: row.count, bytes: row.bytes } +} + +function getNextSortOrder(): number { + const row = getDb().prepare(` + SELECT COALESCE(MAX(sort_order), 0) AS max_order FROM storage_backends + `).get() as { max_order: number } + return row.max_order + 1 +} + +export function generateStorageBackendId(): string { + const bytes = new Uint8Array(4) + crypto.getRandomValues(bytes) + const suffix = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('') + return `s3-${suffix}` +} + +export function insertStorageBackend(input: { + id?: string + name: string + config: S3BackendConfig + secrets: { accessKeyId: string, secretAccessKey: string } + servingMode?: ServingMode + publicUrl?: string + quotaBytes?: number | null + enabled?: boolean + isDefault?: boolean +}): string { + ensureDefaultBackends() + const id = input.id ?? generateStorageBackendId() + const sortOrder = getNextSortOrder() + + getDb().prepare(` + INSERT INTO storage_backends + (id, name, type, config_json, secret_json, serving_mode, public_url, + quota_bytes, enabled, is_default, sort_order) + VALUES (?, ?, 's3', ?, ?, ?, ?, ?, ?, 0, ?) + `).run( + id, + input.name, + JSON.stringify(input.config), + JSON.stringify(input.secrets), + input.servingMode ?? 'proxy', + input.publicUrl ?? '', + input.quotaBytes ?? null, + input.enabled === false ? 0 : 1, + sortOrder + ) + + if (input.isDefault) { + setDefaultBackend(id) + } + + return id +} + +export function deleteStorageBackend(id: string): void { + if (id === LOCAL_BACKEND_ID) { + throw new Error('Cannot delete local backend') + } + + const usage = getBackendUsageStats(id) + if (usage.count > 0) { + throw new Error('Cannot delete backend with stored images') + } + + const row = getStorageBackendRow(id) + if (!row) { + throw new Error(`Storage backend not found: ${id}`) + } + + if (row.is_default) { + throw new Error('Cannot delete the default backend') + } + + getDb().prepare('DELETE FROM storage_backends WHERE id = ?').run(id) +} diff --git a/server/utils/storage-capacity.test.ts b/server/utils/storage-capacity.test.ts new file mode 100644 index 0000000..f9a3838 --- /dev/null +++ b/server/utils/storage-capacity.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest' +import { buildBackendCapacity } from './storage-capacity' + +describe('buildBackendCapacity', () => { + it('calculates quota-based capacity for s3 backends', async () => { + const quotaBytes = 10 * 1024 ** 3 + const capacity = await buildBackendCapacity('s3-test', 's3', 72_390, quotaBytes) + expect(capacity.source).toBe('quota') + expect(capacity.totalBytes).toBe(quotaBytes) + expect(capacity.usedBytes).toBe(72_390) + expect(capacity.freeBytes).toBe(quotaBytes - 72_390) + expect(capacity.percent).toBeGreaterThanOrEqual(0) + }) + + it('allows usage percent to exceed 100 when over quota', async () => { + const quotaBytes = 1024 + const capacity = await buildBackendCapacity('s3-test', 's3', 2048, quotaBytes) + expect(capacity.percent).toBe(200) + expect(capacity.freeBytes).toBe(0) + }) + + it('reads disk stats for local backend when available', async () => { + const capacity = await buildBackendCapacity('local', 'local', 1024, null) + if (capacity.source === 'disk') { + expect(capacity.totalBytes).toBeGreaterThan(0) + expect(capacity.freeBytes).not.toBeNull() + expect(capacity.percent).not.toBeNull() + } else { + expect(capacity.usedBytes).toBe(1024) + } + }) +}) diff --git a/server/utils/storage-capacity.ts b/server/utils/storage-capacity.ts new file mode 100644 index 0000000..aae2dc7 --- /dev/null +++ b/server/utils/storage-capacity.ts @@ -0,0 +1,92 @@ +import { statfs } from 'node:fs/promises' +import { getDataDir } from './data-dir' +import { LOCAL_BACKEND_ID } from './storage-backends' + +export interface StorageCapacity { + totalBytes: number | null + usedBytes: number + freeBytes: number | null + percent: number | null + source: 'disk' | 'quota' +} + +export function parseQuotaBytes(raw: unknown): number | null { + const value = Number(raw) + if (!Number.isFinite(value) || value <= 0) return null + return Math.round(value) +} + +export async function getLocalVolumeStats(): Promise<{ + totalBytes: number + freeBytes: number + usedBytes: number +} | null> { + try { + const stats = await statfs(getDataDir()) + const totalBytes = Number(stats.bsize) * Number(stats.blocks) + const freeBytes = Number(stats.bsize) * Number(stats.bavail) + if (!Number.isFinite(totalBytes) || totalBytes <= 0) return null + return { + totalBytes, + freeBytes: Math.max(0, freeBytes), + usedBytes: Math.max(0, totalBytes - freeBytes) + } + } catch { + return null + } +} + +function buildQuotaCapacity(quotaBytes: number, indexBytes: number): StorageCapacity { + const usedBytes = indexBytes + const freeBytes = Math.max(0, quotaBytes - usedBytes) + const percent = Math.round((usedBytes / quotaBytes) * 1000) / 10 + return { + totalBytes: quotaBytes, + usedBytes, + freeBytes, + percent, + source: 'quota' + } +} + +export async function buildBackendCapacity( + backendId: string, + backendType: 'local' | 's3', + indexBytes: number, + quotaBytes: number | null +): Promise { + if (backendType === 's3') { + if (quotaBytes && quotaBytes > 0) { + return buildQuotaCapacity(quotaBytes, indexBytes) + } + return { + totalBytes: null, + usedBytes: indexBytes, + freeBytes: null, + percent: null, + source: 'quota' + } + } + + if (backendId === LOCAL_BACKEND_ID || backendType === 'local') { + const volume = await getLocalVolumeStats() + if (volume) { + const percent = Math.round((volume.usedBytes / volume.totalBytes) * 1000) / 10 + return { + totalBytes: volume.totalBytes, + usedBytes: volume.usedBytes, + freeBytes: volume.freeBytes, + percent, + source: 'disk' + } + } + } + + return { + totalBytes: null, + usedBytes: indexBytes, + freeBytes: null, + percent: null, + source: 'disk' + } +} diff --git a/server/utils/storage.ts b/server/utils/storage.ts index 103ce1b..de77434 100644 --- a/server/utils/storage.ts +++ b/server/utils/storage.ts @@ -1,314 +1,119 @@ -import { createReadStream, promises as fs } from 'node:fs' import type { ReadStream } from 'node:fs' -import { dirname, join } from 'node:path' -import { isValidFolderName, validateImageKey, DEFAULT_FOLDER } from './image-key' +import type { Readable } from 'node:stream' +import { + countImages as countImagesFromIndex, + deleteImageIndex, + getFolderStorageStats as getFolderStorageStatsFromIndex, + getImageIndexAsStored, + getUserScopedStorageStats as getUserScopedStorageStatsFromIndex, + insertImageIndex, + listFolders as listFoldersFromIndex, + listFoldersForUser as listFoldersForUserFromIndex, + listImageKeysForAutoDelete, + listImages as listImagesFromIndex, + searchImages as searchImagesFromIndex +} from './image-index' +import { + getActiveBackend, + getBackendForKey +} from './storage/resolver' +import type { + FolderStorageStat, + PaginatedResult, + StoredImage, + StoredImageMeta +} from './storage/types' -/** - * 本地文件系统存储层 - * - * 目录布局(DATA_DIR 默认 ./data,容器内挂载 /data): - * //YYYY/MM/<随机ID>.webp 按上传 type 分目录(默认 images) - * /twikoo/YYYY/MM/<随机ID>.webp twikoo 评论图片 - * //YYYY/MM/<随机ID>.webp.meta.json 元数据(原始文件名等) - * - * 列表按日期降序(新月份在前),meta 缺失时回退到文件属性。 - */ +export type { + FolderStorageStat, + PaginatedResult, + StoredImage, + StoredImageMeta +} from './storage/types' -export interface StoredImageMeta { - originalName: string - uploadedAt: string - contentType: string - size: number - userId?: number | null -} - -export interface StoredImage extends StoredImageMeta { - key: string - mtimeMs: number -} - -const META_SUFFIX = '.meta.json' - -export function getDataDir(): string { - return process.env.DATA_DIR || join(process.cwd(), 'data') -} - -function keyToFilePath(key: string): string { - // key 均已通过 validateImageKey 校验,不存在路径穿越 - return join(getDataDir(), ...key.split('/')) -} - -function contentTypeFromKey(key: string): string { - const ext = key.split('.').pop()?.toLowerCase() - switch (ext) { - case 'jpg': - case 'jpeg': - return 'image/jpeg' - case 'png': - return 'image/png' - case 'gif': - return 'image/gif' - case 'webp': - return 'image/webp' - case 'svg': - return 'image/svg+xml' - case 'ico': - return 'image/x-icon' - default: - return 'application/octet-stream' - } -} - -async function readMeta(key: string): Promise> { - try { - const raw = await fs.readFile(keyToFilePath(key) + META_SUFFIX, 'utf8') - return JSON.parse(raw) as Partial - } catch { - return {} - } -} +export { getDataDir } from './data-dir' export async function putImage( key: string, bytes: Uint8Array, meta: StoredImageMeta ): Promise { - const filePath = keyToFilePath(key) - await fs.mkdir(dirname(filePath), { recursive: true }) - await fs.writeFile(filePath, bytes) - await fs.writeFile(filePath + META_SUFFIX, JSON.stringify(meta)) + const backend = await getActiveBackend() + await backend.put(key, bytes, meta) + insertImageIndex({ + key, + backendId: backend.id, + userId: meta.userId, + originalName: meta.originalName, + contentType: meta.contentType, + size: meta.size, + uploadedAt: meta.uploadedAt + }) } export async function headImage(key: string): Promise { - if (!validateImageKey(key)) return null + const indexed = getImageIndexAsStored(key) + if (indexed) return indexed - let stat - try { - stat = await fs.stat(keyToFilePath(key)) - } catch { - return null - } - if (!stat.isFile()) return null + const backend = await getBackendForKey(key) + const stored = await backend.head(key) + if (!stored) return null - const meta = await readMeta(key) - return { - key, - size: stat.size, - mtimeMs: stat.mtimeMs, - contentType: meta.contentType ?? contentTypeFromKey(key), - originalName: meta.originalName ?? key.split('/').pop() ?? 'image', - uploadedAt: meta.uploadedAt ?? stat.mtime.toISOString(), - userId: meta.userId ?? null - } + insertImageIndex({ + key: stored.key, + backendId: backend.id, + userId: stored.userId, + originalName: stored.originalName, + contentType: stored.contentType, + size: stored.size, + uploadedAt: stored.uploadedAt + }) + + return { ...stored, backendId: backend.id } } -export function createImageStream( +export async function createImageStream( key: string, range?: { start: number, end: number } -): ReadStream { - return createReadStream(keyToFilePath(key), range) +): Promise { + const backend = await getBackendForKey(key) + return backend.createStream(key, range) } export async function deleteImage(key: string): Promise { - const filePath = keyToFilePath(key) - await fs.unlink(filePath) - await fs.unlink(filePath + META_SUFFIX).catch(() => { - // meta 文件可能不存在(外部导入的历史图片) - }) -} - -export interface PaginatedResult { - items: T[] - total: number - page: number - pageSize: number - totalPages: number + const backend = await getBackendForKey(key) + await backend.delete(key) + deleteImageIndex(key) } export async function listFolders(): Promise { - const names = await readDirNames(getDataDir()) - const folders = names.filter(isValidFolderName) - if (!folders.includes(DEFAULT_FOLDER)) { - folders.push(DEFAULT_FOLDER) - } - return folders.sort((a, b) => a.localeCompare(b)) + return listFoldersFromIndex() } -/** 遍历各 type 目录下的 YYYY/MM,返回按日期降序的 key 列表(新月份在前) */ -export async function listImageKeys(folder?: string): Promise { - const keys: string[] = [] - const prefixes = folder && isValidFolderName(folder) - ? [folder] - : await listFolders() - - for (const prefix of prefixes) { - const root = join(getDataDir(), prefix) - const years = await readDirNames(root) - for (const year of years) { - const months = await readDirNames(join(root, year)) - for (const month of months) { - const files = await readFileNames(join(root, year, month)) - for (const file of files) { - if (file.endsWith(META_SUFFIX)) continue - const key = `${prefix}/${year}/${month}/${file}` - if (validateImageKey(key)) keys.push(key) - } - } - } - } - - // 按 YYYY/MM/文件名 降序排,使不同 type 按时间交错而不是分块 - return keys.sort((a, b) => { - const aDate = a.slice(a.indexOf('/') + 1) - const bDate = b.slice(b.indexOf('/') + 1) - return bDate.localeCompare(aDate) || b.localeCompare(a) - }) -} - -async function readDirNames(path: string): Promise { - try { - const entries = await fs.readdir(path, { withFileTypes: true }) - return entries.filter(e => e.isDirectory()).map(e => e.name) - } catch { - return [] - } -} - -function matchesUserFilter( - meta: StoredImageMeta, - userFilter: number | 'admin' -): boolean { - if (userFilter === 'admin') return true - const ownerId = meta.userId ?? null - if (ownerId === null) return false - return ownerId === userFilter -} - -async function filterKeysByUser( - keys: string[], - userFilter?: number | 'admin' -): Promise { - if (userFilter === undefined || userFilter === 'admin') { - return keys - } - - const filtered: string[] = [] - for (const key of keys) { - const meta = await readMeta(key) - if (matchesUserFilter(meta as StoredImageMeta, userFilter)) { - filtered.push(key) - } - } - return filtered -} - -async function readFileNames(path: string): Promise { - try { - const entries = await fs.readdir(path, { withFileTypes: true }) - return entries.filter(e => e.isFile()).map(e => e.name) - } catch { - return [] - } -} - -function paginateSlice( - all: T[], - limit: number, - page?: number -): PaginatedResult { - const total = all.length - const pageSize = limit - const totalPages = Math.max(1, Math.ceil(total / pageSize) || 1) - const requestedPage = Math.max(1, page ?? 1) - const safePage = Math.min(requestedPage, totalPages) - const start = (safePage - 1) * pageSize - const items = all.slice(start, start + pageSize) - - return { - items, - total, - page: safePage, - pageSize, - totalPages - } +export async function listImageKeys(): Promise { + const images = await listImageKeysForAutoDelete() + return images.map(image => image.key) } export async function countImages( folder?: string, userFilter?: number | 'admin' ): Promise { - let keys = await listImageKeys(folder) - keys = await filterKeysByUser(keys, userFilter) - return keys.length + return countImagesFromIndex(folder, userFilter) } -export interface FolderStorageStat { - folder: string - count: number - bytes: number -} - -/** 按顶层目录统计当前磁盘上的图片数量与体积 */ export async function getFolderStorageStats( userFilter?: number | 'admin' ): Promise { - let keys = await listImageKeys() - keys = await filterKeysByUser(keys, userFilter) - const map = new Map() - - for (const key of keys) { - const folder = key.split('/')[0] ?? 'unknown' - const meta = await headImage(key) - if (!meta) continue - const current = map.get(folder) ?? { count: 0, bytes: 0 } - current.count += 1 - current.bytes += meta.size - map.set(folder, current) - } - - return [...map.entries()] - .map(([folder, stat]) => ({ folder, ...stat })) - .sort((a, b) => b.bytes - a.bytes || a.folder.localeCompare(b.folder)) + return getFolderStorageStatsFromIndex(userFilter) } export async function listFoldersForUser( userFilter?: number | 'admin' ): Promise { - if (userFilter === undefined || userFilter === 'admin') { - return listFolders() - } - - let keys = await listImageKeys() - keys = await filterKeysByUser(keys, userFilter) - const folders = new Set() - for (const key of keys) { - const folder = key.split('/')[0] - if (folder && isValidFolderName(folder)) { - folders.add(folder) - } - } - if (!folders.size) { - folders.add(DEFAULT_FOLDER) - } - return [...folders].sort((a, b) => a.localeCompare(b)) + return listFoldersForUserFromIndex(userFilter) } -function startOfDayIsoInShanghai(now = new Date()): string { - const shifted = new Date(now.getTime() + 8 * 60 * 60 * 1000) - const y = shifted.getUTCFullYear() - const m = shifted.getUTCMonth() - const d = shifted.getUTCDate() - return new Date(Date.UTC(y, m, d) - 8 * 60 * 60 * 1000).toISOString() -} - -function startOfMonthIsoInShanghai(now = new Date()): string { - const shifted = new Date(now.getTime() + 8 * 60 * 60 * 1000) - const y = shifted.getUTCFullYear() - const m = shifted.getUTCMonth() - return new Date(Date.UTC(y, m, 1) - 8 * 60 * 60 * 1000).toISOString() -} - -/** 普通用户统计:基于本人图片 meta,不依赖全局 activity_logs */ export async function getUserScopedStorageStats(userId: number): Promise<{ storedCount: number uploadBytesTotal: number @@ -316,42 +121,7 @@ export async function getUserScopedStorageStats(userId: number): Promise<{ uploadMonth: number byFolder: FolderStorageStat[] }> { - let keys = await listImageKeys() - keys = await filterKeysByUser(keys, userId) - - const startOfDay = startOfDayIsoInShanghai() - const startOfMonth = startOfMonthIsoInShanghai() - const map = new Map() - let uploadBytesTotal = 0 - let uploadToday = 0 - let uploadMonth = 0 - - for (const key of keys) { - const meta = await headImage(key) - if (!meta) continue - - uploadBytesTotal += meta.size - if (meta.uploadedAt >= startOfDay) uploadToday += 1 - if (meta.uploadedAt >= startOfMonth) uploadMonth += 1 - - const folder = key.split('/')[0] ?? 'unknown' - const current = map.get(folder) ?? { count: 0, bytes: 0 } - current.count += 1 - current.bytes += meta.size - map.set(folder, current) - } - - const byFolder = [...map.entries()] - .map(([folder, stat]) => ({ folder, ...stat })) - .sort((a, b) => b.bytes - a.bytes || a.folder.localeCompare(b.folder)) - - return { - storedCount: keys.length, - uploadBytesTotal, - uploadToday, - uploadMonth, - byFolder - } + return getUserScopedStorageStatsFromIndex(userId) } export async function listImages(options: { @@ -360,16 +130,7 @@ export async function listImages(options: { folder?: string userFilter?: number | 'admin' }): Promise> { - let keys = await listImageKeys(options.folder) - keys = await filterKeysByUser(keys, options.userFilter) - const sliced = paginateSlice(keys, options.limit, options.page) - const items = (await Promise.all(sliced.items.map(headImage))) - .filter((item): item is StoredImage => item !== null) - - return { - ...sliced, - items - } + return listImagesFromIndex(options) } export async function searchImages(options: { @@ -379,20 +140,5 @@ export async function searchImages(options: { folder?: string userFilter?: number | 'admin' }): Promise> { - const needle = options.query.trim().toLowerCase() - let keys = await listImageKeys(options.folder) - keys = await filterKeysByUser(keys, options.userFilter) - const matches: StoredImage[] = [] - - for (const key of keys) { - const keyMatches = key.toLowerCase().includes(needle) - const item = await headImage(key) - if (!item) continue - - if (keyMatches || item.originalName.toLowerCase().includes(needle)) { - matches.push(item) - } - } - - return paginateSlice(matches, options.limit, options.page) + return searchImagesFromIndex(options) } diff --git a/server/utils/storage/index.ts b/server/utils/storage/index.ts new file mode 100644 index 0000000..90362ad --- /dev/null +++ b/server/utils/storage/index.ts @@ -0,0 +1,3 @@ +export { getActiveBackend, getBackendForKey, getActiveBackendRow, getBackendRowForKey } from './resolver' +export { LocalStorageBackend } from './local' +export { S3StorageBackend, buildObjectKey, parseS3Config, validateS3Config } from './s3' diff --git a/server/utils/storage/local.ts b/server/utils/storage/local.ts new file mode 100644 index 0000000..8d4d22d --- /dev/null +++ b/server/utils/storage/local.ts @@ -0,0 +1,116 @@ +import { createReadStream, promises as fs } from 'node:fs' +import type { ReadStream } from 'node:fs' +import { dirname, join } from 'node:path' +import { getDataDir } from '../data-dir' +import { validateImageKey } from '../image-key' +import type { StoredImage, StoredImageMeta, StorageBackend } from './types' + +const META_SUFFIX = '.meta.json' + +function keyToFilePath(key: string): string { + return join(getDataDir(), ...key.split('/')) +} + +function contentTypeFromKey(key: string): string { + const ext = key.split('.').pop()?.toLowerCase() + switch (ext) { + case 'jpg': + case 'jpeg': + return 'image/jpeg' + case 'png': + return 'image/png' + case 'gif': + return 'image/gif' + case 'webp': + return 'image/webp' + case 'svg': + return 'image/svg+xml' + case 'ico': + return 'image/x-icon' + default: + return 'application/octet-stream' + } +} + +async function readMeta(key: string): Promise> { + try { + const raw = await fs.readFile(keyToFilePath(key) + META_SUFFIX, 'utf8') + return JSON.parse(raw) as Partial + } catch { + return {} + } +} + +export class LocalStorageBackend implements StorageBackend { + readonly id: string + readonly type = 'local' as const + readonly servingMode: 'proxy' | 'public' + readonly publicUrl: string + + constructor( + id = 'local', + servingMode: 'proxy' | 'public' = 'proxy', + publicUrl = '' + ) { + this.id = id + this.servingMode = servingMode + this.publicUrl = publicUrl + } + + async put(key: string, bytes: Uint8Array, meta: StoredImageMeta): Promise { + const filePath = keyToFilePath(key) + await fs.mkdir(dirname(filePath), { recursive: true }) + await fs.writeFile(filePath, bytes) + await fs.writeFile(filePath + META_SUFFIX, JSON.stringify(meta)) + } + + async head(key: string): Promise { + if (!validateImageKey(key)) return null + + let stat + try { + stat = await fs.stat(keyToFilePath(key)) + } catch { + return null + } + if (!stat.isFile()) return null + + const meta = await readMeta(key) + return { + key, + size: stat.size, + mtimeMs: stat.mtimeMs, + contentType: meta.contentType ?? contentTypeFromKey(key), + originalName: meta.originalName ?? key.split('/').pop() ?? 'image', + uploadedAt: meta.uploadedAt ?? stat.mtime.toISOString(), + userId: meta.userId ?? null, + backendId: this.id + } + } + + async delete(key: string): Promise { + const filePath = keyToFilePath(key) + await fs.unlink(filePath) + await fs.unlink(filePath + META_SUFFIX).catch(() => {}) + } + + async createStream( + key: string, + range?: { start: number, end: number } + ): Promise { + return createReadStream(keyToFilePath(key), range) + } + + async testConnection(): Promise<{ ok: boolean, message: string }> { + try { + await fs.access(getDataDir()) + return { ok: true, message: '本地磁盘可访问' } + } catch { + return { ok: false, message: '无法访问数据目录' } + } + } + + async getUsageStats(): Promise<{ count: number, bytes: number }> { + return { count: 0, bytes: 0 } + } +} diff --git a/server/utils/storage/resolver.test.ts b/server/utils/storage/resolver.test.ts new file mode 100644 index 0000000..cb79bc9 --- /dev/null +++ b/server/utils/storage/resolver.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest' +import { LOCAL_BACKEND_ID } from '../storage-backends' + +describe('storage resolver constants', () => { + it('uses stable local backend id', () => { + expect(LOCAL_BACKEND_ID).toBe('local') + }) +}) + +describe('default backend routing', () => { + it('returns local when no env override and local is default', async () => { + const original = process.env.STORAGE_BACKEND + delete process.env.STORAGE_BACKEND + + const { setDefaultBackend, LOCAL_BACKEND_ID } = await import('../storage-backends') + setDefaultBackend(LOCAL_BACKEND_ID) + + const { getActiveBackend } = await import('./resolver') + const backend = await getActiveBackend() + expect(backend.type).toBe('local') + + if (original !== undefined) { + process.env.STORAGE_BACKEND = original + } + }) +}) diff --git a/server/utils/storage/resolver.ts b/server/utils/storage/resolver.ts new file mode 100644 index 0000000..6859977 --- /dev/null +++ b/server/utils/storage/resolver.ts @@ -0,0 +1,115 @@ +import { + createBackendFromEnv, + createBackendInstance, + getBackendIdForKey, + getDefaultBackendRow, + getStorageBackendRow, + LOCAL_BACKEND_ID, + ensureDefaultBackends +} from '../storage-backends' +import type { StorageBackend, StorageBackendRow } from './types' + +export async function getActiveBackend(): Promise { + const fromEnv = createBackendFromEnv() + if (fromEnv) return fromEnv + + ensureDefaultBackends() + const row = getDefaultBackendRow() + if (!row) { + return createBackendInstance({ + id: LOCAL_BACKEND_ID, + name: '本地磁盘', + type: 'local', + config_json: '{}', + secret_json: '{}', + serving_mode: 'proxy', + public_url: '', + quota_bytes: null, + enabled: 1, + is_default: 1, + sort_order: 0 + }) + } + + return createBackendInstance(row) +} + +export async function getBackendForKey(key: string): Promise { + const fromEnv = createBackendFromEnv() + if (fromEnv) return fromEnv + + ensureDefaultBackends() + const backendId = getBackendIdForKey(key) ?? LOCAL_BACKEND_ID + const row = getStorageBackendRow(backendId) + + if (!row || !row.enabled) { + return createBackendInstance(fallbackLocalRow()) + } + + try { + return createBackendInstance(row) + } catch { + return createBackendInstance(fallbackLocalRow()) + } +} + +export function getActiveBackendRow(): StorageBackendRow | null { + const fromEnv = createBackendFromEnv() + if (fromEnv) { + return { + id: fromEnv.id, + name: 'S3 (环境变量)', + type: 's3', + config_json: '{}', + secret_json: '{}', + serving_mode: fromEnv.servingMode, + public_url: fromEnv.publicUrl, + quota_bytes: null, + enabled: 1, + is_default: 1, + sort_order: 0 + } + } + + ensureDefaultBackends() + return getDefaultBackendRow() +} + +export async function getBackendRowForKey(key: string): Promise { + const fromEnv = createBackendFromEnv() + if (fromEnv) { + return { + id: fromEnv.id, + name: 'S3 (环境变量)', + type: 's3', + config_json: '{}', + secret_json: '{}', + serving_mode: fromEnv.servingMode, + public_url: fromEnv.publicUrl, + quota_bytes: null, + enabled: 1, + is_default: 1, + sort_order: 0 + } + } + + ensureDefaultBackends() + const backendId = getBackendIdForKey(key) ?? LOCAL_BACKEND_ID + return getStorageBackendRow(backendId) ?? fallbackLocalRow() +} + +function fallbackLocalRow(): StorageBackendRow { + return { + id: LOCAL_BACKEND_ID, + name: '本地磁盘', + type: 'local', + config_json: '{}', + secret_json: '{}', + serving_mode: 'proxy', + public_url: '', + quota_bytes: null, + enabled: 1, + is_default: 1, + sort_order: 0 + } +} diff --git a/server/utils/storage/s3.test.ts b/server/utils/storage/s3.test.ts new file mode 100644 index 0000000..a4e8a46 --- /dev/null +++ b/server/utils/storage/s3.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest' +import { buildObjectKey, parseS3Config, validateS3Config } from './s3' + +describe('S3 backend helpers', () => { + it('buildObjectKey joins prefix and key', () => { + expect(buildObjectKey('pichost', 'images/2026/01/a.webp')).toBe( + 'pichost/images/2026/01/a.webp' + ) + expect(buildObjectKey(undefined, 'images/a.webp')).toBe('images/a.webp') + expect(buildObjectKey('/pichost/', 'images/a.webp')).toBe('pichost/images/a.webp') + }) + + it('parseS3Config requires endpoint, region and bucket', () => { + expect(parseS3Config('{}')).toBeNull() + expect(parseS3Config(JSON.stringify({ + endpoint: 'https://example.r2.cloudflarestorage.com', + region: 'auto', + bucket: 'pics' + }))).toEqual({ + endpoint: 'https://example.r2.cloudflarestorage.com', + region: 'auto', + bucket: 'pics', + prefix: undefined, + forcePathStyle: false + }) + }) + + it('validateS3Config rejects invalid endpoint', () => { + expect(validateS3Config({ + endpoint: 'not-a-url', + region: 'auto', + bucket: 'pics' + })).toBeTruthy() + expect(validateS3Config({ + endpoint: 'https://example.com', + region: 'auto', + bucket: '' + })).toBeTruthy() + expect(validateS3Config({ + endpoint: 'https://example.com', + region: 'auto', + bucket: 'pics' + })).toBeNull() + }) +}) diff --git a/server/utils/storage/s3.ts b/server/utils/storage/s3.ts new file mode 100644 index 0000000..f72263d --- /dev/null +++ b/server/utils/storage/s3.ts @@ -0,0 +1,218 @@ +import { + DeleteObjectCommand, + GetObjectCommand, + HeadBucketCommand, + HeadObjectCommand, + PutObjectCommand, + S3Client +} from '@aws-sdk/client-s3' +import type { Readable } from 'node:stream' +import { validateImageKey } from '../image-key' +import type { + S3BackendConfig, + S3BackendSecrets, + ServingMode, + StoredImage, + StoredImageMeta, + StorageBackend +} from './types' + +function contentTypeFromKey(key: string): string { + const ext = key.split('.').pop()?.toLowerCase() + switch (ext) { + case 'jpg': + case 'jpeg': + return 'image/jpeg' + case 'png': + return 'image/png' + case 'gif': + return 'image/gif' + case 'webp': + return 'image/webp' + case 'svg': + return 'image/svg+xml' + case 'ico': + return 'image/x-icon' + default: + return 'application/octet-stream' + } +} + +export function buildObjectKey(prefix: string | undefined, key: string): string { + const trimmed = prefix?.replace(/^\/+|\/+$/g, '') ?? '' + return trimmed ? `${trimmed}/${key}` : key +} + +export function parseS3Config(raw: string): S3BackendConfig | null { + try { + const parsed = JSON.parse(raw) as Partial + if (!parsed.endpoint || !parsed.region || !parsed.bucket) return null + return { + endpoint: parsed.endpoint, + region: parsed.region, + bucket: parsed.bucket, + prefix: parsed.prefix, + forcePathStyle: parsed.forcePathStyle ?? false + } + } catch { + return null + } +} + +export function parseS3Secrets(raw: string): S3BackendSecrets | null { + try { + const parsed = JSON.parse(raw) as Partial + if (!parsed.accessKeyId || !parsed.secretAccessKey) return null + return { + accessKeyId: parsed.accessKeyId, + secretAccessKey: parsed.secretAccessKey + } + } catch { + return null + } +} + +export function validateS3Config(config: S3BackendConfig): string | null { + if (!config.endpoint.startsWith('http')) { + return 'Endpoint 需为 http(s) 地址' + } + if (!config.bucket.trim()) { + return 'Bucket 不能为空' + } + return null +} + +export class S3StorageBackend implements StorageBackend { + readonly id: string + readonly type = 's3' as const + readonly servingMode: ServingMode + readonly publicUrl: string + private readonly client: S3Client + private readonly bucket: string + private readonly prefix: string | undefined + + constructor( + id: string, + config: S3BackendConfig, + secrets: S3BackendSecrets, + servingMode: ServingMode = 'proxy', + publicUrl = '' + ) { + this.id = id + this.servingMode = servingMode + this.publicUrl = publicUrl + this.bucket = config.bucket + this.prefix = config.prefix + this.client = new S3Client({ + endpoint: config.endpoint, + region: config.region, + credentials: { + accessKeyId: secrets.accessKeyId, + secretAccessKey: secrets.secretAccessKey + }, + forcePathStyle: config.forcePathStyle ?? false + }) + } + + private objectKey(key: string): string { + return buildObjectKey(this.prefix, key) + } + + async put(key: string, bytes: Uint8Array, meta: StoredImageMeta): Promise { + await this.client.send(new PutObjectCommand({ + Bucket: this.bucket, + Key: this.objectKey(key), + Body: bytes, + ContentType: meta.contentType, + Metadata: { + originalName: meta.originalName, + uploadedAt: meta.uploadedAt, + userId: meta.userId != null ? String(meta.userId) : '' + } + })) + } + + async head(key: string): Promise { + if (!validateImageKey(key)) return null + + try { + const response = await this.client.send(new HeadObjectCommand({ + Bucket: this.bucket, + Key: this.objectKey(key) + })) + + const uploadedAt = response.Metadata?.uploadedat + ?? response.LastModified?.toISOString() + ?? new Date().toISOString() + const originalName = response.Metadata?.originalname + ?? key.split('/').pop() + ?? 'image' + const userIdRaw = response.Metadata?.userid + const userId = userIdRaw ? Number(userIdRaw) : null + + return { + key, + size: response.ContentLength ?? 0, + mtimeMs: response.LastModified?.getTime() ?? Date.now(), + contentType: response.ContentType ?? contentTypeFromKey(key), + originalName, + uploadedAt, + userId: Number.isFinite(userId) ? userId : null, + backendId: this.id + } + } catch { + return null + } + } + + async delete(key: string): Promise { + await this.client.send(new DeleteObjectCommand({ + Bucket: this.bucket, + Key: this.objectKey(key) + })) + } + + async createStream( + key: string, + range?: { start: number, end: number } + ): Promise { + const response = await this.client.send(new GetObjectCommand({ + Bucket: this.bucket, + Key: this.objectKey(key), + Range: range ? `bytes=${range.start}-${range.end}` : undefined + })) + + if (!response.Body) { + throw new Error('Empty S3 response body') + } + + return response.Body as Readable + } + + async testConnection(): Promise<{ ok: boolean, message: string }> { + const probeKey = `__pichost-probe-${Date.now()}.txt` + const probeBytes = new TextEncoder().encode('pichost-probe') + + try { + await this.client.send(new HeadBucketCommand({ Bucket: this.bucket })) + await this.client.send(new PutObjectCommand({ + Bucket: this.bucket, + Key: buildObjectKey(this.prefix, probeKey), + Body: probeBytes, + ContentType: 'text/plain' + })) + await this.client.send(new DeleteObjectCommand({ + Bucket: this.bucket, + Key: buildObjectKey(this.prefix, probeKey) + })) + return { ok: true, message: '连接成功' } + } catch (error) { + const message = error instanceof Error ? error.message : '连接失败' + return { ok: false, message } + } + } + + async getUsageStats(): Promise<{ count: number, bytes: number }> { + return { count: 0, bytes: 0 } + } +} diff --git a/server/utils/storage/types.ts b/server/utils/storage/types.ts new file mode 100644 index 0000000..7e89538 --- /dev/null +++ b/server/utils/storage/types.ts @@ -0,0 +1,85 @@ +import type { Readable } from 'node:stream' + +export interface StoredImageMeta { + originalName: string + uploadedAt: string + contentType: string + size: number + userId?: number | null +} + +export interface StoredImage extends StoredImageMeta { + key: string + mtimeMs: number + backendId?: string +} + +export interface PaginatedResult { + items: T[] + total: number + page: number + pageSize: number + totalPages: number +} + +export interface FolderStorageStat { + folder: string + count: number + bytes: number +} + +export type StorageBackendType = 'local' | 's3' +export type ServingMode = 'proxy' | 'public' + +export interface S3BackendConfig { + endpoint: string + region: string + bucket: string + prefix?: string + forcePathStyle?: boolean +} + +export interface S3BackendSecrets { + accessKeyId: string + secretAccessKey: string +} + +export interface StorageBackendRow { + id: string + name: string + type: StorageBackendType + config_json: string + secret_json: string + serving_mode: ServingMode + public_url: string + quota_bytes: number | null + enabled: number + is_default: number + sort_order: number +} + +export interface StorageBackendInfo { + id: string + name: string + type: StorageBackendType + config: S3BackendConfig | Record + servingMode: ServingMode + publicUrl: string + quotaBytes: number | null + enabled: boolean + isDefault: boolean + sortOrder: number +} + +export interface StorageBackend { + readonly id: string + readonly type: StorageBackendType + readonly servingMode: ServingMode + readonly publicUrl: string + put(key: string, bytes: Uint8Array, meta: StoredImageMeta): Promise + head(key: string): Promise + delete(key: string): Promise + createStream(key: string, range?: { start: number, end: number }): Promise + testConnection(): Promise<{ ok: boolean, message: string }> + getUsageStats(): Promise<{ count: number, bytes: number }> +}