From d861a9937bf0739fc601865508fb7504a4195389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=91=E8=90=8C=E8=8A=BD?= <3205788256@qq.com> Date: Thu, 12 Mar 2026 18:58:53 +0800 Subject: [PATCH] chore: sync local changes (2026-03-12) --- AGENTS.md | 47 ++ README.md | 110 +++ mengyaping-backend/.dockerignore | 94 +-- mengyaping-backend/Dockerfile | 112 +-- mengyaping-backend/config/config.go | 352 ++++----- mengyaping-backend/docker-compose.yml | 80 +- mengyaping-backend/handlers/website.go | 398 +++++----- mengyaping-backend/main.go | 94 +-- mengyaping-backend/models/website.go | 211 ++--- mengyaping-backend/router/router.go | 102 +-- mengyaping-backend/services/monitor.go | 720 ++++++++++-------- mengyaping-backend/services/website.go | 269 ++++--- mengyaping-backend/storage/storage.go | 587 +++++++------- mengyaping-backend/utils/dns.go | 60 ++ mengyaping-backend/utils/http.go | 304 ++++---- mengyaping-backend/utils/id.go | 62 +- mengyaping-frontend/index.html | 6 +- mengyaping-frontend/package.json | 4 +- mengyaping-frontend/public/icons/icon-192.png | Bin 0 -> 70651 bytes .../public/icons/icon-512-maskable.png | Bin 0 -> 471553 bytes mengyaping-frontend/public/icons/icon-512.png | Bin 0 -> 471553 bytes .../public/manifest.webmanifest | 32 + mengyaping-frontend/public/sw.js | 70 ++ mengyaping-frontend/src/App.jsx | 14 +- .../src/components/StatsCard.jsx | 290 +++---- .../src/components/UptimeChart.jsx | 424 +++++------ .../src/components/WebsiteCard.jsx | 473 +++++++----- .../src/components/WebsiteModal.jsx | 477 ++++++------ mengyaping-frontend/src/hooks/useMonitor.js | 166 ++-- mengyaping-frontend/src/index.css | 136 ++-- mengyaping-frontend/src/main.jsx | 8 + mengyaping-frontend/src/pages/Dashboard.jsx | 554 +++++++------- mengyaping-frontend/src/services/api.js | 154 ++-- mengyaping-frontend/tailwind.config.js | 16 +- mengyaping-frontend/vite.config.js | 10 + 开启前端.bat | 18 +- 开启后端.bat | 22 +- 构建前端.bat | 20 +- 38 files changed, 3570 insertions(+), 2926 deletions(-) create mode 100644 AGENTS.md create mode 100644 README.md create mode 100644 mengyaping-backend/utils/dns.go create mode 100644 mengyaping-frontend/public/icons/icon-192.png create mode 100644 mengyaping-frontend/public/icons/icon-512-maskable.png create mode 100644 mengyaping-frontend/public/icons/icon-512.png create mode 100644 mengyaping-frontend/public/manifest.webmanifest create mode 100644 mengyaping-frontend/public/sw.js diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f5733d8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,47 @@ +# Repository Guidelines + +## Project Structure & Module Organization +This repository is split into two apps: +- `mengyaping-frontend/`: React + Vite UI. Main code is in `src/` (`components/`, `pages/`, `hooks/`, `services/`), with static assets in `public/`. +- `mengyaping-backend/`: Go + Gin API and monitor service. Core folders are `handlers/`, `services/`, `router/`, `models/`, `storage/`, `config/`, and `utils/`. + +Runtime data is persisted under `mengyaping-backend/data/` (`websites.json`, `records.json`, `groups.json`, `config.json`). Keep data format changes backward-compatible. + +## Build, Test, and Development Commands +Frontend (run inside `mengyaping-frontend/`): +- `npm install`: install dependencies. +- `npm run dev`: start Vite dev server. +- `npm run build`: create production build in `dist/`. +- `npm run lint`: run ESLint checks. + +Backend (run inside `mengyaping-backend/`): +- `go mod tidy`: sync Go modules. +- `go run main.go`: start API server (default `0.0.0.0:8080`). +- `go test ./...`: run all backend tests. +- `docker compose up -d --build`: build and run containerized backend. + +## Coding Style & Naming Conventions +- Frontend: 2-space indentation, ES module imports, React component files in PascalCase (for example `WebsiteCard.jsx`), hooks in `useXxx.js`, utility/service functions in camelCase. +- Backend: format with `gofmt`; keep package names lowercase; exported identifiers in PascalCase, internal helpers in camelCase. +- Keep handlers thin and place business logic in `services/`. + +## Testing Guidelines +There are currently no committed frontend tests and minimal backend test coverage. Add tests for every non-trivial change: +- Backend: `*_test.go` next to implementation; focus on handlers and service logic. +- Frontend: if introducing test tooling, prefer Vitest + Testing Library with `*.test.jsx` naming. + +## Commit & Pull Request Guidelines +Current history uses short, imperative commit text (for example `first commit`). Continue with concise, scoped messages such as: +- `feat(frontend): add status filter` +- `fix(backend): validate monitor interval` + +Each PR should include: +- Clear summary and impacted area (`frontend`, `backend`, or both). +- Validation steps and commands run. +- Screenshots/GIFs for UI changes. +- Linked issue/ticket when available. + +## Security & Configuration Tips +- Do not commit secrets, tokens, or private endpoints. +- Frontend dev API target is `http://localhost:8080/api` in `mengyaping-frontend/src/services/api.js`. +- Commit only sanitized sample data in `mengyaping-backend/data/`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb0e817 --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# 萌芽Ping(MengYaPing) + +一个轻量、可自部署的网站可用性监控面板。 +支持多网站/多 URL 监控、分组管理、实时状态查看、24h/7d 可用率统计与延迟展示。 + +## 功能特性 + +- 多网站监控:每个网站可配置多个 URL,分别检测 +- 自动巡检:默认每 5 分钟检测一次(可配置) +- 状态面板:在线/离线、状态码、响应延迟、最后检测时间 +- 可用率统计:按 24 小时与 7 天维度聚合 +- 分组与检索:支持分组筛选与关键词搜索 +- 手动触发:支持单网站“立即检测” + +## 技术栈 + +- 前端:React 19 + Vite 7 + Tailwind CSS 4 +- 后端:Go 1.25 + Gin +- 存储:本地 JSON 文件(`mengyaping-backend/data/`) +- 部署:Docker / Docker Compose(后端) + +## 项目结构 + +```text +. +├─ mengyaping-frontend/ # 前端应用 +│ ├─ src/components/ # 卡片、图表、弹窗等组件 +│ ├─ src/pages/ # 页面(Dashboard) +│ ├─ src/services/api.js # API 请求封装 +│ └─ public/ # 静态资源(logo、favicon) +├─ mengyaping-backend/ # 后端服务 +│ ├─ handlers/ services/ router/ +│ ├─ models/ storage/ config/ utils/ +│ └─ data/ # 配置与监控数据(JSON) +├─ 开启前端.bat +├─ 开启后端.bat +└─ 构建前端.bat +``` + +## 快速开始(本地开发) + +### 1) 启动后端 + +```bash +cd mengyaping-backend +go mod tidy +go run main.go +``` + +默认地址:`http://localhost:8080` + +### 2) 启动前端 + +```bash +cd mengyaping-frontend +npm install +npm run dev +``` + +前端开发地址通常为:`http://localhost:5173` + +## 常用命令 + +```bash +# 前端 +cd mengyaping-frontend +npm run dev # 开发模式 +npm run build # 生产构建 +npm run lint # 代码检查 + +# 后端 +cd mengyaping-backend +go test ./... # 运行测试 +``` + +Windows 用户也可直接使用仓库根目录下的 `开启前端.bat`、`开启后端.bat`、`构建前端.bat`。 + +## API 概览 + +基础前缀:`/api` + +- `GET /health`:健康检查 +- `GET /websites`:获取全部网站状态 +- `GET /websites/:id`:获取单网站状态 +- `POST /websites`:创建网站 +- `PUT /websites/:id`:更新网站 +- `DELETE /websites/:id`:删除网站 +- `POST /websites/:id/check`:立即检测 +- `GET /groups`:获取分组 +- `POST /groups`:新增分组 + +## 配置说明 + +后端支持环境变量配置(如 `SERVER_PORT`、`MONITOR_INTERVAL`、`MONITOR_TIMEOUT` 等),并会读取 `mengyaping-backend/data/config.json`。 +当前实现中,`config.json` 的值会覆盖环境变量同名项。 + +## Docker 部署(后端) + +```bash +cd mengyaping-backend +docker compose up -d --build +``` + +`docker-compose.yml` 默认映射端口 `6161 -> 8080`。 + +## 展示建议(GitHub) + +- 建议在仓库中新增 `docs/images/` 并放置页面截图 +- 可在本 README 顶部补充截图、动图或在线演示链接,提升展示效果 + diff --git a/mengyaping-backend/.dockerignore b/mengyaping-backend/.dockerignore index 058f085..f582e7a 100644 --- a/mengyaping-backend/.dockerignore +++ b/mengyaping-backend/.dockerignore @@ -1,47 +1,47 @@ -# Git 相关 -.git -.gitignore -.gitattributes - -# 编辑器和 IDE -.vscode -.idea -*.swp -*.swo -*~ - -# 操作系统文件 -.DS_Store -Thumbs.db - -# 数据文件(运行时生成) -data/*.json - -# 日志文件 -*.log - -# 临时文件 -tmp/ -temp/ - -# 文档 -README.md -LICENSE -*.md - -# Docker 相关 -Dockerfile -.dockerignore -docker-compose.yml - -# 测试文件 -*_test.go -test/ -tests/ - -# 构建产物 -*.exe -*.exe~ -*.dll -*.so -*.dylib +# Git 相关 +.git +.gitignore +.gitattributes + +# 编辑器和 IDE +.vscode +.idea +*.swp +*.swo +*~ + +# 操作系统文件 +.DS_Store +Thumbs.db + +# 数据文件(运行时生成) +data/*.json + +# 日志文件 +*.log + +# 临时文件 +tmp/ +temp/ + +# 文档 +README.md +LICENSE +*.md + +# Docker 相关 +Dockerfile +.dockerignore +docker-compose.yml + +# 测试文件 +*_test.go +test/ +tests/ + +# 构建产物 +*.exe +*.exe~ +*.dll +*.so +*.dylib diff --git a/mengyaping-backend/Dockerfile b/mengyaping-backend/Dockerfile index 45530e2..f1566ec 100644 --- a/mengyaping-backend/Dockerfile +++ b/mengyaping-backend/Dockerfile @@ -1,56 +1,56 @@ -# 多阶段构建 - 使用官方 Golang 镜像作为构建环境 -FROM golang:1.25-alpine AS builder - -# 设置工作目录 -WORKDIR /app - -# 安装必要的构建工具 -RUN apk add --no-cache git ca-certificates tzdata - -# 复制 go.mod 和 go.sum 文件 -COPY go.mod go.sum ./ - -# 下载依赖 -RUN go mod download - -# 复制源代码 -COPY . . - -# 构建应用程序 -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o mengyaping-backend . - -# 使用轻量级的 alpine 镜像作为运行环境 -FROM alpine:latest - -# 安装必要的运行时依赖 -RUN apk --no-cache add ca-certificates tzdata - -# 设置时区为上海 -RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo "Asia/Shanghai" > /etc/timezone - -# 创建非 root 用户 -RUN addgroup -g 1000 appuser && \ - adduser -D -u 1000 -G appuser appuser - -# 设置工作目录 -WORKDIR /app - -# 从构建阶段复制编译好的二进制文件 -COPY --from=builder /app/mengyaping-backend . - -# 创建数据目录 -RUN mkdir -p /app/data && chown -R appuser:appuser /app - -# 切换到非 root 用户 -USER appuser - -# 暴露端口 -EXPOSE 8080 - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:8080/api/health || exit 1 - -# 运行应用程序 -CMD ["./mengyaping-backend"] +# 多阶段构建 - 使用官方 Golang 镜像作为构建环境 +FROM golang:1.25-alpine AS builder + +# 设置工作目录 +WORKDIR /app + +# 安装必要的构建工具 +RUN apk add --no-cache git ca-certificates tzdata + +# 复制 go.mod 和 go.sum 文件 +COPY go.mod go.sum ./ + +# 下载依赖 +RUN go mod download + +# 复制源代码 +COPY . . + +# 构建应用程序 +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o mengyaping-backend . + +# 使用轻量级的 alpine 镜像作为运行环境 +FROM alpine:latest + +# 安装必要的运行时依赖 +RUN apk --no-cache add ca-certificates tzdata + +# 设置时区为上海 +RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo "Asia/Shanghai" > /etc/timezone + +# 创建非 root 用户 +RUN addgroup -g 1000 appuser && \ + adduser -D -u 1000 -G appuser appuser + +# 设置工作目录 +WORKDIR /app + +# 从构建阶段复制编译好的二进制文件 +COPY --from=builder /app/mengyaping-backend . + +# 创建数据目录 +RUN mkdir -p /app/data && chown -R appuser:appuser /app + +# 切换到非 root 用户 +USER appuser + +# 暴露端口 +EXPOSE 8080 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/api/health || exit 1 + +# 运行应用程序 +CMD ["./mengyaping-backend"] diff --git a/mengyaping-backend/config/config.go b/mengyaping-backend/config/config.go index b1813b8..1f66719 100644 --- a/mengyaping-backend/config/config.go +++ b/mengyaping-backend/config/config.go @@ -1,176 +1,176 @@ -package config - -import ( - "encoding/json" - "fmt" - "os" - "sync" - "time" -) - -// Config 应用配置 -type Config struct { - Server ServerConfig `json:"server"` - Monitor MonitorConfig `json:"monitor"` - DataPath string `json:"data_path"` -} - -// ServerConfig 服务器配置 -type ServerConfig struct { - Port string `json:"port"` - Host string `json:"host"` -} - -// MonitorConfig 监控配置 -type MonitorConfig struct { - Interval time.Duration `json:"interval"` // 检测间隔 - Timeout time.Duration `json:"timeout"` // 请求超时时间 - RetryCount int `json:"retry_count"` // 重试次数 - HistoryDays int `json:"history_days"` // 保留历史天数 -} - -var ( - cfg *Config - once sync.Once -) - -// GetConfig 获取配置单例 -func GetConfig() *Config { - once.Do(func() { - cfg = &Config{ - Server: ServerConfig{ - Port: getEnv("SERVER_PORT", "8080"), - Host: getEnv("SERVER_HOST", "0.0.0.0"), - }, - Monitor: MonitorConfig{ - Interval: parseDuration(getEnv("MONITOR_INTERVAL", "5m"), 5*time.Minute), - Timeout: parseDuration(getEnv("MONITOR_TIMEOUT", "10s"), 10*time.Second), - RetryCount: parseInt(getEnv("MONITOR_RETRY_COUNT", "3"), 3), - HistoryDays: parseInt(getEnv("MONITOR_HISTORY_DAYS", "7"), 7), - }, - DataPath: getEnv("DATA_PATH", "./data"), - } - - // 尝试从配置文件加载(会覆盖环境变量配置) - loadConfigFromFile() - }) - return cfg -} - -// getEnv 获取环境变量,如果不存在则返回默认值 -func getEnv(key, defaultValue string) string { - if value := os.Getenv(key); value != "" { - return value - } - return defaultValue -} - -// parseInt 解析整数环境变量 -func parseInt(value string, defaultValue int) int { - if value == "" { - return defaultValue - } - var result int - if _, err := fmt.Sscanf(value, "%d", &result); err != nil { - return defaultValue - } - return result -} - -// parseDuration 解析时间间隔环境变量 -func parseDuration(value string, defaultValue time.Duration) time.Duration { - if value == "" { - return defaultValue - } - if duration, err := time.ParseDuration(value); err == nil { - return duration - } - return defaultValue -} - -// loadConfigFromFile 从文件加载配置 -func loadConfigFromFile() { - configFile := "./data/config.json" - if _, err := os.Stat(configFile); os.IsNotExist(err) { - return - } - - data, err := os.ReadFile(configFile) - if err != nil { - return - } - - var fileCfg struct { - Server ServerConfig `json:"server"` - Monitor struct { - IntervalMinutes int `json:"interval_minutes"` - TimeoutSeconds int `json:"timeout_seconds"` - RetryCount int `json:"retry_count"` - HistoryDays int `json:"history_days"` - } `json:"monitor"` - DataPath string `json:"data_path"` - } - - if err := json.Unmarshal(data, &fileCfg); err != nil { - return - } - - if fileCfg.Server.Port != "" { - cfg.Server.Port = fileCfg.Server.Port - } - if fileCfg.Server.Host != "" { - cfg.Server.Host = fileCfg.Server.Host - } - if fileCfg.Monitor.IntervalMinutes > 0 { - cfg.Monitor.Interval = time.Duration(fileCfg.Monitor.IntervalMinutes) * time.Minute - } - if fileCfg.Monitor.TimeoutSeconds > 0 { - cfg.Monitor.Timeout = time.Duration(fileCfg.Monitor.TimeoutSeconds) * time.Second - } - if fileCfg.Monitor.RetryCount > 0 { - cfg.Monitor.RetryCount = fileCfg.Monitor.RetryCount - } - if fileCfg.Monitor.HistoryDays > 0 { - cfg.Monitor.HistoryDays = fileCfg.Monitor.HistoryDays - } - if fileCfg.DataPath != "" { - cfg.DataPath = fileCfg.DataPath - } -} - -// SaveConfig 保存配置到文件 -func SaveConfig() error { - configFile := cfg.DataPath + "/config.json" - - fileCfg := struct { - Server ServerConfig `json:"server"` - Monitor struct { - IntervalMinutes int `json:"interval_minutes"` - TimeoutSeconds int `json:"timeout_seconds"` - RetryCount int `json:"retry_count"` - HistoryDays int `json:"history_days"` - } `json:"monitor"` - DataPath string `json:"data_path"` - }{ - Server: cfg.Server, - Monitor: struct { - IntervalMinutes int `json:"interval_minutes"` - TimeoutSeconds int `json:"timeout_seconds"` - RetryCount int `json:"retry_count"` - HistoryDays int `json:"history_days"` - }{ - IntervalMinutes: int(cfg.Monitor.Interval.Minutes()), - TimeoutSeconds: int(cfg.Monitor.Timeout.Seconds()), - RetryCount: cfg.Monitor.RetryCount, - HistoryDays: cfg.Monitor.HistoryDays, - }, - DataPath: cfg.DataPath, - } - - data, err := json.MarshalIndent(fileCfg, "", " ") - if err != nil { - return err - } - - return os.WriteFile(configFile, data, 0644) -} +package config + +import ( + "encoding/json" + "fmt" + "os" + "sync" + "time" +) + +// Config 应用配置 +type Config struct { + Server ServerConfig `json:"server"` + Monitor MonitorConfig `json:"monitor"` + DataPath string `json:"data_path"` +} + +// ServerConfig 服务器配置 +type ServerConfig struct { + Port string `json:"port"` + Host string `json:"host"` +} + +// MonitorConfig 监控配置 +type MonitorConfig struct { + Interval time.Duration `json:"interval"` // 检测间隔 + Timeout time.Duration `json:"timeout"` // 请求超时时间 + RetryCount int `json:"retry_count"` // 重试次数 + HistoryDays int `json:"history_days"` // 保留历史天数 +} + +var ( + cfg *Config + once sync.Once +) + +// GetConfig 获取配置单例 +func GetConfig() *Config { + once.Do(func() { + cfg = &Config{ + Server: ServerConfig{ + Port: getEnv("SERVER_PORT", "8080"), + Host: getEnv("SERVER_HOST", "0.0.0.0"), + }, + Monitor: MonitorConfig{ + Interval: parseDuration(getEnv("MONITOR_INTERVAL", "1h"), 1*time.Hour), + Timeout: parseDuration(getEnv("MONITOR_TIMEOUT", "10s"), 10*time.Second), + RetryCount: parseInt(getEnv("MONITOR_RETRY_COUNT", "3"), 3), + HistoryDays: parseInt(getEnv("MONITOR_HISTORY_DAYS", "90"), 90), + }, + DataPath: getEnv("DATA_PATH", "./data"), + } + + // 尝试从配置文件加载(会覆盖环境变量配置) + loadConfigFromFile() + }) + return cfg +} + +// getEnv 获取环境变量,如果不存在则返回默认值 +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +// parseInt 解析整数环境变量 +func parseInt(value string, defaultValue int) int { + if value == "" { + return defaultValue + } + var result int + if _, err := fmt.Sscanf(value, "%d", &result); err != nil { + return defaultValue + } + return result +} + +// parseDuration 解析时间间隔环境变量 +func parseDuration(value string, defaultValue time.Duration) time.Duration { + if value == "" { + return defaultValue + } + if duration, err := time.ParseDuration(value); err == nil { + return duration + } + return defaultValue +} + +// loadConfigFromFile 从文件加载配置 +func loadConfigFromFile() { + configFile := "./data/config.json" + if _, err := os.Stat(configFile); os.IsNotExist(err) { + return + } + + data, err := os.ReadFile(configFile) + if err != nil { + return + } + + var fileCfg struct { + Server ServerConfig `json:"server"` + Monitor struct { + IntervalMinutes int `json:"interval_minutes"` + TimeoutSeconds int `json:"timeout_seconds"` + RetryCount int `json:"retry_count"` + HistoryDays int `json:"history_days"` + } `json:"monitor"` + DataPath string `json:"data_path"` + } + + if err := json.Unmarshal(data, &fileCfg); err != nil { + return + } + + if fileCfg.Server.Port != "" { + cfg.Server.Port = fileCfg.Server.Port + } + if fileCfg.Server.Host != "" { + cfg.Server.Host = fileCfg.Server.Host + } + if fileCfg.Monitor.IntervalMinutes > 0 { + cfg.Monitor.Interval = time.Duration(fileCfg.Monitor.IntervalMinutes) * time.Minute + } + if fileCfg.Monitor.TimeoutSeconds > 0 { + cfg.Monitor.Timeout = time.Duration(fileCfg.Monitor.TimeoutSeconds) * time.Second + } + if fileCfg.Monitor.RetryCount > 0 { + cfg.Monitor.RetryCount = fileCfg.Monitor.RetryCount + } + if fileCfg.Monitor.HistoryDays > 0 { + cfg.Monitor.HistoryDays = fileCfg.Monitor.HistoryDays + } + if fileCfg.DataPath != "" { + cfg.DataPath = fileCfg.DataPath + } +} + +// SaveConfig 保存配置到文件 +func SaveConfig() error { + configFile := cfg.DataPath + "/config.json" + + fileCfg := struct { + Server ServerConfig `json:"server"` + Monitor struct { + IntervalMinutes int `json:"interval_minutes"` + TimeoutSeconds int `json:"timeout_seconds"` + RetryCount int `json:"retry_count"` + HistoryDays int `json:"history_days"` + } `json:"monitor"` + DataPath string `json:"data_path"` + }{ + Server: cfg.Server, + Monitor: struct { + IntervalMinutes int `json:"interval_minutes"` + TimeoutSeconds int `json:"timeout_seconds"` + RetryCount int `json:"retry_count"` + HistoryDays int `json:"history_days"` + }{ + IntervalMinutes: int(cfg.Monitor.Interval.Minutes()), + TimeoutSeconds: int(cfg.Monitor.Timeout.Seconds()), + RetryCount: cfg.Monitor.RetryCount, + HistoryDays: cfg.Monitor.HistoryDays, + }, + DataPath: cfg.DataPath, + } + + data, err := json.MarshalIndent(fileCfg, "", " ") + if err != nil { + return err + } + + return os.WriteFile(configFile, data, 0644) +} diff --git a/mengyaping-backend/docker-compose.yml b/mengyaping-backend/docker-compose.yml index 8e15cca..b64fe0b 100644 --- a/mengyaping-backend/docker-compose.yml +++ b/mengyaping-backend/docker-compose.yml @@ -1,40 +1,40 @@ -version: '3.8' - -services: - mengyaping-backend: - build: - context: . - dockerfile: Dockerfile - container_name: mengyaping-backend - restart: unless-stopped - ports: - - "6161:8080" - volumes: - # 持久化数据目录 - - /shumengya/docker/mengyaping-backend/data/:/app/data - environment: - # 服务器配置 - - SERVER_PORT=8080 - - SERVER_HOST=0.0.0.0 - # 监控配置 - - MONITOR_INTERVAL=5m - - MONITOR_TIMEOUT=10s - - MONITOR_RETRY_COUNT=3 - - MONITOR_HISTORY_DAYS=7 - networks: - - mengyaping-network - healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/health"] - interval: 30s - timeout: 3s - retries: 3 - start_period: 5s - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "3" - -networks: - mengyaping-network: - driver: bridge +version: '3.8' + +services: + mengyaping-backend: + build: + context: . + dockerfile: Dockerfile + container_name: mengyaping-backend + restart: unless-stopped + ports: + - "6161:8080" + volumes: + # 持久化数据目录 + - /shumengya/docker/mengyaping-backend/data/:/app/data + environment: + # 服务器配置 + - SERVER_PORT=8080 + - SERVER_HOST=0.0.0.0 + # 监控配置 + - MONITOR_INTERVAL=5m + - MONITOR_TIMEOUT=10s + - MONITOR_RETRY_COUNT=3 + - MONITOR_HISTORY_DAYS=7 + networks: + - mengyaping-network + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/health"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 5s + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + +networks: + mengyaping-network: + driver: bridge diff --git a/mengyaping-backend/handlers/website.go b/mengyaping-backend/handlers/website.go index d69b3e3..258eb9e 100644 --- a/mengyaping-backend/handlers/website.go +++ b/mengyaping-backend/handlers/website.go @@ -1,199 +1,199 @@ -package handlers - -import ( - "net/http" - - "github.com/gin-gonic/gin" - - "mengyaping-backend/models" - "mengyaping-backend/services" -) - -// WebsiteHandler 网站处理器 -type WebsiteHandler struct { - websiteService *services.WebsiteService - monitorService *services.MonitorService -} - -// NewWebsiteHandler 创建网站处理器 -func NewWebsiteHandler() *WebsiteHandler { - return &WebsiteHandler{ - websiteService: services.NewWebsiteService(), - monitorService: services.GetMonitorService(), - } -} - -// GetWebsites 获取所有网站状态 -func (h *WebsiteHandler) GetWebsites(c *gin.Context) { - statuses := h.monitorService.GetAllWebsiteStatuses() - - c.JSON(http.StatusOK, models.APIResponse{ - Code: 0, - Message: "success", - Data: statuses, - }) -} - -// GetWebsite 获取单个网站状态 -func (h *WebsiteHandler) GetWebsite(c *gin.Context) { - id := c.Param("id") - - status := h.monitorService.GetWebsiteStatus(id) - if status == nil { - c.JSON(http.StatusNotFound, models.APIResponse{ - Code: 404, - Message: "网站不存在", - }) - return - } - - c.JSON(http.StatusOK, models.APIResponse{ - Code: 0, - Message: "success", - Data: status, - }) -} - -// CreateWebsite 创建网站 -func (h *WebsiteHandler) CreateWebsite(c *gin.Context) { - var req models.CreateWebsiteRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.APIResponse{ - Code: 400, - Message: "参数错误: " + err.Error(), - }) - return - } - - website, err := h.websiteService.CreateWebsite(req) - if err != nil { - c.JSON(http.StatusInternalServerError, models.APIResponse{ - Code: 500, - Message: "创建失败: " + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, models.APIResponse{ - Code: 0, - Message: "创建成功", - Data: website, - }) -} - -// UpdateWebsite 更新网站 -func (h *WebsiteHandler) UpdateWebsite(c *gin.Context) { - id := c.Param("id") - - var req models.UpdateWebsiteRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.APIResponse{ - Code: 400, - Message: "参数错误: " + err.Error(), - }) - return - } - - website, err := h.websiteService.UpdateWebsite(id, req) - if err != nil { - c.JSON(http.StatusInternalServerError, models.APIResponse{ - Code: 500, - Message: "更新失败: " + err.Error(), - }) - return - } - - if website == nil { - c.JSON(http.StatusNotFound, models.APIResponse{ - Code: 404, - Message: "网站不存在", - }) - return - } - - c.JSON(http.StatusOK, models.APIResponse{ - Code: 0, - Message: "更新成功", - Data: website, - }) -} - -// DeleteWebsite 删除网站 -func (h *WebsiteHandler) DeleteWebsite(c *gin.Context) { - id := c.Param("id") - - if err := h.websiteService.DeleteWebsite(id); err != nil { - c.JSON(http.StatusInternalServerError, models.APIResponse{ - Code: 500, - Message: "删除失败: " + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, models.APIResponse{ - Code: 0, - Message: "删除成功", - }) -} - -// CheckWebsiteNow 立即检测网站 -func (h *WebsiteHandler) CheckWebsiteNow(c *gin.Context) { - id := c.Param("id") - - website := h.websiteService.GetWebsite(id) - if website == nil { - c.JSON(http.StatusNotFound, models.APIResponse{ - Code: 404, - Message: "网站不存在", - }) - return - } - - h.monitorService.CheckWebsiteNow(id) - - c.JSON(http.StatusOK, models.APIResponse{ - Code: 0, - Message: "检测任务已提交", - }) -} - -// GetGroups 获取所有分组 -func (h *WebsiteHandler) GetGroups(c *gin.Context) { - groups := h.websiteService.GetGroups() - - c.JSON(http.StatusOK, models.APIResponse{ - Code: 0, - Message: "success", - Data: groups, - }) -} - -// AddGroup 添加分组 -func (h *WebsiteHandler) AddGroup(c *gin.Context) { - var req struct { - Name string `json:"name" binding:"required"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.APIResponse{ - Code: 400, - Message: "参数错误: " + err.Error(), - }) - return - } - - group, err := h.websiteService.AddGroup(req.Name) - if err != nil { - c.JSON(http.StatusInternalServerError, models.APIResponse{ - Code: 500, - Message: "添加失败: " + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, models.APIResponse{ - Code: 0, - Message: "添加成功", - Data: group, - }) -} +package handlers + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "mengyaping-backend/models" + "mengyaping-backend/services" +) + +// WebsiteHandler 网站处理器 +type WebsiteHandler struct { + websiteService *services.WebsiteService + monitorService *services.MonitorService +} + +// NewWebsiteHandler 创建网站处理器 +func NewWebsiteHandler() *WebsiteHandler { + return &WebsiteHandler{ + websiteService: services.NewWebsiteService(), + monitorService: services.GetMonitorService(), + } +} + +// GetWebsites 获取所有网站状态 +func (h *WebsiteHandler) GetWebsites(c *gin.Context) { + statuses := h.monitorService.GetAllWebsiteStatuses() + + c.JSON(http.StatusOK, models.APIResponse{ + Code: 0, + Message: "success", + Data: statuses, + }) +} + +// GetWebsite 获取单个网站状态 +func (h *WebsiteHandler) GetWebsite(c *gin.Context) { + id := c.Param("id") + + status := h.monitorService.GetWebsiteStatus(id) + if status == nil { + c.JSON(http.StatusNotFound, models.APIResponse{ + Code: 404, + Message: "网站不存在", + }) + return + } + + c.JSON(http.StatusOK, models.APIResponse{ + Code: 0, + Message: "success", + Data: status, + }) +} + +// CreateWebsite 创建网站 +func (h *WebsiteHandler) CreateWebsite(c *gin.Context) { + var req models.CreateWebsiteRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, models.APIResponse{ + Code: 400, + Message: "参数错误: " + err.Error(), + }) + return + } + + website, err := h.websiteService.CreateWebsite(req) + if err != nil { + c.JSON(http.StatusInternalServerError, models.APIResponse{ + Code: 500, + Message: "创建失败: " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, models.APIResponse{ + Code: 0, + Message: "创建成功", + Data: website, + }) +} + +// UpdateWebsite 更新网站 +func (h *WebsiteHandler) UpdateWebsite(c *gin.Context) { + id := c.Param("id") + + var req models.UpdateWebsiteRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, models.APIResponse{ + Code: 400, + Message: "参数错误: " + err.Error(), + }) + return + } + + website, err := h.websiteService.UpdateWebsite(id, req) + if err != nil { + c.JSON(http.StatusInternalServerError, models.APIResponse{ + Code: 500, + Message: "更新失败: " + err.Error(), + }) + return + } + + if website == nil { + c.JSON(http.StatusNotFound, models.APIResponse{ + Code: 404, + Message: "网站不存在", + }) + return + } + + c.JSON(http.StatusOK, models.APIResponse{ + Code: 0, + Message: "更新成功", + Data: website, + }) +} + +// DeleteWebsite 删除网站 +func (h *WebsiteHandler) DeleteWebsite(c *gin.Context) { + id := c.Param("id") + + if err := h.websiteService.DeleteWebsite(id); err != nil { + c.JSON(http.StatusInternalServerError, models.APIResponse{ + Code: 500, + Message: "删除失败: " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, models.APIResponse{ + Code: 0, + Message: "删除成功", + }) +} + +// CheckWebsiteNow 立即检测网站 +func (h *WebsiteHandler) CheckWebsiteNow(c *gin.Context) { + id := c.Param("id") + + website := h.websiteService.GetWebsite(id) + if website == nil { + c.JSON(http.StatusNotFound, models.APIResponse{ + Code: 404, + Message: "网站不存在", + }) + return + } + + h.monitorService.CheckWebsiteNow(id) + + c.JSON(http.StatusOK, models.APIResponse{ + Code: 0, + Message: "检测任务已提交", + }) +} + +// GetGroups 获取所有分组 +func (h *WebsiteHandler) GetGroups(c *gin.Context) { + groups := h.websiteService.GetGroups() + + c.JSON(http.StatusOK, models.APIResponse{ + Code: 0, + Message: "success", + Data: groups, + }) +} + +// AddGroup 添加分组 +func (h *WebsiteHandler) AddGroup(c *gin.Context) { + var req struct { + Name string `json:"name" binding:"required"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, models.APIResponse{ + Code: 400, + Message: "参数错误: " + err.Error(), + }) + return + } + + group, err := h.websiteService.AddGroup(req.Name) + if err != nil { + c.JSON(http.StatusInternalServerError, models.APIResponse{ + Code: 500, + Message: "添加失败: " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, models.APIResponse{ + Code: 0, + Message: "添加成功", + Data: group, + }) +} diff --git a/mengyaping-backend/main.go b/mengyaping-backend/main.go index 8b4fca3..3286ce9 100644 --- a/mengyaping-backend/main.go +++ b/mengyaping-backend/main.go @@ -1,47 +1,47 @@ -package main - -import ( - "fmt" - "log" - "os" - "os/signal" - "syscall" - - "mengyaping-backend/config" - "mengyaping-backend/router" - "mengyaping-backend/services" -) - -func main() { - // 获取配置 - cfg := config.GetConfig() - - // 确保数据目录存在 - os.MkdirAll(cfg.DataPath, 0755) - - // 启动监控服务 - monitorService := services.GetMonitorService() - go monitorService.Start() - - // 设置路由 - r := router.SetupRouter() - - // 优雅关闭 - go func() { - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - <-sigCh - - log.Println("正在关闭服务...") - monitorService.Stop() - os.Exit(0) - }() - - // 启动服务器 - addr := fmt.Sprintf("%s:%s", cfg.Server.Host, cfg.Server.Port) - log.Printf("🌱 萌芽Ping 监控服务已启动,监听地址: %s\n", addr) - - if err := r.Run(addr); err != nil { - log.Fatalf("服务器启动失败: %v", err) - } -} +package main + +import ( + "fmt" + "log" + "os" + "os/signal" + "syscall" + + "mengyaping-backend/config" + "mengyaping-backend/router" + "mengyaping-backend/services" +) + +func main() { + // 获取配置 + cfg := config.GetConfig() + + // 确保数据目录存在 + os.MkdirAll(cfg.DataPath, 0755) + + // 启动监控服务 + monitorService := services.GetMonitorService() + go monitorService.Start() + + // 设置路由 + r := router.SetupRouter() + + // 优雅关闭 + go func() { + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + <-sigCh + + log.Println("正在关闭服务...") + monitorService.Stop() + os.Exit(0) + }() + + // 启动服务器 + addr := fmt.Sprintf("%s:%s", cfg.Server.Host, cfg.Server.Port) + log.Printf("🌱 萌芽Ping 监控服务已启动,监听地址: %s\n", addr) + + if err := r.Run(addr); err != nil { + log.Fatalf("服务器启动失败: %v", err) + } +} diff --git a/mengyaping-backend/models/website.go b/mengyaping-backend/models/website.go index 792700c..e3a281a 100644 --- a/mengyaping-backend/models/website.go +++ b/mengyaping-backend/models/website.go @@ -1,98 +1,113 @@ -package models - -import ( - "time" -) - -// Website 网站信息 -type Website struct { - ID string `json:"id"` - Name string `json:"name"` // 网站名称 - Group string `json:"group"` // 所属分组 - URLs []URLInfo `json:"urls"` // 网站访问地址列表 - Favicon string `json:"favicon"` // 网站图标URL - Title string `json:"title"` // 网站标题 - CreatedAt time.Time `json:"created_at"` // 创建时间 - UpdatedAt time.Time `json:"updated_at"` // 更新时间 -} - -// URLInfo 单个URL的信息 -type URLInfo struct { - ID string `json:"id"` - URL string `json:"url"` // 访问地址 - Remark string `json:"remark"` // 备注说明 -} - -// MonitorRecord 监控记录 -type MonitorRecord struct { - WebsiteID string `json:"website_id"` - URLID string `json:"url_id"` - URL string `json:"url"` - StatusCode int `json:"status_code"` // HTTP状态码 - Latency int64 `json:"latency"` // 延迟(毫秒) - IsUp bool `json:"is_up"` // 是否可访问 - Error string `json:"error"` // 错误信息 - CheckedAt time.Time `json:"checked_at"` // 检测时间 -} - -// WebsiteStatus 网站状态(用于前端展示) -type WebsiteStatus struct { - Website Website `json:"website"` - URLStatuses []URLStatus `json:"url_statuses"` - Uptime24h float64 `json:"uptime_24h"` // 24小时可用率 - Uptime7d float64 `json:"uptime_7d"` // 7天可用率 - LastChecked time.Time `json:"last_checked"` // 最后检测时间 -} - -// URLStatus 单个URL的状态 -type URLStatus struct { - URLInfo URLInfo `json:"url_info"` - CurrentState MonitorRecord `json:"current_state"` // 当前状态 - History24h []MonitorRecord `json:"history_24h"` // 24小时历史 - History7d []HourlyStats `json:"history_7d"` // 7天按小时统计 - Uptime24h float64 `json:"uptime_24h"` // 24小时可用率 - Uptime7d float64 `json:"uptime_7d"` // 7天可用率 - AvgLatency int64 `json:"avg_latency"` // 平均延迟 -} - -// HourlyStats 每小时统计 -type HourlyStats struct { - Hour time.Time `json:"hour"` - TotalCount int `json:"total_count"` - UpCount int `json:"up_count"` - AvgLatency int64 `json:"avg_latency"` - Uptime float64 `json:"uptime"` -} - -// Group 分组 -type Group struct { - ID string `json:"id"` - Name string `json:"name"` -} - -// DefaultGroups 默认分组 -var DefaultGroups = []Group{ - {ID: "normal", Name: "普通网站"}, - {ID: "admin", Name: "管理员网站"}, -} - -// CreateWebsiteRequest 创建网站请求 -type CreateWebsiteRequest struct { - Name string `json:"name" binding:"required"` - Group string `json:"group" binding:"required"` - URLs []string `json:"urls" binding:"required,min=1"` -} - -// UpdateWebsiteRequest 更新网站请求 -type UpdateWebsiteRequest struct { - Name string `json:"name"` - Group string `json:"group"` - URLs []string `json:"urls"` -} - -// APIResponse API响应 -type APIResponse struct { - Code int `json:"code"` - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` -} +package models + +import ( + "time" +) + +// Website 网站信息 +type Website struct { + ID string `json:"id"` + Name string `json:"name"` // 网站名称 + Groups []string `json:"groups"` // 所属分组列表(支持多分组) + Group string `json:"group,omitempty"` // 已废弃,仅用于旧数据兼容 + URLs []URLInfo `json:"urls"` // 网站访问地址列表 + IPAddresses []string `json:"ip_addresses,omitempty"` // 域名解析的IP地址 + Favicon string `json:"favicon"` // 网站图标URL + Title string `json:"title"` // 网站标题 + CreatedAt time.Time `json:"created_at"` // 创建时间 + UpdatedAt time.Time `json:"updated_at"` // 更新时间 +} + +// URLInfo 单个URL的信息 +type URLInfo struct { + ID string `json:"id"` + URL string `json:"url"` // 访问地址 + Remark string `json:"remark"` // 备注说明 +} + +// MonitorRecord 监控记录 +type MonitorRecord struct { + WebsiteID string `json:"website_id"` + URLID string `json:"url_id"` + URL string `json:"url"` + StatusCode int `json:"status_code"` // HTTP状态码 + Latency int64 `json:"latency"` // 延迟(毫秒) + IsUp bool `json:"is_up"` // 是否可访问 + Error string `json:"error"` // 错误信息 + CheckedAt time.Time `json:"checked_at"` // 检测时间 +} + +// WebsiteStatus 网站状态(用于前端展示) +type WebsiteStatus struct { + Website Website `json:"website"` + URLStatuses []URLStatus `json:"url_statuses"` + DailyHistory []DailyStats `json:"daily_history"` // 90天逐日统计 + Uptime24h float64 `json:"uptime_24h"` // 24小时可用率 + Uptime7d float64 `json:"uptime_7d"` // 7天可用率 + Uptime90d float64 `json:"uptime_90d"` // 90天可用率 + LastChecked time.Time `json:"last_checked"` // 最后检测时间 +} + +// URLStatus 单个URL的状态 +type URLStatus struct { + URLInfo URLInfo `json:"url_info"` + CurrentState MonitorRecord `json:"current_state"` // 当前状态 + History24h []MonitorRecord `json:"history_24h"` // 24小时历史 + History7d []HourlyStats `json:"history_7d"` // 7天按小时统计 + Uptime24h float64 `json:"uptime_24h"` // 24小时可用率 + Uptime7d float64 `json:"uptime_7d"` // 7天可用率 + AvgLatency int64 `json:"avg_latency"` // 平均延迟 +} + +// HourlyStats 每小时统计 +type HourlyStats struct { + Hour time.Time `json:"hour"` + TotalCount int `json:"total_count"` + UpCount int `json:"up_count"` + AvgLatency int64 `json:"avg_latency"` + Uptime float64 `json:"uptime"` +} + +// DailyStats 每日统计 +type DailyStats struct { + Date time.Time `json:"date"` + TotalCount int `json:"total_count"` + UpCount int `json:"up_count"` + AvgLatency int64 `json:"avg_latency"` + Uptime float64 `json:"uptime"` +} + +// Group 分组 +type Group struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// DefaultGroups 默认分组 +var DefaultGroups = []Group{ + {ID: "normal", Name: "普通网站"}, + {ID: "admin", Name: "管理员网站"}, +} + +// CreateWebsiteRequest 创建网站请求 +type CreateWebsiteRequest struct { + Name string `json:"name" binding:"required"` + Groups []string `json:"groups" binding:"required,min=1"` + Group string `json:"group"` + URLs []string `json:"urls" binding:"required,min=1"` +} + +// UpdateWebsiteRequest 更新网站请求 +type UpdateWebsiteRequest struct { + Name string `json:"name"` + Groups []string `json:"groups"` + Group string `json:"group"` + URLs []string `json:"urls"` +} + +// APIResponse API响应 +type APIResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} diff --git a/mengyaping-backend/router/router.go b/mengyaping-backend/router/router.go index 25a559b..e6177d8 100644 --- a/mengyaping-backend/router/router.go +++ b/mengyaping-backend/router/router.go @@ -1,51 +1,51 @@ -package router - -import ( - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - - "mengyaping-backend/handlers" -) - -// SetupRouter 设置路由 -func SetupRouter() *gin.Engine { - r := gin.Default() - - // CORS配置 - r.Use(cors.New(cors.Config{ - AllowOrigins: []string{"*"}, - AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, - ExposeHeaders: []string{"Content-Length"}, - AllowCredentials: true, - })) - - // 创建处理器 - websiteHandler := handlers.NewWebsiteHandler() - - // API路由组 - api := r.Group("/api") - { - // 健康检查 - api.GET("/health", func(c *gin.Context) { - c.JSON(200, gin.H{ - "status": "ok", - "message": "服务运行正常", - }) - }) - - // 网站相关 - api.GET("/websites", websiteHandler.GetWebsites) - api.GET("/websites/:id", websiteHandler.GetWebsite) - api.POST("/websites", websiteHandler.CreateWebsite) - api.PUT("/websites/:id", websiteHandler.UpdateWebsite) - api.DELETE("/websites/:id", websiteHandler.DeleteWebsite) - api.POST("/websites/:id/check", websiteHandler.CheckWebsiteNow) - - // 分组相关 - api.GET("/groups", websiteHandler.GetGroups) - api.POST("/groups", websiteHandler.AddGroup) - } - - return r -} +package router + +import ( + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + + "mengyaping-backend/handlers" +) + +// SetupRouter 设置路由 +func SetupRouter() *gin.Engine { + r := gin.Default() + + // CORS配置 + r.Use(cors.New(cors.Config{ + AllowOrigins: []string{"*"}, + AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, + ExposeHeaders: []string{"Content-Length"}, + AllowCredentials: true, + })) + + // 创建处理器 + websiteHandler := handlers.NewWebsiteHandler() + + // API路由组 + api := r.Group("/api") + { + // 健康检查 + api.GET("/health", func(c *gin.Context) { + c.JSON(200, gin.H{ + "status": "ok", + "message": "服务运行正常", + }) + }) + + // 网站相关 + api.GET("/websites", websiteHandler.GetWebsites) + api.GET("/websites/:id", websiteHandler.GetWebsite) + api.POST("/websites", websiteHandler.CreateWebsite) + api.PUT("/websites/:id", websiteHandler.UpdateWebsite) + api.DELETE("/websites/:id", websiteHandler.DeleteWebsite) + api.POST("/websites/:id/check", websiteHandler.CheckWebsiteNow) + + // 分组相关 + api.GET("/groups", websiteHandler.GetGroups) + api.POST("/groups", websiteHandler.AddGroup) + } + + return r +} diff --git a/mengyaping-backend/services/monitor.go b/mengyaping-backend/services/monitor.go index 46f5876..757ceb5 100644 --- a/mengyaping-backend/services/monitor.go +++ b/mengyaping-backend/services/monitor.go @@ -1,302 +1,418 @@ -package services - -import ( - "log" - "sync" - "time" - - "mengyaping-backend/config" - "mengyaping-backend/models" - "mengyaping-backend/storage" - "mengyaping-backend/utils" -) - -// MonitorService 监控服务 -type MonitorService struct { - httpClient *utils.HTTPClient - storage *storage.Storage - stopCh chan struct{} - running bool - mu sync.Mutex -} - -var ( - monitorService *MonitorService - monitorOnce sync.Once -) - -// GetMonitorService 获取监控服务单例 -func GetMonitorService() *MonitorService { - monitorOnce.Do(func() { - cfg := config.GetConfig() - monitorService = &MonitorService{ - httpClient: utils.NewHTTPClient(cfg.Monitor.Timeout), - storage: storage.GetStorage(), - stopCh: make(chan struct{}), - } - }) - return monitorService -} - -// Start 启动监控服务 -func (s *MonitorService) Start() { - s.mu.Lock() - if s.running { - s.mu.Unlock() - return - } - s.running = true - s.mu.Unlock() - - log.Println("监控服务已启动") - - // 立即执行一次检测 - go s.checkAll() - - // 定时检测 - cfg := config.GetConfig() - ticker := time.NewTicker(cfg.Monitor.Interval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - go s.checkAll() - case <-s.stopCh: - log.Println("监控服务已停止") - return - } - } -} - -// Stop 停止监控服务 -func (s *MonitorService) Stop() { - s.mu.Lock() - defer s.mu.Unlock() - - if s.running { - close(s.stopCh) - s.running = false - } -} - -// checkAll 检查所有网站 -func (s *MonitorService) checkAll() { - websites := s.storage.GetWebsites() - - var wg sync.WaitGroup - semaphore := make(chan struct{}, 10) // 限制并发数 - - for _, website := range websites { - for _, urlInfo := range website.URLs { - wg.Add(1) - go func(w models.Website, u models.URLInfo) { - defer wg.Done() - semaphore <- struct{}{} - defer func() { <-semaphore }() - - s.checkURL(w, u) - }(website, urlInfo) - } - } - - wg.Wait() - - // 保存记录 - s.storage.SaveAll() -} - -// checkURL 检查单个URL -func (s *MonitorService) checkURL(website models.Website, urlInfo models.URLInfo) { - result := s.httpClient.CheckWebsite(urlInfo.URL) - - record := models.MonitorRecord{ - WebsiteID: website.ID, - URLID: urlInfo.ID, - URL: urlInfo.URL, - StatusCode: result.StatusCode, - Latency: result.Latency.Milliseconds(), - IsUp: result.Error == nil && utils.IsSuccessStatus(result.StatusCode), - CheckedAt: time.Now(), - } - - if result.Error != nil { - record.Error = result.Error.Error() - } - - s.storage.AddRecord(record) - - // 更新网站信息(标题和Favicon) - if result.Title != "" || result.Favicon != "" { - w := s.storage.GetWebsite(website.ID) - if w != nil { - needUpdate := false - if result.Title != "" && w.Title != result.Title { - w.Title = result.Title - needUpdate = true - } - if result.Favicon != "" && w.Favicon != result.Favicon { - w.Favicon = result.Favicon - needUpdate = true - } - if needUpdate { - w.UpdatedAt = time.Now() - s.storage.UpdateWebsite(*w) - } - } - } - - log.Printf("检测 [%s] %s - 状态码: %d, 延迟: %dms, 可用: %v", - website.Name, urlInfo.URL, result.StatusCode, result.Latency.Milliseconds(), record.IsUp) -} - -// CheckWebsiteNow 立即检查指定网站 -func (s *MonitorService) CheckWebsiteNow(websiteID string) { - website := s.storage.GetWebsite(websiteID) - if website == nil { - return - } - - for _, urlInfo := range website.URLs { - go s.checkURL(*website, urlInfo) - } -} - -// GetWebsiteStatus 获取网站状态 -func (s *MonitorService) GetWebsiteStatus(websiteID string) *models.WebsiteStatus { - website := s.storage.GetWebsite(websiteID) - if website == nil { - return nil - } - - status := &models.WebsiteStatus{ - Website: *website, - URLStatuses: []models.URLStatus{}, - } - - now := time.Now() - since24h := now.Add(-24 * time.Hour) - since7d := now.Add(-7 * 24 * time.Hour) - - var totalUptime24h, totalUptime7d float64 - var urlCount int - - for _, urlInfo := range website.URLs { - urlStatus := s.getURLStatus(website.ID, urlInfo, since24h, since7d) - status.URLStatuses = append(status.URLStatuses, urlStatus) - - totalUptime24h += urlStatus.Uptime24h - totalUptime7d += urlStatus.Uptime7d - urlCount++ - } - - if urlCount > 0 { - status.Uptime24h = totalUptime24h / float64(urlCount) - status.Uptime7d = totalUptime7d / float64(urlCount) - } - - // 获取最后检测时间 - for _, urlStatus := range status.URLStatuses { - if urlStatus.CurrentState.CheckedAt.After(status.LastChecked) { - status.LastChecked = urlStatus.CurrentState.CheckedAt - } - } - - return status -} - -// getURLStatus 获取URL状态 -func (s *MonitorService) getURLStatus(websiteID string, urlInfo models.URLInfo, since24h, since7d time.Time) models.URLStatus { - urlStatus := models.URLStatus{ - URLInfo: urlInfo, - } - - // 获取最新记录 - latest := s.storage.GetLatestRecord(websiteID, urlInfo.ID) - if latest != nil { - urlStatus.CurrentState = *latest - } - - // 获取24小时记录 - records24h := s.storage.GetRecords(websiteID, urlInfo.ID, since24h) - urlStatus.History24h = records24h - - // 计算24小时可用率 - if len(records24h) > 0 { - upCount := 0 - var totalLatency int64 - for _, r := range records24h { - if r.IsUp { - upCount++ - } - totalLatency += r.Latency - } - urlStatus.Uptime24h = float64(upCount) / float64(len(records24h)) * 100 - urlStatus.AvgLatency = totalLatency / int64(len(records24h)) - } - - // 获取7天记录并按小时统计 - records7d := s.storage.GetRecords(websiteID, urlInfo.ID, since7d) - urlStatus.History7d = s.aggregateByHour(records7d) - - // 计算7天可用率 - if len(records7d) > 0 { - upCount := 0 - for _, r := range records7d { - if r.IsUp { - upCount++ - } - } - urlStatus.Uptime7d = float64(upCount) / float64(len(records7d)) * 100 - } - - return urlStatus -} - -// aggregateByHour 按小时聚合记录 -func (s *MonitorService) aggregateByHour(records []models.MonitorRecord) []models.HourlyStats { - hourlyMap := make(map[string]*models.HourlyStats) - - for _, r := range records { - hourKey := r.CheckedAt.Truncate(time.Hour).Format(time.RFC3339) - - if _, exists := hourlyMap[hourKey]; !exists { - hourlyMap[hourKey] = &models.HourlyStats{ - Hour: r.CheckedAt.Truncate(time.Hour), - } - } - - stats := hourlyMap[hourKey] - stats.TotalCount++ - if r.IsUp { - stats.UpCount++ - } - stats.AvgLatency += r.Latency - } - - var result []models.HourlyStats - for _, stats := range hourlyMap { - if stats.TotalCount > 0 { - stats.AvgLatency /= int64(stats.TotalCount) - stats.Uptime = float64(stats.UpCount) / float64(stats.TotalCount) * 100 - } - result = append(result, *stats) - } - - return result -} - -// GetAllWebsiteStatuses 获取所有网站状态 -func (s *MonitorService) GetAllWebsiteStatuses() []models.WebsiteStatus { - websites := s.storage.GetWebsites() - var statuses []models.WebsiteStatus - - for _, website := range websites { - status := s.GetWebsiteStatus(website.ID) - if status != nil { - statuses = append(statuses, *status) - } - } - - return statuses -} +package services + +import ( + "log" + "sort" + "sync" + "time" + + "mengyaping-backend/config" + "mengyaping-backend/models" + "mengyaping-backend/storage" + "mengyaping-backend/utils" +) + +// MonitorService 监控服务 +type MonitorService struct { + httpClient *utils.HTTPClient + storage *storage.Storage + stopCh chan struct{} + running bool + mu sync.Mutex +} + +var ( + monitorService *MonitorService + monitorOnce sync.Once +) + +// GetMonitorService 获取监控服务单例 +func GetMonitorService() *MonitorService { + monitorOnce.Do(func() { + cfg := config.GetConfig() + monitorService = &MonitorService{ + httpClient: utils.NewHTTPClient(cfg.Monitor.Timeout), + storage: storage.GetStorage(), + stopCh: make(chan struct{}), + } + }) + return monitorService +} + +// Start 启动监控服务 +func (s *MonitorService) Start() { + s.mu.Lock() + if s.running { + s.mu.Unlock() + return + } + s.running = true + s.mu.Unlock() + + log.Println("监控服务已启动") + + // 立即执行一次检测 + go s.checkAll() + + // 定时检测 + cfg := config.GetConfig() + ticker := time.NewTicker(cfg.Monitor.Interval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + go s.checkAll() + case <-s.stopCh: + log.Println("监控服务已停止") + return + } + } +} + +// Stop 停止监控服务 +func (s *MonitorService) Stop() { + s.mu.Lock() + defer s.mu.Unlock() + + if s.running { + close(s.stopCh) + s.running = false + } +} + +// checkAll 检查所有网站(错峰执行,避免并发暴涨) +func (s *MonitorService) checkAll() { + websites := s.storage.GetWebsites() + semaphore := make(chan struct{}, 3) // 最多 3 个并发检测 + + for i, website := range websites { + // 每个网站之间间隔 1 秒,把检测分散开 + if i > 0 { + time.Sleep(1 * time.Second) + } + + var wg sync.WaitGroup + for _, urlInfo := range website.URLs { + wg.Add(1) + go func(w models.Website, u models.URLInfo) { + defer wg.Done() + semaphore <- struct{}{} + defer func() { <-semaphore }() + s.checkURL(w, u) + }(website, urlInfo) + } + wg.Wait() + } + + // 检测完毕后,逐个解析 DNS + s.resolveAllWebsiteIPs(websites) + + // 保存记录 + s.storage.SaveAll() + log.Printf("本轮检测完成,共 %d 个网站", len(websites)) +} + +// resolveAllWebsiteIPs 逐个解析所有网站域名 IP(每次都刷新) +func (s *MonitorService) resolveAllWebsiteIPs(websites []models.Website) { + for i, website := range websites { + if len(website.URLs) == 0 { + continue + } + if i > 0 { + time.Sleep(500 * time.Millisecond) + } + s.resolveWebsiteIP(website) + } +} + +// resolveWebsiteIP 解析单个网站的域名 IP +func (s *MonitorService) resolveWebsiteIP(website models.Website) { + if len(website.URLs) == 0 { + return + } + + ips, err := utils.ResolveDomainIPs(website.URLs[0].URL) + if err != nil { + log.Printf("DNS解析失败 [%s]: %v", website.Name, err) + return + } + + if len(ips) == 0 { + return + } + + w := s.storage.GetWebsite(website.ID) + if w == nil { + return + } + + w.IPAddresses = ips + w.UpdatedAt = time.Now() + s.storage.UpdateWebsite(*w) + log.Printf("DNS解析 [%s] → %v", website.Name, ips) +} + +// checkURL 检查单个URL(带重试) +func (s *MonitorService) checkURL(website models.Website, urlInfo models.URLInfo) { + cfg := config.GetConfig() + maxRetries := cfg.Monitor.RetryCount + + var result utils.CheckResult + + for attempt := 0; attempt <= maxRetries; attempt++ { + if attempt > 0 { + time.Sleep(time.Duration(attempt) * 2 * time.Second) + log.Printf("重试 [%s] %s - 第 %d 次重试", website.Name, urlInfo.URL, attempt) + } + + result = s.httpClient.CheckWebsiteStatus(urlInfo.URL) + if result.Error == nil && utils.IsSuccessStatus(result.StatusCode) { + break + } + } + + record := models.MonitorRecord{ + WebsiteID: website.ID, + URLID: urlInfo.ID, + URL: urlInfo.URL, + StatusCode: result.StatusCode, + Latency: result.Latency.Milliseconds(), + IsUp: result.Error == nil && utils.IsSuccessStatus(result.StatusCode), + CheckedAt: time.Now(), + } + + if result.Error != nil { + record.Error = result.Error.Error() + } + + s.storage.AddRecord(record) + + // 仅当网站无标题时才做完整检测来获取元数据 + if record.IsUp && website.Title == "" { + fullResult := s.httpClient.CheckWebsite(urlInfo.URL) + if fullResult.Title != "" { + w := s.storage.GetWebsite(website.ID) + if w != nil { + w.Title = fullResult.Title + w.UpdatedAt = time.Now() + s.storage.UpdateWebsite(*w) + } + } + } + + log.Printf("检测 [%s] %s - 状态码: %d, 延迟: %dms, 可用: %v", + website.Name, urlInfo.URL, result.StatusCode, result.Latency.Milliseconds(), record.IsUp) +} + +// CheckWebsiteNow 立即检查指定网站(状态 + DNS,等待完成后保存) +func (s *MonitorService) CheckWebsiteNow(websiteID string) { + website := s.storage.GetWebsite(websiteID) + if website == nil { + return + } + + // 逐个检测该网站的所有 URL + for _, urlInfo := range website.URLs { + s.checkURL(*website, urlInfo) + } + + // 刷新 DNS + s.resolveWebsiteIP(*website) + + s.storage.SaveAll() +} + +// GetWebsiteStatus 获取网站状态 +func (s *MonitorService) GetWebsiteStatus(websiteID string) *models.WebsiteStatus { + website := s.storage.GetWebsite(websiteID) + if website == nil { + return nil + } + + status := &models.WebsiteStatus{ + Website: *website, + URLStatuses: []models.URLStatus{}, + } + + now := time.Now() + since24h := now.Add(-24 * time.Hour) + since7d := now.Add(-7 * 24 * time.Hour) + since90d := now.Add(-90 * 24 * time.Hour) + + var totalUptime24h, totalUptime7d float64 + var urlCount int + var allRecords90d []models.MonitorRecord + + for _, urlInfo := range website.URLs { + urlStatus := s.getURLStatus(website.ID, urlInfo, since24h, since7d) + status.URLStatuses = append(status.URLStatuses, urlStatus) + + totalUptime24h += urlStatus.Uptime24h + totalUptime7d += urlStatus.Uptime7d + urlCount++ + + records90d := s.storage.GetRecords(website.ID, urlInfo.ID, since90d) + allRecords90d = append(allRecords90d, records90d...) + } + + if urlCount > 0 { + status.Uptime24h = totalUptime24h / float64(urlCount) + status.Uptime7d = totalUptime7d / float64(urlCount) + } + + // 90 天逐日统计 + status.DailyHistory = s.aggregateByDay(allRecords90d) + if len(allRecords90d) > 0 { + upCount := 0 + for _, r := range allRecords90d { + if r.IsUp { + upCount++ + } + } + status.Uptime90d = float64(upCount) / float64(len(allRecords90d)) * 100 + } + + // 获取最后检测时间 + for _, urlStatus := range status.URLStatuses { + if urlStatus.CurrentState.CheckedAt.After(status.LastChecked) { + status.LastChecked = urlStatus.CurrentState.CheckedAt + } + } + + return status +} + +// getURLStatus 获取URL状态 +func (s *MonitorService) getURLStatus(websiteID string, urlInfo models.URLInfo, since24h, since7d time.Time) models.URLStatus { + urlStatus := models.URLStatus{ + URLInfo: urlInfo, + } + + // 获取最新记录 + latest := s.storage.GetLatestRecord(websiteID, urlInfo.ID) + if latest != nil { + urlStatus.CurrentState = *latest + } + + // 获取24小时记录 + records24h := s.storage.GetRecords(websiteID, urlInfo.ID, since24h) + urlStatus.History24h = records24h + + // 计算24小时可用率 + if len(records24h) > 0 { + upCount := 0 + var totalLatency int64 + for _, r := range records24h { + if r.IsUp { + upCount++ + } + totalLatency += r.Latency + } + urlStatus.Uptime24h = float64(upCount) / float64(len(records24h)) * 100 + urlStatus.AvgLatency = totalLatency / int64(len(records24h)) + } + + // 获取7天记录并按小时统计 + records7d := s.storage.GetRecords(websiteID, urlInfo.ID, since7d) + urlStatus.History7d = s.aggregateByHour(records7d) + + // 计算7天可用率 + if len(records7d) > 0 { + upCount := 0 + for _, r := range records7d { + if r.IsUp { + upCount++ + } + } + urlStatus.Uptime7d = float64(upCount) / float64(len(records7d)) * 100 + } + + return urlStatus +} + +// aggregateByHour 按小时聚合记录 +func (s *MonitorService) aggregateByHour(records []models.MonitorRecord) []models.HourlyStats { + hourlyMap := make(map[string]*models.HourlyStats) + + for _, r := range records { + hourKey := r.CheckedAt.Truncate(time.Hour).Format(time.RFC3339) + + if _, exists := hourlyMap[hourKey]; !exists { + hourlyMap[hourKey] = &models.HourlyStats{ + Hour: r.CheckedAt.Truncate(time.Hour), + } + } + + stats := hourlyMap[hourKey] + stats.TotalCount++ + if r.IsUp { + stats.UpCount++ + } + stats.AvgLatency += r.Latency + } + + var result []models.HourlyStats + for _, stats := range hourlyMap { + if stats.TotalCount > 0 { + stats.AvgLatency /= int64(stats.TotalCount) + stats.Uptime = float64(stats.UpCount) / float64(stats.TotalCount) * 100 + } + result = append(result, *stats) + } + + return result +} + +// aggregateByDay 按天聚合记录 +func (s *MonitorService) aggregateByDay(records []models.MonitorRecord) []models.DailyStats { + dayMap := make(map[string]*models.DailyStats) + + for _, r := range records { + dayTime := time.Date(r.CheckedAt.Year(), r.CheckedAt.Month(), r.CheckedAt.Day(), 0, 0, 0, 0, r.CheckedAt.Location()) + dayKey := dayTime.Format("2006-01-02") + + if _, exists := dayMap[dayKey]; !exists { + dayMap[dayKey] = &models.DailyStats{ + Date: dayTime, + } + } + + stats := dayMap[dayKey] + stats.TotalCount++ + if r.IsUp { + stats.UpCount++ + } + stats.AvgLatency += r.Latency + } + + result := make([]models.DailyStats, 0, len(dayMap)) + for _, stats := range dayMap { + if stats.TotalCount > 0 { + stats.AvgLatency /= int64(stats.TotalCount) + stats.Uptime = float64(stats.UpCount) / float64(stats.TotalCount) * 100 + } + result = append(result, *stats) + } + + sort.Slice(result, func(i, j int) bool { + return result[i].Date.Before(result[j].Date) + }) + + return result +} + +// GetAllWebsiteStatuses 获取所有网站状态 +func (s *MonitorService) GetAllWebsiteStatuses() []models.WebsiteStatus { + websites := s.storage.GetWebsites() + var statuses []models.WebsiteStatus + + for _, website := range websites { + status := s.GetWebsiteStatus(website.ID) + if status != nil { + statuses = append(statuses, *status) + } + } + + return statuses +} diff --git a/mengyaping-backend/services/website.go b/mengyaping-backend/services/website.go index 23d85fb..4ae914d 100644 --- a/mengyaping-backend/services/website.go +++ b/mengyaping-backend/services/website.go @@ -1,127 +1,142 @@ -package services - -import ( - "time" - - "mengyaping-backend/models" - "mengyaping-backend/storage" - "mengyaping-backend/utils" -) - -// WebsiteService 网站服务 -type WebsiteService struct { - storage *storage.Storage -} - -// NewWebsiteService 创建网站服务 -func NewWebsiteService() *WebsiteService { - return &WebsiteService{ - storage: storage.GetStorage(), - } -} - -// CreateWebsite 创建网站 -func (s *WebsiteService) CreateWebsite(req models.CreateWebsiteRequest) (*models.Website, error) { - website := models.Website{ - ID: utils.GenerateID(), - Name: req.Name, - Group: req.Group, - URLs: make([]models.URLInfo, 0), - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - for _, url := range req.URLs { - urlInfo := models.URLInfo{ - ID: utils.GenerateShortID(), - URL: url, - } - website.URLs = append(website.URLs, urlInfo) - } - - if err := s.storage.AddWebsite(website); err != nil { - return nil, err - } - - // 立即检测该网站 - go GetMonitorService().CheckWebsiteNow(website.ID) - - return &website, nil -} - -// GetWebsite 获取网站 -func (s *WebsiteService) GetWebsite(id string) *models.Website { - return s.storage.GetWebsite(id) -} - -// GetAllWebsites 获取所有网站 -func (s *WebsiteService) GetAllWebsites() []models.Website { - return s.storage.GetWebsites() -} - -// UpdateWebsite 更新网站 -func (s *WebsiteService) UpdateWebsite(id string, req models.UpdateWebsiteRequest) (*models.Website, error) { - website := s.storage.GetWebsite(id) - if website == nil { - return nil, nil - } - - if req.Name != "" { - website.Name = req.Name - } - if req.Group != "" { - website.Group = req.Group - } - if len(req.URLs) > 0 { - // 保留已有URL的ID,添加新URL - existingURLs := make(map[string]models.URLInfo) - for _, u := range website.URLs { - existingURLs[u.URL] = u - } - - newURLs := make([]models.URLInfo, 0) - for _, url := range req.URLs { - if existing, ok := existingURLs[url]; ok { - newURLs = append(newURLs, existing) - } else { - newURLs = append(newURLs, models.URLInfo{ - ID: utils.GenerateShortID(), - URL: url, - }) - } - } - website.URLs = newURLs - } - - website.UpdatedAt = time.Now() - - if err := s.storage.UpdateWebsite(*website); err != nil { - return nil, err - } - - return website, nil -} - -// DeleteWebsite 删除网站 -func (s *WebsiteService) DeleteWebsite(id string) error { - return s.storage.DeleteWebsite(id) -} - -// GetGroups 获取所有分组 -func (s *WebsiteService) GetGroups() []models.Group { - return s.storage.GetGroups() -} - -// AddGroup 添加分组 -func (s *WebsiteService) AddGroup(name string) (*models.Group, error) { - group := models.Group{ - ID: utils.GenerateShortID(), - Name: name, - } - - if err := s.storage.AddGroup(group); err != nil { - return nil, err - } - - return &group, nil -} +package services + +import ( + "time" + + "mengyaping-backend/models" + "mengyaping-backend/storage" + "mengyaping-backend/utils" +) + +// WebsiteService 网站服务 +type WebsiteService struct { + storage *storage.Storage +} + +// NewWebsiteService 创建网站服务 +func NewWebsiteService() *WebsiteService { + return &WebsiteService{ + storage: storage.GetStorage(), + } +} + +// CreateWebsite 创建网站 +func (s *WebsiteService) CreateWebsite(req models.CreateWebsiteRequest) (*models.Website, error) { + groups := req.Groups + if len(groups) == 0 && req.Group != "" { + groups = []string{req.Group} + } + + website := models.Website{ + ID: utils.GenerateID(), + Name: req.Name, + Groups: groups, + URLs: make([]models.URLInfo, 0), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + for _, url := range req.URLs { + urlInfo := models.URLInfo{ + ID: utils.GenerateShortID(), + URL: url, + } + website.URLs = append(website.URLs, urlInfo) + } + + // 创建前先解析域名 IP + if len(req.URLs) > 0 { + if ips, err := utils.ResolveDomainIPs(req.URLs[0]); err == nil { + website.IPAddresses = ips + } + } + + if err := s.storage.AddWebsite(website); err != nil { + return nil, err + } + + // 立即检测该网站 + go GetMonitorService().CheckWebsiteNow(website.ID) + + return &website, nil +} + +// GetWebsite 获取网站 +func (s *WebsiteService) GetWebsite(id string) *models.Website { + return s.storage.GetWebsite(id) +} + +// GetAllWebsites 获取所有网站 +func (s *WebsiteService) GetAllWebsites() []models.Website { + return s.storage.GetWebsites() +} + +// UpdateWebsite 更新网站 +func (s *WebsiteService) UpdateWebsite(id string, req models.UpdateWebsiteRequest) (*models.Website, error) { + website := s.storage.GetWebsite(id) + if website == nil { + return nil, nil + } + + if req.Name != "" { + website.Name = req.Name + } + if len(req.Groups) > 0 { + website.Groups = req.Groups + } else if req.Group != "" { + website.Groups = []string{req.Group} + } + if len(req.URLs) > 0 { + // 保留已有URL的ID,添加新URL + existingURLs := make(map[string]models.URLInfo) + for _, u := range website.URLs { + existingURLs[u.URL] = u + } + + newURLs := make([]models.URLInfo, 0) + for _, url := range req.URLs { + if existing, ok := existingURLs[url]; ok { + newURLs = append(newURLs, existing) + } else { + newURLs = append(newURLs, models.URLInfo{ + ID: utils.GenerateShortID(), + URL: url, + }) + } + } + website.URLs = newURLs + website.IPAddresses = nil // URL 变更后清空 IP,等下次检测重新解析 + } + + website.UpdatedAt = time.Now() + + if err := s.storage.UpdateWebsite(*website); err != nil { + return nil, err + } + + return website, nil +} + +// DeleteWebsite 删除网站 +func (s *WebsiteService) DeleteWebsite(id string) error { + return s.storage.DeleteWebsite(id) +} + +// GetGroups 获取所有分组 +func (s *WebsiteService) GetGroups() []models.Group { + return s.storage.GetGroups() +} + +// AddGroup 添加分组 +func (s *WebsiteService) AddGroup(name string) (*models.Group, error) { + group := models.Group{ + ID: utils.GenerateShortID(), + Name: name, + } + + if err := s.storage.AddGroup(group); err != nil { + return nil, err + } + + return &group, nil +} diff --git a/mengyaping-backend/storage/storage.go b/mengyaping-backend/storage/storage.go index a027e36..d4737ac 100644 --- a/mengyaping-backend/storage/storage.go +++ b/mengyaping-backend/storage/storage.go @@ -1,285 +1,302 @@ -package storage - -import ( - "encoding/json" - "os" - "path/filepath" - "sync" - "time" - - "mengyaping-backend/config" - "mengyaping-backend/models" -) - -// Storage 数据存储 -type Storage struct { - dataPath string - mu sync.RWMutex - websites []models.Website - records map[string][]models.MonitorRecord // key: websiteID_urlID - groups []models.Group -} - -var ( - store *Storage - once sync.Once -) - -// GetStorage 获取存储单例 -func GetStorage() *Storage { - once.Do(func() { - cfg := config.GetConfig() - store = &Storage{ - dataPath: cfg.DataPath, - websites: []models.Website{}, - records: make(map[string][]models.MonitorRecord), - groups: models.DefaultGroups, - } - store.ensureDataDir() - store.load() - }) - return store -} - -// ensureDataDir 确保数据目录存在 -func (s *Storage) ensureDataDir() { - os.MkdirAll(s.dataPath, 0755) -} - -// load 加载数据 -func (s *Storage) load() { - s.loadWebsites() - s.loadRecords() - s.loadGroups() -} - -// loadWebsites 加载网站数据 -func (s *Storage) loadWebsites() { - filePath := filepath.Join(s.dataPath, "websites.json") - data, err := os.ReadFile(filePath) - if err != nil { - return - } - json.Unmarshal(data, &s.websites) -} - -// loadRecords 加载监控记录 -func (s *Storage) loadRecords() { - filePath := filepath.Join(s.dataPath, "records.json") - data, err := os.ReadFile(filePath) - if err != nil { - return - } - json.Unmarshal(data, &s.records) - - // 清理过期记录 - s.cleanOldRecords() -} - -// loadGroups 加载分组 -func (s *Storage) loadGroups() { - filePath := filepath.Join(s.dataPath, "groups.json") - data, err := os.ReadFile(filePath) - if err != nil { - // 使用默认分组 - s.groups = models.DefaultGroups - s.saveGroups() - return - } - json.Unmarshal(data, &s.groups) -} - -// saveWebsites 保存网站数据 -func (s *Storage) saveWebsites() error { - filePath := filepath.Join(s.dataPath, "websites.json") - data, err := json.MarshalIndent(s.websites, "", " ") - if err != nil { - return err - } - return os.WriteFile(filePath, data, 0644) -} - -// saveRecords 保存监控记录 -func (s *Storage) saveRecords() error { - filePath := filepath.Join(s.dataPath, "records.json") - data, err := json.MarshalIndent(s.records, "", " ") - if err != nil { - return err - } - return os.WriteFile(filePath, data, 0644) -} - -// saveGroups 保存分组 -func (s *Storage) saveGroups() error { - filePath := filepath.Join(s.dataPath, "groups.json") - data, err := json.MarshalIndent(s.groups, "", " ") - if err != nil { - return err - } - return os.WriteFile(filePath, data, 0644) -} - -// cleanOldRecords 清理过期记录 -func (s *Storage) cleanOldRecords() { - cfg := config.GetConfig() - cutoff := time.Now().AddDate(0, 0, -cfg.Monitor.HistoryDays) - - for key, records := range s.records { - var newRecords []models.MonitorRecord - for _, r := range records { - if r.CheckedAt.After(cutoff) { - newRecords = append(newRecords, r) - } - } - s.records[key] = newRecords - } -} - -// GetWebsites 获取所有网站 -func (s *Storage) GetWebsites() []models.Website { - s.mu.RLock() - defer s.mu.RUnlock() - - result := make([]models.Website, len(s.websites)) - copy(result, s.websites) - return result -} - -// GetWebsite 获取单个网站 -func (s *Storage) GetWebsite(id string) *models.Website { - s.mu.RLock() - defer s.mu.RUnlock() - - for _, w := range s.websites { - if w.ID == id { - website := w - return &website - } - } - return nil -} - -// AddWebsite 添加网站 -func (s *Storage) AddWebsite(website models.Website) error { - s.mu.Lock() - defer s.mu.Unlock() - - s.websites = append(s.websites, website) - return s.saveWebsites() -} - -// UpdateWebsite 更新网站 -func (s *Storage) UpdateWebsite(website models.Website) error { - s.mu.Lock() - defer s.mu.Unlock() - - for i, w := range s.websites { - if w.ID == website.ID { - s.websites[i] = website - return s.saveWebsites() - } - } - return nil -} - -// DeleteWebsite 删除网站 -func (s *Storage) DeleteWebsite(id string) error { - s.mu.Lock() - defer s.mu.Unlock() - - for i, w := range s.websites { - if w.ID == id { - s.websites = append(s.websites[:i], s.websites[i+1:]...) - // 删除相关记录 - for key := range s.records { - if len(key) > len(id) && key[:len(id)] == id { - delete(s.records, key) - } - } - s.saveRecords() - return s.saveWebsites() - } - } - return nil -} - -// AddRecord 添加监控记录 -func (s *Storage) AddRecord(record models.MonitorRecord) error { - s.mu.Lock() - defer s.mu.Unlock() - - key := record.WebsiteID + "_" + record.URLID - s.records[key] = append(s.records[key], record) - - // 每100条记录保存一次 - if len(s.records[key])%100 == 0 { - return s.saveRecords() - } - return nil -} - -// GetRecords 获取监控记录 -func (s *Storage) GetRecords(websiteID, urlID string, since time.Time) []models.MonitorRecord { - s.mu.RLock() - defer s.mu.RUnlock() - - key := websiteID + "_" + urlID - records := s.records[key] - - var result []models.MonitorRecord - for _, r := range records { - if r.CheckedAt.After(since) { - result = append(result, r) - } - } - return result -} - -// GetLatestRecord 获取最新记录 -func (s *Storage) GetLatestRecord(websiteID, urlID string) *models.MonitorRecord { - s.mu.RLock() - defer s.mu.RUnlock() - - key := websiteID + "_" + urlID - records := s.records[key] - - if len(records) == 0 { - return nil - } - - latest := records[len(records)-1] - return &latest -} - -// GetGroups 获取所有分组 -func (s *Storage) GetGroups() []models.Group { - s.mu.RLock() - defer s.mu.RUnlock() - - result := make([]models.Group, len(s.groups)) - copy(result, s.groups) - return result -} - -// AddGroup 添加分组 -func (s *Storage) AddGroup(group models.Group) error { - s.mu.Lock() - defer s.mu.Unlock() - - s.groups = append(s.groups, group) - return s.saveGroups() -} - -// SaveAll 保存所有数据 -func (s *Storage) SaveAll() error { - s.mu.Lock() - defer s.mu.Unlock() - - if err := s.saveWebsites(); err != nil { - return err - } - if err := s.saveRecords(); err != nil { - return err - } - return s.saveGroups() -} +package storage + +import ( + "encoding/json" + "os" + "path/filepath" + "sync" + "time" + + "mengyaping-backend/config" + "mengyaping-backend/models" +) + +// Storage 数据存储 +type Storage struct { + dataPath string + mu sync.RWMutex + websites []models.Website + records map[string][]models.MonitorRecord // key: websiteID_urlID + groups []models.Group +} + +var ( + store *Storage + once sync.Once +) + +// GetStorage 获取存储单例 +func GetStorage() *Storage { + once.Do(func() { + cfg := config.GetConfig() + store = &Storage{ + dataPath: cfg.DataPath, + websites: []models.Website{}, + records: make(map[string][]models.MonitorRecord), + groups: models.DefaultGroups, + } + store.ensureDataDir() + store.load() + }) + return store +} + +// ensureDataDir 确保数据目录存在 +func (s *Storage) ensureDataDir() { + os.MkdirAll(s.dataPath, 0755) +} + +// load 加载数据 +func (s *Storage) load() { + s.loadWebsites() + s.loadRecords() + s.loadGroups() +} + +// loadWebsites 加载网站数据 +func (s *Storage) loadWebsites() { + filePath := filepath.Join(s.dataPath, "websites.json") + data, err := os.ReadFile(filePath) + if err != nil { + return + } + json.Unmarshal(data, &s.websites) + s.migrateWebsiteGroups() +} + +// migrateWebsiteGroups 将旧的单分组字段迁移到多分组数组 +func (s *Storage) migrateWebsiteGroups() { + migrated := false + for i := range s.websites { + w := &s.websites[i] + if len(w.Groups) == 0 && w.Group != "" { + w.Groups = []string{w.Group} + w.Group = "" + migrated = true + } + } + if migrated { + s.saveWebsites() + } +} + +// loadRecords 加载监控记录 +func (s *Storage) loadRecords() { + filePath := filepath.Join(s.dataPath, "records.json") + data, err := os.ReadFile(filePath) + if err != nil { + return + } + json.Unmarshal(data, &s.records) + + // 清理过期记录 + s.cleanOldRecords() +} + +// loadGroups 加载分组 +func (s *Storage) loadGroups() { + filePath := filepath.Join(s.dataPath, "groups.json") + data, err := os.ReadFile(filePath) + if err != nil { + // 使用默认分组 + s.groups = models.DefaultGroups + s.saveGroups() + return + } + json.Unmarshal(data, &s.groups) +} + +// saveWebsites 保存网站数据 +func (s *Storage) saveWebsites() error { + filePath := filepath.Join(s.dataPath, "websites.json") + data, err := json.MarshalIndent(s.websites, "", " ") + if err != nil { + return err + } + return os.WriteFile(filePath, data, 0644) +} + +// saveRecords 保存监控记录 +func (s *Storage) saveRecords() error { + filePath := filepath.Join(s.dataPath, "records.json") + data, err := json.MarshalIndent(s.records, "", " ") + if err != nil { + return err + } + return os.WriteFile(filePath, data, 0644) +} + +// saveGroups 保存分组 +func (s *Storage) saveGroups() error { + filePath := filepath.Join(s.dataPath, "groups.json") + data, err := json.MarshalIndent(s.groups, "", " ") + if err != nil { + return err + } + return os.WriteFile(filePath, data, 0644) +} + +// cleanOldRecords 清理过期记录 +func (s *Storage) cleanOldRecords() { + cfg := config.GetConfig() + cutoff := time.Now().AddDate(0, 0, -cfg.Monitor.HistoryDays) + + for key, records := range s.records { + var newRecords []models.MonitorRecord + for _, r := range records { + if r.CheckedAt.After(cutoff) { + newRecords = append(newRecords, r) + } + } + s.records[key] = newRecords + } +} + +// GetWebsites 获取所有网站 +func (s *Storage) GetWebsites() []models.Website { + s.mu.RLock() + defer s.mu.RUnlock() + + result := make([]models.Website, len(s.websites)) + copy(result, s.websites) + return result +} + +// GetWebsite 获取单个网站 +func (s *Storage) GetWebsite(id string) *models.Website { + s.mu.RLock() + defer s.mu.RUnlock() + + for _, w := range s.websites { + if w.ID == id { + website := w + return &website + } + } + return nil +} + +// AddWebsite 添加网站 +func (s *Storage) AddWebsite(website models.Website) error { + s.mu.Lock() + defer s.mu.Unlock() + + s.websites = append(s.websites, website) + return s.saveWebsites() +} + +// UpdateWebsite 更新网站 +func (s *Storage) UpdateWebsite(website models.Website) error { + s.mu.Lock() + defer s.mu.Unlock() + + for i, w := range s.websites { + if w.ID == website.ID { + s.websites[i] = website + return s.saveWebsites() + } + } + return nil +} + +// DeleteWebsite 删除网站 +func (s *Storage) DeleteWebsite(id string) error { + s.mu.Lock() + defer s.mu.Unlock() + + for i, w := range s.websites { + if w.ID == id { + s.websites = append(s.websites[:i], s.websites[i+1:]...) + // 删除相关记录 + for key := range s.records { + if len(key) > len(id) && key[:len(id)] == id { + delete(s.records, key) + } + } + s.saveRecords() + return s.saveWebsites() + } + } + return nil +} + +// AddRecord 添加监控记录 +func (s *Storage) AddRecord(record models.MonitorRecord) error { + s.mu.Lock() + defer s.mu.Unlock() + + key := record.WebsiteID + "_" + record.URLID + s.records[key] = append(s.records[key], record) + + // 每100条记录保存一次 + if len(s.records[key])%100 == 0 { + return s.saveRecords() + } + return nil +} + +// GetRecords 获取监控记录 +func (s *Storage) GetRecords(websiteID, urlID string, since time.Time) []models.MonitorRecord { + s.mu.RLock() + defer s.mu.RUnlock() + + key := websiteID + "_" + urlID + records := s.records[key] + + var result []models.MonitorRecord + for _, r := range records { + if r.CheckedAt.After(since) { + result = append(result, r) + } + } + return result +} + +// GetLatestRecord 获取最新记录 +func (s *Storage) GetLatestRecord(websiteID, urlID string) *models.MonitorRecord { + s.mu.RLock() + defer s.mu.RUnlock() + + key := websiteID + "_" + urlID + records := s.records[key] + + if len(records) == 0 { + return nil + } + + latest := records[len(records)-1] + return &latest +} + +// GetGroups 获取所有分组 +func (s *Storage) GetGroups() []models.Group { + s.mu.RLock() + defer s.mu.RUnlock() + + result := make([]models.Group, len(s.groups)) + copy(result, s.groups) + return result +} + +// AddGroup 添加分组 +func (s *Storage) AddGroup(group models.Group) error { + s.mu.Lock() + defer s.mu.Unlock() + + s.groups = append(s.groups, group) + return s.saveGroups() +} + +// SaveAll 保存所有数据 +func (s *Storage) SaveAll() error { + s.mu.Lock() + defer s.mu.Unlock() + + if err := s.saveWebsites(); err != nil { + return err + } + if err := s.saveRecords(); err != nil { + return err + } + return s.saveGroups() +} diff --git a/mengyaping-backend/utils/dns.go b/mengyaping-backend/utils/dns.go new file mode 100644 index 0000000..18b4e04 --- /dev/null +++ b/mengyaping-backend/utils/dns.go @@ -0,0 +1,60 @@ +package utils + +import ( + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/url" + "time" +) + +const dnsAPIBase = "https://cf-dns.smyhub.com/api/dns?domain=" + +type dnsResponse struct { + Status string `json:"status"` + IPv4 []string `json:"ipv4"` + IPv6 []string `json:"ipv6"` +} + +// ResolveDomainIPs 通过 DNS API 解析域名的 IPv4 + IPv6 地址 +func ResolveDomainIPs(rawURL string) ([]string, error) { + parsed, err := url.Parse(rawURL) + if err != nil { + return nil, err + } + + hostname := parsed.Hostname() + if hostname == "" { + return nil, fmt.Errorf("no hostname in URL") + } + + if net.ParseIP(hostname) != nil { + return []string{hostname}, nil + } + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Get(dnsAPIBase + hostname) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(io.LimitReader(resp.Body, 1024*10)) + if err != nil { + return nil, err + } + + var dnsResp dnsResponse + if err := json.Unmarshal(body, &dnsResp); err != nil { + return nil, err + } + + if dnsResp.Status != "success" { + return nil, fmt.Errorf("DNS lookup failed for %s", hostname) + } + + ips := append(dnsResp.IPv4, dnsResp.IPv6...) + return ips, nil +} diff --git a/mengyaping-backend/utils/http.go b/mengyaping-backend/utils/http.go index a398703..aef1015 100644 --- a/mengyaping-backend/utils/http.go +++ b/mengyaping-backend/utils/http.go @@ -1,131 +1,173 @@ -package utils - -import ( - "crypto/tls" - "fmt" - "io" - "net/http" - "net/url" - "regexp" - "strings" - "time" -) - -// HTTPClient HTTP客户端工具 -type HTTPClient struct { - client *http.Client -} - -// NewHTTPClient 创建HTTP客户端 -func NewHTTPClient(timeout time.Duration) *HTTPClient { - return &HTTPClient{ - client: &http.Client{ - Timeout: timeout, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - CheckRedirect: func(req *http.Request, via []*http.Request) error { - if len(via) >= 10 { - return fmt.Errorf("too many redirects") - } - return nil - }, - }, - } -} - -// CheckResult 检查结果 -type CheckResult struct { - StatusCode int - Latency time.Duration - Title string - Favicon string - Error error -} - -// CheckWebsite 检查网站 -func (c *HTTPClient) CheckWebsite(targetURL string) CheckResult { - result := CheckResult{} - - start := time.Now() - - req, err := http.NewRequest("GET", targetURL, nil) - if err != nil { - result.Error = err - return result - } - - req.Header.Set("User-Agent", "MengYaPing/1.0 (Website Monitor)") - req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") - - resp, err := c.client.Do(req) - if err != nil { - result.Error = err - result.Latency = time.Since(start) - return result - } - defer resp.Body.Close() - - result.Latency = time.Since(start) - result.StatusCode = resp.StatusCode - - // 读取响应体获取标题 - body, err := io.ReadAll(io.LimitReader(resp.Body, 1024*100)) // 限制100KB - if err == nil { - result.Title = extractTitle(string(body)) - result.Favicon = extractFavicon(string(body), targetURL) - } - - return result -} - -// extractTitle 提取网页标题 -func extractTitle(html string) string { - re := regexp.MustCompile(`(?i)]*>([^<]+)`) - matches := re.FindStringSubmatch(html) - if len(matches) > 1 { - return strings.TrimSpace(matches[1]) - } - return "" -} - -// extractFavicon 提取Favicon -func extractFavicon(html string, baseURL string) string { - parsedURL, err := url.Parse(baseURL) - if err != nil { - return "" - } - - // 尝试从HTML中提取favicon链接 - patterns := []string{ - `(?i)]*rel=["'](?:shortcut )?icon["'][^>]*href=["']([^"']+)["']`, - `(?i)]*href=["']([^"']+)["'][^>]*rel=["'](?:shortcut )?icon["']`, - `(?i)]*rel=["']apple-touch-icon["'][^>]*href=["']([^"']+)["']`, - } - - for _, pattern := range patterns { - re := regexp.MustCompile(pattern) - matches := re.FindStringSubmatch(html) - if len(matches) > 1 { - faviconURL := matches[1] - return resolveURL(parsedURL, faviconURL) - } - } - - // 默认返回 /favicon.ico - return fmt.Sprintf("%s://%s/favicon.ico", parsedURL.Scheme, parsedURL.Host) -} - -// resolveURL 解析相对URL -func resolveURL(base *url.URL, ref string) string { - refURL, err := url.Parse(ref) - if err != nil { - return ref - } - return base.ResolveReference(refURL).String() -} - -// IsSuccessStatus 判断是否为成功状态码 -func IsSuccessStatus(statusCode int) bool { - return statusCode >= 200 && statusCode < 400 -} +package utils + +import ( + "crypto/tls" + "fmt" + "io" + "net" + "net/http" + "net/url" + "regexp" + "strings" + "time" +) + +// HTTPClient HTTP客户端工具 +type HTTPClient struct { + client *http.Client +} + +// NewHTTPClient 创建HTTP客户端 +func NewHTTPClient(timeout time.Duration) *HTTPClient { + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + MaxIdleConns: 100, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ResponseHeaderTimeout: timeout, + DialContext: (&net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + } + + return &HTTPClient{ + client: &http.Client{ + Timeout: timeout, + Transport: transport, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + if len(via) >= 10 { + return fmt.Errorf("too many redirects") + } + return nil + }, + }, + } +} + +// CheckResult 检查结果 +type CheckResult struct { + StatusCode int + Latency time.Duration + Title string + Favicon string + Error error +} + +// CheckWebsiteStatus 轻量级状态检测(不读取页面内容) +func (c *HTTPClient) CheckWebsiteStatus(targetURL string) CheckResult { + result := CheckResult{} + + start := time.Now() + + req, err := http.NewRequest("GET", targetURL, nil) + if err != nil { + result.Error = err + return result + } + + req.Header.Set("User-Agent", "MengYaPing/1.0 (Website Monitor)") + req.Header.Set("Accept", "*/*") + req.Header.Set("Connection", "close") + + resp, err := c.client.Do(req) + if err != nil { + result.Error = err + result.Latency = time.Since(start) + return result + } + defer resp.Body.Close() + + result.Latency = time.Since(start) + result.StatusCode = resp.StatusCode + + // 丢弃少量 body 以便连接正确释放 + io.Copy(io.Discard, io.LimitReader(resp.Body, 4096)) + + return result +} + +// CheckWebsite 完整检查(读取页面提取标题等元数据) +func (c *HTTPClient) CheckWebsite(targetURL string) CheckResult { + result := CheckResult{} + + start := time.Now() + + req, err := http.NewRequest("GET", targetURL, nil) + if err != nil { + result.Error = err + return result + } + + req.Header.Set("User-Agent", "MengYaPing/1.0 (Website Monitor)") + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + + resp, err := c.client.Do(req) + if err != nil { + result.Error = err + result.Latency = time.Since(start) + return result + } + defer resp.Body.Close() + + result.Latency = time.Since(start) + result.StatusCode = resp.StatusCode + + body, err := io.ReadAll(io.LimitReader(resp.Body, 1024*100)) + if err == nil { + result.Title = extractTitle(string(body)) + result.Favicon = extractFavicon(string(body), targetURL) + } + + return result +} + +// extractTitle 提取网页标题 +func extractTitle(html string) string { + re := regexp.MustCompile(`(?i)]*>([^<]+)`) + matches := re.FindStringSubmatch(html) + if len(matches) > 1 { + return strings.TrimSpace(matches[1]) + } + return "" +} + +// extractFavicon 提取Favicon +func extractFavicon(html string, baseURL string) string { + parsedURL, err := url.Parse(baseURL) + if err != nil { + return "" + } + + patterns := []string{ + `(?i)]*rel=["'](?:shortcut )?icon["'][^>]*href=["']([^"']+)["']`, + `(?i)]*href=["']([^"']+)["'][^>]*rel=["'](?:shortcut )?icon["']`, + `(?i)]*rel=["']apple-touch-icon["'][^>]*href=["']([^"']+)["']`, + } + + for _, pattern := range patterns { + re := regexp.MustCompile(pattern) + matches := re.FindStringSubmatch(html) + if len(matches) > 1 { + faviconURL := matches[1] + return resolveURL(parsedURL, faviconURL) + } + } + + return fmt.Sprintf("%s://%s/favicon.ico", parsedURL.Scheme, parsedURL.Host) +} + +// resolveURL 解析相对URL +func resolveURL(base *url.URL, ref string) string { + refURL, err := url.Parse(ref) + if err != nil { + return ref + } + return base.ResolveReference(refURL).String() +} + +// IsSuccessStatus 判断是否为成功状态码 +func IsSuccessStatus(statusCode int) bool { + return statusCode >= 200 && statusCode < 400 +} diff --git a/mengyaping-backend/utils/id.go b/mengyaping-backend/utils/id.go index 09fa6b7..6a4c379 100644 --- a/mengyaping-backend/utils/id.go +++ b/mengyaping-backend/utils/id.go @@ -1,31 +1,31 @@ -package utils - -import ( - "crypto/rand" - "encoding/hex" - "time" -) - -// GenerateID 生成唯一ID -func GenerateID() string { - timestamp := time.Now().UnixNano() - randomBytes := make([]byte, 4) - rand.Read(randomBytes) - return hex.EncodeToString([]byte{ - byte(timestamp >> 56), - byte(timestamp >> 48), - byte(timestamp >> 40), - byte(timestamp >> 32), - byte(timestamp >> 24), - byte(timestamp >> 16), - byte(timestamp >> 8), - byte(timestamp), - }) + hex.EncodeToString(randomBytes) -} - -// GenerateShortID 生成短ID -func GenerateShortID() string { - randomBytes := make([]byte, 6) - rand.Read(randomBytes) - return hex.EncodeToString(randomBytes) -} +package utils + +import ( + "crypto/rand" + "encoding/hex" + "time" +) + +// GenerateID 生成唯一ID +func GenerateID() string { + timestamp := time.Now().UnixNano() + randomBytes := make([]byte, 4) + rand.Read(randomBytes) + return hex.EncodeToString([]byte{ + byte(timestamp >> 56), + byte(timestamp >> 48), + byte(timestamp >> 40), + byte(timestamp >> 32), + byte(timestamp >> 24), + byte(timestamp >> 16), + byte(timestamp >> 8), + byte(timestamp), + }) + hex.EncodeToString(randomBytes) +} + +// GenerateShortID 生成短ID +func GenerateShortID() string { + randomBytes := make([]byte, 6) + rand.Read(randomBytes) + return hex.EncodeToString(randomBytes) +} diff --git a/mengyaping-frontend/index.html b/mengyaping-frontend/index.html index 862c185..60260fb 100644 --- a/mengyaping-frontend/index.html +++ b/mengyaping-frontend/index.html @@ -5,8 +5,12 @@ + + + 萌芽Ping - 网站监控面板 - + + diff --git a/mengyaping-frontend/package.json b/mengyaping-frontend/package.json index 9f9aea2..a10477c 100644 --- a/mengyaping-frontend/package.json +++ b/mengyaping-frontend/package.json @@ -5,9 +5,11 @@ "type": "module", "scripts": { "dev": "vite", + "dev:local": "vite --host 0.0.0.0 --port 5173 --strictPort", "build": "vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "preview:local": "vite preview --host 0.0.0.0 --port 4173 --strictPort" }, "dependencies": { "@tailwindcss/vite": "^4.1.18", diff --git a/mengyaping-frontend/public/icons/icon-192.png b/mengyaping-frontend/public/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d28aa1b81a9fad67daed1cc809a3cdf67f1785 GIT binary patch literal 70651 zcmWh!c{~*VAD-PEc30T7I$W_>A&Nw9yCRkjM}5mtju1jb$-TQ&NZ8zRR-}U{SIV)D zNQ5YNteoZ8buZTL&+m_!dCk1$HLsb^d_M2@^E~h89cy{PRDe&64*&oGGorEe-jVkI z4Z*v&w;uI|?;Rik)~2U{cU=;H_f9}}!}Ep!@IG-L+nHzY9O*~24FCYa=KpVyHs4pS z0KhFYGd8>w`@qqa`AtDSxP>zgG_#(Gx7>XP02uGy82 z+RRYXg&3FmZg%2|{K^W6STq}MAW35MyZSFB{#n-UZox2)#mLWkKW?3sj6X7S?NQ6@ zWn-6BaoP807Jm#r?;?6TTPRmy_s4fdA47a`iE}=CI(DwtQtuQ0h*D3){f!rv%2jIn z6HEEN{E>QbZJ&Lj%e>K#L0+H9_vg#LmmMoB(W?4yB*gVmKAT%yz zjuU*46x;ml(jO0nR6Q~CBZ<%S1$801(+leFw0U%e&0D*5I*@WMvqnf8wLr+uA?M(a z76!$LQbobS|vuadm$?-0&U>M!u`T!=;Cf$yf<)aj^DBFzO#@G|G|dN zq_PyRzFS$Xi`y8K_g{PNmos0cbLuJSRR{idocktyFSnw7YF1`}Ve@?#J) zsOBzZu3QD(cw#L4R*pBWb%18jy8EN`W8LNJ%XcGRkpo{~#b{NpzFZ&idt7yR_Fhw< znM}PsCbq6@H|d-N_0kGxkBarZ`>7SE8(z#QUpAG`=4)pVSMc<`; zqAPp+_LIE!(HEr*iu7$e?Xr&X<_Pc%JKKoN%655YwfW$A<8&vhG?(OmKGw@)!-UXg zNzp$HcuqeK@Y`x=bHuImr!G%DE_xm-dgyw)DC^#A<5=^RlV>0P>2t%Ly2k5r(p6z2 zfxqnrUkrrmZiePX^0Tmn{RdBoCy+r^*+%H#=~yc8{!1B(pZx>n43O4jN4?|_W#5Kw zE;Iui;(`k~EXPi-N}53aev^7ytLhhpr0I=90L->c3IGS3lIU2#6e>YE{AJ^|#44!u zz{>(zfT}`9SO8*3>b?#ba%Fk=G&v1afXpEy>`nkpY2YhA6X+%P#~>9%8e)D6h7z@g6 z3!9fc%T$T)PSVLccT}|gpVJyQl(f8Nf3NkTFG<3z_Q6MU3zU=mSkkjs)zxlTK4u@` zz9HHPVT?!y-`fInpP8KoP%;2E$kO~=6Kpd;tkB%V9CRegg{HCF4Q;` zhyl!jVH0m(s3#)A7$D~fk#omu8-%ceP~0Sd=dS^Pdtl&TrRi;cV-Z8ZgaWW8V}RQj z`g0hU2n}NR3XzXJCyT{ezP^zw4#H$qB48B=f)m%oX^0BwD_5|UF9BjTGXaR)u)1nULNk4~YSsS}ey zIuYqnzql+?)DulcoZR?W9wuaG2y?bUz{vteSE;TBB1B{W*+x~nlT^TpY_0G#14*?r zMwq(8<^DDX_{( z0I7z{EI~lDH+oEJMP3vJxsC;th2RjUSeCP#k&F@41Zs{&6Wpk0D8|_!R#YL%{{Y*} z0wCt!>80i2=*iQ7B9cJwBS!K`oMaXyn}E2Mk5`TrOK#q@h87?~LP#2>Q12q^#gEl- zfEdr945r}p;L=@VCv~O}nZ7B673WzHLDF5VbwrTI<;fH&v+}B;$#kJcMsp+y+vK2r zPd8&XaZ^*3xw8JFrDENahocMxivgUM6uc3z@M5~gnVd(KXZ_uZcJbAfg^WQUH?*O( zabDjiQD<&+pvic>b5K>W&Qt_hD!%JVTnjep5;Su|bJxP&Q;6nC@)~$!1@+t?bA3FN zqyZf_%O4CBk?WCMXIFq^sYD7t_OwidrCB~0BSkq3L)U>Tfta>#8dRIGqDMA|qFFs$ zP5w^e}(Q_Y1ds5JRQoS;%TqKHuM&)uVxZ#}$nRCz!Sy-N0$@do|2 zrN;1PP*pw;an*R>Z~{Plc+J}T2YCySCAVYqV2{B%xdsznc?uCoGPZXR2WzV7@9Ba4 zlkSTv0kW|GhF`{+6Bjkky-TWlGTR^)-wO@fTWG7UdCf0GiKvH*wkw($}-1j`V-P~-Hby^$57 z?FbR-_!h-7D8I?w;d#J|_+$Xo7s$o)F-?)2!yfmZ-Q5cnvWNUeaE58XV`v)zY65}g z;Sl7!75LaVg1{`_HY!EYy}U3)N}@vkOE7;x9-=@hJ6~E#FZ}^#fKGx`0D>WsGeLWq zQG_fD5~h)i7lpoRoc9`uKShPRP9;))EANYtBj}ukngH=&$Z>>6hIw{JLVTHpbaM=w z+?J%8=COz}){3c}bP9nt;wI@W)b=>lSf*GAn}E~$5wmzCL;mzW8EAn8lox8l z^EH_~LOl(TWMacbgk64wk*#QFv8WJeDlUIZf=0`e1N5~ZJD&OX@;7!otH3I}RGE5$ zJ1sG7XWK4`j4V&e7sdq*Hm-1WN#V_?G~I?<54ut*F*+j$6l5rhmhiddj6YEn`#P~_vM1J zk+)GjJQ$DtOw8A_hn~s<@hw=Ez8S)X@dM_xIU@PBu|*Sh>(**Ej5IS$c-!VYSdF(- z$Yhs9&636&o-yBc#}c06#=#&*n8)8O}> z0J4U5jf@!T>us$?#!apr*W&5s=1VKWDsY6x8?n9ud^VH3Wsg} z&4)m9^Xs!g)hk10g}b*=EG#nnnxMD-O#Cb~EjUN^Ymq~8$wBF-dh<@(`3a8(`=VFFc&CXfr- z^9pW8K=B^Rq!4Jvrh5m10DU!UK?R2rR7;?*Ih!}0U{fouVpCs(TSuwY9SGH?NV_qJ z4aIzQWa(qW5yadbzE&5GUZYg)!`a_e4RF^Kfeymc>;%rHwBa~2uHnV3JPQtqFwcVo zk>a4Eo4vDlgS;(byj{O$M~f;V!b~6-X(A95k#VF6AQ{V1rvY=Q{DVPhlda@R9^hmx zNdl?bkuL_B+dT$t!QdLjfc|r#ls$L^3dx?{k8_fXF^)M?Cvjc$r!d)-srEIPtF;3W zgf0N2NIL$Jk9q!!!LG}Z*6n@3u|kWglge4}An1PW7yL4FQ6N_F=(3~KpGp+}L$!U7`=x65|9osN!E({SOxn$n>NSLnDn60M$W{1|A) z(wJ_b6G*QYYS!ZdX8G;!-||bh3z*I=x*2 zY7(#lx1u2kE@1wwrs#eO|096?<7->cF=W}n9jxI`ny;Ky5@7u`p67%AoFITF0CJQ~ zw)c?YL!a-kFzpnOylG))nh`=mOQ%91)(h}G$O+=7y4_~X2vD5x0o8vIX#`|MGJa;+ z7AiOQck8?Lu$`b2vg{0G2GZX<-#3}O2aQ)b^-$MB%d>Z0Goolk!OYOz#BiP>_@-OF zTNb3@b1OtUV9c@Om5|=S+Hv_M!#^jqt0*C{Yjl`kJ(scjpt+Q!h#|?Qq*OzkB6G*a zOvz84kWU#yfvIM@`Ay7MqEqbq!!C+vd_qK(yJ;YL;lRNCbdWrd*n$WYBnK2& zUhm}9QVdt#iob8R7bw#RE@RYv`SG%1Et5*>qBlqc*1vuhhuzk z6#jl$Z!o6KhVOT5S}^%+q%#uNaG6(Qx724r1VK-t7norW^QYh6ESkN-G=eHZt_tQK zzWyLcfa2?bcR|jP&@>|bY(AEJlLXBR$)j(APU~$tU)27?rUUkZv`2~EJhd}$NH3O< zzwKgnyxP+c%C;W?pTp(tQN!drqVEMJD?crkt_N=2T?*r6RnT`bZTAz`3i_HI@2GO{ zT+RK#i&|%Mz?yV}D64-Yv8vmhNAW}bGGy4N87PFQd%b5e3&Ij)M+p(7W_Qa?_R_n{ z6Uq;)SQZRW+v0%$91oEd(;dh`SS@2x>_}5F?+KZ~!TgLFn9}=QAqP_^nO~@?j9*BE zM-Ot2PmTbLOSev-4Gxu=qt_eEu=6l1G6U#a6=tt-dN@>kgz>8*O<2l04D=_b$N~Xm z9L8A#-6%w^YxE4_RwU67R1orN$A@JEu-{c04}Qx(8b#kd59QIv+X=?3j^*h${z2}A zF!u6J9=JPwO!hQ7UzPFuq-#FUhN%Q05{{#Uiip4of8DCE`RVaOeu`)}7iUN|uiryJC9&t+%D zwj81ry~e>8+hy=JvJ_`+e8G;lFLn?HiFb+QEJeudn6$h!~qmW zqobe^v{-15QL#MFg$?aw{n`cfLrLd1g;`Z4a+W*>dItJ>l?~$D=Kg#4GdJ4;O*kNO z4|%-%z91YV_&C!C)dR%n)9QFAr6XOF0lGmK61PIFuYX~RPO>^ZE)JSzT`*l234nN& z7>gyE=K1+?NQ#mw{T>_VZ_3k43OBFxoiO~#O~GFlvWEIyQg{I%s94tBtLW%60mcfZ6zd(`-=!{; zFbh?Ed>iEU2xG(#A->O8^bQg)6HVyR68kv=o%2H+dyoVc1Mk6q=6UDtYe0KyK^X}9 z)I2vuA8eRsTa%N4b7A4og=8n$D8Yvv{Lx2{wVTmyIyDlklXdl=L1O5tlN=Rk0?pVN ziv;eKEPb9=X}D00^pDJpmdZ&s`)v;|x@lO%35e zCaU4cF2E(Q+(a5JLPbp&zVzzeqO%bPSQ2X{bm6te{*d5fYFhrrC6ivj6KC2wFC5-# z>sywNXEY9Oxq&hF-IpD>h7#wihsIewr*EL|2@YmgH7c9SZKK)dfJoX-BE{9eySnr6!A z9;3@tjL~;<-gEL6w=|%GUoEl#Bj_L%Zu4m5mTGoCx8z^4xo&AG?(5} z+qWFQH#R<`XOsb7i5y(SFiZqlAngAvc zB31)K*9h{okpKZ!Dp*#$uza$C2%tiefc8?cE?s88bN=@0E|l8acjk)uAu`3#zYD4x zKr#@6*aNc34&)qOwfnLi1TyNo%vacc_PK|du#ee*5Lv$|1%FEQR$WN=xabWWy|`?V z`R}vSEeD|U#{JtjMBJzrWLwCGl_I+v;&cl~Y8Pj6r6sLxy6eGs%E%$o+1$bP$7}qQ z{hnz$W@H&TLmh!di;EaCAi*g%%0CQIbIpGOst0lR%Qv&e90P-FxB6zFHprg{L)u>Q zz?Fr|hpVIf@~@CjN{N^OBUJ$s$eTNBOu=jjBU?jqbtYl?6L!#)jAuP1xrh(EB<>{w ziV5_duL#%rHOmmrAaP6adW&hUA!(8CNkL)=^3;_V01i!5MGIo+2TXww?f@k0qk*`V@mgww?b=t+V4&M)871c@pEo-@C9=8tc5JJ;0X+1d*Ezp<*`2>r)KM#^wlhXKg|Lu@NaQwGPDJVSJHGpfd|ZzNjYx}CF@R=N%YPMM&n((v zrf+u$@p|Auj1zvp%sCm?B7rvbdpgDtG?!FMXG;9@5n8! zX(H2+&$P(&1@jXQh*n&C%c<6L!m>Z{_X1%WM_SDhGmw{D0am@+{yojkY4evPkb4kl zNW|D?PQ4{a2s$HGuiSSMz0}B1egpe6s)|TwJ>4jpiDp9}m$m`+6GWXs(lw3Cp*{a% zEN9nW0Q$NOKfT=$;vCx4p%12B9NsXqEjg=}N1fCXwtv()ni4*Y)f7WO=*wb5AzVf?# z;ita^mo&1e-$J}Kb5pm|R(pptgQZdU!xV?M%w0UpYxH`}b*GdCcsp zj2ay#h(T(al!cU%Jk^!%mKSM=C zWDV4~ycznwAY!;c&N_iwcw!~9^NDshbJ1dH6}1`B+qj!d0)iauZa_zUzoFayVxg)S z{@#@RfQntNtk9($ule|Ci^#lV^Bk?xu^a;YV-EjyFFJhg_9)F_Pjd^o99r?m2ouD7 z@#~q8cQ}chYE)URSq~n=p$fYZOj)SMbo8h3wR$0dp4hbN6@7@}dyLZERcah2PA(T7 z7jd-+Ygk)bN_*P+-TG=JZ@#1Aul0Gz#x-Eg@Yjxvyx%c?@R@|Uyl0UgN?q z;7-A_X<)3u=G64@EU^$!zi~%~ zgKnf=m@9MeyH8I}WuKe=Rn*-)v zZ8wl8M&p(d>!@wL9)JGfCa;f@X!{JMsGFGVDf8RfJ?o}~+#KdlTr-b|p@Yh`--iks z@|0SCgr@(gKhQfGap8@4!sM#|T`iB$5@72nT3{u5Tk2S0;xx~BzO@Oz7Zri2hPMvT z17^)G+CNznU`k$9Oc2YU-zTYU?+NN-Lq@4a(MR+ z@YG43br?bbS2;MK(W0ks-o9f3b81Fvpf7uWZ_+F1=(jvun!B8ZO|(Jt4tyr!C4+290fPc&8O{4czCGyRyM1x&Pwitph(2CfRp z2s!fj-AXwW$bLJjx_T;8i)$#lK32D~cz}!Ds(f2ea3l3+l{a<5`CXkc4a1}wMjW=> zThDSr$gqAcG9rYu0il9aeY49Cy3E`MAd1KB<-@~AXa8I|g-)RVA9p6NEo}9U#UpyW zgs`9a?u&C#yB3*0H7y?^{P|)vUci(Ll%p_n6(M_NB5g=9ux0hk08*v)s^C2I_U%za zW6GyAYvYI3?WNi(;|^=WxG7ZoCETA$_c8gn#PFDTger#CDc@+@E{uax7)0i4I(PH& z0BsAww%aSc3qqR5Wn>^?RR6$A=ZeXhGDtU}1 zb)GigcIf)}8-q_EAEdM+-*Q81OPFJcq$QNv+>ryyF7S!XbX+2H66D5{LJe36qq~jO zZfgrWZk&k$CeMe54dTFW-k$R2xYRwRf)@%Np@}24peZmqmh=Y)tdSyP8D>HMlC1|m zp8xl+dU8~BG`S;xkO^NBha3k|B>1cp7WJvxnF!i4{wgZw33=z`t|H}6td4P1K=02G zA^OV}XNyvKV4ia$LeDY{tWB7YteR{q+SpVwLDAV|we>zvN7u$QrzYI+R-fJfZcgVu z7*9@fm^qMww%wK5tG4kQ%CHAqyQWR3oPl?3w|U-=&w3HNEG|cVnBA>ZtzY^uu=5Mo zGsd8Iugs39`2W5A_GhPMnHWS*sI$*YFn{F6<-W}vJ5aw4-%kG8c!HB+sV;ao5Vr@| zO@!Dc%v2hsYm-DvqjXc^;M#vau%b$6f8(_gzlPN6;c;vvDTvQM%K3d<0Th^v`K zrveh(fI%9nWL(ql>e2Z?8Fwv!ZZtDT(;W5`DL|d8+cQVdKg>Po9S*;$Lx9SWk4HBx z6C_~9P(QT9TfzaBkS))f=$9%#d)@IE`)g2rVdT}>bXkg&;G%BRo&P*c$@?AB(~j2~ zNc%44$Z2+yE=k&QE>^(&W4&1odv4Bf+)eGFo0kpw|=F;Z8Sd|bmp z6N0;I_w>N_d|BzhHA$XG+H7DTene@ZOItMMTw7iMbI$^j8C&cpO>VFGD3P>aMi2E- zjP1N=@8wi=Hyq(-!I$*K;05&Kr2Y~%%+#tIhp&5JJ8WNTR+`6Gt z=s5bW_TL;0(pO2rfc|G)mzS4mvq4-N*LXWM!<+`IH(6J{R;PzMwtgoW-?HEK$s}(y z?$My~R3+-;Ta7yly!q6rBR~ilZ^neJwJQ*$c<2!Jr3HX|jxNsX`y{a~>gl0NZV6VI zx-3WwZH3^0h@#!TY!J59e3z`nDk+@DApm5`5y)tg0+wRQr^s@a;Q99kU(8Y!Q$KdYI$V4%1XE zxN%@AF)J^bV)#uC`cRu3%q>peByaAh-wLG%uL6QlrdmhPZ5!TB!^xVC>fn;kobmF` z^<2Wxz%t6p`E6vL#SKxuZd$eOq3vuigD3xk3Oxt#GmPmx<@OkVW55+jmyLwrji7bB zI*t&Fa-VC^6E|W@9hVKSgiR*NI}7@|sFe1xKP?q$ngAgY7=S)7ZBaYp7#mpmv_R9G zOTVf3`YLwuu+&+%dGoD~gz@(3D;#1(^&-l;XXQkFYGjdqpy7(_A$QQ>@g%$ z>*_?u-~xRRVA zY?KzE%amNq*=){_nj~wXyUg=SdVgJ%`Bf=#qY=!kt4(7+osDK&F)UtiW}8ohxH-Kt zduo5S@Y!o%3;BZBko``NuLrM$w_VMs4e)+#M0lPA|1MQof5PT;u3V<(KdY0bB*ELkj-(RAfP3}*?J6>2zVMTC-JNK^PJ!SaYj+83bX(DYJZt1r zpxJ+ufng?NaNu6P#oOC?^zPdx^kxj$Py(7lEAHm-LI%&CgK9BK>6@~9^&&^8TTLHk zOdaE#gvI!zJ#5|A`=kBIi#6skmiHRFm{TUJe4kr-O(*nb;rrJ%!^7sxI<=dsm^-k+ zl+#wCg4Jh@bdzs=i;~vw|MB|~V+j+}D&OVTE`;*;1yk`ekgjmaO`yJ3j_fH|CRy<5 zyVvNJP}`9IOPl;(jXtNgelJar(DZ+ehTgCfc{kyBG*v`WOl*6>y=ghK`X!H@E(F^o zE)H=5@G?=6+Vi`0LTqeoWY)-%mo*M-M~frhU~{z?riY)wlMul1>sePwGmL(D!RmtIc^dU{V9XI!K1 z(*@8+>g^}~Rzt|2ncKU}h0xpIZ@l)rsn<{NK}R^RsZA@Ki#< z1z@mj$_&mq$;aV`-044L1}x%bpeeL{vtMaI^^*nYnjPXbu|}J(Ltn21NOcP`6~l` zex0F#@Zp8|P9nK;xl@CqW5>)5^L1aUWS=eS(nl-cDJ-c zvT3z%UOqXDcksq*-(8}A$3en3GQVQNvh6;m@1=`S>`;l9ZS3Zm=lh@@zoLzDoQt=a zimD59No$6qJAmHAsky}x|AL?gop_y#2?t9bE;#*)ABmA=SfJBL*aL zn%$rO(C%g$-q9IO8WV9C0=x*PuKD)1P9+8hpETXi(=amdl{U~k5+FCL3h91O95yfI z3sOyIvLJKK&Fawcs^+Wzz=H{XG0oN@Z7hKGZXkU{{#`CfE@G+@SUsrtD$McQ&9Syq zwGolBdIOQb{GJVS&zcPUIgTs#qEA;(id_z|S-F2kdm#NSekTO)wYwm!TGueyi^+WN z`!u^`qIvE3Y0TCM?i%~nXhZ+@`OSS^yPjOM9<(04L)B8fe7EcJtyK9ceRJ` zVj1d3KI`|6IHEGAkUd2=WKWksjWNb9M%H$=3h^cEtDsPAeS#1aSt0z`YR~NxI-)$N zMw_Zy(q-OAfWP~GRk*Cvi#^Xif4vPZ9*|_XKwj;A5 zd3TO4W$ufdrn%%AJ`c4PvrCluCjnU&JbSlK+H*-yNj%~Bli4%_SqtCr3;o&GWWF!f z00Y`=ls55=3NiHFyF2R+k$Q@)+_M-k4`Ei)ZKkruIpUKPd;GPJQqn`guc-z9g?rDH z!y^J6O(T2Nb?#FcKTppK5AJ#8k`$|OgeoaoE;jQL^yewH(`VjyUpT_piW(b-eXkVl z@y7=p|7fwAE7k^eI=s9sv{*EHv3tt}85XGY;HGwpvES@gbRfrF=DoJ~89<#pb|hCE z(kb!kz@X%stxBnh;tRVmZySv|xPJWfxd9Bzp~DpWS;6Pdc~aHPvd@=QSFPhgwrr8!SfxXz3p%?^MxGb5@!$-9o8$@w!J3c-RnWm zW6xa)b*XdA+u_-A+2=4cD0#taidMDJW%OaXGxWn^Q&{cn?d%Q;TpyAz^<;YnW^Gk1 zWwsXa+H*K@*Qj?m!sb)x1;Tt@8#fR+^|kGgqr&d9xDQ=bGq)qNB4JKuvea)hW-(dnzmri#jmCx93C+eY6$R^q?dO*R6vtaFXOsTT~q^l-VA&%&q^zrtxogWU<#Qm)K#7-#@_Y->PRs;-$!_u&N>m% z2M+VptghS~40IR|x%d5g@p8aJ#4`F;^C9JOPtK#T0s=}ry#^lwT#xv76m$Ep1hm;FPQuL10vYb@N} z58g|psPbPEB9LvuKXYRx6JaR`yFdhw?vnd-VWnpkC9r8`dq&d#*jSW{GV(Y|9Yv#` zKpCUH(NB=^-xD-do{j1wZjFcJyH{P~&=X02a#d6Dr%tN}jQx%xd7wzVy|_eBb=QK~ zO{tr;19VIXCvDefXgNbK-`&Gr@r2pF$jvQDRnfMO1FkUVg|B-5;uX9WbN}_Cv$yl9 zq>~E4T4{Nxn*5=o-C^{*=U1=ST&f4KWA~X13gbrL>tK3cRa3^)ht>vDv5|fl(H^2B z-tQD=|Gw4tM%?e=vKxngWp*`k{mq9%5{S2c9n2K?&Dp4OmupBs{doCKo~}Y4yZraZ zhoU33>16^v@*js`hZ;1PUcM@bp0V%?I!0F|d6FV?9Ix&vnSvNrue`A1Tkgf35f2M! zs@hlHPlr`TqCuMbRWoLXw@$nDhIOKJAUV+Ds&Tq{I?XK&D76iPE- zemVsMT(EYNb75BZ18;zJk&8Oz`(lqrpVX}OG5#tKsV&-U{ZhiuR`xZ2)GU~Ax|#80I4!65 zz@K&Zf^vpd3~(AU*FI=l3s+__bv0zk$L}`XC1r1J>et&svCbye+2>LkKXMZ7VG0+w z-v6_xg1REh1Vf1$lcBHEpFe$J$9dfLxgvY0=hFQc<4oh{euL$SfF%1dkfPIna$Lceb;_2Wy3e*$x;;^1a9mO zZzQ{f+<^czn!W73I(bcKb|KR-81b- zDipMH!R))}+ja0%)8x)Xhq`z#O}lo;Y~j|CE05i}1bX@zvPhQ?`>siC`nUZTrpKzB z@x99pO1T?$A&_E{;D2+{!1LB`+~ME$%K{&NWotjb*tTBwCxCy9h3k?^`nP?!XZbS$ z8W@?=v|haC*SjM}-URP#pBQCaO#`-B7nA7FcjX%RlQ>IedG(<*Cr}|hA$PTRC=LNY zaRBFjQa-fwZOcyM>SrXw0IDGLaRo-CeJK+`>U=hLEz27T`|v{G9{p*ae%(6u^s|bJ z1F}U}p;0}c8hmstdQOI58qd!!?Q03O+zdDlP%L`j6h`6w@dWyM)y9SY&c{|%sGjCA zqj~Lm)dr(`BikYeY6jeE!w5^R#EC9luA5V6`C#_RZU9%khuVH||=f8=P`jbhG!(&vqk zBflGWNwziaKkK%;LsU(_o+aF0^Vkf{#zmzY+&ESDV}RuomPnhgzUQ5udn+*H_5sby z!GeFTl#5>V`MpzEdn@awrcXLkRmSk?f0AjsbveAM0j^F=>`ETQJk#uPCfL#C^=&3iD}s`kh(SL$~_7UFcPS2va*`m-k^j`2zqUsl4V z$v4HxLhHCamM0O8k=!E0$}6E+fi)A*;Uw;wemrZ}MpOo}?Wd3@=hu|Xg47@fcoF;@>wn9eihR=1T}xNy8ZajFn)k7&5Q?> zDJC*^dw+JiX7xUtj9^E-Kff8EedpNSmqye#xYLTy?Ig#NH{E#TBfWTWrT-qUtzB%o zp94Z!w&Pq7qQ}%{>e-k9A-^h8u1uZv%1Q(cX|NbR^Q9e^F?Ky3zVB=5g4fVCk=~sc zmUB1c2Xjnzj~^sApCR8VhJhIa$^A#sFZO1)sLR>7`xCD{`-Q&LLr=m^btQuilU`pQ z&X$$ z-^WY-p`$kwjkXCp0iwx$|0zvv<&ct!0@+rVvon#30ha&7stC>g3wZZ{A00-5(kSBX z)M~Ab$%VOP4qn&w({aw@GaPw`Lb;W44X&h$IOLe zfTaDUd))`&L?D#)Kx(rT?4jHOy|V5E9z1(bbC$nnCzNT=^Hms|Nys8|EYwac8C_ax zoqcDrb~L&FotUk^_j|q1yKcdu3}w%MH{cTMCn!=sxnn!RJ>JMNEDNC?`KZ(lN1Nq4 zQRH-2A!xC|O|Y!d>mw>M$B!45d=rTjJ$6o(lCHBy4v?ptoU(A1&x8E>SRt6kZ%=apWncdlo#_i|hCQWi zMfTBwIqG-Iewq!zP`RVZ@3kuNjTgF?uJ}1U-$x5LX8~>Zd9)spus#$Ox4mO}lkOE> zxQh8jad_JSF^F1j-d;ae!^}lTy*-P&*8#+bBAA)qz3wxndj_R%QlV3?^Zw1%;oElJ z-hRF>$+2svmw1W-L7bY_DUM+kdmnMyEKdudP zddhZdi~F$hQw*p+zYQO`?f4CQP z)}s%!T%L0U3?RNzvl+QQtoQc-B6CUc_?3F2hYzdiXUCOue$N+{xa6o;WN1|Hbv1RB zs39=Qc-`}o<81L@|2CJEKkteP7-dYcl88T7^2qBPApzo7*JhY-VZ!>ag(G zd{#naEudeEmnG;-)ETVT!)xWq!6bv-2bfpFm{+h+^T+OyMsc}hmjC|YRS%N=5*#n0)SVVY&{(bAg{mcEkg zT#?5Z2eHiA%_oh@L|!opL$5(IO>@0IZQ;Y4vWkshJwoxA3zK(hSLvgKa~Q z2lq9;Pn%c4L`M8}Zv?&m4s;&@vH(Y=^T(vlUh8Y?9qxM8nKhA9EyK6if+`cIPuvvD zW`+|V%@1@mz3}aLF6-OAe7#;pLpNQm`cp&6Kkv77Zn=!Y-st~30mbn8`fckK_p+1T z9sh<63fWlsuvD7VM#~NUsd#Mqp-y?f%CFvWM+Mk!Cp^FX0E?>sX-Zq;lRN?D(2-OW z98m>^hq^01hac+s*K$I9C$INe&q0XymT*wm1{^E8@wQ=WMLv1LMh2JF6`_1K;dt8l zLsjPPQf~X|^yb-AgTJc4IEsF6qVDNk%z2l9)Ss}y`18N8#Hjlrx`rkf-n&XV(N;)5 zJZv0>)UHWJaNGR5pEEjKvK*wDp4!9*Q ztL$0o0ZSq_vNzNRH3&fVR}_DN|6f$HRzPeJdDmQQ-Bf8*KW zG~ZgTfauQBZ~wOEr5)$@Nj^Ej7?T}Y-P8WCSA1;b<9)mep9QD>tS)Xry|=3idv!00 zAF>cT7f41qSu9W%d8mP~o?^D&RlIxXV(Pz#0?bcL)~DkZl(jVpO0)l ztEq$&wvF~~J2EJqU9#-n=-b<-r-yVpkoRy#&saZed3wEpk~i~Ezl?**7e{{VN5wj| z{<8=Om6PC6GT|L-?LX={?|K04^y4<+CAZ|~-$L|TMWL^M9KtrS*F$A7%VM4$#Setv zL>YPT+UhXt_}fjXmnPlEWvf+ob4Rxt$2$ZVzva0V9As>ZVhNh3VshS}8Pa_Fyp1P# z9G~1St{=EnIW|I)(PYM!hQ?!PA(5%T`xgJ0(-&|MkM*rj-swE#ec>0Ft!6-^$PY-H zvh)!MkvHjn{+^dOtN6#jYf zSlR^Gk<2!>9Fs((NTUD=Agb{>;fNzFZSg9O-%r51N1~Myj?~E6%H8tfzUSqY!>>wS z!x4OE)%Y&>)E6YUI=VA3@J%Pd8~AxH5^01t#``e&)#;C3{@CybMgS7P9*P0aA0Pl* zdiPLY6FxVGP7FgPQSY;#Ac`njDow47ECfto7{p~saF!HVVsQSzP?*GMm!9qx`PHgF z%I)|q?_d67*pKAi55Ff<=Y2y;Ti6f61o?6)lm)GA8+b`Lu1uXu;j^Y9Fn$jII)5!+ z=S)f9??=MY9LYXOfR!M?#}Z8d>8|W3t@Z9=@MfKkFCQ78K{gO1a=D{u@+T|na6-%hU z3KF9p;35&|t$XmlRDiUIA_RTXS;qU@`v3M!V3+)I$wNkhG{HZF->OF{s*$v(m<;zp zuMfc30cIqqZz_XPrV&t|))6)mfR=PJijWkP7Y48wcTGnm|E4^={HO8|?7n5+mxmVr zNZNJW5kVRRFSi|l1K`!;>h(ERh{jSa3BhCj8uvO)keywAPk-&@1c)X+as&Wqy~XMA z-ApTh+CN|}0s>@=z{WW<5`rzgJV9@@d@ptMq15X234WM9uXin?&U)AeeEFO3qwJI? zHa{+7-nmb1p8qwO@ZLl6=m+1IidL@X#zznl{2aS)fT!dk=necq0+{ztQUXEumsYI> zU^o?$;OBf^YJDcUlsO#da~`tjsMIuY3_UA3oYYQ9#E4)MT688?Dm%2n>iP7*^q^I8 zq;!+aJw8WHRP2JJx*&Ber=_{=jC6Eel>WW~iG+)BPY!g0XN45vYpCMmqZtyTQThgJr3VD)2$f4)H>*a~Qrws$>(75AHJz;g_uzB!>5ujVNG*Hv z*}q!-;bTyJ(rfzPBEXPT*5pCgBU)fPk?g~DB!H2DJ;9VG4NWCd-$)X0JWh}R4wTw* zpWq)!4kLm{PUTs7XyK3L)&<{_I~IRWZhG$#S#ge4KJWeQ^>(j+&+NWh$A|aV_sf7E z=5&3Q01ov2T;@JByOw_e0>Bd|09FM*TX<@EdmOU|O#3JRkgGQ*3A~o)agu}Ho%=L* z4nS9PUSBOv;G4xA0iyAZ_&7VIwso^CJu^%GyzS32=h&;#4C&L*&J`Uv;bkAZuxWal zPGEt>K?H5#@d^AGh@?+&N$iuN@|Du1;AfAoTfR^UVD)|V_FTakIYCY19srh$+_IL> zQm0L0q~_OYj43aB3UEJ$a7vjX>Fmmp)u-NjhFdO#F;m?c+i~`(dBxq`>&`OR0 zHVg19n6gAj>2ZQTg=iI(+WHdd3$;sqSDnl~vQ*waFkd#F*(}{%ly;s}_L`CRq&)~#>VLr`IR&IgODX*E+CzD0v!>03fHNudh(L2J+;1*=A|uz&4!E zJl$O%cI`U1^eK~b{rxV# zC(MIY_jx{WPR)b)(}4de*9NbH@y`)pH3+aCK!;3$#S#E)VjcF`k{AFoj*}qj!Omj@JUu|m0H z^K%D>_en+VYRDNI#Fo^t`<%MC7x%G8mU1FX3g^me*1_}RUa7-)y&Ko>JwNroBY-b& zrji_yc@YEyuFY3>c1w8OS4g7vTc;12jQ@7D73Alf``D>kd0XX`ybhceLEn+xfa-Y(VkOCUek`^yfmz0kXx zq083;IMn=3fV)cTY^ojqryy`54BUQeY?IrMTN-t&aiw;Yvl*a1K7S8*uE7fBgY zjU@IA-jkhP9NSO1L+Qg69iaKuY+xH}{FFUip#l({xdiL?jR2*4)-TDRi(d814tV|l z^5ST@Zsy%`{k!+c4YTi;>*w4r*S&Y2Tr=k``O2(Y<%=_?$`!M2kume`m%m^AOIeZg zF%nI_o`0;;3;~>_Iv9@Is{1pZ-w2Qd0cfnWWXPZ`!JGtnilwoM#3iH&Dg!2g9ElD~ zMNP4^0O$t%Y+k_EHn-Lx`mhpZxj}V9r8MicBmwW^J$w+TLGYp-L5iTc>v=vyfXlCS z*97+YXAvMq0&IjA_n`Nnb--016Vx8;9HV2V(+~ALlUjOytLpGxY$my+bDR&rPc5HH>;(w7!kezecehKy&pEps$(f2XVLXQOd<4-N=^s3Re*=lE zc9xxzhSqJ;*1?FuN0TyfrcwVp@Vojxdv-@IUd2@$&f^Q=s?8-*({xnMR#MV1kgc``WT!4&|V*q;g?5g+Ygcf#cxlx_ExUEfW>F(w9 zMv}}3;3EKYfRyF{VVQksrhN9*i8AWlhh)_3hh_AfM`X<0ugTc?Uz2h3AC_?o9+L43 zAC&P69*{8$?vZQWyIsCA^H!O<;Jfndp6OE3UMi{Bp!E0ieuENsiEcImj0AhuMn(k! zyR5PRIS=oX{p$&JN<~#6ehcOp3EUUC1uHhLdc`viP46_bLpJmd;2v3{Ba z`jqVv1PE^XYcGEd?&(XP8S_MN1YiV+$JT)Wn*dsX)d4>W(_zL+07rr-JUAaK#1{M6 zsfTtf`^X{OLyh34m-paz+in0G>-JoazoO;?cvwy@*=gP$1PJx*fQMKI@Ni`|62R8y zr0n5@6-gh3M>&kd6$x_d7TH<6L=Kd!(1C4y8jFFSa>p4YPeP@Sb#7(BPh-Ms!y9bX z=3+TeuwGu>@|?VX{9WnR8C$I0SmlSq8jJ)07T4`@Yxi*FhH$uCn%XFX>T#VL(59m~ z*}+8;82D|1dkf@GF(LpfN$t1~J-?SeNP;!_9h75bhvc)b+#*-a1OeuJL&m)KO&K@u zTLAnwWWvI4$VA$rM_>=b9#WgM07RL8zkKE0TV?FL2j!1jpOfNd$~gYf(;N78x})lS z2YPKC@KpxEwCsqepk>aal-HKXzT$&&wE8&mpi1ooOC}BI_S|5C&)4o*t%)FVRNx-I zKY{l_R@2jts7m?h6JiJ25v>Qh2d>{{8@$$=y*JH13y$e$aK|&rGovEjIUVcy3_uG4 zY%y=`EdXAE*`AzkL@?0%Z5xYkB!So0hfEvzl~4q{4qPMXv*71`I<*~1>RNmWT)mmA zF^~Y9pSw>62X{$B^ICw2px+JflL%axmIScri>V|#k50&f!*9y-yI+uJcfBO}&0JoD zi7kQeJ-&H;uH3AvH2cI?A74cI!w5hEa7Bj#{FsHZ=hSMMzvXS&a*-2nAaB^at3A9l zdVd;$Ph(Fnq5$+gJH2X~&f@=uw|7r+v>9A(M5ltEonl6S-hp!I8{q5OajXT_3S&<% zw_!Lg0O`Dw%Vq4GZ_2grJSCl&i(S$cOQ`J=RTrDn6&ttGI{YgU|*NXi@qk4 z7d{MoMB51q9+azR-zwuEo!;0#TLvSRjM}4+0CbuIx~>E2iQtAp(5wTB!y`lvlpd7l z_Pi#~?R!l&=fa8MN?kOU*Ny-oqW~Az8bn@2l9Ld;r-7ZHpal6sILglh!0KyxF4)ox zup`aChrjmIjGrgCkN4L95!;%u&>B_VH$D)=zF9RRddar_F*;qX4>1#190=zd+D znc!#j$H4C=u|#9XWyz`8^1`m?G<20qZbOb7EI%U4k?=mh zZ?^n+-IH?nlAp<__r8XF;653>;1QX$2qakeb(yjNM8N(ONTews1BvieB+rj5{h6Gq zVx;3AK?;u#aA-?_0wmVSM4$xCJolc0z4Fw~m*vF+(`A1-*F+^CaXte|4^RVeB-*EA zhj<-3v4XilcW)c=0p?F>dvAS>zfaKoJ=tD<&A&wgf80KYZu7k%(J}-`g8%?72(T3d z09X?oQq0~}n@D0rz_BR6tRyfHE4ZT^dSD)1$v^^Np99sRI2YCH>@lOfm)(!!^Ecy* z*a3~d2HtW7{&%j%;8te<(77r@9LZ}Lz1{*4II=4@b~oQAb~!F&(5k0e-ua@0KKB-2tK!YeFHzu zd3~+&fCQNtT-EpFTM3B&w6X!A2R5wjx=zG5qmVr$i#`D5Do9czkbbSh|K<<#^3$dHukAd3f0`<+^w8 zmm5HaNg%+K1t0>zKLte4r~sLC-JCn++8KAs+@s6!dRB{A!zTe~0Zs0_zk$e--Hz^N zscx>2{iTOw!}%@JGr-Tp>v>%elxF;#gTvj@f*gR`N!*W-gd|{|pppvt!*H0>IXzfC zsCiHKSu)2-07{_Xbu_;Aa`U92`D6Z`o&YL=Hh}=!K>z?Y0g%Vpqe^ptje;D`ln6mk zlLQXz3VIlc5#{wU+>;$sxYoAO#%bH(fp=-yGt5q_%$htd0>Ha-LP}jTK1+lIaHbtu zt}_d5I&~aCIfUydd$n>wOhKMNSu_zu7&ZT1 z`P^HR&T%`?@-jS~(@XS*a^nLxE z(%ET4A1ISZ0?QBVGl%-xb^TEi%t+ut>w(Q}e^0;d&(pZ=-akWFf6iYUG&{~eO@aY( z5MU>O3GmVs`~R&3L7-j1{N#9)hK5nfBf{rDQZma8$)2>e)l2fX4gY42Ju zHBIaBEyA0t_6G@~o&X2%K2!F%H-Da=-P`5&@s=eK{~Q6f`LN=%K0 z7#%XNXYRsnZ~QK3+_ul>f$raDqy-27=>Y=lLkI?7698)*t%9DdZTkUcdR%3UHv-;3 zPtTtPyBneauWr(YrOe&%ntLVGzg?=DHsI~qLrc&z2VfNlp8>|@2-C?U($c;QVV~oF zSiZDUrgmPXP9jM@B4yPZaX)rE5&Sf+)^G+tB(%LggNt6JKv!++4)H1N1Tu)g1!|>Q znmfJBAJ75+y)$eQ;z;13Q~N>j^4Y46O#|ITCd@&mbph3AlZLS-aGcb z>?qhH<*j@=JmnAn_=9pmTkG|;@&1DG4{GPx z=iZns&+mALH^N^W?;Qv55gnUAb_Ejw4~e7z41z2Q`8y;P)de_50uWpVRLUF@ptapP z!T32wf-`6dTY^1lIZoz1nU`qgC{UeZ1ag*HoY$GnU+|yL8}F6%{J!O%Bfw64I`*Uj ztVzfb=yQECBD^)_i7qGwVDrIooOWnZ#t}eC5hRS$^LV|1ngBQBx|^JlRnf2!iQY~C zjy<^M_2ZiO(hlGrlsU(wqjMjm(q3H0@+CdL)$m#6scB#b+ED!GO2H2rx$>$nx3AXz=sy~OO)evMgr@_CFqBS zt023MsjhG9$cJVp@B!wu8^;4}&-HrJls7%4kPQSky*WF&4E$QAho1p;IvfDK;` z8f8#NZ%pjyb=N&*nZ~wqL`OfM)m4S~eo^Pm^GP6$F45hj&gS0Z1K5NdkCtG6vwMOtQsf zZQFoPbATtH<6Lggyt!X&)|MJSh;Y7N@+#K?DAe`7qH_S(9fTc1#6TORw?75|9gwE> zJ@}L?U0OMl51;D^9g^Bc4s|#$i71Yf036Pcsqiq-Wl~IFnbe>s9T=?E)t1AtN@?w+ z3}OOF&CN;+!N%IWCA#YQDRqr=DDmVJz zsd!BC8s3z~t_?CAVfm7zpyp<07QpYcerv}iX= zoBh20{j?tdz{NWPfC_#aS*h#e6TIrh32u7$Y)(M)d4Zib8DoRe%;@oxi32c#{jk)u zZ-#o9jpvTweP~>2+#7WM@NtlZ5{L^<@i|%-q`74uKnPN#FKASt?8$n5 zc5JZ@S>A#hi&VF8|o&B=RICq82%(+5l<$OtIo&UVd zIsaK%aQ<&)>4h)J+DjAUX!$=&eHZ5+5V)+w0Q`s;T|JB_^xSO{jx^wN8@oFVYt zAU5}ax+HZzuInABhQy(KV#$(A4-(XjJhXl-ZE|gX0$meVmNfx>TfU@EYyXvPr7~{D zUGl|gljMeX@0PLi?vn{1z~n{H^R$HzLeoD0O%FMP_L|vKWz4&`$^0Yl|k|0gwyKdql?1dk_)ke);U1lVs^BuEYm*sNfHb7gF$n0BQh| znt_U-t*Yhfb0jyTKnywM5F&t;PP1SPZWD|&MvJ5j4E4yl(qqyZvO#qQcD^?V;6bk7 z=W!2s62M5RLpGG8jszM70`HlL4E((8e2XvwkO1(+3G?^{X!Ga>d^37_B><~CXtU=C zelKT?1nT`2{G9N8LiSynD=%;VvrOCftdur$bq9KRf}bsCYH{Qyflo;Tt&cVyJ&B}q z7l2M+pM&1ck&>z{_y`R;W~Ni z{Pi*uHtYO#G6(x}FJ3S6FI_K-@^6qO1=q{6{HtYI-sfdy-WOy~>BCahzEb-8YVh8S z3Y0H4PKUal<;#vPHp$?b;yGS`mTTpqIO~vcvH@y#thGNkpAp*ESj(=YM7rAEoT`~zd0VBaU z5a7Dmcgd(3_en!HWgopbz%>GNo&Z_1s1QT68<4Up$V&$}c5pSd@;;P2-Xjx)zBMo! z0Ypxg9+ublydu-~zbWM{jK+9>C14QbFeNiT6F(ct?MSM}{AV5c`D~xW;5L(OPk_XJ zjR2t(s1C1TLftC0tvOQPmIvVUATe*n@m3I{QF{BD zq_pNF>;!%!dTd*Rhua+>(23ep^45`g^3c+Mlxt?(E~DSOSEerHkO!EH43j~GuTGyR zKm71F(m!lv(Ilb@{|`P3AQWnnmYycLYxWOh%xp-ZxsQMV56YPL9+1zzF;Sk}`l@?R z4?KEX8UaQS5lr0o1mO3RmZlbV-qA@Uq;H^G=K>f3f?nSUK!BU4kH|2Hy7Jg^dE>wv z^2)wf;MQr?QJq9 zBWI#aPHjIH-LE#{yn1^AouKECV;gp?+cYG)?669w&qy;8*TsjY%f|Byq@i=aG{c%Y z_DfG62eh5SHRte6bEpEx>$rp$p1!Gry~3=vbrnTFzK`CuckeFjnSV9EB(V66B7OMIgbF z{L%7Z;VAj2cr-{bR@Rq|mNf-mmW`JtNmCDh5dMIWJ&Xp>@tk~K+r;Hb*ue$alVmBh zT~}@B=%&7}l+wD>Qs2U|EIl#xt(QO{3H8?jBm@Y47(Gw0(;`ycfSjYP2(qAEhQgfV z%k$K(re8^l&|sUa&)q3sU-~a{?K^iP3Oo$(KcwKNL}GsMm3OAdKdydUA~CB5b#&E8 zb1R<#OfraU&)Xx{O}j@X&V5)3FdBKr4e#C~*S>X+RCJK|B#r@%5<|hKAfiWw2iMkv z8Xh|tk(#;^ybcO}CUaDHkWySFiWy9HZ+N;g? zND$;89$THf??_-oaNb`*j?cuL!F{$Y5tta0tOkA`1^DL(a0tK3ad>rBVhDOBq}=uv z0KaRqz$f5?X5hEcnC!7VhBrS6fSi@OMwUU@o5~TGr=X)xKp!8K=5|(l_Dg5a5depe z9p$J@dh!CPKm@4oJSNR;#{dMY?j+(D0e&uKWrNsgeyNgFisO2!q_q8G`R&24%AZeM zCr_WgQC>JZMqWBIR$fIr?c7-80ppPajFWfIj{+I40U55B1xkX^kU}6q(P&u(60Cu( zM>JSle3g8Rh|$o+`3I~|`$V=&2KYGAHuf#_;s0#K|5T}~HVh3jA0W_srM-hyCHB^` z)M*pgnGbaLR>KJ}2k6In<{MnP1V1d=&K{1Rp=TXL!W~i~^bfQpJBAi!w=+o~}3Oe}y~iC|L-<1QM+TuZ5=$!!MstkVEhq3HfB`tjiUR$hP^0Y*ax83|^epD3#? zeN*1gxkcVN{bl3@SIYZ{0v{BNkrjnwWVJ?uQL?UVjI1lZPCm|=C|&&|8NWzYbx4AK zM5+=wTf_%#S3}Nl=mV17G&{3wnew*wVuW}QLg2}9ERpO07~mZN{!o9t-p__H@P`Sj zB{q{rAhb_a2h5b2#tdLAmN3M1^<0g($<%!CqYc&#pGF zfcH8+Xy)NNk<+}nYr6bp$8+-9-nZe%5Dhzj?0x*Ei#|PR?fIXJ?xL_;qHROYjxs9_VO|7%+D%Llk`<9h=ninMBM8V2P+n z8R~fhe%4e5F+!-;chws=G$CV2X!s;scRXnwSCw_!@WUMfP}!LUUCW`$eAommonr4Q zt2gla>0G?W1$e(x^2VN*sO z487$@HuL4Vqc_X%4qqjIIXPOMIyFv7@bcNw@+JuI*12&q?d*7Y>+D$h;KDcLOw}wY zY+5B73x6bY&s~jZaHT9O91STnR#umembK+KDhXB=epU7q{0RRS%amPeDf|fa&4qF* ze+K|+xq)_g(aY;P{oNpR9dZDd4C9zRo-~Xku@~g$j{$##Fda{JgXkSdek+g@GyTsh+o83O{`2)lmvgYq{o-y|C^a4^Yx|5OAe2fR0+i8(-$g`PJFE4f9drncR3>>y z0t3Dgz#@P}0gD7AfhEW^1AnGf5;y|rcNHh$NPy$`2F?J`04x*I*fD_LM+0A*H;ZWt z;A^ekJiG$l_0kgXHsQp#h%88GORG8kkptFvEn6G5z`A}~M{C-uYe^1KrUa5=S$txS z{AKqu^8A4p-g=ir`bc!N}EwE#$Sr+8{On$lli}I)A zqvbCr#>>+u$5|AB6ng#4IC=BT1bGuActfS^XEh zKZiSnlmIk>a(D4Q`SP2$;584(jqid0vmTbuy?LAbY|RsT-T>>tp7s7DkAXU-n!dH8 zNw%KbDu3Maj6A#N75T%KCvk5^f4ptrwF-XT6U4Vfmjqy>8^-_GAvF!mKf(^=*(i`9 zg0?<6=IdbqxRQy4P!cezpvAuB3RzO{^Wypd0SIQ2Aa()(HzLrI3V0)c5yEv^X$g~n zb_7t*PZC%XOYn1WTK~W?sjT0MH)Pd@;HTL_vpROi5kS}6OmHAup7agoNJ--nS$=+z z%sl$G-K@0DhaG-XZ%dX3EcZeO?|vGFtw8 zbeueSe7rn+a)P{gYJ$9cdZN5~dK|z%4rD+iKty=`#Fu2=u_@Bn&Zs5GJr2qG{6}Tp z#jnV+;_GE)$&IqQ3~lk3WpC+skeK%%QgMtBzz>k~QKUn|4Q|OouEb{GPiexNlvt!+ zx(5rSxOKK1uK2cWFS-s9=ReDa!v9S+7ylo!z3lU{yYkDjuj&dpP<6E&thzyt)!rZ{ zYOj=IHD8t^RbK>2u934f-$pL720t`4GwwWs0F+SxB>=wW%m%sYt=pmNAA+<&ZZHSH zo^!8!@%5V^FWIjW2XN?Jt%Ga;^6IYn-7aK_CibJbo7Ze2NNJU2E%fLmKow%=>HoUV-nu|9R{a{P_s~NSKlI zvM08GAWz!5c$_nJBSEplNJO? zQ|N#ks{DzpD)@@5DEM0>*I$**<=4yBiW_7b|IwCt(5K@I@)`)WtYfts;$ zq;|X&ME8`q9|FQI1q!B?m#2occ#4G|Y8~5Dfr+%M*s-fonlb0yZtb-}Jm( zG5uE6_G3T-YWvSlpCW(Q`l7xniNxEyDFGq!%=WkCbFbY3@IM0kn%n^U{A;(%jso-G zrrG0=di^?5l+OjLs^$vW4ZsAeN(8qh#r(gy&M3cw;o%-BD&wP^JPDXf(9G+b{BZyV zsQH?jhH@Dg?!adR!0|ns)FY@3&{<;8+uL!y2fhcS;kd@UKO=%CiIK}lpb;R&2oNU$ zIHcIg7&W)81Yk!Mqkx&oBbt%I@&Nx@4}KUtJgYu+&3lj>vKqwW?76iXzdmLN3Ghhp z=SU3_;S{9}WKe6@X?PU&18}uvB*D7oK0K_Ffu6n`I=VpG5e*tzPT|MTk*>ZxIaR$) z+Iq`m$;Cg(kGFqWezR|!Ja%wANH7*87%$J97z-kd1rf#~A|N+_Gze?615`38VMJZ`rf-@}pwW*~NZTO*B-3cK&fvC{# z9eo#MU+K?fS>ET6Q~aH*D!mc}x#mgJ)z?XDXuW>Mcmm0IOF5iDg93{Htn4h6 zQPBOP;QhxS5{#Mmpj6h zdDk>Voib_fX_2P327K4m_#YeLh)8mRo%eBob717LwvIX^>&$l|kVoxOSaQxq0#?$P zlzPzfwQLDuBT`daHGWqs5Ew~0qyyKuNXPd`cmnJDI)FPVsAXFoBk&!`GuNOc5nT@8 z2;kSt$q*nA0aWhLNCqC~F(;9f09o*R51)j0=VDWp^}DsaNx+i;3Vin5;yi)x!QVG{ zUMd^7s+&z#NlU3Tw4DSgID?B7AbaFc=24?MKNm#cC{B$G)sRV*Qr2=*7N46VCu?>~ zMf)-N`S$DN7rRHxZ}yLo#}Enrd}O>ld2F2g_4pWh`ow6+qOtM1rfe1r)rt%V#4YIQIMvwsc1QTnLplqCME*mdf%g4#~igB_Nw!3Pa?5!Fv`>H3X zkqC$C#()GcfS-08+b0^vgACWpk@BnHD0axe;D8K;nRG`aisYF;H2(`fW09 z{v*1K%2*`EpMQIbY`DbbDANiol7M7x3f0M&cOF*hb3MR+-OPvNi*Mc~|M1Z-WqaNl zscK>Jovq%e#)gOW`a4^CjyxxJI4j@}uVgxWV0(c2Df&i2!9>m_T4^kKj%)=*? z6aj*0>t3hYo&+GUq3Pk>WASrRTE|7L{BQ*uvS4OOFExMv04qg%aX&6iatX%)bby)? zXE@AqCkYUE@`_Gb^d#UU7F(i(BuFIdwM{ z83`sJFBmI-IW`K(@fdjya)^@X6-0v95D6F!UIPi9+5H7MQ_ctT*-Bz%ZJTBBg|EsI zM1Z3;Pb&dpv3h)7E^ET!5=rRgv$Fcqz4C6(|1Jv)u9781H_8V^qvXTl(XzbQI@wm0 zjFpc|#>x8939_+ttdd|0BEk+3VORMC*EjdRIphW}oth}GAOgGwdxJ^x$s6RQz1K-q8+(CmIKqL7=Vj)( z{|Eqbs6Z5-vlXA2m6^C)YFr`jp1KO)|Dr6oG)flc-5`q#M$3{y_Arl;s1OaK$@!%J*0N6Fzf7 zdI8Ap-bP58S{WMZmS3&?t9D&EsZ4=YXc( z*GxeMqwJ|`rWa>96W1r{c`1k50PZ3H+T@cF$Go-OkM|vv(&{{1!%{6HfN%M}zV+L% zG|%(1ktE(c5qO=uuYN`n)Ds|a4g_G2s+T3K$nbHWeDE_pc90D0FJM$439uixV@4cY z%i#<(l{Ii1z`=B7 zIEI`c?m=H_t)IfE^F;5%dLiFaTaq`OXYh~tH-h}A%OmH{UmsQXV@nJ zjv+c6uf0)@;`Mz)_)b7d{^)~{L&G|VPvo@&bL8`H-i&T0z68JaTco%kKo45$zaZFc zo5u-wY-^9Z9b=m%gMD64QaUeuRz`pv5P&_Zti~Al{U9@Y^gqYm&@4hEW1NKrBCyq` zqmv*sPaf9tZ74$vlGu)rTXI}iVPLN<>->2Lx4BYUvmHQp;ExmhBtbs*3lTjoNdqJh z^?fSNCoSTORNxy4QaX!^;OC?jS|zTnmHOsWGCYi^z$FOXf>iu6&(!RYf8IDwez9w` zJbqv*qQDgSrXy3V4Dmy@! z)DXzRUS)zpWZto5@?Tz>B-g%uuU!51eIUSH^5r*fl{3|+B@94&0;pampcJqk{LbU5 zC+0N+QdL(hd=^=pV8!QRYn^YyNZ9M7tOAZvhg6dUo&cQEIV{zUh~|BD*bhle3D5;I zBE;F7t=n!NZ9>gRk|Drl5y1#R60#p)kOVl7Ps0S0GRT>dfZ)%zLF0flf?o%wF;e(z zc@F~J&B?88Iff*bv!^&2onv(_0WkFLc~VfZ4eTZv@ZJi1MhOrETAy=wyL&H4OB?Ie z@a_P;t+-6@U#6DQ#b;?1xHqRQazc6?WD`G6weH)K7X9K>VMKzX^2E-24C(r56Ms z_=DD|ZH(I_gZ6CmI0?b`Fw>#s>iKE*Sw|2c5x)Qe6exhrNP;ZbEn)R79}nuyU87G& zhmkZoAdM1-Kq;2W#v{;`kVB9=T#h!n1DGWq5opNm$#+AC+@-NdUJbAqZfRpkKNM%4FW@-^wp` zj*(yPK{PlxS^f-gKZ%wkcp4;l=GY`iqVYumq7|;l- zS6(S+svef%2=bl*4$5nS#3`0R=&$G+8BZ zNq|-7H^|?;j0i9tM1fre(tP2K+a;&&g7k!%04jJ^2T~&dTay(T0`R;8cwKXu^!4-p zJ?6#La~nA6xp@uyE~;x-+UEL1yr)g-?+7(WeG43(Fal&jKY|2FTDK7)a~=^O?V8dr=6Q1 z({m=vjGXZr31;Ptk@s@O$^z`qJ9mT3K69mfaN$ujbwYXY%h9L zR$RPNR^?qGYm0A`jb)=`bH!-cR*B@eW}NJ<9WVQ6kVX4y#zGpw``0i6TrY=9CJ7hY zjl>AR0pYlzVaTA;))KjX`W=ux_dy0dB-bIi{UT(I5g;XaO#i;uy@;QYA-9S^q5U%`Tq2>d=h0992tJ6jEtetW0L@8 zjLuE1<%k5V`Y;mkwS$OEtx|~`f@7#+0J?#^1C}KMkDKS`F?XB`jRE-CmPA0*0r>56 zlbjrc*Uu#Qk|Tf{SD$GqOFZBW_<6fFNxrAQLK0Dq*%^}JwqvsD(o6E%k#EVL_f7$*Z;;3KUN4XDzh0g= zc#S*-&;P=)>yZ#ogtz_|{9l7o(SBSW+x->!^O0+{Q|);W;H5L;HF`M^XZ z#N&0;>GU(#%B+*u%F0WR!jmqP-eFd(*jZ(NRc$+D)1`-G!MV@NlDw;sAB>Szu+>F3 z%G%N!WkWehFh;gkjh5}z<78*`c-dV&7Sd>(?5iFl2dhWP!HR3;Q1KLipA&W>@O~ya zdU_kAsi#iH%z8kz{|)axtSdu&>5V%9YD$l!boDd>*zB+pS#xfy{2wntF3tM7Ts!M) z@|776Kr%fdog9S<0&wCEhi+RA02xG&t>(+1j1D|c5W~B7)ksSRelvjB0O|-p@9d%g z!O3gun~J5Xmh+0)&wxZe(g`ODJW01ilWhPy!B1<0wQCz2aoii0YB?4rO( z0>tw{0P1Uk*8|@fJvx%o;1FcnXUk)@ZQ!RTH}I!epGQ(!Q_B_CSfT_O0RGNy_72mN zBcXMG&(`v7TbCvw0a|kqJs2hI=__d(fzKtzX?&P8Jij*%fNSq6#E(gjVJom@=j%e_ zx{2#7jka}iAzan~;Xoe0r&%T8fUYETeW$eb7fDI`KFE>va>V*k6kTG z&;Pv?G;frsKIxHPK7a6tw02*V%@_Vz=AQa8@&p@-y)=K6EXyCG3G(W~F|xK~l&nV- z*ib%3HkFT+t(D_sM-?OyY2MFO@VSHt;i;%d@_rAyQ3Fo2VhptiA4 z>X7`B1gscwEmD4GMgRgIZK~BwkF}ePhzu4j4E)^p@9E!<@K-l zPE>pgKdSe_rop@V>fznH@V^hq>JzKv$<w{LD2C`O+KWlQO$y+r?eTjcnn71b(Y5_*<0}QAC2M0-o04tTS+o#t49%z`e(a z2$0YSU|yNYrnTOa6V%Y>y6=zkm^V)tl7W)OJpa<;@5=oP{$6fh^nLm9>c`|#EywaV zY66^n5A*yafR-~|MMrd*@h0Q|AQRW!(Uq5(>;}Al#!Tl^;!xUjbrnl^7+)6G*w-hm za9qAKK2RFQMX6d50TSF!<}9&CwR=A0nx^R0%Y%_VOl>Ozx7Oto?%gE0X8O z4zM$AlKdG-^IwkLB+nk7illk6JbU5>dG-(hSv(UzAiXN<+yobgDa53@{gBK(algED z^mC9$*UKCv#dCAV$%0ErlmUG98N82n86?okqOr23WGrORSXoy#9+6-iNH9*ekOVLe zpV(10Ms}5sk?n<7NKQ4YKwMbLyfMMlH`J-;nF59u)Fp<(_Xtn>g(K_jpxqEAj*A22P1@;!t?$bdVdxIJWOvb< z^5XvMUM(T>}mB z$owD3l=r?a_bvXh+`jO8ay@d9T_v2H!+f9#*6aw-Vy%Lo#%)gmiv}?`u$U&qo(S$Y zO?N{A#VCP%1h9lu3E)ZKlT|f(ZLiZmF73U=GXMB@kz{`k z3H1%~`pL0KlE)xP9w##|OprMsz}!5LAb)}^E|>@cjF+V#z=t5fa?VUELQ>3V06FwA z$gs9#ysU$51n{@QID}$D{#T`}X#+lgmkw>{f^lqqS=~9gSbkXeNL8-q-_zeA-F@vi z-y$8*`|aJ$Qdo6fj$eY*Kr-A8nbRI>P`T3)YSL0oRNXs>CrQy*)KJUn?~r%(VUQ%3tw&zP|4*scJ8m_P!=*?X88?NOKQgQy~qV z<BR=aoEKgccf9+k5ddl8B&@dFgX@{xF0E`^1Sy6vU zE|wkwL2#_U(OPKz5RBV=Oyb`anp zxqHzM<(~I{B=;=&k=!`*etGZce0--p5&*TIFBBVzWLURM=e2|H; zWCsY4ECm6mud|gHj8Nkc1;-v2%kt3z^`N0Ng(e~rOf)Hk2tX1nr5pkQmVp4vVH`rS68oAU zgA5yrCd!)ptK>-eAMu`Cr=Q6-r5h#15F!{7TxfSn6B`CV-OtH0W;`B2ve~Ckd+`#5 z#&bbw5=LY#0hw5)#QzE)ayfQ0CcKvPYRjZpzq=tsgK{}kupN<+GL{;jvak~ovQyiv z*>V03(RjD~X6;|)#_12pO$)v!V`e=p-&*o>X$ry7BC>h{WUa*v81qIc6jAVd!#;j< z8p)u23n?W)8GdyDkYLt?)Ky~=0JiqNk{AR`0PtKqRtKQjHWS$-6ImURStsSKC*{>W zZ_4BApOSSsYotl13AW)imbhx?Rpyzw{`tA=EUCedLNatn2SA!iut%51Ro7U{XUp=Y zl{vW(m0xz8_wT}&JqSaa+L3=I(NZ2Toz+Y7Iz& zBYc1z$rh2JNUQwIhI{1EjT7VtTW^&gZNF81y6a~77kKr5+J6Yb&nIi98m?E<-PL_8s zQ4UR%d5}X3^2f>|*kXX*B+*1f10w-ftXRoaE3m!3aFVRJcn#zQXKisfv`a)`1bC>w z0pMoefE&c-hAH@MUc+(vJ&;TUyS<(OHuJrxBtaLveRnVK#rnOK8?Cicqsk4}D;s0U z6MB0X`BYaoogN&E10Mky12|)BVFpkpw#eo1JF+TN*;pv=pZrk%uLe0UPqGXkjLJUhJ1%eP5&J$q$)p+(E3rjbJ*2t;;X83D|Eg@*=$r1{z= zX=u)saG0dvc>Cn&})l-oaIfvUq@H5bV2iTaVic`HWIYszMcsD$GfDXy9{2B zQH4W>34AMsa(W}BOC-iem@+yE64&L(W3G|!Zn{~1u;n)S;kMi4Cp&MI@9p@i%s=-B zT*28*Os)uic+2##e0cf^`Ps(L$?x`#mdEywmB$Z^aU>Wk&m0>oFPz|%%t?p@Q{|1b zH_6-Qr#cc$0tqI{97KS5@cawm?H54~X<`g%w6tJ?EXxNGuy37Z987l|BGh*zmaw|N z2e1)fNIId#*?C2;?8o3_Nnix%hR*MWBw_`~mkJg9x=mx!*e6XL<(gZtbZHe9tH0Ao z0umsKj_6{?0waRd+ zUWSwP06eV;fTlGo(7lC$2L(Dl6C*%`5d#h|jB~?kMg*?Q6iKx)2Pg*t*r^0SYXl&m zK>`jo%M3Upu+76~2%se3aD)m+0vn`}3jk@>fKGXO4}RXLb1X z6nW+JP4dRMsWSciWSMz!3i1I)g7NYm0KWjnoPf3nwiw`lAK+gKg0Qn}MZqX}|J+xk zq?xn3_{Tl~>xj})BtBDXXN^h<0$vBHrL&QM!zCzz+B>VYBP>e*8teM5Oo^8Fu5T&T zjzE?%qe%juK=%gEPT+gPZxa>i}AUtyP*qf`(R>HVIlcRKbCt zp4|qmk@;;P3|AV)eemHqgdv%*)@u0%{t*Tc_93xqXrae|o>T&Gx+0&a1>nPU{eAr7 zJxI8EkSH?@vc|8vR1Mp>hPpNx@0Zind*zNL*U2MmZkBJYzfHcg={ETR2=L8Kqvc2q zSGq`K!H>VSXJ3}@ulbz(eD_%Sm)#TOS9>PNulJ3Y-|d?qf7m|(xxfTOf${Rxk%?M1 zeeMK1&nC$$XC}*=ImidjPeLRB5pt0jlLUwc^I;2M3jzLx`IA6|iLi09g#87%SICCk z?;*MEfoE=29dD7qCY%%OZ5&MrFe~8E>Tm@GH;va0N^5&15=pKzT_BJ7-E#NaXN z{5~CA7mm~Gv!jV1_btH>&#u)Nw3>_>0b05$q^-Lg+r0L2;5VzqC{gjdl-95+Scvz+ z>v61es75MVNB{!7UIu}ap=2!#Ey=)$0An;50ttpe2r~oyum``hVMYRgf7lZsNdm+z z0+{3h;I-q)ZLXaOI3{JxOwQ2LvzjtETmw(q1S7CHLns#{V6C3O zB>@0>${J^BczKeg9WsR;o!bijL<_)-56wk3c%9?h+LJe4v|Ca@AUq-)(F53 zEgWMD8ysrFkIsArIv2;8kTLX{5zy=@jLFM;AD5e!UMCN&zE!@q_BP}Jx5+m)Pn0JQ z{uKWY2Ytb_;=TB{1BvqYR(?r-vTcm~Y{vxor=1f(f{BO(lR$zA@_P{A@dFd&Plrf? zN%9njUZ0pO&!3zqFKRw8Nv54+PB00Q$f`p#FG3FGPLR3G3ocDUZU8cn2ze9a{k$AY}k&Yfd1*Z$ZYt-QjnxnW8o8Ab1l7NHYNB~9)1v^2nwi5{tGDpIMVz*jF%`9rTxbHEP}fVo3MYl%JtB#z&a-q~A*001VRgkmO{DcEE3Erkf4WDrq zfevOhAtk4)w#YY@e;E>Kl>B(>MES?<6Xj<+C&@qUnk>KAJqaY3D8B&#et%#hqQGQG zp~;X!QxF9vfdu0b32eP09Y+KrusZZs&SV!6CL%XrsdS>uX1O$%!zRYdtg~N~1Ep`` z{Sl}MSdBtlw`WMA?ED&Nm0f4o$;qMv(mw#eCU~C1Iv5>n>^^e_&KzT&U`aASpNRlo z{#Ya!47b9$6ybeXho`3}=#2zE3Rs0D>*eVj>RO7Vcd$kQ?IlXqI2@%zN`Z%RsD%Ws z8G^K-7w7eLodvKwjQ~T)4b>P0fHAYnw~YiOfEgl(YmERP0~|B=8IgvQAV4x+1p+`@ zC#oF@*isS^{55(kYd8nclNh#L3u9G>WUBsH4}LY4J=@xrXegOjfY9C3!>6q+6 z1_^W~+5~w@muZ=x1L|Hp6&O&bM3@XBOhH7LBr{+$&rgs!7sksw=dYC2`9FtOW=Ycq zzGV|z^XvqBeM^Z9ggdoz!(qwoUA4kBN8_2rXruaUg8o5zVZPSnj!Gc~J_&&DjfIT9 zI%$QRQ3BxmHv$BW)fRentGbW?F@UVPDNhEY4e<0$IPSH2qXR$ABVw1oZZ%bg$IB6oju zt4v)!MxH+O8=R#kMiLnBlgBrHNA6huCHcm>@$&5r6A=X_g9KCM$J?i7NboOvCdn`N zviEqh{1*26gHz=XIwf1TUQd5zbCjBD}`@;OrE63uKrHdGyxV zt7Xoa`=vL`l5R>pzv+3?`6K|N03X27tqWvv7=x|m9EtVfyJ^$)c6vaH?!G2DTX72V z$!hincuRUSExtqMIIW$P+W8d+Xrn4~2y#jt=kf8I+BT9*{(ek2L5#B63!=4p1-~Nz zerNu!jc{<~^%oTUgX+DV7f+J_tx{ZnMmh#c@X0|61wYSuumku6yb^)H_r~+?K1wER z1K$dMC6ZYj_yplt2CW4m09sBTOu&;n%MySF;4ApO(etxBNw05}8-02T{y{z#)XI&V znN)?a$;uBkHXr@Z(jOCLagu4u1pILV%7$TM9fnO>3C@Y~@@TAl?c)jZ&Gi%IyBnv< z_qR+138sJqQ{<;RZh{n=3KC2O38u=wf&jnSKSh3fa1w~X$@voy4JODFM?nH4#?PLd zB+r2a9A5nrqJb^rI9aBF2(O&FM&_J-Kw5fBr8m@psKqEmN#hA%5rFe^>zmluK`(CS zsQa}{i|a{-SbRu|0nWJx=1NJ!CG87f?VjHe0ne(9Cjs{l|>+bl$Dr+vnW3gNc5)kb0 z?DXn<_(T+6x?fVnma_NKj zWAA5u3-SPfgV9JwFM=$*z88|vtL>*ekhHy}x#Adh@JNxr^riafgECi&i`TjU2I!4J3HBtO}Hlak=)J8zbM-hDIV z&`t8M`=-dR4}b`;-yfJFe}E+7$kHc{AU8NRRh~Y<+yK!4rYlvP0x^yO{AV7O&H)w} zxG*neTrDCfrG)8vN+C+0p57LnaL)flb-gCQe!_|wBLjy))Ix%AQhW=XMm-YNmJSXt zfrnHw@H(p+F;Qj*n8}^2)X_RS@M9%LR#~j(9>H(U&ssqz1~6O65}3iYtvw}DRCP>= z5mv8E0uZe9-1OiLG5|T0UvmN=u2!IX8%$R^u+y-ea+W!6t&)YUlSYGr`-{mV-k*IA z$TvIzSY3fPPcRptH9&5##W5bohf%N;R&a~}mf+HZN8(+wJ$I8F zE88ob@bs*LGy?dX85Pr24LC~Ch%q#TWLp=0VUf69^jxlFNu&FAUS^W=p;*!k8`Dpaq{G$E9Ik0 zkIA48&xql3`l!ao9YNo>RY=s$Uto2rxfNPHkQZ>9QNk)fzJ)JrEWz)^4l|qBLT}G8 zL~W3VjUYpNSG9EYH_5)zJ+e7>s|>|ja19e)62NNgT4!gSURx%eR%MCO%lDK?eS4uJ z0C0o0v%g%y9|a+X0Z5ZGbpWb)Yl4-Mr(>uB1ULyI)j3dmfP0YB$^%y64BLWp0^(?k z>(xLAvG-yWU{wlaV@FuMMgW330l+65z_n$#(&W_>gR`p$d=GwlYa@UTCs05W_#9fm z3Q4z?C9TtmiK+qLyg13=>+bN}iC!tM%E7hl)Z&i`Pu@`qFU8jqoR-KM_d5HFjw*b9{6Ca5;Ybhuk1EKyHjse>;@b89w4|02i2pAP z-xKAK5(ha#YdgnU^+|9h6{D&~M0~Fi$i)j)CuGjf>GJB<*X1G{1AjLgyVIeGDha?+ zCOtVVqLmlODPXF)Jzq({jx2in?x9*KsyPK21StehY%+yjx>|wnEeaylbrnci^I81I z^#FAc@NQci*wxBlMu2_?b_GAlf_)zA*JF4uwHkb;7Q8nIkcc-12;hytC)f${ChX%M z&|~lb3VQ1KEb#q!8hU&_I*195RY`iJxuaO0hs#8l6&r$Hjov%}Xq__(EneG@j|7;5 zqK2h!kX`ib7r>8*knBNE$-`sRwEVGKJF29ixl9_7$kw-%%bvm=vcG5-&YQ01k55mR zS;at)xnvQMfs?jLk|g94$J0E&>v{R|j8QUn{v9%I(OoiO$=x!3$(?f32X{(qUxWN< z?LW)tIoHYUNRaPZezV;3(M=%1E%MN+TjUWX!A(etZ$dPfif8~ywD}g~1U8Q7Cp&JD zpYFO9k>FPO)!tj=*ZV*Kkl-;ygFiwJ{rS)odHldw`TgFjWcKMFNJR&ihQYTN0x;qn zwqc3ANgMlLjNmCbRj^M^;s3Vb8T|k>31CDp5?F`XMaeq z^h3w*Ke<6pmF$xGu1YC~L#uAhm$ndqhlmiz@5+h_$v`4R7zJP)(^K7+Cq3*3!ggP{ zUW)3@L3UU&>h-x+a+Clxcxdd$5CKY?&PjcDF0O^vC*V^6cZy&K(7CO^C+H3Q{oE!g z0DL0>&*2)h{YscDA!u>^3^gPGBLFJ_Mqm(lZGZvs)w9`cCT;eB>7>Gi$Ao1emK&hCCl0d zt{ztmEnFiJ9OHS@tzlhigR3I&bs6BRaT^jKb381yb@@8%nfn~o`SZ2Ek}DvAM$fxb z#w@rC+W&4D0|MOk;eE34{A#)B-AOWe!A)|@lH26=4{w#bRZj#^ay%hwwS$?~Bocwy%_43A% zN9B0+Dts4=Y@FdmY1bod(C5~70WhoiD8PqGbV*Z3g>?5d;(xCOS*%Amj{lj`#t6Xg zcv$lTKCzPbVy`YiuXAVd`xzm4jE<~JBQPbFCC4}itn`H&rKzh}Dx1zrRm(*rn94_QvaQy*qAEpc*720jfcOL^Hk>cw^_~)zbs;>fzO#x2L%Q zW-4I-W~<~F==o)FIDzVMfSvVXs~LAlRU?Nd&~s&h&yu8RSs2TT^wKJQxSU6y6jh&= zC1;n&8;9PKO?m5etxI;;)HW4MZG91bz-okk4|;-v_cvhJC`g-&${$>Tj{9cJ_lg4S zBnux;THnMzhZu;|B@e&xovLFA(hq)(@YXGxO49RL{l^hqZ8h*mBF*yryAIjZ&lHc8Yajm;&} z*U$Sz@H_BYe!u-XRHCuDSif(2dL;lsPb0vU1OPAx2;z6FX)KiCI3hPB4842|UY_!S z!xea}FH$SbNNTG=gsPTH(%x4gor5*<{;8$%%#Igi?uq$w0a|`2R*z2x*+VU^l^S|> zfRRbAlQm3m843E6l~PoH9Bq~KCIR3Sz@5VXlLF9{1n}%61iZglsq|*H%cK`1v29*c zj&s~M^O1mk3j=8o0I~+(Za2IQ$C2QlgvGT~Nm-P&IF9QvYIl_{H1GAzJOMNcC<&N! zf|qr9I=Mv4l_ZE>!}1!vKTHWgvqYA&sMefcDbMVEUfwu5O`h5Pq8upPgC7(LSd8+B zo{<2?4-461;P(Vz*r(hXk}7!i7?MLuoh$)@)et*IkdQ2cgB{X4)Gkx!JR(=kx>>HD zbB8@!Yd(mu;2s&h@D4kIZka5PZk{0D**IE$wgt)V-bdwwv#&~S<575Reipc+ZpZ*i zKIgTqBx$)!Q(LJF471Ek@(xLNs2Sj|!T&pm>nL%|0q_A49PZH$d8iME*6$UOu^j*y zf|J8NP3kz{69d{B0qD?rhZ>}*y#&{G;u~rOXqfbx&Sx7=L(2n5Oejf?P_#-q`^sd~ zg|+hdwkPD7eb33Sw?2*po{yC3fGc7mYWkd7+$XduU(_n~n%04Q=zov~@~2lp?+H+% zK<~`}yG%kjXI3nsbO{XG&iu7zo*axM6-q`#780Nh1PB4(2wxe1a~sBE83PG2=NSRA zEyu#Ev{7(pL2cv62c@C4Of{?xHQ+Uj7$krtqy~C+)(mKEySuwdmYw`Sp4}T#0U+t@Zj^F- z)*ENsEmzIFO|G4Nhd!14hPeR$JVb$cAjG`eWenO03+|Swi|+*i?v-1RAm6&|X1R6w zRJmit6hwo`awn4GyH<>oyFMBt_pcr!U;lW#{9xm4^84N2l^G|Vk?kcb+})7RWzF$qxK~_E73!oO26O@j7=O9`6Y?NOLQP zst@D4?Zj)U^_u~JVcT5j{6+x34c}G^jMW~NGpXaPx7A4CdS218 zcti>AGKD3NOap{@r8jJNI&g`CV z?{ARBrxs~NXEQts>)r->wgf$XI^!}WYAh@0-!9rVo zVx@fLty|>kcW#$!X5A^*%)SSa;6AzWy?Y>u?lf5ho4DX!88!cQ8M_ejXZeHj=<07l z#{8o^zWZP0$$h_<*N!|ZbI(kZHTmz$j^efO@JFPfr&w&oW==Lo41?5R?=wl5T{5s& zXQQ?1di(^o13yWxjv^_w%>}aitty?RyqX(?|xAvGOmrERECBLD$E=tw||;PqvVC!{+Dpwg2&@7|LtLR+LD z&yc}c56%<#-P+nUN&*r@&%1L;M-UK((Iu2DM-Gte00HP(2xJ=#sjYT4xiCo*Xhgs@ zG`*JBkpyf3{v@?M!!|+hzzQ<(2jq`XR-cd1O-%~V3s1$h<6GLRG>L6!#s>}6YtJP= zi+QiGcIb5VHX+Ha0#KZ%^PXNymoy}2x)zQHzxVJ2bX2-~TXBDaiBZ54zyqJ4F$oln z^-Bmj#=3JK%iq2>QLdbRC!)aJx|H}ev+vbNaQ&RSK!m#y5pevy+vRsVpOyVJN2I8! zK-zm7Bn09P4fFpU`8Pbk|BDaVU_QdKBb`Ja{x=TWs8>&KB}-O!KyF;q+EIq@)uNec z`(Y5H3*SY(CZ)wyr?AiAA0RUB#rrAvX-XL052hptBd0npIc2+~v$saCNdV-WByM`2 z1aPyoVElZARp(?VPVY~zZ{ug!V@t`S-k$`(zIy(&?_gpHvr+20a-}<5E!7?8k;sz8on75egfQNO}7yX&SgBp#-n1m4?to$*(<%`_~$f30ehik%G7< z4S>UfBp}EQ{ItT%b{Dq0aE~tC#_N-g1Tac55P<~fGy>3ryQ&OZTWXqwFfy2#)Uj>+ zSpFCZfa|PxmLxDDu;mX_(N-vnPArfmCl*Tyz|z`YEsd@CGISV%Xvn9{7wZ)V$m#aAH!|RPLTt<>ti___zma|jN-R1kyYu(Re|}+%eD$r{<*V=9g=F|1x$52fK!UsF+Bq-~;Oh78hpt_V*K*yQ zIEcf+e|SG7b}OV5ry;UR76wT9l=n7(m}N=lP*?;22)p~>g%IhC04!IUx7Q>d#NdM@ z`1v@em(HTC0~%Q$gG3??NPV*>3BEI$0-yJAWWqMzmq~BGPVr>@zgFs73M7o2j5!#S z-K_L!#l4U~cfcuiumh`IC69sJ%N!$s_Nrz)zxB3qI~1*yVkEE$5Tv=cP#XI3Ro?VD z58ewf4nURw6)JC<2QNx_#|bHKLxif>D(@biCbJH`ErspJaa}opTOeIY02yZBhNTNL zMCcI!8~fe7&RM5g;bpDcP8Q)_MYwmRbR|kCfx18dCQYWzN0NXb_dus1QPShQ2G?TW zlO+4PAOQ&EK}eX7W%}Xiu$i*6aHj$}1A6{w?5t7nn|CJ=c)qC(;W5k(wFm%Eu9XgM zTgMfF>_N}`A);;8sPP#EI0&pysvApj4eR5SK8zOhti#d*Z_26?A2P?kbvYa5e|>JW zd}SI4Fyk(e;BNUUNO0x5w0q@mrr(6zWV&9<&))!WMctSidR(pCK(29Y4TmY5DLn`< zmEaoAqh(aI_vZgMFvKMZ320j%-~SJsiN^6v2hVTDcTlNiUY35f1d8vL??cjX6lXJ} zqyB!HTM_)(;mnN04NdTXNUFKbiVw*$6mEu$$_4oteR`erArYYU<1JEJnA?W3G zAewr9lRN}GD>?L}R>QA?t7(>^hLh6Te+mCdtu*%+NNv{zd{QK#3VwQQBSHwE<#xXm zE%86 z{nN{ROAs0O%}6x-v|V*b#5qcpe+lg&u4{mF>11+_$f8n6WYxJ1@;^T_M!xjs?Q#WV z&{vQUe;HEf3fNa>-7Wul+9cUsYL5+VM?%TDv^pf&eQtuE{|~)5pNf)Oaa>xE)VBB3 z>pGq7JvER%4e-eAa-s6DoUb^fPfO|I`a4K!`FT3J>Op{7IbU+ntu9B>npZamWG#O; zE>g_q?nf)|W0*!3|+CT-q z#b&%KM`8MrnC9(=33`?>0etIKW%;rS-(Zcj4VFm}68i|wvF6@7TmTt#2A_0>%U1h(O~$Bg2q(?;D`3O4NZLyQZF?+$W>C~{!z6!qH%>HM}uik+cT~b^L4}$SQaS<-L677;mq(?4R9M%C} zL&IGf3Ahj!>-0>{`FXn$K|+0XxEJ#V0^dy6*W~vOM?=}AQx)-AdUJla`dv4b>B!g& z_@V7NYEuWVr8s<{Rr7%MzH({oESA!?Tsc&JNQWS>lZalO+Mb@Ca>qzu9^aQQt#rvw zsd9YNwUSqJOnRb_7f3$YIn^F1gtVcQVIoVfpEZ>)0I(*lAOuNL-*-t0n|OUCK6N2L zoGa}Bay!7?j&1IDW-RFKNfLjIugR4*zK(zw8VH~*q5`ZF#A9!9S5gVU2*4i>i4eff z*;1P5Dgo%(3F;u&4df$_c{BZPT9oF)v6bX>p{5olr223ITX?3y7si-zVR+za>D*P8b?5?~tyAm}-C zqzU-}{~r>IYwZt)TOhZ(o!9qc_1%a~9mr-w7nlb$g-T`C(f8!_Bk#$f>iu{fXJGm9 zIR<{~yrS2)PAe^2rYLRjUhw{v?Kx7~#KCKos`FW@?1~pjam!KZQ;$!{1A}HKxRGW< z!kCO98F=6GwFjjod=Xg)zzP7E7G7~sv@Ga?Z5pCkk;F!98&Y`?fw z7M@%zI}12Yh;oT~oRuI0EeWu@=#cy`Pmh<+zIuy%9#P=4)9#edy>*v-{_VTvb8p`z z|I;hukU-0n^!nCwtKer(E37wJE}>+F^u#LV zVLPnH3J|n>9m!zZ1V7t;$dguGRhSXL zAE%yAf*7?<&)Z}7KP?9^;Bz)q5}vjlq=EJv=#UO*_U6t8XlHs}CdH0~`uS3Ftn!5X zuTM{uzkB&s`P^%_%V%G|Q$G9V-ST(S?v}rW{ZFqBh|pj8vV z?+D<=<=faC5&#kl1Q-B8Dq7D-WA8a^muXF2b$+T;b#`IPF=>xoPIu3ANk~RRHW<)Rov=iVN0s!R699)y5fG7BQzCDFw*l)wOni2u~AcC4F z7NUR>AXPvD^nw6PN)6n;H6zfoB=Ba}kQ4y|;NMpO14tfFR;P#2JLAV>8&3{MNp%6D z0X%nx05o`Ux6jYR`hAbS(Y2R%V(THjg{h1>WETPl(4mLdX} zgffeRgj^?-r9mSBjk!f(bH4oTb2rQ1ym*Uz_LV#2v#$aCuiYts^ZH%#H*ef2|J!S~ z$TjagBq7MTP%k^IAPb=3jd=E0f6AZcwn}Jy$RC#n@pv!-FiGwT@k#3S`rcXZ@2&^X zS+5@qLpC-s?_r{j?;pnKp*hB2yHVP!!{xnLTenf=!;UdYe-;Ynq&MJ8H zB=O)<9Twfb~mnpb_cjyVIIT=Y-OlOYOp0@lc2QNX~@YY4KsX8vgO;4z#h5eR;^9=tJly=Ld-`NIcE z!h6J}xBvfH-g`&Onw{mHZ8Kh;KW3IKV9a9KUWS?F1V&^e%LpMMVGKee2^fqK9_+Ek zumBq*Ss)Y|MH?i9)ZOZ&j_OvY+uir}%{ixY&OJHjoKKyct4_W1{N8u(sye4z;PE&x zGqvus_E%qh^;Ok9&+~@8-@W&=jZTq-61X6M@B;ZWHn&%;f2iGNW$j}SOkOVE!>`%0 zk%=DvoOnGxcXDaOzT@t<+Uq{{M*Fruc#8z^Hv6_u%lbyNQQ zLQFi)`(nGJ*@a2v8f~_|LD!yg1s7-y(s$zXhsU}+q>6iNi-0TkhR?s({)dnJusndQ z|EJ$(f9*3s$Dff16b^mU$G+eG_49Y@`m)|diS~vBx8Plu6C&%C&a+nU8_?lh_|EaU zv3f(0o9Ak2cchp5d&krTb19NrOGksY^Z?n0gEIb0B>(RR=Np`!fYxbfIE?v_xuQf?1Rq`VW zMtkdqPr8N2l0)vW-hv1}DGMJYzwShza9mHgDqIn+Iq4z%gxZNcy~eTL$Mr4@d4fsP zXNde&0W@Y0pm^NLUloCeG6(^(WJ``1k^rJEC#+iaI{whK8R@x`KU?Vdqe5Qxri#yo z{P7ysLE7;txn0HB+#`}>CAYdJ-v_I|ySrw?BVAV9C9EmyANN(rGtuk!L=Zu~5a51r z9wBYp&}gSk==1no?(<8J{TutsAO3!M09k)|fWL+aKK<6fXWptX=uP%F|KO)=#=k#1 zpAfQyRi_K`RMU%gz3+leD+i!(4NJeIkZD~a*_Obl(ML2hitXe7U`PKWYqq1`x#=5R zSC=wjlFzx;Blh*H4^(<~+~Sv6eIX~dOhl(nhww%TZ)w*0#u{w17@-dHaake3Yk72S z=hzJ!FEsJXo%m6Q1NoJ>A^?GwIG)*6OT`{*9lc z-35S^PXdq_7^UrBxGr1~;ZlFSV*DKXtSW5kp1)=-vnS+_>J!luVV(Gy#3FPNT=RBZ zgwNz3S38afMEdbPolmWU_qrf-A9;ZZ2>>CG8z9K(y*q`wL|hkuTX!dVkUipfkvCgk zCt;A^1yFO05TZ7RYZEHOi;yG8&oQLjvMq%u-M%d*0>H{wJpt#&Cgt8mm_3qTPk^~c zB)K`{;n6(YU|+fPupMeTY@I{;PhX;MpX4}yHZ9*Nz6a0ACvr}~Yg|7$O&HVX_xz(X z57;+-@b&gT-7Vt(!Q1UyKlu~(EuVU;@D}?Pg+t&jz5a!>kNfplZMrTI0=uG*PsOBs z&aH3Fdc`M$W|OnMHZj}d6VRvU`+X0!vyIQ%^z4X@E7={JVpdzXzDKvu%Ux7Bv$-?l zMrLH9)kkuv)ghh_i=TUSu<()CB7$vMer8||PSi;-GkPADe|cxvI!ABVw55(1#-1`%-F=wu&W zy=A@gmn8;-L3d^300^K^>afebFWGoeWQM2^R$)z${+1Bd9>P~~)y714NAZe{mM-fU zT(!|XUc0PwdW~ZjL>7PqB@uY|BoWZns!$67tVsZ=P%K++x7r|x?8Rp3weSp?@SIaC zaa5CGtbHb1B7GuXCNKh^$7Oh&9u5-G-HHH$*!q(xdBvLM&?l^Z768?hMH%Z|d*sH~ z>{I7IZ@+)?Gj^u?g3jyCmFu(UG9WznBR}CcfjGx^C@fCd^5VE1Z$4xH{qKCgeapvx z+`jehpRm{cfpUTh-eP}M_)8!C5&Qd}d#?ye2&Cj*GKMkoqApQ3gM2(pRIi$Mrbprz^v5Y$f|Ex zzdd&S9{a7Ae$#&I@bB1{FMrXN%k3W8cvqxIAN}_nH#p(PHtMW<_L6?V9h;Nool_z@ zUvAX@ijwE43)V1kOy9I#B9K>d>z)Z8trM9x6Tb-AFNx$rQHgr-f z(7nrohe(kd%qtn5EY=}_bqOH0sjmuvKNdu(C5=esWK4udAXsHT7v#+$Otn`gy|;qe z*LRGyY1+HlNL$xB`Qvqtso>-@Ll{{+IoT!TVLz*~J#*_hyZhvSu#cVioHdTI@NOx6 z4#?Ldy7XK8yV4%?1R(+n;<^W0>~DSaC+vUx$d5_{BK;@cW)xj_G+X~0z9K4UD=|Bi zh}l{l-)hx}J!)08W^GDswFN<`Ra>bVQPtAgBh*$^ql#LwH?@TnL55#`=iGDux#ymH z?tAWdKhOIN^w_@9qQsH0!oJ;+@huUZmnpxQK0O}1W-S5jr;b~KtJ zWfYKehMaWx=^3+{aGNNWnje0s%7M*uhp#?g-(5N+pIjFF_;kr}fuVza?;3rIW_AmH zpLNQjjS_NH#%H)LpuhKEvEsoBogfgomx(^Ra5Re{F_=#lUiGo)r8K8HhiV$*NL_JI7ZBIP$8-Z_yd8r|+5Vpuc;b#Jg?ltv7%bYQa8Na3 zc;d0Ev7~3OtwFXU}R|617rI_@S07l{eNJlpOeQT z3HH1mSt;^idx-5ef7lk`0c1gaQ8L8@lsqQh_b_f% zEkT5dY6*8?v?S#3cjCQ4mWsS~4dX8UF@IQX&tv#$EzNZqoUCrqoXJ9Xl3%EnFq zb(&r^EgPwD8n16z2E`bdo#L;Sy&&YUKI@MsGeq$&6PWKs`%3*>{fMB7Ry8YM?O`_1 zII{n)W(L{rjGjoIHE(a&42;3hoxs4Jbb6IJ=ZXOz=k*x|hc#0YMyLR#{AY2I6;F7GhanRv9J-peh`}@(|_pxsr zMK?l)OeYksUa^Vev$f&*u*nbo@q4CNwf#R$xrfd7>h~!%p5EDsLd@mM#F}IyFUOJ4 z4On3KJeilo#k63KEVn;q|Ha6`+@km4&pk0EDo(#;cK6gD>2yZ!ogZ7vO35;DnTKg52B@}hA(ysEeZ|k9?CF#6zFMyZi=G?i)~Im z_NC_KT@9(2SU_0hrC(+rd?nnXF@BxNJ@g5C;O;CJ2VwoNEB2pstL{vK z=KJlV9;PiT5%o674ojHofjB@@b;7d#sN;frrte6Lo~OlDU%oo02Hoe`LKb`H`0Iyp zdnWPgFB1DYmA#IdFCa|6=&%}et`5Cow=E8e)2UPq zqGTHPT4Lv(g7-`2K(Ot}KlW{GnvLMTy5bp&qV)<2JJ>0yj9-h2 zBo@A780j-BMTNFwX(J|Ww~;7ld;gFX-6LY_v3{A$Lraa0ti0y~;kIPe0Pb+=_9VSM zOcheQK5}s_!}Nt2e#jS!(opSYfdlhP(V?OEMFW$zUxm zY9eUC>O)Du*-iO;!YM-;b8Px(B9HNm{yhOTM_q)LVA?H#%2Rk(X8n2b7$K{JDC{jJIazF9~Qme;mBc*q-kv^e|M`bHkzF1YXWYm zqaAwt(qp18ktdXFwR8C5K{^r#Q`@PjA^08Ge(tDJK*jin_&fCvG;Yt_@$Me*iM8LN zNYhffEZFNq7_b{-ew5n}Ge2^*Rr@7YM7HPS!VU*5Yw~(o8fk9s$Q2^uE-chQ^rmvj znu7HwyI&0Ny9Z3pI)v(HOb%s~uQJiFa)@_;B!4pF8@IWPDD=<9zfv3M0>id1gOUfi z)g=NVZdDVmZp%f@DDNj)cnA)2vzx(Fuan~i5$*xa^NZoKHIBg;aQ+ONqAtDQ2DmvfKpldM@oA$`uJ{6I!{R!`lT^5qM1EmZ4KKw zSfQ~r`C!HsPuldDm~JI#A0;x*IkH-!al#_%uAbivG2ulEvd@)TaFoOM=0T-_27#)W zW;#T^qh=vDRGFGE7k6bQa|t^~7nJff%I*A~l9MV2tDm{Z8vM!fv)(%CqMd;0TcnOM z($4LP3sGJ z8Wr=77#ZRV)WKf?wj6Q1(dbeQ^VIE+4SGS~_FERY>5_j&MuhbLc({FJ*2uUj$i_V0 zF{SZI*hb&CHiIoHW51@F>|-Nc)N#jCHbsFi8u}78RT`;mg&ygA5vAt1?ah7mXD(oV zaFTWYpCsvLH#KTD@BY`b1mP6EEldZWNQroBTH8bNjUl=`xN34N@1Om->)6YVl$k$y z9fG@JON{U~x%0`!?`mD7Yf&%SfQ~8lBSjyWdLe6pV|Djd=lr4&L8gN>rSgq$=-uxn z@``e6*ePj3*xSR1mbLFzSUiQGKb@p-pncTEh#ra`$SXl-RTOE=h|Sjo1@HY!6@h-k z7pJ}uPK}0C9>XKgYX)hxqfWEm2x(j*$FJOt4fA46;Z$bK$|S#g$)L>BtTylhA)KwfCe*&Lk-|h+Pq! zlxyPD=wnR60CsZW$;S&iqb&71nv^s@e8b@6YTl1itX-GwW9WRZ)#g#Mh!hkn&PF2e zE4&-~uy!quf-3b|sO`s#f*;S|L3@Sw*esPXLKfry1sSz%MTEYHXAgb&?!YW(I(C~Q z%fRl33>Z3=IkJ1<%E4^Vw)Yy+W&Q*|?}`k-4^9#1NqQtJ>JRhnT-LV1bN4DH15Vz@ z#2kve5DLH0mxN^{I4(I>t58)5wh>6=bvnp{UE9eUyqfA4p0ZD&j#L3apj7{4Fi_cw$7eYqw;j8*P9%wG7{m2Ml^xe&3$>L=@Nj&nJO8Gfw@~ zLd`?lB43_0?L@Z{+)3f2IR`X8Y0lkIJ#P$q?3Ws5~;Yh&!!c004`f%mU@yO091Vgz5`bKOy%9aWp{;akTdmYz-sm5Lq z%J`}2$L|vpvAiiyKZftMdyFrd;V)mML@G2X-~$HyAM+oClT9d<7Ic-pC6&D)X{`J=r=dNEYrP-53R(I{=W#yUC?i*LdJ{;aY7bR0owH#Pj zDPp^`z^fQr%AzR9D1K?Wg6OYjId*G=6{d+Yp;pCLlOyGIcntys=zePg(BQ|B80h5e z(#SjpGPr+So;r1yjjIWmoN^pA+gdS2joY0ywNU3U8TZZtNhyq>bFEg0<5px~rUgc! zKxZv3=29oCw(h#Xuu7=N%?vsAD=+C`p~P>8J9(Oay!O9g{BJ!0!&%!&{tfz#BDRVg zT9=mnOe-)Y`vAh<xkZ<1g&QM=QhvE#e^nLWia_gVZ2K+| zb!IC+Aqu0(zkeeJJjT7lJvBOuFnO(kJv(?Buh$)YY_o4)V@A12d`zh0QsLbRBFX&JDelG-tziKd64!Yvgqw6j`bOg2eOLUtqJ_;KY4<8$E+) z?wM;s>GYI6lKcAaa*Q&5Qhvt`GnKS5x5XU6Wfbe?B{QmsZNzWt)#mEYcBBx?Vn+v)&uLBqBGp)6)GuPuqp9 zT_HHO2hpQx8OFw_N_#$@3^V;C-}!g20teV;%;;1-^n}srH$8QpuI2QX9hlpMD4wxY zftf%tvyY*Q?<02=0l0AfNc^4Z;z#yagKiz&ZLVis$&zvGPjigabeC17TITXu zw%M{bjjuAeZy3*1GW|T_D802gT^}$~+93|a5c2nJ<<*ox>yz@E>%N*$S&;*BVZIS@{g9{)oncW5 z%%jB?O>+yS8=3-d=v5&cD@peCHz2X!mygX}j;G4SkeyRsk@KdAxYn-2MTUstAW@w- zKmGl{rk!t)^?~2S;!vZ59_z!8Uwyn$y&0;Z=Q<(T?JEbx{hA!!|3;2h$|@!q6Dj2( zD+eO>2@1}XR?d>SrlbSLUe;=E00PGWn^)a4xZA5~SljbIY>HYdQoW~{%D_`tB-B>z zoZq7t#swwwBB)wDZs8TF?$>`zLak65_QWXrv_nVvVAY%Tv(H2B7$3H_UK%BYOF(bm1M_gMm{s1cJ6b)T}&>ZmIrc-Onu z2DjQC%5QtAmLfI9D{vC6wxqmNh$UNxKoeEUz z)+p2FEij)e&>+&aT#V9=QCjC#-_8O1-d>|sKju*Y9Zdcuo#|isW>Dv^*(5BuD}qnZ z=fVwnWvsl)gSm0Tbf#R`TErj4QS(f8ynC(f^#R8H2FaI6agR1|>5uIGa*V&e`HgSn z=Dqq1HwT9y-bO-Y$nmkp_HGTPmX(JpU&2>9;tPqbih!fB5j77(gWR71%y0Jqajoh` z88>=?{s)6R&&1KnZ0$y|Q^BTG!3bB%3m9NtswZBzKGJ>gqO@7DM_^dib{cLKs%n(C z5j|92k*wjR5V4-u?8-8otdIfyz2&H`t6#oSh+?Pp7waV=|Cvp=VFk+cCrMVPzN8&P zzDhLZSH*N@1vTZ6yWD*K7pJMSrdVqHb{b+gQj_Ox7L@XsC;mW>1}HWEUWn^K|2T(} znvN|ma&;m$i|;UqOcyVNkOe>LQFTt?)P*@FtP(jC);1W+w}OWS35B(?t)M4|TC~Lo z_*TKCHu2$CKO!RGQbQ6+i%XL#5K|rI+*FAAwyPSTlReCL$=0Dm10O>?KbHUSadK}@ z!%PI+;?OMb6}dII_2`{6yKGzL+9GH1z6ZXX`#Vf+XNw=ihdq~9Be?RI5hi7 zCM7DgVO46vaUgi=;@uW&LzZP}=~O!Yu_M@o@y_omcjO;E+)lM8guF0!;0Y=&Ihna*%+WATEGp!#S zK(N{&X{>L=ukVXLde+6Lg?e*JUzkJ&Yk zT9aZg)+`f8TY%d~*`$8-EMAw{`cA!#xevxKDi#`?lN) z1Nub`Jn0n4&lKSNZyxMR2@|3Lf@-=}Pn)+2El3mB68bRJsCb#n3OCoAG*%>=dQ&9r zEHW`kW`h)eS%afQqj*Hf-j*0Q(E_zx!Ar&-=!L>8jgWF693Mg85*Y3gK9&h6*0V6f zct-Y_C`p(!K=1e5(;FT&pc3kYQiroiig??ABi*)$tGdLCJF&06jg5!d2K+n-7knL} z3_1zH5|{3m-eaGT4Kh0nsTPSD@6pfM!}7MPB{M)*SoJI2{eIpdb0#5KgNc%lVg_@}6UU{CTmzbQiGn9lWK*DbQx<;qZQv;b6id1bGI7lS2nr^p zP3)coGZXwfL zrhyrdFUI0lcqWhT(w@p-t*X-tQu>|&HfH623KNB$_q)eH%{25TQ!RAPzklEm=`!om zEjM$S#ycrNt{Wj!r!Tc5MrFQol}HWueI>?z3qg2F+4~RkRVC*Bz-a~4{44086r{B_ zX_5+J1{*SkpdI--=9^?V;vc*HBy)rHvi*e)vGWMx2qqSPf1RNkNbaFb%Nfcu@KC9LRt*ufKq*IWUlV4Jn#YgG;Q#L=?5xE{lN&&S?7Wojz)|>X;+Tt1=x0kC*zhz22 z>0yNeotY5GEBlj;zpB9RvTNF)eDga;-}@y>N~#Vt+exQHCip3-_kz0dRkdw(iqwG? z_`J^$X@p;F-rCI$dRCiIHka)47U=6Bu^|-ppNZZI__Xz0-!wCI#_Q)vy6F9VH9{rX z`I^C_ziE$W#8gdIgczoccU%WYa802)HiM>HMgN6gLVnlg6a=LX(nSfo@3ZgI;aW88 zcVR>HuyOz5oj=|SAkSfT!XMt$`<6c8aF8-02A>?zxT*_z#iIposbvmtj2B6#fA+^Q zkLNAmCz|gc5e@me5XkgRq$l!suP2m!>#Dq}+#tS8$O{oD+`^9JQ(4dnoubEk?a3*K z$tjtxWY=AcQhCGL*;Co9s0O}_c*zwhYV>s*4$%~qZwS`~K`I$0mJ%4NwZrm?j9 z-_{{H{A`q^J6+=z0)&gZizOkjW4LkEc=?5#>balhb>F6G!_x7x7sqdWb#kiq4_%mj zcLV6A&vzD;R0kWbI8H*1tiNr%dUlPas1$XQL%YaI;Nyn@;fm$BgM!we{kbaRvI7m2zHX!f7OY^N z&Dk~u+Z+loK6@* zo{jWuc zm*k*pmLW-$9+2XBrniITLZt7+ZS=L_3Xjr<-~=NR+Yp`2U}bO$yC;vlVvYt}h#!2@ zmZfj`L0c+U!7l{-{8B>?9$^y0Rk5HvQ4^k}N9ye3%LGEl^BO3T34<3W)?JfyrH;&P zRC--u!^@Y8PZ(>03tDgSLlM?i5}(}%COXV2`YWna4!)Vw#uL(trg!28st*>bH|E%W z{PwF)D8o*)E5Gv9ovq9J@<4XTuSda~JaSZ(hN;RhH8L_nSh>=EliKluC9E67^1e+K zG8_$AsPpAqxs@?+y{-bLSa#@75CBCfug0I=tuOuLIVX8h!R}R4QH%D_kDR#G?oAA| zAt`R9La@MYgCCtt3%@wWW)4;i=Ii8+&D|dIvmch-$M`P^0D1nu#AunY*tK!CXAtJB z2Y!j`cV9B6ItMa`?0y6PY4BbF&E`iRWRCF_W_V)Ze8n5rUKa2PevxBXv*5`p#1^;A z)6q4@&0xn{asSAdW5Qw&vmd_6c6<4nb78&rE0(N8hNN`dfr4}gYI0#Lk0s(~zLY9; z7F`rY(a9;7j()ym<+cKt*VRh97nvtceZg_7(bA!xT&k^88<;i9~-n$B$bH1P(Wa z%OTFjol=Of+T!%IGiNnmpZps2x?G3`Q@|^rcWNZXI%6l0e{ru5`6R4{>zi%rKPOIN zQ`UTT->k%92S4cLkN4`M6=t(w%f^~gu!EpX;aGA0q$zuftTvR&;3B2qu~Uoi%GP9^ z`YydD2noBNYEo^yaQaQsA_+?NZPXItfVCMa3+TLm1R1RgISvdA%dJ?KDnZ@^zf#O{ zP5!TEL#v8--hnl__q|$JG_?1qW#DAWWh6wbLGdIE0J`svw+a*FVF(#I(WyTsn8PHW zEtK^t%-J=Sdx8&Cb$@qws<8Y!z4ZnRSV2P1H2|7HN9+UYSJG+S&?it?v*(1gJk^zM_Q`ezI(~)c|6zj z@YC%4h?Su|M}w*!o*=k!Sgt<>zstp|Ux^&;T~UVzKuSGAui1N~^qQ-9iXa0OcN)xY ztq{lRluiMnzD?p%h-!OkOm0X*T{V~X>$O{9;$iCyk8j?VEnM+4o4+fkSUM5L2kpe2NFPb|E(9f60g-= zjmL9oY~wc6(yJqq2-4N~oc?5EDcV6Bos#1gRs8qOLtW>f+fyx3$1E3536}vEEJ{D* zyMoX9@)1@xY5I*E(2Gx(lJHq>-Pe1f{kNn^f^c0tOLzlL`1dOcT zjh-ZqvBVKE*gBBB=o;IJ!|K>k20zJ5M1I9_?@JP$Xkgf-bOiq=ZVfIgaO6R*iB}Sy zPLR{4imaU7Gv7)o1;dBqw+&2xQ{ zpAYZ9Wvs-9r_E~wZqUU!bZZDZ{4655IeqSz^vkVm%qwZ_hsAp2wXEI>{X~pzrPkJG zu!lZ^f5y)5`A^G=Il-* zEZpR6qp$MT%hqt025vTEbuiT*4#lJmjo4h3PU8SoDzyE=mYIS}g13kepbRf&+}m1^I6CIB$#jx=*BUB7aV{FA!P| zW!xkqxYNOy1*}y%;5T2`_KkOx{wWjF5K*XAkF0}x6D%pG)++sIOTu}cWVSNcqsquQ zpE<~1X%@+tkH}Ojvvv>kG&x;X9DSLKRPY55v`u_k}9q$#3lA7sPMP(lQpWF1%$XDju z5w|P-`rbPZn2wBUZCNN(Oq^v@vu5z90^_D!s)HU=6Gy&$kdF~*&=`k@qCQ)xW5`*G zZ_{0B>JmCian-x}xQLBo*P7x|8hTzX_V~f>FFebhWNp;Gi21&F()Ds~-B*&#Ga)>@po;d*0S2q2T6FQ)=g+9aoVWa! zC=NH4Ym@8PIURI3>>M-aQiNC8T$S%hy?i*qz#RTkk}n#m_77Qek36tXOQb^1$9 z;>kZFWYEOa-^9tz0pE(lqz7l}vcjpz(6!L5WD#r0_HT1&@6F=e4Rlu!PERbL$v*u$ z@y@$X0d4y0x*zmSsK)k(;mX&*aG&cN7wDSP!yu{(Ju#n*;(cEqdnF!Q?zjFaxY^y? z#!sNU51GC`wN%l3@0U{2mgP%Xz$9V{=Gkg?#3Gw>%VB(Ex8HZY~(}XxLX<5AKsI;mxcde*<&MlLS%5T8E&9d>vTJ9#h@w!=|%^e`rbDec|tY9D)b z!K=uHkF5yVLdRGFbx1wxN&0^yv#}7{GTu6l1o^sZ#vWgVTYoq~#r2DCC)7t{^)G>+ z0sIm0whkVw>y7Wtb0@P#E?49~Rz~-V?VQ?w78zdt;q!ByheQ9~h}*+_ zYlf*LQw?vpBwH0oqbq)5otQuCnBK;R?zT0lCVPyBImTZw8C3+_w^#zSzFg0?(xBb;-K`!}unyY8C!11(9L z#17sMFX)43uZ;g-`A_zw1mRRx3q{88<|*+anDQOW#|!IBF?41JlOacO3dEDSCVVSN zYVM~>A(9OQojhV%z(Pm~$rWEL%{leulIo7~Y8iKzl{M%2<2$COP(lRMF}p>c5CfaS zl-QC6fu$fPi%N<;2_?m%=3P93_GXWcy3vz!rxQpl-!~#=BK|P?&5MkNl}Sg`+p1G> zw~pJwP|VmA$lyq^O@ZE0C-B+cs_tnweqz(Fv_;qzMF2meW$ilS)y0&8>k6qo+G@w1e%qEh}I zzAA+ zjRB-84{I$^H|Uf)|8aeI+AT57Q{sCI4$K-7-Of!nO7rzipBJNg%!W`MM3703zjT&+ zJnfLV>=MgLptXEXfeChIbA}`{1uIgJn>os@(U{A6waMP|+CqyAnW--c;4ukBz9u&$ z@Z`UsQE3$}ojzBu9@;g^4$V zVXGJ45UOpnQ!SaoK@DSNUR&3i>h2pjV^`c((q9Mui7jw=>f-73kWj1$(X&*(v*gIbIPkph3>> zW0`NCTx{2RkMRysD3_O_@$;SW=BN+}0*ZC6>8R!U0j#BxOna&2K|M`X-3=4pKUGRQ z;DZjVCRvet?xfgm^v^l+I+8}A_|>$%VJtkl1G4(F$F=r-y4z!cr1PyAeplvq1m^O*WGEWxdqesg5&BMyb0Sj~}XX_0Q7 zvmI|rG95o~VEWMiBdd}&XtY&(ZB}Bn#eg`^%nDD}3i4USltkH)q7+hA(%PIurq^5xgVjOzC6??cNqtj>Pd-bJjrgNNE*N2y|62-y@Hu_t zv#OGoXptN$i(O?UXz1&+0sL{%cfOR|VOqF|mYu{c>|W8_cjHm4eQbc1rnH)@R!+#cuHqDD}TU*T&s z+~w_}R!z6Ayr@b1&Xp~BzL$Na0f-OMDBXMw`KrzJi$0*<(c~=57Omkb`=DvcrA%eq z%{lh%P&%}UQ4t6LxXKWW>-DV-+0x%)fW}n7B(vtJ5wassEv3!-^z-bx%=3@iKUT$V zRz6lqMm}1mo0(#~5dqzBe{r14`%T2fUQfGN(ZQWSbcJ5hH>yKVpon9v~ zTAkAT{V~U!O>&g#&>_yB6i1HDNFZp14vx+=e*8J&HviX~HHRWqg5}7S75{YAm$1Zt z=b!(~=YnsP?aX(d_B|+C7XDx`cvTD$@b&eANidd5)^eGE;tZR(dK_`RXATtn(M7RUp9_#E-m&<#<}z=L za(iYq-LkolRs&;s!=8oN-|yig<*FHrys|FIu8I4w<>r&;1^RUzl7e&9U5ge*m7M1i!p zeEcXIv#+>it;-LMozxh#<7x2)k6Yi=uK#nub=P`-9Gpt|E>eoxDh{%ss3h^G&B*lHK9+uBoUJ~%;RgWO+Fe&EcgNu7+t-IDGTaVFb-~2M?L!tl+ z2xd>G`a&gLrW06P$KK*dxPv}&NUf}CDJvxPeiRtzZjGO8m$tg9qSpXSXE;iEjTVv@yH`V zrO3_au&!|YMrzIIw-|lX8Wa$vV1ix>#=<-JH%VK~A~BaO>ot-l-?j?)g_uNIJov=3 zkeZNbdMzRFo6ZxN)tXx2o#q&{!F*1oh>_mjJH92_%)15RU*PgDCK(k7L0uFgu;XsV z95zQ^;iZ0Kgb#3^+*1oW{g*}q0R_uDw-GG@DbI{ZiA(;fK=@T~&&)#SxmdFDY}ayP z5UiDs;DIb3|IQv%L7<-%VX;!sqSr<&%)hGhzw1w@yP0c`bmf?RibGDbUa*GET14bA-e;R@Tf2?wvqr8q)spnykifv}$aTsG4W~f=_Ni_U)A`@m z@La1o8zJ&{e5JoVsvzlzif>j$*=AyOpBWUOaL?n=(W=98p`fz3^si%UpWh zHz?x0?~J{Gd3~ANZq6*PjO{)94b!0y-itXN;@zr;RksD2i>?Lgl~)v>Qzf)(=?7%} zr&lmt_N}dtbf89+PiaX{oxT?2UrB{1;xLl^4_)~{PD0zTlMNJcj+Z+uzq2mzmA!F> z)uL{VGM8fNbTRa^Qukp5y9L7O31O=En3DJh{`j7&{9o0Id5%T)eGd;g8(%pMV0z(} z#aVIB*}J`Oa@s3Ez{i1MmPnQPw@z(mALQ~)(OFm6x*#5IO_==4^D&)4RzV(6nP@4K zvu!%(ia}!EU?8R^=}@Gu;=E7ztIok5CeK#M8*=<;^<=(bV+O?~ZT@V0Pj!(A+T)=S zhBt4b-=XF|vB8`6@&f%hoPS^A>rFSOuew~V-IFM_?+>6 z>Otl!7p}PZd>ja3F{W|io5zxJDjOH=aJPMbon}b&4T~M!i&E~AR{6bM@ILKs09?V4 zP3^P*VpY>@oi3HO(6xR2`4hEbc8lz55#~XxzA;25RB?&H%RWej=Dgk)x(ZoHR5d{J zf>^v*$UT(JlWeIZVZKnn|Jc@3L!XEMKzBw1Y8hUrgrPnMz*b>kFQpPtKx%)KNHmgED zpd$D}hZ2oQ0j_!xd*Z9`;RdMXHP9@?7y}gr!*7XRe=hXRf{8yvs%Tt9sQKVb8we|l zb2j#$=4_a>eoxB>&b^gwxm)!ii))Lw$<9sS5O+Ihy0K_?;!kjiZmHko%yW!H?`ego zAObld5ImKQWo;!l9pzdD)U;5k{*%O+GdM zn)(1m2fn#;S0OM0rQ_`QSld;Vn`u8?A#jGvU)}X3cKY1fbg71N{y~Go@$0*+R`nSv zSJ@us&aNZvJUBMmh~F_-zM%EBDB{!>o^+YCUqNn;37MMuTw%7{DbpknF`k6+`cF5v z=IDe4*Cej&VX>cO*~-EaTcwFdEmhy=?Bs^f%syMi14`fUh}{OCWWD2E$sC)P+3m!8 z5k_uj$z@#I*IG-SDr*%#^-KK65DWh*f$<$Sw8rfU^*=Cv@|zVBGIQ!k2OTN8WXZ1& zz0`p$u!ACs#~aS!=LBDK$L@ncwvg5uAj zdb4|Zr&nWlD=5$nOgD>a$7L>A=Ebjha6*L6FfyUDIb%L8%JY%jWg0Xz%_GUr3zJ`? z`|?*Lh7VMXU<%^_T#|rFswueD^qN3xESYmJN2ZkxcxCBMdArSQu;N$wCn3E2d?~rI zn<&_J0QPGB{7=U#XMU%y<0xz_ArH%UOk*7rk;mS&)fY8l@88jBMf3FB!K!~OdCkqa zX-L@rD(BWb?7N-3-n@T&xxSS{LY$tTdQyr-51!@8iC&Cx@Gx`9-J9XLzWaAhEP(6e z-V!mAS#~uQl0Pni9AVEYWsBL>l>@XS5$*Kw2q1&6irCc86F< zh^?2Ya2XHRhMgZ5yHW6PoWFPKfiP>b>Z9zvI+Tn`zM8Vrkav*zd;g6f@Ojni2fe+e znisrY)e2VDDxE?VnPVXi&%h=xe-e04ttqhYA9v{ju1SR@N03uQHICUkJygR!IUX1X z3|;ZV1gaFAV5k$*(+Wx4p4ymF8dv6UAhI8E->KhauAcQ|D)@rixxo$ohvSXw9RyUs zav-qM)v>n90#mF$ZI2y%DRS!^RFjrwzmWYz1h&1+9%icKn@I$1I@L8~rUo3fvt zUiVA;SrHJverV$41a;Zzw6l zHOxNH5lS2!9K_oOacqlrp^`OqmbZoTy4T$#;AnTu+qW7(?O@zqM9;in$$Tx18+C)q z1yNB3XquqHGVDUu0iqFT5ICO-JNxaqZ+sfvX!!Wox7%g52v@RlQ>-KR#>vR7vb1YktY1M81H}OGRgB57z^8x>MFl*%?YM<8?}VnA znN(Nm4o=H;brS|rzx5%i&3wFJ1{V4+tH{Z1qJ4vfoHTh%IL%ln5ylzlXHRko$_$~fu z_BK%%$BV#gtZ)P>^ZD*irg%L6o5U8rd948#*skh?A*bRG;YC*7g%U&{o6^G|dPHlQ z)jo1g9dxPA&ZzC*!3Wi?vtx~h=EGc+gYd7LhTB(DGVN02(&ia}4OX@`E=GJ>+Dx#5 zIa-sZIi$0MKY!^^VkLyk@4FKTU1gOyphKWOseu&-4IwKaPlDKyl*aLofj3uo8TJ5^ zvv=zqU;D+a20(Qsb8N(#NG3IpQOeDtd!^_AAjay#D@94K&-WOY7%Qof$iulZnJ;o- zv0DZ|uZv01t>}I-O}z#N}D065VCP=PvU{#>c(gqRs?RZ7CboSc^GdH z1vGmgbsyVwz0JJT1QdAiLAUCY7d{8fGnUEi3WJq7*dm*i4wmc*>;sY9F-j7p0i6f@ zVORLq<&+i0dAS0~w3;(Q>ki-VZrIUlfrosrV8)I7s@(tckSAS>#hnp(KM{@?f3OG^ zbl`r_0|5KHma5VdqagIl?ASal^qx$&vP0xMqRl>CVbY5@TE~ajWhJnWN3TYc5zWzx z{9%x2=XM!`#HIRYXK7`~zU=$ehZVXJLa$>;b_wXn%BKUM;@1idM3D?B+8Iq!w%Q-e z%Z~+5bnhbdsG1Sp3CNYA#5S|FdBoDa*yGES^pBRHpMIO5MS{$#1&!RgrbglpT3wkC z`D-FXs_H!YDPZsy3ZlK#3{G}Krd-L)mH8ar{?k!%@BC!G%7A8{6KDp0)=)TWz%~(n55m&ph@A)O(P(Eex z4mgu_XQHTJ5V@W`_iTjs`KgsZY$f0!TGg5yi?Ej&&E_xC&u4rnF; z6J88k6&ZH(ki_tc|BlrlPab@#j9#iUDHqJ(OS=4Qb`Fo}d%tlKYx<1$^rl)$$6e+z zx$mEl7Ts)$+H3$$L0ld_H`vWIN0chrMigH#?O^%#cHG4m;SPbnq#tCTxhj_mh7}ag z2WfVO-cLb3(hy4mHY4eRLj3ZY-#L*nRH)Wjm!+w2EiC|P$&h?h#5LeRH)_d_!&H9G zVZz6(NuKA{dmX8s@D6=K2yTt4LN#nb0f{xQ&=wf!Im$MoAH%ga%2delpY z{T3LL(64t}>`f6)wu&qP$FB|tRW-nIEwRM-+G;{|$AzGNl>G`r%n&_WIwEWQV+2lu) znNuzBqkcwbNGm>2!5?~p-KKk4t=+@UpF$)*QAR9=32f&8B2;t6z2^6Sqd4yQ*Z3e*k=z%6@@_ZbC#%x>%W);f6HZmm5cd{ubZZmlM8 zEZklfHXf1~(y?$$;<%-@srb0Uw6EE<<@?{B==-1NA+r8jh=6b=6Sx5$cD0lEeH|FfJvNC=B zDeQ^L&X+;}0+U!;5P%~D5TOuW00BVuu=?KhsgTH#@`0+=r;IuB074;;_Z7}$%kI{F z?a?6pDtOp#3O80Bv>VG0+I8W^@qTB%ijEm?9Aj%w)02CGt7B7hGSpZ1{ zZv7!YRi5maNQLJyg*!);>|*^zeiB~#{e>uGlNXQ}+H}5k`$dsm0+98uAPFE=TaYJE zXcHocJixm>6&fLkJKK~ri~O4+e?lb>AeP^FRF)gE%W|(gA_7Br0ip}nmL9aL1@M4y zzwi~iw)lX=aDT#Aga`b(Yl{#0eF%MY-R~y4{+D@_%iBo=$RNlYB@RL8{l#+Npa;m; z=4-0gdRUb2sk|Upe|ibDhd=v?U$OS0UE~OW1b9`1&Ixej0U>}yc#%CUKfwA^_;w%! zQvAw@)w7pg9j{x?%mGB}01c1d4%g+)gvm!lK ze^w%x)ALA#*`2*kxYBY2hBbF!yCUKl>%SAY7We&(2Q$v7Md-U)1pzb?#o_ z9y>Sl1-m@|WxGPv7fuTbf1gv;_SWuWyzl(rSjIVGbJ*MpdtiH7icS_&elF3 z*P=@N>UG&0Wb2OrLIl!YDax@;Ts_Qf%sT`$dBdE0?6dg0tgX!7$nP| z%b=^hKzSS>MBp>NBm^G^a1Ssj;*$)g{k+l=hrYs_umEzePo?)qgopG9K?0cd9Du2h zjS79(kv)w9moGaIKx|7y4iMd)nf!xT{}Przc>#_Q!5%ZaPAcXn>^UZ@pXTv0=q2*A z3ybft;sQu5lU_U@kO*>Je-c10eem8Oc>q>hVBjkfV2&g1C+W>GKO?j;ScL!p0#FDf z4{&q!0c%)!+J?7oTL1cW8xVR{FWdE*C#`qoiZw4Bv$n<4RzLr|otY5Xr|-A!m2-A^ z<^el5{Y7hDI;_y?G3(trQ@%d+cs-wOHTxestTGV!wrYMxi(|L6jR{ITv&AeTch z8tipIE`TV43r~=eV7ve#P%hv-L1|NqD)~kB%4!b@ISk64K$tP&f5IQ;@ge|ufYOa> zv73`W5CTYvuUmg&e#oDMkUT*2`?Hiu76ASHRSyuGPDBWlS5NMu974z;k+0V5t2$8i z`E`kc(gzaZ81;4XdrMCb$u0sQ1fqrbfZm=s*T{1hz$12j<$k-l_=rtyx7+MapUv;| zTkrB!yEOi&&2IPG+}41Nt+(3bW``Xf`m{9{j=IrpTRdgQMm{H=EZUvb=Vyo38-6m; z^1trGZ0kEpd%IpZhYW(WSnm)+5SJ`$`DXv51N_6uNH#1Eu>z1X##^*u`>e@Vo5 z0RV+RB6=jfOm}1j2y%h85P-bE^j@>b-=Od(l|YoPn-74|1u79l*+caHKz_PB-2>#1 z$76oh^5Hox0$`RGq0cKKJd<32h%SQDh5&fZSO5Z0>!A<=P*RKl(%fF}|Ca!w?kAHU z%dghEIy3oGFOVNIGK&Cs9)nL}vyD^7|5l>of4fJiwEICNyR1d1$NrV2 z^YOm<1r?&_<1C2U=X#$KVxJG}VNbUFRWDF2bq<6`sv-z45QMK3zh&|}@g)$^G3EjX z`9lDdL}U+;N+C#u5rBjc^*=+&>Iarf5CMO(!iI4#8)05cYO-Jf}6rQ%W;0v;HniFx^4E#8*N!oG^zF$S-`xSB3al2(=y{3Bb>b{Am=J z3GfxtR{;4#06YF4$}yfG7Qc-gAVz^>J^%%A<1T<05ThrE?$1E2^=CK7MfU)mf|A}E z>(8FO4w2oLKY{r20v`7(^qEoYKW%q>1bD(~1rX&98A1N@4g?U%uae!O2w#NotlvqS zHmjiDe~(dL!k{3%LsmY7aoB7BqZ>53MPpzL4}bs^`joK#F$(MgV8t~C#e9MbmS4%R zj++Q^l9kV;4sQJ&I`)a`dQAdo@EqVFt6O@)=6CyTOdeonyVnL*Z`;k8XKbZ3YvUX3 z3W3_JW0{4zu3EwRtzq`iv*X*He>>6c&)=h1EWWi|miehX*m2x2EdfA$gb*8FMCcRY4+5BU z5n%lp4D;?!7XXXhh5(5DK_tLk2Skts&@3p~RSWU4{Ifcq(eX?1z!*<(?wEC7i9iipqBCX_$u`JRX`VRWm- z;yd|$TtGk&6#+2aF@--)euxhFYnL$r3W=EN=!tIj0+cw&+Kc?)&UO+2dBDcw-V4;Q z`lPk59<{cWqn^~>S$tW^?!#8U@Pf6h9I-(=XDu5qTJ!3QC%ab9z9rG@&;O%b-hH!# z@O8E9T#+7-`n9ZnEpoSM769kS2^bYl17m3rtU3WkaEp)ik1&X?(eMnAf4?Vy{9gMb z@<*G22Ot5)1Eg|jM3CkJc<)bz{7C?1(5K@x0OlK9R0WX3AgunJ$Uk3h5E?~(gis@Z zR081>VGuw$4}dWO9C-kJ{;_8l9w6#{BGx~h!k&}fM)I2kpd27?ToJ1`%Nrnwgzi0s zJMse6wfrGJ>v@I%e3HADJ`?+A0pI}y34m4Fd@(DXZ}!*l26%!9cf8lfJptr@RN+&s z)Zm^$xd5ZM*OuMkxp1G0rLp{&U0S?n<3iz2o<24Chi^`_`hVX;tp5&K z^$(TH#rnKPfAs;$GwENe4e|#N&A}o$fH%lO$YGGYfqQ_|9ZC<7_X1@Rq}%`j5DIw< zLIjKgS4A)_^3x4kYyCl#K{&iOsM-a(BmpeB0CFxsZ%{`HeGmalo-_sd)zbSDm7t6P z+>rn%g^tzU+!Ig+;km&6_}@K%mpRktpHNmf1Y8sdB8z}^{A+<%z$4_um$<)H1i2?D zdV#0{jT|7223Np%00U!Rw^j+DRk=X35@7NHmLs9phZ$Y8u{_D+dOg~x_kYZqi%(dm zJ#9VqygY-v#pYvm3Y$K7YwLmkv@+&Dua9!M{CatWe$xLxf(VIH5?odepl~S4qVWKX0Wb(wD}d|) z1gbzOg%I|YLL@LEoI@cGfgHK-AM!^V#c-5ESC>5W(cm;EfJE3u;3W|f0p-r+vkDdD z30T=JgT-wf@&@JO!cmFiXl!Q3bBQ450F{bRM4%)&Cc}W}333s5IP`*#4KM+oL9%8< z!JwEAj6I|CPg$El>s_VEBQ#44Ejv#tk$%#N-fPd=upP27drALm&*Cu(62-Zu-6uZO zxbwv86J7ql{Sec?RbJs8rSk4a1Yln62Wd{pS4XGWnBl=%>MfX3Z&pIx!Gy5yL=6U5CPpx$p zpX%-`J@r!O?$>|6v-tJ5ca@*~mPCvH|31XJzaS6s?ZO)+h+mW#J}iOUtHk+v;eyb( zSKjL0D{qYKl{cqK<&D{5d1FCsLFXJwl7%&4#i5))SS%FFpb+g|p|Gn~9za+~Z93jt zC@2&XW((WGR(YndS)LZAgo%O@;lldfNMUVnxUg0lDku*q$RiY#8wi7rweo<_m(c5w zHz+KYI}3%q&Vq7+K)Fq5EzFl&919gR3r#{}VXj+ZOdfDY~lU7NwggAGvNjqUh6Qbfd`Drwc`q zLUP%M$4ADp4Jv=-nP};Kdh{KdKXS_~sp(0eH#Kl!d6N&T&zm2crSd`S8CF zR3BLo27vOh*OtwD;{wO!xYpv~n4Yw5+=de)GqMCvw(&>9J=r zLvxy~ed8xxCu$;H_ps}-R%X4@Y@@%cy*RWs+~`)o>&VJ^ug z;DwlZC*a?W%p26*Pu@xlXPfqBt*d?GCS2HDb(q>(+&cfD`Y++8is38V(Zqm=9ImUM zpy{aJNMhhaYS=68DaKe>QRV(8m4Qk%L0iX#nS^F#bzV$Q?c=(`%sssY>#lA(X`LPW zaNMG;jeAIV=;y!F){SRgtuegXR~q?J_HUzq*>J$hsh<^xdrhmN`%cDJjIYfa8+0j6 zoPWN5j%ro@_EcX&OtJJwq4lTQBG=oen)fMBijncAMRec45o^$J>oX%nE3lM zdUN?9*EQ0*+UC(Q$%~*^YE8mlq1=&8+@`$mQxw6VeVOp!H8MN$m8iW|dpUL0-O;hO z=ta`&LrMPfR`zn~6B?fTZ0h2tMYpd^wTdcgj+KdCG#b6;Ha%kxD9XCF3+hLtd9|pq zz?R&prfeyyvv4%=;B3&>*f_7p-tyOe+lNAmpFJ-fUSnGuSo^{ysJr6uD`DEv;y+GP z(Utp^C*|&CeZGlInumIrbUvcJ_|*6{YGL!(qO7O%nqGEfO=od!%Ba&L3DmZ(%>G32 zkE+roT>nCPP2Gke#l2S{No#XH8ScyXi9XO@W8A&!;g{T+A4iJ|*80=@1~tc;M=J%# zIh_7?pGPolgIB%^yGA zY59#EqMWS%lJ!!(F2P$G(b}7|t8&1&(&lg96V#9)DKKy%n%VdiZvL zGPicBt(03x+aU<>AB(?1h#F8|Kwh2bDGa_n7qcYs(Kbo8^!8JxAuI#b;x##<*0Fze~uY;Twzo+qT6pu=}*3nQozUFZ&{WT?!FQ)tl(zl)BVp!m2GmTUj~Pf zg)QX=_}&O59iMJmgF+6onjAsUTZQ``k&0K z)6(*2=lGt|?XL|V(xyue6dXQmw>m3J>&oC!$-%Y6J6E^`)TiU$7gZfTMPBw$_PRab zLHOYZQ_>Ti`y>j##@77xvwOe1_%>g8=zMcX`GKUJ{XZICEF8MRZEUqyRyUqH>Nh5y zD|}J=-@j5THmPbS)xPmeOLfj%&zV~R#S){oeBtk7oe>{S#uttoW}W&mb*sm<$ge^x zr|o`e)xNCfZQ=hbnyapLlec^J$mg{ecV{(CW>TMy&Y$R&uWz*O-e0aRIJ{6WNd0hf zT?CmWabL;)cS^#l-#?ossEYSI&mPw;BQ9*8^+*%AJsx_I#W<-5T}kxoyZPamIF)wE zvX!o((Y&E;u-()vignX z-*Rtvt;$5-UVK_y-Id*L^MrbUeUQzXo+@Zwx^iD%QS0!!uD%^a!Ql3;;Y7)j5bIAH z%J);n)N3_G@#MXIt5&w1xAOnrzod4~?|u<Kt@f+#P@tC`H`=zb(btS#& z-bHG9g_=JPvn? z+hX%53-n0qZHzcq(4UQ-H5txoqSu?jnpn<%qxzc@JGl1Ia~c-$LO6XK ze9QNCXMH%iI$4=ewy{>`maNp4Y4g1R%AGL2Hfl@&;tP6?tg457HEqvejTVzCX zDmH_#9H$NHFo|N+ka<2^IMgUNPzsm4|n@F5>o0fHUBQ`a^J$ja!!i(-R)Y1xRwVNa!Jb9T|R&MsJf|b zw&yI4j^!VPfwh|a9WWRNPIlOF`xj~0(?8o6rEKB^u-FOfo(TU! zzGRQpFzed44^hx0%~p24Yv&;rW$`d9rC0 zBs;mLq__G7^KOh8$_y+PMDCA#h8ArFnA<55^!Nc!5UMs{0hy`KNWL5L7xUWeaGdN_ z{5#L>P0{PXk|QjI85n={^6`DW3xJ2}o%@R6LxRcto^}>H@9IWTlksd<`6A+Fw_=Zj zl(QU~EWGC6=dBxRUj-JQX_xi43UXdGkJFXqhxo!_ynOiiuQ&9gmCk};H6F1ez+_&0 ze-Ou=`r`AIJ`q034mB6}j97no`XOl<5}3Dwb*I^9Pkia-jv>j@l|Fi&?r0$Y9+qEl z{r*bQ6Q7t)tsA_U{D#*%BRrunz_;u8ULU<7(i~D7wb05d+-Tbtt0t;C8Jd`Z!h!*4 z0dF-*ar%-5Vg_^xxNLVAErOMI#{e7q?#4@>gwx5x_zq23PsOdvgpLnEJi$ZE@uZli z$@E%1s})CVj5-ouTm}dpM}dyJQE(@My?M20QB$G1%oMUv17r&Kz~?ZIU&A8#hlbq)(BlSr}x(6;B7t{vAlLzAE&mv0%5huU=(kK}g9!vce!*wX%#tEz=csOVhBatBXje#m&2vUHyo7 zY&|D{WIfeq4sHkg2J}|H>qD%$y|eD{??3h}?&!DZfrX*2k67J{VizNkCD-S<9flo{ z7&B>sx+-uS`mQdD?VMfI^0aUR&X>t?ju{f#YK-6)hhGiL$llI=MLwt;ua(D3XVqsjjg;h`fLw(w+{<1CYC~e7tTu16i-IMW;)$R`{&kPkjNcfP9x)#AhHeY}HmhLC3LOWwz8GHF24Q;@D3H4ET6 z^l03llZ-k?PEtEhiJR&~bGD$)@nXUozbjL(&Ro5G|7drQlgHqu{kq4x!tI=5vnK*r zZyPUqBV>QVZh}bjr`7aK_>3(43IXY?-Y$3uNPnzdm*W+?55|l3!~Ahbw(4-#_pD)t z>J4`sBWb-7ADyi4_kgl-yd`F=wYw>}5|nHg{351@LpHKH@C`G*Ov)XGsgfRbP9JMi zff`KPuKclZyxx~1yYk`2?()M0*H@NQALQZrO@^^pUmS{s#*RFPgMrKiw!Z~s!cB5W zia0+5gZE+WbO=p(yxD}swtsvX1iB)Dy{?b3DarR~-6;}U0mu}fB(EWA*ZRF69U4VE zIhYH<3s|?1+l>omx5E2GiXJD~V@RtPd=m~M;2l#IOrtO}b`PQ_0#(c3OVttNq zg2=O8&$B~=RxvA?)X*IVE{I8=ox(&TAR zYAc@gizE4?f7JO^Jp&;4g~ALEwOM7Ogla95sHhX6E*15QGpd4OQXC`hx+F9&H)#n0 z6zZP-n)#6a3`nw&d4W9|Sa`PLlrqzSqCu-ZQqsGc?z#Kl+U`F8)92|s*~7|%@>jC| zP1>ox@W|+IY?q+>Gs^5WKC;iJ&wiVSMV{jDKjdF~F1Z)pymbFAC5XbdCGQHRzmJMK zo7-ji*82Rtbg(or6X+v|%RiKX#JzBJQnGXTRWqAa!T1haBG-kt^r6zgMf&%`h-A($n>=Nxj5tyO0RZE%*oo>ZxH}t2W ziwsktGg+I#nI+z!2rj7IU#SI=tVw84Ni3-c$~dV7st~xM!3~053mPd0R5d8Wve>Z_ z(w3#dS`thg<^CJjS$HmPUR!dIQI8+dpRMnwAMQu!y2yfvy@=&L*9iaa?yPWYe>d!i zzstjykyrzFKtg?jR=|h`au>Trh%Z3NfYV3TZBSRzpcqioYQiX2s9UJViHeSD#Q?Y@ z)i~9JW$;MxZ+nB(vON+e@ha-AyMuS3+EKj;|H)2h%BK11&eS84201Ll#dIw)G7b zKa)iZxPoqG1z9=36c1(7>E({U*!S1$tfq#aeShYNkoY|)S6}-xa5*29e4q7}()UE@ zZ655CryL)6ZB(DAvyHP;_kuetU(z(MHnMzbv1p=<`E;0KfL-iscMu!mx%hI>sd+c- zc@Uqe*4VQ!w7a%vW)e!Qmo1r(F5i)?VYMEqOe|1IdB4Z#)|<~Oe9IxwIn8FhzBaH4r%beIKh zd%NCWq%_{PZn(wL2pl2l@f6#14u8G}GK?tSNHb*W3Si8kUt7I}vkA<|hU27gLX&dR zdMc=#Fmh8Xy`H!nsRrr596@icrkDMwz&`RtpIMOc8g6OlSw~e^TDt|qBwL=(Abs23 zWa*r9Puh#s<%w4QWjK=le+MLV&UZUv|oX4`x!!Ffyu zZUXN|N1}QmbKt&&Q~OvC<_2oC z%VT00Ij8wZWX-{z$J6ZpUVfB>%l==E5OI5lKGMTqI*KEk@6@|E`{%KgMGaq+9X9@` zpNKOHpIuSS_+gz<-)+Pp8t{m!NwkrcUP*vFH#e)xpt&VlQ$fA1>yoOIP*r1bv#BUO zy>va_{)y(rqN8zQ+iDr&auV5x+#Pb$SXmhv?i2QbLPYw?mJbp69y9r}H}%Ge}YDTJVE(bqK7*Je=CcY5T7yS_H4LNX_;b~xlX!nomuUEp0tx8_uXW#ZD?vy6D z+AUYF34Hu6_oXCoS%=SjF}1#OE9U2y`KA-8f5IPxtrPiA@fSnOpVd_k=kE;Ed{=ur(-B^e<1iM)5hoHnRiJ}J&J;oPGCWBhdM_IA z{d%V9$@W`9Jjw+;AHxzz)S5Yg8co~|h|2-L50=`6Bb6EGQFz*G1t*WZwDvJgP?l9Z zli`x6YFbqLZ}e4A2DT72H>F|)!^eYm>H*0|`Jg zALmTfu0o>MP&8ES$vJo0*62NDNlSi_9T=}#Z-4;hIYDWY31q;!iS01woTTU@qgl_@yFSXHzuPlx(lt~t zcR6xaqBdgz;*@dIaq?8SVF3_r#~p9Ue_ggHL-^|?;O(sPOuh^U=PBY8Yi9T4<898g zJlTm${}-M!DeAIVxC>QI(SZ;f@6A<6M}(01TDz-fKXtFBjU=J(q!Qa;+lx-Ma{pt`}^Eo$)CK8z3yd8^mFe&G)ysB1<*`HPScwXq-Yt*L3!y4+$ACvZ$mg z_Jyw zUk{71Z;4sUCLk1&ABLx|=DpJ#&zd|X4hm#z&&JkKyZ_Z3VFZVvC>il!BH&L>V5|PLn;CZ z9Ocro{}D6P=u_)q;6N>aO~DD2WQPuav~6k`J#1!~oNS=Sk?n`Jz;|3VMCo-TXaF=6 z#=a-=xRrFs;U)bh*bB^QkLaq)1p;Q03LpIg*3)KSEVM;owEc=A&sGId) zfPf8Far+VC0>BSi;||OOWJ{POypT~K3b-x>n32#7)tZ5-t~>Jk;D82lP4(+R zaSi|~JXaxDiyE>(f1Iy*avK0c!nN=kyAnIzw_}V$+)TG&^)T)lV|VZb{crMJM~c&l z#c_<&^Y^SeeCj)^E<<&>e_kE%6X^0xQMX>_gte%*?D2Rj#E0U-=vkBa zg&lV@JBcc8r<$(yH#DQpS)nrE--`UJ6T5IxNlJ7ZPCFIrurLb8j~<^2I~cF{IPJkP zYSEvyFZf>9@1gCh%)`RSnPQ~@)qbKdK5hID^O#{*iAwg063~A~1psHj3hQ?6KhWH5a9|~v_^9aS z3qLIhK2r$Hz#$Z$J2-7^=%sPEF^8ME8B!F1z(?RWg+2rEWllKX$IDfEfTJ2^Mex&2 zt_yqv$$+*Sp5kQ^ue330^qYZ0xM!5%~PO+>Hc8tPiK*=!Jcvpi06y{+)49!PeG zMeSYBO3tJ88L`Uu0bTeU20d5Q*YJw*nUNM0NREf%6wlEuj5Q4+b^b2MVW;QMpcp3l zI5$XeP55d6A6JNXqiphp`Oh%~xF9OIJ5F@fbbimb>t=+O;uhqC+6oM)m3XZ+z#Vu# ztwIzOgL_)ozqewF2-)To!&aZ)ytsB^a2vS%U20;%r@==cMrL>QDMLGMKw)cc=nkB zmEl*Tmu-z!tfwr|%gA;iDKl4Yg^SQ&t^iupm$Jgff)T`Hj5B^lwpjk*UZ%s;6qhYP z4RQ6PyjOEVYTE#GP9Hi4Z_@>`B`~3b>#Ih>3PqNIY*jVHCs(@2N2AHWCqQ0hs+MNM z^;ptz1Nn@a!$G;v6}#0NAl_P#rYav(s6ielehhbvT!C$dP^(F5=BVGFLI)s{_|)~& z+;8kIPbpGcql}aI^Zhnexk(9QMj=2Jj<{5yzp0z@Mu;mZk__=Q{}V*|!gOuh3g?Q_ zhRGkgJ#awO=69LC?zcRf!oCh1wQRO2*A>F8!xbm#6s3hevXOdN?@{f7lbqi>QNoiK zH8^XMBapY-kR+8f$iISq{nR>o!x_Z2e785^6?QvjSlQ%6qQAOlZ&PS{;gY$<`~XAK0*Q(Q>FaU!!{ZAA75ITbHx0eD4O=Pe z3*iiC2sMca{O{%XzB?A|EkA_?k0^*Y)f@fskT7ipXpNf$WU4U_pZ%R#yJo~57#1wf zO(bd!B4y6`!kNRAeW}5ygTd641e8q}YfRs_!bm55{*~3i9~_+wRI8l&q<_x#OK;86 zLRyMrXpNB^GZRtuqmn6hagD$9i6dMF^Q*5WVieadTu~9*qNMZZuS>osHia_ou3^Yc z^_0gPJ|Xld3@0l;5+8^eVca0Ii0h-Go`@5-Y8ZEi>d1}H(i z_$94u5O=)+MWuE!T<8PzI@NPp>G=jM2#&4(>&^D-ijDlp2pyiLEP9+lK(NWTkt=hy zujj3!(zI6ZtB@7Pzt9{pMeT=3=#1ndQ*_Cg4T9wqO`#}T5du$OiE9atTu!Qh9czO^ zzzq;;j`{Y3<5SZ`xUf?r*8|tE1U$1o5i2AID%RX{|4F<>OZF@OanwNE zA>d?AF^9Oc*Z$A7L?bLuw{$msWH&Var>UPwKX~agW8McQp}~5@uXvq4YE;#5T+pCJ zU1qr@_>-f=6yA!p@KCJC63Sj$ss+wH)QDXcCv(6{0~E9` zlpznw*p-G>J(ZH-zc>qcYyCg%DNZ<}F)+irAr3gdME^n5PRdP4K5G_9egLj(yBWS& zV&jFJx}oM}F!xDyeTZ6TiW$IAFnk#f<^2H_iMs{KEYzNBn5=Xv?csHb0YXF;BcGGH zZhemY-7?Wy)7uF56A-HvMqVW`K~_*iT0HMo#*IUv=XCWlrrvi2a_5U&bZiAVq+sn) zwVAazO@jt|+&Ubxp;dA=vVG>o#fTY}kQ$J<#PZxsxQ1)5Lbgj@T^ZZT?D1bpok@xx zUmP}Vjrl$2DM85Vm1)|)GbKuQq;Fv*nYbUP}Qh20yLThmCQZ0+?l-d)kZaMZc`PIg*x-haAj# zWb+pC>%#B}8}&%yda%wRl-Jew6JSLs(E!xqGfp;tP^>SkQafBXJB;S!0UCE$adfVb z5ep6onbE3}%f*1trl3Iq95ldXgn+0pA#hgL10mT%AovrCr2`!R3R`FTHs^%*@L|0z zYbpOpp2MwG&`%Fz3WeMs2IA3>d;qmqM^(G$cIuI0!QRh(evmtGP@`P-rr+fJH6%m) z(F!=;nXU^Y`+w>c#P3AEet_NE^8nJrTp?wHTZc-H>mfga@&R8q7DjoGTA&y8f?|bQ zne|bwE&Ql}WE#5JCN^xO?-v+Vi8|->!M43X4~D4c;E@z`(hkz<;5043U$37sGos(^ zcX>F-^ukY_hM$1m;8rhZ+Lc&tAt4nqreh^Y36-0po{g8cpLK~M;yzyn1GG}5DcGg1 z2U8aE41k_;yM&gj#gWznx#Mqj{^IewKX|MrCtS7bwA-~Ok=6Y@ca{u25B2}XnGgJPch&IHk{4|I z{yiCITH14axf(FbhfYqZ-H(6fLd!a{XKH#`ZJ|W+u%@rY)iT|;dIwrjKn+Y9DBD!(*W58WD0(IosI@`AK~pxI z2*^Ls^h1w1ec^H_1`7m-HNuv2dR=&odS)2st~+W*5pG|A7b_F8AX>b#7L6@YZ?KBI zzNqAa$>Vs?7SkN-crfaVF%@j;bqRa07IzQD&j!7MsxWQEJzJxOo0`b*lmmGa zNYw&LSI8I5IjLe_scJy+xQ2=Z@bIPwoKJQKV$h;AAnFQAl*6$^!NXfW*zJofQ5rnz zBHpKv%N51zhR!bIk+}DbscV%uQFm2Eg-+bEVM}#R=;GvBmn3ItL}}1_jPXRA0&y|t zP%X5cJ=Ai*BTmqf+Eqw4+nI)PU9RF}Km`K7GUk-jwoMQyQB4MG`B~XgO8#0_>~A86 z>+I<^4Eq*4Be#*fXywchbx8?A|Fr$$YJvXB^a|!4l=RZ=NcAnt3H;)LcTQRvM(F=$ zBR|XfJkhmL|%+_nBfjH~D=fXp$ z=2j>E3A{Oc#zlnj+a?UX8ZI~IOr0-2wG3_guUX5^8^1H_5}q$~NitAtpMJHM-7g>S zPK>>Ob|^MSzQOgyA9+I2HfomQ_|h%Bj*z(iptW%-n=MT=OaAF^B3JhMn<&k~=ZA9| zWa`I=dJus>1$7Z)x@YL|6Hvl7H&|^1mK3PfKO660cl9nC2(JG~>2>B!`EFjKYrq+f zBO%3Q7Eqi#1H?;TT=7gdf3x$#Qqp7?<+|Dp(4e~|ULFyR1(lGeGH&$#XLS`tcxl@& z{$5^h4I?n%hb9_N)5GYuMIxRO$|fjDHZKO)|cNVcd$r&gj9Z z8?L|QYvj?x^mAZb+~nSu6ZDMv9H;7smSFT7aTvv$MoHrMW?=gl=^i)3EjqHlC_{#q zRo?N`FZnX5(FxFcpI?7h;RqMlMWU~+Bz$5zLmcupj?o5>;HeVem29FOrX3H=p&KAw z1E{1sm1_L&wRw6W7U2YJA;qIgRZ*F9f5D_paFHn)(F>jIS${*}t3gu#ymcX^H%*JB zLQ~+4JK9srTpj4KBr=)+l9-|)S~4|2-Go8zW&2>GzA}o@`+Me5&Lj|8^hHs~?PVjq zDiusAISVcN2jTSQqiUbjdZ=mn6aljut1mt8qW9{q_S5~)ty(eUD3#NoG@FT zuwy;&s-CbgS)r-TAFp@qIdkMI1}QfR59SBi29EVu+d9nKc2szB3_d^g z7ls#ggQSYPKcYKh9ho%Y5$_?_uI^!hN8fa}imylNd;{l^rp<2mNmtw^5w92D1zSU`Z>OP>9Ph*P43VlTV%fYq4C1>?;dP}-C@~Y}?x0_vZu0_(3ctr+ z5)8mFdW*tdl!M;VUh%W<5eM<1V>#Y|^y`qG-U?o?|b))_|ZuYIRaC zW`!B4#cmz%IudUH>tDK~yuX;@30msAJ%+5^C@N2^x3ufm`Dn`2O6sC_rz8+ZNSTf_iDfy^X8OJ{e0`NIi<`UVATz>AEG<@ElpUJ zX_-hWs?HzEly21+VGJ-ny_$c?jvNbD=FwE)7B66~&WQwQPP7c~e-Ou+^>|^#i}cjd zf;_Qc5UDJFmb!uh7c`9-i7H^i_B-q`0-P+@Q$x0=ba}(Z;Y++yHZ37Ah+9FF-;$P? za(X669A4>hH_r~iq(cXwJ&nMT`kL_>3dUbcBnq*js>q&4EQvP;oNP!2eUAW`tXZMC zTDu4d*H6K;e|j?(0ajDTQ#8JYAQxifXE3QAzQ$cFxr8iq^Xx^a@jS2D&=Fnm!7|=W90y_9m^Bhge_JjKYieQ?RP# zoOQ>Lz>cLInrg@+_D302#`S=DKwSHyXX0$RIv9jvxDtI(}fXw+IsWg@Zr!q z%3V3e&r;OS57lTc+&#w;JRU(tk4Q88E>t!w=mrK#3ZZD^&i{ z#)wHAfrFaC(0|JsWD9148c(GeM>+dW+}@XLhJxutlmS$t5lm4pdivcuDhM7$q_&3` zAAQb%dc{2hmX7bG4F|r>p8yg)7`usNK!SZAsqARLECHXAFVDLJUld<>Q_em-^qc9b z#<_y5R#~U@DaEN2XFAF?3$t18nYI_{G;4jN`uGp^Wq+;!f=-l-xSoUr{a3{;yZ}j_ zYOvnAqcc!$_+rgnfqN2}6bzTi2YIS+kV_Kf&|~f3I{4@GdreOdR)jAmHSuZ!<<9z3 zIp(*_f?0lvk2mdy++Y*K1E{Nqe0roHecY)m;SO}uu`ii6w6pt`Jm4c?p9kx5~EADMS zahvz>p8Z0VjfvEgz-xbnUyab>e+Jl~PVxZw*nlfQlM5jNbPkZzu5O0Wz5QyG$LOe) zDwBHt?-?Q*q!^^%YVZYEP49P1sl0yzrPu|(iE1cbGBio2(03A#{;A49H;y)k~6BaP^CEP@y zdAZ^CJEZD!ENF0;pAyk2c~!D3GjuhPlt(^G=diKJJ}_zucl$%p1CeWe?X5_r8E?J7 zQC;NVd?t#dXY=6|<0N4@Wognyt@3Ap!+6@`g*=SKLnDbWpgKz|u%>FSXB&f53L{m?PeF0c!5c zPgQq@ND8JoCrqOG*?-i&1SQPCbPed27$W$F?j-Y!H)I~Obl=6pcrD&Cxh)2wXj34P zkoboj3sFEVdA0Vqxp1A%8`J=B=HYqlXH-*c>-R5Dp`Ds6Eg&gAq`;3nH1l$5I@rs5AMPnT6d_h^F7 zP$pt8427!cJ_&Upv-#Y~+B3FKD!&hl#jD=Y-B1aNQ-y7#u^QJ^wEnxqB$p_dYWAz| zS+8X|#`qlgj=9By`CtR{0`Opm{5EMq7w%BI$6x zzi>^zfTLhBv5D)ZIRn}i5}Ad5J%((QB3ww6htozJ_q2ri9hzdil-As zn05I($odlUE{xDpxVb1MOf2En7%|5Ws&H#9`V!0Gy>4z`6ogqB6yp zG#l-lQ35zx4Yo_&C~)vj6p)39H0Mk;Eg-H2!4z&?Ht{_x6d|A{>@U#c6*EHWA`17n zr0*xhs!TO>P)7bysx8*od>uR^>H(gU35il~2*{}v-V0$}q}HS6IEfql#z?gpY9VDK zvbw|w(cu|Ed^O6gMYgcQEP$=5L z|Ck|%@f{0Cw0mE7AGf#ibdrMc$2Qr7mLT(ovp_>M>&r&8)#R&Z`f}FRy-~ddHs`=VGfI?e z$a7$IABMDW!H0R=7)~vAg*{aFrH zt3iQDte=6R&WIld;ApSaM&gIb4^&wG&NAeumT_@!g$$Q2^$hM+M?R`D@}DnlJDFSbU#AFFRF zw&C1}`(Gd(Okqj(h%*SI>t1LsK&N9_I>D1EBy6k7okyywlh}|iq8cGyvO@byM^`}@ zm8m;?Dqu7NzS@NJwoa+*@t+`D0^dxjrLQDx1QZPGB@)$$@z&zNqCHP^JZ-biU+eE# zHtZMF1x&%GJc7CbXwOVx(a1rGceQNT&|R0efyijBVy7|hlZp3)uw%_wAvc0jHeaCl zFcf=?OZG^qf`^ zJgSO13WlZXiYj_cx$_^5>w-1v$DZbAH@rk_)rwi;;6 z;WbTL!eJ8s&T!u^q*@*ErL49`ozr7oC4fioA{AF+eRfhNK}(>kB=+7Xjwi?tNAVyk zCr_$M-BiH|HecyN{rkUQ_f&54A0@ze1}&Cx5cmFuSW7pKrh)y z81*+J%#~uYkiA|$@k?_K5;$6M?o$1eX|3>D=pS+Og^B4Lbh^q%j+5RIs!hU4eeiL< z2ZXgcV1iLR2H4MD=+kZlzRy)M8mUH}<)Fb#oG?|h)n5Ni4*KV_gyjmHtV;C?Y%=q7 zL40!Qi!r=Q99UMX({U*c-++>B1+9T7Nz5NcpT+@GkTXc<(8i|IfO)0$QrCJJe{x+W z+7pWLgw7`EfvsS*^5(~3V=}<27^yz&nX?#nCtz`;>Hf~pBKI{b_{9ld6t;{k013^Y z6>4{+@xK_K(X1PwZRj|u0u3uZD*MH0zyaqwbWY_euOl42m0UI&f7AvI5JP^Xn<1c^ zE7z^&g>i^F+XWFp%EGruPd%vAjY7|3Xr0d_Ci8yXm5?gdTSNlSwM;gHr1gLw0k`n_ z0^>Q>jg-VewMk(@cfU^%MU$Xe;57Z>$P^A=5}8$6)*homu_c#)O@;8)4oc?RcV%6e zR*e6_@IInl{$@LJ)13`A`)3m;L+VUw>mPBW8Hed;tEd|xZ951jui+?UhU08P@r&76 z;h~|q*zRZ0m|)1ZYnUEbqS|0L|2sNr3dJrtu;e*g0NcekH^|cr*RP+e{9J#uRIq8% z{sMcS%1-#iHn^}b9++xe9y@jaXcAJ*p?`56D$pMBKga0^CCvzgByVrSJlceddI|Ksh#SE^03)Bd``EzoXy=76Hv>p=cTb?wY!tEt zb@otWckFndhoWIF4IF;l7g7kv=y8^kACUwX-vyS|lvh9z1%0MUt0LBE><= zE1>pk$YgXah^X_|4tAyBy|4=pWTScFLart3h(}6s6sA^og0w+NNI_@~P$Z$6OjR}3 zL<8XIb`e%(??(2$SS5@!Ysm*;D(ngQEPQhCfAvS(fjboBGptl z2PJ86E+fXKs{I>R{5BM~f?Lo|?I`b-mC1Fv#_2M~XS{BM7qTE0KJ5vw=2#=c9mG=Q zw}|H0ogii}bS)Zc*#^#J7kPaG{FaWDVCb55->{}>PCE^#`cNG;Nm7mc!%7e!$-b+o6vu?a5ulLt#dOIF_Nr>cXX`tF;l!~OAU3e0&1ieIlS)vo?1 z%T#V(XhZ5($&((ME1+0U+ ziHLdZcf=SO5yiI+P`*^#{9;yJbP-J9GH9koRL{;A2h;-8)ii z0S#yj#06)W*?N<_<)Y+ykfYEYYFhNLCr!FKp3D+T^xZxIRFEyi`h80eS_jGGj^8v>>%= z)^*&R+!guc1P#Gc7t;|OqE)8aH*wf_OJ89E#uVJ%9xpw)Se6CHlxZtKr8T^51;xLMykv_z&tnEWUL`c3GuEh&yTP#ABQ#20DBuNge^4;Jt<1%t{ zBdd#M!VyZA)z!BKX} zC!*X338Fa!2mayUc0plPsc_Qf7B4gSWb`7+7LtZ4pKcCPQ5hnCMOu1;)Lyqmq|-|_ zr;sA`I5@x|&!|#)${irvJtxK#M{^>X0#6lMLR8}z(5>L;dgcFdbms9;?f)PDoHMI^ z4_Rhx(TX-?nPVwZp}K9iVv5ox$}RiMQNH2cZlhFmD>L_2mln~U8s=24rET02l9(c7 zX)p|C{eAB5e;&5ueBSTZ^8Dx^A#(9KA_wXD=Qrc16`hvWl_+*nyMAAx8{LO(pJlBV z7UQ0*W~vImk%w)YR_-`0cXBJGl zkMJL;V*bodpyNwRG?w>ip``B5p$KNpv;}R!rcel-baC(qr8&?Z{RWuR!N6@-2MP3x zzkN<#TL6!$fFdoM<5O@BgyD)1-4+AI(g;!x=$VQ!Cv_o}oG`gyo(`V&uaP=TgV%)r z(0Vz9SrMHg?9!_2A+>TJdv#W9HPn-;+ik{3Bb^S&dK9mX{OAM9z?^zBIWQ+Xjy1XC^}jzC;yTtN2{?2J-o(6)yu`CfLExLZ(1~yfGN#A4IX{UxK;nLBKr|?p1(GMbv%HmYDpCiRM)iH%l$BQ z7#g)-rQUBe*=oR0}p%^6CWku2@on=6{31Dxks!g8s= zuN(JbYIZ#E#B7>e^$UK>(Eo8x&6u_qb4_(CKQw~6H6X9v5-Ff`eA-`XCpm=@lQ`yl zVMI`!Mr7*tyYACA#}*tf*JYau{7u*4NtL0^#|)jwDjfp>T2KVbhY0c5C&|xnvm~+s zjCdj5TU_38!m!(*Wtks0Mn@kZ?uCA>xPntTV<5HB=q2_)Scn;-tPKle`&#m}e%Y>% zXpU;HsX`Lg_}{!x0J76e0N^-?yVwz|QuNl0#ceSQhXTn~x!%QoWic*kh_i9;Hf{X) z0Xhf)hq3&6u~la}(K)H$^{G7Td_x8QttdVSTpdWvFT7>ya@5!`s*0G||LQ3toAF-t zasE??MAYr?%A0nTA8gw z=%N_+40sf;{VxcdqhnvVVThYw8p+B!GOsBGDKoV8fJQ$}GP}wxZyi&VLr?Mo)Dc%@ zmnn?h!Aw%zU+|F!Gf-l?cBu*OtV^9*51q7`;Cj=~bHO<43V-fZX1$h)+M-fPGW9J$7X=UUj4u4|`tuEVatm+3^d^s8T(;{}*d;0K~$?g5n{5-%i}N zw913cSod$jfE#HoU9m^NUt=mTLo&K%&7d4}LdupV>yI?2$?yA5pIXl>d7Hu#e9DmD z^Xy#%p4M}RWSg~xC9Ow(Pstkc-O0I$cFU|{xr%`_O*H&|HUrbj%#mSVlE#6RXzM)? z-}}m}CRE5GjELml-LZ&yF@g#XX?L0iVPi8)n*a==EweX0-4rNHIioP7sqjSt{WeC& z2ZbH7L>7e{eNfS!DyR9s?RZ`K%j3XU+C1(4ni{TIfyvb1mQTy#zy!|EmlEHRuhRwa z;Kna4-GLm}UPt$(P2j9wvk6wB`Z)c+B=K)RKum@-^Rhku2E=C8Trt0k?Pov8Zb zIY;0ll`4U)Au|LsWyWijQ%oOtf_K8@DV22t+~EK_7yK#wT%<|{0xGIsDJMNK#la>i zNm5$Ts|&+(xd`W6>_S01OqXb@x8_bqPrUg$x+wnCBMm9<~5~R&y5raD|$26OFPXT5A)d1&;;m@D*y(++e%^G-W&vykB}OHh;`MOG*UX}%Dsl|NKy7y}kbEob zsb7Pi9P~lczN)K0S+*#V^x7Y+G6knKo5s(fYztf?bnvJZG~;p&^fnYL<)rcDx-;-4 z_8HGnXWA@t!8FNo&dVN!cHHl_gI&mw!||h^>}SroQp|DA_W8 z?_qDcW_-*0zR|2w@l9!kXTU(l+N+X2-CiiPJ{Di}Bru0WFxp5 zoigP+8hK497ji6*+p?Q^HzvS>3u>Ix>w6NfPcX|i2BDOKk2mYP=X5d+Rs(kc|;n%3l%2h6EE$ zg*}@dioldS^+*SIq{`bUngGM%9!&hjl=+;D9x|ic1hvZU&*I&e=1C%eFe427tyxF^ z!_8Tz@;14oTb)bpc?~;4Wq{+3H(=(#8LB(P)AlRNKwHe;raOUk*co=a8!5{jSE2HHnJYuG;*G^2 zratk2n1$f}mLwBH_`1Za5B!9@-2G^;oe$~^j=qyDjbuO5!@VuAAPKq?pLCbdMiR^{ z2v^XqOswFiPo8!QyBo7h`>H$A3b0MHpHvZo{e+sKiYO$n zHMV&a77TDlrnobdw4uE~IjY(cdJ2qYB2UbK1B(00+;$xq;$0*quVVLPkutNn{ONiy zxlP|ZlJAGxlJxW2Mo?x0-tv~B3efG}hHr{%m>!;X4Q1N18o6T~NZMSo12Q_UCs;!s z7+@dQPA<8fcI8pU!}`$?AD_y|Ss3kg)!3uMlomA2RA~Pu1hRpQg4;|ueH(d z@Z-hr;+fD5(t+T0tl!h#D&A2W_4`rkh78#vy<TGQ@njf4kET za26N(^}l^dVa_&=WSC-h>o9x3HX1QQ!T2goOo*ln?o6lQM_rR@a_2S3CQlQgiz6 zw16HxMNpmT$B7<&wG4Sut?r{PQ08}tS0V}~Kr{x4YElnu)TB4yF3{EO`XIe)2D+!+ ze_8;t#M!p@QS2a&wMpEi9pU(DHm`$$f+VNU-HpG^JKJZp$4nF-A=ETXjrOi3lNGkp z?~7Mv;{gtMR-?t{vL5SMhSyLl)wk*7)k)P|&P+?Hjw)2lz^MIS|Kwqd(QVm3XQvOG zeLYv)SpsvEuA0$}HC64G%>-#bV(s>~6{>ZO4USS?cgq&Hj`>1gQLysjoq>B6?1=2~ z%B9@~Z<{k%$oI!yF5O_k{@Ghtc13IDozdc&aZd*O_VmL>PD${V*xRz?ak!?b_jP3K zJo!PUO75w_C(^68lyB6Ek5xZnWg7m->=$+FyXwmxfS&{(?;I%vrb@D^R%&$pGx1rF z$otGTLWqlX3r`ijdHn2YZ)jkY=p3m9eU8M{J*o}97N~C9AwujtT5U7N6sN!wDcw6T ztNm5%8g%@%ox+1NW8xy_QJRc;;4xj#N|B$6invi&NiBvm(4@QiDAu+2q0rxBAMtiI z{^KFAr?78r+DwoI<_P_m!8O5rd+o_jnkZ6%ayd0qD?%981i|3`Ago;@J@7Xe+EkScrqq1at(G7@awEc$&q*pRH41rK#A zdr?dGgg8%XrWKEhgqG}Z5C@*{)X3B&I`OGuvuKUFTa7eQKu0{P-~={x+~sUHfiaa+ zWFqTPt8E^!9l}-WwbB;ppE|&m6k={mWjX^L2o{;FG0)2cej2&LS7YShCgm%B)d;6@ zElH3yGdP(KgxYi$k8s>yQ{PPM|BXH`ZN5jR=ClLI_DV$Ww;J_xw79TqQW%`2N1?VtOuAHN7*dn?_$2fD8LakhL_cT8Cxw@Pv(bb&j1_-ph+)t&j)0)_{4j5!SPNZ7dQtZD^-9^e|43oxfrcRHO6wb)unZUH5Cs6)Axp`?-bpK zml2>AXxhFA^hZGF{p*&+*7wux?M1z9H{&m9U|i~mo8TGaj7Jt#IZ;xbh?E3YWS>@? z?jG0EukW+3I+3;i;PLO97jBd#d`KY^yAvv2Fs+92?e64G4)v{>99*VcBZ?eu70Uu( z`am{%Z^!HFee%b>^7wlf)aql;SS9fB47FCpufomu+E=l3xJdc{?tT7Ypb!d59>l58 zM349y{Np-*5cw9!{?hkuVZ|rNS4RV~QyihDPJyHKL;i+@uNN5URqm^BL}=e83f;n< zv#)9o?yt5m&_DK!0|%W3g*~o|OxdAxx`zwRn1awBDRv+Eo)`8T)I$3iO>kvS#F^=oU{i<~~2r`@lyNG6j zUQ=zwlVH*{kkrRD2M_8ek^4f13;Vz)WnxpkvOY{dP8bBP%mjqq)P|G=U`(r@VB1xF{|__QCf4dg@f{eW_WJoPzmiEG_UePI&Wa&FJ{e z>eSU4$#2}_5L0Y3?m=AH$K4=*JWznw$E`u>q=LBx@L`99%Ud;{7X2aew{;Wn6cR) z)LiJnIZU}Zvv;>Jb# zc#;Yo>J#eC>zSR(3E}AUu5M1DKWFDf|N9l9_}|l5A9;R}EOyq$o2!r>$XOs`R`X6kq$=&853d39Xm)A4te>_!wxwYJ&a$!^KAA_04sN5b7Uj0>_ zs;t#kL-7+<8hzWiWoZ zzbFce0Fu#Qa)X(->$gi=p%Rs+61nmiveN-bYP^;tho-J*;M;I6$gv8cFwLo(CKq{=CwQ5&Vg)EB&Fc8k;xuYW)ShX!eFD#UOp#R7gbS@#%pfYc0UU3 zx&URpuUZRP{Vn?(FjWoq!*mjfNfn}2DDm@GCE%LjXH=KzZL#AY?y$n=0vD}b8+OBNZL)wzI#(z^Ld)lDd7XxP2!#%-N@<_navL(6WpHFKIG;5&f zQ>^HjVBFeDx&p11ywP0!HW@sc%EWV(T*HR$iBL`7|1kgFxjCaFYskMEdp8<6;(cOI zECN|A#(A?#Vi;!HN|ugd>#fKxAlcTcyRk5>EsYU2Q!^2 zd(RNG+Rp+P_T!Hr$S$f=B-Cuv>&Owj!Zk}*j;t6)pehUF4bGefAmHA^txfTwildCl z7lVC&vrdC(^RtuS>g8BGRsDynVUWWvy^qX9m;xzhpm%gXw_^OWFl< z`qWDO*Ef%S`(o39BtiI8)>j39!I#l~hO9udRwszQbWE<62ed#4o*8wIDfTty6^wgr zLNi~(nK##}VFoIjg~YyNgkX3iF6C;O@0^$ zm{1A-r{eJDL#FKbK_O2O%QWcsZ~XZ+cA1uJbT zOK`($h)zXGRol2Tk_>L#eze?zj_g_YFOx=>FWO_tJjMKsuVK`sluc9D;7=4Cx9@a{ zSHzs_6qS5bzL%@o#p>68#&or2jD1$5z4yP9e!l*W=0e$|W+*Kr!?wb6a#Z-Uds9qB ztD<^?+dr(np0r)+EeUc>sx!4FO;R@brl|hl?!nj-@Rf=71P6T5{)ukK_l&GRY@H8^w||{}9}}A@?XV*M|mBu}g${4vG`JP9i#51gOAzKGiIg@B;An;de{~MGHYm6n2oT?b*mH9Mehrye;r!Jl5F`a-49|a7}<{iN4M$Vdj9K4 zSsVYza>pmbONN5EV*bgDTfM(0e^0#lV^i1OOb+k;Zo{s^_)6NwBRrYXA@^AA%S*S} zw(C4@m~jH;;2fV>=C5Mhi&YQ-n>a~g`N!D%Z%v4>m7BR&=yNXyF0<$23~+E=Q?yS) z@7+`BCV-UYH;yOB!`F5Fi>}RsXWebOZPWHwnsfJW*$tlw598aalkBcns1aF+SGdC<)4@24|Bh7$JTIsn$r%gNlwq1GV|o+sk!XJ-|FQO( zB4VJPNuC9a(P*bjIsaJ^M3Ijx5JZ*$T$)0C?XBgNc8#&9O->V}vD1n^J-nv8-d7_Y zWfAsbek%8Wq$qHHo5i*eLJ;Z40G4I1qQDI5bgH6J?)sF`E13;GW0HziIxDZAper^i<+t{7pizI?Jj z*pd}DttTw%p_&(v+K}P#kIH)ep-bP41hlE|+?#bw-2&WAcO&_+FKV@`x9uT?1J4yUR}7;(TbL04(J8ZON)OmUemVY+QaW+gAli(X%*uFgWVLYP z@Cya9p#gON+)Zk~aVqns$>raJ2+Xq(qk4zdqA?Op1L zjax)}8K9r2{%f&UHMHBJz84Q(X zH!vsl81yR*?uW_G!XzFsa~#H$O?SN1Fkw|^ju?Q8p)|cYYmHGU#fkUU2OmJoaN4_1#v`Po>(4N;; z-J+3hjkYaz(I-aIJf7mM<6sjBpCs-G74nzAIk zInWnBl;p_1j_AQE@O>SfTizCuN)Uo|YQq!KAmnX?iV3wZ6)BlrA%d$MIn*dKqH#LV@zvlbCy%x8WH0to1VELgl$7m5Ev)*{Oq2{YRtA8{Q9O) z5)aY5?uXe`F}n@|^2qnZM#go~GybwM?L~#1fSjX$KnKS@-@stC%^PV4zU=pqqMvTS zY{@bP&q*hDyp9*Hqze9Lfm<&|mV#3g<46+aXvwO}0WG961}8QoN`?<{Bij?k2h-k9 zcOI8EWvoYUKWOPz`_+{yJxk?76V;`yYvh%l(OujFi}-za7#Vj?NE?<7jQ@#VwdINm z59QMKbvU!{r7kB+#pVYz_eMV+h?usd{4g?j@h_^J)T5JB!)n?#L_B;!OWj7gi!UQ0 z@?8yz7c#I7e81HCiT!WsFBfHt!YC!I9e4o6BU%+JW-P(8+APJH%6S)qO^0ZK?1NC` z{BtGon72sFvy&@L1syt-T_Je)|Axxr&$@g!LX-Y_3DTT1iLD7F2>g&3=@8_8m<^u{ z{ozVuN8+sa0U40k{K>T;N-yqdgBlO$49LeDG%LsVSN5GCjFI+)lQ8-PE^T)P7SN4% zQu|Iv9*I%K>LLBxvJ3F*Gnp@x@rLz)qTvj?=jWuV_epQm(3PnxV5-+Q}SGYgz`qy@O(%fO-jCO~FJYvo~*BJ$>Z zZDLD<#u!Avv%T5f6jqbV(6!{9sj%mnp~W@wH#iP;7pqRrDe?+voJ$&m!Pnu8Q>$=) zqH999G&LM}TaVo5>`s&GrAZwjtV`S(dCKmgmPd3?r+f6jxp!7gWw5mI@}-G-;C zGFo+%n=$2p>??HjxPiEl(4_K5Dkq04AoH6VA=eVhU)Rb0H+*virR_$XEob+BMrA-33)9i6ME6Vo40(* zSeSdaZ+|rF=_iARYW}PCo5S@1?)k?Qyj9v5u}%GcqeA2IZ@z6-@fL$0c9--;(CZ&F z#6wFTHuI5P+LA>FM>b^*s}~(K^>}TcmX>wXLYy)p%EQR95wljKJHBe`Be9rk!Kmxax3RDLAx|f3cWJ#(23sj+V7>ji7=Hb-F!JT{>yc-8Gn(TuP{u(lo!%}_w_s04J<4> z5*^a1og|MG0EY5r6keg)!2hOgq+mTHc7$mNvd03T_@7h^4WSnFj`n1QGf#Ml6SzRR zns|Gf*uKRae-#3H{<+Cm2YED5qlnOxs6Q2GmeYr(gQ>Q<7zKA8-;VfrR$BRIg=as- zxF=LbQ|{k?8qm9E;trke_@?iNU^@CZT|lmV zZ;2oXR`W=oSLi3`quRjLQGU)fcW3&#}#p8_%NMp{L>ydHK_YudY z5p9lP2=Wbg)Ws1Au`i}q=_QrR)>m4zN} zhew@ic#}OT*hP*abq32O_Uwj9d;gX!zvI$!`3`9JYKE5WD5!bjnRG9C%dvwF(&eVYkEu~EN3{Cqhf_p~aLeO4A zDY$uR<H~f5;6;fC839>2H^8BVCphIsUe$Q%lbS--+Yi)9raS(NP@@Y`< zVOv;E-{l_=k}_FtpLMeq~yDGi0> z1bAk2fHBBu^L1uAdKCVnxl`Gv$`>FQhAMqYy45`G%ZCDp}Wm- zT5l>}>Z=bowRm2>`P2i#i2e(c-a#43)7;6{PjWD3YIE@Zn|JG*U7b4NoQ~Ym zS3&BpG~f$p9~&7RbI>`s$(wk`H9-n$qL@);p0nLB5~T#SXcs!Np%XxrJ1qQ*pMYB$9QcrAa&!HxL|X8umwG z6nZ144tO=CZ|9M27(51X{`G@TvU=J}g0xs_T60z zrT;+4tE`QLmu?po9n$;~Q<>GU4?lJdGJG*P}0 z-=FL{_`oq#>oH3Eqr?q5j!3TI(8p$e1G#G2sKEue-=t^2LZZh=hA5Hzhg7L zP1`h(6grgJzZN%Sv~Ifz(6m15sak;Z{vtF5XE%a(mZsSD!I2RIMGv^-_Cgj4t6fZW zax2(%8G_v;^q8yMuCZLxsC}4dLhL2`c+s-bSH{1}GF653{I;_|i8RMI#m|p(aDL{l z{1V0pVgG~xl`gFSJYMx$+x?=0j6Iwyurw()LEF0hwS6zcHM!(sP@sqF3Ias7Wp!2a z!n*ZNANQPH87?v-<`6D_C}$5Z->5Bp;I)E|F=IS6AMHVeO_eBuQcP|*dJ&Y}pObWr zbM&A|4kjQAoPV)Fv0~;lC?j>y;P7z6*AjM62|Lru$4B)>sF`6w1k083@>Pt-iBcm; z_r$#udfZfZkPw8x>cV9&&3w8EQ&vAHzQ#(`G1Z>^z89H{&A5O%~dro*w) zxYVHza@cl3#DBp^qau||HzvFp`kG!ElRnq8BbQpxvHOiG+GGB|HJ3?15)96$`RkJB zeM{WMb=Bn4bIj@YTcNv+{9#2Ofq2-8BQDdfXN`9eJMbj~$U<%qlD54Ps}ZV&WO@~U z8AMN~)6w|psz#$G-b}W87s1Q-NU9Z=BHlX8rY<4uAvM{b*OZbm;zyAd$j@5HysY;~ zLKTt1066C0pRpXz6%6+Bl?OzDA|33}optob%myVzu#j3z30u#{uVf$6{q?@}l{H!D zWB*5ePv`&i!Rw9RgHSf5!n)M|L3@4I??;DO0lWu=Y2C7^v>U_H?Mj|FmQj=P$ok5g znRV&@;u~GRuMA^dtce!?T*FA{m3)6+Z+k&hBp;XN2gn6g z9cxk*TY8a7e)(q{p2OABm%qa8~j>E<=9VTEJm_D!}*scGF)Ly ziIq5P*s~RN*9^T;IePcgAZhBHh&1yz)811Rd z#yf|%Y0My83PQF+F0&osESG;NX%zkF@!gsoK{LL+;(24^_VGR${k3M66%T3VS&Snc?iVe7Griw$ZUGtJ_>;XX0WXDrGb30xvxG}dd$0z0>{Rri4NBTL<;O(} z=H>M0A5Q2qlRy*DM@i}xJ-hTy(izPXxGzF*eRG{8h;2eT@hDON{w~@N%X#3{mAsJU zjEw9!>3`9-sg)DEKAAIABne3`g`6-j$snFIfS_bgdu|4KEhQ*24yO_x-y(-mvo(v0$pBn zz@f|t-TQlg_tS)_&l;29l@|mTGIe&XxvPo&_K!`o&A483qrB*lIx!581$}|cGt-b5 z8-zV)lJV(AhvNAWvn73(A>3|2t}-(OQ}IuilYh1zIYh{Q3PlPI*)XMO@%mq_Ub7Z7 zz5nCm$9Adh7L3(+Ed7+uF+a{tWDS_}UF-}fl64=dR_7Ix&-KJN{@1_HrfTm`%A-i{ zLapG_(B7kWwokMvnuW1SjZoq-9i-8cf6y>ZUo~>H=;WAyTw0K!Ey}KWff^`)<}r3`2 zEo;8VPZ0kryMEEzgNk#vcRL`*Kj5>-8-CV-)jBs+yeFZ(+Xb6NMb*26+>8O2Pq~W) zhGhn2@HVZL3gzvRH@5A#k9~l8$qx@OfL*qiIOF*t|25BF?X5Z{_xaBf z7ak<8-g?qH($PNXVca6Cn>d>=lXWX{B0bt~-${<5E#}*l-v!Yo7SFQ+tPG6*Zv~g=TEi%IIzD;nUnLt9wh+C_F~q%>?EcwFB8qY)Hs< z7l_vAs&IXRhC{X#ep_Y{~5xy0qAbygAvYGRsnH@vq%52NE`B1}U) zgm21nJe+P(At5wgH8*O&)nHFa~WxV75 zgr_Q)skF_eSZ zMe;L9KS^VcC;Mex)qHe)@Teg>kWrayEHL#rpNZ#?d%jB_F2i&PC)pJ!sy!-=+*-oY z!E220TX2c`IuW}`r=K-fQWHT%8uGzijhP{C1*Y( z&M^i%RmNz0t+r8~YkuDm+a4nLL8q4mgFonC#CG@vJe96{A$yJ$C{llL%Z~)O5Tl}( zs+2Q~T4oOc`+OJ4et!q_aDq_W@1qK2&P5`1;#SUdW&oVKj;XT+^qf{XnB?fE^0}vI zC)|=U*zH1(vKPo&of4a+5%gLxnM2EbQ1J}b#R!|7IHku<>eWx`L6lZ;Sn?&evI%=0 zD)AtK5+>K0!rDnPS#h_6UZzlNK)mdlK1Vqfnt`e1X*zv2@%S{XGV;9l3J$r)u*hwtm%EBU5?o{d3Ly<%+W(sZOLeBmRH{j>DN(QyPB@a#WA zz_PU@1dt`Gc`gS4!sH6Va`HoNYu8u-32Qsm#CyBs$PGHE2ZcjLC%FVm7wjVrgq8>u zeFI)dn@>#gGFRq4;7UnRsPK->$~!$mYRoBEzJfqf#YURl?wS}^i%!_1LKWOnr|bFhchT#zH(D^CByt8<(P zp7jT){Ui+u?CPmsj?Kkt2)hb9_Y$~reCn*hSruAJN7W$z3I`8}!yjBjQxk${vj8{-A$HzbD)4gpIxtrS@+ z?+GIC#p&OMT(AR@iOiCZ86J;7@_6M1qyD?0zrUAf_I}`Wci8jaR#eYS<;F=F_d1i) zqglMA-4BZDGXlC*wgZFG-lN)xS>kff1Ul2Z|5T6%)!r%J_wg)7`TBxfQxC zvLLJUXicWh?|b+cdNoqr-P#e3;gD^HYp5pEzD5TRWSyWHJa!yTyf;1CgG|FqZ}8aZ zv7z-VOy~_qU&myXv0d0qWY=@P5txZ8_pBNbj_Z%Fc* zS6Yvz`lr(Lm6{(;zZ6VtghkVfWcqzs3k%c6I*|05VKC23nQJTg_&y*wIZXA+PMh}K zCG74@S%OXWo9d z11X=-$#z5oX2U68%1C5Xy2B*a<$2oQc7cGg8a#T}Rn4n$89H!{`qN)AedN?~MY%0| zx#<}a(1Byo>&SPEMlL$(e-Q0K1!7$l%k^o5KyS8WH&Fn7Ld?J_RjtYg0c|8r!Eu`t z7P>^SQ___d`uK8~oblC3`q!RC7A%k-Itk6C0Y|d4c8k0(kKqG4)b1#I#9hLuhJyQB zP3io@lJ}q>=a23p(vESnnNeBKEZ`St20{)!=zqYrk2b_u&+VdM?-=>nRy`6O>u|v2 z(PGZ960*Iiv%_{u&VyTP;D6j08U-9jX_7l*igFl*TwG^^j!w*lb~J-HHv{;FFbSWX+ZEt_r;et z9D8Qx`!1Pw>8E9{iA?OoXbEqLWJ%kaI-4-P@rT{p$1d5EB)}`XgI1Aq&v-@VI<;v?akP>bh~f+`1I?FK4l*cXq4lDo1rxU>rn24_vDWuux>NUJ_P~kvPkiDs6_9QG z&)9jwZen;rfO8XC>tJq7QH+i(F=9fn3b`|f=Dn{p2E@*og-7kAQ`P&;*T5eVYC#x z-8a!j*5ENiFREc$>d7rmCzal|)dDvMu%8d{jyPw>tmBYw8?GH4Cgp zHZ|@Ad^%1Cn(ty8Qob_OBrG!~!RCmc`nO#!qrqt2@D<}?GF`IV@m=m=W5*_VE1$)4 zLu3!%Ck~oLx5?3&E5toJnGlgJ1YTdx9uJM#|Y|GUhfqcOCz#9W$WuF+DRsPTW z$U0UjW#df7JvIv6}j{!l9k!z zVT5A#gv%#>jkDUNMii|#v?~~?d6PySaAiq6mVo>i*<&*zT9lWaU9t^Cm@n3Wle8t~ z2mAiXusXSbG?4C!^cJ%R91H@wBQwg=YKCZdy)5!V70h-gKY}#kHKqNNJ#8F0x>AzuWPF_VXrgGHl$n-ydso=vGrm7U2G6{CEMW zxpggF--Bv`+px3gm7(oaZh6;9*;*^z9wX8(;U^EZjN8s*o?8YsV!Lnp7~CkS=bXI5 zZ#N*SBOf6W&h2{FiIT(u>aFkHRquUnN&7xz^6uT{izmy~oOd#zVR!tO04_up80CRE zx&qNVz9%;SUcKnbe>Wh`LfsbFi=~T7xV^IY`@>StmeE@|q+85XP8hLoiFQBb5%+OG zuA!Z}Li)q*Y2qEz{$VigB7F)Urk$6M6mSNw=Vw61Ua&6MGrXgc+RQU$a)={+ z)H2quke5Z&CvW<^-;HO69iHT#QcUl{Qm5t5zrUY}eV&7*jZzis(7XxXPIvccoQoZ8 z+K1t3`wOUH0BRRmB>xQ4!TS55{`@v>5UABeekdseY1Rehe$!SrAelD@9EWhz(u3Eq zrSbD!bs{!x6}YcE&FQ@~2Qx3$Ys)%o^s(~}7w)W>Kzv7J7X)yP>j@wDLV~FF`!ktX zmjzqec%>qa02~{LFToB=D03d{KDzdn4h%5MWRz2gs@htIYei^gU%)as*J~CLw|$Ji6|Hv#RdDb%MU=gwS)i>$y-jZ(+pFJ93Gb64 zXHNb}2U9V|dz_?tL%w9(RN{2p8T@JmG}LrDEOeFCD|s`)W;0#EDOA8Yd&~#t6cN7~ zBl)-kvVWBlKy>_Dt#TRLj0E|Y$Qdpyo#U<8O#7yE*bE{*YReE$4Bi;@oK$7pP>PZ@ z2@K5|WiPtblQ|bSE3dEq8(t}Lu}a+k^w6iSdoPGl z)@@`dQdS8nG!u8Qk24)%)$B}`D_OT~ek%+#_S6np(Z zgScRV=pxO?%TA#%cgmmLbtfIO!)Of-v~&9ODPkhAb0J3)H= z9d5|96nsKE46HtTwWp%M1sN+yB4!ZaAlUts2ZaF(7bbX6jpZ$QU0S-b55y+#-KgQDxl;1!8h!y%xlt>Q^_}`xB-uywCVLvUj zpM3J8yRTfzI)yZ(_Nh)yR}ZEz#T=J{80#rg%T0@9`3?604EpC<`%P5sEDM$Oh+oNz z02tP8JAP=`wfjm|XUBn~nqQv%V3dfr(s)n)s1?D!>r{5xeka0wHdZZj3 z8Q69j58j9_4(PtY{;;H|5_ie+JK9GAFq3=vr?9MWaLFBC7}d$zb$l7DWpOPdtJ)X9 z0uquMo`;85tnb>4PPMQ9Hg#LGd{}2K=r_=OdiQ@Eoq0Tz>;J~@=b6nugE~l=A<}Zn zR-rJXjaFLKsfg&{or7n4kvL&3c%ADpx=EWz9=Y^(P=SLqB<(LWY6;d1xib+ zt>Ng;)O4RcK|?Ea?n1uS2&Y+)d{G0ACQnds7jrVEjwDW*Nk=#Wk z`r^S6^X8r2%6l!@gCX`&TNP%KQK(DiiXP3)V=lGv>n1zH>YTKScCo25^eHYuobGaM zCNnxK<40q0gBM0}#zIn-e&@@~DJAV!ak~mWd^OlREt(Anl}D)~5>ont&HyYc!zwhk zC~Z4WkOp&o0h5qg-Ph@7IO&_wisPS<&}CVsGnCwm*kIT#R`g2U4I8ZVlSg$q^>vjg z;{lFH7Q%SN&9W*x^5pPI5_=2e7V${M#%l^QE7J{TgFBW?@@(q}@kb1Y>226v`q@QM zru!l~wkZxiPx0ulB#EMK^_Kn07*5_ovB#Co`Y8+i8S4gctv}~TocJv%>)Jm{C-%lY zyW$^M%prl78lA<9)tzKU>E@XXqF1K0Np>UXODyzi*}Xzcr*CTC+grS5-lifN`mng5 z<-KrR_l!`^@e3k}HGSp2IGS>oHApKHpCVgB_cKn;dE25IhIg(h%(~aZq|@n(vvFzu z&Y&lCwY~uPub*}S@NUM!j?po$wVCh+Nw=5C*_4j5gkOn3{ch_;0*lHx~V-Jja=glW`fzT zESn`w>*=3P_5j)lNgaI`lD3bq`u;?Z#O)y)uONXFzUsPjQ;zmlv=(4BGORJ7lWFHTI`>)0+9X{=`S;JKUzv2W8@X z8&M<{Urg6GOz3C9tlAOJ&A8MEfCt}cwtd>qX1|Zd_|RKOiKM1 z@NZ(ZdkfJ2iYL5!|m!0#>L2J)W!|>APUK(1)b#IS#Y=;v8RY z1zpE*6H?`>9XW_F63CM1x6>&2-tGOW)J#md%gQzCxi7&JGv0Xm;CUGJ7+%hWiy*{8 zYBi>24=r#5fEu;sU_}I0&mbQLpRRG=lf3deg&~#5g;#^VFXl4Zrko6EzF#bCOF-6t zt_&;QE4w~$ym;@tlP3brd-A(8(%8lC*>nh6Y8Umt8hb(>co18)x~A==IDSq&sV6!~ zZ7I=h+ti*H=4g{WKG=OYQ1CiP)WFLoJqj!^%RVy$poY)0Q=XEkEGcugTA!}Bs9tZ> zZP#+yyPx^*ZRN=K!B0E{SjgVwmc}oa+^uF`fGQ#R-7*skT>%b;lxP`vd-dn|5>g+M zps_bcXkOX66W=s;&0*i2BDEp-la|W8Za-uuQ6J|Cefy%pufnrkKVuF5%Nii4XOpi_ zqCCpY4LS|32Si=aKyaq!YDNrM0wWez*4bMXa0FF2JEs@B8Q+~l&@%(>U$dnzTA1|^ zl9FfX4}ot1@SINsC&A!c*rBGmV89t(fqlEnd(eFcLa;bDMr_ShIFKNmYk{fWFpCK5 zZz6xdzmD%y^>jpUv3g$1dq)9}J0{N(rdF0{EC$h(H#lNy8tVf1K7lP(8GJ~|d^I~x zXYQXVWu6qqmQBhGhivXozqBfP>7OuvSR%)*736i~g>82QbK(U*jO?AB@R(15NAi`` zHb{K)l+SvN*acOf$I=4x;yr!yK5}!pQn4f~6G*PKz>mu-)Y9;LM1vfy8R>mc`r}RB zKNcux_JMi^nBWH`Ybc-{*JisLAd){MW`weNGQKS`=eq&uyPwXZ+Lh!chJ^}EIglph z3Ye^;7r39%6vM%6&>IX^wZ(J!D&2oc6`W8&{MOz;l5a<{=T^;GI_fb2%?URL+>eow zOsQ-;iX<~Q|LzBmZ_jTP+>5z`N=8aiFe`i(&0WEH)`e%J1x5ZH$bPCOyair^e*#D0st`VpVz1*SH? z0r3zqx+2gYy>io`^lvb8{5r9cDD5P}#E99ZKys|-!GR~d;upjy>}F6K_VQdZ-Re-F z;0!)t)8ECa2QOa!R2&afq-51pM$~IFhPI2GRQEZWXX9IYeI@yVcJLB>JFJa+c3L{M zDrgIxZ%d2~63P~dHz%x_2^o`U-9zp~>X)~@r@Q!b><#JKie$-jflr;bR>2vNc+Dm# zz0-|We*y{DjPlAj_p?eH2)sXw+g4t?2TKK7N%%||r24J{y!pITSEyhYz;=x<{_}xa z@i!CD-Mk<^tL9G=*JrSUsqA~@#|#_HS3ua2ULMGPVR!sxB?~E)-t04En<&%g%cs4t z>t!wY@`~_wjPZHAmI4o}>x!2QF+OupJR5O)N*@Tv%iECS=f7qf zopE+%*`kk$CjKRhBomn6fHcQtQj+RA*#X-z-@3J7c`)SLgiI_m`Q`jT-e-6*EM*md|oMQcuEzsNoef0zk{A!_rTK!fUgnQZw%`gmB z#XjNG8tZK(NAhA{^qN<{!#E#Hu793`kxpuQbr}t#l@ql4CLP9w_wY zfRSEj^CT7KKQJ=59@%>K6ZbYg{EvANB;Q^3wHFKxavf_HWe?3AY^ktGvb4^W3V!?I z2v&mU(y;o^!nyjKf4Fa9I-(xAjcu)&g`_?LW0$3Z&gL+-P|?3sQ`=6ed+;ALKhz8_ zACVK}Sra+x{DnP?yq_@M)@W^pS5MQZV0i&lSB=z+7)K51wHyfH)sqNgE+BsWb$p!yHq0d(vylrY zN$49hZ=`P%8}p^>x(H9(AW^`~#!2*33`&tFei7IL>(=i-;-=DS3o z$wos_qD1AFM{a&Z4imd0b)(Y0sHggf)ku{G>5ipy^Hfc;ZC)j(NtZL0@u2edBnfv9 z5W|y>2sflWlgLX7c_N1pZtYUr2on|azJ?F zET_(t5}H%zJ42yoh?16S&Bq7^%s)Cyf7hlJFCVCwD3)29Sm(KhVTbo5!5#*%Q9{Za z8J!4W9#wrzTn6Wr%6bMaD&1SM6AV;=Ew&_gWQwrBywGCb@=2|+_~RDPgitv}0vaBc z7k@b?=~#xnBDBW&)cgk9PJFJ0^_CaPe#q9xR)ol^6~`-hUHTH<#$M^>Cak^j7MtGt zjH-WeOgB;Ru~wG$ccIX~Td!Da!Z4S@mu3>(REynddHfv0OfbuKVQUbV z+skJD!=LGA$Tub@7~fN;o%T8HOE!^{Oi?vTX$WY@kNHTWNYX@Ui?0<=33ebMSk*wZUIg0j3Vrsd@KoPKv}xyxlB z2*tuIH+kpXXRQigy`w;U>eE1Lkb_GP#3PsC1l>#C2FsR zu>6f8^+kVT$}n*z?2QMj;I&0y7j-QFyT4j<1Q%7LBZG?w#~`7q7&^}=E9ZxoqRcf$m=?$}85AxlAKl|I z3l8I!jf{EpYmtw!L0gK_P|4IZp2wY%=~##%0~V7Ox)hwR6`YMAm)JVdc2Qh}%Y&zm zSKFV}(WSkN^prPX3rKoT-h;K6-hfd3a6I5V)vlCl&#n1kA_rBOX)+?sx zSueZ*)Jnm?XAx}g(A%8K6N-hL9F2`OF%(P|LZuDaF%nD2;}X!}I(-2igT^f>cM;aH zpwLncMX$S6tJsVR=%TF9&cKf?o$2jV4+(1PEsw-T72(*S#vPP}x*B=EBhzVE+k3zI zJTow*Y-GedgTylq%4=?CEKp8!aL}H_*=WN?sS@rY;VFhQ4tnjO+4n-4C2Kv-LOC7A zTtFz8kzH!qw{w(dBqm~p^4U-YCea=uZ@+Ipx`?Lmb$nww`=E>IOn|1Yv%YtJjLD;a zpi2b3{{#OSn6&8LBgm9NFB4<}6u(;nzGSr|jK{~3#$q|j4iMqj274R=o@Z)oIp)aOALB*&`fIOD_Zjkitnk~xc1Rn%7 zsyilr?!tjk-E>mk>&PD$`Du64j#@Oo0<+g6btIYxBT6<0`%IPOXR#WO@{J^Qy&Ypc zr8$QL)2GqK$u5*_t^ZV$p5Gc%9UgK%L=qxwDXtl=Gx3rGLm~E(R9q!O)500ax@@>0#2Mx!Ya4!ydk1hrk(HR6aWNju%8PVIfT~I* z{>29DyGtw#k=vDsM!x@;DE9NBG?y;IBH}$sI>H#y2niPH=WsCo(U{>%vC5;$j9&VU zBj4ou7TMG+fk9uL5@cwv7V#z;s+oQRb{WRLLgp0qZ{tjXyfow4a8R83bz1ShBJgQ8 zge=ZPqmSf;!U~6c+{;n=GRZ9c^i#93F>;yt;2b5jq;|F|?U`BBQHIdNCCUcgS|rZ6 z=Mf?}(&(PAHOn0E`77`^Pm8cGVFkDcu+7<)sLx%2Q-{bCx5aC6-pA~X(*My0UL@fA zNRS+L@h3brSDy$sxd|Y4{sc^Q$=z#2S_5wMG0H%XJfK8d+4*T)&eaF++mzIa! z5B$CVv(xJ*ugj!gjyHTiceFhCs(<0oM5?Ouna1Lq<-2pA%T!TJUc2P17oQ!V=)B8s z5y@iKae|aX$LO)%!?M>-fswJg)Js5r*s*TJu(`!ZkhHhcZPevEwc6xyg-Uc!dJ5kd zHgl^XU*Vr5zi0gCc1O4Py6M$ywWT_1!hGzOdGo<_paO%DU?NQ0fUmiW zp&WPP#0ueX_e@$I*IA#Kgr$PCAk3;Pp<8?ht_#ggPUT+XimmWDkU$mije}ZCz+rN;cfmC@wFQ6OFZ|r z8t#2Y{n7#sBF3jHk?4~o69krXs`Jf-*}9d)Z>K@ccH>kcV>rwuDu7*Ny^iA!{>u7q z#eKK3=?iaU7za0|3m+kur2DB1u}FpjwMh3c&|*qr;AFq|~L67GqmZ}=PVzYf4)dc{Pj zuE{_RuITSxPR~X(7bfBMTj)xU36Wmh)hrHAR#OZRrAUHPigA;nFR-cSCx9oA%Zk{L9 z9M(UAOoL49GHixQ?>zvHJJcd;A=`KHq;Oa(9oU)j39HiqzeGgxb0g<F zYnrD})`BK%qQrL|X1yV7EH=z-DX+9y9~uNi_xbH=`&bW{y~&2JOP%Y9^Ja{?;H=?O zl(*Y!K`&gkO(dznCO`)tXf{Ie4AtIsnT%t<-w0C~w*QJcV9k{@n89L4un1k?xV;@MvQA(M#!Cepq>YGKbkBTe!b$psD}IK7+AO-y zn298fQZO_bsj7xGUeM5E5@LQR>xnh$X$sG9NP=XGoUjGL&dxwLJVO%KoICf9iT%x= zVqEj|mVEBTg(sbBbxT|T>npE+o-IVJachgkIcJ2koTtqACE2M{;DLYBo~RS_zuZ8) zPH7CY-iI3U%^EjpT=IN&@ZlCLX?#@eY@U*DuBOMKm`*S!Pwa&Nxij&cXFLdHm zaHz8BuAY4kCvkXn53c9;VBTDzYDZc{<-vLS+i&q2V$TeaWB{ISG$w7hDoHnD|IsQN z_1wOJa%gwO8N`(ly8*e^Pmu^mj2(hw3< z9Bsr>(v0_Z><@l}{Fn&iOQmC?-?S6SkFv3Ub*(3)VzDR<=Wcwk^jWm>-z_M+NcH23 z;8r(5<6bZ~>}8YDw%xs3a35`PIiWRcKaHp{j4~W82rMag_mT!6uD7kjx?)|I6-#E z%FL6*mo50aAl${spSO6WD`*%IOl&xby=COUgU1A`!;f$nRF@L1>F4F>%(?p51E=r7 zo8vAl;l-8KASZSO5@Zs@v|8CC=)Lq3NI_!svav)}z>oz!!Sj^Iy9|+Khi0cDeWh zNo{w$j1HWZ(0rORfa@lqx^(>6msA|2;H|?Qxan%Lzd(-c!8YQzfIFx~l_s}GNWs>b zeLG-6fO`Z{L}gpWcV878wp`FE_q=EwJXtlSc3XonS9uk{RWZJ;#GGw*P%B=>cpQ4)VGCStxp|qN9<-d4sEhdc4LylMzq!fk)epp#!elnz(cahDox`>rp zUB&$Zd{-@b1ON-?uYJZ#J_z-|ecwXb!|iF43%O6TW0ZeCbj$?zHS^A$KiCV{sdQnO zexqdcCUJqr(bb(6zYzlVJM&x0E$cq?el3iqDuo3;eAd;ETE)(Sh;pROO);RQDixna z342m>PUDq6YXX7(tHgsCf#1&EB1p*=~1LuiU4HWJtMA>LdyT%jj zz-b%dGz7Nx!q9kXFa>zfO3g5p(YX|7_<2@G?#m%Hp9B+vZq+64-bGKiL1|^eGFvcL z@yO{K6{By9%2LgZJpVKZs5Ro$2-s_FS1QCCg>Q>{SEvw@l!&^x6-+i({5! z*$Co~&(mBS%e64h#FRykM@id_f@xt5W+iLfrK=O9LmxnhCO;)VmSZ6`Ez0G zu$r5?7+dRbBhiNDS^FDdYkRq!`lD2#|Klw9H8<2SY8ZIP{65N&j*6!lbU$TIokyFB zzjl+x#K{Kc75EKmGVh$D{kKaxFI6m0u&W4jWgIgUKT4I!IevP&LsP}TCjE^QWh%#g9C9^03K=Ur1400c@oOGzTn;m3~Z;tq> z)gk|TgDY+*Sc;eH8qy)Ka~UljG|oBkb#=mW;ugop3A>|AmSc3DAk0dwj>@IrLgDI7 zXr3v!zd?8q1)-BXDR*mE&o97Pz!4^Ewr~fq4VR6evsGRz=$Kef_;n0Xc4p``DV|wR zPV28edinZ6%TscSF9Rv4^xdn%ogiEp&bJI-xK-(w(z2-7GU<20ndF^mx7)8vy}pd` z&f&%ajnuJ<&QiYb*LYo3;YZD3HcNC^;8c4fH`z)bu#py~quS&xn1Oh!Vs-MXoFQ0J z#2(YDr4VHDi_GJyK|cXSar)6H)m`zngRosfiy52FAS~u+LSY@Y3aLl{;V2ChH9Yez z3k!slozF2IM!omtTjriNX>W}zl(B3&{2z1eIG0u-|Mdj-*bcZOp6`G!{`*nbt;wLT zO0$Ku0%B_6N&tQhFKy)BBky|@77Nedls&q62wZM`;RVHdUrDs~ydp)$Na2&@x);y^ z*K6KtdxEwp*dBt z=dV@$Cy6vsS`_0`@+-KMsgT1)LfD3%FYCNqOY7INNE3EOD_y~(r;ru zxcLYyg=q%2(H{19LyPIA5oPI!0ZaKud8;R66b}78Fkt_m9}pGl+RZ=7Ce7@6C3ahC zsQs9aA9RM<!ytdzl^bTb-*rp+ zPD@us;5C97LXJpYWX?%QY9R*$2hm{ruqLebAx0c4KX(1z}PiXU(S271IXL_C-D2? zaYs^unKIUq4jrM57$U}fynSPIYppEgYf&1WLhL`+{aI1y3O@K&*<8(m52KkpqZx!P z#ZLwlV$Xk}f6LJyFB+B*OP+^h+XvrRs0Sto`;%@J)_)6#hQW$u^k*%JGt*dJnkj|g zVO#s?Q6&F$thcmeB+@Rbkv%Bxt}J@YkLV^I)of!%9W042DU3;qqu>1!rc&Au><_Eu zrbhnjKPq*U%6B}zc8&{VmHBk)+bB&yud_aa$&pUyP&4#FKcg$EL4(~U-KN*$GQCtX zJi@uw$eGvbKNH2KQ4qh9>EM|Sq)YC}3uTB+J&)^VfZ5B@BOj>}4;RT>!A1RA$SDHy zrDk&F4ScqJ<`l-Qpd)C|?C{C{o~8wjoK@pP*MIGSdjYcPp}(pEVM?YV)uMR@2|FHO zcstf!UYkN{9)D(@O2)D0AoeNo!9i|+jkl3ei7oi?GiNa7C%=-=`POj3Kz`RC?(}Q* z;#9C#749{TEuKYL5Ct)%D;OE5+;A2@*m}3+YNIKq$za!~#X~Z-DYo9^B91Z0&xWpd z7myRpu19FgC@6y}iANcC_`)Dp$?m|_u%b(beoq=nQf#$Nn~6;8PGtTAFWg%h^j!!N zSZmCj*p6U$$$OG+BdVdK_bdEX!JZsWD5KtR+0_J*|Dd0Jr=6&w?izy8H zv2w=Gg|PeLLB-}DbJ0~M!dvK_lsyILN|XOVV98OyoRKFsY4k;$xEu{uey|GSxlJm z1==U4A4jw?W89mCY5cn>jtnOU{xNGDoMUf2d01{K`I!G|3$f4m7q5QnC#1J$^6SCs zsgsa-#OqTeT$k`ypvo4kZXPmfx0^!_` z(QI4F1Z+i_2?H?!Wma&Cy{MACl-n-ji`R73E~>8 zSZ1;Nd3U}0R8Wu+4p+o4FB=mYvo~~)_B$h6aLY_!Gs0!$0`l0juKQ=MuRwwaAqW9Z zHR$0OJ+xr1B2HDxt5p_oK;Q9KGXKKi94e39W1Ndb%^gRT4lT-u=N)=R z)H!;-EqdR?&%pJ9Bo|AHe6a&XC~adV>z=LB*s48}#B`}%igLstCMshrWs?x~KM7)A-J4`VU;cQx|7Zh7eI z)lYNXf2j6+bCd6Gj1q&a`i(7#9S1@`;I2BO50_Gi*QDP^y5Hk^lN9~Z1#hy932Qx{ zuq)XAFWeVZ{Y|Hp;-0F;98MDPeMe=SVM0_@sPJNs00dsqLXPcaJvplOncY?GJEn&4 ztx9RP0c4uX&jNLkvC14|M|6~ehxFOVfPM6|5Yl>!-%OKMcR$^13&F;$dr1{XkS9x# z5jC!H&yerFg~Dd~W#iAgoi#e*FNFh_(4*<*G=dP8H*JzngV(3oGVxn%9PHx0AC`J? zj|N5_toarZ6_gcLbwi8xhf3R7*T}RFE!y$K*@{KV8-qU=ganB84h05r4Ko}9a*hSY z-xQ}jUuzo5F|s2AYoiOh7q8{Dh^s~YV)ulS-#*gC>WavSc(ZSf!?Vf^mHMfR{x`<) z++H(1uw;748{L1-%+KUtR1S$_vn>rfMsYAL1_aoRV`=JO>(hX9QTM&8G=kd_M!m;~ zdi7|0i>ZlQUBg0o%NfiCVL3Esfz}aL2EH#WPUxJr&^0g2^nY=TM_?uGfbcXek0SYM zp6yk^X+huv>N*oon~k*CdKtv^^s0V{=B*w z{JYfD(_eCj`ohiy-irU>VeAzzIS2O3ctH#&QB`|OYpPk^8O%rTHBW~O!m3PS*&LzN znEa7{-{?qinu_52x_`P)c#N{ey)6>1H&!9o7K~qXjf_RGj+R!Y@=cnjv{h?XYaW>k z6V{79ddEqtQw)Ez1zd9R5GN|B>+<5$?p3UDx#VN6Wnz$~zN^OL)`A_0Er+W&mr?_F zBr8U$FPBaY-0@fwBMXQt>*Z}c?ZF!V-Zkb(U!-e<1ruT$MBRYuRPQ#~^$D~t7fzTP zpS}}c)YpXK*^xtuJJmtfX(}thF#{*)+vsPJrElP&@k8SscH{{0;n(u7+nXGUUUhdv zMKdVAY{GE%^kS!~1M1O;;werOn(^w7;;Lk|vO#54Je;zD zjba8VUDQXC`#K^os&PqjH*xL~NjFP5=qT@=NSsN#2ZSM(lD&-OHdo*qs;^ke&@KgQ z;3)5Lf4(zCSe&h@hc%m&lI^CnFq#>bCiX``z!?tk(E!jH06 z=LM#C8(vx2zkvVfA7L}0M{gwP7ZY#;0L_o_8}^$DJG}$JPAnQndn95YnU_lGFLCnr z1%Xz1C3Y!^Nyvs^rfhY+)7W^zSFRK}QE9v&ijKoM!~!3NMe{vw^7yT9wL97dAF(#| zc1%1U$>Z(_ z=Nn-e-8k-Bh-I>t8DP|7udvR)s?uFZ7c~9`l0SegRq^$~b8!8tffHJnya&sV!vf)N zBWhDzwjT+$N*&68+E4zpJDBMXLQUcM7O`-x2_h39yhXeK$jcE0wK>YAlfZae^d{R+ z>#~AFLy9OXCO69{@k|=;Fq@dU<-6_@F)jxP4fxNv-jV*h_#P*KR8}0XTPoz0LG0qX zu^apc;^mvtzu_1KlKEmY`{oQ!$%ro^tQP&CSAn{`umf;%xyP8N4TcQFJhSX?5u<`M z=I?IULMS*iJrOi4`%u=DdzUpbn81oJ8eL13s(->-Ae%$4c=c0vYwHXyV7l731gELj zFsI1Zqjfk8@42qti@wLON4I#G&ckCfzwMhW?4;KkU=iCqSIOBw#6=S;)!jt8xHqQR z&XnO?axd%~-Nr~v=CIDfax1-)zN2C_Cm2a7>Lv{Go9VxEt>TB+D*>ln{?@m6+OXjr zq55bdZ_y5R_xozM7Iu`nN7_lhnK(qsSREH|xa@mYcZQ>kzQ0U0qR7Ztj^`P^{HQkf zU`{9NU+yx#_V_6>zTQ>ep?4vt3wz(&#Z%e6pNxCpGp^I8LR%rW?_?p$h||o9P@#v+ z*fFXUv${But`Zi}$d8zqXMKtvC}UM~j{X#X3i7BcJEB{jY!o|L4F%9E3}14pSJRV{ zl9c~wGu-*c@ABrBD$j4)x&_s9Rm?@HUBh7({K}!hu6H+@rWEPE*N#2A>GLgBoPV0c zuh^xoF;`i680Gu*w_t&!))KuOoabv%To?I479w0bj3K<~*tiQVUC_3A*cgw}7^J?I zn=`_1z>KeH`r^Z!(4{@lKB^-Ir_s9sJp!j#DEwy2=rzviPMQPOLIxD`Fek$tu+t{+ zW4=?zeK1@nqQ6?VWT)w%mta?kX1SS(gL<~a($4rl(>`~H=GeK_I9-w$3z-GB5PvI* z$Kr4^!D-@73&yMso-dye^LSzjZUh|qIdBwA>L#q{j?@QyIm#LGnN1wOc{oV&x%)*; zUTf5LNrd*ro?+IPLS)i-M+L94-yt&Omf;~+7@!Zj1pZn@=lVa7ZIpD@sJ@rWQ1me!dakCu3E{h>FL%ReP<5oTB3%A7(Nwo|hFwX;r-mQGWel zrcvVG3d6oIBN10#F&2a|qsZCYd%2%rtf>ybmeg-Ez}94i3%UhdEe>32Hf0PlM5ts( zHvpa7nCqtUKn})?y#=F?)Mk%49^zWvO<=N;W(u*s;PHm2rhgdDB;d_hJI#E3UR<-U za4Go29WpD?4;GS3%%AHU2j9y~O-gc_TOez5^m=~P&;|N#!|33vo?0_NBl`;#+*0Zi zT24NHkRxQyh^oCfRJ_QPwe>{;3F4}qKnQAT9hGEo)8lk!pS^JR;(%KVLv(&blR8`< zfJGP9NRw*uGYHmEkz1Mf*k7Ce;pGl;-hf-Ji4UqsdnDc$$VkO!?C%ziD?qhPa$kA$ zOhVlRG;87B8kU;0eI)y5Z<|K(nz}u$oljzXYEX`-;Eoy#utx^}J8~pSc+kO4i||{_Xr}!z{I~ zOS3r4DJY>k99T6aronHZQf0S}irr^sYjY8!vEl13an zb>RsavJJCUiYvkyAnPX_z&B75KEUJw;k7B24FXt^4(BeTz*KTnc)k2L!TeuYIVw2FdEF1FHw=c%eVgG`;eJFbm0F#87s zd`s<9e#gMfN3xJA{wi78``@h?H;%!)XhQ(YJp(tobFGENn~&1^q!luMRQ8LKiy2*3x@ibSo_qKg`YUjW09&9chRCn@$1b9!_&kWP!z>}8AvMQ*=;tGL%+XFh z`@#t}Sr|HL4frI|UF&U7kn27Y`yNEMl{YTKlHGt$0?#fQ1oS3@2XW5cCr!~4rNYjW;the9eyuqk^NzYO~u!02&xBlh+dALHj z{vh@ZmRpeh`+5X3%;Q9#I&tdtV3BLyLS9vXflzCh-Q_U>LbaIT6}lu&nA=J6)xt&#wtd3q5XfdtZObz68YN0Km;wyJ5=D7I?4{mk2#x8yP@TayL zn?HyaR3)~5Nm3KLi{CjpS~XV9Pu1S{uu{>L54toEycje$ThP}p@J$`PPPUBbWZ!`h z@2*(U&cF&y+g!~wh7IrHg0hV4$~39UwR>jXyUT5^q@w=km@R>zUTuRX7#$&}sUALth=a?t(J zmIe$b1a;9JHGeUd)Tj1_=kMW;C9FME11)%XTsgb-6>Sv>jAU5{!aply?{r2l7w=Mx- z9}+YpjBVb7rUazaj$hF2t(q0i+5+%zwwTe#gRk30)wd=*7#ZRFij$P+*V$_+s~kdI zXweib8%zIY8U>q*u*-WGFOVY{#3_=cHdg0DaL6A^2uV_qT8MO*!6ZrsZr{chcGIlB zS8eUi#0%+>XWjC#o45-Y;o(Wf&XX>CfGmi|sxa90IP*LklWy?S_eDHoQU`X$%^SWnvtBf%h@*RywI^v6r%L&ky z^Mhl9u7|;nh*u$SBo34!&s)S1#_to+An=J{kD+|np6+~NRQVX)F{Aq!-2{nD{nd&6 z!cqw-gE}XMI0`Y&UkhCN=5tyQwYWM zSZ4AiwkOrQ2kEcP@IB^a&trG}-DQ<5&t+=gcMYyKemOLJ*(iXs7T2jtSLaq;%G7;7 zBoVUiNb8J+)dl7Srq-8^WZ%HmPb=$4#@+alOIY-ZOgoBHnII>p`xCN3|Lo;9TY78{ zlxdBQ)h)cr-AR(Jgwy0mZF#lmmXdENaJ7-gg$fBMXAT04@ryhp($5CMR}oL)Sj{>9 zn(5F~sVM8J3i0^DpId)=^eXFt=$R(t)HB^4ZNkqmu?DtSKU|!`pN{!EWUpbr$Sok+ z4!<7fhzMuBV2;&U*biEVtZ!6t1Y6XX@ zl~#W1PVDtU4Zr=hp3p4zlT0*eseoE1uk$cTmz!yA^nLrd`U}) zki3reofO=hM1yu(mH)4jn=rhKIsI4NmNa$=RAJv3%vAtm@;GmZ>n%{jdaDgF%*=G` zb3T|~3yWFJ4l=0C9TzjeAGoEscleoRmo_Y{dwRNR|2b;O;7jl&!7RIGywWPFUpk)f zv{vcg@mIj;t4iL&E(=DL6tvoRH~#aSp0L&D1buID^EgabsjQZtQ?=Oa6uG zUtYdW-KYBB_SY)<*O++OJw|q^zQta4UlO?B1*P1@pp>ovzx; z8s9d|E#*~!FQ5~=s%nqyNv*ky7KrWi&GYhLtawVcDSdln5G@|QpvM7!qv?VbRmf?Z zD7n-v2#x!$Z~;DIDKmbgT9*ZHYFH{m{0GBbdAWu9l^PYZV};}1sF$1ZYs`MvZ(&?C z;*|xcQgdcJp^EADR1nCCY~Hv`I7*M=IuDt*CJO{2ozIpo@U?NT8~!?8qK9=SW48wL zmGkP^sJ1#zbN8)+BsaxL#?7Jk@a<2We0xc*!{ACytPSQY^F?R@a>BT^SoDNmYIk!) zT0rd-2$`%$u(S?kOF>;jt~4w_ykT)-N5g7kS6WJjJFoKhvLCDSP32;G>gY30eA)NE z$mB%&pr@R|Nrah{(ZqBg?9i=9kG!i5(lW$B+^ZtBmO=b*r`L6S&f}ZCv zT|7+4oPzx~LN(IwuVLLdIw>)@wLhJeKf9V-i6U1^!! zjCfd#-auHv;hzxx)V!ga2x1uB{O@@eNe}%ZlDlVAH7-Pti*>4{uTKVWaDK$T>EpK5 zq;uPqkUHAyYO%zGM$D);V(fo(NJ4`qscrCeBvvVWGSB2`i^whbD&oV~oBccQ^;64m zFyCQi0ZM5#sx8 z*H-iUmCf`!8(7)M`0}C_9SXsUw+MeCLW|JYtZ0Szz2nHNWQ2G;l(AFcc!IJ!+gtmg zbHRR-wARl%Qodg?e5L>3DUYo%_WUk*t4v7aihstV;dYvG`@%=6)rnI2>*|x^Nveu|Nr>s96-T6DjOz zw9K^3oWL==Y1ayy+7g>KEUnCu3V1Cw)3BVmz?`|j0kZj>@9)nZkN)z(Ip@7z&nLf$ zV*{2{DU183Hz$e*a5Zbt?UvT8;|+HMGTl%yFMit4Ct-M^`+13vB{@nWG8$m^=Nht$ zH6nv)w{sKXP7!^lz*rnMhy6hoGYp5AVT;AdSdUog{bGc^fMh{vt;J1cRP!ox;Y*g) ziWONCWAo)0G`3o^;*g|-LDjY9!>1`xF}+{;uO79CR$>8Ip~pO&NVfze*R?mQ8{uh zv2Y8bKw0o6iQ#&GueZUP>LSbs#-?d4j`hLBn*fagjEq;lT*=*9h(mV5OVd_EU{xD9 zrl{Erj*x}F0$1FgjyE0(a}gjN@l%}IXUDqsZc348_Gqt5n${G=?->0fzcPLA>$-XT z95RLssyik=f2~~;va@-fUAFQsk?6k!6!m-TT8sF>As@D8;cLo(LeB9TVfIc|4ZLf) zKKVx^fFxh}0|S!iVTpuZ=Fl^etsNPDBKRs;Hd|YzwQN-m`mL@w0SC+gP_~DK&zu!$ z(wGGP1f@zVma_KD%kyKGsS5z3*^{)(coC@mxF$Z%G3x64PSMx6htE`>fVcXg$)HlQ z&BnWyEpPLhj0}p93J$w320zvCV*r61T*sY0+(@-_fzE^sieuwR8d?_1Oemk}8EDK; zCQ%oYurb}C+cA`~`7b}|8 z%lro^L%O<{N!%Lu?;_g$f}tgNgf_I{Ik(MQBRMIsFI3Kv6&Bo!Y(`;e;Xeg)$Lg7X zGaqu+B}_}&FO^uY50y=QWCpJlO$cr$YXyeWl!P|%#B{D@jAM8&(`+0aqr(%fuy;Sj z0-UL3orgZP$5ME_<>jb?0c(dE8QCNu10fK^<)zDu|(Qh1vqahm%s;mU?0&ZL^f z+!;vMh}!~G1|7ob7tL-tqJJN4#LHIa8VC~PkGk(vo_MRScP;$rAtX;flQnnD$1SrbI57RR(Ia7m<6-{es zQZ1o}nS{_f-VmfSe7UFDRJEMDcF;~dfY;WiyDxZbat`esy z=(CNtw`gnm+jq4$rof%8 z&Od(Y1Nqg4v176={G$&<`X^E^GoBS7swS~ra6%=@%N|^wsO|b%(2LS~k)SYvlgZg~@|HGzaZe|a zX3e3_tIy_tWoF;UVrAw=5&hbwRnw1QpxB}FDHrb{un7E0!}202&)xq8`LOT_!z3|A zJ!yoig`!Ghek+g!Gp*_f$sD4pVaO%Y7cZW2nw6%nioQI&(x`Qft9OS$BykcG!+bUd zTF8n206sfiVlt3E;KXMP|Ji`QF|}ra$^$rGg%nFreh2q#f_8F(cX=G6>KMyjw@-s; ze$TBd@!%oZT47$HWG5P$cxI^F8T}P7}N?D;sGm4*@14~u9n=rWUzV7u-U6cb+>Z(i0*4(`)R;pB$ z{3PD>gKKQW`$#Bcaw)Zf$*Bk$a6 zlM$m_t_0l&tQjMcnFAjy_Rvbt74?1dGo&#!n(jAAy+#ql^2~vO^VIF=ISua{;%W34 znSSNHo0>iRtK=)h2l-gH`6j52 zGI{(^gW&R+p{bVEA`bhn<9SaGDdUW&y0fQe$2Uh$#pDKe`Gl~Bvjs_-Og`hLD9A@! zJs_F>^_B%w@7C)%DaL1qmT{O;x99|$G$vbhH}5U`+jJ0l)_Wo6>*RBO%bI+N$bp!- zUOTP#9euFiZ(MhSm{*9+p=H)Cfdn4RgW49r(o$S6#7r!^sahCG;SKi9Q+?1a9CTeq zk#?vjrm%uB?HT^s&??e|&YfAJXx zX}TyrZ;l4IEvDAm07+jI*9I?@;iQxMerqB>j!)2`v{Hs!yqQWYLm}BP3Y4-T$@zX- zSalIDl4TQska4>--i?lEBPYfpn<@5KWI};(SaL`&LOomU=oW$(jq1EwHwgm<-L(;p7a7W(~fr=pJ=a5OZ@2Z zP_!wdd%XMQK(2ZHLze{6ZKqc|j%zB~20ZG_50jDucVr;1FA(>r|1wQRv*go9C3N=pT2qv98^Wc<7tVun)4*h<`iR>#ZPE+Dr3sE)EdKizx z_Zp7)V;J#@#*fBmi9a@>a_t&$btA%A^uHhL97}+uV-4#H3m+wQ%&L9}s%0^wxkJjd z4b+`H4|V)_n|LIZHgAmH+uuDIePCTjM9Z^^`97V89>L2N{62S~^gP6ALhshLwUrLF zSiqY!G~<)X88B*YV6}7!X@^SP@& zluPh_TV%eIt=Rh_`AUUKNZj9&7oKJ!$wBcO@A*`$l_RYC3E@FPK8jSCkUfi>rdE z)^QBU)#1s~j&VU4!|%%UWWn(pd+d-xXT^>y8v6UUIozbn<=5k#v)Ka5dU}F@GdD(3 z`2Ww;`VUXIs|=96TiU9BN$)_bcsq2&^Ga=?@zy6;P`U}6CxP3l;P<{rcwW`8*TH(t zNOwKF(KaiSzI*y^yg8C~HEcF15@*>53ijQC&l2v<_T5~`3g+U@Aop&3=IW?+2W3AyfPT(r z@Gy!lj30Q^5tIJWv9%1j;MYHq9pMz1yY2l@**87W}C zg-?qusxg{{5J&AJ6!~lasYrlK)9Reez_;ot3mJc4F3neDAxpt8UG~?(Ftnd)C!R%C z@Xk<5Rdscy&RasBE|Fo>tRZ(%e-aU=RGyunY(CEJKM)V+s>6_bi3X9k=F9n0O+ zol7q91(NaZ8?RY&-xjNjy5D6x&F+dDUVRmlb8Im3I!n6mv290|WX&rz4npVGfLZ5= zz>^3)9ZncxY%#%7Cu{CcmuSlt%+YYxAo19ugAt7MD;8R`tJz4Mk%-3s4N*TPvQ8jc zuAV+8h)^F4HYn_(SLPM!+X!!$O@}(>8uV}SxM;8d8)z?Lry+n*3>R_NcPQ#NRcxSf zH%B3UJW;fY@_6zQ8KrTW%tZj`otiJ-&3~Eb5tS3EyWMDS+C^KvXJ#@D6HiU&9GT4` zK7(_$X`1C7Yf4o~!IyFqM$emO_!zReqc^$}tI$PRLf<0RCXcjqN8W-z;afDwHpG%o z6QTk7c_t{{>BTswsuVGe0k#I=@7>Os6QoD1kD{5uBxkT0tU5YU{%H|7oP#^}Vol(V z#8)2w#wE4q*C}+~4e1UVypLO^bdkmFV5Z*_jq7mltvslmKdO1_PCIT^ZtZ65Q64)Er}zGumE)IejC_`V6;(Ee5j%!#Pvwq3+- zhcnWDVtGF)0ImGeX^%h>`In~b64_e^7=HyL5`$mR=ouc0IV1HL;Zg$aJe;2{pfc8G z6t=Jr0IKCK-ZS^6?DN;eP`?d|dEX1m%7DUQcpCTD@97qyPbM@Vflz~jiC-tWh(tg! zt-Hh|Fcn+t7o!cv`!}tw*RS29Bm0HPC7LaFx1v6b1xyHqt#6fH!VBda;0FdV*QnD8 zcR{Bb$Z+>caB6b9C;>T`SZuDwV>S{pMAxSrSWIJI?Xh29_Wb_y8omFDqFP0y4}`-= zvd?>(nHJVumz(R5x@dtEPF`{WD(gvbnEp#oRoq%-1E@5_Dc7oAx1Por{nY(j$)Ee3 zJ0vnr?>sjp7B{fx8d-U2yt=qv%m8c~9Dfad8w`g!;0`(YsAm0ov*HG3T*_T|gr@|% z!f_-lRr1A3Vf4R?_9d~h;>ATMM9#|D-ol{MWoa->M_p&Lre7F)l2!=3!dxfDA4}5XJ!B=E zjhUKr0|yBgiP5{{e`oj`f>PYA?}kFp$HYO=@MJ|@G;M7MgLu-&0&^K3v6%}AP@EHM zz$gWyc!S@F!?&hKhi_Yd5lM&KmtY263t0pFL#{1EzBYcy3FVP#0}Q=Sp$D6^8+4j> z;rX*dn_rr4Ng>C@P0?so;Zm4zQr2KMlg<~$4wGGEn8GW0>zrYX6eI5&+9mCY4$3<+9B3@<#n7^oXF}wp? zDc3>g1f;BMe}xBT8Kzj^W6At5l3cq|%Jzv1YPsv=PwVfxn{u~kC6iZ1T+m-xTiE_% ze$=-#+eQC&{rhy|*2f9k8YYf3+@I*mLOjdkvk+LX%YKD9K~y$Pg%-}xqn4MP%l+!Y z4}Vj()7mce#)>0{<0HGam-0WWlwCFVu0)ZZT$4i! zYZ_b1=N)!L0pX5)Ck=nD*x3g*_P=f!kTv!$T4`}FKCJW8v6A%R;d-W>FgU*7{^Pi* zns#60N2dKysV85!r)Ihi?aphhx28X4$`j6VI)dN&Nx%8Tzi@rg%UqJDA!`53OOW!H zYrWC}I$jbjI=vONSy`JSI+8qL%a~_hb?YNLe4>n`1V!x1z`7N}HcBs)3a~KMLbiTz z{-y?}x0epFaK7|P%q^0qZv5?@YY31RE%jC&rp@iiGX4vhA*|uiME%U~gHz_Wdd5p-p%v)rqYrmH}93^263~eAI9d**v zpDbi0mDluzdmzek@TlC__Vi5OyF99QQWXp|_EZxsc|d)6YI=2l()N0$}L}nj2rbrgK&berlQ4oQOY+aEH)kbll+Ih{HaX!1J!GuXcMjK!^kkLKNL35 zBl(*9k+`$^f1}4|V_{8OD#DkU#Sm2U03|WO9-mjfiE_AR`dfktdTTOS=$7X6SZM=q z*ikzjY9FvkZ++OnkFTqPe>t5Ry1@?Mf4_XbXMvZ(Hiq98GGda_XC%X7cbyLXzIqL` zZZJAo_Bt_kKIslL5u$-Kk0dv39dUw?l z<~W+<#SVsDQ2b62NHM!l$?sW=_B(oC<-PjoMK3sMg{@<6OH8rwrQ3io)8y)H$|^&W z85j)jqVSC}_vMrVi`UeE&mhm@5q=x61G^%2k(PEhacx1VoK*-_eq+Ac^|) z=QVJ=?VwTGf=hr(d9%EGR*DiW$iJkE{+EpWY~p-r9YWcuFAKnRE{@pR6OwJc0(dO^ z-3+dFZ@h35zMf>ZQs||0d&~|b+a1yQeHx6}R8P;CYO8Cv2y5w%Dk$>lh#jc(SYRtQ z&ht59bEe*cr0kf&yjb+)4F|i-8 zoV=BPmiYHDshAc)IRag}q@&H@DVQoRY~agZ3w7mJ&Q8zB!%RJEs+1I+*$^1gg}5Rt z`>YVHHQsw47<~s_DYv4oZ{wg5a3o2Qcbq6>Z_JvqkE%P=Q?^qmx<^4_@iq=4_UK8A z(=3A_UAEo|@UnXbl4*~W7J&mj7Hil1G$aY7^W#krpp78lt5cs7HH@np8giH7if{|P z+MLd`T&ThZ5&|vd=}kJ+`9FmX$vy^DE7?3-3@q|cO&5Vu<^^{QHT$U9K~eFwA4 z>-T^&hr>y9iRUn07|CysJMG^aJ$OqtdOFrE?t4n?r;9}&our3W+SAQ7>!^-fo{nv z@^`J*zdAk<#Qzn>uWN&-Z=Mfr!q#gZ;sXssE<69tGrlX_8d&Xl1I}k0yV>vXm;$Ri za7cgdr{@!AUT3Xh78+&AH-Hofe^dJ=e%`63w!aFF3b5+rOQm8m11drPgPF)Pxf@mGNOuNkgMh|>4O zJ^uBb^TH{L6*3V-BReUNowifHDjV`4HV5&0zqQ6`Yxv)bPK6Jz)U)Nq@CVwO?ggpl z5C65feIPQM5|#6ipUzGvhcleIl5g>sR#ttQTGA8gPP7POw2d~uhljQA*pD8iqj#Ux z!(kh?;Yjmkjws0brJGabsDad6r_O4bgP2{*bQ zV*#?p7BH?7U|a2W;b5F}Z znv@C0%4eGr{lOt|l2DWk0&(5~ikG}z>_B+BW%mb%O>6tTrYQZ)RdiG-!dNDI#7;p{ z{>$3Ml35HI!-`Emrzt6aXhMOBSb2ur7f^#yh5Q%b!#$jvBM(+q6>4iWL!+WL?ovf) zeAz&9fmZdmT5`HE{V&!{-~ckF|My(@FYq-H&x7dqM}A(#HGdopx?j+nT#z@pZ#_eA z4eU2{WL=dpOKbfTq($qc*?^}Z^6r7~_%a|Ux4x0q#<)U&d7}tIR%TxfP1Gkuz{nrk zT(X8vnUxYsJ;oydGYvyG-T|1cQQp*9#7hs~Frv(}BCD^Jm5ppEiMSp-(>E0q1yI`< zuM?T<-EfEo&m%4eZ~DoES;%1vYDtuDeE+%neWu8-0__ysx4gj|;0Yw5F%tz!k+ssl z>I~9*EN!y!P?e&=wbb5MzN?JzZ zB^T^xhwsFXGw;u8pZFLVCc3_3*O8QHUa4OF%=+^}pF(Pg%1HdCq(B~1Se8E$q%4`E zJ;q$4B#tJFRJF&$@0})gU0OSisqV5ceSuomZs>KWq+Aj>Ge3(r_mS18fB@{Cf_w~o z;ZMe=MyWXuTG)2uzzjonDaI@^PlxwpT_Nnetm@{1L2$&y`P?GOGQ{`7@iNYpXdWy+ zn{eScopa#zE#ip{XKBxTT!@j-ogg(UzG2 zdF$?LP3+G@;YW9Vbbta4^EKz+UXQ=bSzh>5s#1mcs0LKrSuxsaru7s>`PIgJw#$zz zsdqcdX~GeNMim?c6~d7pnzG!rvaX6=(OZzX?1Eh2gneujN=5U@IG~7xnb3RQI+jFU zdU}rcuC$9F&|%Q{0|l^(*`XI8`kT~`Hj`ts0(UlN<-GI9Y&FTEHKa<%VBV8*eV&zS zPrqIltbxQQlh*d)JdIQ-6S%2KThaylhjz}DI8Q1OVaIJL&T<$+b?NKo;0>)Kyn2$s zj3rNq=}wL|lOeyS#YWQ_x}i$VV#JZf%E4yOcY3eS%(xIP5?Ca5t# zRl3-Vj@NPtOWTJ>s(;A~UJP%#-#&h8bnV)Tv978(3R==Mrb0({fX&`mce5j@?cm z4Rt3p1MW3hPMGW3Dx(L7e#@ODkyvqBZ%XA1eS~T)^nrzYfp=m6N?Y}oNs>G4BdXpW zq8xtRyt9z*Mfmj82r+c*bcxZ`z!QLzM8;C1V#C7!6|BzKXjA^c^~OSRC2!Jf6+?2S z{0r|Lc@=8MwWaD25NFbvIliBCRgO%_dhP2c|AdUt&-Z-a)Sl06pLMS8{r9lc(Vu(h ztGg;KoPj0Ej-6CluKUogIscnk$SPCdlvjVyngz0!(W5<_Bi!*yi&5?Rp_S5q)^LI* z3psBx6SiP#;B&g_RpUn|9E`=g*`dBVG8RYFTP^0O$fUUe<$)$hxIE zX%Jq=E^AhF+8U!HQ42Sg56JAPxrt5_-s?}md+tEl9r}xINF#It%qvIu~cfu?)RosA!4PXjx7CR#zon)p-k* zGN@kZ^MZ+h!|VFUtjkr)UjI{OB~H#+$d6$`X(=G)%7$1Dh|1h?0hhHXF!!zo^BIbB zU&2oAj~Gr2fJr*`3$i-&sW}_3l<%RE9U%_@;<*p`N3nVM8B3^U;Jb+OxH}zqu$rsP zver?#k5-UX3xY1_j{jj1&Ro^*s56v3L5r)WofaRsMEQpM(S}5-1FM+puloWcRE|%Y z>ype!LXqf;%4goJ#5t>9oU7l0;GtQF8Mb=j*JJR##xl; zGyl}xo2IuH++Q=%9Zq@iE}+Tb&{WSm$PpC=EQKu9=B?W%S?0hNV|E04>7K3~e0~fo zTn0`kW$Hq!xNk<8mALuNVUPkrr>I1Xr4HRGN3odS_9nj6hZ-f&+5x)k5@@Vp+ z-sEUFaLDi18{A>3*QHDKstPk<3wp60G6|DVG<+ZBfp*{_E@M(x!W_=Da{D0Y`g1pY zTYP`(34+em{k64PjA zLNzbHL=bzxZnm9?sdBx%wMD*uyf29h?7#iWjKWzS1l8D{k=?xS9y1$UqHdW7o^Yt+ zj4ZF(y{zZ;`DO>2!tMQ`Ylk&Q2;vp$p}x7o0^ul#%)$C+C|BcJMp1G$&zL-Jj_f-!; znz5~KCr-#ST*zHGs>N}Q)*`5>rxZht9nCF=^fMH5 z$kR6qF!SuG>HS133GZRqqc0Q~LGa z4QxcY&pnMR-&h=3K=gDD2wZeEIHdSsDTl{dr-+o^4`+0cmlK5A$9SIKZAK?~xn!I4 z@}inlp!z{{F7)@elph z&U3~YYxZL6BOm0>Y0-WX{m*yM5bYZaYl=wT>UmH7L7vINFGqwZ*ACSC^)nD_v9TdB zjp#WUoyFP>IdWyXp#YUNX~)q?4@NbwY1ud+=QBt0;A5upKvt2=)Y;d#O;ZGd#_SyDkxvlJ!ISvoRwt zn=BCGuChfUJ8IuOSAlY^O`w6TV`gZ=T`wxJO1lg{c*gvql%JZoKSaa$#riCgTR`G< zcu&~d(zQ9M3vcu=zvFvV@pg zj*Egd($K>>!M-bVRMz%}w+Cl4s>mxPtXrCQKhb|ZPiSXEPY_#0I~5z0K2D6b%oybf z&Lexph1_EJQVJCDh8r@7P!=#(O~K*{EO=w0W!hl;f#qlO4*j)jy=^^a+xp!9Tvw9@ z6a%exri2yHVlH;@^rxMHOZ(zVSVuLdyw>Y(-}aEjS2tDNYTSZXhf4Mod%JjTrI&TE z`lpLe4!iGKre^+;w>t3-N_WiTzJKL*q_pOQAwKQ-LxC~3Cq!G=GH;pR7;cR=bbD+I zwf13TJxwV~8R+)vDbXv{wo?j6pJ|^JYK!lW+9a~`TY(eRxPBlc;06FQ`~XkSclYL% z#-r?EgeWwMh@`|0L4VHLBI|fv5Dm#`#9*7ehP}$z2EGsb4Fr@~m;Ct$0kXa)|8$5V zMBO`lA)ZPf`p+E&KDS1Opc~w^L)pPMnzdB^tUfymQYPJdl@ONs39Cu5Uufr{JP-HX z0Y9sgrA}s4jxFfL$>>L|RIj6DB`yFqX-hb%&X`uJGe`)lne~hL!0=AKz!CTpuw9zd z+C~+wHwlz_BcU>Q?1*AIke7(pJ9mT^M9!Elb5~qkC^`cmMK|p2$-By^@B?1G+5=lG zai>=I4;z1|$UGp^exNZ&3$YXID6tkxrSvV4e*G%Q^VzBXmwx2lu_3=rfMa`v+TJqC zy{5gPJ~zhvu<0Sc^=-vU>~~8+g)NXh{%HQiIO9p@6E#K_qQdHX0%wW2TMCZk4Z@YF zGq%(xue~VecArSEB79cDYu`KgMyCTTB6Pp~a0XtSdWYRc{!FUVm|{~ZcUs#50^XJi zi2Joy0|HL{dhhTixMAVd)M%4@tKN6BvGs*>L$tLUVhfus=Aa#6ig!#t2P^pHxTsp?k6ToWuQpK%Riede(+9p_5G6Dd70 z7*aoj$_*F(`=(ubEh;P{aGDtLnsj}dIM{CP%YQ-7kAn)+z08msMrS;c)ryAiU);CY z&bB!EI$d2<$%dHzgr|Kse^$00?@(F11wZFy-lHih!sveX9dhG2ytz~%(E{oo@M`yp z3~x)078FX9&26e{uF|r7l=nW@)c5G;e-@5>*sa+(aZs64Jv@;1lDSxZ^mgo$9wPds zV^)#hOi{|jgyD|VZK+N}c_1bWZDNfS=+XoVQFTueb+mSH#xR82u6-4q>XDH(Jc`5t zT`Y*mHmcv@{ODTL*M`mL%%8B@UWE7~l^z50Yi4FFzZP|NX;gmtdwy!~{@xEeqR2#$ zF1a;_A9v-^0gSJxBCeRt+Rw`8ShM|_m(Y7-YhnbLD1@W=yOd@I`fD^nrOdZMOyavk zP1^M4Cs#Hl$}V&dqzX(&(~u(%U$V5%n8s@ckkIMCm6wXoW z^A2kmd$54FF?H`c{NR(j7!GhdArt(Vs+eM(H?BWAlj=E7H>=_-8b9Q6n;T_^CK#uD z!Wg?rN1?E*kvFanKN{UVtJ*l?t8uR<-NZo;e+=3J^DLinOW>b+yh-Y*{7jx2;t#e_ z_;cIVALC6X%z7)w$nxI}%t$e32bcj^Gj+h9u&TKemvHU#%(Y^S^oy17B6TCOMsbEo zX`}p5KA0t*4RQb+y|ImgqQ~NhR|VyiZ@nj+(q%=PfTu|topOvPz)%gqF_bMjEsu9* zE2!3Hy;j212tA$;YzGM)nKdhS3MuFtYT6tsLND^*je zkJCL4?;uamzmRAFT+qbKGWb6;leRKmr&8AgG%iPt9hTxB6VEy^ip=SCP+8?f0d)vc z$X%tHsH7|fJD6qR zrPdjW6qN(jguvuxpVoNwRLw+6Y!9`WJ88P%tR-eCkOkDaiaz{n#52Q`=M<;Tk0h({v$9la~>^0iS$#HrOgN>U+_~PWwPb;Q4HJh`B9Y$ zRt(vJ{jvU#r_#%;5sJY>pCO^{R7is*uKI}+{og1m$u#RmVENkqw;0MIm}H+}mzSq= zFQ?7Y18<%Z$`6` zleMPzqTYIO7Ap@Fhxad11~GK^H9xu*d)8